From c545758552ababa069fc090ac50b79a69ad72457 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Thu, 21 Sep 2023 21:35:18 -0500 Subject: ddclient: T5612: Enable TTL support for web-service based protocols Enable TTL support for web-service based protocols in addition to RFC2136 based (nsupdate) protocol. Since TTL is not supported by all protocols, and thus cannot have a configuration default, the existing XML snippet `include/dns/time-to-live.xml.i` does not have common `300` anymore and is instead added explicitly whenever necessary. --- interface-definitions/include/dns/time-to-live.xml.i | 1 - 1 file changed, 1 deletion(-) (limited to 'interface-definitions/include') diff --git a/interface-definitions/include/dns/time-to-live.xml.i b/interface-definitions/include/dns/time-to-live.xml.i index 5c1a1472d..000eea108 100644 --- a/interface-definitions/include/dns/time-to-live.xml.i +++ b/interface-definitions/include/dns/time-to-live.xml.i @@ -10,6 +10,5 @@ - 300 -- cgit v1.2.3 From 25588799dd5f655ee093b650f607d0bab1fd3d00 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Sat, 23 Sep 2023 19:06:03 -0500 Subject: ddclient: T5612: Relax hostname validation for apex and wildcard entry Some porvides (like 'namecheap') allow to use '@' or '*' as hostname prefix for apex and wildcard records. This commit relaxes the hostname validation to allow these prefixes. --- .../dns/dynamic-service-host-name-server.xml.i | 3 ++- smoketest/scripts/cli/test_service_dns_dynamic.py | 26 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) (limited to 'interface-definitions/include') diff --git a/interface-definitions/include/dns/dynamic-service-host-name-server.xml.i b/interface-definitions/include/dns/dynamic-service-host-name-server.xml.i index ee1af2a36..9dd14f97c 100644 --- a/interface-definitions/include/dns/dynamic-service-host-name-server.xml.i +++ b/interface-definitions/include/dns/dynamic-service-host-name-server.xml.i @@ -4,8 +4,9 @@ Hostname to register with Dynamic DNS service #include + (\@|\*)[-.A-Za-z0-9]* - Host-name must be alphanumeric and can contain hyphens + Host-name must be alphanumeric, can contain hyphens and can be prefixed with '@' or '*' diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index aa4891829..f1870320e 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -203,7 +203,31 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): self.assertIn(f'password={key_file.name}', ddclient_conf) self.assertIn(f'ttl={ttl}', ddclient_conf) - def test_05_dyndns_vrf(self): + def test_05_dyndns_hostname(self): + # Check if DDNS service can be configured and runs + svc_path = ['address', interface, 'service', 'namecheap'] + proto = 'namecheap' + hostnames = ['@', 'www', hostname, f'@.{hostname}'] + + for name in hostnames: + self.cli_set(base_path + svc_path + ['protocol', proto]) + self.cli_set(base_path + svc_path + ['server', server]) + self.cli_set(base_path + svc_path + ['username', username]) + self.cli_set(base_path + svc_path + ['password', password]) + self.cli_set(base_path + svc_path + ['host-name', name]) + + # commit changes + self.cli_commit() + + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + self.assertIn(f'protocol={proto}', ddclient_conf) + self.assertIn(f'server={server}', ddclient_conf) + self.assertIn(f'login={username}', ddclient_conf) + self.assertIn(f'password={password}', ddclient_conf) + self.assertIn(f'{name}', ddclient_conf) + + def test_06_dyndns_vrf(self): vrf_name = f'vyos-test-{"".join(random.choices(string.ascii_letters + string.digits, k=5))}' svc_path = ['address', interface, 'service', 'cloudflare'] -- cgit v1.2.3 From 7a1711419eeaaf6bee837e824eec977b16767427 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Thu, 21 Sep 2023 22:04:15 -0500 Subject: ddclient: T5612: Refactor zone configuration Refactor zone configuration to use shared XML snippet for all cases. --- interface-definitions/dns-dynamic.xml.in | 23 ++-------------------- .../include/dns/dynamic-service-zone.xml.i | 14 +++++++++++++ 2 files changed, 16 insertions(+), 21 deletions(-) create mode 100644 interface-definitions/include/dns/dynamic-service-zone.xml.i (limited to 'interface-definitions/include') diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in index 93b1dbc23..ba7f426c1 100644 --- a/interface-definitions/dns-dynamic.xml.in +++ b/interface-definitions/dns-dynamic.xml.in @@ -74,18 +74,7 @@ #include - - - Forwarding zone to be updated - - txt - RFC2136 Zone to be updated - - - - - - + #include @@ -113,15 +102,7 @@ - - - DNS zone to update (not used by all protocols) - - txt - Name of DNS zone - - - + #include IP address version to use diff --git a/interface-definitions/include/dns/dynamic-service-zone.xml.i b/interface-definitions/include/dns/dynamic-service-zone.xml.i new file mode 100644 index 000000000..0cc00468f --- /dev/null +++ b/interface-definitions/include/dns/dynamic-service-zone.xml.i @@ -0,0 +1,14 @@ + + + + DNS zone to be updated + + txt + Name of DNS zone + + + + + + + -- cgit v1.2.3 From c3ba4527824c9f4d2e53e7fbd0bff4b84c3012f4 Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Wed, 13 Sep 2023 01:02:12 -0500 Subject: ddclient: T5574: Support per-service cache management for services Add support for per-service cache management for ddclient providers via `wait-time` and `expiry-time` options. This allows for finer-grained control over how often a service is updated and how long the hostname will be cached before being marked expired in ddclient's cache. More specifically, `wait-time` controls how often ddclient will attempt to check for a change in the hostname's IP address, and `expiry-time` controls how often ddclient to a forced update of the hostname's IP address. These options intentionally don't have any default values because they are provider-specific. They get treated similar to the other provider- specific options in that they are only used if defined. --- data/templates/dns-dynamic/ddclient.conf.j2 | 11 ++++----- interface-definitions/dns-dynamic.xml.in | 2 ++ .../dns/dynamic-service-wait-expiry-time.xml.i | 28 ++++++++++++++++++++++ smoketest/scripts/cli/test_service_dns_dynamic.py | 12 ++++++++++ src/conf_mode/dns_dynamic.py | 3 +++ 5 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 interface-definitions/include/dns/dynamic-service-wait-expiry-time.xml.i (limited to 'interface-definitions/include') diff --git a/data/templates/dns-dynamic/ddclient.conf.j2 b/data/templates/dns-dynamic/ddclient.conf.j2 index 5905b19ea..6e77abdb5 100644 --- a/data/templates/dns-dynamic/ddclient.conf.j2 +++ b/data/templates/dns-dynamic/ddclient.conf.j2 @@ -14,10 +14,8 @@ if{{ ipv }}={{ address }}, \ {% endif %} {% endfor %} {# Other service options #} -{% for k,v in kwargs.items() %} -{% if v is vyos_defined %} -{{ k }}={{ v }}{{ ',' if not loop.last }} \ -{% endif %} +{% for k,v in kwargs.items() if v is vyos_defined %} +{{ k | replace('_', '-') }}={{ v }}{{ ',' if not loop.last }} \ {% endfor %} {# Actual hostname for the service #} {{ host }} @@ -49,7 +47,6 @@ use=no {{ render_config(host, address, service_cfg.web_options, protocol='nsupdate', server=config.server, zone=config.zone, password=config.key, ttl=config.ttl) }} - {% endfor %} {% endfor %} {% endif %} @@ -66,8 +63,8 @@ use=no # Web service dynamic DNS configuration for {{ name }}: [{{ config.protocol }}, {{ host }}] {{ render_config(host, address, service_cfg.web_options, ip_suffixes, protocol=config.protocol, server=config.server, zone=config.zone, - login=config.username, password=config.password, ttl=config.ttl) }} - + login=config.username, password=config.password, ttl=config.ttl, + min_interval=config.wait_time, max_interval=config.expiry_time) }} {% endfor %} {% endfor %} {% endif %} diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in index ba7f426c1..723223f1c 100644 --- a/interface-definitions/dns-dynamic.xml.in +++ b/interface-definitions/dns-dynamic.xml.in @@ -61,6 +61,7 @@ #include #include + #include File containing the TSIG secret key shared with remote DNS server @@ -88,6 +89,7 @@ #include #include + #include #include #include #include diff --git a/interface-definitions/include/dns/dynamic-service-wait-expiry-time.xml.i b/interface-definitions/include/dns/dynamic-service-wait-expiry-time.xml.i new file mode 100644 index 000000000..866690cbe --- /dev/null +++ b/interface-definitions/include/dns/dynamic-service-wait-expiry-time.xml.i @@ -0,0 +1,28 @@ + + + + Time in seconds to wait between update attempts + + u32:60-86400 + Time in seconds + + + + + Wait time must be between 60 and 86400 seconds + + + + + Time in seconds for the hostname to be marked expired in cache + + u32:300-2160000 + Time in seconds + + + + + Expiry time must be between 300 and 2160000 seconds + + + diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index 66dcde434..acabc0070 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -116,6 +116,9 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): svc_path = ['address', interface, 'service', 'dynv6'] proto = 'dyndns2' ip_version = 'ipv6' + wait_time = '600' + expiry_time_good = '3600' + expiry_time_bad = '360' self.cli_set(base_path + ['timeout', timeout]) self.cli_set(base_path + svc_path + ['ip-version', ip_version]) @@ -124,6 +127,13 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + svc_path + ['username', username]) self.cli_set(base_path + svc_path + ['password', password]) self.cli_set(base_path + svc_path + ['host-name', hostname]) + self.cli_set(base_path + svc_path + ['wait-time', wait_time]) + + # expiry-time must be greater than wait-time, exception is raised otherwise + self.cli_set(base_path + svc_path + ['expiry-time', expiry_time_bad]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + svc_path + ['expiry-time', expiry_time_good]) # commit changes self.cli_commit() @@ -137,6 +147,8 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): self.assertIn(f'server={server}', ddclient_conf) self.assertIn(f'login={username}', ddclient_conf) self.assertIn(f'password={password}', ddclient_conf) + self.assertIn(f'min-interval={wait_time}', ddclient_conf) + self.assertIn(f'max-interval={expiry_time_good}', ddclient_conf) # IPv4+IPv6 dual DDNS service configuration def test_03_dyndns_service_dual_stack(self): diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py index 8a438cf6f..874c4b689 100755 --- a/src/conf_mode/dns_dynamic.py +++ b/src/conf_mode/dns_dynamic.py @@ -111,6 +111,9 @@ def verify(dyndns): raise ConfigError(f'"{config["protocol"]}" does not support ' f'both IPv4 and IPv6 at the same time for "{config["server"]}"') + if {'wait_time', 'expiry_time'} <= config.keys() and int(config['expiry_time']) < int(config['wait_time']): + raise ConfigError(f'"expiry-time" must be greater than "wait-time"') + return None def generate(dyndns): -- cgit v1.2.3 From 78a7f0182a3ae504f8a29502cc064f56769df75a Mon Sep 17 00:00:00 2001 From: Indrajit Raychaudhuri Date: Thu, 2 Nov 2023 20:44:57 -0500 Subject: ddclient: T5708: Migrate `timeout` to `interval` Time interval in seconds to wait between DNS updates would be a bit more intuitive as `interval` than `timeout`. --- data/templates/dns-dynamic/ddclient.conf.j2 | 2 +- interface-definitions/dns-dynamic.xml.in | 6 +-- .../include/version/dns-dynamic-version.xml.i | 2 +- smoketest/scripts/cli/test_service_dns_dynamic.py | 6 +-- src/migration-scripts/dns-dynamic/1-to-2 | 52 ++++++++++++++++++++++ 5 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 src/migration-scripts/dns-dynamic/1-to-2 (limited to 'interface-definitions/include') diff --git a/data/templates/dns-dynamic/ddclient.conf.j2 b/data/templates/dns-dynamic/ddclient.conf.j2 index 6e77abdb5..879887a1f 100644 --- a/data/templates/dns-dynamic/ddclient.conf.j2 +++ b/data/templates/dns-dynamic/ddclient.conf.j2 @@ -21,7 +21,7 @@ if{{ ipv }}={{ address }}, \ {{ host }} {% endmacro %} ### Autogenerated by dns_dynamic.py ### -daemon={{ timeout }} +daemon={{ interval }} syslog=yes ssl=yes pid={{ config_file | replace('.conf', '.pid') }} diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in index 723223f1c..07b1bf1b8 100644 --- a/interface-definitions/dns-dynamic.xml.in +++ b/interface-definitions/dns-dynamic.xml.in @@ -134,9 +134,9 @@ - + - Time in seconds to wait between DNS updates + Interval in seconds to wait between Dynamic DNS updates u32:60-3600 Time in seconds @@ -144,7 +144,7 @@ - Timeout must be between 60 and 3600 seconds + Interval must be between 60 and 3600 seconds 300 diff --git a/interface-definitions/include/version/dns-dynamic-version.xml.i b/interface-definitions/include/version/dns-dynamic-version.xml.i index b25fc6e76..7bdb90a35 100644 --- a/interface-definitions/include/version/dns-dynamic-version.xml.i +++ b/interface-definitions/include/version/dns-dynamic-version.xml.i @@ -1,3 +1,3 @@ - + diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index acabc0070..6c2f584c9 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -112,7 +112,7 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): # IPv6 only DDNS service configuration def test_02_dyndns_service_ipv6(self): - timeout = '60' + interval = '60' svc_path = ['address', interface, 'service', 'dynv6'] proto = 'dyndns2' ip_version = 'ipv6' @@ -120,7 +120,7 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): expiry_time_good = '3600' expiry_time_bad = '360' - self.cli_set(base_path + ['timeout', timeout]) + self.cli_set(base_path + ['interval', interval]) self.cli_set(base_path + svc_path + ['ip-version', ip_version]) self.cli_set(base_path + svc_path + ['protocol', proto]) self.cli_set(base_path + svc_path + ['server', server]) @@ -140,7 +140,7 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): # Check the generating config parameters ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') - self.assertIn(f'daemon={timeout}', ddclient_conf) + self.assertIn(f'daemon={interval}', ddclient_conf) self.assertIn(f'usev6=ifv6', ddclient_conf) self.assertIn(f'ifv6={interface}', ddclient_conf) self.assertIn(f'protocol={proto}', ddclient_conf) diff --git a/src/migration-scripts/dns-dynamic/1-to-2 b/src/migration-scripts/dns-dynamic/1-to-2 new file mode 100644 index 000000000..b4679769c --- /dev/null +++ b/src/migration-scripts/dns-dynamic/1-to-2 @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# T5708: +# - migrate "service dns dynamic timeout ..." +# to "service dns dynamic interval ..." + +import sys +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +base_path = ['service', 'dns', 'dynamic'] +timeout_path = base_path + ['timeout'] + +if not config.exists(base_path): + # Nothing to do + sys.exit(0) + +# Migrate "service dns dynamic timeout ..." +# to "service dns dynamic interval ..." +if config.exists(timeout_path): + config.rename(timeout_path, 'interval') + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) -- cgit v1.2.3