]> git.gsnw.org Git - fping.git/commitdiff
IPv6 support for -g, --generate
authorErik Auerswald <auerswal@unix-ag.uni-kl.de>
Wed, 15 Jan 2025 16:42:12 +0000 (17:42 +0100)
committerErik Auerswald <auerswal@unix-ag.uni-kl.de>
Sat, 15 Feb 2025 19:02:58 +0000 (20:02 +0100)
The limit for generated addresses is increased by two to
allow /111 IPv6 prefixes similar to /15 IPv4 prefixes.

The implementation uses two unsigned 64 bit integers to
represent a single IPv6 address.  The required arithmetic
is open-coded and limited to what is needed to implement
IPv6 support for -g, --generate.

This addresses GitHub issue #59.

CHANGELOG.md
ci/test-06-options-f-h.pl
ci/test-issue-58.pl
doc/fping.pod
src/fping.c

index 6f92dbac1f33ee78b410cd68e4798226c04e3248..2049f15dc7b146764b110df47669a35614a277e1 100644 (file)
@@ -6,6 +6,11 @@ Next
 - Fix fallback to SO\_TIMESTAMP if SO\_TIMESTAMPNS is not available (#375, thanks
   @auerswal)
 
+## New features
+
+- The -g, --generate option now also supports IPv6 addresses (#376,
+  thanks @auerswal)
+
 fping 5.3 (2025-01-02)
 ======================
 
index a354ae58977631c8258849d242fa5d76eb9ec039..4229efe2cce45d18522fe1f3bae09927a811399b 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 
-use Test::Command tests => 102;
+use Test::Command tests => 146;
 use Test::More;
 use File::Temp;
 
@@ -208,18 +208,18 @@ SKIP: {
     $cmd->stderr_like(qr{can't parse address 127\.0\.0\.1:.*(not supported|not known)});
 }
 
-# fping -g (range - no IPv6 generator)
+# fping -g (range - IPv6)
 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");
+    $cmd->exit_is_num(0);
+    $cmd->stdout_is_eq("::1 is alive\n");
+    $cmd->stderr_is_eq("");
 }
 
-# fping -g (empty range - no IPv6 generator)
+# fping -g (empty range - IPv6)
 SKIP: {
     if($ENV{SKIP_IPV6}) {
         skip 'Skip IPv6 tests', 3;
@@ -227,21 +227,118 @@ SKIP: {
     my $cmd = Test::Command->new(cmd => "fping -g ::1 ::");
     $cmd->exit_is_num(1);
     $cmd->stdout_is_eq("");
-    $cmd->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
+    $cmd->stderr_is_eq("");
+}
+
+# fping -g (empty range - IPv6 - crossing 64 bit boundary)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 2;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -g 2001:db8:0:2:: 2001:db8:0:1:ffff:ffff:ffff:ffff");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
 }
 
-# fping -6 -g (range - no IPv6 generator)
+# fping -6 -g (range - IPv6)
 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(0);
+    $cmd->stdout_is_eq("::1 is alive\n");
+    $cmd->stderr_is_eq("");
+}
+
+# fping -g (range - scoped IPv6)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 1;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -i1 -t10 -r0 -g fe80::47%2 fe80::48%2");
+    $cmd->stdout_like(qr{fe80::47%2 is (alive|unreachable)\nfe80::48%2 is (alive|unreachable)\n});
+}
+
+# fping -g (range - scoped IPv6 - only start address is scoped)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -i1 -t10 -r0 -g fe80::47%2 fe80::48");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: different scopes for start and end addresses\n");
+}
+
+# fping -g (range - scoped IPv6 - only end address is scoped)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -i1 -t10 -r0 -g fe80::47 fe80::48%2");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: different scopes for start and end addresses\n");
+}
+
+# fping -g (range - inconsistently scoped IPv6)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -i1 -t10 -r0 -g fe80::47%2 fe80::48%3");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: different scopes for start and end addresses\n");
+}
+
+# fping -g (range - unreachable documentation addresses)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 2;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -t10 -r0 -i1 -g 2001:db8:1:2:3:4:5:6 2001:db8:1:2:3:4:5:7");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("2001:db8:1:2:3:4:5:6 is unreachable\n2001:db8:1:2:3:4:5:7 is unreachable\n");
+}
+
+# fping -g (range - unreachable documentation addresses - crossing 64 bit boundary)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 2;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -t10 -i1 -r0 -g 2001:db8:1:2:ffff:ffff:ffff:fffe 2001:db8:1:3::1");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("2001:db8:1:2:ffff:ffff:ffff:fffe is unreachable
+2001:db8:1:2:ffff:ffff:ffff:ffff is unreachable
+2001:db8:1:3:: is unreachable
+2001:db8:1:3::1 is unreachable\n");
+}
+
+# fping -g (range - too many addresses - lower 64 bit)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -t10 -i1 -r0 -g 2001:db8:1:2:3:4:0:1 2001:db8:1:2:3:4:ff:ffff");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: -g parameter generates too many addresses\n");
+}
+
+# fping -g (range - too many addresses - upper 64 bit)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -t10 -i1 -r0 -g 2001:db8:1:2::1 2001:db8:1:3::1");
     $cmd->exit_is_num(1);
     $cmd->stdout_is_eq("");
-    $cmd->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
+    $cmd->stderr_is_eq("fping: -g parameter generates too many addresses\n");
 }
 
-# fping -6 -g (range - no IPv6 generator - start address IPv6)
+# fping -6 -g (range - mixed address families - start address IPv6)
 SKIP: {
     if($ENV{SKIP_IPV6}) {
         skip 'Skip IPv6 tests', 3;
@@ -249,10 +346,10 @@ SKIP: {
     my $cmd = Test::Command->new(cmd => "fping -6 -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");
+    $cmd->stderr_like(qr{fping: can't parse address 127\.0\.0\.1: .*\n});
 }
 
-# fping -g (range - no IPv6 generator - end address IPv6)
+# fping -g (range - mixed address families - end address IPv6)
 SKIP: {
     if($ENV{SKIP_IPV6}) {
         skip 'Skip IPv6 tests', 3;
@@ -263,7 +360,7 @@ SKIP: {
     $cmd->stderr_like(qr{fping: can't parse address ::1: .*\n});
 }
 
-# fping -6 -g (range - no IPv6 generator - end address IPv6)
+# fping -6 -g (range - mixed address families - end address IPv6)
 SKIP: {
     if($ENV{SKIP_IPV6}) {
         skip 'Skip IPv6 tests', 3;
@@ -296,26 +393,123 @@ SKIP: {
     $cmd->stderr_like(qr{can't parse address 127\.0\.0\.1:.*(not supported|not known)});
 }
 
-# fping -g (CIDR - no IPv6 generator)
+# fping -g (CIDR - IPv6)
 SKIP: {
     if($ENV{SKIP_IPV6}) {
         skip 'Skip IPv6 tests', 3;
     }
     my $cmd = Test::Command->new(cmd => "fping -g ::1/128");
+    $cmd->exit_is_num(0);
+    $cmd->stdout_is_eq("::1 is alive\n");
+    $cmd->stderr_is_eq("");
+}
+
+# fping -6 -g (CIDR - IPv6)
+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(0);
+    $cmd->stdout_is_eq("::1 is alive\n");
+    $cmd->stderr_is_eq("");
+}
+
+# fping -g (CIDR - scoped IPv6)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 1;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -t10 -r0 -g fe80::4:3:2:1%2/128");
+    $cmd->stdout_like(qr{fe80::4:3:2:1%2 is (alive|unreachable)\n});
+}
+
+# fping -g (CIDR - scoped IPv6 - wrong syntax)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -t10 -r0 -g fe80::4:3:2:1/128%2");
     $cmd->exit_is_num(1);
     $cmd->stdout_is_eq("");
-    $cmd->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
+    $cmd->stderr_is_eq("fping: address scope must precede prefix length\n");
+}
+
+# fping -g (CIDR - IPv6 - unreachable documentation addresses - host prefix)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 2;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -t10 -r0 -g 2001:db8:abcd:1234:5678:9098:7654:4321/128");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("2001:db8:abcd:1234:5678:9098:7654:4321 is unreachable\n");
+}
+
+# fping -g (CIDR - IPv6 - unreachable documentation addresses - point-to-point prefix)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 2;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -t10 -r0 -g 2001:db8:abcd:1234:5678:9098:7654:4320/127");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("2001:db8:abcd:1234:5678:9098:7654:4320 is unreachable\n2001:db8:abcd:1234:5678:9098:7654:4321 is unreachable\n");
 }
 
-# fping -6 -g (CIDR - no IPv6 generator)
+# fping -g (CIDR - IPv6 - unreachable documentation addresses - normal prefix)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 2;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -t10 -i1 -r0 -g 2001:db8:abcd:1234:5678:9098:7654:4320/126");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("2001:db8:abcd:1234:5678:9098:7654:4320 is unreachable
+2001:db8:abcd:1234:5678:9098:7654:4321 is unreachable
+2001:db8:abcd:1234:5678:9098:7654:4322 is unreachable
+2001:db8:abcd:1234:5678:9098:7654:4323 is unreachable\n");
+}
+
+# fping -g (CIDR - IPv6 - prefix too short)
 SKIP: {
     if($ENV{SKIP_IPV6}) {
         skip 'Skip IPv6 tests', 3;
     }
-    my $cmd = Test::Command->new(cmd => "fping -6 -g ::1/128");
+    my $cmd = Test::Command->new(cmd => "fping -g 2001:db8::/64");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: netmask must be between 65 and 128 (is: 64)\n");
+}
+
+# fping -g (CIDR - IPv6 - too many addresses)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -g 2001:db8::/65");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: -g parameter generates too many addresses\n");
+}
+
+# fping -g (CIDR - IPv6 - too many addresses)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -g 2001:db8::/104");
+    $cmd->exit_is_num(1);
+    $cmd->stdout_is_eq("");
+    $cmd->stderr_is_eq("fping: -g parameter generates too many addresses\n");
+}
+
+# fping -g (CIDR - IPv6 - prefix too long)
+SKIP: {
+    if($ENV{SKIP_IPV6}) {
+        skip 'Skip IPv6 tests', 3;
+    }
+    my $cmd = Test::Command->new(cmd => "fping -g 2001:db8::/129");
     $cmd->exit_is_num(1);
     $cmd->stdout_is_eq("");
-    $cmd->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
+    $cmd->stderr_is_eq("fping: netmask must be between 65 and 128 (is: 129)\n");
 }
 
 # fping -H
index db5161a19a8a098364268fc5ea1c9c0a5065a830..3eacfc54368ab16cf14dc31908680437592b1e05 100755 (executable)
@@ -7,4 +7,4 @@ use Test::Command tests => 3;
 my $cmd1 = Test::Command->new(cmd => "fping -a -g 2001:db8:120:4161::4/64");
 $cmd1->exit_is_num(1);
 $cmd1->stdout_is_eq("");
-$cmd1->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
+$cmd1->stderr_is_eq("fping: netmask must be between 65 and 128 (is: 64)\n");
index 77092250f68770347ef5043c9a77fef5265eb2b1..2bb9330d9face1845d890d3974dbf1dfd61edaca 100644 (file)
@@ -122,9 +122,11 @@ Read list of targets from a file.
 
 Generate a target list from a supplied IP netmask, or a starting and ending IP.
 Specify the netmask or start/end in the targets portion of the command line. If
-a network with netmask is given, the network and broadcast addresses will be
-excluded. ex. To ping the network 192.168.1.0/24, the specified command line
-could look like either:
+an IPv4 network with netmask is given, the network and broadcast addresses will
+be excluded.
+
+Example: To ping the network 192.168.1.0/24, the specified command line could
+look like either:
 
  $ fping -g 192.168.1.0/24
 
@@ -322,8 +324,8 @@ line arguments, and 4 for a system call failure.
 =head1 RESTRICTIONS
 
 The number of addresses that can be generated using the C<-g>, C<--generate>
-option is limited to 131070 (the number of host addresses in one 15-bit IPv4
-prefix).
+option is limited to 131072 (the number of host addresses in one 111-bit IPv6
+prefix, two addresses more than the host addresses in one 15-bit IPv4 prefix).
 
 If fping was configured with C<--enable-safe-limits>, the following values are
 not allowed for non-root users:
index edabdfa7a576371340bd4e5dffa84d58d9d2019e..cc1c339f0210f4dce4e12c3eddd98523af484c61 100644 (file)
@@ -139,7 +139,7 @@ extern int h_errno;
 #define SIZE_ICMP_HDR 8 /* from ip_icmp.h */
 #define MAX_PING_DATA (MAX_IP_PACKET - SIZE_IP_HDR - SIZE_ICMP_HDR)
 
-#define MAX_GENERATE 131070 /* maximum number of hosts that -g can generate */
+#define MAX_GENERATE 131072 /* maximum number of hosts that -g can generate */
 
 /* sized so as to be like traditional ping */
 #define DEFAULT_PING_DATA_SIZE 56
@@ -404,6 +404,12 @@ void add_cidr(char *);
 void add_cidr_ipv4(unsigned long, unsigned long);
 void add_range(char *, char *);
 void add_addr_range_ipv4(unsigned long, unsigned long);
+#ifdef IPV6
+uint64_t be_octets_to_uint64(uint8_t*);
+void uint64_to_be_octets(uint64_t, uint8_t*);
+void add_cidr_ipv6(uint64_t, uint64_t, unsigned long, const char *);
+void add_addr_range_ipv6(uint64_t, uint64_t, uint64_t, uint64_t, const char *);
+#endif
 void print_warning(char *fmt, ...);
 int addr_cmp(struct sockaddr *a, struct sockaddr *b);
 void host_add_ping_event(HOST_ENTRY *h, int index, int64_t ev_time);
@@ -1302,17 +1308,31 @@ void add_cidr(char *addr)
     struct addrinfo addr_hints;
     struct addrinfo *addr_res;
     unsigned long net_addr;
+#ifdef IPV6
+    uint64_t net_upper, net_lower;
+    char *scope_str;
+#endif /* IPV6 */
 
     /* Split address from mask */
-    addr_end = strchr(addr, '/');
+    addr_end = strrchr(addr, '/');
     if (addr_end == NULL) {
         usage(1);
     }
-    *addr_end = '\0';
     mask_str = addr_end + 1;
+
+#ifdef IPV6
+    /* IPv6 addresses can have a scope */
+    scope_str = strchr(addr, '%');
+    if (scope_str && mask_str < scope_str) {
+        fprintf(stderr, "%s: address scope must precede prefix length\n", prog);
+        exit(1);
+    }
+#endif /*IPV6 */
+
+    *addr_end = '\0';
     mask = atoi(mask_str);
 
-    /* parse address (IPv4 only) */
+    /* parse address */
     memset(&addr_hints, 0, sizeof(struct addrinfo));
     addr_hints.ai_family = hints_ai_family;
     addr_hints.ai_flags = AI_NUMERICHOST;
@@ -1325,9 +1345,17 @@ void add_cidr(char *addr)
         net_addr = ntohl(((struct sockaddr_in*)addr_res->ai_addr)->sin_addr.s_addr);
         freeaddrinfo(addr_res);
         add_cidr_ipv4(net_addr, mask);
+#ifdef IPV6
+    } else if (addr_res->ai_family == AF_INET6) {
+        uint8_t *ipv6_addr = ((struct sockaddr_in6*)addr_res->ai_addr)->sin6_addr.s6_addr;
+        net_upper = be_octets_to_uint64(ipv6_addr);
+        net_lower = be_octets_to_uint64(ipv6_addr + 8);
+        freeaddrinfo(addr_res);
+        add_cidr_ipv6(net_upper, net_lower, mask, scope_str);
+#endif /* IPV6 */
     } else {
         freeaddrinfo(addr_res);
-        fprintf(stderr, "%s: -g works only with IPv4 addresses\n", prog);
+        fprintf(stderr, "%s: -g does not support this address family\n", prog);
         exit(1);
     }
 }
@@ -1360,6 +1388,29 @@ void add_cidr_ipv4(unsigned long net_addr, unsigned long mask)
     add_addr_range_ipv4(net_addr, net_last);
 }
 
+#ifdef IPV6
+void add_cidr_ipv6(uint64_t net_upper, uint64_t net_lower, unsigned long mask, const char *scope_str)
+{
+    uint64_t bitmask_lower;
+    uint64_t last_lower;
+
+    /* check mask -- 2^63 addresses should suffice for now */
+    if (mask < 65 || mask > 128) {
+        fprintf(stderr, "%s: netmask must be between 65 and 128 (is: %lu)\n", prog, mask);
+        exit(1);
+    }
+
+    /* convert mask integer from 65 to 128 to the lower part of a bitmask */
+    bitmask_lower = ((uint64_t)-1) << (128 - mask);
+
+    /* calculate network range */
+    net_lower &= bitmask_lower;
+    last_lower = net_lower + ((uint64_t)1 << (128 - mask)) - 1;
+
+    add_addr_range_ipv6(net_upper, net_lower, net_upper, last_lower, scope_str);
+}
+#endif /* IPV6 */
+
 void add_range(char *start, char *end)
 {
     struct addrinfo addr_hints;
@@ -1367,8 +1418,27 @@ void add_range(char *start, char *end)
     unsigned long start_long;
     unsigned long end_long;
     int ret;
-
-    /* parse start address (IPv4 only) */
+#ifdef IPV6
+    uint64_t start_upper, start_lower;
+    uint64_t end_upper, end_lower;
+    char *start_scope_str, *end_scope_str;
+
+    /*
+     * The compiler does not know that setting the address family hint to
+     * ensure that start and end are from the same address family also
+     * ensures that either start_long and end_long are initialized and used,
+     * or start_upper, start_lower, end_upper, and end_lower are initialized
+     * and used.  Thus initialize all variables when both IPv4 and IPv6 are
+     * supported to suppress compiler warnings.
+     */
+    start_long = -1;
+    end_long = 0;
+    start_upper = start_lower = -1;
+    end_upper = end_lower = 0;
+    start_scope_str = end_scope_str = NULL;
+#endif /* IPV6 */
+
+    /* parse start address */
     memset(&addr_hints, 0, sizeof(struct addrinfo));
     addr_hints.ai_family = hints_ai_family;
     addr_hints.ai_flags = AI_NUMERICHOST;
@@ -1382,13 +1452,32 @@ void add_range(char *start, char *end)
     if (addr_res->ai_family == AF_INET) {
         start_long = ntohl(((struct sockaddr_in*)addr_res->ai_addr)->sin_addr.s_addr);
         freeaddrinfo(addr_res);
+#ifdef IPV6
+    } else if (addr_res->ai_family == AF_INET6) {
+        uint8_t *ipv6_addr = ((struct sockaddr_in6*)addr_res->ai_addr)->sin6_addr.s6_addr;
+        start_upper = be_octets_to_uint64(ipv6_addr);
+        start_lower = be_octets_to_uint64(ipv6_addr + 8);
+        freeaddrinfo(addr_res);
+#endif /* IPV6 */
     } else {
         freeaddrinfo(addr_res);
-        fprintf(stderr, "%s: -g works only with IPv4 addresses\n", prog);
+        fprintf(stderr, "%s: -g does not support this address family\n", prog);
         exit(1);
     }
 
-    /* parse end address (IPv4 only) */
+    /* IPv6 addresses can have a scope */
+    if (hints_ai_family == AF_INET6) {
+        start_scope_str = strchr(start, '%');
+        end_scope_str = strchr(end, '%');
+        if ((!start_scope_str && end_scope_str) ||
+            (start_scope_str && !end_scope_str) ||
+            (start_scope_str && end_scope_str && strcmp(start_scope_str, end_scope_str) != 0)) {
+                fprintf(stderr, "%s: different scopes for start and end addresses\n", prog);
+                exit(1);
+        }
+    }
+
+    /* parse end address */
     memset(&addr_hints, 0, sizeof(struct addrinfo));
     addr_hints.ai_family = hints_ai_family;
     addr_hints.ai_flags = AI_NUMERICHOST;
@@ -1401,9 +1490,17 @@ void add_range(char *start, char *end)
         end_long = ntohl(((struct sockaddr_in*)addr_res->ai_addr)->sin_addr.s_addr);
         freeaddrinfo(addr_res);
         add_addr_range_ipv4(start_long, end_long);
+#ifdef IPV6
+    } else if (addr_res->ai_family == AF_INET6) {
+        uint8_t *ipv6_addr = ((struct sockaddr_in6*)addr_res->ai_addr)->sin6_addr.s6_addr;
+        end_upper = be_octets_to_uint64(ipv6_addr);
+        end_lower = be_octets_to_uint64(ipv6_addr + 8);
+        freeaddrinfo(addr_res);
+        add_addr_range_ipv6(start_upper, start_lower, end_upper, end_lower, start_scope_str);
+#endif /* IPV6 */
     } else {
         freeaddrinfo(addr_res);
-        fprintf(stderr, "%s: -g works only with IPv4 addresses\n", prog);
+        fprintf(stderr, "%s: -g does not support this address family\n", prog);
         exit(1);
     }
 }
@@ -1426,6 +1523,63 @@ void add_addr_range_ipv4(unsigned long start_long, unsigned long end_long)
     }
 }
 
+#ifdef IPV6
+uint64_t be_octets_to_uint64(uint8_t *be_octets)
+{
+    int i;
+    uint64_t ret = 0;
+    for (i = 0; i < 8; i++) {
+        ret |= (uint64_t)be_octets[7 - i] << (i * 8);
+    }
+    return ret;
+}
+
+void uint64_to_be_octets(uint64_t num, uint8_t *be_octets)
+{
+    int i;
+    for (i = 0; i < 8; i++) {
+        be_octets[7 - i] = (uint8_t)((num >> (i * 8)) & 0xff);
+    }
+}
+
+void add_addr_range_ipv6(uint64_t start_upper, uint64_t start_lower,
+                         uint64_t end_upper, uint64_t end_lower,
+                         const char *scope_str)
+{
+    struct in6_addr in6_addr_tmp;
+    char buffer[100];
+
+    /* prevent generating too many addresses */
+    if ((start_upper + 1 < end_upper) ||
+        (start_upper + 1 == end_upper && end_lower >= start_lower) ||
+        (start_upper + 1 == end_upper && end_lower - MAX_GENERATE >= start_lower) ||
+        (start_upper == end_upper && end_lower - MAX_GENERATE >= start_lower &&
+                                     start_lower + MAX_GENERATE <= end_lower)) {
+        fprintf(stderr, "%s: -g parameter generates too many addresses\n", prog);
+        exit(1);
+    }
+
+    while ((start_upper < end_upper) ||
+           (start_upper == end_upper && start_lower <= end_lower)) {
+        uint64_to_be_octets(start_upper, in6_addr_tmp.s6_addr);
+        uint64_to_be_octets(start_lower, in6_addr_tmp.s6_addr + 8);
+        inet_ntop(AF_INET6, &in6_addr_tmp, buffer, sizeof(buffer));
+        if (scope_str) {
+           if (strlen(buffer) + strlen(scope_str) + 1 > sizeof(buffer)) {
+                fprintf(stderr, "%s: scope identifier is too long\n", prog);
+                exit(1);
+            }
+            strncat(buffer, scope_str, sizeof(buffer) - strlen(buffer) - 1);
+        }
+        add_name(buffer);
+        start_lower++;
+        if (start_lower == 0) {
+            start_upper++;
+        }
+    }
+}
+#endif /* IPv6 */
+
 void main_loop()
 {
     int64_t lt;