From 00528758a49dcf3f87f3c7d5a65faa94b211b3e4 Mon Sep 17 00:00:00 2001
From: Erik Auerswald <auerswal@unix-ag.uni-kl.de>
Date: Sun, 27 Mar 2022 19:52:05 +0200
Subject: [PATCH 4/8] check address family consistency in generator

Before, "fping -6 -g IPv4_ADDR_SPEC" would generate all requested
IPv4 addresses and try to add them to the host table, but that
would fail because of address family mismatch.

Now, "fping -6 -g IPv4_ADDR_SPEC" detects the problem before
generating any addresses, and fails early.

Mixing IPv4 and IPv6 addresses when using -g START END is detected
before generating any addresses and fails early, too.

Add tests for the new behavior:

* check combination of -4 and -g
* detect mismatch between -4/-6 and -g address specification
* check error message when attempting to use -g for IPv6
* check combining IPv4 and IPv6 in -g START END
---
 ci/test-06-options-f-h.pl | 129 +++++++++++++++++++++++++++++++++++++-
 src/fping.c               |   7 ++-
 2 files changed, 132 insertions(+), 4 deletions(-)

diff --git a/ci/test-06-options-f-h.pl b/ci/test-06-options-f-h.pl
index 2bfde36..70f04f7 100755
--- a/ci/test-06-options-f-h.pl
+++ b/ci/test-06-options-f-h.pl
@@ -1,6 +1,7 @@
 #!/usr/bin/perl -w
 
-use Test::Command tests => 48;
+use Test::Command tests => 84;
+use Test::More;
 use File::Temp;
 
 #  -f file    read list of targets from a file ( - means stdin) (only if no -g specified)
@@ -84,6 +85,80 @@ $cmd->stdout_is_eq("");
 $cmd->stderr_is_eq("fping: -g parameter generates too many addresses\n");
 }
 
+# fping -4 -g (range)
+{
+my $cmd = Test::Command->new(cmd => "fping -4 -g 127.0.0.1 127.0.0.1");
+$cmd->exit_is_num(0);
+$cmd->stdout_is_eq("127.0.0.1 is alive\n");
+$cmd->stderr_is_eq("");
+}
+
+# fping -4 -g (range, wrong address family)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -4 -g ::1 ::1");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_like(qr{can't parse address ::1:.*(not supported|not known)});
+}
+
+# fping -6 -g (range, wrong address family)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -6 -g 127.0.0.1 127.0.0.1");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_like(qr{can't parse address 127\.0\.0\.1:.*(not supported|not known)});
+}
+
+# fping -g (range - no IPv6 generator)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -6 -g ::1 ::1");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
+}
+
+# fping -6 -g (range - no IPv6 generator)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -g ::1 ::1");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
+}
+
+# fping -g (range - mixed address families: start IPv4, end IPv6)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -g 127.0.0.1 ::1");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_like(qr{can't parse address ::1:.*(not supported|not known)});
+}
+
+# fping -g (range - mixed address families: start IPv6, end IPv4)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -g ::1 127.0.0.1");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
+}
+
 # fping -g (cidr)
 {
 my $cmd = Test::Command->new(cmd => "fping -g 127.0.0.1/30");
@@ -132,6 +207,58 @@ $cmd->stdout_is_eq("");
 $cmd->stderr_is_eq("fping: -g parameter generates too many addresses\n");
 }
 
+# fping -4 -g (cidr)
+{
+my $cmd = Test::Command->new(cmd => "fping -4 -g 127.0.0.1/32");
+$cmd->exit_is_num(0);
+$cmd->stdout_is_eq("127.0.0.1 is alive\n");
+$cmd->stderr_is_eq("");
+}
+
+# fping -4 -g (cidr, wrong address family)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -4 -g ::1/128");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_like(qr{can't parse address ::1:.*(not supported|not known)});
+}
+
+# fping -6 -g (cidr, wrong address family)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -6 -g 127.0.0.1/32");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_like(qr{can't parse address 127\.0\.0\.1:.*(not supported|not known)});
+}
+
+# fping -g (cidr - no IPv6 generator)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -g ::1/128");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
+}
+
+# fping -6 -g (cidr - no IPv6 generator)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -6 -g ::1/128");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
+}
+
 # fping -H
 {
 my $cmd = Test::Command->new(cmd => "fping -H 1 127.0.0.1");
diff --git a/src/fping.c b/src/fping.c
index 2e69f2b..94735e9 100644
--- a/src/fping.c
+++ b/src/fping.c
@@ -1195,7 +1195,7 @@ void add_cidr(char* addr)
 
     /* parse address (IPv4 only) */
     memset(&addr_hints, 0, sizeof(struct addrinfo));
-    addr_hints.ai_family = AF_UNSPEC;
+    addr_hints.ai_family = hints_ai_family;
     addr_hints.ai_flags = AI_NUMERICHOST;
     ret = getaddrinfo(addr, NULL, &addr_hints, &addr_res);
     if (ret) {
@@ -1241,7 +1241,7 @@ void add_range(char* start, char* end)
 
     /* parse start address (IPv4 only) */
     memset(&addr_hints, 0, sizeof(struct addrinfo));
-    addr_hints.ai_family = AF_UNSPEC;
+    addr_hints.ai_family = hints_ai_family;
     addr_hints.ai_flags = AI_NUMERICHOST;
     ret = getaddrinfo(start, NULL, &addr_hints, &addr_res);
     if (ret) {
@@ -1253,11 +1253,12 @@ void add_range(char* start, char* end)
         fprintf(stderr, "%s: -g works only with IPv4 addresses\n", prog);
         exit(1);
     }
+    hints_ai_family = addr_res->ai_family;
     start_long = ntohl(((struct sockaddr_in*)addr_res->ai_addr)->sin_addr.s_addr);
 
     /* parse end address (IPv4 only) */
     memset(&addr_hints, 0, sizeof(struct addrinfo));
-    addr_hints.ai_family = AF_UNSPEC;
+    addr_hints.ai_family = hints_ai_family;
     addr_hints.ai_flags = AI_NUMERICHOST;
     ret = getaddrinfo(end, NULL, &addr_hints, &addr_res);
     if (ret) {
-- 
2.25.1

