From 5d545aae5c01c81046e2262626400bdbcd7c070b Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 13 Jun 2024 01:20:13 -0400 Subject: [PATCH 1/4] Simplify arg assignment for readability --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index ae9ab9e1..ecc38979 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2860,7 +2860,7 @@ sub get_ip { local $_l = pushlogctx("use=$use $argvar=" . ($arg // '')); if ($use eq 'ip') { - $ip = opt('ip', $h); + $ip = $arg; if (!is_ipv4($ip) && !is_ipv6($ip)) { warning('not a valid IPv4 or IPv6 address'); $ip = undef; @@ -2874,7 +2874,7 @@ sub get_ip { $reply = '' if $?; } } elsif ($use eq 'web') { - $url = opt('web', $h) // ''; + $url = $arg; $skip = opt('web-skip', $h); if (my $biw = $builtinweb{$url}) { warning("'$arg' is deprecated: $biw->{deprecated}") if $biw->{deprecated}; From f3678ce119c1d3291064f103222ee29f609db442 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 14 Jun 2024 18:58:05 -0400 Subject: [PATCH 2/4] Don't get host-specific values of global options --- ddclient.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddclient.in b/ddclient.in index ecc38979..80b83dc4 100755 --- a/ddclient.in +++ b/ddclient.in @@ -2883,7 +2883,7 @@ sub get_ip { } if ($url) { $reply = geturl( - proxy => opt('proxy', $h), + proxy => opt('proxy'), url => $url, ssl_validate => opt('web-ssl-validate', $h), ); @@ -3287,7 +3287,7 @@ sub get_ipv4 { } if ($url) { $reply = geturl( - proxy => opt('proxy', $h), + proxy => opt('proxy'), url => $url, ipversion => 4, # when using a URL to find IPv4 address we should force use of IPv4 ssl_validate => opt('web-ssl-validate', $h), From c71f6f6eaea0967e5d9d21e41641c65b4f00b8f6 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 14 Jun 2024 00:23:35 -0400 Subject: [PATCH 3/4] Prefetch the data relevant to the `use*` strategies This is a preparatory step to improving the deduplication of queries. It also makes possible future improvements to config validation and help usage output. --- ddclient.in | 371 ++++++++++++++++++++++++++----------------- t/builtinfw_query.pl | 153 ++++++++++++++---- t/skip.pl | 14 +- t/ssl-validate.pl | 4 +- t/use_web.pl | 6 +- 5 files changed, 363 insertions(+), 185 deletions(-) diff --git a/ddclient.in b/ddclient.in index 80b83dc4..29bb1ec6 100755 --- a/ddclient.in +++ b/ddclient.in @@ -279,11 +279,11 @@ our %builtinweb = ( ); sub query_cisco { - my ($h, $asa, $v4) = @_; + my ($asa, $v4, %p) = @_; warning("'--if' is deprecated; use '--ifv4' instead") - if ($v4 && !defined(opt('ifv4', $h)) && defined(opt('if', $h))); - my $if = ($v4 ? opt('ifv4', $h) : undef) // opt('if', $h); - my $fw = ($v4 ? opt('fwv4', $h) : undef) // opt('fw', $h); + if ($v4 && !defined($p{'ifv4'}) && defined($p{'if'})); + my $if = ($v4 ? $p{'ifv4'} : undef) // $p{'if'}; + my $fw = ($v4 ? $p{'fwv4'} : undef) // $p{'fw'}; # Convert slashes to protected value "\/" $if =~ s%\/%\\\/%g; # Protect special HTML characters (like '?') @@ -292,10 +292,10 @@ sub query_cisco { url => ($asa) ? "https://$fw/exec/show%20interface%20$if" : "http://$fw/level/1/exec/show/ip/interface/brief/$if/CR", - login => opt('fw-login', $h), - password => opt('fw-password', $h), + login => $p{'fw-login'}, + password => $p{'fw-password'}, ignore_ssl_option => 1, - ssl_validate => opt('fw-ssl-validate', $h), + ssl_validate => $p{'fw-ssl-validate'}, ); return undef if !header_ok($reply, \&warning); $reply =~ s/^.*?\n\n//s; @@ -345,14 +345,18 @@ our %builtinfw = ( }, 'cisco' => { 'name' => 'Cisco FW', - 'query' => sub { return query_cisco($_[0], 0, 0); }, - 'queryv4' => sub { return query_cisco($_[0], 0, 1); }, + 'query' => sub { return query_cisco(0, 0, @_); }, + 'inputs' => ['fw', 'if', 'fw-login', 'fw-password', 'fw-ssl-validate'], + 'queryv4' => sub { return query_cisco(0, 1, @_); }, + 'inputsv4' => ['fwv4', 'fw', 'ifv4', 'if', 'fw-login', 'fw-password', 'fw-ssl-validate'], 'help' => sub { return " at the host given by --fw$_[0]= and interface given by --if$_[0]="; }, }, 'cisco-asa' => { 'name' => 'Cisco ASA', - 'query' => sub { return query_cisco($_[0], 1, 0); }, - 'queryv4' => sub { return query_cisco($_[0], 1, 1); }, + 'query' => sub { return query_cisco(1, 0, @_); }, + 'inputs' => ['fw', 'if', 'fw-login', 'fw-password', 'fw-ssl-validate'], + 'queryv4' => sub { return query_cisco(1, 1, @_); }, + 'inputsv4' => ['fwv4', 'fw', 'ifv4', 'if', 'fw-login', 'fw-password', 'fw-ssl-validate'], 'help' => sub { return " at the host given by --fw$_[0]= and interface given by --if$_[0]="; }, }, 'dlink-524' => { @@ -552,66 +556,109 @@ our %builtinfw = ( }, ); -my %ip_strategies = ( - 'disabled' => ": do not use a deprecated method to obtain an IP address for this host", - 'no' => ": deprecated, see '--use=disabled'", - 'ip' => ": deprecated, see '--usev4=ipv4' and '--usev6=ipv6'", - 'web' => ": deprecated, see '--usev4=webv4' and '--usev6=webv6'", - 'fw' => ": deprecated, see '--usev4=fwv4' and '--usev6=fwv6'", - 'if' => ": deprecated, see '--usev4=ifv4' and '--usev6=ifv6'", - 'cmd' => ": deprecated, see '--usev4=cmdv4' and '--usev6=cmdv6'", - map({ - my $fw = $builtinfw{$_}; - $_ => ": deprecated, see '--usev4=$_'" . - (defined($fw->{queryv6}) ? " and '--usev6=$_'" : ''); - } keys(%builtinfw)), +sub builtinfw_strategy { + my ($n) = @_; + my $fw = $builtinfw{$n}; + return ($n => { + help => ": deprecated, see '--usev4=$n'" . + (defined($fw->{queryv6}) ? " and '--usev6=$n'" : ''), + inputs => $fw->{inputs} // ['fw', 'fw-skip', 'fw-login', 'fw-password', 'fw-ssl-validate'], + }); +} + +our %ip_strategies = ( + 'disabled' => {help => ": do not use a deprecated method to obtain an IP address for this host", + inputs => []}, + 'no' => {help => ": deprecated, see '--use=disabled'", + inputs => []}, + 'ip' => {help => ": deprecated, see '--usev4=ipv4' and '--usev6=ipv6'", + inputs => ['ip']}, + 'web' => {help => ": deprecated, see '--usev4=webv4' and '--usev6=webv6'", + inputs => ['web', 'web-skip', 'proxy', 'web-ssl-validate']}, + 'fw' => {help => ": deprecated, see '--usev4=fwv4' and '--usev6=fwv6'", + inputs => ['fw', 'fw-skip', 'fw-login', 'fw-password', 'fw-ssl-validate']}, + 'if' => {help => ": deprecated, see '--usev4=ifv4' and '--usev6=ifv6'", + inputs => ['if']}, + 'cmd' => {help => ": deprecated, see '--usev4=cmdv4' and '--usev6=cmdv6'", + inputs => ['cmd', 'cmd-skip']}, + map(builtinfw_strategy($_), keys(%builtinfw)), ); sub ip_strategies_usage { - return map({ sprintf(" --use=%-22s %s.", $_, $ip_strategies{$_}) } + return map({ sprintf(" --use=%-22s %s.", $_, $ip_strategies{$_}{help}); } 'disabled', 'no', 'ip', 'web', 'if', 'cmd', 'fw', sort(keys(%builtinfw))); } -my %ipv4_strategies = ( - 'disabled' => ": do not obtain an IPv4 address for this host (except possibly via the deprecated '--use' option, if it is enabled)", - 'ipv4' => ": obtain IPv4 from the address given by --ipv4=
", - 'webv4' => ": obtain IPv4 from an IP discovery page on the web", - 'ifv4' => ": obtain IPv4 from the interface given by --ifv4=", - 'cmdv4' => ": obtain IPv4 from the command given by --cmdv4=", - 'fwv4' => ": obtain IPv4 from the URL given by --fwv4=", - map({ - my $fw = $builtinfw{$_}; - $_ => defined($fw->{queryv4}) +sub builtinfwv4_strategy { + my ($n) = @_; + my $fw = $builtinfw{$n}; + return ($n => { + help => defined($fw->{queryv4}) ? ": obtain IPv4 from $fw->{name}@{[($fw->{help} // sub {})->('v4') // '']}" - : ": obtain IPv4 from $fw->{name} at the host or URL given by --fwv4="; - } keys(%builtinfw)), + : ": obtain IPv4 from $fw->{name} at the host or URL given by --fwv4=", + inputs => $fw->{inputsv4} // ['fwv4', 'fw', 'fwv4-skip', 'fw-skip', 'fw-login', + 'fw-password', 'fw-ssl-validate'], + }); +} + +our %ipv4_strategies = ( + 'disabled' => {help => ": do not obtain an IPv4 address for this host (except possibly via the deprecated '--use' option, if it is enabled)", + inputs => []}, + 'ipv4' => {help => ": obtain IPv4 from the address given by --ipv4=
", + inputs => ['ipv4']}, + 'webv4' => {help => ": obtain IPv4 from an IP discovery page on the web", + inputs => ['webv4', 'webv4-skip', 'proxy', 'web-ssl-validate']}, + 'ifv4' => {help => ": obtain IPv4 from the interface given by --ifv4=", + inputs => ['ifv4']}, + 'cmdv4' => {help => ": obtain IPv4 from the command given by --cmdv4=", + inputs => ['cmdv4', 'cmd-skip']}, + 'fwv4' => {help => ": obtain IPv4 from the URL given by --fwv4=", + inputs => ['fwv4', 'fw', 'fwv4-skip', 'fw-skip', 'fw-login', 'fw-password', 'fw-ssl-validate']}, + map(builtinfwv4_strategy($_), keys(%builtinfw)), ); sub ipv4_strategies_usage { - return map({ sprintf(" --usev4=%-22s %s.", $_, $ipv4_strategies{$_}) } + return map({ sprintf(" --usev4=%-22s %s.", $_, $ipv4_strategies{$_}{help}) } 'disabled', 'ipv4', 'webv4', 'ifv4', 'cmdv4', 'fwv4', sort(keys(%builtinfw))); } -my %ipv6_strategies = ( - 'disabled' => ": do not obtain an IPv6 address for this host (except possibly via the deprecated '--use' option, if it is enabled)", - 'no' => ": deprecated, use '--usev6=disabled'", - 'ipv6' => ": obtain IPv6 from the address given by --ipv6=
", - 'ip' => ": deprecated, use '--usev6=ipv6'", - 'webv6' => ": obtain IPv6 from an IP discovery page on the web", - 'web' => ": deprecated, use '--usev6=webv6'", - 'ifv6' => ": obtain IPv6 from the interface given by --ifv6=", - 'if' => ": deprecated, use '--usev6=ifv6'", - 'cmdv6' => ": obtain IPv6 from the command given by --cmdv6=", - 'cmd' => ": deprecated, use '--usev6=cmdv6'", - 'fwv6' => ": obtain IPv6 from the URL given by --fwv6=", - map({ - my $fw = $builtinfw{$_}; - defined($fw->{queryv6}) - ? ($_ => ": obtain IPv6 from $fw->{name}@{[($fw->{help} // sub {})->('v6') // '']}") - : (); - } keys(%builtinfw)), +sub builtinfwv6_strategy { + my ($n) = @_; + my $fw = $builtinfw{$n}; + return defined($fw->{queryv6}) + ? ($n => { + help => ": obtain IPv6 from $fw->{name}@{[($fw->{help} // sub {})->('v6') // '']}", + inputs => $fw->{inputsv6} // ['fwv6', 'fwv6-skip'], + }) + : (); +} + +our %ipv6_strategies = ( + 'disabled' => {help => ": do not obtain an IPv6 address for this host (except possibly via the deprecated '--use' option, if it is enabled)", + inputs => []}, + 'no' => {help => ": deprecated, use '--usev6=disabled'", + inputs => []}, + 'ipv6' => {help => ": obtain IPv6 from the address given by --ipv6=
", + inputs => ['ipv6', 'ip']}, + 'ip' => {help => ": deprecated, use '--usev6=ipv6'", + inputs => ['ipv6', 'ip']}, + 'webv6' => {help => ": obtain IPv6 from an IP discovery page on the web", + inputs => ['webv6', 'web', 'webv6-skip', 'web-skip', 'proxy', 'web-ssl-validate']}, + 'web' => {help => ": deprecated, use '--usev6=webv6'", + inputs => ['webv6', 'web', 'webv6-skip', 'web-skip', 'proxy', 'web-ssl-validate']}, + 'ifv6' => {help => ": obtain IPv6 from the interface given by --ifv6=", + inputs => ['ifv6', 'if']}, + 'if' => {help => ": deprecated, use '--usev6=ifv6'", + inputs => ['ifv6', 'if']}, + 'cmdv6' => {help => ": obtain IPv6 from the command given by --cmdv6=", + inputs => ['cmdv6', 'cmd', 'cmd-skip']}, + 'cmd' => {help => ": deprecated, use '--usev6=cmdv6'", + inputs => ['cmdv6', 'cmd', 'cmd-skip']}, + 'fwv6' => {help => ": obtain IPv6 from the URL given by --fwv6=", + inputs => ['fwv6', 'fwv6-skip']}, + map(builtinfwv6_strategy($_), keys(%builtinfw)), ); sub ipv6_strategies_usage { - return map({ sprintf(" --usev6=%-22s %s.", $_, $ipv6_strategies{$_}) } + return map({ sprintf(" --usev6=%-22s %s.", $_, $ipv6_strategies{$_}{help}) } 'disabled', 'no', 'ipv6', 'ip', 'webv6', 'web', 'ifv6', 'if', 'cmdv6', 'cmd', 'fwv6', sort(map({exists($ipv6_strategies{$_}) ? ($_) : ()} keys(%builtinfw)))); } @@ -1525,7 +1572,7 @@ sub update_nics { $ip = $iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd}; } else { # Else need to find the IP address... - $ip = get_ip($use, $h); + $ip = get_ip(strategy_inputs('use', $h)); if (is_ipv4($ip) || is_ipv6($ip)) { # And if it is valid, remember it... $iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd} = $ip; @@ -1542,7 +1589,7 @@ sub update_nics { $ipv4 = $ipv4list{$usev4}{$arg_ipv4}{$arg_fwv4}{$arg_ifv4}{$arg_webv4}{$arg_cmdv4}; } else { # Else need to find the IPv4 address... - $ipv4 = get_ipv4($usev4, $h); + $ipv4 = get_ipv4(strategy_inputs('usev4', $h)); if (is_ipv4($ipv4)) { # And if it is valid, remember it... $ipv4list{$usev4}{$arg_ipv4}{$arg_fwv4}{$arg_ifv4}{$arg_webv4}{$arg_cmdv4} = $ipv4; @@ -1559,7 +1606,7 @@ sub update_nics { $ipv6 = $ipv6list{$usev6}{$arg_ipv6}{$arg_fwv6}{$arg_ifv6}{$arg_webv6}{$arg_cmdv6}; } else { # Else need to find the IPv6 address... - $ipv6 = get_ipv6($usev6, $h); + $ipv6 = get_ipv6(strategy_inputs('usev6', $h)); if (is_ipv6($ipv6)) { # And if it is valid, remember it... $ipv6list{$usev6}{$arg_ipv6}{$arg_fwv6}{$arg_ifv6}{$arg_webv6}{$arg_cmdv6} = $ipv6; @@ -2143,7 +2190,8 @@ sub test_possible_ip { printf "----- Test_possible_ip with 'get_ip' -----\n"; if (defined(opt('ip'))) { local $opt{'use'} = 'ip'; - printf "use=ip, ip=%s address is %s\n", opt('ip'), get_ip('ip') // 'NOT FOUND'; + printf("use=ip, ip=%s address is %s\n", + opt('ip'), get_ip(strategy_inputs('use')) // 'NOT FOUND'); } { local $opt{'use'} = 'if'; @@ -2157,18 +2205,21 @@ sub test_possible_ip { warning("failed to get list of interfaces") if !@ifs; for my $if (@ifs) { local $opt{'if'} = $if; - printf "use=if, if=%s address is %s\n", opt('if'), get_ip('if') // 'NOT FOUND'; + printf("use=if, if=%s address is %s\n", + opt('if'), get_ip(strategy_inputs('use')) // 'NOT FOUND'); } } if (opt('fw')) { if (opt('fw') !~ m%/%) { for my $fw (sort keys %builtinfw) { local $opt{'use'} = $fw; - printf "use=%s address is %s\n", $fw, get_ip($fw) // 'NOT FOUND'; + printf("use=%s address is %s\n", + $fw, get_ip(strategy_inputs('use')) // 'NOT FOUND'); } } local $opt{'use'} = 'fw'; - printf "use=fw, fw=%s address is %s\n", opt('fw'), get_ip('fw') // 'NOT FOUND' + printf("use=fw, fw=%s address is %s\n", + opt('fw'), get_ip(strategy_inputs('use')) // 'NOT FOUND') if !exists $builtinfw{opt('fw')}; } @@ -2176,21 +2227,25 @@ sub test_possible_ip { local $opt{'use'} = 'web'; for my $web (sort keys %builtinweb) { local $opt{'web'} = $web; - printf "use=web, web=%s address is %s\n", $web, get_ip('web') // 'NOT FOUND'; + printf("use=web, web=%s address is %s\n", + $web, get_ip(strategy_inputs('use')) // 'NOT FOUND'); } - printf "use=web, web=%s address is %s\n", opt('web'), get_ip('web') // 'NOT FOUND' + printf("use=web, web=%s address is %s\n", + opt('web'), get_ip(strategy_inputs('use')) // 'NOT FOUND') if !exists $builtinweb{opt('web')}; } if (opt('cmd')) { local $opt{'use'} = 'cmd'; - printf "use=cmd, cmd=%s address is %s\n", opt('cmd'), get_ip('cmd') // 'NOT FOUND'; + printf("use=cmd, cmd=%s address is %s\n", + opt('cmd'), get_ip(strategy_inputs('use')) // 'NOT FOUND'); } # Now force IPv4 printf "----- Test_possible_ip with 'get_ipv4' ------\n"; if (defined(opt('ipv4'))) { local $opt{'usev4'} = 'ipv4'; - printf "usev4=ipv4, ipv4=%s address is %s\n", opt('ipv4'), get_ipv4('ipv4') // 'NOT FOUND'; + printf("usev4=ipv4, ipv4=%s address is %s\n", + opt('ipv4'), get_ipv4(strategy_inputs('usev4')) // 'NOT FOUND'); } { local $opt{'usev4'} = 'ifv4'; @@ -2204,29 +2259,34 @@ sub test_possible_ip { warning("failed to get list of interfaces") if !@ifs; for my $if (@ifs) { local $opt{'ifv4'} = $if; - printf "usev4=ifv4, ifv4=%s address is %s\n", opt('ifv4'), get_ipv4('ifv4') // 'NOT FOUND'; + printf("usev4=ifv4, ifv4=%s address is %s\n", + opt('ifv4'), get_ipv4(strategy_inputs('usev4')) // 'NOT FOUND'); } } { local $opt{'usev4'} = 'webv4'; for my $web (sort keys %builtinweb) { local $opt{'webv4'} = $web; - printf "usev4=webv4, webv4=$web address is %s\n", get_ipv4('webv4') // 'NOT FOUND' + printf("usev4=webv4, webv4=%s address is %s\n", + $web, get_ipv4(strategy_inputs('usev4')) // 'NOT FOUND') if ($web !~ "6") ## Don't bother if web site only supports IPv6; } - printf "usev4=webv4, webv4=%s address is %s\n", opt('webv4'), get_ipv4('webv4') // 'NOT FOUND' + printf("usev4=webv4, webv4=%s address is %s\n", + opt('webv4'), get_ipv4(strategy_inputs('usev4')) // 'NOT FOUND') if ! exists $builtinweb{opt('webv4')}; } if (opt('cmdv4')) { local $opt{'usev4'} = 'cmdv4'; - printf "usev4=cmdv4, cmdv4=%s address is %s\n", opt('cmdv4'), get_ipv4('cmdv4') // 'NOT FOUND'; + printf("usev4=cmdv4, cmdv4=%s address is %s\n", + opt('cmdv4'), get_ipv4(strategy_inputs('usev4')) // 'NOT FOUND'); } # Now force IPv6 printf "----- Test_possible_ip with 'get_ipv6' -----\n"; if (defined(opt('ipv6'))) { local $opt{'usev6'} = 'ipv6'; - printf "usev6=ipv6, ipv6=%s address is %s\n", opt('ipv6'), get_ipv6('ipv6') // 'NOT FOUND'; + printf("usev6=ipv6, ipv6=%s address is %s\n", + opt('ipv6'), get_ipv6(strategy_inputs('usev6')) // 'NOT FOUND'); } { local $opt{'usev6'} = 'ifv6'; @@ -2240,22 +2300,26 @@ sub test_possible_ip { warning("failed to get list of interfaces") if !@ifs; for my $if (@ifs) { local $opt{'ifv6'} = $if; - printf "usev6=ifv6, ifv6=%s address is %s\n", opt('ifv6'), get_ipv6('ifv6') // 'NOT FOUND'; + printf("usev6=ifv6, ifv6=%s address is %s\n", + opt('ifv6'), get_ipv6(strategy_inputs('usev6')) // 'NOT FOUND'); } } { local $opt{'usev6'} = 'webv6'; for my $web (sort keys %builtinweb) { local $opt{'webv6'} = $web; - printf "usev6=webv6, webv6=$web address is %s\n", get_ipv6('webv6') // 'NOT FOUND' + printf("usev6=webv6, webv6=%s address is %s\n", + $web, get_ipv6(strategy_inputs('usev6')) // 'NOT FOUND') if ($web !~ "4"); ## Don't bother if web site only supports IPv4 } - printf "usev6=webv6, webv6=%s address is %s\n", opt('webv6'), get_ipv6('webv6') // 'NOT FOUND' + printf("usev6=webv6, webv6=%s address is %s\n", + opt('webv6'), get_ipv6(strategy_inputs('usev6')) // 'NOT FOUND') if ! exists $builtinweb{opt('webv6')}; } if (opt('cmdv6')) { local $opt{'usev6'} = 'cmdv6'; - printf "usev6=cmdv6, cmdv6=%s address is %s\n", opt('cmdv6'), get_ipv6('cmdv6') // 'NOT FOUND'; + printf("usev6=cmdv6, cmdv6=%s address is %s\n", + opt('cmdv6'), get_ipv6(strategy_inputs('usev6')) // 'NOT FOUND'); } exit 0 unless opt('debug'); @@ -2847,35 +2911,51 @@ sub geturl { return $reply; } +# Collects and returns all configuration data that get_ip* needs to determine the IP address. This +# makes it possible to avoid redundant queries by comparing the configuration data for different +# hosts. +sub strategy_inputs { + my ($whichuse, $h) = @_; + my $use = opt($whichuse, $h); + my $strategies + = $whichuse eq 'use' ? \%ip_strategies + : $whichuse eq 'usev4' ? \%ipv4_strategies + : $whichuse eq 'usev6' ? \%ipv6_strategies + : undef; + my $s = $strategies->{$use}; + my @v = @{$s->{inputs} // []}; + return map({ $_ => opt($_, $h); } $whichuse, @v); +} + ###################################################################### ## get_ip ###################################################################### sub get_ip { - my ($use, $h) = @_; + my %p = @_; my ($ip, $reply, $url, $skip) = (undef, ''); - my $argvar = $use; + my $argvar = $p{'use'}; # Note that --use=firewallname uses --fw=arg, not --firewallname=arg. - $argvar = 'fw' if $builtinfw{$use}; - my $arg = opt($argvar, $h); - local $_l = pushlogctx("use=$use $argvar=" . ($arg // '')); + $argvar = 'fw' if $builtinfw{$p{'use'}}; + my $arg = $p{$argvar}; + local $_l = pushlogctx("use=$p{'use'} $argvar=" . ($arg // '')); - if ($use eq 'ip') { + if ($p{'use'} eq 'ip') { $ip = $arg; if (!is_ipv4($ip) && !is_ipv6($ip)) { warning('not a valid IPv4 or IPv6 address'); $ip = undef; } - } elsif ($use eq 'if') { + } elsif ($p{'use'} eq 'if') { $ip = get_ip_from_interface($arg); - } elsif ($use eq 'cmd') { + } elsif ($p{'use'} eq 'cmd') { if ($arg) { - $skip = opt('cmd-skip', $h); + $skip = $p{'cmd-skip'}; $reply = `$arg`; $reply = '' if $?; } - } elsif ($use eq 'web') { + } elsif ($p{'use'} eq 'web') { $url = $arg; - $skip = opt('web-skip', $h); + $skip = $p{'web-skip'}; if (my $biw = $builtinweb{$url}) { warning("'$arg' is deprecated: $biw->{deprecated}") if $biw->{deprecated}; $skip //= $biw->{skip}; @@ -2885,7 +2965,7 @@ sub get_ip { $reply = geturl( proxy => opt('proxy'), url => $url, - ssl_validate => opt('web-ssl-validate', $h), + ssl_validate => $p{'web-ssl-validate'}, ); if (header_ok($reply, \&warning)) { $reply =~ s/^.*?\n\n//s; @@ -2893,17 +2973,17 @@ sub get_ip { $reply = undef; } } - } elsif ($use eq 'disabled') { + } elsif ($p{'use'} eq 'disabled') { ## This is a no-op... Do not get an IP address for this host/service $reply = ''; - } elsif ($use eq 'fw' || defined(my $fw = $builtinfw{$use})) { + } elsif ($p{'use'} eq 'fw' || defined(my $fw = $builtinfw{$p{'use'}})) { $url = $arg; - $skip = opt('fw-skip', $h); + $skip = $p{'fw-skip'}; if ($fw) { $skip //= $fw->{'skip'}; if (defined(my $query = $fw->{'query'})) { $url = undef; - $reply = $query->($h); + $reply = $query->(%p); } else { $url = "http://$url$fw->{'url'}" unless $url =~ /\//; } @@ -2911,10 +2991,10 @@ sub get_ip { if ($url) { $reply = geturl( url => $url, - login => opt('fw-login', $h), - password => opt('fw-password', $h), + login => $p{'fw-login'}, + password => $p{'fw-password'}, ignore_ssl_option => 1, - ssl_validate => opt('fw-ssl-validate', $h), + ssl_validate => $p{'fw-ssl-validate'}, ); if (header_ok($reply, \&warning)) { $reply =~ s/^.*?\n\n//s; @@ -2923,7 +3003,7 @@ sub get_ip { } } } else { - warning("ignoring unsupported '--use' strategy: $use"); + warning("ignoring unsupported '--use' strategy: $p{'use'}"); } if (!defined $reply) { $reply = ''; @@ -2933,7 +3013,7 @@ sub get_ip { $reply =~ s/^.*?${skip}//is; } $ip //= extract_ipv4($reply) // extract_ipv6($reply); - if ($use ne 'ip' && ($ip // '') eq '0.0.0.0') { + if ($p{'use'} ne 'ip' && ($ip // '') eq '0.0.0.0') { $ip = undef; } warning('did not find an IPv4 or IPv6 address') if !defined($ip); @@ -3246,40 +3326,41 @@ sub get_ip_from_interface { ## get_ipv4 ###################################################################### sub get_ipv4 { - my ($usev4, $h) = @_; + my %p = @_; my $ipv4 = undef; ## Found IPv4 address my $reply = ''; ## Text returned from various methods my $url = ''; ## URL of website or firewall my $skip = undef; ## Regex of pattern to skip before looking for IP - my $argvar = $usev4; + my $argvar = $p{'usev4'}; # Note that --usev4=firewallname uses --fwv4=arg, not --firewallname=arg. - $argvar = (defined(opt('fwv4', $h)) || !defined(opt('fw', $h))) ? 'fwv4' : 'fw' - if $builtinfw{$usev4}; - my $arg = opt($argvar, $h); - local $_l = pushlogctx("usev4=$usev4 $argvar=" . ($arg // '')); + $argvar = (defined($p{'fwv4'}) || !defined($p{'fw'})) ? 'fwv4' : 'fw' + if $builtinfw{$p{'usev4'}}; + my $arg = $p{$argvar}; + local $_l = pushlogctx("usev4=$p{'usev4'} $argvar=" . ($arg // '')); - if ($usev4 eq 'ipv4') { + if ($p{'usev4'} eq 'ipv4') { ## Static IPv4 address is provided in "ipv4=
" $ipv4 = $arg; if (!is_ipv4($ipv4)) { warning('not a valid IPv4 address'); $ipv4 = undef; } - } elsif ($usev4 eq 'ifv4') { + } elsif ($p{'usev4'} eq 'ifv4') { ## Obtain IPv4 address from interface mamed in "ifv4=" $ipv4 = get_ip_from_interface($arg, 4); - } elsif ($usev4 eq 'cmdv4') { + } elsif ($p{'usev4'} eq 'cmdv4') { ## Obtain IPv4 address by executing the command in "cmdv4=" - warning("'--cmd-skip' ignored") if (opt('verbose') && opt('cmd-skip', $h)); + warning("'--cmd-skip' ignored for '--usev4=$p{'usev4'}'") + if (opt('verbose') && $p{'cmd-skip'}); if ($arg) { my $sys_cmd = quotemeta($arg); $reply = qx{$sys_cmd}; $reply = '' if $?; } - } elsif ($usev4 eq 'webv4') { + } elsif ($p{'usev4'} eq 'webv4') { ## Obtain IPv4 address by accessing website at url in "webv4=" $url = $arg; - $skip = opt('webv4-skip', $h); + $skip = $p{'webv4-skip'}; if (my $biw = $builtinweb{$url}) { warning("'$arg' is deprecated: $biw->{deprecated}") if $biw->{deprecated}; $skip //= $biw->{skip}; @@ -3290,7 +3371,7 @@ sub get_ipv4 { proxy => opt('proxy'), url => $url, ipversion => 4, # when using a URL to find IPv4 address we should force use of IPv4 - ssl_validate => opt('web-ssl-validate', $h), + ssl_validate => $p{'web-ssl-validate'}, ); if (header_ok($reply, \&warning)) { $reply =~ s/^.*?\n\n//s; @@ -3298,21 +3379,21 @@ sub get_ipv4 { $reply = undef; } } - } elsif ($usev4 eq 'disabled') { + } elsif ($p{'usev4'} eq 'disabled') { ## This is a no-op... Do not get an IPv4 address for this host/service $reply = ''; - } elsif ($usev4 eq 'fwv4' || defined(my $fw = $builtinfw{$usev4})) { + } elsif ($p{'usev4'} eq 'fwv4' || defined(my $fw = $builtinfw{$p{'usev4'}})) { warning("'--fw' is deprecated; use '--fwv4' instead") - if (!defined(opt('fwv4', $h)) && defined(opt('fw', $h))); + if (!defined($p{'fwv4'}) && defined($p{'fw'})); warning("'--fw-skip' is deprecated; use '--fwv4-skip' instead") - if (!defined(opt('fwv4-skip', $h)) && defined(opt('fw-skip', $h))); + if (!defined($p{'fwv4-skip'}) && defined($p{'fw-skip'})); $url = $arg; - $skip = opt('fwv4-skip', $h) // opt('fw-skip', $h); + $skip = $p{'fwv4-skip'} // $p{'fw-skip'}; if ($fw) { $skip //= $fw->{'skip'}; if (defined(my $query = $fw->{'queryv4'})) { $url = undef; - $reply = $query->($h); + $reply = $query->(%p); } else { $url = "http://$url$fw->{'url'}" unless $url =~ /\//; } @@ -3320,11 +3401,11 @@ sub get_ipv4 { if ($url) { $reply = geturl( url => $url, - login => opt('fw-login', $h), - password => opt('fw-password', $h), + login => $p{'fw-login'}, + password => $p{'fw-password'}, ipversion => 4, # when using a URL to find IPv4 address we should force use of IPv4 ignore_ssl_option => 1, - ssl_validate => opt('fw-ssl-validate', $h), + ssl_validate => $p{'fw-ssl-validate'}, ); if (header_ok($reply, \&warning)) { $reply =~ s/^.*?\n\n//s; @@ -3333,7 +3414,7 @@ sub get_ipv4 { } } } else { - warning("ignoring unsupported '--usev4' strategy: $usev4"); + warning("ignoring unsupported '--usev4' strategy: $p{'usev4'}"); } ## Set to loopback address if no text set yet @@ -3345,7 +3426,7 @@ sub get_ipv4 { ## If $ipv4 not set yet look for IPv4 address in the $reply text $ipv4 //= extract_ipv4($reply); ## Return undef for loopback address unless statically assigned by "ipv4=0.0.0.0" - $ipv4 = undef if (($usev4 ne 'ipv4') && (($ipv4 // '') eq '0.0.0.0')); + $ipv4 = undef if (($p{'usev4'} ne 'ipv4') && (($ipv4 // '') eq '0.0.0.0')); warning('did not find an IPv4 address') if !defined($ipv4); debug("found IPv4 address: $ipv4") if $ipv4; return $ipv4; @@ -3355,46 +3436,46 @@ sub get_ipv4 { ## get_ipv6 ###################################################################### sub get_ipv6 { - my ($usev6, $h) = @_; + my %p = @_; my $ipv6 = undef; ## Found IPv6 address my $reply = ''; ## Text returned from various methods my $url = ''; ## URL of website or firewall my $skip = undef; ## Regex of pattern to skip before looking for IP - my $argvar = $usev6; - if (grep($usev6 eq $_, qw(ip if cmd web))) { - my $new = $usev6 . 'v6'; - warning("'--usev6=$usev6' is deprecated; use '--usev6=$new'"); - $argvar = $new if defined(opt($new, $h)); + my $argvar = $p{'usev6'}; + if (grep($p{'usev6'} eq $_, qw(ip if cmd web))) { + my $new = $p{'usev6'} . 'v6'; + warning("'--usev6=$p{'usev6'}' is deprecated; use '--usev6=$new'"); + $argvar = $new if defined($p{$new}); } # Note that --usev6=firewallname uses --fwv6=arg, not --firewallname=arg. - $argvar = 'fwv6' if $builtinfw{$usev6}; - my $arg = opt($argvar, $h); - local $_l = pushlogctx("usev6=$usev6 $argvar=" . ($arg // '')); + $argvar = 'fwv6' if $builtinfw{$p{'usev6'}}; + my $arg = $p{$argvar}; + local $_l = pushlogctx("usev6=$p{'usev6'} $argvar=" . ($arg // '')); - if ($usev6 eq 'ipv6' || $usev6 eq 'ip') { + if ($p{'usev6'} eq 'ipv6' || $p{'usev6'} eq 'ip') { ## Static IPv6 address is provided in "ipv6=
" $ipv6 = $arg; if (!is_ipv6($ipv6)) { warning('not a valid IPv6 address'); $ipv6 = undef; } - } elsif ($usev6 eq 'ifv6' || $usev6 eq 'if') { + } elsif ($p{'usev6'} eq 'ifv6' || $p{'usev6'} eq 'if') { ## Obtain IPv6 address from interface mamed in "ifv6=" $ipv6 = get_ip_from_interface($arg, 6); - } elsif ($usev6 eq 'cmdv6' || $usev6 eq 'cmd') { + } elsif ($p{'usev6'} eq 'cmdv6' || $p{'usev6'} eq 'cmd') { ## Obtain IPv6 address by executing the command in "cmdv6=" - warning("'--cmd-skip' ignored") if (opt('verbose') && opt('cmd-skip', $h)); + warning("'--cmd-skip' ignored") if opt('verbose') && p{'cmd-skip'}; if ($arg) { my $sys_cmd = quotemeta($arg); $reply = qx{$sys_cmd}; $reply = '' if $?; } - } elsif ($usev6 eq 'webv6' || $usev6 eq 'web') { + } elsif ($p{'usev6'} eq 'webv6' || $p{'usev6'} eq 'web') { ## Obtain IPv6 address by accessing website at url in "webv6=" warning("'--web-skip' ignored; use '--webv6-skip' instead") - if (!defined(opt('webv6-skip', $h)) && defined(opt('web-skip', $h))); + if (!defined($p{'webv6-skip'}) && defined($p{'web-skip'})); $url = $arg; - $skip = opt('webv6-skip', $h); + $skip = $p{'webv6-skip'}; if (my $biw = $builtinweb{$url}) { warning("'--webv6=$url' is deprecated! $biw->{deprecated}") if $biw->{deprecated}; $skip //= $biw->{skip}; @@ -3405,7 +3486,7 @@ sub get_ipv6 { proxy => opt('proxy'), url => $url, ipversion => 6, # when using a URL to find IPv6 address we should force use of IPv6 - ssl_validate => opt('web-ssl-validate', $h), + ssl_validate => $p{'web-ssl-validate'}, ); if (header_ok($reply, \&warning)) { $reply =~ s/^.*?\n\n//s; @@ -3413,18 +3494,18 @@ sub get_ipv6 { $reply = undef; } } - } elsif ($usev6 eq 'disabled') { + } elsif ($p{'usev6'} eq 'disabled') { $reply = ''; - } elsif ($usev6 eq 'fwv6' || defined(my $fw = $builtinfw{$usev6})) { - $skip = opt('fwv6-skip', $h) // $fw->{'skip'}; + } elsif ($p{'usev6'} eq 'fwv6' || defined(my $fw = $builtinfw{$p{'usev6'}})) { + $skip = $p{'fwv6-skip'} // $fw->{'skip'}; if ($fw && defined(my $query = $fw->{'queryv6'})) { $skip //= $fw->{'skip'}; - $reply = $query->($h); + $reply = $query->(%p); } else { warning("not implemented (does nothing)"); } } else { - warning("ignoring unsupported '--usev6' strategy: $usev6"); + warning("ignoring unsupported '--usev6' strategy: $p{'usev6'}"); } ## Set to loopback address if no text set yet @@ -3436,7 +3517,7 @@ sub get_ipv6 { ## If $ipv6 not set yet look for IPv6 address in the $reply text $ipv6 //= extract_ipv6($reply); ## Return undef for loopback address unless statically assigned by "ipv6=::" - $ipv6 = undef if (($usev6 ne 'ipv6') && ($usev6 ne 'ip') && (($ipv6 // '') eq '::')); + $ipv6 = undef if (($p{'usev6'} ne 'ipv6') && ($p{'usev6'} ne 'ip') && (($ipv6 // '') eq '::')); warning('did not find an IPv6 address') if !defined($ipv6); debug("found IPv6 address: $ipv6") if $ipv6; return $ipv6; diff --git a/t/builtinfw_query.pl b/t/builtinfw_query.pl index e5ea9496..d7b44508 100644 --- a/t/builtinfw_query.pl +++ b/t/builtinfw_query.pl @@ -2,26 +2,26 @@ BEGIN { SKIP: { eval { require Test::Warnings; 1; } or skip($@, 1); } } BEGIN { eval { require 'ddclient'; } or BAIL_OUT($@); } -my $got_host; -my $builtinfw = 't/builtinfw_query.pl'; -$ddclient::builtinfw{$builtinfw} = { - name => 'dummy device for testing', - query => sub { - ($got_host) = @_; - return '192.0.2.1 skip1 192.0.2.2 skip2 192.0.2.3'; - }, - queryv4 => sub { - ($got_host) = @_; - return '192.0.2.4 skip1 192.0.2.5 skip3 192.0.2.6'; - }, - queryv6 => sub { - ($got_host) = @_; - return '2001:db8::1 skip1 2001:db8::2 skip4 2001:db8::3'; - }, -}; -%ddclient::builtinfw if 0; # suppress spurious warning "Name used only once: possible typo" +sub setbuiltinfw { + my ($fw) = @_; + no warnings 'once'; + $ddclient::builtinfw{$fw->{name}} = $fw; + %ddclient::ip_strategies = ddclient::builtinfw_strategy($fw->{name}); + %ddclient::ipv4_strategies = ddclient::builtinfwv4_strategy($fw->{name}); + %ddclient::ipv6_strategies = ddclient::builtinfwv6_strategy($fw->{name}); +} + +my @gotcalls; -my @test_cases = ( +my $skip_test_fw = 't/builtinfw_query.pl skip test'; +setbuiltinfw({ + name => $skip_test_fw, + query => sub { return '192.0.2.1 skip1 192.0.2.2 skip2 192.0.2.3'; }, + queryv4 => sub { return '192.0.2.4 skip1 192.0.2.5 skip3 192.0.2.6'; }, + queryv6 => sub { return '2001:db8::1 skip1 2001:db8::2 skip4 2001:db8::3'; }, +}); + +my @skip_test_cases = ( { desc => 'query', getip => \&ddclient::get_ip, @@ -61,20 +61,109 @@ }, ); -for my $tc (@test_cases) { - subtest $tc->{desc} => sub { - my $h = "t/builtinfw_query.pl $tc->{desc}"; - $ddclient::config{$h} = { - $tc->{useopt} => $builtinfw, - 'fw-skip' => 'skip1', - %{$tc->{cfgxtra}}, - }; - %ddclient::config if 0; # suppress spurious warning "Name used only once: possible typo" - undef($got_host); - my $got = $tc->{getip}($builtinfw, $h); - is($got_host, $h, "host is passed through"); - is($got, $tc->{want}, "returned IP matches"); +for my $tc (@skip_test_cases) { + my $h = "t/builtinfw_query.pl $tc->{desc}"; + $ddclient::config{$h} = { + $tc->{useopt} => $skip_test_fw, + 'fw-skip' => 'skip1', + %{$tc->{cfgxtra}}, + }; + my $got = $tc->{getip}(ddclient::strategy_inputs($tc->{useopt}, $h)); + is($got, $tc->{want}, $tc->{desc}); +} + +my $default_inputs_fw = 't/builtinfw_query.pl default inputs'; +setbuiltinfw({ + name => $default_inputs_fw, + query => sub { my %p = @_; push(@gotcalls, \%p); return '192.0.2.1'; }, + queryv4 => sub { my %p = @_; push(@gotcalls, \%p); return '192.0.2.2'; }, + queryv6 => sub { my %p = @_; push(@gotcalls, \%p); return '2001:db8::1'; }, +}); +my @default_inputs_test_cases = ( + { + desc => 'use with default inputs', + getip => \&ddclient::get_ip, + useopt => 'use', + want => {use => $default_inputs_fw, fw => 'server', 'fw-skip' => 'skip', + 'fw-login' => 'login', 'fw-password' => 'password', 'fw-ssl-validate' => 1}, + }, + { + desc => 'usev4 with default inputs', + getip => \&ddclient::get_ipv4, + useopt => 'usev4', + want => {usev4 => $default_inputs_fw, fwv4 => 'serverv4', fw => 'server', + 'fwv4-skip' => 'skipv4', 'fw-skip' => 'skip', 'fw-login' => 'login', + 'fw-password' => 'password', 'fw-ssl-validate' => 1}, + }, + { + desc => 'usev6 with default inputs', + getip => \&ddclient::get_ipv6, + useopt => 'usev6', + want => {usev6 => $default_inputs_fw, fwv6 => 'serverv6', 'fwv6-skip' => 'skipv6'}, + }, +); +for my $tc (@default_inputs_test_cases) { + my $h = "t/builtinfw_query.pl $tc->{desc}"; + $ddclient::config{$h} = { + $tc->{useopt} => $default_inputs_fw, + 'fw' => 'server', + 'fwv4' => 'serverv4', + 'fwv6' => 'serverv6', + 'fw-login' => 'login', + 'fw-password' => 'password', + 'fw-ssl-validate' => 1, + 'fw-skip' => 'skip', + 'fwv4-skip' => 'skipv4', + 'fwv6-skip' => 'skipv6', + }; + @gotcalls = (); + $tc->{getip}(ddclient::strategy_inputs($tc->{useopt}, $h)); + is_deeply(\@gotcalls, [$tc->{want}], $tc->{desc}); +} + +my $custom_inputs_fw = 't/builtinfw_query.pl custom inputs'; +setbuiltinfw({ + name => $custom_inputs_fw, + query => sub { my %p = @_; push(@gotcalls, \%p); return '192.0.2.1'; }, + inputs => ['if'], + queryv4 => sub { my %p = @_; push(@gotcalls, \%p); return '192.0.2.2'; }, + inputsv4 => ['ifv4'], + queryv6 => sub { my %p = @_; push(@gotcalls, \%p); return '2001:db8::1'; }, + inputsv6 => ['ifv6'], +}); + +my @custom_inputs_test_cases = ( + { + desc => 'use with custom inputs', + getip => \&ddclient::get_ip, + useopt => 'use', + want => {use => $custom_inputs_fw, if => 'eth0'}, + }, + { + desc => 'usev4 with custom inputs', + getip => \&ddclient::get_ipv4, + useopt => 'usev4', + want => {usev4 => $custom_inputs_fw, ifv4 => 'eth4'}, + }, + { + desc => 'usev6 with custom inputs', + getip => \&ddclient::get_ipv6, + useopt => 'usev6', + want => {usev6 => $custom_inputs_fw, ifv6 => 'eth6'}, + }, +); + +for my $tc (@custom_inputs_test_cases) { + my $h = "t/builtinfw_query.pl $tc->{desc}"; + $ddclient::config{$h} = { + $tc->{useopt} => $custom_inputs_fw, + 'if' => 'eth0', + 'ifv4' => 'eth4', + 'ifv6' => 'eth6', }; + @gotcalls = (); + $tc->{getip}(ddclient::strategy_inputs($tc->{useopt}, $h)); + is_deeply(\@gotcalls, [$tc->{want}], $tc->{desc}); } done_testing(); diff --git a/t/skip.pl b/t/skip.pl index ba5dac98..1ee68a3d 100644 --- a/t/skip.pl +++ b/t/skip.pl @@ -22,6 +22,11 @@ BEGIN if httpd('6'); $ddclient::builtinfw{$builtinfw} = {name => 'test', skip => 'skip'}; %ddclient::builtinfw if 0; # suppress spurious warning "Name used only once: possible typo" +%ddclient::ip_strategies = (%ddclient::ip_strategies, ddclient::builtinfw_strategy($builtinfw)); +%ddclient::ipv4_strategies = + (%ddclient::ipv4_strategies, ddclient::builtinfwv4_strategy($builtinfw)); +%ddclient::ipv6_strategies = + (%ddclient::ipv6_strategies, ddclient::builtinfwv6_strategy($builtinfw)); sub run_test_case { my %tc = @_; @@ -31,9 +36,12 @@ sub run_test_case { my $h = 't/skip.pl'; $ddclient::config{$h} = $tc{cfg}; %ddclient::config if 0; # suppress spurious warning "Name used only once: possible typo" - is(ddclient::get_ip($tc{cfg}{use}, $h), $tc{want}, $tc{desc}) if ($tc{cfg}{use}); - is(ddclient::get_ipv4($tc{cfg}{usev4}, $h), $tc{want}, $tc{desc}) if ($tc{cfg}{usev4}); - is(ddclient::get_ipv6($tc{cfg}{usev6}, $h), $tc{want}, $tc{desc}) if ($tc{cfg}{usev6}); + is(ddclient::get_ip(ddclient::strategy_inputs('use', $h)), $tc{want}, $tc{desc}) + if ($tc{cfg}{use}); + is(ddclient::get_ipv4(ddclient::strategy_inputs('usev4', $h)), $tc{want}, $tc{desc}) + if ($tc{cfg}{usev4}); + is(ddclient::get_ipv6(ddclient::strategy_inputs('usev6', $h)), $tc{want}, $tc{desc}) + if ($tc{cfg}{usev6}); } } diff --git a/t/ssl-validate.pl b/t/ssl-validate.pl index 36e510ab..5939dd91 100644 --- a/t/ssl-validate.pl +++ b/t/ssl-validate.pl @@ -75,9 +75,9 @@ BEGIN skip("HTTP::Daemon too old for IPv6 support", 1) if $tc->{ipv6} && !$httpd_ipv6_supported; $ddclient::config{$h} = $tc->{cfg}; %ddclient::config if 0; # suppress spurious warning "Name used only once: possible typo" - is(ddclient::get_ipv4($tc->{cfg}{usev4}, $h), $tc->{want}, $tc->{desc}) + is(ddclient::get_ipv4(ddclient::strategy_inputs('usev4', $h)), $tc->{want}, $tc->{desc}) if ($tc->{cfg}{usev4}); - is(ddclient::get_ipv6($tc->{cfg}{usev6}, $h), $tc->{want}, $tc->{desc}) + is(ddclient::get_ipv6(ddclient::strategy_inputs('usev6', $h)), $tc->{want}, $tc->{desc}) if ($tc->{cfg}{usev6}); } } diff --git a/t/use_web.pl b/t/use_web.pl index 139f492a..b129ecf8 100644 --- a/t/use_web.pl +++ b/t/use_web.pl @@ -87,11 +87,11 @@ BEGIN SKIP: { skip("IPv6 not supported on this system", 1) if $tc->{ipv6} && !$ipv6_supported; skip("HTTP::Daemon too old for IPv6 support", 1) if $tc->{ipv6} && !$httpd_ipv6_supported; - is(ddclient::get_ip($tc->{cfg}{use}, $h), $tc->{want}, $tc->{desc}) + is(ddclient::get_ip(ddclient::strategy_inputs('use', $h)), $tc->{want}, $tc->{desc}) if $tc->{cfg}{use}; - is(ddclient::get_ipv4($tc->{cfg}{usev4}, $h), $tc->{want}, $tc->{desc}) + is(ddclient::get_ipv4(ddclient::strategy_inputs('usev4', $h)), $tc->{want}, $tc->{desc}) if $tc->{cfg}{usev4}; - is(ddclient::get_ipv6($tc->{cfg}{usev6}, $h), $tc->{want}, $tc->{desc}) + is(ddclient::get_ipv6(ddclient::strategy_inputs('usev6', $h)), $tc->{want}, $tc->{desc}) if $tc->{cfg}{usev6}; } } From ad3cd1144625ef8b5ee3c2dbe794407db2c43f86 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 14 Jun 2024 01:30:59 -0400 Subject: [PATCH 4/4] Improve deduplication of redundant `use*` queries --- ddclient.in | 84 ++++++++++++++++-------------------------------- t/update_nics.pl | 52 ++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 57 deletions(-) diff --git a/ddclient.in b/ddclient.in index 29bb1ec6..ba4b463b 100755 --- a/ddclient.in +++ b/ddclient.in @@ -1533,9 +1533,9 @@ sub runpostscript { ###################################################################### sub update_nics { my %examined = (); - my %iplist = (); - my %ipv4list = (); - my %ipv6list = (); + my %use_results; + my %usev4_results; + my %usev6_results; for my $p (sort keys %protocols) { my (@hosts, %ipsv4, %ipsv6) = (); @@ -1547,74 +1547,44 @@ sub update_nics { my $use = opt('use', $h); my $usev4 = opt('usev4', $h); my $usev6 = opt('usev6', $h); - my $arg_ip = opt('ip', $h) // ''; - my $arg_ipv4 = opt('ipv4', $h) // ''; - my $arg_ipv6 = opt('ipv6', $h) // ''; - my $arg_fw = opt('fw', $h) // ''; - my $arg_fwv4 = opt('fwv4', $h) // ''; - my $arg_fwv6 = opt('fwv6', $h) // ''; - my $arg_if = opt('if', $h) // ''; - my $arg_ifv4 = opt('ifv4', $h) // ''; - my $arg_ifv6 = opt('ifv6', $h) // ''; - my $arg_web = opt('web', $h) // ''; - my $arg_webv4 = opt('webv4', $h) // ''; - my $arg_webv6 = opt('webv6', $h) // ''; - my $arg_cmd = opt('cmd', $h) // ''; - my $arg_cmdv4 = opt('cmdv4', $h) // ''; - my $arg_cmdv6 = opt('cmdv6', $h) // ''; my $ip = undef; my $ipv4 = undef; my $ipv6 = undef; if ($use ne 'disabled') { - if (exists $iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd}) { - # If we have already done a get_ip() for this, don't do it again. - $ip = $iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd}; - } else { - # Else need to find the IP address... - $ip = get_ip(strategy_inputs('use', $h)); - if (is_ipv4($ip) || is_ipv6($ip)) { - # And if it is valid, remember it... - $iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd} = $ip; - } else { - warning("unable to determine IP address with strategy '--use=$use'") - if !$daemon || opt('verbose'); - } + my %inputs = strategy_inputs('use', $h); + my $sig = repr(\%inputs, Indent => 0); + $use_results{$sig} //= get_ip(%inputs); + if (!is_ipv4($use_results{$sig}) && !is_ipv6($use_results{$sig})) { + warning("unable to determine IP address with strategy '--use=$use'") + if !$daemon || opt('verbose'); + delete $use_results{$sig}; } + $ip = $use_results{$sig}; } if ($usev4 ne 'disabled') { - if (exists $ipv4list{$usev4}{$arg_ipv4}{$arg_fwv4}{$arg_ifv4}{$arg_webv4}{$arg_cmdv4}) { - # If we have already done a get_ipv4() for this, don't do it again. - $ipv4 = $ipv4list{$usev4}{$arg_ipv4}{$arg_fwv4}{$arg_ifv4}{$arg_webv4}{$arg_cmdv4}; - } else { - # Else need to find the IPv4 address... - $ipv4 = get_ipv4(strategy_inputs('usev4', $h)); - if (is_ipv4($ipv4)) { - # And if it is valid, remember it... - $ipv4list{$usev4}{$arg_ipv4}{$arg_fwv4}{$arg_ifv4}{$arg_webv4}{$arg_cmdv4} = $ipv4; - } else { - warning("unable to determine IPv4 address with strategy '--usev4=$usev4'") - if !$daemon || opt('verbose'); - } + my %inputs = strategy_inputs('usev4', $h); + my $sig = repr(\%inputs, Indent => 0); + $usev4_results{$sig} //= get_ipv4(%inputs); + if (!is_ipv4($usev4_results{$sig})) { + warning("unable to determine IPv4 address with strategy '--usev4=$usev4'") + if !$daemon || opt('verbose'); + delete $usev4_results{$sig}; } + $ipv4 = $usev4_results{$sig}; } if ($usev6 ne 'disabled') { - if (exists $ipv6list{$usev6}{$arg_ipv6}{$arg_fwv6}{$arg_ifv6}{$arg_webv6}{$arg_cmdv6}) { - # If we have already done a get_ipv6() for this, don't do it again. - $ipv6 = $ipv6list{$usev6}{$arg_ipv6}{$arg_fwv6}{$arg_ifv6}{$arg_webv6}{$arg_cmdv6}; - } else { - # Else need to find the IPv6 address... - $ipv6 = get_ipv6(strategy_inputs('usev6', $h)); - if (is_ipv6($ipv6)) { - # And if it is valid, remember it... - $ipv6list{$usev6}{$arg_ipv6}{$arg_fwv6}{$arg_ifv6}{$arg_webv6}{$arg_cmdv6} = $ipv6; - } else { - warning("unable to determine IPv6 address with strategy '--usev6=$usev6'") - if !$daemon || opt('verbose'); - } + my %inputs = strategy_inputs('usev6', $h); + my $sig = repr(\%inputs, Indent => 0); + $usev6_results{$sig} //= get_ipv6(%inputs); + if (!is_ipv6($usev6_results{$sig})) { + warning("unable to determine IPv6 address with strategy '--usev6=$usev6'") + if !$daemon || opt('verbose'); + delete $usev6_results{$sig}; } + $ipv6 = $usev6_results{$sig}; } $ip //= $ipv4 // $ipv6; diff --git a/t/update_nics.pl b/t/update_nics.pl index 913b7757..d656c0fb 100644 --- a/t/update_nics.pl +++ b/t/update_nics.pl @@ -272,6 +272,58 @@ sub mergecfg { %$_, }; } {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}), + map({ + my %cfg = %{delete($_->{cfg})}; + my $desc = join(' ', map("$_=$cfg{$_}", sort(keys(%cfg)))); + { + desc => "deduplicates identical IP discovery, $desc", + cfg => { + hosta => {protocol => 'legacy', %cfg}, + hostb => {protocol => 'legacy', %cfg}, + }, + want_reqs_webv4 => 1, + want_updates => [['hosta', 'hostb']], + want_recap_changes => { + hosta => { + 'atime' => $ddclient::now, + 'ipv4' => '192.0.2.1', + 'mtime' => $ddclient::now, + 'status-ipv4' => 'good', + }, + hostb => { + 'atime' => $ddclient::now, + 'ipv4' => '192.0.2.1', + 'mtime' => $ddclient::now, + 'status-ipv4' => 'good', + }, + }, + %$_, + }; + } {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}), + { + desc => "deduplicates identical IP discovery, usev6=webv6", + ipv6 => 1, + cfg => { + hosta => {protocol => 'legacy', usev6 => 'webv6'}, + hostb => {protocol => 'legacy', usev6 => 'webv6'}, + }, + want_reqs_webv6 => 1, + want_updates => [['hosta', 'hostb']], + want_recap_changes => { + hosta => { + 'atime' => $ddclient::now, + 'ipv6' => '2001:db8::1', + 'mtime' => $ddclient::now, + 'status-ipv6' => 'good', + }, + hostb => { + 'atime' => $ddclient::now, + 'ipv6' => '2001:db8::1', + 'mtime' => $ddclient::now, + 'status-ipv6' => 'good', + }, + }, + }, ); for my $tc (@test_cases) {