git: 7fd2c91a291b - main - ping: Simplify protocol selection.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Tue, 10 Oct 2023 22:48:01 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=7fd2c91a291bd518e012b438d6ca6fdd04d39dbf

commit 7fd2c91a291bd518e012b438d6ca6fdd04d39dbf
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2023-10-10 22:47:46 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2023-10-10 22:47:59 +0000

    ping: Simplify protocol selection.
    
    * Interrupt the option loop as soon as we have an indication of which
      protocol is intended.
    * If we end up having to perform a DNS lookup, loop over the entire
      result looking for either IPv4 or IPv6 addresses.
    
    Sponsored by:   NetApp, Inc.
    Sponsored by:   Klara, Inc.
    Reviewed by:    rscheff, kevans, allanjude
    Differential Revision:  https://reviews.freebsd.org/D42137
---
 sbin/ping/main.c             | 125 +++++++++++++++++++------------------------
 sbin/ping/tests/ping_test.sh |  26 ++++++---
 2 files changed, 76 insertions(+), 75 deletions(-)

diff --git a/sbin/ping/main.c b/sbin/ping/main.c
index 6321178e1228..e07b30952199 100644
--- a/sbin/ping/main.c
+++ b/sbin/ping/main.c
@@ -62,112 +62,99 @@
 int
 main(int argc, char *argv[])
 {
-#if defined(INET) && defined(INET6)
+#if defined(INET)
 	struct in_addr a;
-	struct in6_addr a6;
 #endif
-#if defined(INET) || defined(INET6)
-	struct addrinfo hints;
+#if defined(INET6)
+	struct in6_addr a6;
 #endif
-	int ch;
-#ifdef INET
-	bool ipv4 = false;
+#if defined(INET) && defined(INET6)
+	struct addrinfo hints, *res, *ai;
+	int error;
 #endif
-#ifdef INET6
-	bool ipv6 = false;
+	int opt;
 
+#ifdef INET6
 	if (strcmp(getprogname(), "ping6") == 0)
-		ipv6 = true;
+		return ping6(argc, argv);
 #endif
 
-	while ((ch = getopt(argc, argv, ":" OPTSTR)) != -1) {
-		switch(ch) {
+	while ((opt = getopt(argc, argv, ":" OPTSTR)) != -1) {
+		switch (opt) {
 #ifdef INET
 		case '4':
-			ipv4 = true;
-			break;
+			goto ping4;
 #endif
 #ifdef INET6
 		case '6':
-			ipv6 = true;
-			break;
+			goto ping6;
 #endif
-#if defined(INET) && defined(INET6)
 		case 'S':
 			/*
 			 * If -S is given with a numeric parameter,
 			 * force use of the corresponding version.
 			 */
+#ifdef INET
 			if (inet_pton(AF_INET, optarg, &a) == 1)
-				ipv4 = true;
-			else if (inet_pton(AF_INET6, optarg, &a6) == 1)
-				ipv6 = true;
-			break;
+				goto ping4;
+#endif
+#ifdef INET6
+			if (inet_pton(AF_INET6, optarg, &a6) == 1)
+				goto ping6;
 #endif
+			break;
 		default:
 			break;
 		}
 	}
 
+	/*
+	 * For IPv4, only one positional argument, the target, is allowed.
+	 * For IPv6, multiple positional argument are allowed; the last
+	 * one is the target, and preceding ones are intermediate hops.
+	 * This nuance is lost here, but the only case where it matters is
+	 * an error.
+	 */
 	if (optind >= argc)
 		usage();
 
-	optreset = 1;
-	optind = 1;
 #if defined(INET) && defined(INET6)
-	if (ipv4 && ipv6)
-		errx(1, "-4 and -6 cannot be used simultaneously");
-#endif
-
-#if defined(INET) && defined(INET6)
-	if (inet_pton(AF_INET, argv[argc - 1], &a) == 1) {
-		if (ipv6)
-			errx(1, "IPv6 requested but IPv4 target address "
-			    "provided");
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_socktype = SOCK_RAW;
+	if (feature_present("inet") && !feature_present("inet6"))
 		hints.ai_family = AF_INET;
-	}
-	else if (inet_pton(AF_INET6, argv[argc - 1], &a6) == 1) {
-		if (ipv4)
-			errx(1, "IPv4 requested but IPv6 target address "
-			    "provided");
-		hints.ai_family = AF_INET6;
-	} else if (ipv6)
+	if (feature_present("inet6") && !feature_present("inet"))
 		hints.ai_family = AF_INET6;
-	else if (ipv4)
-		hints.ai_family = AF_INET;
-	else {
-		if (!feature_present("inet6"))
-			hints.ai_family = AF_INET;
-		else if (!feature_present("inet"))
-			hints.ai_family = AF_INET6;
-		else {
-			struct addrinfo *res;
-
-			memset(&hints, 0, sizeof(hints));
-			hints.ai_socktype = SOCK_RAW;
-			hints.ai_family = AF_UNSPEC;
-			getaddrinfo(argv[argc - 1], NULL, &hints, &res);
-			if (res != NULL) {
-				hints.ai_family = res[0].ai_family;
-				freeaddrinfo(res);
-			}
+	else
+		hints.ai_family = AF_UNSPEC;
+	error = getaddrinfo(argv[argc - 1], NULL, &hints, &res);
+	if (res == NULL)
+		errx(1, "%s", gai_strerror(error));
+	for (ai = res; ai != NULL; ai = ai->ai_next) {
+		if (ai->ai_family == AF_INET) {
+			freeaddrinfo(res);
+			goto ping4;
+		}
+		if (ai->ai_family == AF_INET6) {
+			freeaddrinfo(res);
+			goto ping6;
 		}
 	}
-#elif defined(INET)
-	hints.ai_family = AF_INET;
-#elif defined(INET6)
-	hints.ai_family = AF_INET6;
+	freeaddrinfo(res);
+	errx(1, "Unknown host");
 #endif
-
 #ifdef INET
-	if (hints.ai_family == AF_INET)
-		return ping(argc, argv);
-#endif /* INET */
+ping4:
+	optreset = 1;
+	optind = 1;
+	return ping(argc, argv);
+#endif
 #ifdef INET6
-	if (hints.ai_family == AF_INET6)
-		return ping6(argc, argv);
-#endif /* INET6 */
-	errx(1, "Unknown host");
+ping6:
+	optreset = 1;
+	optind = 1;
+	return ping6(argc, argv);
+#endif
 }
 
 void
diff --git a/sbin/ping/tests/ping_test.sh b/sbin/ping/tests/ping_test.sh
index 4a2dda0ebcce..1dff25bd43a6 100644
--- a/sbin/ping/tests/ping_test.sh
+++ b/sbin/ping/tests/ping_test.sh
@@ -135,21 +135,34 @@ ping_46_body()
 	require_ipv4
 	require_ipv6
 	atf_check -s exit:1 \
-	    -e match:"-4 and -6 cannot be used simultaneously" \
+	    -e match:"illegal option -- 6" \
 	    ping -4 -6 localhost
 }
 
-ping6_46_head()
+ping_64_head()
 {
 	atf_set "descr" "-4 and -6 cannot be used simultaneously"
 }
-ping6_46_body()
+ping_64_body()
 {
 	require_ipv4
 	require_ipv6
 	atf_check -s exit:1 \
-	    -e match:"-4 and -6 cannot be used simultaneously" \
-	    ping6 -4 -6 localhost
+	    -e match:"illegal option -- 4" \
+	    ping -6 -4 localhost
+}
+
+ping6_4_head()
+{
+	atf_set "descr" "ping6 does not accept -4"
+}
+ping6_4_body()
+{
+	require_ipv4
+	require_ipv6
+	atf_check -s exit:1 \
+	    -e match:"illegal option -- 4" \
+	    ping6 -4 localhost
 }
 
 atf_test_case "inject_opts" "cleanup"
@@ -212,7 +225,8 @@ atf_init_test_cases()
 	atf_add_test_case ping_c1t6
 	atf_add_test_case ping6_c1t4
 	atf_add_test_case ping_46
-	atf_add_test_case ping6_46
+	atf_add_test_case ping_64
+	atf_add_test_case ping6_4
 	atf_add_test_case inject_opts
 	atf_add_test_case inject_pip
 	atf_add_test_case inject_reply