From 213cf6ad098a97d713cdaaecb5d2967987abe8b0 Mon Sep 17 00:00:00 2001 From: David Kerr Date: Sun, 2 Aug 2020 22:30:00 -0400 Subject: [PATCH] Add get_ip_from_interface function --- Makefile.am | 1 + ddclient.in | 121 +++++++++++++- t/get_ip_from_if.pl | 29 ++++ t/is-and-extract-ipv6-global.pl | 6 +- t/is-and-extract-ipv6.pl | 14 +- t/lib/ddclient/t.pm | 277 +++++++++++++++++++++++++++++--- 6 files changed, 412 insertions(+), 36 deletions(-) create mode 100644 t/get_ip_from_if.pl diff --git a/Makefile.am b/Makefile.am index 5e5d633..ce01304 100644 --- a/Makefile.am +++ b/Makefile.am @@ -71,6 +71,7 @@ AM_PL_LOG_FLAGS = -Mstrict -w \ -I'$(abs_top_srcdir)'/t/lib \ -MDevel::Autoflush handwritten_tests = \ + t/get_ip_from_if.pl \ t/geturl_ssl.pl \ t/is-and-extract-ipv4.pl \ t/is-and-extract-ipv6.pl \ diff --git a/ddclient.in b/ddclient.in index 4a8409b..25c1bd1 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2461,9 +2461,7 @@ sub get_ip { $arg = 'ip'; } elsif ($use eq 'if') { - $reply = `command -v ip >/dev/null && ip address show dev $arg`; - $reply = `command -v ifconfig >/dev/null && ifconfig $arg` if $?; - $reply = '' if $?; + $ip = get_ip_from_interface($arg); } elsif ($use eq 'cmd') { if ($arg) { @@ -2696,6 +2694,123 @@ sub extract_ipv6_global { return $ip; } +###################################################################### +## Regex that matches an IPv6 address that is unique local (ULA). +## Accepts embedded leading zeros. +###################################################################### +my $regex_ipv6_ula = qr{ + (?= # Address starts with + f[cd][0-9a-f]{2}: # fc00::/7 RFC4193 ULA + ) + $regex_ipv6 # And is a valid IPv6 address +}xi; + +###################################################################### +## get_ip_from_interface() finds an IPv4 or IPv6 address from a network +## interface. Defaults to IPv4 unless '6' passed as 2nd parameter. +###################################################################### +sub get_ip_from_interface { + my $interface = shift; + my $ipver = int(shift // 4); ## Defaults to IPv4 if not specified + my $scope = lc(shift // "gua"); ## "gua" or "ula" + my $reply = shift // ''; ## Pass in data for unit testing purposes only + my $MacOS = shift // 0; ## For testing can set to 1 if input data is MacOS/FreeBSD format + my $count = 0; + my $cmd = "test"; + + if (($ipver != 4) && ($ipver != 6)) { + warning("get_ip_from_interface() invalid IP version: %s", $ipver); + return undef; + } + + if ($reply eq '') { ## skip if test data passed in. + return undef if !defined($interface); + } + + if ($ipver == 4) { + if (!$reply) { ## skip if test data passed in. + ## Try ip first, then ifconfig. + $cmd = "ip -4 -o addr show dev $interface scope global"; $reply = qx{$cmd 2>/dev/null}; + if ($?) { $cmd = "ifconfig $interface"; $reply = qx{$cmd 2>/dev/null}; } + if ($?) { $cmd = "missing ip or ifconfig command"; + failed("Unable to obtain information for '%s' -- %s", $interface, $cmd); + } + } + debug("Reply from '%s' :\n------\n%s------", $cmd, $reply); + + ## IPv4 is simple, we just need to find the first IPv4 address returned in the list. + my @reply = split(/\n/, $reply); + @reply = grep(/\binet\b/, @reply); # Select only IPv4 entries + return extract_ipv4($reply[0]); + } + + ## From this point on we only looking for IPv6 address. + if (($scope ne "gua") && ($scope ne "ula")) { + warning("get_ip_from_interface() invalid IPv6 scope: %s, using type GUA", $scope); + $scope = "gua"; + } + + $cmd = "test data"; + if (!$reply) { ## skip if test data passed in. + ## Try ip first, then ifconfig with -L for MacOS/FreeBSD then finally ifconfig for everything else + $cmd = "ip -6 -o addr show dev $interface scope global"; $reply = qx{$cmd 2>/dev/null}; # Linux + if ($?) { $cmd = "ifconfig -L $interface"; $MacOS = 1; $reply = qx{$cmd 2>/dev/null}; } # MacOS/FreeBSD + if ($?) { $cmd = "ifconfig $interface"; $reply = qx{$cmd 2>/dev/null}; } # Anything without iproute2 or -L + if ($?) { $cmd = "missing ip or ifconfig command"; + failed("Unable to obtain information for '%s' -- %s", $interface, $cmd); + } + } + debug("Reply from '%s' :\n------\n%s------", $cmd, $reply); + + ## IPv6 is more complex than IPv4. Start by filtering on only "inet6" addresses + ## Then remove deprecated or temporary addresses and finally seleect on global or local addresses + my @reply = split(/\n/, $reply); + @reply = grep(/\binet6\b/, @reply); # Select only IPv6 entries + @reply = grep(!/\bdeprecated\b|\btemporary\b/, @reply); # Remove deprecated and temporary + @reply = ($scope eq "gua") ? grep(/$regex_ipv6_global/, @reply) # Select only global addresses + : grep(/$regex_ipv6_ula/, @reply); # or only ULA addresses + debug("Raw IPv6 after filtering for %s addresses %s: (%s)\r\n%s", uc($scope), $interface, scalar(@reply), join("\n", @reply)); + + ## If we filter down to zero or one result then we are done... + return undef if (($count = scalar(@reply)) == 0); + return extract_ipv6($reply[0]) if ($count == 1); + + ## If there are more than one we need to select the "best". + ## First choice would be a static address. + my @static = ($MacOS == 1) ? grep(!/^.*\bvltime\b.*$/i, @reply) # MacOS/FreeBSD, no 'vltime' + : grep(/^.*\bvalid_lft.\bforever\b.*$/i, @reply); # Everything else 'forever' life + $count = scalar(@static); + debug("Possible Static IP addresses %s: (%s)\r\n%s", $interface, $count, join("\n", @static)); + + ## If only one result then we are done. If there are more than one static addresses + ## then we will replace our original list with the list of statics and sort on them. + ## If zero static addresses we fall through with our original list. + return extract_ipv6($static[0]) if ($count == 1); + @reply = @static if ($count > 1); + + ## Sort what we have by the prefix length, IP address "length" and finally valid life. + my @sorted = sort { + ## We give preference to IP addressess with the longest prefix... so we prefer a /128 over a /64 + ## this is a decimal (\d+) either after the word "prefixlen" or after a forward slash. + (($b =~ /(?:\bprefixlen\b\s*|\/)(\d+)/i)[0] // 0) <=> (($a =~ /(?:\bprefixlen\b\s*|\/)(\d+)/i)[0] // 0) + + ## If there are multiple the same then we prefer "shorter" IP addresses in the + ## theory that a shorter address is more likely assigned by DHCPv6 than SLAAC. + ## E.g. 2001:db8:4341:0781::8214/64 is preferable to 2001:db8:4341:0781:34a6:c329:c52e:8ba6/64 + ## So we count the number () of groups of [0-9a-f] blocks in the IP address. + || (()= (extract_ipv6($a) // '') =~ /[0-9A-F]+/gi) <=> (()= (extract_ipv6($b) // '') =~ /[0-9A-F]+/gi) + + ## Finally we check remaining valid lifetime and prefer longer remaining life. + ## This is a desimal (\d+) after the word "valid_lft" or "vltime". Only available + ## from iproute2 or MacOS/FreeBSD version of ifconfig (-L parameter). + || (($b =~ /(?:\bvalid_lft\b\s*|\bvltime\b\s*)(\d+)/i)[0] // 0) <=> (($a =~ /(?:\bvalid_lft\b\s*|\bvltime\b\s*)(\d+)/i)[0] // 0) + } @reply; + debug("Sorted list of IP addresss for %s: (%s)\r\n%s", $interface, scalar(@sorted), join("\n", @sorted)); + + ## Whatever sorted to the top is the best choice for IPv6 address + return extract_ipv6($sorted[0]); +} + ###################################################################### ## group_hosts_by ###################################################################### diff --git a/t/get_ip_from_if.pl b/t/get_ip_from_if.pl new file mode 100644 index 0000000..d4a0c4b --- /dev/null +++ b/t/get_ip_from_if.pl @@ -0,0 +1,29 @@ +use Test::More; +use ddclient::t; +SKIP: { eval { require Test::Warnings; } or skip($@, 1); } +eval { require 'ddclient'; } or BAIL_OUT($@); + +# To aid in debugging, uncomment the following lines. (They are normally left commented to avoid +# accidentally interfering with the Test Anything Protocol messages written by Test::More.) +#STDOUT->autoflush(1); +#$ddclient::globals{'debug'} = 1; + +subtest "get_ip_from_interface tests" => sub { + for my $sample (@ddclient::t::interface_samples) { + # interface name is undef as we are passing in test data + if (defined($sample->{want_ipv4_from_if})) { + my $ip = ddclient::get_ip_from_interface(undef, 4, undef, $sample->{text}, $sample->{MacOS}); + is($ip, $sample->{want_ipv4_from_if}, $sample->{name}); + } + if (defined($sample->{want_ipv6gua_from_if})) { + my $ip = ddclient::get_ip_from_interface(undef, 6, 'gua', $sample->{text}, $sample->{MacOS}); + is($ip, $sample->{want_ipv6gua_from_if}, $sample->{name}); + } + if (defined($sample->{want_ipv6ula_from_if})) { + my $ip = ddclient::get_ip_from_interface(undef, 6, 'ula', $sample->{text}, $sample->{MacOS}); + is($ip, $sample->{want_ipv6ula_from_if}, $sample->{name}); + } + } +}; + +done_testing(); diff --git a/t/is-and-extract-ipv6-global.pl b/t/is-and-extract-ipv6-global.pl index de7a991..b0e660e 100644 --- a/t/is-and-extract-ipv6-global.pl +++ b/t/is-and-extract-ipv6-global.pl @@ -56,8 +56,10 @@ subtest "extract_ipv6_global()" => sub { subtest "interface config samples" => sub { for my $sample (@ddclient::t::interface_samples) { - my $got = ddclient::extract_ipv6_global($sample->{text}); - is($got, $sample->{want_extract_ipv6_global}, $sample->{name}); + if (defined($sample->{want_extract_ipv6_global})) { + my $got = ddclient::extract_ipv6_global($sample->{text}); + is($got, $sample->{want_extract_ipv6_global}, $sample->{name}); + } } }; diff --git a/t/is-and-extract-ipv6.pl b/t/is-and-extract-ipv6.pl index a7d97b9..7362be6 100644 --- a/t/is-and-extract-ipv6.pl +++ b/t/is-and-extract-ipv6.pl @@ -422,13 +422,17 @@ subtest "extract_ipv6() of valid addr with adjacent non-word char" => sub { subtest "interface config samples" => sub { for my $sample (@ddclient::t::interface_samples) { - subtest $sample->{name} => sub { - my $ip = ddclient::extract_ipv6($sample->{text}); - ok(ddclient::is_ipv6($ip), "extract_ipv6() returns an IPv6 address"); + if (defined($sample->{want_extract_ipv6_global})) { + subtest $sample->{name} => sub { + my $ip = ddclient::extract_ipv6($sample->{text}); + ok(ddclient::is_ipv6($ip), "extract_ipv6() returns an IPv6 address"); + }; foreach my $line (split(/\n/, $sample->{text})) { my $ip = ddclient::extract_ipv6($line); - ok(ddclient::is_ipv6($ip), - sprintf("extract_ipv6(%s) returns an IPv6 address", perlstring($line))); + if ($ip) { ## Test cases may have lines that do not contain IPv6 address. + ok(ddclient::is_ipv6($ip), + sprintf("extract_ipv6(%s) returns an IPv6 address", perlstring($line))); + } } } } diff --git a/t/lib/ddclient/t.pm b/t/lib/ddclient/t.pm index 2468b73..2899e4a 100644 --- a/t/lib/ddclient/t.pm +++ b/t/lib/ddclient/t.pm @@ -3,54 +3,62 @@ require v5.10.1; use strict; use warnings; + +###################################################################### +## Outputs from ip addr and ifconfig commands to find IP address from IF name +## Samples from Ubuntu 20.04, RHEL8, Buildroot, Busybox, MacOS 10.15, FreeBSD +## NOTE: Any tabs/whitespace at start or end of lines are intentional to match real life data. +###################################################################### our @interface_samples = ( - # Sample output from: - # ip -6 -o addr show dev scope global # This seems to be consistent accross platforms. The last line is from Ubuntu of a static # assigned IPv6. { name => 'ip -6 -o addr show dev scope global', text => <<'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 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 +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 - want_extract_ipv6_global => '2001:DB8:4341:781::8214', + want_extract_ipv6_global => '2001:db8:4341:781::8214', + want_ipv6gua_from_if => "2001:db8:4341:781::100", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:1::8214", }, - # Sample output from MacOS: - # ifconfig | grep -w "inet6" - # (Yes, there is a tab at start of each line.) The last two lines are with a manually + # (Yes, there is a tab at start of each line.) The last lines is with a manually # configured static GUA. { name => 'MacOS: ifconfig | grep -w inet6', + MacOS => 1, text => <<'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 + 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 2001:db8:4341:0781::101 prefixlen 64 EOF - want_extract_ipv6_global => '2001:DB8:4341:781:141d:66b9:2ba1:b67d', + want_extract_ipv6_global => '2001:db8:4341:781:141d:66b9:2ba1:b67d', + want_ipv6gua_from_if => "2001:db8:4341:781::101", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:1::c5b3", }, { name => 'RHEL: ifconfig | grep -w inet6', text => <<'EOF', - inet6 2001:DB8:4341:0781::dc14 prefixlen 128 scopeid 0x0 + inet6 2001:db8:4341:0781::dc14 prefixlen 128 scopeid 0x0 inet6 fe80::cd48:4a58:3b0f:4d30 prefixlen 64 scopeid 0x20 - inet6 2001:DB8:4341:0781:e720:3aec:a936:36d4 prefixlen 64 scopeid 0x0 + inet6 2001:db8:4341:0781:e720:3aec:a936:36d4 prefixlen 64 scopeid 0x0 inet6 fdb6:1d86:d9bd:1:9c16:8cbf:ae33:f1cc prefixlen 64 scopeid 0x0 inet6 fdb6:1d86:d9bd:1::dc14 prefixlen 128 scopeid 0x0 EOF - want_extract_ipv6_global => '2001:DB8:4341:781::dc14', + want_extract_ipv6_global => '2001:db8:4341:781::dc14', + want_ipv6gua_from_if => "2001:db8:4341:781::dc14", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:1::dc14", }, { name => 'Ubuntu: ifconfig | grep -w inet6', @@ -60,19 +68,236 @@ EOF inet6 fdb6:1d86:d9bd:1::8214 prefixlen 128 scopeid 0x0 inet6 fdb6:1d86:d9bd:1:b417:fe35:166b:4816 prefixlen 64 scopeid 0x0 inet6 fe80::5b31:fc63:d353:da68 prefixlen 64 scopeid 0x20 - inet6 2001:DB8:4341:0781::8214 prefixlen 128 scopeid 0x0 - inet6 2001:DB8:4341:0781:34a6:c329:c52e:8ba6 prefixlen 64 scopeid 0x0 - inet6 2001:DB8:4341:0781:89b9:4b1c:186c:a0c7 prefixlen 64 scopeid 0x0 - inet6 2001:DB8:4341:0781:f911:a224:7e69:d22 prefixlen 64 scopeid 0x0 + inet6 2001:db8:4341:0781::8214 prefixlen 128 scopeid 0x0 + inet6 2001:db8:4341:0781:34a6:c329:c52e:8ba6 prefixlen 64 scopeid 0x0 + inet6 2001:db8:4341:0781:89b9:4b1c:186c:a0c7 prefixlen 64 scopeid 0x0 + inet6 2001:db8:4341:0781:f911:a224:7e69:d22 prefixlen 64 scopeid 0x0 EOF - want_extract_ipv6_global => '2001:DB8:4341:781::8214', + want_extract_ipv6_global => '2001:db8:4341:781::8214', + want_ipv6gua_from_if => "2001:db8:4341:781::8214", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:1::8214", }, { name => 'Busybox: ifconfig | grep -w inet6', text => <<'EOF', inet6 addr: fe80::4362:31ff:fe08:61b4/64 Scope:Link - inet6 addr: 2001:DB8:4341:0781:ed44:eb63:b070:212f/128 Scope:Global + inet6 addr: 2001:db8:4341:781:ed44:eb63:b070:212f/128 Scope:Global EOF - want_extract_ipv6_global => '2001:DB8:4341:781:ed44:eb63:b070:212f', + want_extract_ipv6_global => '2001:db8:4341:781:ed44:eb63:b070:212f', + want_ipv6gua_from_if => "2001:db8:4341:781:ed44:eb63:b070:212f", + }, + { name => "ip -4 -o addr show dev ens33 scope global (most linux IPv4)", + text => < "198.51.100.33", + }, + { name => "ip -6 -o addr show dev ens33 scope global (most linux)", + text => < "2001:db8:450a:e723:adee:be82:7fba:ffb2", + want_ipv6gua_from_if => "2001:db8:450a:e723::21", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:3::21", + }, + { name => "ip -6 -o addr show dev ens33 scope global (most linux static IPv6)", + text => < "2001:db8:450a:e723::101", + want_ipv6gua_from_if => "2001:db8:450a:e723::101", + }, + { name => "ifconfig ens33 (most linux autoconf IPv6 and DHCPv6)", + text => < mtu 1500 + inet 198.51.100.33 netmask 255.255.255.0 broadcast 198.51.100.255 + inet6 fdb6:1d86:d9bd:3::21 prefixlen 128 scopeid 0x0 + inet6 fe80::32c0:b270:245b:d3b4 prefixlen 64 scopeid 0x20 + inet6 fdb6:1d86:d9bd:3:a1fd:1ed9:6211:4268 prefixlen 64 scopeid 0x0 + inet6 2001:db8:450a:e723:adee:be82:7fba:ffb2 prefixlen 64 scopeid 0x0 + inet6 2001:db8:450a:e723::21 prefixlen 128 scopeid 0x0 + inet6 fdb6:1d86:d9bd:3:adee:be82:7fba:ffb2 prefixlen 64 scopeid 0x0 + inet6 2001:db8:450a:e723:dbc5:1c4e:9e9b:97a2 prefixlen 64 scopeid 0x0 + ether 00:00:00:da:24:b1 txqueuelen 1000 (Ethernet) + RX packets 3782541 bytes 556082941 (556.0 MB) + RX errors 0 dropped 513 overruns 0 frame 0 + TX packets 33294 bytes 6838768 (6.8 MB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 +EOF + want_extract_ipv6_global => "2001:db8:450a:e723:adee:be82:7fba:ffb2", + want_ipv6gua_from_if => "2001:db8:450a:e723::21", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:3::21", + want_ipv4_from_if => "198.51.100.33", + }, + { name => "ifconfig ens33 (most linux DHCPv6)", + text => < mtu 1500 + inet 198.51.100.33 netmask 255.255.255.0 broadcast 198.51.100.255 + inet6 fdb6:1d86:d9bd:3::21 prefixlen 128 scopeid 0x0 + inet6 fe80::32c0:b270:245b:d3b4 prefixlen 64 scopeid 0x20 + inet6 2001:db8:450a:e723::21 prefixlen 128 scopeid 0x0 + ether 00:00:00:da:24:b1 txqueuelen 1000 (Ethernet) + RX packets 3781554 bytes 555602847 (555.6 MB) + RX errors 0 dropped 513 overruns 0 frame 0 + TX packets 32493 bytes 6706131 (6.7 MB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 +EOF + want_extract_ipv6_global => "2001:db8:450a:e723::21", + want_ipv6gua_from_if => "2001:db8:450a:e723::21", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:3::21", + want_ipv4_from_if => "198.51.100.33", + }, + { name => "ifconfig ens33 (most linux static IPv6)", + text => < mtu 1500 + inet 198.51.100.33 netmask 255.255.255.0 broadcast 198.51.100.255 + inet6 fe80::32c0:b270:245b:d3b4 prefixlen 64 scopeid 0x20 + inet6 2001:db8:450a:e723::101 prefixlen 64 scopeid 0x0 + ether 00:00:00:da:24:b1 txqueuelen 1000 (Ethernet) + RX packets 3780219 bytes 554967876 (554.9 MB) + RX errors 0 dropped 513 overruns 0 frame 0 + TX packets 31556 bytes 6552122 (6.5 MB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 +EOF + want_extract_ipv6_global => "2001:db8:450a:e723::101", + want_ipv6gua_from_if => "2001:db8:450a:e723::101", + want_ipv4_from_if => "198.51.100.33", + }, + { name => "ifconfig en0 (MacOS IPv4)", + text => < mtu 9000 + options=50b + ether 00:00:00:90:32:8f + inet6 fe80::85b:d150:cdd9:3198%en0 prefixlen 64 secured scopeid 0x4 + inet6 2001:db8:450a:e723:1c99:99e2:21d0:79e6 prefixlen 64 autoconf secured + inet6 2001:db8:450a:e723:808d:d894:e4db:157e prefixlen 64 deprecated autoconf temporary + inet6 fdb6:1d86:d9bd:3:837:e1c7:4895:269e prefixlen 64 autoconf secured + inet6 fdb6:1d86:d9bd:3:a0b3:aa4d:9e76:e1ab prefixlen 64 deprecated autoconf temporary + inet 198.51.100.5 netmask 0xffffff00 broadcast 198.51.100.255 + inet6 2001:db8:450a:e723:2474:39fd:f5c0:6845 prefixlen 64 autoconf temporary + inet6 fdb6:1d86:d9bd:3:2474:39fd:f5c0:6845 prefixlen 64 autoconf temporary + inet6 fdb6:1d86:d9bd:3::8076 prefixlen 64 dynamic + nd6 options=201 + media: 1000baseT + status: active +EOF + want_extract_ipv6_global => "2001:db8:450a:e723:1c99:99e2:21d0:79e6", + want_ipv6gua_from_if => "2001:db8:450a:e723:1c99:99e2:21d0:79e6", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:3::8076", + want_ipv4_from_if => "198.51.100.5", + }, + { name => "ifconfig em0 (FreeBSD IPv4)", + text => < metric 0 mtu 1500 + options=81009b + ether 00:00:00:9f:c5:32 + inet6 fe80::20c:29ff:fe9f:c532%em0 prefixlen 64 scopeid 0x1 + inet6 2001:db8:450a:e723:20c:29ff:fe9f:c532 prefixlen 64 autoconf + inet6 fdb6:1d86:d9bd:3:20c:29ff:fe9f:c532 prefixlen 64 autoconf + inet 198.51.100.207 netmask 0xffffff00 broadcast 198.51.100.255 + media: Ethernet autoselect (1000baseT ) + status: active + nd6 options=23 +EOF + want_extract_ipv6_global => "2001:db8:450a:e723:20c:29ff:fe9f:c532", + want_ipv6gua_from_if => "2001:db8:450a:e723:20c:29ff:fe9f:c532", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:3:20c:29ff:fe9f:c532", + want_ipv4_from_if => "198.51.100.207", + }, + { name => "ifconfig -L en0 (MacOS autoconf IPv6)", + MacOS => 1, + text => < mtu 9000 + options=50b + ether 00:00:00:90:32:8f + inet6 fe80::85b:d150:cdd9:3198%en0 prefixlen 64 secured scopeid 0x4 + inet6 2001:db8:450a:e723:1c99:99e2:21d0:79e6 prefixlen 64 autoconf secured pltime 86205 vltime 86205 + inet6 2001:db8:450a:e723:808d:d894:e4db:157e prefixlen 64 deprecated autoconf temporary pltime 0 vltime 86205 + inet6 fdb6:1d86:d9bd:3:837:e1c7:4895:269e prefixlen 64 autoconf secured pltime 86205 vltime 86205 + inet6 fdb6:1d86:d9bd:3:a0b3:aa4d:9e76:e1ab prefixlen 64 deprecated autoconf temporary pltime 0 vltime 86205 + inet 198.51.100.5 netmask 0xffffff00 broadcast 198.51.100.255 + inet6 2001:db8:450a:e723:2474:39fd:f5c0:6845 prefixlen 64 autoconf temporary pltime 76882 vltime 86205 + inet6 fdb6:1d86:d9bd:3:2474:39fd:f5c0:6845 prefixlen 64 autoconf temporary pltime 76882 vltime 86205 + inet6 fdb6:1d86:d9bd:3::8076 prefixlen 64 dynamic pltime 78010 vltime 78010 + nd6 options=201 + media: 1000baseT + status: active +EOF + want_extract_ipv6_global => "2001:db8:450a:e723:1c99:99e2:21d0:79e6", + want_ipv6gua_from_if => "2001:db8:450a:e723:1c99:99e2:21d0:79e6", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:3::8076", + want_ipv4_from_if => "198.51.100.5", + }, + { name => "ifconfig -L en0 (MacOS static IPv6)", + MacOS => 1, + text => < mtu 1500 + options=400 + ether 00:00:00:42:96:eb + inet 198.51.100.199 netmask 0xffffff00 broadcast 198.51.100.255 + inet6 fe80::1445:78b9:1d5c:11eb%en1 prefixlen 64 secured scopeid 0x5 + inet6 2001:db8:450a:e723::100 prefixlen 64 + nd6 options=201 + media: autoselect + status: active +EOF + want_extract_ipv6_global => "2001:db8:450a:e723::100", + want_ipv6gua_from_if => "2001:db8:450a:e723::100", + want_ipv4_from_if => "198.51.100.199", + }, + { name => "ifconfig -L em0 (FreeBSD autoconf IPv6)", + MacOS => 1, + text => < metric 0 mtu 1500 + options=81009b + ether 00:00:00:9f:c5:32 + inet6 fe80::20c:29ff:fe9f:c532%em0 prefixlen 64 scopeid 0x1 + inet6 2001:db8:450a:e723:20c:29ff:fe9f:c532 prefixlen 64 autoconf pltime 86114 vltime 86114 + inet6 fdb6:1d86:d9bd:3:20c:29ff:fe9f:c532 prefixlen 64 autoconf pltime 86114 vltime 86114 + inet 198.51.100.207 netmask 0xffffff00 broadcast 198.51.100.255 + media: Ethernet autoselect (1000baseT ) + status: active + nd6 options=23 +EOF + want_extract_ipv6_global => "2001:db8:450a:e723:20c:29ff:fe9f:c532", + want_ipv6gua_from_if => "2001:db8:450a:e723:20c:29ff:fe9f:c532", + want_ipv6ula_from_if => "fdb6:1d86:d9bd:3:20c:29ff:fe9f:c532", + want_ipv4_from_if => "198.51.100.207", + }, + { name => "ip -4 -o addr show dev eth0 scope global (Buildroot IPv4)", + text => < "198.51.157.237", + }, + { name => "ip -6 -o addr show dev eth0 scope global (Buildroot IPv6)", + text => < "2001:db8:450b:13f:ed44:eb63:b070:212f", + want_ipv6gua_from_if => "2001:db8:450b:13f:ed44:eb63:b070:212f", + }, + { name => "ifconfig eth0 (Busybox)", + text => < "2001:db8:450b:13f:ed44:eb63:b070:212f", + want_ipv6gua_from_if => "2001:db8:450b:13f:ed44:eb63:b070:212f", + want_ipv4_from_if => "198.51.157.237", }, );