Skip to content

Commit

Permalink
IPv6 support for -g, --generate
Browse files Browse the repository at this point in the history
Similarly to IPv4, using CIDR notation excludes a special
per-subnet address, the Subnet-Router anycast address.

The limit for generated addresses is increased by one 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 schweikert#59.
  • Loading branch information
auerswal committed Jan 19, 2025
1 parent 88510d2 commit 358e61c
Show file tree
Hide file tree
Showing 5 changed files with 396 additions and 33 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
Next
====

## New features

- The -g, --generate option now also supports IPv6 addresses (#376,
thanks @auerswal)

fping 5.3 (2025-01-02)
======================

Expand Down
227 changes: 210 additions & 17 deletions ci/test-06-options-f-h.pl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/perl -w

use Test::Command tests => 102;
use Test::Command tests => 146;
use Test::More;
use File::Temp;

Expand Down Expand Up @@ -208,51 +208,148 @@
$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(0);
$cmd->stdout_is_eq("::1 is alive\n");
$cmd->stderr_is_eq("");
}

# fping -g (empty range - IPv6)
SKIP: {
if($ENV{SKIP_IPV6}) {
skip 'Skip IPv6 tests', 3;
}
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 - no IPv6 generator)
# fping -g (empty range - IPv6 - crossing 64 bit boundary)
SKIP: {
if($ENV{SKIP_IPV6}) {
skip 'Skip IPv6 tests', 3;
}
my $cmd = Test::Command->new(cmd => "fping -g ::1 ::");
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("");
$cmd->stderr_is_eq("fping: -g works only with IPv4 addresses\n");
}

# 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', 3;
}
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', 3;
}
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;
}
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;
Expand All @@ -263,7 +360,7 @@
$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;
Expand Down Expand Up @@ -296,26 +393,122 @@
$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 -6 -g (CIDR - no IPv6 generator)
# fping -g (CIDR - IPv6 - unreachable documentation addresses - host prefix)
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 -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', 3;
}
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 -g (CIDR - IPv6 - unreachable documentation addresses - normal prefix)
SKIP: {
if($ENV{SKIP_IPV6}) {
skip 'Skip IPv6 tests', 3;
}
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: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 -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
Expand Down
2 changes: 1 addition & 1 deletion ci/test-issue-58.pl
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
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");
13 changes: 8 additions & 5 deletions doc/fping.pod
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,12 @@ 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. If an IPv6 network with netmask is given, the Subnet-Router anycast
address 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

Expand Down Expand Up @@ -322,8 +325,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 131071 (the number of host addresses in one 111-bit IPv6
prefix, one address 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:
Expand Down
Loading

0 comments on commit 358e61c

Please sign in to comment.