Skip to content

Commit

Permalink
Merge pull request ddclient#732 from rhansen/legacy-status
Browse files Browse the repository at this point in the history
Fix handling of legacy `status` value
  • Loading branch information
rhansen authored Aug 22, 2024
2 parents 80bbf1d + de5d894 commit 3dafdbf
Show file tree
Hide file tree
Showing 7 changed files with 532 additions and 146 deletions.
3 changes: 3 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master).
removed. [#716](https://github.com/ddclient/ddclient/pull/716)
* `googledomains`: Support was removed because the service shut down.
[#716](https://github.com/ddclient/ddclient/pull/716)
* The `--retry` option was removed.
[#732](https://github.com/ddclient/ddclient/pull/732)

### New features

Expand Down Expand Up @@ -131,6 +133,7 @@ repository history](https://github.com/ddclient/ddclient/commits/master).
[#734](https://github.com/ddclient/ddclient/pull/734)
* Fixed unnecessary repeated updates for some services.
[#670](https://github.com/ddclient/ddclient/pull/670)
[#732](https://github.com/ddclient/ddclient/pull/732)
* Fixed DNSExit provider when configured with a zone and non-identical
hostname. [#674](https://github.com/ddclient/ddclient/pull/674)
* `infomaniak`: Fixed frequent forced updates after 25 days (`max-interval`).
Expand Down
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ handwritten_tests = \
t/protocol_dyndns2.pl \
t/skip.pl \
t/ssl-validate.pl \
t/update_nics.pl \
t/use_web.pl \
t/variable_defaults.pl \
t/write_recap.pl
Expand Down
2 changes: 2 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ m4_foreach_w([_m], [
B
File::Spec::Functions
File::Temp
List::Util
re
], [AX_PROG_PERL_MODULES([_m], [],
[AC_MSG_WARN([some tests will fail due to missing module _m])])])

Expand Down
243 changes: 103 additions & 140 deletions ddclient.in

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions sample-etc_cron.d_ddclient
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,3 @@
## force an update twice a month (only if you are not using daemon-mode)
##
## 30 23 1,15 * * root /usr/bin/ddclient -daemon=0 -syslog -quiet -force
######################################################################
## retry failed updates every hour (only if you are not using daemon-mode)
##
## 0 * * * * root /usr/bin/ddclient -daemon=0 -syslog -quiet retry
352 changes: 352 additions & 0 deletions t/update_nics.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
use Test::More;
use File::Temp;
use List::Util qw(max);
eval { require ddclient::Test::Fake::HTTPD; } or plan(skip_all => $@);
SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@);
my $ipv6_supported = eval {
require IO::Socket::IP;
my $ipv6_socket = IO::Socket::IP->new(
Domain => 'PF_INET6',
LocalHost => '::1',
Listen => 1,
);
defined($ipv6_socket);
};
my $http_daemon_supports_ipv6 = eval {
require HTTP::Daemon;
HTTP::Daemon->VERSION(6.12);
};

sub run_httpd {
my ($ipv) = @_;
return undef if $ipv eq '6' && (!$ipv6_supported || !$http_daemon_supports_ipv6);
my $httpd = ddclient::Test::Fake::HTTPD->new(
host => $ipv eq '4' ? '127.0.0.1' : '::1',
daemon_args => {V6Only => 1},
);
my $ip = $ipv eq '4' ? '192.0.2.1' : '2001:db8::1';
$httpd->run(sub { return [200, ['content-type' => 'text/plain; charset=utf-8'], [$ip]]; });
diag("started IPv$ipv HTTP server running at " . $httpd->endpoint());
return $httpd;
}
my %httpd = (
'4' => run_httpd('4'),
'6' => run_httpd('6'),
);
local %ddclient::builtinweb = (
v4 => {url => "" . $httpd{'4'}->endpoint()},
defined($httpd{'6'}) ? (v6 => {url => "" . $httpd{'6'}->endpoint()}) : (),
);

local $ddclient::globals{debug} = 1;
local $ddclient::globals{verbose} = 1;
local $ddclient::now = 1000;
our @updates;
local %ddclient::protocols = (
# The `legacy` protocol reads the legacy `wantip` property and sets the legacy `ip` and `status`
# properties. (Modern protocol implementations read `wantipv4` and `wantipv6` and set `ipv4`,
# `ipv6`, `status-ipv4`, and `status-ipv6`.) It always succeeds.
legacy => {
update => sub {
for my $h (@_) {
push(@updates, [@_]);
$ddclient::config{$h}{status} = 'good';
$ddclient::config{$h}{ip} = delete($ddclient::config{$h}{wantip});
$ddclient::config{$h}{mtime} = $ddclient::now;
}
},
variables => {
%{$ddclient::variables{'protocol-common-defaults'}},
},
},
);

my @test_cases = (
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, fresh, $desc",
cfg => {
'protocol' => 'legacy',
%cfg,
},
want_update => 1,
want_recap_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
{
desc => 'legacy, fresh, use=web (IPv6)',
ipv6 => 1,
cfg => {
'protocol' => 'legacy',
'use' => 'web',
'web' => 'v6',
},
want_update => 1,
want_recap_changes => {
'atime' => $ddclient::now,
'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now,
'status-ipv6' => 'good',
},
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now,
'status-ipv6' => 'good',
},
},
{
desc => 'legacy, fresh, usev6=webv6',
ipv6 => 1,
cfg => {
'protocol' => 'legacy',
'usev6' => 'webv6',
},
want_update => 1,
want_recap_changes => {
'atime' => $ddclient::now,
'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now,
'status-ipv6' => 'good',
},
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now,
'status-ipv6' => 'good',
},
},
{
desc => 'legacy, fresh, usev4=webv4 usev6=webv6',
ipv6 => 1,
cfg => {
'protocol' => 'legacy',
'usev4' => 'webv4',
'usev6' => 'webv6',
},
want_update => 1,
want_recap_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
},
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, no change, not yet time, $desc",
recap => {
'atime' => $ddclient::now - ddclient::opt('min-interval'),
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now - ddclient::opt('min-interval'),
'status-ipv4' => 'good',
},
cfg => {
'protocol' => 'legacy',
%cfg,
},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, min-interval elapsed but no change, $desc",
recap => {
'atime' => $ddclient::now - ddclient::opt('min-interval') - 1,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now - ddclient::opt('min-interval') - 1,
'status-ipv4' => 'good',
},
cfg => {
'protocol' => 'legacy',
%cfg,
},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, needs update, not yet time, $desc",
recap => {
'atime' => $ddclient::now - ddclient::opt('min-interval'),
'ipv4' => '192.0.2.2',
'mtime' => $ddclient::now - ddclient::opt('min-interval'),
'status-ipv4' => 'good',
},
cfg => {
'protocol' => 'legacy',
%cfg,
},
want_recap_changes => {
'warned-min-interval' => $ddclient::now,
},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, min-interval elapsed, needs update, $desc",
recap => {
'atime' => $ddclient::now - ddclient::opt('min-interval') - 1,
'ipv4' => '192.0.2.2',
'mtime' => $ddclient::now - ddclient::opt('min-interval') - 1,
'status-ipv4' => 'good',
},
cfg => {
'protocol' => 'legacy',
%cfg,
},
want_update => 1,
want_recap_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
},
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, previous failed update, not yet time to retry, $desc",
recap => {
'atime' => $ddclient::now - ddclient::opt('min-error-interval'),
'ipv4' => '192.0.2.2',
'mtime' => $ddclient::now - max(ddclient::opt('min-error-interval'),
ddclient::opt('min-interval')) - 1,
'status-ipv4' => 'failed',
},
cfg => {
'protocol' => 'legacy',
%cfg,
},
want_recap_changes => {
'warned-min-error-interval' => $ddclient::now,
},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
map({
my %cfg = %{delete($_->{cfg})};
my $desc = join(' ', map("$_=$cfg{$_}", keys(%cfg)));
{
desc => "legacy, previous failed update, time to retry, $desc",
recap => {
'atime' => $ddclient::now - ddclient::opt('min-error-interval') - 1,
'ipv4' => '192.0.2.2',
'mtime' => $ddclient::now - ddclient::opt('min-error-interval') - 2,
'status-ipv4' => 'failed',
},
cfg => {
'protocol' => 'legacy',
%cfg,
},
want_update => 1,
want_recap_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
%$_,
};
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
);

for my $tc (@test_cases) {
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} && !$http_daemon_supports_ipv6;
subtest($tc->{desc} => sub {
local $ddclient::_l = ddclient::pushlogctx($tc->{desc});
# Copy %{$tc->{recap}} so that updates to $recap{$h} don't update %{$tc->{recap}}.
local %ddclient::recap = (host => {%{$tc->{recap} // {}}});
my $cachef = File::Temp->new();
# $cachef is an object that stringifies to a filename.
local $ddclient::globals{cache} = "$cachef";
my %cfg = (
%{$tc->{recap} // {}}, # Simulate a previous update.
web => 'v4',
webv4 => 'v4',
webv6 => 'v6',
%{$tc->{cfg} // {}},
);
# Copy %cfg so that updates to $config{$h} don't update %cfg.
local %ddclient::config = (host => {%cfg});
local @updates;

ddclient::update_nics();

TODO: {
local $TODO = $tc->{want_update_TODO};
is_deeply(\@updates, [(['host']) x ($tc->{want_update} ? 1 : 0)],
'got expected update');
}
my %want_recap = (host => {
%{$tc->{recap} // {}},
%{$tc->{want_recap_changes} // {}},
});
TODO: {
local $TODO = $tc->{want_recap_changes_TODO};
is_deeply(\%ddclient::recap, \%want_recap, 'recap matches')
or diag(ddclient::repr(Values => [\%ddclient::recap, \%want_recap],
Names => ['*got', '*want']));
}
my %want_cfg = (host => {
$tc->{want_update} ? (update => 1) : (),
%cfg,
%{$tc->{want_cfg_changes} // {}},
});
TODO: {
local $TODO = $tc->{want_cfg_changes_TODO};
is_deeply(\%ddclient::config, \%want_cfg, 'config matches')
or diag(ddclient::repr(Values => [\%ddclient::config, \%want_cfg],
Names => ['*got', '*want']));
}
});
}
}

done_testing();
Loading

0 comments on commit 3dafdbf

Please sign in to comment.