diff --git a/ddclient.in b/ddclient.in
index 445979f..8344e44 100755
--- a/ddclient.in
+++ b/ddclient.in
@@ -75,6 +75,9 @@ my ($result, %config, %cache);
my $saved_cache;
my %saved_opt;
my $daemon;
+# Control how many times warning message logged for invalid IP addresses
+my (%warned_ip, %warned_ipv4, %warned_ipv6);
+my $inv_ip_warn_count = opt('max-warn') // 1;
sub T_ANY { 'any' }
sub T_STRING { 'string' }
@@ -90,9 +93,13 @@ sub T_FILE { 'file name' }
sub T_FQDNP { 'fully qualified host name and optional port number' }
sub T_PROTO { 'protocol' }
sub T_USE { 'ip strategy' }
+sub T_USEV4 { 'ipv4 strategy' }
+sub T_USEV6 { 'ipv6 strategy' }
sub T_IF { 'interface' }
sub T_PROG { 'program name' }
sub T_IP { 'ip' }
+sub T_IPV4 { 'ipv4' }
+sub T_IPV6 { 'ipv6' }
sub T_POSTS { 'postscript' }
## strategies for obtaining an ip address.
@@ -352,14 +359,15 @@ my %builtinfw = (
);
my %ip_strategies = (
- 'ip' => ": use IP given by '-ip
'",
- 'web' => ": obtain IP from the web-based IP discovery service given by '-web |'",
- 'fw' => ": obtain IP from a firewall/router device by visiting the URL given by '-fw '",
- 'if' => ": obtain IP from the interface given by '-if '",
- 'cmd' => ": obtain IP by running the command given by '-cmd '",
- 'cisco' => ": obtain IP from Cisco FW device at the address given by '-fw '",
- 'cisco-asa' => ": obtain IP from Cisco ASA device at the address given by '-fw '",
- map({ $_ => sprintf(": obtain IP from %s device at the address given by '-fw '",
+ 'no' => ": deprecated, see 'usev4' and 'usev6'",
+ 'ip' => ": deprecated, see 'usev4' and 'usev6'",
+ 'web' => ": deprecated, see 'usev4' and 'usev6'",
+ 'fw' => ": deprecated, see 'usev4' and 'usev6'",
+ 'if' => ": deprecated, see 'usev4' and 'usev6'",
+ 'cmd' => ": deprecated, see 'usev4' and 'usev6'",
+ 'cisco' => ": deprecated, see 'usev4' and 'usev6'",
+ 'cisco-asa' => ": deprecated, see 'usev4' and 'usev6'",
+ map({ $_ => sprintf(": Built-in firewall %s deprecated, see 'usev4' and 'usev6'",
$builtinfw{$_}->{'name'}) }
keys(%builtinfw)),
);
@@ -369,6 +377,41 @@ sub ip_strategies_usage {
('ip', 'web', 'if', 'cmd', 'fw', sort('cisco', 'cisco-asa', keys(%builtinfw))));
}
+my %ipv4_strategies = (
+ 'disabled' => ": do not obtain an IPv4 address for this host",
+ 'ipv4' => ": obtain IPv4 from -ipv4 {address}",
+ 'webv4' => ": obtain IPv4 from an IP discovery page on the web",
+ 'ifv4' => ": obtain IPv4 from the -ifv4 {interface}",
+ 'cmdv4' => ": obtain IPv4 from the -cmdv4 {external-command}",
+ 'fwv4' => ": obtain IPv4 from the firewall specified by -fwv4 {type|address}",
+ 'ciscov4' => ": obtain IPv4 from Cisco FW at the -fwv4 {address}",
+ 'cisco-asav4' => ": obtain IPv4 from Cisco ASA at the -fwv4 {address}",
+ map { $_ => sprintf ": obtain IPv4 from %s at the -fwv4 {address}", $builtinfw{$_}->{'name'} } keys %builtinfw,
+);
+sub ipv4_strategies_usage {
+ return map { sprintf(" -usev4=%-22s %s.", $_, $ipv4_strategies{$_}) } sort keys %ipv4_strategies;
+}
+
+my %ipv6_strategies = (
+ 'no' => ": deprecated, use 'disabled'",
+ 'disabled' => ": do not obtain an IPv6 address for this host",
+ 'ip' => ": deprecated, use 'ipv6'",
+ 'ipv6' => ": obtain IPv6 from -ipv6 {address}",
+ 'web' => ": deprecated, use 'webv6'",
+ 'webv6' => ": obtain IPv6 from an IP discovery page on the web",
+ 'if' => ": deprecated, use 'ifv6'",
+ 'ifv6' => ": obtain IPv6 from the -if {interface}",
+ 'cmd' => ": deprecated, use 'cmdv6'",
+ 'cmdv6' => ": obtain IPv6 from the -cmdv6 {external-command}",
+ 'fwv6' => ": obtain IPv6 from the firewall specified by -fwv6 {type|address}",
+ 'ciscov6' => ": obtain IPv6 from Cisco FW at the -fwv6 {address}",
+ 'cisco-asav6' => ": obtain IPv6 from Cisco ASA at the -fwv6 {address}",
+ map { $_ => sprintf ": obtain IPv6 from %s at the -fwv6 {address}", $builtinfw{$_}->{'name'} } keys %builtinfw,
+);
+sub ipv6_strategies_usage {
+ return map { sprintf(" -usev6=%-22s %s.", $_, $ipv6_strategies{$_}) } sort keys %ipv6_strategies;
+}
+
sub setv {
return {
'type' => shift,
@@ -389,28 +432,44 @@ my %variables = (
'protocol' => setv(T_PROTO, 0, 0, 'dyndns2', undef),
'use' => setv(T_USE, 0, 0, 'ip', undef),
+ 'usev4' => setv(T_USEV4, 0, 0, 'disabled', undef),
+ 'usev6' => setv(T_USEV6, 0, 0, 'disabled', undef),
'ip' => setv(T_IP, 0, 0, undef, undef),
+ 'ipv4' => setv(T_IPV4, 0, 0, undef, undef),
+ 'ipv6' => setv(T_IPV6, 0, 0, undef, undef),
'if' => setv(T_IF, 0, 0, 'ppp0', undef),
+ 'ifv4' => setv(T_IF, 0, 0, 'default', undef),
+ 'ifv6' => setv(T_IF, 0, 0, 'default', undef),
'web' => setv(T_STRING,0, 0, 'dyndns', undef),
'web-skip' => setv(T_STRING,1, 0, '', undef),
+ 'webv4' => setv(T_STRING,0, 0, 'googledomains', undef),
+ 'webv4-skip' => setv(T_STRING,1, 0, '', undef),
+ 'webv6' => setv(T_STRING,0, 0, 'googledomains', undef),
+ 'webv6-skip' => setv(T_STRING,1, 0, '', undef),
'fw' => setv(T_ANY, 0, 0, '', undef),
'fw-skip' => setv(T_STRING,1, 0, '', undef),
+ 'fwv4' => setv(T_ANY, 0, 0, '', undef),
+ 'fwv4-skip' => setv(T_STRING,1, 0, '', undef),
+ 'fwv6' => setv(T_ANY, 0, 0, '', undef),
+ 'fwv6-skip' => setv(T_STRING,1, 0, '', undef),
'fw-login' => setv(T_LOGIN, 1, 0, '', undef),
'fw-password' => setv(T_PASSWD,1, 0, '', undef),
'cmd' => setv(T_PROG, 0, 0, '', undef),
'cmd-skip' => setv(T_STRING,1, 0, '', undef),
+ 'cmdv4' => setv(T_PROG, 0, 0, '', undef),
+ 'cmdv6' => setv(T_PROG, 0, 0, '', undef),
'timeout' => setv(T_DELAY, 0, 0, interval('120s'), interval('120s')),
'retry' => setv(T_BOOL, 0, 0, 0, undef),
'force' => setv(T_BOOL, 0, 0, 0, undef),
'ssl' => setv(T_BOOL, 0, 0, 0, undef),
'curl' => setv(T_BOOL, 0, 0, 0, undef),
- 'ipv6' => setv(T_BOOL, 0, 0, 0, undef),
'syslog' => setv(T_BOOL, 0, 0, 0, undef),
'facility' => setv(T_STRING,0, 0, 'daemon', undef),
'priority' => setv(T_STRING,0, 0, 'notice', undef),
'mail' => setv(T_EMAIL, 0, 0, '', undef),
'mail-failure' => setv(T_EMAIL, 0, 0, '', undef),
+ 'max-warn' => setv(T_NUMBER,0, 0, 1, undef),
'exec' => setv(T_BOOL, 0, 0, 1, undef),
'debug' => setv(T_BOOL, 0, 0, 0, undef),
@@ -442,12 +501,15 @@ my %variables = (
'fw-ssl-validate' => setv(T_BOOL, 0, 0, 1, undef),
'cmd' => setv(T_PROG, 0, 0, '', undef),
'cmd-skip' => setv(T_STRING,0, 0, '', undef),
- 'ipv6' => setv(T_BOOL, 0, 0, 0, undef),
- 'ip' => setv(T_IP, 0, 1, undef, undef),
+ 'ip' => setv(T_IP, 0, 1, undef, undef), #TODO remove from cache?
+ 'ipv4' => setv(T_IPV4, 0, 1, undef, undef),
+ 'ipv6' => setv(T_IPV6, 0, 1, undef, undef),
'wtime' => setv(T_DELAY, 0, 1, 0, interval('30s')),
'mtime' => setv(T_NUMBER,0, 1, 0, undef),
'atime' => setv(T_NUMBER,0, 1, 0, undef),
- 'status' => setv(T_ANY, 0, 1, '', undef),
+ 'status' => setv(T_ANY, 0, 1, '', undef), #TODO remove from cache?
+ 'status-ipv4' => setv(T_ANY, 0, 1, '', undef),
+ 'status-ipv6' => setv(T_ANY, 0, 1, '', undef),
'min-interval' => setv(T_DELAY, 0, 0, interval('30s'), 0),
'max-interval' => setv(T_DELAY, 0, 0, interval('25d'), 0),
'min-error-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0),
@@ -784,28 +846,46 @@ my @opt = (
["cache", "=s", "-cache : record address used in "],
["pid", "=s", "-pid : record process id in if daemonized"],
"",
- ["use", "=s", "-use : how the IP address should be obtained"],
+ ["use", "=s", "-use : deprecated, see 'usev4' and 'usev6'"],
&ip_strategies_usage(),
+ [ "usev4", "=s", "-usev4 : how the should IPv4 address be obtained."],
+ &ipv4_strategies_usage(),
+ [ "usev6", "=s", "-usev6 : how the should IPv6 address be obtained."],
+ &ipv6_strategies_usage(),
"",
" Options that apply to 'use=ip':",
- ["ip", "=s", "-ip : set the IP address to "],
+ ["ip", "=s", "-ip : deprecated, use 'ipv4' or 'ipv6'"],
+ ["ipv4", "=s", "-ipv4 : set the IPv4 address to "],
+ ["ipv6", "=s", "-ipv6 : set the IPv6 address to "],
"",
" Options that apply to 'use=if':",
- ["if", "=s", "-if : obtain IP address from "],
+ ["if", "=s", "-if : deprecated, use 'ifv4' or 'ifv6'"],
+ ["ifv4", "=s", "-ifv4 : obtain IPv4 address from "],
+ ["ifv6", "=s", "-ifv6 : obtain IPv6 address from "],
"",
" Options that apply to 'use=web':",
- ["web", "=s", "-web | : obtain IP address from a web-based IP discovery service, either a known or a custom "],
- ["web-skip", "=s", "-web-skip : skip any IP addresses before in the text returned from the web-based IP discovery service"],
+ ["web", "=s", "-web | : deprecated, use 'webv4' or 'webv6'"],
+ ["web-skip", "=s", "-web-skip : deprecated, use 'webv4-skip' or 'webv6-skip'"],
+ ["webv4", "=s", "-webv4 |: obtain IPv4 address from a web-based IP discovery service, either a known or a custom "],
+ ["webv4-skip", "=s", "-webv4-skip : skip any IP addresses before in the output of 'ip address show dev ' (or 'ifconfig ')"],
+ ["webv6", "=s", "-webv6 |: obtain IPv6 address from a web-based IP discovery service, either a known or a custom "],
+ ["webv6-skip", "=s", "-webv6-skip : skip any IP addresses before in the output of 'ip address show dev ' (or 'ifconfig ')"],
"",
" Options that apply to 'use=fw' and 'use=':",
- ["fw", "=s", "-fw | : obtain IP address from device with IP address or URL "],
- ["fw-skip", "=s", "-fw-skip : skip any IP addresses before in the text returned from the device"],
+ ["fw", "=s", "-fw | : deprecated, use 'fwv4' or 'fwv6'"],
+ ["fw-skip", "=s", "-fw-skip : deprecated, use 'fwv4-skip' or 'fwv6-skip'"],
+ ["fwv4", "=s", "-fwv4 | : obtain IPv4 address from device with IP address or URL "],
+ ["fwv4-skip", "=s", "-fwv4-skip : skip any IP addresses before in the text returned from the device"],
+ ["fwv6", "=s", "-fwv6 | : obtain IPv6 address from device with IP address or URL "],
+ ["fwv6-skip", "=s", "-fwv6-skip : skip any IP addresses before in the text returned from the device"],
["fw-login", "=s", "-fw-login : use when getting the IP from the device"],
["fw-password", "=s", "-fw-password : use password when getting the IP from the device"],
"",
" Options that apply to 'use=cmd':",
- ["cmd", "=s", "-cmd : obtain IP address from the output of "],
- ["cmd-skip", "=s", "-cmd-skip : skip any IP addresses before in the command's output"],
+ ["cmd", "=s", "-cmd : deprecated, use 'cmdv4' or 'cmdv6'"],
+ ["cmd-skip", "=s", "-cmd-skip : deprecated, filter in program wrapper script"],
+ ["cmdv4", "=s", "-cmdv4 : obtain IPv4 address from the output of "],
+ ["cmdv6", "=s", "-cmdv6 : obtain IPv6 address from the output of "],
"",
["login", "=s", "-login : log in to the dynamic DNS service as "],
["password", "=s", "-password : log in to the dynamic DNS service with password "],
@@ -825,13 +905,13 @@ my @opt = (
["syslog", "!", "-{no}syslog : log messages to syslog"],
["facility", "=s", "-facility : log messages to syslog to facility "],
["priority", "=s", "-priority : log messages to syslog with priority "],
+ ["max-warn", "=i", "-max-warn : log at most warning messages for undefined IP address"],
["mail", "=s", "-mail : e-mail messages to "],
["mail-failure", "=s", "-mail-failure : e-mail messages for failed updates to "],
["exec", "!", "-{no}exec : do {not} execute; just show what would be done"],
["debug", "!", "-{no}debug : print {no} debugging information"],
["verbose", "!", "-{no}verbose : print {no} verbose information"],
["quiet", "!", "-{no}quiet : print {no} messages for unnecessary updates"],
- ["ipv6", "!", "-{no}ipv6 : use ipv6"],
["help", "", "-help : display this message and exit"],
["postscript", "", "-postscript : script to run after updating ddclient, has new IP as param"],
["query", "!", "-{no}query : print {no} ip addresses and exit"],
@@ -907,6 +987,10 @@ sub main {
fatal("invalid argument '-use %s'; possible values are:\n%s", $opt{'use'}, join("\n", ip_strategies_usage()))
unless exists $ip_strategies{lc opt('use')};
+ if (defined($opt{'usev6'})) {
+ usage("invalid argument '-usev6 %s'; possible values are:\n%s", $opt{'usev6'}, join("\n",ipv6_strategies_usage()))
+ unless exists $ipv6_strategies{lc opt('usev6')};
+ }
$daemon = opt('daemon');
@@ -968,9 +1052,11 @@ sub runpostscript {
sub update_nics {
my %examined = ();
my %iplist = ();
+ my %ipv4list = ();
+ my %ipv6list = ();
foreach my $s (sort keys %services) {
- my (@hosts, %ips) = ();
+ my (@hosts, %ipsv4, %ipsv6) = ();
my $updateable = $services{$s}{'updateable'};
my $update = $services{$s}{'update'};
@@ -978,33 +1064,103 @@ sub update_nics {
next if $config{$h}{'protocol'} ne lc($s);
$examined{$h} = 1;
# we only do this once per 'use' and argument combination
- my $use = opt('use', $h);
- my $arg_ip = opt('ip', $h) // '';
- my $arg_fw = opt('fw', $h) // '';
- my $arg_if = opt('if', $h) // '';
- my $arg_web = opt('web', $h) // '';
- my $arg_cmd = opt('cmd', $h) // '';
- my $ip = "";
- if (exists $iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd}) {
- $ip = $iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd};
- } else {
- $ip = get_ip($use, $h);
- if (!defined($ip)) {
- warning("unable to determine IP address")
- if !$daemon || opt('verbose');
- next;
+ my $use = opt('use', $h) // 'disabled';
+ my $usev4 = opt('usev4', $h) // 'disabled';
+ my $usev6 = opt('usev6', $h) // 'disabled';
+ $use = 'disabled' if ($use eq 'no'); # backward compatibility
+ $usev6 = 'disabled' if ($usev6 eq 'no'); # backward compatibility
+ 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($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("%s: unable to determine IP address with strategy use=%s", $h, $use)
+ if !$daemon || opt('verbose');
+ }
}
- $iplist{$use}{$arg_ip}{$arg_fw}{$arg_if}{$arg_web}{$arg_cmd} = $ip;
+ # And remember it as the IP address we want to send to the DNS service.
+ $config{$h}{'wantip'} = $ip;
}
- $config{$h}{'wantip'} = $ip;
+
+ 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($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("%s: unable to determine IPv4 address with strategy usev4=%s", $h, $usev4)
+ if !$daemon || opt('verbose');
+ }
+ }
+ # And remember it as the IPv4 address we want to send to the DNS service.
+ $config{$h}{'wantipv4'} = $ipv4;
+ }
+
+ 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($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("%s: unable to determine IPv6 address with strategy usev6=%s", $h, $usev6)
+ if !$daemon || opt('verbose');
+ }
+ }
+ # And remember it as the IP address we want to send to the DNS service.
+ $config{$h}{'wantipv6'} = $ipv6;
+ }
+
+ # DNS service update functions should only have to handle 'wantipv4' and 'wantipv6'
+ $config{$h}{'wantipv4'} = $ipv4 = $ip if (!$ipv4 && is_ipv4($ip));
+ $config{$h}{'wantipv6'} = $ipv6 = $ip if (!$ipv6 && is_ipv6($ip));
+ # But we will set 'wantip' to the IPv4 so old functions continue to work until we update them all
+ $config{$h}{'wantip'} = $ipv4 if (!$ip && $ipv4);
+
next if !nic_updateable($h, $updateable);
push @hosts, $h;
- $ips{$ip} = $h;
+
+ $ipsv4{$ipv4} = $h if ($ipv4);
+ $ipsv6{$ipv6} = $h if ($ipv6);
}
if (@hosts) {
$0 = sprintf("%s - updating %s", $program, join(',', @hosts));
&$update(@hosts);
- runpostscript(join ' ', keys %ips);
+ runpostscript(join ' ', keys %ipsv4, keys %ipsv6);
}
}
foreach my $h (sort keys %config) {
@@ -1052,8 +1208,7 @@ sub write_cache {
## merge the updated host entries into the cache.
foreach my $h (keys %config) {
if (!exists $cache{$h} || $config{$h}{'update'}) {
- map { $cache{$h}{$_} = $config{$h}{$_} } @{$config{$h}{'cacheable'}};
-
+ map { defined($config{$h}{$_}) ? ($cache{$h}{$_} = $config{$h}{$_}) : () } @{$config{$h}{'cacheable'}};
} else {
map { $cache{$h}{$_} = $config{$h}{$_} } qw(atime wtime status);
}
@@ -1341,10 +1496,22 @@ sub init_config {
$opt{'quiet'} = 0 if opt('verbose');
## infer the IP strategy if possible
- if (!defined($opt{'use'})) {
- $opt{'use'} = 'web' if defined($opt{'web'});
- $opt{'use'} = 'if' if defined($opt{'if'});
- $opt{'use'} = 'ip' if defined($opt{'ip'});
+ if (!$opt{'use'}) {
+ $opt{'use'} = 'web' if ($opt{'web'});
+ $opt{'use'} = 'if' if ($opt{'if'});
+ $opt{'use'} = 'ip' if ($opt{'ip'});
+ }
+ ## infer the IPv4 strategy if possible
+ if (!$opt{'usev4'}) {
+ $opt{'usev4'} = 'webv4' if ($opt{'webv4'});
+ $opt{'usev4'} = 'ifv4' if ($opt{'ifv4'});
+ $opt{'usev4'} = 'ipv4' if ($opt{'ipv4'});
+ }
+ ## infer the IPv6 strategy if possible
+ if (!$opt{'usev6'}) {
+ $opt{'usev6'} = 'webv6' if ($opt{'webv6'});
+ $opt{'usev6'} = 'ifv6' if ($opt{'ifv6'});
+ $opt{'usev6'} = 'ipv6' if ($opt{'ipv6'});
}
## sanity check
@@ -1532,6 +1699,7 @@ sub process_args {
sub test_possible_ip {
local $opt{'debug'} = 0;
+ printf "----- Test_possible_ip with 'get_ip' -----\n";
printf "use=ip, ip=%s address is %s\n", opt('ip'), get_ip('ip') // 'NOT FOUND'
if defined opt('ip');
@@ -1575,6 +1743,75 @@ sub test_possible_ip {
local $opt{'use'} = 'cmd';
printf "use=cmd, cmd=%s address is %s\n", opt('cmd'), get_ip('cmd') // 'NOT FOUND';
}
+
+ # Now force IPv4
+ printf "----- Test_possible_ip with 'get_ipv4' ------\n";
+ printf "use=ipv4, ipv4=%s address is %s\n", opt('ipv4'), get_ipv4('ipv4') // 'NOT FOUND'
+ if defined opt('ipv4');
+
+ {
+ # Note: The `ip` command adds a `@eth0` suffix to the names of VLAN
+ # interfaces. That `@eth0` suffix is NOT part of the interface name.
+ my @ifs = map({ /^[^\s:]*:\s*([^\s:@]+)/ ? $1 : () }
+ `command -v ip >/dev/null && ip -o link show`);
+ @ifs = map({ /^([a-zA-Z].*?)(?::?\s.*)?$/ ? $1 : () }
+ `command -v ifconfig >/dev/null && ifconfig -a`) if $? || !@ifs;
+ @ifs = () if $?;
+ warning("failed to get list of interfaces") if !@ifs;
+ foreach my $if (@ifs) {
+ local $opt{'ifv4'} = $if;
+ printf "use=ifv4, ifv4=%s address is %s\n", opt('ifv4'), get_ipv4('ifv4') // 'NOT FOUND';
+ }
+ }
+ {
+ local $opt{'usev4'} = 'webv4';
+ foreach my $web (sort keys %builtinweb) {
+ local $opt{'webv4'} = $web;
+ printf "use=webv4, webv4=$web address is %s\n", get_ipv4('webv4') // 'NOT FOUND'
+ if ($web !~ "6") ## Don't bother if web site only supports IPv6;
+ }
+ printf "use=webv4, webv4=%s address is %s\n", opt('webv4'), get_ipv4('webv4') // 'NOT FOUND'
+ if ! exists $builtinweb{opt('webv4')};
+ }
+ if (opt('cmdv4')) {
+ local $opt{'usev4'} = 'cmdv4';
+ printf "use=cmdv4, cmdv4=%s address is %s\n", opt('cmdv4'), get_ipv4('cmdv4') // 'NOT FOUND';
+ }
+
+ # Now force IPv6
+ printf "----- Test_possible_ip with 'get_ipv6' -----\n";
+ printf "use=ipv6, ipv6=%s address is %s\n", opt('ipv6'), get_ipv6('ipv6') // 'NOT FOUND'
+ if defined opt('ipv6');
+
+ {
+ # Note: The `ip` command adds a `@eth0` suffix to the names of VLAN
+ # interfaces. That `@eth0` suffix is NOT part of the interface name.
+ my @ifs = map({ /^[^\s:]*:\s*([^\s:@]+)/ ? $1 : () }
+ `command -v ip >/dev/null && ip -o link show`);
+ @ifs = map({ /^([a-zA-Z].*?)(?::?\s.*)?$/ ? $1 : () }
+ `command -v ifconfig >/dev/null && ifconfig -a`) if $? || !@ifs;
+ @ifs = () if $?;
+ warning("failed to get list of interfaces") if !@ifs;
+ foreach my $if (@ifs) {
+ local $opt{'ifv6'} = $if;
+ printf "use=ifv6, ifv6=%s address is %s\n", opt('ifv6'), get_ipv6('ifv6') // 'NOT FOUND';
+ }
+ }
+ {
+ local $opt{'usev6'} = 'webv6';
+ foreach my $web (sort keys %builtinweb) {
+ local $opt{'webv6'} = $web;
+ printf "use=webv6, webv6=$web address is %s\n", get_ipv6('webv6') // 'NOT FOUND'
+ if ($web !~ "4"); ## Don't bother if web site only supports IPv4
+ }
+ printf "use=webv6, webv6=%s address is %s\n", opt('webv6'), get_ipv6('webv6') // 'NOT FOUND'
+ if ! exists $builtinweb{opt('webv6')};
+ }
+ if (opt('cmdv6')) {
+ local $opt{'usev6'} = 'cmdv6';
+ printf "use=cmdv6, cmdv6=%s address is %s\n", opt('cmdv6'), get_ipv6('cmdv6') // 'NOT FOUND';
+ }
+
exit 0 unless opt('debug');
}
######################################################################
@@ -1942,6 +2179,14 @@ sub check_value {
$value = lc $value;
return undef if !exists $ip_strategies{$value};
+ } elsif ($type eq T_USEV4) {
+ $value = lc $value;
+ return undef if ! exists $ipv4_strategies{$value};
+
+ } elsif ($type eq T_USEV6) {
+ $value = lc $value;
+ return undef if ! exists $ipv6_strategies{$value};
+
} elsif ($type eq T_FILE) {
return undef if $value eq "";
@@ -1956,6 +2201,13 @@ sub check_value {
} elsif ($type eq T_IP) {
return undef if !is_ipv4($value) && !is_ipv6($value);
+
+ } elsif ($type eq T_IPV4) {
+ return undef if !is_ipv4($value);
+
+ } elsif ($type eq T_IPV6) {
+ return undef if !is_ipv6($value);
+
}
return $value;
}
@@ -2448,6 +2700,7 @@ sub fetch_via_curl {
######################################################################
sub get_ip {
my $use = lc shift;
+ $use = 'disabled' if ($use eq 'no'); # backward compatibility
my $h = shift;
my ($ip, $arg, $reply, $url, $skip) = (undef, opt($use, $h), '');
$arg = '' unless $arg;
@@ -2455,7 +2708,7 @@ sub get_ip {
if ($use eq 'ip') {
$ip = opt('ip', $h);
if (!is_ipv4($ip) && !is_ipv6($ip)) {
- warning("'%s' is not a valid IPv4 or IPv6 address", $ip);
+ warning("'%s' is not a valid IPv4 or IPv6 address", $ip // '');
$ip = undef;
}
$arg = 'ip';
@@ -2534,6 +2787,10 @@ sub get_ip {
) // '';
$arg = $url;
+ } elsif ($use eq 'disabled') {
+ ## This is a no-op... Do not get an IP address for this host/service
+ $reply = '';
+
} else {
$url = opt('fw', $h) // '';
$skip = opt('fw-skip', $h) // '';
@@ -2571,7 +2828,6 @@ sub get_ip {
return $ip;
}
-
######################################################################
## Regex to find IPv4 address. Accepts embedded leading zeros.
######################################################################
@@ -2873,10 +3129,242 @@ sub get_ip_from_interface {
return extract_ipv6($sorted[0]);
}
+######################################################################
+## get_ipv4
+######################################################################
+sub get_ipv4 {
+ my $usev4 = lc(shift); ## Method to obtain IP address
+ my $h = shift; ## Host/service making the request
+
+ my $ipv4 = undef; ## Found IPv4 address
+ my $reply = ''; ## Text returned from various methods
+ my $url = ''; ## URL of website or firewall
+ my $skip = ''; ## Regex of pattern to skip before looking for IP
+ my $arg = opt($usev4, $h) // ''; ## Value assigned to the "usev4" method
+
+ if ($usev4 eq 'ipv4') {
+ ## Static IPv4 address is provided in "ipv4="
+ $ipv4 = $arg;
+ if (!is_ipv4($ipv4)) {
+ warning("'%s' is not a valid IPv4",$ipv4 // '');
+ $ipv4 = undef;
+ }
+ $arg = 'ipv4'; # For debug message at end of function
+
+ } elsif ($usev4 eq 'ifv4') {
+ ## Obtain IPv4 address from interface mamed in "ifv4="
+ warning("'if-skip' is deprecated and does nothing for IPv4") if (opt('verbose') && opt('if-skip', $h));
+ $ipv4 = get_ip_from_interface($arg,4);
+
+ } elsif ($usev4 eq 'cmdv4') {
+ ## Obtain IPv4 address by executing the command in "cmdv4="
+ warning("'cmd-skip' is deprecated and does nothing for IPv4") if (opt('verbose') && opt('cmd-skip', $h));
+ if ($arg) {
+ my $sys_cmd = quotemeta($arg);
+ $reply = qx{$sys_cmd};
+ $reply = '' if $?;
+ }
+
+ } elsif ($usev4 eq 'webv4') {
+ ## Obtain IPv4 address by accessing website at url in "webv4="
+ $url = $arg;
+ $skip = opt('webv4-skip', $h) // '';
+ if (exists $builtinweb{$url}) {
+ $skip = $builtinweb{$url}->{'skip'} unless $skip;
+ $url = $builtinweb{$url}->{'url'};
+ $arg = $url;
+ }
+ if ($url) {
+ $reply = geturl( proxy => opt('proxy', $h),
+ url => $url,
+ ipversion => 4, # when using a URL to find IPv4 address we should force use of IPv4
+ ssl_validate => opt('ssl-validate', $h),
+ ) // '';
+ }
+
+ } elsif ($usev4 eq 'cisco' || $usev4 eq 'cisco-asa') {
+ # Stuff added to support Cisco router ip http or ASA https daemon
+ # User fw-login should only have level 1 access to prevent
+ # password theft. This is pretty harmless.
+ warning("'if' does nothing for IPv4. Use 'ifv4'") if (opt('if', $h));
+ warning("'fw' does nothing for IPv4. Use 'fwv4'") if (opt('fw', $h));
+ warning("'fw-skip' does nothing for IPv4. Use 'fwv4-skip'") if (opt('fw-skip', $h));
+ my $queryif = opt('ifv4', $h) // opt('if', $h);
+ $skip = opt('fwv4-skip', $h) // opt('fw-skip', $h) // '';
+ # Convert slashes to protected value "\/"
+ $queryif =~ s%\/%\\\/%g;
+ # Protect special HTML characters (like '?')
+ $queryif =~ s/([\?&= ])/sprintf("%%%02x", ord($1))/ge;
+ if ($usev4 eq 'cisco') {
+ $url = "http://" . (opt('fwv4', $h) // opt('fw', $h)) . "/level/1/exec/show/ip/interface/brief/${queryif}/CR";
+ } else {
+ $url = "https://" . (opt('fwv4', $h) // opt('fw', $h)) . "/exec/show%20interface%20${queryif}";
+ }
+ $arg = $url;
+ $reply = geturl(
+ url => $url,
+ login => opt('fw-login', $h),
+ password => opt('fw-password', $h),
+ ipversion => 4, # when using a URL to find IPv4 address we should force use of IPv4
+ ignore_ssl_option => 1,
+ ssl_validate => opt('ssl-validate', $h),
+ ) // '';
+
+ } elsif ($usev4 eq 'disabled') {
+ ## This is a no-op... Do not get an IPv4 address for this host/service
+ $reply = '';
+
+ } else {
+ warning("'fw' does nothing for IPv4. Use 'fwv4'") if (opt('fw', $h));
+ warning("'fw-skip' does nothing for IPv4. Use 'fwv4-skip'") if (opt('fw-skip', $h));
+ $url = opt('fwv4', $h) // opt('fw', $h) // '';
+ $skip = opt('fwv4-skip', $h) // opt('fw-skip', $h) // '';
+
+ if (exists $builtinfw{$usev4}) {
+ $skip = $builtinfw{$usev4}->{'skip'} unless $skip;
+ $url = "http://${url}" . $builtinfw{$usev4}->{'url'} unless $url =~ /\//;
+ }
+ $arg = $url;
+ if ($url) {
+ $reply = geturl(
+ url => $url,
+ login => opt('fw-login', $h),
+ password => opt('fw-password', $h),
+ ipversion => 4, # when using a URL to find IPv4 address we should force use of IPv4
+ ignore_ssl_option => 1,
+ ssl_validate => opt('ssl-validate', $h),
+ ) // '';
+ }
+ }
+
+ ## Set to loopback address if no text set yet
+ $reply = '0.0.0.0' if !defined($reply);
+ if (($skip // '') ne '') {
+ $skip =~ s/ /\\s/is;
+ $reply =~ s/^.*?${skip}//is;
+ }
+ ## 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'));
+ debug("get_ipv4: using (%s, %s) reports %s", $usev4, $arg, $ipv4 // "");
+ return $ipv4;
+}
+
+######################################################################
+## get_ipv6
+######################################################################
+sub get_ipv6 {
+ my $usev6 = lc(shift); ## Method to obtain IP address
+ $usev6 = 'disabled' if ($usev6 eq 'no'); # backward compatibility
+ my $h = shift; ## Host/service making the request
+
+ my $ipv6 = undef; ## Found IPv6 address
+ my $reply = ''; ## Text returned from various methods
+ my $url = ''; ## URL of website or firewall
+ my $skip = ''; ## Regex of pattern to skip before looking for IP
+ my $arg = opt($usev6, $h) // ''; ## Value assigned to the "usev6" method
+
+ if ($usev6 eq 'ipv6' || $usev6 eq 'ip') {
+ ## Static IPv6 address is provided in "ipv6="
+ if ($usev6 eq 'ip') {
+ warning("'usev6=ip' is deprecated. Use 'usev6=ipv6'");
+ $usev6 = 'ipv6';
+ ## If there is a value for ipv6= use that, else use value for ip=
+ $arg = opt($usev6, $h) // $arg;
+ }
+ $ipv6 = $arg;
+ if (!is_ipv6($ipv6)) {
+ warning("'%s' is not a valid IPv6",$ipv6 // '');
+ $ipv6 = undef;
+ }
+ $arg = 'ipv6'; # For debug message at end of function
+
+ } elsif ($usev6 eq 'ifv6' || $usev6 eq 'if' ) {
+ ## Obtain IPv6 address from interface mamed in "ifv6="
+ if ($usev6 eq 'if') {
+ warning("'usev6=if' is deprecated. Use 'usev6=ifv6'");
+ $usev6 = 'ifv6';
+ ## If there is a value for ifv6= use that, else use value for if=
+ $arg = opt($usev6, $h) // $arg;
+ }
+ warning("'if-skip' is deprecated and does nothing for IPv6") if (opt('verbose') && opt('if-skip', $h));
+ $ipv6 = get_ip_from_interface($arg,6);
+
+ } elsif ($usev6 eq 'cmdv6' || $usev6 eq 'cmd') {
+ ## Obtain IPv6 address by executing the command in "cmdv6="
+ if ($usev6 eq 'cmd') {
+ warning("'usev6=cmd' is deprecated. Use 'usev6=cmdv6'");
+ $usev6 = 'cmdv6';
+ ## If there is a value for cmdv6= use that, else use value for cmd=
+ $arg = opt($usev6, $h) // $arg;
+ }
+ warning("'cmd-skip' is deprecated and does nothing for IPv6") if (opt('verbose') && opt('cmd-skip', $h));
+ if ($arg) {
+ my $sys_cmd = quotemeta($arg);
+ $reply = qx{$sys_cmd};
+ $reply = '' if $?;
+ }
+
+ } elsif ($usev6 eq 'webv6' || $usev6 eq 'web') {
+ ## Obtain IPv6 address by accessing website at url in "webv6="
+ if ($usev6 eq 'web') {
+ warning("'usev6=web' is deprecated. Use 'usev6=webv6'");
+ $usev6 = 'webv6';
+ ## If there is a value for webv6= use that, else use value for web=
+ $arg = opt($usev6, $h) // $arg;
+ }
+ warning("'web-skip' does nothing for IPv6. Use 'webv6-skip'") if (opt('web-skip', $h));
+ $url = $arg;
+ $skip = opt('webv6-skip', $h) // '';
+ if (exists $builtinweb{$url}) {
+ $skip = $builtinweb{$url}->{'skip'} unless $skip;
+ $url = $builtinweb{$url}->{'url'};
+ $arg = $url;
+ }
+ if ($url) {
+ $reply = geturl(
+ proxy => opt('proxy'),
+ url => $url,
+ ipversion => 6, # when using a URL to find IPv6 address we should force use of IPv6
+ ssl_validate => opt('ssl-validate', $h),
+ ) // '';
+ }
+
+ } elsif ($usev6 eq 'cisco' || $usev6 eq 'cisco-asa') {
+ warning("'usev6=cisco' and 'usev6=cisco-asa' are not implemented and do nothing");
+ $reply = '';
+
+ } elsif ($usev6 eq 'disabled') {
+ ## This is a no-op... Do not get an IPv6 address for this host/service
+ warning("'usev6=no' is deprecated. Use 'usev6=disabled'") if ($usev6 eq 'no');
+ $reply = '';
+
+ } else {
+ warning("'usev6=%s' is not implemented and does nothing", $usev6);
+ $reply = '';
+
+ }
+
+ ## Set to loopback address if no text set yet
+ $reply = '::' if !defined($reply);
+ if (($skip // '') ne '') {
+ $skip =~ s/ /\\s/is;
+ $reply =~ s/^.*?${skip}//is;
+ }
+ ## 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') && (($ipv6 // '') eq '::'));
+ debug("get_ipv6: using (%s, %s) reports %s", $usev6, $arg, $ipv6 // "");
+ return $ipv6;
+}
+
######################################################################
## group_hosts_by
######################################################################
sub group_hosts_by {
+##TODO - Update for wantipv4 and wantipv6
my ($hosts, $attributes) = @_;
my %attrs = (map({ ($_ => 1) } @$attributes), 'wantip' => 1);
my @attrs = sort(keys(%attrs));
@@ -2984,12 +3472,35 @@ EoEXAMPLE
}
######################################################################
## nic_updateable
+## Returns true if we can go ahead and update the IP address at server
######################################################################
sub nic_updateable {
my $host = shift;
my $sub = shift;
my $update = 0;
my $ip = $config{$host}{'wantip'};
+ my $ipv4 = $config{$host}{'wantipv4'};
+ my $ipv6 = $config{$host}{'wantipv6'};
+ my $use = opt('use', $host) // 'disabled';
+ my $usev4 = opt('usev4', $host) // 'disabled';
+ my $usev6 = opt('usev6', $host) // 'disabled';
+ $use = 'disabled' if ($use eq 'no'); # backward compatibility
+ $usev6 = 'disabled' if ($usev6 eq 'no'); # backward compatibility
+
+ # If we have a valid IP address and we have previously warned that it was invalid.
+ # reset the warning count back to zero.
+ if (($use ne 'disabled') && $ip && $warned_ip{$host}) {
+ $warned_ip{$host} = 0;
+ warning("IP address for %s valid: %s. Reset warning count", $host, $ip);
+ }
+ if (($usev4 ne 'disabled') && $ipv4 && $warned_ipv4{$host}) {
+ $warned_ipv4{$host} = 0;
+ warning("IPv4 address for %s valid: %s. Reset warning count", $host, $ipv4);
+ }
+ if (($usev6 ne 'disabled') && $ipv6 && $warned_ipv6{$host}) {
+ $warned_ipv6{$host} = 0;
+ warning("IPv6 address for %s valid: %s. Reset warning count", $host, $ipv6);
+ }
if ($config{$host}{'login'} eq '') {
warning("null login name specified for host %s.", $host);
@@ -3021,7 +3532,9 @@ sub nic_updateable {
);
$update = 1;
- } elsif (!exists($cache{$host}{'ip'}) || $cache{$host}{'ip'} ne $ip) {
+ } elsif ( ($use ne 'disabled')
+ && ((!exists($cache{$host}{'ip'})) || ("$cache{$host}{'ip'}" ne "$ip"))) {
+ ## Check whether to update IP address for the "use" method"
if (($cache{$host}{'status'} eq 'good') &&
!interval_expired($host, 'mtime', 'min-interval')) {
@@ -3036,17 +3549,117 @@ sub nic_updateable {
$cache{$host}{'warned-min-interval'} = $now;
- } elsif (($cache{$host}{'status'} ne 'good') && !interval_expired($host, 'atime', 'min-error-interval')) {
+ } elsif (($cache{$host}{'status'} ne 'good') &&
+ !interval_expired($host, 'atime', 'min-error-interval')) {
- warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.",
+ if ( opt('verbose')
+ || ( ! $cache{$host}{'warned-min-error-interval'}
+ && (($warned_ip{$host} // 0) < $inv_ip_warn_count)) ) {
+
+ warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.",
+ $host,
+ ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''),
+ $ip,
+ ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''),
+ ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''),
+ prettyinterval($config{$host}{'min-error-interval'})
+ );
+ if (!$ip && !opt('verbose')) {
+ $warned_ip{$host} = ($warned_ip{$host} // 0) + 1;
+ warning("IP address for %s undefined. Warned %s times, suppressing further warnings", $host, $inv_ip_warn_count)
+ if ($warned_ip{$host} >= $inv_ip_warn_count);
+ }
+ }
+
+ $cache{$host}{'warned-min-error-interval'} = $now;
+
+ } else {
+ $update = 1;
+ }
+
+ } elsif ( ($usev4 ne 'disabled')
+ && ((!exists($cache{$host}{'ipv4'})) || ("$cache{$host}{'ipv4'}" ne "$ipv4"))) {
+ ## Check whether to update IPv4 address for the "usev4" method"
+ if (($cache{$host}{'status-ipv4'} eq 'good') &&
+ !interval_expired($host, 'mtime', 'min-interval')) {
+
+ warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.",
$host,
- ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''),
- $ip,
+ ($cache{$host}{'ipv4'} ? $cache{$host}{'ipv4'} : ''),
+ $ipv4,
($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''),
- ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''),
- prettyinterval($config{$host}{'min-error-interval'})
+ prettyinterval($config{$host}{'min-interval'})
)
- if opt('verbose') || !($cache{$host}{'warned-min-error-interval'} // 0);
+ if opt('verbose') || !($cache{$host}{'warned-min-interval'} // 0);
+
+ $cache{$host}{'warned-min-interval'} = $now;
+
+ } elsif (($cache{$host}{'status-ipv4'} ne 'good') &&
+ !interval_expired($host, 'atime', 'min-error-interval')) {
+
+ if ( opt('verbose')
+ || ( ! $cache{$host}{'warned-min-error-interval'}
+ && (($warned_ipv4{$host} // 0) < $inv_ip_warn_count)) ) {
+
+ warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.",
+ $host,
+ ($cache{$host}{'ipv4'} ? $cache{$host}{'ipv4'} : ''),
+ $ipv4,
+ ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''),
+ ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''),
+ prettyinterval($config{$host}{'min-error-interval'})
+ );
+ if (!$ipv4 && !opt('verbose')) {
+ $warned_ipv4{$host} = ($warned_ipv4{$host} // 0) + 1;
+ warning("IPv4 address for %s undefined. Warned %s times, suppressing further warnings", $host, $inv_ip_warn_count)
+ if ($warned_ipv4{$host} >= $inv_ip_warn_count);
+ }
+ }
+
+ $cache{$host}{'warned-min-error-interval'} = $now;
+
+ } else {
+ $update = 1;
+ }
+
+ } elsif ( ($usev6 ne 'disabled')
+ && ((!exists($cache{$host}{'ipv6'})) || ("$cache{$host}{'ipv6'}" ne "$ipv6"))) {
+ ## Check whether to update IPv6 address for the "usev6" method"
+ if (($cache{$host}{'status-ipv6'} eq 'good') &&
+ !interval_expired($host, 'mtime', 'min-interval')) {
+
+ warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.",
+ $host,
+ ($cache{$host}{'ipv6'} ? $cache{$host}{'ipv6'} : ''),
+ $ipv6,
+ ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''),
+ prettyinterval($config{$host}{'min-interval'})
+ )
+ if opt('verbose') || !($cache{$host}{'warned-min-interval'} // 0);
+
+ $cache{$host}{'warned-min-interval'} = $now;
+
+ } elsif (($cache{$host}{'status-ipv6'} ne 'good') &&
+ !interval_expired($host, 'atime', 'min-error-interval')) {
+
+ if ( opt('verbose')
+ || ( ! $cache{$host}{'warned-min-error-interval'}
+ && (($warned_ipv6{$host} // 0) < $inv_ip_warn_count)) ) {
+
+ warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.",
+ $host,
+ ($cache{$host}{'ipv6'} ? $cache{$host}{'ipv6'} : ''),
+ $ipv6,
+ ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''),
+ ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''),
+ prettyinterval($config{$host}{'min-error-interval'})
+ );
+ if (!$ipv6 && !opt('verbose')) {
+ $warned_ipv6{$host} = ($warned_ipv6{$host} // 0) + 1;
+ warning("IPv6 address for %s undefined. Warned %s times, suppressing further warnings", $host, $inv_ip_warn_count)
+ if ($warned_ipv6{$host} >= $inv_ip_warn_count);
+ }
+ }
$cache{$host}{'warned-min-error-interval'} = $now;
@@ -3068,13 +3681,27 @@ sub nic_updateable {
$update = 1;
} else {
- success("%s: skipped: IP address was already set to %s.", $host, $ip)
- if opt('verbose');
+ if (opt('verbose')) {
+ if ($use ne 'disabled') {
+ success("%s: skipped: IP address was already set to %s.", $host, $ip);
+ }
+ if ($usev4 ne 'disabled') {
+ success("%s: skipped: IPv4 address was already set to %s.", $host, $ipv6);
+ }
+ if ($usev6 ne 'disabled') {
+ success("%s: skipped: IPv6 address was already set to %s.", $host, $ipv6);
+ }
+ }
}
+
$config{$host}{'status'} = $cache{$host}{'status'} // '';
+ $config{$host}{'status-ipv4'} = $cache{$host}{'status-ipv4'} // '';
+ $config{$host}{'status-ipv6'} = $cache{$host}{'status-ipv6'} // '';
$config{$host}{'update'} = $update;
if ($update) {
$config{$host}{'status'} = 'noconnect';
+ $config{$host}{'status-ipv4'} = 'noconnect';
+ $config{$host}{'status-ipv6'} = 'noconnect';
$config{$host}{'atime'} = $now;
$config{$host}{'wtime'} = 0;
$config{$host}{'warned-min-interval'} = 0;
@@ -4333,12 +4960,14 @@ EoEXAMPLE
## hostname2.example.com|5.6.7.8|http://example/update/url3
## hostname2.example.com|9.10.11.12|http://example/update/url4
## hostname3.example.com|cafe::f00d|http://example/update/url5
+## hostname4.example.com|NULL|http://example/update/url6
##
## The record's columns are separated by '|'. The first is the hostname, the second is the current
## address, and the third is the record-specific update URL. There can be multiple records for the
-## same host, and they can even have the same address type. Any record can be updated to hold
-## either type of address (e.g., if given an IPv6 address the record will automatically become an
-## AAAA record).
+## same host, and they can even have the same address type. To update an IP address the record
+## must already exist of the type we want to update... We will not change a record type from
+## an IPv4 to IPv6 or viz versa. Records may exist with a NULL address which we will allow to be
+## updated with an IPv4 address, not an IPv6.
##
## The second step is to visit the appropriate record's update URL with
## ?address= appended. "Updated" in the result means success, "fail" means
@@ -4354,7 +4983,10 @@ sub nic_freedns_update {
my $url_tmpl = "http://$config{$_[0]}{'server'}/api/?action=getdyndns&v=2&sha=";
my $creds = sha1_hex("$config{$_[0]}{'login'}|$config{$_[0]}{'password'}");
(my $url = $url_tmpl) =~ s//$creds/;
- my $reply = geturl(proxy => opt('proxy'), url => $url);
+
+ my $reply = geturl(proxy => opt('proxy'),
+ url => $url
+ );
my $record_list_error = '';
if ($reply && header_ok($_[0], $reply)) {
$reply =~ s/^.*?\n\n//s; # Strip the headers.
@@ -4363,7 +4995,8 @@ sub nic_freedns_update {
next if ($#rec < 2);
my $recs = is_ipv6($rec[1]) ? \%recs_ipv6 : \%recs_ipv4;
$recs->{$rec[0]} = \@rec;
- debug("host: %s, current address: %s, update URL: %s", @rec);
+ # Update URL contains credentials that don't require login to use, so best to hide.
+ debug("host: %s, current address: %s, update URL: ", $rec[0], $rec[1]);
}
if (keys(%recs_ipv4) + keys(%recs_ipv6) == 0) {
chomp($reply);
@@ -4374,54 +5007,61 @@ sub nic_freedns_update {
}
foreach my $h (@_) {
- if (!$h) { next }
- my $ip = delete $config{$h}{'wantip'};
-
- info("%s: setting IP address to %s", $h, $ip);
+ next if (!$h);
+ my $ipv4 = delete $config{$h}{'wantipv4'};
+ my $ipv6 = delete $config{$h}{'wantipv6'};
if ($record_list_error ne '') {
- $config{$h}{'status'} = 'failed';
+ $config{$h}{'status-ipv4'} = 'failed' if ($ipv4);
+ $config{$h}{'status-ipv6'} = 'failed' if ($ipv6);
failed("updating %s: %s", $h, $record_list_error);
next;
}
- # If there is a record with matching type then update it, otherwise let
- # freedns convert the record to the desired type.
- my $rec = is_ipv6($ip)
- ? ($recs_ipv6{$h} // $recs_ipv4{$h})
- : ($recs_ipv4{$h} // $recs_ipv6{$h});
- if (!defined($rec)) {
- $config{$h}{'status'} = 'failed';
- failed("updating %s: host record does not exist", $h);
- next;
- }
- if ($ip eq $rec->[1]) {
- $config{$h}{'ip'} = $ip;
- $config{$h}{'mtime'} = $now;
- $config{$h}{'status'} = 'good';
- success("update not necessary %s: good: IP address already set to %s", $h, $ip)
- if (!$daemon || opt('verbose'));
- } else {
- my $url = $rec->[2] . "&address=" . $ip;
- debug("Update: %s", $url);
- my $reply = geturl(proxy => opt('proxy'), url => $url);
- if (!defined($reply) || !$reply || !header_ok($h, $reply)) {
- $config{$h}{'status'} = 'failed';
- failed("updating %s: Could not connect to %s.", $h, $url);
+ # IPv4 and IPv6 handling are similar enough to do in a loop...
+ foreach my $ip ($ipv4, $ipv6) {
+ next if (!$ip);
+ my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4';
+ my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A';
+ my $rec = ($ip eq ($ipv6 // '')) ? $recs_ipv6{$h}
+ : $recs_ipv4{$h};
+ if (!$rec) {
+ failed("updating %s: Cannot set IPv$ipv to %s No '$type' record at FreeDNS", $h, $ip);
next;
}
- $reply =~ s/^.*?\n\n//s; # Strip the headers.
- if ($reply =~ /Updated.*$h.*to.*$ip/) {
- $config{$h}{'ip'} = $ip;
- $config{$h}{'mtime'} = $now;
- $config{$h}{'status'} = 'good';
- success("updating %s: good: IP address set to %s", $h, $ip);
+ info("updating %s: setting IP address to %s", $h, $ip);
+ $config{$h}{"status-ipv$ipv"} = 'failed';
+
+ if ($ip eq $rec->[1]) {
+ $config{$h}{"ipv$ipv"} = $ip;
+ $config{$h}{'mtime'} = $now;
+ $config{$h}{"status-ipv$ipv"} = 'good';
+ success("updating %s: update not necessary, '$type' record already set to %s", $h, $ip)
+ if (!$daemon || opt('verbose'));
} else {
- $config{$h}{'status'} = 'failed';
- warning("SENT: %s", $url) unless opt('verbose');
- warning("REPLIED: %s", $reply);
- failed("updating %s: Invalid reply.", $h);
+ my $url = $rec->[2] . "&address=" . $ip;
+ ($url_tmpl = $url) =~ s/\?.*\&/?&/; # redact unique update token
+ debug("updating: %s", $url_tmpl);
+
+ my $reply = geturl(proxy => opt('proxy'),
+ url => $url
+ );
+ if ($reply && header_ok($h, $reply)) {
+ $reply =~ s/^.*?\n\n//s; # Strip the headers.
+ if ($reply =~ /Updated.*$h.*to.*$ip/) {
+ $config{$h}{"ipv$ipv"} = $ip;
+ $config{$h}{'mtime'} = $now;
+ $config{$h}{"status-ipv$ipv"} = 'good';
+ success("updating %s: good: IPv$ipv address set to %s", $h, $ip);
+ } else {
+ warning("SENT: %s", $url_tmpl) unless opt('verbose');
+ warning("REPLIED: %s", $reply);
+ failed("updating %s: Invalid reply.", $h);
+ }
+ } else {
+ failed("updating %s: Could not connect to %s.", $h, $url_tmpl);
+ }
}
}
}
@@ -4759,7 +5399,6 @@ sub nic_cloudflare_update {
my @hosts = @{$groups{$sig}};
my $hosts = join(',', @hosts);
my $key = $hosts[0];
- my $ip = $config{$key}{'wantip'};
my $headers = "Content-Type: application/json\n";
if ($config{$key}{'login'} eq 'token') {
@@ -4772,102 +5411,104 @@ sub nic_cloudflare_update {
# FQDNs
for my $domain (@hosts) {
(my $hostname = $domain) =~ s/\.$config{$key}{zone}$//;
- delete $config{$domain}{'wantip'};
+ my $ipv4 = delete $config{$domain}{'wantipv4'};
+ my $ipv6 = delete $config{$domain}{'wantipv6'};
- info("setting IP address to %s for %s", $ip, $domain);
- verbose("UPDATE:", "updating %s", $domain);
+ info("getting Cloudflare Zone ID for %s", $domain);
# Get zone ID
my $url = "https://$config{$key}{'server'}/zones?";
- $url .= "name=".$config{$key}{'zone'};
+ $url .= "name=" . $config{$key}{'zone'};
- my $reply = geturl(proxy => opt('proxy'), url => $url, headers => $headers);
- unless ($reply) {
+ my $reply = geturl(proxy => opt('proxy'),
+ url => $url,
+ headers => $headers
+ );
+ unless ($reply && header_ok($domain, $reply)) {
failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'});
next;
}
- next if !header_ok($domain, $reply);
# Strip header
$reply =~ s/^.*?\n\n//s;
- my $response = eval { decode_json($reply) };
- if (!defined $response || !defined $response->{result}) {
- failed("invalid json or result.");
+ my $response = eval {decode_json($reply)};
+ unless ($response && $response->{result}) {
+ failed("updating %s: invalid json or result.", $domain);
next;
}
# Pull the ID out of the json, messy
- my ($zone_id) = map { $_->{name} eq $config{$key}{'zone'} ? $_->{id} : () } @{$response->{result}};
+ my ($zone_id) = map {$_->{name} eq $config{$key}{'zone'} ? $_->{id} : ()} @{$response->{result}};
unless ($zone_id) {
failed("updating %s: No zone ID found.", $config{$key}{'zone'});
next;
}
- info("zone ID is %s", $zone_id);
+ info("Zone ID is %s", $zone_id);
- # Get DNS record ID
- $url = "https://$config{$key}{'server'}/zones/$zone_id/dns_records?";
- if (is_ipv6($ip)) {
- $url .= "type=AAAA&name=$domain";
- } else {
- $url .= "type=A&name=$domain";
+
+ # IPv4 and IPv6 handling are similar enough to do in a loop...
+ foreach my $ip ($ipv4, $ipv6) {
+ next if (!$ip);
+ my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4';
+ my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A';
+
+ info("updating %s: setting IPv$ipv address to %s", $domain, $ip);
+ $config{$domain}{"status-ipv$ipv"} = 'failed';
+
+ # Get DNS 'A' or 'AAAA' record ID
+ $url = "https://$config{$key}{'server'}/zones/$zone_id/dns_records?";
+ $url .= "type=$type&name=$domain";
+ $reply = geturl(proxy => opt('proxy'),
+ url => $url,
+ headers => $headers
+ );
+ unless ($reply && header_ok($domain, $reply)) {
+ failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'});
+ next;
+ }
+ # Strip header
+ $reply =~ s/^.*?\n\n//s;
+ $response = eval {decode_json($reply)};
+ unless ($response && $response->{result}) {
+ failed("updating %s: invalid json or result.", $domain);
+ next;
+ }
+ # Pull the ID out of the json, messy
+ my ($dns_rec_id) = map {$_->{name} eq $domain ? $_->{id} : ()} @{$response->{result}};
+ unless($dns_rec_id) {
+ failed("updating %s: Cannot set IPv$ipv to %s No '$type' record at Cloudflare", $domain, $ip);
+ next;
+ }
+ debug("updating %s: DNS '$type' record ID: $dns_rec_id", $domain);
+ # Set domain
+ $url = "https://$config{$key}{'server'}/zones/$zone_id/dns_records/$dns_rec_id";
+ my $data = "{\"content\":\"$ip\"}";
+ $reply = geturl(proxy => opt('proxy'),
+ url => $url,
+ headers => $headers,
+ method => "PATCH",
+ data => $data
+ );
+ unless ($reply && header_ok($domain, $reply)) {
+ failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'});
+ next;
+ }
+ # Strip header
+ $reply =~ s/^.*?\n\n//s;
+ $response = eval {decode_json($reply)};
+ if ($response && $response->{result}) {
+ success("updating %s: IPv$ipv address set to %s", $domain, $ip);
+ $config{$domain}{"ipv$ipv"} = $ip;
+ $config{$domain}{'mtime'} = $now;
+ $config{$domain}{"status-ipv$ipv"} = 'good';
+ } else {
+ failed("updating %s: invalid json or result.", $domain);
+ }
}
-
- $reply = geturl(proxy => opt('proxy'), url => $url, headers => $headers);
- unless ($reply) {
- failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'});
- next;
- }
- next if !header_ok($domain, $reply);
-
- # Strip header
- $reply =~ s/^.*?\n\n//s;
- $response = eval { decode_json($reply) };
- if (!defined $response || !defined $response->{result}) {
- failed("invalid json or result.");
- next;
- }
-
- # Pull the ID out of the json, messy
- my ($dns_rec_id) = map { $_->{name} eq $domain ? $_->{id} : () } @{$response->{result}};
- unless ($dns_rec_id) {
- failed("updating %s: No DNS record ID found.", $domain);
- next;
- }
- info("DNS record ID is %s", $dns_rec_id);
-
- # Set domain
- $url = "https://$config{$key}{'server'}/zones/$zone_id/dns_records/$dns_rec_id";
- my $data = "{\"content\":\"$ip\"}";
- $reply = geturl(
- proxy => opt('proxy'),
- url => $url,
- headers => $headers,
- method => "PATCH",
- data => $data,
- );
- unless ($reply) {
- failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'});
- next;
- }
- next if !header_ok($domain, $reply);
-
- # Strip header
- $reply =~ s/^.*?\n\n//s;
- $response = eval { decode_json($reply) };
- if (!defined $response || !defined $response->{result}) {
- failed("invalid json or result.");
- } else {
- success("%s -- Updated Successfully to %s", $domain, $ip);
-
- }
-
- # Cache
- $config{$domain}{'ip'} = $ip;
- $config{$domain}{'mtime'} = $now;
- $config{$domain}{'status'} = 'good';
}
}
}
+
######################################################################
## nic_yandex_examples
######################################################################