New is_ipv6 and extract_ipv6 algorithms

Also add unit tests.
This commit is contained in:
David Kerr 2020-06-28 22:50:17 -04:00 committed by Richard Hansen
parent 92c1294af9
commit 29202f5bc1
3 changed files with 549 additions and 29 deletions

View file

@ -71,6 +71,7 @@ AM_PL_LOG_FLAGS = -Mstrict -w \
-MDevel::Autoflush -MDevel::Autoflush
handwritten_tests = \ handwritten_tests = \
t/is-and-extract-ipv4.pl \ t/is-and-extract-ipv4.pl \
t/is-and-extract-ipv6.pl \
t/geturl_connectivity.pl \ t/geturl_connectivity.pl \
t/geturl_ssl.pl \ t/geturl_ssl.pl \
t/parse_assignments.pl \ t/parse_assignments.pl \

View file

@ -2297,44 +2297,71 @@ sub extract_ipv4 {
return $ip; return $ip;
} }
######################################################################
## Regex that matches an IPv6 address. Accepts embedded leading zeros.
## Accepts IPv4-mapped IPv6 addresses such as 64:ff9b::192.0.2.13.
######################################################################
my $regex_ipv6 = qr/
# Define some named groups so we can use Perl's recursive subpattern feature for shorthand:
(?<g>[0-9A-F]{1,4}){0} # "g" matches a group of 1 to 4 hex chars
(?<g_>(?&g):){0} # "g_" matches a group of 1 to 4 hex chars followed by a colon
(?<_g>:(?&g)){0} # "_g" matches a colon followed by a group of 1 to 4 hex chars
(?<g0>(?&g)?){0} # "g0" is an optional "g" (matches a group of 0 to 4 hex chars)
(?<g0_>(?&g0):){0} # "g0_" is an optional "g" followed by a colon
(?<x>[:.0-9A-Z]){0} # "x" matches chars that should never come before or after the address
(?<ip4>$regex_ipv4){0} # "ip4" matches an IPv4 address x.x.x.x
# Now for the regex itself:
(?<!(?&x)) # Not preceded by a character that is not allowed to come before the address
(?:
(?&g_){7}(?&g) # Exactly 8 groups of 1-4 hex chars
| (?&g_){1,7}: # OR compressed form with the double colon at the end
| :(?&_g){1,7} # OR compressed form with the double colon at the beginning
| :: # OR compressed to just a double colon
| # OR compressed form with the double colon in the middle:
(?= # Only consider this case if the string...
(?&g0_){2,7}(?&g0) # ...has 3 to 8 possibly empty groups (at least 3 because there
# will be at least one group before the ::, at least one group
# after the ::, and the :: itself is an empty group)...
(?!(?&x)) # ...then ends.
) # If the condition is true, then:
(?&g_){1,6} # 1 to 6 non-empty groups before the double colon
(?&_g){1,6} # 1 to 6 non-empty groups after the double colon
| # OR an IPv4-mapped IPv6 address:
(?: # It starts with a 96-bit prefix represented by:
(?&g_){6} # Exactly 6 groups of 1-4 hex chars with their colons
| (?&g_){1,5}: # OR compressed form with the double colon at the end
| ::(?&g_){0,5} # OR compressed form with the double colon at the beginning
| # OR compressed form with the double colon in the middle:
(?= # Only consider this case if the prefix...
(?&g0_){3,6} # ...has 3 to 6 possibly empty groups...
(?&ip4)(?!(?&x)) # ...then ends.
) # If the condition is true, then:
(?&g_){1,4} # 1 to 4 non-empty groups before the double colon
(?&_g){1,4} # 1 to 4 non-empty groups after the double colon
: # colon separating the IPv6 part from the IPv4 part
)
(?&ip4) # Prefix is followed by an IPv4 address
)
(?!(?&x)) # Not followed by a character that is not allowed to come after the address
/xi;
###################################################################### ######################################################################
## is_ipv6() validates if string is valid IPv6 address with no preceding ## is_ipv6() validates if string is valid IPv6 address with no preceding
## or trailing spaces/characters. ## or trailing spaces/characters, not even line breaks.
###################################################################### ######################################################################
sub is_ipv6 { sub is_ipv6 {
my ($value) = @_; return (shift // '') =~ /\A$regex_ipv6\z/;
return (length($value // '') != 0) &&
((extract_ipv6($value) // '') eq (($value =~ s/\b0+\B//g) ? $value : $value));
} }
###################################################################### ######################################################################
## extract_ipv6() extracts the first valid IPv6 address from the given string. ## extract_ipv6() finds the first valid IPv6 address in the given string,
## Accepts leading zeros in the address but removes them in returned value. ## removes embedded leading zeros, and returns the result.
###################################################################### ######################################################################
sub extract_ipv6 { sub extract_ipv6 {
my $content = shift; (shift // '') =~ /($regex_ipv6)/ or return undef;
my $omits; (my $ip = $1) =~ s/\b0+\B//g; ## remove embedded leading zeros
my $ip = ""; return $ip;
my $linenumbers = 0;
my @values = split('\n', $content);
foreach my $val (@values) {
next unless $val =~ /((:{0,2}[A-F0-9]{1,4}){0,7}:{1,2}[A-F0-9]{1,4})/i; # invalid char
my $parsed = $1;
# check for at least 7 colons
my $count_colon = () = $parsed =~ /:/g;
if ($count_colon != 7) {
# or one double colon
my $count_double_colon = () = $parsed =~ /::/g;
if ($count_double_colon != 1) {
next
}
}
$parsed =~ s/\b0+\B//g; ## remove embedded leading zeros
return $parsed;
}
return;
} }
###################################################################### ######################################################################

492
t/is-and-extract-ipv6.pl Normal file
View file

@ -0,0 +1,492 @@
use Test::More;
use B qw(perlstring);
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
my @valid_ipv6 = (
"::abcd:efAB:CDEF", # case sensitivity
"08:09:0a:0b:0c:0d:0e:0f", # leading zeros
# with thanks to http://home.deds.nl/~aeron/regex/valid_ipv6.txt
"1111:2222:3333:4444:5555:6666:7777:8888",
"1111:2222:3333:4444:5555:6666:7777::",
"1111:2222:3333:4444:5555:6666::",
"1111:2222:3333:4444:5555::",
"1111:2222:3333:4444::",
"1111:2222:3333::",
"1111:2222::",
"1111::",
"::",
"1111:2222:3333:4444:5555:6666::8888",
"1111:2222:3333:4444:5555::8888",
"1111:2222:3333:4444::8888",
"1111:2222:3333::8888",
"1111:2222::8888",
"1111::8888",
"::8888",
"1111:2222:3333:4444:5555::7777:8888",
"1111:2222:3333:4444::7777:8888",
"1111:2222:3333::7777:8888",
"1111:2222::7777:8888",
"1111::7777:8888",
"::7777:8888",
"1111:2222:3333:4444::6666:7777:8888",
"1111:2222:3333::6666:7777:8888",
"1111:2222::6666:7777:8888",
"1111::6666:7777:8888",
"::6666:7777:8888",
"1111:2222:3333::5555:6666:7777:8888",
"1111:2222::5555:6666:7777:8888",
"1111::5555:6666:7777:8888",
"::5555:6666:7777:8888",
"1111:2222::4444:5555:6666:7777:8888",
"1111::4444:5555:6666:7777:8888",
"::4444:5555:6666:7777:8888",
"1111::3333:4444:5555:6666:7777:8888",
"::3333:4444:5555:6666:7777:8888",
"::2222:3333:4444:5555:6666:7777:8888",
# IPv4-mapped IPv6 addresses
"1111:2222:3333:4444:5555:6666:0.0.0.0",
"1111:2222:3333:4444:5555:6666:00.00.00.00",
"1111:2222:3333:4444:5555:6666:000.000.000.000",
"1111:2222:3333:4444:5555:6666:123.123.123.123",
"1111:2222:3333:4444:5555::123.123.123.123",
"1111:2222:3333:4444::123.123.123.123",
"1111:2222:3333::123.123.123.123",
"1111:2222::123.123.123.123",
"1111::123.123.123.123",
"::123.123.123.123",
"1111:2222:3333:4444::6666:123.123.123.123",
"1111:2222:3333::6666:123.123.123.123",
"1111:2222::6666:123.123.123.123",
"1111::6666:123.123.123.123",
"::6666:123.123.123.123",
"1111:2222:3333::5555:6666:123.123.123.123",
"1111:2222::5555:6666:123.123.123.123",
"1111::5555:6666:123.123.123.123",
"::5555:6666:123.123.123.123",
"1111:2222::4444:5555:6666:123.123.123.123",
"1111::4444:5555:6666:123.123.123.123",
"::4444:5555:6666:123.123.123.123",
"1111::3333:4444:5555:6666:123.123.123.123",
"::3333:4444:5555:6666:123.123.123.123",
"::2222:3333:4444:5555:6666:123.123.123.123",
);
my @invalid_ipv6 = (
# Empty string and bogus text
undef,
"",
" ",
"foobar",
# Valid IPv6 with extra text before or after
"foo2001:DB8:4341:0781:1111:2222:3333:4444",
"foo 2001:DB8:4341:0781::4444",
"foo 2001:DB8:4341:0781:1111:: bar",
"foo2001:DB8:4341:0781::100bar",
"2001:DB8:4341:0781::1 bar",
"2001:DB8:4341:0781::0001bar",
"foo bar 3001:DB8:4341:0781:1111:2222:3333:4444 foo bar",
"__3001:DB8:4341:0781::4444",
"__3001:DB8:4341:0781:1111::__",
"--3001:DB8:4341:0781::100--",
"/3001:DB8:4341:0781::1/",
"3001:DB8:4341:0781::0001%",
"fdb6:1d86:d9bd:1::4444%eth0",
"fdb6:1d86:d9bd:1:1111::%ens192",
"fdb6:1d86:d9bd:1::100%en0",
"fdb6:1d86:d9bd:1::1%eth1.100",
# With thanks to http://home.deds.nl/~aeron/regex/invalid_ipv6.txt
# Invalid data
"XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX",
# Too many components
"1111:2222:3333:4444:5555:6666:7777:8888:9999",
"1111:2222:3333:4444:5555:6666:7777:8888::",
"::2222:3333:4444:5555:6666:7777:8888:9999",
# Too few components
"1111:2222:3333:4444:5555:6666:7777",
"1111:2222:3333:4444:5555:6666",
"1111:2222:3333:4444:5555",
"1111:2222:3333:4444",
"1111:2222:3333",
"1111:2222",
"1111",
# Missing :
"11112222:3333:4444:5555:6666:7777:8888",
"1111:22223333:4444:5555:6666:7777:8888",
"1111:2222:33334444:5555:6666:7777:8888",
"1111:2222:3333:44445555:6666:7777:8888",
"1111:2222:3333:4444:55556666:7777:8888",
"1111:2222:3333:4444:5555:66667777:8888",
"1111:2222:3333:4444:5555:6666:77778888",
# Missing : intended for ::
"1111:2222:3333:4444:5555:6666:7777:8888:",
"1111:2222:3333:4444:5555:6666:7777:",
"1111:2222:3333:4444:5555:6666:",
"1111:2222:3333:4444:5555:",
"1111:2222:3333:4444:",
"1111:2222:3333:",
"1111:2222:",
"1111:",
":",
":8888",
":7777:8888",
":6666:7777:8888",
":5555:6666:7777:8888",
":4444:5555:6666:7777:8888",
":3333:4444:5555:6666:7777:8888",
":2222:3333:4444:5555:6666:7777:8888",
":1111:2222:3333:4444:5555:6666:7777:8888",
# :::
":::2222:3333:4444:5555:6666:7777:8888",
"1111:::3333:4444:5555:6666:7777:8888",
"1111:2222:::4444:5555:6666:7777:8888",
"1111:2222:3333:::5555:6666:7777:8888",
"1111:2222:3333:4444:::6666:7777:8888",
"1111:2222:3333:4444:5555:::7777:8888",
"1111:2222:3333:4444:5555:6666:::8888",
"1111:2222:3333:4444:5555:6666:7777:::",
# Double ::
"::2222::4444:5555:6666:7777:8888",
"::2222:3333::5555:6666:7777:8888",
"::2222:3333:4444::6666:7777:8888",
"::2222:3333:4444:5555::7777:8888",
"::2222:3333:4444:5555:7777::8888",
"::2222:3333:4444:5555:7777:8888::",
"1111::3333::5555:6666:7777:8888",
"1111::3333:4444::6666:7777:8888",
"1111::3333:4444:5555::7777:8888",
"1111::3333:4444:5555:6666::8888",
"1111::3333:4444:5555:6666:7777::",
"1111:2222::4444::6666:7777:8888",
"1111:2222::4444:5555::7777:8888",
"1111:2222::4444:5555:6666::8888",
"1111:2222::4444:5555:6666:7777::",
"1111:2222:3333::5555::7777:8888",
"1111:2222:3333::5555:6666::8888",
"1111:2222:3333::5555:6666:7777::",
"1111:2222:3333:4444::6666::8888",
"1111:2222:3333:4444::6666:7777::",
"1111:2222:3333:4444:5555::7777::",
# Invalid data
"XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4",
"1111:2222:3333:4444:5555:6666:256.256.256.256",
# Too many components
"1111:2222:3333:4444:5555:6666:7777:8888:1.2.3",
"1111:2222:3333:4444:5555:6666:7777:1.2.3.4",
"1111:2222:3333:4444:5555:6666::1.2.3.4",
"::2222:3333:4444:5555:6666:7777:1.2.3.4",
"1111:2222:3333:4444:5555:6666:1.2.3.4.5",
# Too few components
"1111:2222:3333:4444:5555:1.2.3.4",
"1111:2222:3333:4444:1.2.3.4",
"1111:2222:3333:1.2.3.4",
"1111:2222:1.2.3.4",
"1111:1.2.3.4",
"1.2.3.4",
# Missing :
"11112222:3333:4444:5555:6666:1.2.3.4",
"1111:22223333:4444:5555:6666:1.2.3.4",
"1111:2222:33334444:5555:6666:1.2.3.4",
"1111:2222:3333:44445555:6666:1.2.3.4",
"1111:2222:3333:4444:55556666:1.2.3.4",
"1111:2222:3333:4444:5555:66661.2.3.4",
# Missing .
"1111:2222:3333:4444:5555:6666:255255.255.255",
"1111:2222:3333:4444:5555:6666:255.255255.255",
"1111:2222:3333:4444:5555:6666:255.255.255255",
# Missing : intended for ::
":1.2.3.4",
":6666:1.2.3.4",
":5555:6666:1.2.3.4",
":4444:5555:6666:1.2.3.4",
":3333:4444:5555:6666:1.2.3.4",
":2222:3333:4444:5555:6666:1.2.3.4",
":1111:2222:3333:4444:5555:6666:1.2.3.4",
# :::
":::2222:3333:4444:5555:6666:1.2.3.4",
"1111:::3333:4444:5555:6666:1.2.3.4",
"1111:2222:::4444:5555:6666:1.2.3.4",
"1111:2222:3333:::5555:6666:1.2.3.4",
"1111:2222:3333:4444:::6666:1.2.3.4",
"1111:2222:3333:4444:5555:::1.2.3.4",
# Double ::
"::2222::4444:5555:6666:1.2.3.4",
"::2222:3333::5555:6666:1.2.3.4",
"::2222:3333:4444::6666:1.2.3.4",
"::2222:3333:4444:5555::1.2.3.4",
"1111::3333::5555:6666:1.2.3.4",
"1111::3333:4444::6666:1.2.3.4",
"1111::3333:4444:5555::1.2.3.4",
"1111:2222::4444::6666:1.2.3.4",
"1111:2222::4444:5555::1.2.3.4",
"1111:2222:3333::5555::1.2.3.4",
# Missing parts
"::.",
"::..",
"::...",
"::1...",
"::1.2..",
"::1.2.3.",
"::.2..",
"::.2.3.",
"::.2.3.4",
"::..3.",
"::..3.4",
"::...4",
# Extra : in front
":1111:2222:3333:4444:5555:6666:7777::",
":1111:2222:3333:4444:5555:6666::",
":1111:2222:3333:4444:5555::",
":1111:2222:3333:4444::",
":1111:2222:3333::",
":1111:2222::",
":1111::",
":::",
":1111:2222:3333:4444:5555:6666::8888",
":1111:2222:3333:4444:5555::8888",
":1111:2222:3333:4444::8888",
":1111:2222:3333::8888",
":1111:2222::8888",
":1111::8888",
":::8888",
":1111:2222:3333:4444:5555::7777:8888",
":1111:2222:3333:4444::7777:8888",
":1111:2222:3333::7777:8888",
":1111:2222::7777:8888",
":1111::7777:8888",
":::7777:8888",
":1111:2222:3333:4444::6666:7777:8888",
":1111:2222:3333::6666:7777:8888",
":1111:2222::6666:7777:8888",
":1111::6666:7777:8888",
":::6666:7777:8888",
":1111:2222:3333::5555:6666:7777:8888",
":1111:2222::5555:6666:7777:8888",
":1111::5555:6666:7777:8888",
":::5555:6666:7777:8888",
":1111:2222::4444:5555:6666:7777:8888",
":1111::4444:5555:6666:7777:8888",
":::4444:5555:6666:7777:8888",
":1111::3333:4444:5555:6666:7777:8888",
":::3333:4444:5555:6666:7777:8888",
":::2222:3333:4444:5555:6666:7777:8888",
":1111:2222:3333:4444:5555:6666:1.2.3.4",
":1111:2222:3333:4444:5555::1.2.3.4",
":1111:2222:3333:4444::1.2.3.4",
":1111:2222:3333::1.2.3.4",
":1111:2222::1.2.3.4",
":1111::1.2.3.4",
":::1.2.3.4",
":1111:2222:3333:4444::6666:1.2.3.4",
":1111:2222:3333::6666:1.2.3.4",
":1111:2222::6666:1.2.3.4",
":1111::6666:1.2.3.4",
":::6666:1.2.3.4",
":1111:2222:3333::5555:6666:1.2.3.4",
":1111:2222::5555:6666:1.2.3.4",
":1111::5555:6666:1.2.3.4",
":::5555:6666:1.2.3.4",
":1111:2222::4444:5555:6666:1.2.3.4",
":1111::4444:5555:6666:1.2.3.4",
":::4444:5555:6666:1.2.3.4",
":1111::3333:4444:5555:6666:1.2.3.4",
":::3333:4444:5555:6666:1.2.3.4",
":::2222:3333:4444:5555:6666:1.2.3.4",
# Extra : at end
"1111:2222:3333:4444:5555:6666:7777:::",
"1111:2222:3333:4444:5555:6666:::",
"1111:2222:3333:4444:5555:::",
"1111:2222:3333:4444:::",
"1111:2222:3333:::",
"1111:2222:::",
"1111:::",
":::",
"1111:2222:3333:4444:5555:6666::8888:",
"1111:2222:3333:4444:5555::8888:",
"1111:2222:3333:4444::8888:",
"1111:2222:3333::8888:",
"1111:2222::8888:",
"1111::8888:",
"::8888:",
"1111:2222:3333:4444:5555::7777:8888:",
"1111:2222:3333:4444::7777:8888:",
"1111:2222:3333::7777:8888:",
"1111:2222::7777:8888:",
"1111::7777:8888:",
"::7777:8888:",
"1111:2222:3333:4444::6666:7777:8888:",
"1111:2222:3333::6666:7777:8888:",
"1111:2222::6666:7777:8888:",
"1111::6666:7777:8888:",
"::6666:7777:8888:",
"1111:2222:3333::5555:6666:7777:8888:",
"1111:2222::5555:6666:7777:8888:",
"1111::5555:6666:7777:8888:",
"::5555:6666:7777:8888:",
"1111:2222::4444:5555:6666:7777:8888:",
"1111::4444:5555:6666:7777:8888:",
"::4444:5555:6666:7777:8888:",
"1111::3333:4444:5555:6666:7777:8888:",
"::3333:4444:5555:6666:7777:8888:",
"::2222:3333:4444:5555:6666:7777:8888:",
);
my @if_samples = (
# Sample output from:
# ip -6 -o addr show dev <interface> scope global
# This seems to be consistent accross platforms. The last line is from Ubuntu of a static
# assigned IPv6.
["ip -6 -o addr show dev <interface> scope global", <<'EOF'],
2: ens160 inet6 fdb6:1d86:d9bd:1::8214/128 scope global dynamic noprefixroute \ valid_lft 63197sec preferred_lft 63197sec
2: ens160 inet6 2001:DB8:4341:0781::8214/128 scope global dynamic noprefixroute \ valid_lft 63197sec preferred_lft 63197sec
2: ens160 inet6 2001:DB8:4341:0781:89b9:4b1c:186c:a0c7/64 scope global temporary dynamic \ valid_lft 85954sec preferred_lft 21767sec
2: ens160 inet6 fdb6:1d86:d9bd:1:89b9:4b1c:186c:a0c7/64 scope global temporary dynamic \ valid_lft 85954sec preferred_lft 21767sec
2: ens160 inet6 fdb6:1d86:d9bd:1:34a6:c329:c52e:8ba6/64 scope global temporary deprecated dynamic \ valid_lft 85954sec preferred_lft 0sec
2: ens160 inet6 fdb6:1d86:d9bd:1:b417:fe35:166b:4816/64 scope global dynamic mngtmpaddr noprefixroute \ valid_lft 85954sec preferred_lft 85954sec
2: ens160 inet6 2001:DB8:4341:0781:34a6:c329:c52e:8ba6/64 scope global temporary deprecated dynamic \ valid_lft 85954sec preferred_lft 0sec
2: ens160 inet6 2001:DB8:4341:0781:f911:a224:7e69:d22/64 scope global dynamic mngtmpaddr noprefixroute \ valid_lft 85954sec preferred_lft 85954sec
2: ens160 inet6 2001:DB8:4341:0781::100/128 scope global noprefixroute \ valid_lft forever preferred_lft forever
EOF
# Sample output from MacOS:
# ifconfig <interface> | grep -w "inet6"
# (Yes, there is a tab at start of each line.) The last two lines are with a manually
# configured static GUA.
["MacOS: ifconfig <interface> | grep -w \"inet6\"", <<'EOF'],
inet6 fe80::1419:abd0:5943:8bbb%en0 prefixlen 64 secured scopeid 0xa
inet6 fdb6:1d86:d9bd:1:142c:8e9e:de48:843e prefixlen 64 autoconf secured
inet6 fdb6:1d86:d9bd:1:7447:cf67:edbd:cea4 prefixlen 64 autoconf temporary
inet6 fdb6:1d86:d9bd:1::c5b3 prefixlen 64 dynamic
inet6 2001:DB8:4341:0781:141d:66b9:2ba1:b67d prefixlen 64 autoconf secured
inet6 2001:DB8:4341:0781:64e1:b68f:e8af:5d6e prefixlen 64 autoconf temporary
inet6 fe80::1419:abd0:5943:8bbb%en0 prefixlen 64 secured scopeid 0xa
inet6 2001:DB8:4341:0781::101 prefixlen 64
EOF
["RHEL: ifconfig <interface> | grep -w \"inet6\"", <<'EOF'],
inet6 2001:DB8:4341:0781::dc14 prefixlen 128 scopeid 0x0<global>
inet6 fe80::cd48:4a58:3b0f:4d30 prefixlen 64 scopeid 0x20<link>
inet6 2001:DB8:4341:0781:e720:3aec:a936:36d4 prefixlen 64 scopeid 0x0<global>
inet6 fdb6:1d86:d9bd:1:9c16:8cbf:ae33:f1cc prefixlen 64 scopeid 0x0<global>
inet6 fdb6:1d86:d9bd:1::dc14 prefixlen 128 scopeid 0x0<global>
EOF
["Ubuntu: ifconfig <interface> | grep -w \"inet6\"", <<'EOF'],
inet6 fdb6:1d86:d9bd:1:34a6:c329:c52e:8ba6 prefixlen 64 scopeid 0x0<global>
inet6 fdb6:1d86:d9bd:1:89b9:4b1c:186c:a0c7 prefixlen 64 scopeid 0x0<global>
inet6 fdb6:1d86:d9bd:1::8214 prefixlen 128 scopeid 0x0<global>
inet6 fdb6:1d86:d9bd:1:b417:fe35:166b:4816 prefixlen 64 scopeid 0x0<global>
inet6 fe80::5b31:fc63:d353:da68 prefixlen 64 scopeid 0x20<link>
inet6 2001:DB8:4341:0781::8214 prefixlen 128 scopeid 0x0<global>
inet6 2001:DB8:4341:0781:34a6:c329:c52e:8ba6 prefixlen 64 scopeid 0x0<global>
inet6 2001:DB8:4341:0781:89b9:4b1c:186c:a0c7 prefixlen 64 scopeid 0x0<global>
inet6 2001:DB8:4341:0781:f911:a224:7e69:d22 prefixlen 64 scopeid 0x0<global>
EOF
["Busybox: ifconfig <interface> | grep -w \"inet6\"", <<'EOF'],
inet6 addr: fe80::4362:31ff:fe08:61b4/64 Scope:Link
inet6 addr: 2001:DB8:4341:0781:ed44:eb63:b070:212f/128 Scope:Global
EOF
);
subtest "is_ipv6() with valid addresses" => sub {
foreach my $ip (@valid_ipv6) {
ok(ddclient::is_ipv6($ip), "is_ipv6('$ip')");
}
};
subtest "is_ipv6() with invalid addresses" => sub {
foreach my $ip (@invalid_ipv6) {
ok(!ddclient::is_ipv6($ip), sprintf("!is_ipv6(%s)", defined($ip) ? "'$ip'" : 'undef'));
}
};
subtest "is_ipv6() with char adjacent to valid address" => sub {
foreach my $ch (split(//, '/.,:z @$#&%!^*()_-+'), "\n") {
subtest perlstring($ch) => sub {
foreach my $ip (@valid_ipv6) {
subtest $ip => sub {
my $test = $ch . $ip; # insert at front
ok(!ddclient::is_ipv6($test), "!is_ipv6('$test')");
$test = $ip . $ch; # add at end
ok(!ddclient::is_ipv6($test), "!is_ipv6('$test')");
$test = $ch . $ip . $ch; # wrap front and end
ok(!ddclient::is_ipv6($test), "!is_ipv6('$test')");
};
}
};
}
};
subtest "extract_ipv6()" => sub {
my @test_cases = (
{name => "undef", text => undef, want => undef},
{name => "empty", text => "", want => undef},
{name => "invalid", text => "::12345", want => undef},
{name => "two addrs", text => "::1\n::2", want => "::1"},
{name => "zone index", text => "fe80::1%0", want => "fe80::1"},
{name => "url host+port", text => "[::1]:123", want => "::1"},
{name => "url host+zi+port", text => "[fe80::1%250]:123", want => "fe80::1"},
{name => "zero pad", text => "::0001", want => "::1"},
);
foreach my $tc (@test_cases) {
is(ddclient::extract_ipv6($tc->{text}), $tc->{want}, $tc->{name});
}
};
subtest "extract_ipv6() of valid addr with adjacent non-word char" => sub {
foreach my $wb (split(//, '/, @$#&%!^*()_-+'), "\n") {
subtest perlstring($wb) => sub {
my $test = "";
foreach my $ip (@valid_ipv6) {
$test = "foo" . $wb . $ip . $wb . "bar"; # wrap front and end
$ip =~ s/\b0+\B//g; ## remove embedded leading zeros for testing
is(ddclient::extract_ipv6($test), $ip, perlstring($test));
}
};
}
};
subtest "interface config samples" => sub {
for my $sample (@if_samples) {
my ($name, $text) = @$sample;
subtest $name => sub {
my $ip = ddclient::extract_ipv6($text);
ok(ddclient::is_ipv6($ip), "extract_ipv6(\$text) returns an IPv6 address");
foreach my $line (split(/\n/, $text)) {
my $ip = ddclient::extract_ipv6($line);
ok(ddclient::is_ipv6($ip),
sprintf("extract_ipv6(%s) returns an IPv6 address", perlstring($line)));
}
}
}
};
done_testing();