From ebdff5d1132c0f9474991012b1d12fb5c9f53b8a Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Wed, 2 Oct 2024 16:51:20 +1000 Subject: [PATCH 01/17] T6773: dhcp-server: ddns: DDNS support WIP --- .../service_dhcp-server.xml.in | 122 +++++++++++++++++- 1 file changed, 119 insertions(+), 3 deletions(-) diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in index c0ab7c0489..b44c3efb24 100644 --- a/interface-definitions/service_dhcp-server.xml.in +++ b/interface-definitions/service_dhcp-server.xml.in @@ -10,12 +10,128 @@ #include - + Dynamically update Domain Name System (RFC4702) - - + + + + Name of the TSIG key for DNS updates + + #include + + Invalid TSIG key name. May only contain letters, numbers and -_ + + + + + TSIG key algorithm + + hmac-md5 hmac-sha1 hmac-sha224 hmac-sha256 hmac-sha384 hmac-sha512 + + + hmac-md5 + MD5 HMAC algorithm + + + hmac-sha1 + SHA1 HMAC algorithm + + + hmac-sha224 + SHA224 HMAC algorithm + + + hmac-sha256 + SHA256 HMAC algorithm + + + hmac-sha384 + SHA384 HMAC algorithm + + + hmac-sha512 + SHA512 HMAC algorithm + + + (hmac-md5|hmac-sha1|hmac-sha224|hmac-sha256|hmac-sha384|hmac-sha512) + + Invalid TSIG key algorithm + + + + + TSIG key secret (base64-encoded) + + + + + + + + + + Forward DNS domain name + + #include + + Invalid forward DNS domain name + + + + + TSIG key name for forward DNS updates + + #include + + Invalid TSIG key name. May only contain letters, numbers and -_ + + + + + DNS server specification + + u32:1-999999 + Number for this DNS server + + + + + DNS server number must be between 1 and 999999 + + + + + DNS server IP address + + ipv4 + DNS server IP address + + + + + + + + + DNS server port + + u16 + DNS server port + + + + + Invalid forward DNS server port + + + + + + + + DHCP high availability configuration From 20c19267d1c3fbd4294267f05d200c2bd2322cdf Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Fri, 4 Oct 2024 13:12:19 +1000 Subject: [PATCH 02/17] T6773: dhcp-server: ddns: Dynamic DNS support for KEA WIP Resolve conflicts with current --- .../dhcp-server/kea-dhcp-ddns.conf.j2 | 26 +++ data/templates/dhcp-server/kea-dhcp4.conf.j2 | 13 ++ .../include/dhcp/ddns-server.xml.i | 33 ++++ .../service_dhcp-server.xml.in | 164 ++++++++++++++---- python/vyos/kea.py | 10 ++ python/vyos/template.py | 70 ++++++++ 6 files changed, 279 insertions(+), 37 deletions(-) create mode 100644 data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 create mode 100644 interface-definitions/include/dhcp/ddns-server.xml.i diff --git a/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 b/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 new file mode 100644 index 0000000000..24654c9fc9 --- /dev/null +++ b/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 @@ -0,0 +1,26 @@ +{ + "DhcpDdns": { + "ip-address": "127.0.0.1", + "port": 53001, + "control-socket": { + "socket-type": "unix", + "socket-name": "/run/kea/kea-ddns-ctrl-socket" + }, + "tsig-keys": {{ tsig_key_name | kea_dynamic_dns_update_tsig_key_json }}, + "forward-ddns" : {{ forward_ddns_domain_name | kea_dynamic_dns_update_domains }}, + "reverse-ddns" : {{ reverse_ddns_domain_name | kea_dynamic_dns_update_domains }}, + "loggers": [ + { + "name": "kea-dhcp-ddns", + "output_options": [ + { + "output": "stdout", + "pattern": "%-5p %m\n" + } + ], + "severity": "INFO", + "debuglevel": 0 + } + ] + } +} diff --git a/data/templates/dhcp-server/kea-dhcp4.conf.j2 b/data/templates/dhcp-server/kea-dhcp4.conf.j2 index 8d9ffb194c..c10b2b3b33 100644 --- a/data/templates/dhcp-server/kea-dhcp4.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2 @@ -36,6 +36,19 @@ "space": "ubnt" } ], +{% if dynamic_dns_update.enable_updates is vyos_defined %} + "dhcp-ddns": { + "enable-updates": true, + "server-ip": "127.0.0.1", + "server-port": 53001, + "sender-ip": "", + "sender-port": 0, + "max-queue-size": 1024, + "ncr-protocol": "UDP", + "ncr-format": "JSON" + }, + {{ dynamic_dns_update | kea_dynamic_dns_update_main_json }} +{% endif %} "hooks-libraries": [ {% if high_availability is vyos_defined %} { diff --git a/interface-definitions/include/dhcp/ddns-server.xml.i b/interface-definitions/include/dhcp/ddns-server.xml.i new file mode 100644 index 0000000000..95f5b4dd98 --- /dev/null +++ b/interface-definitions/include/dhcp/ddns-server.xml.i @@ -0,0 +1,33 @@ + + + + DNS server specification + + u32:1-999999 + Number for this DNS server + + + + + DNS server number must be between 1 and 999999 + + + + + DNS server IP address + + ipv4 + DNS server IP address + + + + + + + #include + + 53 + + + + diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in index b44c3efb24..4ee604c556 100644 --- a/interface-definitions/service_dhcp-server.xml.in +++ b/interface-definitions/service_dhcp-server.xml.in @@ -15,6 +15,114 @@ Dynamically update Domain Name System (RFC4702) + + + Enable DDNS updates + + + + + + Override client delegation + + + + + + Override client delegation + + + + + + Replace client name mode + + never always when-present when-not-present + + + never + Use the name the client sent. If the client sent no name, do not generate one. This is the default behavior + + + always + Replace the name the client sent. If the client sent no name, generate one for the client + + + when-present + Replace the name the client sent. If the client sent no name, do not generate one + + + when-not-present + Use the name the client sent. If the client sent no name, generate one for the client + + + (never|always|when-present|when-not-present) + + Invalid replace client name mode + + + + + The prefix used in the generation of an FQDN + + + + Invalid generated prefix + + + + + The suffix used when generating an FQDN, or when qualifying a partial name + + + + Invalid qualifying suffix + + + + + Update DNS record on lease renew + + + + + + Defines DNS conflict resolution behavior + + check-with-dhcid no-check-with-dhcid check-exists-with-dhcid no-check-without-dhcid + + + check-with-dhcid + Carry out RFC 4703-compliant conflict resolution. Existing DNS entries may only be overwritten if they have a DHCID record and it matches the client's DHCID. This is the default behavior + + + no-check-with-dhcid + Existing DNS entries may be overwritten by any client, whether those entries include a DHCID record or not. The new entries will include a DHCID record for the client to whom they belong + + + check-exists-with-dhcid + Existing DNS entries may only be overwritten if they have a DHCID record. The DHCID record need not match the client's DHCID + + + no-check-without-dhcid + Existing DNS entries may be overwritten by any client; new entries will not include DHCID records + + + (check-with-dhcid|no-check-with-dhcid|check-exists-with-dhcid|no-check-without-dhcid) + + Invalid DNS conflict resolution mode + + + + + A regular expression describing the invalid character set in the host name + + + + + A string of zero or more characters with which to replace each invalid character in the host name + + Name of the TSIG key for DNS updates @@ -74,7 +182,7 @@ Forward DNS domain name - #include + Invalid forward DNS domain name @@ -88,46 +196,28 @@ Invalid TSIG key name. May only contain letters, numbers and -_ - + #include + + + + + Reverse DNS domain name + + + + Invalid reverse DNS domain name + + + - DNS server specification - - u32:1-999999 - Number for this DNS server - + TSIG key name for reverse DNS updates - + #include - DNS server number must be between 1 and 999999 + Invalid TSIG key name. May only contain letters, numbers and -_ - - - - DNS server IP address - - ipv4 - DNS server IP address - - - - - - - - - DNS server port - - u16 - DNS server port - - - - - Invalid forward DNS server port - - - - + + #include diff --git a/python/vyos/kea.py b/python/vyos/kea.py index 2b0cac7e65..a72654e66d 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -350,6 +350,16 @@ def kea6_parse_subnet(subnet, config): return out +def kea_parse_tsig_algo(algo_spec): + translate = { + 'hmac-md5': 'HMAC-MD5', + 'hmac-sha1': 'HMAC-SHA1', + 'hmac-sha224': 'HMAC-SHA224', + 'hmac-sha256': 'HMAC-SHA256', + 'hmac-sha384': 'HMAC-SHA384', + 'hmac-sha512': 'HMAC-SHA512' + } + return translate[algo_spec] def _ctrl_socket_command(inet, command, args=None): path = kea_ctrl_socket.format(inet=inet) diff --git a/python/vyos/template.py b/python/vyos/template.py index 7ba85a046a..1ab34fa710 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -859,6 +859,76 @@ def kea_high_availability_json(config): return dumps(data) +@register_filter('kea_dynamic_dns_update_main_json') +def kea_dynamic_dns_update_main_json(config): + from json import dumps + + data = { + "ddns-send-updates": True, + "ddns-override-no-update": 'override_no_update' in config, + "ddns-override-client-update": 'override_client_update' in config, + "ddns-update-on-renew": 'update_on_renew' in config, + } + + if 'replace_client_name' in config: + data['ddns-replace-client-name'] = config['replace_client_name'] + if 'conflict_resolution_mode' in config: + data['ddns-conflict-resolution-mode'] = config['conflict_resolution_mode'] + if 'generated_prefix' in config: + data['ddns-generated-prefix'] = config['generated_prefix'] + if 'qualifying_suffix' in config: + data['ddns-qualifying-suffix'] = config['qualifying_suffix'] + if 'hostname_char_set' in config: + data['hostname-char-set'] = config['hostname_char_set'] + if 'hostname_char_replacement' in config: + data['hostname-char-replacement'] = config['hostname_char_replacement'] + + return dumps(data, indent=4)[1:-1] + +@register_filter('kea_dynamic_dns_update_tsig_key_json') +def kea_dynamic_dns_update_tsig_key_json(tsig_keys): + from kea import kea_parse_tsig_algo + from json import dumps + out = [] + + for tsig_key_name, tsig_key_config in tsig_keys.items(): + tsig_key = { + 'name': tsig_key_name, + 'algorithm': kea_parse_tsig_algo(tsig_key_config['algorithm']), + 'secret': tsig_key_config['secret'] + } + out.append(tsig_key) + + return dumps(out, indent=4) + +@register_filter('kea_dynamic_dns_update_domains') +def kea_dynamic_dns_update_domains(domains): + from json import dumps + out = [] + + for domain_name, domain_config in domains.items(): + domain = { + 'name': domain_name, + + } + if 'key_name' in domain_config: + domain['key-name'] = domain_config['key_name'] + + if 'dns_server' in domain_config: + dns_servers = [] + for dns_server_no, dns_server_config in domain_config['dns_server'].items(): + dns_server = { + 'ip-address': dns_server_config['ip_address'] + } + if 'port' in dns_server_config: + dns_server['port'] = dns_server_config['port'] + dns_servers.append(dns_server) + domain['dns-servers'] = dns_servers + + out.append(domain) + + return dumps(out, indent=4) + @register_filter('kea_shared_network_json') def kea_shared_network_json(shared_networks): from vyos.kea import kea_parse_options From 609d5778187739b375592968ba1a027b82a6945a Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Fri, 4 Oct 2024 20:16:22 +1000 Subject: [PATCH 03/17] T6773: dhcp-server: ddns: Resolve merge conflicts in the smoke tests --- .../dhcp-server/kea-dhcp-ddns.conf.j2 | 10 ++- data/templates/dhcp-server/kea-dhcp4.conf.j2 | 2 +- .../include/dhcp/ddns-server.xml.i | 16 +--- python/vyos/template.py | 24 ++++-- smoketest/config-tests/basic-vyos | 11 +++ smoketest/configs/basic-vyos | 29 +++++++ .../scripts/cli/test_service_dhcp-server.py | 86 +++++++++++++++++++ src/conf_mode/service_dhcp-server.py | 34 ++++++++ .../override.conf | 7 ++ 9 files changed, 193 insertions(+), 26 deletions(-) create mode 100644 src/etc/systemd/system/kea-dhcp-ddns-server.service.d/override.conf diff --git a/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 b/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 index 24654c9fc9..4243c004dc 100644 --- a/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 @@ -6,9 +6,13 @@ "socket-type": "unix", "socket-name": "/run/kea/kea-ddns-ctrl-socket" }, - "tsig-keys": {{ tsig_key_name | kea_dynamic_dns_update_tsig_key_json }}, - "forward-ddns" : {{ forward_ddns_domain_name | kea_dynamic_dns_update_domains }}, - "reverse-ddns" : {{ reverse_ddns_domain_name | kea_dynamic_dns_update_domains }}, + "tsig-keys": {{ dynamic_dns_update | kea_dynamic_dns_update_tsig_key_json }}, + "forward-ddns" : { + "ddns-domains": {{ dynamic_dns_update | kea_dynamic_dns_update_domains('forward_ddns_domain_name') }} + }, + "reverse-ddns" : { + "ddns-domains": {{ dynamic_dns_update | kea_dynamic_dns_update_domains('reverse_ddns_domain_name') }} + }, "loggers": [ { "name": "kea-dhcp-ddns", diff --git a/data/templates/dhcp-server/kea-dhcp4.conf.j2 b/data/templates/dhcp-server/kea-dhcp4.conf.j2 index c10b2b3b33..6a05dc1f5b 100644 --- a/data/templates/dhcp-server/kea-dhcp4.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2 @@ -47,7 +47,7 @@ "ncr-protocol": "UDP", "ncr-format": "JSON" }, - {{ dynamic_dns_update | kea_dynamic_dns_update_main_json }} + {{ dynamic_dns_update | kea_dynamic_dns_update_main_json }}, {% endif %} "hooks-libraries": [ {% if high_availability is vyos_defined %} diff --git a/interface-definitions/include/dhcp/ddns-server.xml.i b/interface-definitions/include/dhcp/ddns-server.xml.i index 95f5b4dd98..8539f97e71 100644 --- a/interface-definitions/include/dhcp/ddns-server.xml.i +++ b/interface-definitions/include/dhcp/ddns-server.xml.i @@ -12,22 +12,8 @@ DNS server number must be between 1 and 999999 - - - DNS server IP address - - ipv4 - DNS server IP address - - - - - - + #include #include - - 53 - diff --git a/python/vyos/template.py b/python/vyos/template.py index 1ab34fa710..009e792ca4 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -886,11 +886,16 @@ def kea_dynamic_dns_update_main_json(config): return dumps(data, indent=4)[1:-1] @register_filter('kea_dynamic_dns_update_tsig_key_json') -def kea_dynamic_dns_update_tsig_key_json(tsig_keys): - from kea import kea_parse_tsig_algo +def kea_dynamic_dns_update_tsig_key_json(config): + from vyos.kea import kea_parse_tsig_algo from json import dumps out = [] + if 'tsig_key_name' not in config: + return dumps(out) + + tsig_keys = config['tsig_key_name'] + for tsig_key_name, tsig_key_config in tsig_keys.items(): tsig_key = { 'name': tsig_key_name, @@ -899,13 +904,18 @@ def kea_dynamic_dns_update_tsig_key_json(tsig_keys): } out.append(tsig_key) - return dumps(out, indent=4) + return dumps(out, indent=12) @register_filter('kea_dynamic_dns_update_domains') -def kea_dynamic_dns_update_domains(domains): +def kea_dynamic_dns_update_domains(config, type_key): from json import dumps out = [] + if type_key not in config: + return dumps(out) + + domains = config[type_key] + for domain_name, domain_config in domains.items(): domain = { 'name': domain_name, @@ -916,9 +926,9 @@ def kea_dynamic_dns_update_domains(domains): if 'dns_server' in domain_config: dns_servers = [] - for dns_server_no, dns_server_config in domain_config['dns_server'].items(): + for dns_server_config in domain_config['dns_server'].values(): dns_server = { - 'ip-address': dns_server_config['ip_address'] + 'ip-address': dns_server_config['address'] } if 'port' in dns_server_config: dns_server['port'] = dns_server_config['port'] @@ -927,7 +937,7 @@ def kea_dynamic_dns_update_domains(domains): out.append(domain) - return dumps(out, indent=4) + return dumps(out, indent=12) @register_filter('kea_shared_network_json') def kea_shared_network_json(shared_networks): diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos index 4793e069e4..3d408ab613 100644 --- a/smoketest/config-tests/basic-vyos +++ b/smoketest/config-tests/basic-vyos @@ -28,6 +28,17 @@ set protocols static arp interface eth2.200.201 address 100.64.201.20 mac '00:50 set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50:00:00:00:30' set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40' set protocols static route 0.0.0.0/0 next-hop 100.64.0.1 +set service dhcp-server dynamic-dns-update enable-updates +set service dhcp-server dynamic-dns-update tsig-key-name domain-lan-updates algorithm 'hmac-sha256' +set service dhcp-server dynamic-dns-update tsig-key-name domain-lan-updates secret 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==' +set service dhcp-server dynamic-dns-update tsig-key-name reverse-0-168-192 algorithm 'hmac-sha256' +set service dhcp-server dynamic-dns-update tsig-key-name reverse-0-168-192 secret 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ==' +set service dhcp-server dynamic-dns-update forward-ddns-domain-name domain.lan dns-server 1 address '192.168.0.1' +set service dhcp-server dynamic-dns-update forward-ddns-domain-name domain.lan dns-server 2 address '100.100.0.1' +set service dhcp-server dynamic-dns-update forward-ddns-domain-name domain.lan key-name 'domain-lan-updates' +set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in-addr.arpa dns-server 1 address '192.168.0.1' +set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in-addr.arpa dns-server 2 address '100.100.0.1' +set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in-addr.arpa key-name 'reverse-0-168-192' set service dhcp-server shared-network-name LAN authoritative set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net' diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos index a6cd3b6e16..e102d95825 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -99,6 +99,35 @@ protocols { } service { dhcp-server { + dynamic-dns-update { + enable-updates + forward-ddns-domain-name domain.lan { + dns-server 1 { + address 192.168.0.1 + } + dns-server 2 { + address 100.100.0.1 + } + key-name domain-lan-updates + } + reverse-ddns-domain-name 0.168.192.in-addr.arpa { + dns-server 1 { + address 192.168.0.1 + } + dns-server 2 { + address 100.100.0.1 + } + key-name reverse-0-168-192 + } + tsig-key-name domain-lan-updates { + algorithm hmac-sha256 + secret SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ== + } + tsig-key-name reverse-0-168-192 { + algorithm hmac-sha256 + secret VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ== + } + } shared-network-name LAN { authoritative subnet 192.168.0.0/24 { diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 935be6227f..27917bf0f7 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -32,7 +32,10 @@ from vyos.template import dec_ip PROCESS_NAME = 'kea-dhcp4' +D2_PROCESS_NAME = 'kea-dhcp-ddns' +CTRL_PROCESS_NAME = 'kea-ctrl-agent' KEA4_CONF = '/run/kea/kea-dhcp4.conf' +KEA4_D2_CONF = '/run/kea/kea-dhcp-ddns.conf' KEA4_CTRL = '/run/kea/dhcp4-ctrl-socket' HOSTSD_CLIENT = '/usr/bin/vyos-hostsd-client' base_path = ['service', 'dhcp-server'] @@ -1116,6 +1119,89 @@ def test_dhcp_high_availability_standby(self): # Check for running process self.verify_service_running() + def test_dhcp_dynamic_dns_update(self): + ddns = base_path + ['dynamic-dns-update'] + + self.cli_set(ddns + ['enable-updates']) + self.cli_set(ddns + ['conflict-resolution-mode', 'check-exists-with-dhcid']) + self.cli_set(ddns + ['generated-prefix', 'myfunnyprefix']) + self.cli_set(ddns + ['qualifying-suffix', 'suffix.lan']) + self.cli_set(ddns + ['hostname-char-set', 'xXyYzZ']) + self.cli_set(ddns + ['hostname-char-replacement', '_xXx_']) + self.cli_set(ddns + ['override-no-update']) + self.cli_set(ddns + ['override-client-update']) + self.cli_set(ddns + ['replace-client-name', 'always']) + self.cli_set(ddns + ['update-on-renew']) + + self.cli_set(ddns + ['tsig-key-name', 'domain-lan-updates', 'algorithm', 'hmac-sha256']) + self.cli_set(ddns + ['tsig-key-name', 'domain-lan-updates', 'secret', 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==']) + self.cli_set(ddns + ['tsig-key-name', 'reverse-0-168-192', 'algorithm', 'hmac-sha256']) + self.cli_set(ddns + ['tsig-key-name', 'reverse-0-168-192', 'secret', 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ==']) + self.cli_set(ddns + ['forward-ddns-domain-name', 'domain.lan', 'dns-server', '1', 'address', '192.168.0.1']) + self.cli_set(ddns + ['forward-ddns-domain-name', 'domain.lan', 'dns-server', '2', 'address', '100.100.0.1']) + self.cli_set(ddns + ['forward-ddns-domain-name', 'domain.lan', 'key-name', 'domain-lan-updates']) + self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'dns-server', '1', 'address', '192.168.0.1']) + self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'dns-server', '1', 'port', '1053']) + self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'dns-server', '2', 'address', '100.100.0.1']) + self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'dns-server', '2', 'port', '1153']) + self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'key-name', 'reverse-0-168-192']) + + self.cli_commit() + + config = read_file(KEA4_CONF) + d2_config = read_file(KEA4_D2_CONF) + + obj = loads(config) + d2_obj = loads(d2_config) + + # Verify DDNS parameters in the main config file + self.verify_config_object( + obj, + ['Dhcp4', 'dhcp-ddns'], + {'enable-updates': True, 'server-ip': '127.0.0.1', 'server-port': 53001, 'sender-ip': '', 'sender-port': 0, + 'max-queue-size': 1024, 'ncr-protocol': 'UDP', 'ncr-format': 'JSON'}) + + self.verify_config_value(obj, ['Dhcp4'], 'ddns-send-updates', True) + self.verify_config_value(obj, ['Dhcp4'], 'ddns-conflict-resolution-mode', 'check-exists-with-dhcid') + self.verify_config_value(obj, ['Dhcp4'], 'ddns-generated-prefix', 'myfunnyprefix', True) + self.verify_config_value(obj, ['Dhcp4'], 'ddns-qualifying-suffix', 'suffix.lan', True) + self.verify_config_value(obj, ['Dhcp4'], 'ddns-hostname-char-set', 'xXyYzZ', True) + self.verify_config_value(obj, ['Dhcp4'], 'ddns-hostname-char-replacement', '_xXx_', True) + self.verify_config_value(obj, ['Dhcp4'], 'ddns-override-no-update', True) + self.verify_config_value(obj, ['Dhcp4'], 'ddns-override-client-update', True) + self.verify_config_value(obj, ['Dhcp4'], 'ddns-replace-client-name', 'always', True) + self.verify_config_value(obj, ['Dhcp4'], 'ddns-update-on-renew', True) + + # Verify keys and domains configuration in the D2 config + self.verify_config_object( + d2_obj, + ['DhcpDdns', 'tsig-keys', 0], + {'name': 'domain-lan-updates', 'algorithm': 'HMAC-SHA256', 'secret': 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ=='} + ) + self.verify_config_object( + d2_obj, + ['DhcpDdns', 'tsig-keys', 1], + {'name': 'reverse-0-168-192', 'algorithm': 'HMAC-SHA256', 'secret': 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ=='} + ) + + self.verify_config_object( + d2_obj, + ['DhcpDdns', 'forward-ddns', 'ddns-domains', 0], + {'name': 'domain.lan', 'key-name': 'domain-lan-updates', + 'dns-servers': [{'ip-address': '192.168.0.1'}, {'ip-address': '100.100.0.1'}]} + ) + self.verify_config_object( + d2_obj, + ['DhcpDdns', 'reverse-ddns', 'ddns-domains', 0], + {'name': '0.168.192.in-addr.arpa', 'key-name': 'reverse-0-168-192', + 'dns-servers': [{'ip-address': '192.168.0.1', 'port': 1053}, {'ip-address': '100.100.0.1', 'port': 1153}]} + ) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.assertTrue(process_named_running(D2_PROCESS_NAME)) + self.assertTrue(process_named_running(CTRL_PROCESS_NAME)) + def test_dhcp_on_interface_with_vrf(self): self.cli_set(['interfaces', 'ethernet', 'eth1', 'address', '10.1.1.1/30']) self.cli_set(['interfaces', 'ethernet', 'eth1', 'vrf', 'SMOKE-DHCP']) diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py index e46d916fd5..9fd51db5b8 100755 --- a/src/conf_mode/service_dhcp-server.py +++ b/src/conf_mode/service_dhcp-server.py @@ -43,6 +43,7 @@ ctrl_socket = '/run/kea/dhcp4-ctrl-socket' config_file = '/run/kea/kea-dhcp4.conf' +config_file_d2 = '/run/kea/kea-dhcp-ddns.conf' lease_file = '/config/dhcp/dhcp4-leases.csv' lease_file_glob = '/config/dhcp/dhcp4-leases*' user_group = '_kea' @@ -170,6 +171,15 @@ def get_config(config=None): return dhcp +def verify_ddns_domain_servers(domain_type, domain): + if 'dns_server' in domain: + invalid_servers = [] + for server_no, server_config in domain['dns_server'].items(): + if 'address' not in server_config: + invalid_servers.append(server_no) + if len(invalid_servers) > 0: + raise ConfigError(f'{domain_type} DNS servers {", ".join(invalid_servers)} in DDNS configuration need to have an IP address') + return None def verify(dhcp): # bail out early - looks like removal from running config @@ -422,6 +432,22 @@ def verify(dhcp): if not interface_exists(interface): raise ConfigError(f'listen-interface "{interface}" does not exist') + if 'dynamic_dns_update' in dhcp: + ddns = dhcp['dynamic_dns_update'] + if 'tsig_key_name' in ddns: + invalid_keys = [] + for tsig_key_name, tsig_key_config in ddns['tsig_key_name'].items(): + if not ('algorithm' in tsig_key_config and 'secret' in tsig_key_config): + invalid_keys.append(tsig_key_name) + if len(invalid_keys) > 0: + raise ConfigError(f'Both algorithm and secret need to be set for TSIG keys: {", ".join(invalid_keys)}') + + if 'forward_ddns_domain_name' in ddns: + verify_ddns_domain_servers('Forward', ddns['forward_ddns_domain_name']) + + if 'reverse_ddns_domain_name' in ddns: + verify_ddns_domain_servers('Reverse', ddns['reverse_ddns_domain_name']) + return None @@ -485,6 +511,14 @@ def generate(dhcp): user=user_group, group=user_group, ) + if 'dynamic_dns_update' in dhcp: + render( + config_file_d2, + 'dhcp-server/kea-dhcp-ddns.conf.j2', + dhcp, + user=user_group, + group=user_group + ) return None diff --git a/src/etc/systemd/system/kea-dhcp-ddns-server.service.d/override.conf b/src/etc/systemd/system/kea-dhcp-ddns-server.service.d/override.conf new file mode 100644 index 0000000000..cdfdea8ebd --- /dev/null +++ b/src/etc/systemd/system/kea-dhcp-ddns-server.service.d/override.conf @@ -0,0 +1,7 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +ExecStart= +ExecStart=/usr/sbin/kea-dhcp-ddns -c /run/kea/kea-dhcp-ddns.conf From 6f958441c0001b5f1d8f57cf55fd79da79d9fd0f Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sat, 5 Oct 2024 21:07:40 +1000 Subject: [PATCH 04/17] T6773: dhcp-server: ddns: Redo the ddns option layout to support scoped ddns --- data/templates/dhcp-server/kea-dhcp4.conf.j2 | 2 +- ...dns-server.xml.i => ddns-dns-server.xml.i} | 2 +- .../include/dhcp/ddns-settings.xml.i | 88 ++++++++++++ .../service_dhcp-server.xml.in | 129 +++--------------- python/vyos/kea.py | 25 ++++ python/vyos/template.py | 29 ++-- .../scripts/cli/test_service_dhcp-server.py | 70 +++++++--- 7 files changed, 195 insertions(+), 150 deletions(-) rename interface-definitions/include/dhcp/{ddns-server.xml.i => ddns-dns-server.xml.i} (91%) create mode 100644 interface-definitions/include/dhcp/ddns-settings.xml.i diff --git a/data/templates/dhcp-server/kea-dhcp4.conf.j2 b/data/templates/dhcp-server/kea-dhcp4.conf.j2 index 6a05dc1f5b..c10b2b3b33 100644 --- a/data/templates/dhcp-server/kea-dhcp4.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2 @@ -47,7 +47,7 @@ "ncr-protocol": "UDP", "ncr-format": "JSON" }, - {{ dynamic_dns_update | kea_dynamic_dns_update_main_json }}, + {{ dynamic_dns_update | kea_dynamic_dns_update_main_json }} {% endif %} "hooks-libraries": [ {% if high_availability is vyos_defined %} diff --git a/interface-definitions/include/dhcp/ddns-server.xml.i b/interface-definitions/include/dhcp/ddns-dns-server.xml.i similarity index 91% rename from interface-definitions/include/dhcp/ddns-server.xml.i rename to interface-definitions/include/dhcp/ddns-dns-server.xml.i index 8539f97e71..ba9f186d09 100644 --- a/interface-definitions/include/dhcp/ddns-server.xml.i +++ b/interface-definitions/include/dhcp/ddns-dns-server.xml.i @@ -1,4 +1,4 @@ - + DNS server specification diff --git a/interface-definitions/include/dhcp/ddns-settings.xml.i b/interface-definitions/include/dhcp/ddns-settings.xml.i new file mode 100644 index 0000000000..d6231c310f --- /dev/null +++ b/interface-definitions/include/dhcp/ddns-settings.xml.i @@ -0,0 +1,88 @@ + + + + Send updates for this scope + + + + + + Always update both forward and reverse DNS data, regardless of the client's request + + + + + + Perform a DDNS update, even if the client instructs the server not to + + + + + + Replace client name mode + + never always when-present when-not-present + + + never + Use the name the client sent. If the client sent no name, do not generate one. This is the default behavior + + + always + Replace the name the client sent. If the client sent no name, generate one for the client + + + when-present + Replace the name the client sent. If the client sent no name, do not generate one + + + when-not-present + Use the name the client sent. If the client sent no name, generate one for the client + + + (never|always|when-present|when-not-present) + + Invalid replace client name mode + + + + + The prefix used in the generation of an FQDN + + + + Invalid generated prefix + + + + + The suffix used when generating an FQDN, or when qualifying a partial name + + + + Invalid qualifying suffix + + + + + Update DNS record on lease renew + + + + + + Defines DNS conflict resolution behavior + + + + + + A regular expression describing the invalid character set in the host name + + + + + A string of zero or more characters with which to replace each invalid character in the host name + + + diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in index 4ee604c556..d8d397b56a 100644 --- a/interface-definitions/service_dhcp-server.xml.in +++ b/interface-definitions/service_dhcp-server.xml.in @@ -15,114 +15,7 @@ Dynamically update Domain Name System (RFC4702) - - - Enable DDNS updates - - - - - - Override client delegation - - - - - - Override client delegation - - - - - - Replace client name mode - - never always when-present when-not-present - - - never - Use the name the client sent. If the client sent no name, do not generate one. This is the default behavior - - - always - Replace the name the client sent. If the client sent no name, generate one for the client - - - when-present - Replace the name the client sent. If the client sent no name, do not generate one - - - when-not-present - Use the name the client sent. If the client sent no name, generate one for the client - - - (never|always|when-present|when-not-present) - - Invalid replace client name mode - - - - - The prefix used in the generation of an FQDN - - - - Invalid generated prefix - - - - - The suffix used when generating an FQDN, or when qualifying a partial name - - - - Invalid qualifying suffix - - - - - Update DNS record on lease renew - - - - - - Defines DNS conflict resolution behavior - - check-with-dhcid no-check-with-dhcid check-exists-with-dhcid no-check-without-dhcid - - - check-with-dhcid - Carry out RFC 4703-compliant conflict resolution. Existing DNS entries may only be overwritten if they have a DHCID record and it matches the client's DHCID. This is the default behavior - - - no-check-with-dhcid - Existing DNS entries may be overwritten by any client, whether those entries include a DHCID record or not. The new entries will include a DHCID record for the client to whom they belong - - - check-exists-with-dhcid - Existing DNS entries may only be overwritten if they have a DHCID record. The DHCID record need not match the client's DHCID - - - no-check-without-dhcid - Existing DNS entries may be overwritten by any client; new entries will not include DHCID records - - - (check-with-dhcid|no-check-with-dhcid|check-exists-with-dhcid|no-check-without-dhcid) - - Invalid DNS conflict resolution mode - - - - - A regular expression describing the invalid character set in the host name - - - - - A string of zero or more characters with which to replace each invalid character in the host name - - + #include Name of the TSIG key for DNS updates @@ -196,7 +89,7 @@ Invalid TSIG key name. May only contain letters, numbers and -_ - #include + #include @@ -217,7 +110,7 @@ Invalid TSIG key name. May only contain letters, numbers and -_ - #include + #include @@ -311,6 +204,14 @@ Invalid shared network name. May only contain letters, numbers and .-_ + + + Dynamically update Domain Name System (RFC4702) + + + #include + + Option to make DHCP server authoritative for this physical network @@ -338,6 +239,14 @@ #include #include #include + + + Dynamically update Domain Name System (RFC4702) + + + #include + + IP address to exclude from DHCP lease range diff --git a/python/vyos/kea.py b/python/vyos/kea.py index a72654e66d..f2ea39ab2d 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -221,6 +221,9 @@ def kea_parse_subnet(subnet, config): reservations.append(reservation) out['reservations'] = reservations + if 'dynamic_dns_update' in config: + out.update(kea_parse_ddns_settings(config['dynamic_dns_update'])) + return out @@ -361,6 +364,28 @@ def kea_parse_tsig_algo(algo_spec): } return translate[algo_spec] +def kea_parse_ddns_settings(config): + data = { + "ddns-send-updates": 'send_updates' in config, + "ddns-override-no-update": 'override_no_update' in config, + "ddns-override-client-update": 'override_client_update' in config, + "ddns-update-on-renew": 'update_on_renew' in config, + "ddns-use-conflict-resolution": 'use_conflict_resolution' in config, + } + + if 'replace_client_name' in config: + data['ddns-replace-client-name'] = config['replace_client_name'] + if 'generated_prefix' in config: + data['ddns-generated-prefix'] = config['generated_prefix'] + if 'qualifying_suffix' in config: + data['ddns-qualifying-suffix'] = config['qualifying_suffix'] + if 'hostname_char_set' in config: + data['hostname-char-set'] = config['hostname_char_set'] + if 'hostname_char_replacement' in config: + data['hostname-char-replacement'] = config['hostname_char_replacement'] + + return data + def _ctrl_socket_command(inet, command, args=None): path = kea_ctrl_socket.format(inet=inet) diff --git a/python/vyos/template.py b/python/vyos/template.py index 009e792ca4..9f6e89e7ed 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -861,29 +861,12 @@ def kea_high_availability_json(config): @register_filter('kea_dynamic_dns_update_main_json') def kea_dynamic_dns_update_main_json(config): + from vyos.kea import kea_parse_ddns_settings from json import dumps - data = { - "ddns-send-updates": True, - "ddns-override-no-update": 'override_no_update' in config, - "ddns-override-client-update": 'override_client_update' in config, - "ddns-update-on-renew": 'update_on_renew' in config, - } + data = kea_parse_ddns_settings(config) - if 'replace_client_name' in config: - data['ddns-replace-client-name'] = config['replace_client_name'] - if 'conflict_resolution_mode' in config: - data['ddns-conflict-resolution-mode'] = config['conflict_resolution_mode'] - if 'generated_prefix' in config: - data['ddns-generated-prefix'] = config['generated_prefix'] - if 'qualifying_suffix' in config: - data['ddns-qualifying-suffix'] = config['qualifying_suffix'] - if 'hostname_char_set' in config: - data['hostname-char-set'] = config['hostname_char_set'] - if 'hostname_char_replacement' in config: - data['hostname-char-replacement'] = config['hostname_char_replacement'] - - return dumps(data, indent=4)[1:-1] + return dumps(data, indent=8)[1:-1] + ',' @register_filter('kea_dynamic_dns_update_tsig_key_json') def kea_dynamic_dns_update_tsig_key_json(config): @@ -931,7 +914,7 @@ def kea_dynamic_dns_update_domains(config, type_key): 'ip-address': dns_server_config['address'] } if 'port' in dns_server_config: - dns_server['port'] = dns_server_config['port'] + dns_server['port'] = int(dns_server_config['port']) dns_servers.append(dns_server) domain['dns-servers'] = dns_servers @@ -943,6 +926,7 @@ def kea_dynamic_dns_update_domains(config, type_key): def kea_shared_network_json(shared_networks): from vyos.kea import kea_parse_options from vyos.kea import kea_parse_subnet + from vyos.kea import kea_parse_ddns_settings from json import dumps out = [] @@ -957,6 +941,9 @@ def kea_shared_network_json(shared_networks): 'user-context': {} } + if 'dynamic_dns_update' in config: + network.update(kea_parse_ddns_settings(config['dynamic_dns_update'])) + if 'option' in config: network['option-data'] = kea_parse_options(config['option']) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 27917bf0f7..f729aaa556 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -1120,6 +1120,29 @@ def test_dhcp_high_availability_standby(self): self.verify_service_running() def test_dhcp_dynamic_dns_update(self): + shared_net_name = 'SMOKE-1' + + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + range_1_start = inc_ip(subnet, 40) + range_1_stop = inc_ip(subnet, 50) + + self.cli_set(base_path + ['listen-interface', interface]) + + pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] + self.cli_set(pool + ['subnet-id', '1']) + self.cli_set(pool + ['ignore-client-id']) + # we use the first subnet IP address as default gateway + self.cli_set(pool + ['option', 'default-router', router]) + self.cli_set(pool + ['option', 'name-server', dns_1]) + self.cli_set(pool + ['option', 'name-server', dns_2]) + self.cli_set(pool + ['option', 'domain-name', domain_name]) + + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + self.cli_set(pool + ['range', '1', 'start', range_1_start]) + self.cli_set(pool + ['range', '1', 'stop', range_1_stop]) + ddns = base_path + ['dynamic-dns-update'] self.cli_set(ddns + ['enable-updates']) @@ -1155,47 +1178,60 @@ def test_dhcp_dynamic_dns_update(self): d2_obj = loads(d2_config) # Verify DDNS parameters in the main config file - self.verify_config_object( + self.verify_config_value( obj, - ['Dhcp4', 'dhcp-ddns'], + ['Dhcp4'], 'dhcp-ddns', {'enable-updates': True, 'server-ip': '127.0.0.1', 'server-port': 53001, 'sender-ip': '', 'sender-port': 0, 'max-queue-size': 1024, 'ncr-protocol': 'UDP', 'ncr-format': 'JSON'}) self.verify_config_value(obj, ['Dhcp4'], 'ddns-send-updates', True) self.verify_config_value(obj, ['Dhcp4'], 'ddns-conflict-resolution-mode', 'check-exists-with-dhcid') - self.verify_config_value(obj, ['Dhcp4'], 'ddns-generated-prefix', 'myfunnyprefix', True) - self.verify_config_value(obj, ['Dhcp4'], 'ddns-qualifying-suffix', 'suffix.lan', True) - self.verify_config_value(obj, ['Dhcp4'], 'ddns-hostname-char-set', 'xXyYzZ', True) - self.verify_config_value(obj, ['Dhcp4'], 'ddns-hostname-char-replacement', '_xXx_', True) + self.verify_config_value(obj, ['Dhcp4'], 'ddns-generated-prefix', 'myfunnyprefix') + self.verify_config_value(obj, ['Dhcp4'], 'ddns-qualifying-suffix', 'suffix.lan') + self.verify_config_value(obj, ['Dhcp4'], 'hostname-char-set', 'xXyYzZ') + self.verify_config_value(obj, ['Dhcp4'], 'hostname-char-replacement', '_xXx_') self.verify_config_value(obj, ['Dhcp4'], 'ddns-override-no-update', True) self.verify_config_value(obj, ['Dhcp4'], 'ddns-override-client-update', True) - self.verify_config_value(obj, ['Dhcp4'], 'ddns-replace-client-name', 'always', True) + self.verify_config_value(obj, ['Dhcp4'], 'ddns-replace-client-name', 'always') self.verify_config_value(obj, ['Dhcp4'], 'ddns-update-on-renew', True) # Verify keys and domains configuration in the D2 config self.verify_config_object( d2_obj, - ['DhcpDdns', 'tsig-keys', 0], + ['DhcpDdns', 'tsig-keys'], {'name': 'domain-lan-updates', 'algorithm': 'HMAC-SHA256', 'secret': 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ=='} ) self.verify_config_object( d2_obj, - ['DhcpDdns', 'tsig-keys', 1], + ['DhcpDdns', 'tsig-keys'], {'name': 'reverse-0-168-192', 'algorithm': 'HMAC-SHA256', 'secret': 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ=='} ) + self.verify_config_value(d2_obj, ['DhcpDdns', 'forward-ddns', 'ddns-domains', 0], 'name', 'domain.lan') + self.verify_config_value(d2_obj, ['DhcpDdns', 'forward-ddns', 'ddns-domains', 0], 'key-name', 'domain-lan-updates') self.verify_config_object( d2_obj, - ['DhcpDdns', 'forward-ddns', 'ddns-domains', 0], - {'name': 'domain.lan', 'key-name': 'domain-lan-updates', - 'dns-servers': [{'ip-address': '192.168.0.1'}, {'ip-address': '100.100.0.1'}]} - ) + ['DhcpDdns', 'forward-ddns', 'ddns-domains', 0, 'dns-servers'], + {'ip-address': '192.168.0.1'} + ) self.verify_config_object( d2_obj, - ['DhcpDdns', 'reverse-ddns', 'ddns-domains', 0], - {'name': '0.168.192.in-addr.arpa', 'key-name': 'reverse-0-168-192', - 'dns-servers': [{'ip-address': '192.168.0.1', 'port': 1053}, {'ip-address': '100.100.0.1', 'port': 1153}]} - ) + ['DhcpDdns', 'forward-ddns', 'ddns-domains', 0, 'dns-servers'], + {'ip-address': '100.100.0.1'} + ) + + self.verify_config_value(d2_obj, ['DhcpDdns', 'reverse-ddns', 'ddns-domains', 0], 'name', '0.168.192.in-addr.arpa') + self.verify_config_value(d2_obj, ['DhcpDdns', 'reverse-ddns', 'ddns-domains', 0], 'key-name', 'reverse-0-168-192') + self.verify_config_object( + d2_obj, + ['DhcpDdns', 'reverse-ddns', 'ddns-domains', 0, 'dns-servers'], + {'ip-address': '192.168.0.1', 'port': 1053} + ) + self.verify_config_object( + d2_obj, + ['DhcpDdns', 'reverse-ddns', 'ddns-domains', 0, 'dns-servers'], + {'ip-address': '100.100.0.1', 'port': 1153} + ) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) From 318c2a890878a60bbaa748e6864f4d31e22296a2 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sun, 6 Oct 2024 14:22:18 +1100 Subject: [PATCH 05/17] T6773: dhcp-server: ddns: Add ddns-ttl-percent option --- .../include/dhcp/ddns-settings.xml.i | 9 +++++++++ python/vyos/kea.py | 2 ++ smoketest/scripts/cli/test_service_dhcp-server.py | 12 +----------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/interface-definitions/include/dhcp/ddns-settings.xml.i b/interface-definitions/include/dhcp/ddns-settings.xml.i index d6231c310f..880c36583a 100644 --- a/interface-definitions/include/dhcp/ddns-settings.xml.i +++ b/interface-definitions/include/dhcp/ddns-settings.xml.i @@ -75,6 +75,15 @@ + + + Calculate TTL of the DNS record as a percentage of the lease lifetime + + + + Invalid qualifying suffix + + A regular expression describing the invalid character set in the host name diff --git a/python/vyos/kea.py b/python/vyos/kea.py index f2ea39ab2d..66d82d5777 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -379,6 +379,8 @@ def kea_parse_ddns_settings(config): data['ddns-generated-prefix'] = config['generated_prefix'] if 'qualifying_suffix' in config: data['ddns-qualifying-suffix'] = config['qualifying_suffix'] + if 'ttl_percent' in config: + data['ddns-ttl-percent'] = int(config['ttl_percent']) / 100 if 'hostname_char_set' in config: data['hostname-char-set'] = config['hostname_char_set'] if 'hostname_char_replacement' in config: diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index f729aaa556..1cab246750 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -1120,28 +1120,18 @@ def test_dhcp_high_availability_standby(self): self.verify_service_running() def test_dhcp_dynamic_dns_update(self): - shared_net_name = 'SMOKE-1' + shared_net_name = 'SMOKE-1DDNS' range_0_start = inc_ip(subnet, 10) range_0_stop = inc_ip(subnet, 20) - range_1_start = inc_ip(subnet, 40) - range_1_stop = inc_ip(subnet, 50) self.cli_set(base_path + ['listen-interface', interface]) pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] self.cli_set(pool + ['subnet-id', '1']) - self.cli_set(pool + ['ignore-client-id']) - # we use the first subnet IP address as default gateway - self.cli_set(pool + ['option', 'default-router', router]) - self.cli_set(pool + ['option', 'name-server', dns_1]) - self.cli_set(pool + ['option', 'name-server', dns_2]) - self.cli_set(pool + ['option', 'domain-name', domain_name]) self.cli_set(pool + ['range', '0', 'start', range_0_start]) self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) - self.cli_set(pool + ['range', '1', 'start', range_1_start]) - self.cli_set(pool + ['range', '1', 'stop', range_1_stop]) ddns = base_path + ['dynamic-dns-update'] From 87caaa1a9f2f5486a735e95d859b1e2ebec5c154 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sun, 6 Oct 2024 14:47:30 +1100 Subject: [PATCH 06/17] T6773: dhcp-server: ddns: Fix the smoketests for the new parameters --- .../scripts/cli/test_service_dhcp-server.py | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 1cab246750..424d8c97a3 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -1127,20 +1127,10 @@ def test_dhcp_dynamic_dns_update(self): self.cli_set(base_path + ['listen-interface', interface]) - pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] - self.cli_set(pool + ['subnet-id', '1']) - - self.cli_set(pool + ['range', '0', 'start', range_0_start]) - self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) - ddns = base_path + ['dynamic-dns-update'] - self.cli_set(ddns + ['enable-updates']) - self.cli_set(ddns + ['conflict-resolution-mode', 'check-exists-with-dhcid']) - self.cli_set(ddns + ['generated-prefix', 'myfunnyprefix']) - self.cli_set(ddns + ['qualifying-suffix', 'suffix.lan']) - self.cli_set(ddns + ['hostname-char-set', 'xXyYzZ']) - self.cli_set(ddns + ['hostname-char-replacement', '_xXx_']) + self.cli_set(ddns + ['send-updates']) + self.cli_set(ddns + ['use-conflict-resolution']) self.cli_set(ddns + ['override-no-update']) self.cli_set(ddns + ['override-client-update']) self.cli_set(ddns + ['replace-client-name', 'always']) @@ -1159,6 +1149,25 @@ def test_dhcp_dynamic_dns_update(self): self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'dns-server', '2', 'port', '1153']) self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'key-name', 'reverse-0-168-192']) + shared = base_path + ['shared-network-name', shared_net_name] + + self.cli_set(shared + ['dynamic-dns-update', 'send-updates']) + self.cli_set(shared + ['dynamic-dns-update', 'use-conflict-resolution']) + self.cli_set(shared + ['dynamic-dns-update', 'ttl-percent', '75']) + + pool = shared + [ 'subnet', subnet] + + self.cli_set(pool + ['subnet-id', '1']) + + self.cli_set(pool + ['range', '0', 'start', range_0_start]) + self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) + + self.cli_set(pool + ['dynamic-dns-update', 'send-updates']) + self.cli_set(pool + ['dynamic-dns-update', 'generated-prefix', 'myfunnyprefix']) + self.cli_set(pool + ['dynamic-dns-update', 'qualifying-suffix', 'suffix.lan']) + self.cli_set(pool + ['dynamic-dns-update', 'hostname-char-set', 'xXyYzZ']) + self.cli_set(pool + ['dynamic-dns-update', 'hostname-char-replacement', '_xXx_']) + self.cli_commit() config = read_file(KEA4_CONF) @@ -1167,7 +1176,7 @@ def test_dhcp_dynamic_dns_update(self): obj = loads(config) d2_obj = loads(d2_config) - # Verify DDNS parameters in the main config file + # Verify global DDNS parameters in the main config file self.verify_config_value( obj, ['Dhcp4'], 'dhcp-ddns', @@ -1175,16 +1184,26 @@ def test_dhcp_dynamic_dns_update(self): 'max-queue-size': 1024, 'ncr-protocol': 'UDP', 'ncr-format': 'JSON'}) self.verify_config_value(obj, ['Dhcp4'], 'ddns-send-updates', True) - self.verify_config_value(obj, ['Dhcp4'], 'ddns-conflict-resolution-mode', 'check-exists-with-dhcid') - self.verify_config_value(obj, ['Dhcp4'], 'ddns-generated-prefix', 'myfunnyprefix') - self.verify_config_value(obj, ['Dhcp4'], 'ddns-qualifying-suffix', 'suffix.lan') - self.verify_config_value(obj, ['Dhcp4'], 'hostname-char-set', 'xXyYzZ') - self.verify_config_value(obj, ['Dhcp4'], 'hostname-char-replacement', '_xXx_') + self.verify_config_value(obj, ['Dhcp4'], 'ddns-use-conflict-resolution', True) self.verify_config_value(obj, ['Dhcp4'], 'ddns-override-no-update', True) self.verify_config_value(obj, ['Dhcp4'], 'ddns-override-client-update', True) self.verify_config_value(obj, ['Dhcp4'], 'ddns-replace-client-name', 'always') self.verify_config_value(obj, ['Dhcp4'], 'ddns-update-on-renew', True) + # Verify scoped DDNS parameters in the main config file + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'ddns-send-updates', True) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'ddns-use-conflict-resolution', True) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'ddns-ttl-percent', 75) + + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-send-updates', True) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-generated-prefix', 'myfunnyprefix') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-qualifying-suffix', 'suffix.lan') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-hostname-char-set', 'xXyYzZ') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-hostname-char-replacement', '_xXx_') + # Verify keys and domains configuration in the D2 config self.verify_config_object( d2_obj, From 18f661aa266347165716fb22a5ec4f504bb6397a Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sat, 12 Oct 2024 11:33:34 +1100 Subject: [PATCH 07/17] T6773: dhcp-server: ddns: Fixes to DDNS config templates and smoketests --- data/templates/dhcp-server/kea-dhcp4.conf.j2 | 2 +- smoketest/scripts/cli/test_service_dhcp-server.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/data/templates/dhcp-server/kea-dhcp4.conf.j2 b/data/templates/dhcp-server/kea-dhcp4.conf.j2 index c10b2b3b33..d08ca0eaa8 100644 --- a/data/templates/dhcp-server/kea-dhcp4.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2 @@ -36,7 +36,7 @@ "space": "ubnt" } ], -{% if dynamic_dns_update.enable_updates is vyos_defined %} +{% if dynamic_dns_update is vyos_defined %} "dhcp-ddns": { "enable-updates": true, "server-ip": "127.0.0.1", diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 424d8c97a3..d0782db1d0 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -1194,15 +1194,15 @@ def test_dhcp_dynamic_dns_update(self): self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name) self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'ddns-send-updates', True) self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'ddns-use-conflict-resolution', True) - self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'ddns-ttl-percent', 75) + self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'ddns-ttl-percent', 0.75) self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet) self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1) self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-send-updates', True) self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-generated-prefix', 'myfunnyprefix') self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-qualifying-suffix', 'suffix.lan') - self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-hostname-char-set', 'xXyYzZ') - self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'ddns-hostname-char-replacement', '_xXx_') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'hostname-char-set', 'xXyYzZ') + self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'hostname-char-replacement', '_xXx_') # Verify keys and domains configuration in the D2 config self.verify_config_object( @@ -1245,7 +1245,6 @@ def test_dhcp_dynamic_dns_update(self): # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) self.assertTrue(process_named_running(D2_PROCESS_NAME)) - self.assertTrue(process_named_running(CTRL_PROCESS_NAME)) def test_dhcp_on_interface_with_vrf(self): self.cli_set(['interfaces', 'ethernet', 'eth1', 'address', '10.1.1.1/30']) From 88003705cb3ad4d6cb1b33bd8b12c4a76f27824f Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sat, 12 Oct 2024 20:46:13 +1100 Subject: [PATCH 08/17] T6773: dhcp-server: ddns: Fix config load test for DHCP server --- smoketest/config-tests/basic-vyos | 8 +++++++- smoketest/configs/basic-vyos | 33 ++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos index 3d408ab613..0d4f5d81dc 100644 --- a/smoketest/config-tests/basic-vyos +++ b/smoketest/config-tests/basic-vyos @@ -28,7 +28,8 @@ set protocols static arp interface eth2.200.201 address 100.64.201.20 mac '00:50 set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50:00:00:00:30' set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40' set protocols static route 0.0.0.0/0 next-hop 100.64.0.1 -set service dhcp-server dynamic-dns-update enable-updates +set service dhcp-server dynamic-dns-update send-updates +set service dhcp-server dynamic-dns-update use-conflict-resolution set service dhcp-server dynamic-dns-update tsig-key-name domain-lan-updates algorithm 'hmac-sha256' set service dhcp-server dynamic-dns-update tsig-key-name domain-lan-updates secret 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==' set service dhcp-server dynamic-dns-update tsig-key-name reverse-0-168-192 algorithm 'hmac-sha256' @@ -40,6 +41,8 @@ set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in-addr.arpa dns-server 2 address '100.100.0.1' set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in-addr.arpa key-name 'reverse-0-168-192' set service dhcp-server shared-network-name LAN authoritative +set service dhcp-server shared-network-name LAN dynamic-dns-update send-updates +set service dhcp-server shared-network-name LAN dynamic-dns-update ttl-percent 75 set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-search 'vyos.net' @@ -57,6 +60,9 @@ set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-map set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 ip-address '192.168.0.21' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 mac '00:01:02:03:04:22' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update send-updates +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update generated-prefix myhost +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update qualifying-suffix lan1.domain.lan set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 interface 'eth0' set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 option domain-search 'vyos.net' set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 option name-server 'fe88::1' diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos index e102d95825..1bde7f0ad7 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -100,7 +100,6 @@ protocols { service { dhcp-server { dynamic-dns-update { - enable-updates forward-ddns-domain-name domain.lan { dns-server 1 { address 192.168.0.1 @@ -119,6 +118,7 @@ service { } key-name reverse-0-168-192 } + send-updates tsig-key-name domain-lan-updates { algorithm hmac-sha256 secret SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ== @@ -127,34 +127,49 @@ service { algorithm hmac-sha256 secret VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ== } + use-conflict-resolution } shared-network-name LAN { authoritative + dynamic-dns-update { + send-updates + ttl-percent 75 + } subnet 192.168.0.0/24 { - default-router 192.168.0.1 - dns-server 192.168.0.1 - domain-name vyos.net - domain-search vyos.net + dynamic-dns-update { + generated-prefix myhost + qualifying-suffix lan1.domain.lan + send-updates + } + option { + default-router 192.168.0.1 + domain-name vyos.net + domain-search vyos.net + name-server 192.168.0.1 + } range LANDynamic { start 192.168.0.30 stop 192.168.0.240 } static-mapping TEST1-1 { ip-address 192.168.0.11 - mac-address 00:01:02:03:04:05 + mac 00:01:02:03:04:05 } static-mapping TEST1-2 { + disable ip-address 192.168.0.12 - mac-address 00:01:02:03:04:05 + mac 00:01:02:03:04:05 } static-mapping TEST2-1 { ip-address 192.168.0.21 - mac-address 00:01:02:03:04:21 + mac 00:01:02:03:04:21 } static-mapping TEST2-2 { + disable ip-address 192.168.0.21 - mac-address 00:01:02:03:04:22 + mac 00:01:02:03:04:22 } + subnet-id 1 } } } From 0e90e828164c4e24d894b9b6d6cc593478c350cb Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sat, 12 Oct 2024 22:10:41 +1100 Subject: [PATCH 09/17] T6773: dhcp-server: ddns: Sorry, quotes are important --- smoketest/config-tests/basic-vyos | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos index 0d4f5d81dc..46bffe0d6e 100644 --- a/smoketest/config-tests/basic-vyos +++ b/smoketest/config-tests/basic-vyos @@ -42,7 +42,7 @@ set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in-addr.arpa key-name 'reverse-0-168-192' set service dhcp-server shared-network-name LAN authoritative set service dhcp-server shared-network-name LAN dynamic-dns-update send-updates -set service dhcp-server shared-network-name LAN dynamic-dns-update ttl-percent 75 +set service dhcp-server shared-network-name LAN dynamic-dns-update ttl-percent '75' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-search 'vyos.net' @@ -61,8 +61,8 @@ set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-map set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 mac '00:01:02:03:04:22' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update send-updates -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update generated-prefix myhost -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update qualifying-suffix lan1.domain.lan +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update generated-prefix 'myhost' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update qualifying-suffix 'lan1.domain.lan' set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 interface 'eth0' set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 option domain-search 'vyos.net' set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 option name-server 'fe88::1' From 7c0b58ff8dc42bcc2fed237dc1daa3a203d73f20 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Wed, 16 Oct 2024 15:54:05 +1100 Subject: [PATCH 10/17] T6773: dhcp-server: ddns: Rename a few options according to conventions --- .../dhcp-server/kea-dhcp-ddns.conf.j2 | 4 +- .../include/dhcp/ddns-settings.xml.i | 158 +++++++++--------- .../service_dhcp-server.xml.in | 14 +- python/vyos/kea.py | 6 +- python/vyos/template.py | 4 +- smoketest/config-tests/basic-vyos | 26 +-- smoketest/configs/basic-vyos | 6 +- .../scripts/cli/test_service_dhcp-server.py | 34 ++-- src/conf_mode/service_dhcp-server.py | 12 +- 9 files changed, 135 insertions(+), 129 deletions(-) diff --git a/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 b/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 index 4243c004dc..7b0394a882 100644 --- a/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 @@ -8,10 +8,10 @@ }, "tsig-keys": {{ dynamic_dns_update | kea_dynamic_dns_update_tsig_key_json }}, "forward-ddns" : { - "ddns-domains": {{ dynamic_dns_update | kea_dynamic_dns_update_domains('forward_ddns_domain_name') }} + "ddns-domains": {{ dynamic_dns_update | kea_dynamic_dns_update_domains('forward_domain') }} }, "reverse-ddns" : { - "ddns-domains": {{ dynamic_dns_update | kea_dynamic_dns_update_domains('reverse_ddns_domain_name') }} + "ddns-domains": {{ dynamic_dns_update | kea_dynamic_dns_update_domains('reverse_domain') }} }, "loggers": [ { diff --git a/interface-definitions/include/dhcp/ddns-settings.xml.i b/interface-definitions/include/dhcp/ddns-settings.xml.i index 880c36583a..263dd5c7cb 100644 --- a/interface-definitions/include/dhcp/ddns-settings.xml.i +++ b/interface-definitions/include/dhcp/ddns-settings.xml.i @@ -1,97 +1,103 @@ - - - Send updates for this scope - - + + + Send updates for this scope + + - - - Always update both forward and reverse DNS data, regardless of the client's request - - + + + Always update both forward and reverse DNS data, regardless of the client's request + + - - - Perform a DDNS update, even if the client instructs the server not to - - + + + Perform a DDNS update, even if the client instructs the server not to + + - - Replace client name mode - - never always when-present when-not-present - - - never - Use the name the client sent. If the client sent no name, do not generate one. This is the default behavior - - - always - Replace the name the client sent. If the client sent no name, generate one for the client - - - when-present - Replace the name the client sent. If the client sent no name, do not generate one - - - when-not-present - Use the name the client sent. If the client sent no name, generate one for the client - - - (never|always|when-present|when-not-present) - - Invalid replace client name mode - + + Replace client name mode + + never always when-present when-not-present + + + never + Use the name the client sent. If the client sent no name, do not generate + one + + + always + Replace the name the client sent. If the client sent no name, generate one + for the client + + + when-present + Replace the name the client sent. If the client sent no name, do not + generate one + + + when-not-present + Use the name the client sent. If the client sent no name, generate one for + the client + + never + + (never|always|when-present|when-not-present) + + Invalid replace client name mode + - - The prefix used in the generation of an FQDN - - - - Invalid generated prefix - + + The prefix used in the generation of an FQDN + + + + Invalid generated prefix + - - The suffix used when generating an FQDN, or when qualifying a partial name - - - - Invalid qualifying suffix - + + The suffix used when generating an FQDN, or when qualifying a partial name + + + + Invalid qualifying suffix + - - Update DNS record on lease renew - - + + Update DNS record on lease renew + + - - Defines DNS conflict resolution behavior - - + + Defines DNS conflict resolution behavior + + - - Calculate TTL of the DNS record as a percentage of the lease lifetime - - - - Invalid qualifying suffix - + + Calculate TTL of the DNS record as a percentage of the lease lifetime + + + + Invalid qualifying suffix + - - A regular expression describing the invalid character set in the host name - + + A regular expression describing the invalid character set in the host name + - - A string of zero or more characters with which to replace each invalid character in the host name - + + A string of zero or more characters with which to replace each invalid character in + the host name + diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in index d8d397b56a..ef826160ce 100644 --- a/interface-definitions/service_dhcp-server.xml.in +++ b/interface-definitions/service_dhcp-server.xml.in @@ -16,13 +16,13 @@ #include - + - Name of the TSIG key for DNS updates + TSIG key definition for DNS updates #include - Invalid TSIG key name. May only contain letters, numbers and -_ + Invalid TSIG key name. May only contain letters, numbers, hyphen and underscore @@ -71,7 +71,7 @@ - + Forward DNS domain name @@ -86,13 +86,13 @@ #include - Invalid TSIG key name. May only contain letters, numbers and -_ + Invalid TSIG key name. May only contain letters, numbers, numbers, hyphen and underscore #include - + Reverse DNS domain name @@ -107,7 +107,7 @@ #include - Invalid TSIG key name. May only contain letters, numbers and -_ + Invalid TSIG key name. May only contain letters, numbers, numbers, hyphen and underscore #include diff --git a/python/vyos/kea.py b/python/vyos/kea.py index 66d82d5777..ca62ad231c 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -366,9 +366,9 @@ def kea_parse_tsig_algo(algo_spec): def kea_parse_ddns_settings(config): data = { - "ddns-send-updates": 'send_updates' in config, - "ddns-override-no-update": 'override_no_update' in config, - "ddns-override-client-update": 'override_client_update' in config, + "ddns-send-updates": 'force_updates' in config, + "ddns-override-no-update": 'force_no_update' in config, + "ddns-override-client-update": 'force_client_update' in config, "ddns-update-on-renew": 'update_on_renew' in config, "ddns-use-conflict-resolution": 'use_conflict_resolution' in config, } diff --git a/python/vyos/template.py b/python/vyos/template.py index 9f6e89e7ed..1b17ebb92b 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -874,10 +874,10 @@ def kea_dynamic_dns_update_tsig_key_json(config): from json import dumps out = [] - if 'tsig_key_name' not in config: + if 'tsig_key' not in config: return dumps(out) - tsig_keys = config['tsig_key_name'] + tsig_keys = config['tsig_key'] for tsig_key_name, tsig_key_config in tsig_keys.items(): tsig_key = { diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos index 46bffe0d6e..2f222c3cdd 100644 --- a/smoketest/config-tests/basic-vyos +++ b/smoketest/config-tests/basic-vyos @@ -28,20 +28,20 @@ set protocols static arp interface eth2.200.201 address 100.64.201.20 mac '00:50 set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50:00:00:00:30' set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40' set protocols static route 0.0.0.0/0 next-hop 100.64.0.1 -set service dhcp-server dynamic-dns-update send-updates +set service dhcp-server dynamic-dns-update force-updates set service dhcp-server dynamic-dns-update use-conflict-resolution -set service dhcp-server dynamic-dns-update tsig-key-name domain-lan-updates algorithm 'hmac-sha256' -set service dhcp-server dynamic-dns-update tsig-key-name domain-lan-updates secret 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==' -set service dhcp-server dynamic-dns-update tsig-key-name reverse-0-168-192 algorithm 'hmac-sha256' -set service dhcp-server dynamic-dns-update tsig-key-name reverse-0-168-192 secret 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ==' -set service dhcp-server dynamic-dns-update forward-ddns-domain-name domain.lan dns-server 1 address '192.168.0.1' -set service dhcp-server dynamic-dns-update forward-ddns-domain-name domain.lan dns-server 2 address '100.100.0.1' -set service dhcp-server dynamic-dns-update forward-ddns-domain-name domain.lan key-name 'domain-lan-updates' -set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in-addr.arpa dns-server 1 address '192.168.0.1' -set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in-addr.arpa dns-server 2 address '100.100.0.1' -set service dhcp-server dynamic-dns-update reverse-ddns-domain-name 0.168.192.in-addr.arpa key-name 'reverse-0-168-192' +set service dhcp-server dynamic-dns-update tsig-key domain-lan-updates algorithm 'hmac-sha256' +set service dhcp-server dynamic-dns-update tsig-key domain-lan-updates secret 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==' +set service dhcp-server dynamic-dns-update tsig-key reverse-0-168-192 algorithm 'hmac-sha256' +set service dhcp-server dynamic-dns-update tsig-key reverse-0-168-192 secret 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ==' +set service dhcp-server dynamic-dns-update forward-domain domain.lan dns-server 1 address '192.168.0.1' +set service dhcp-server dynamic-dns-update forward-domain domain.lan dns-server 2 address '100.100.0.1' +set service dhcp-server dynamic-dns-update forward-domain domain.lan key-name 'domain-lan-updates' +set service dhcp-server dynamic-dns-update reverse-domain 0.168.192.in-addr.arpa dns-server 1 address '192.168.0.1' +set service dhcp-server dynamic-dns-update reverse-domain 0.168.192.in-addr.arpa dns-server 2 address '100.100.0.1' +set service dhcp-server dynamic-dns-update reverse-domain 0.168.192.in-addr.arpa key-name 'reverse-0-168-192' set service dhcp-server shared-network-name LAN authoritative -set service dhcp-server shared-network-name LAN dynamic-dns-update send-updates +set service dhcp-server shared-network-name LAN dynamic-dns-update force-updates set service dhcp-server shared-network-name LAN dynamic-dns-update ttl-percent '75' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net' @@ -60,7 +60,7 @@ set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-map set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 ip-address '192.168.0.21' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 mac '00:01:02:03:04:22' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1' -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update send-updates +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update force-updates set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update generated-prefix 'myhost' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update qualifying-suffix 'lan1.domain.lan' set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 interface 'eth0' diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos index 1bde7f0ad7..7dc5a16298 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -118,7 +118,7 @@ service { } key-name reverse-0-168-192 } - send-updates + force-updates tsig-key-name domain-lan-updates { algorithm hmac-sha256 secret SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ== @@ -132,14 +132,14 @@ service { shared-network-name LAN { authoritative dynamic-dns-update { - send-updates + force-updates ttl-percent 75 } subnet 192.168.0.0/24 { dynamic-dns-update { generated-prefix myhost qualifying-suffix lan1.domain.lan - send-updates + force-updates } option { default-router 192.168.0.1 diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index d0782db1d0..b18ee24efd 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -1129,29 +1129,29 @@ def test_dhcp_dynamic_dns_update(self): ddns = base_path + ['dynamic-dns-update'] - self.cli_set(ddns + ['send-updates']) + self.cli_set(ddns + ['force-updates']) self.cli_set(ddns + ['use-conflict-resolution']) - self.cli_set(ddns + ['override-no-update']) - self.cli_set(ddns + ['override-client-update']) + self.cli_set(ddns + ['force-no-update']) + self.cli_set(ddns + ['force-client-update']) self.cli_set(ddns + ['replace-client-name', 'always']) self.cli_set(ddns + ['update-on-renew']) - self.cli_set(ddns + ['tsig-key-name', 'domain-lan-updates', 'algorithm', 'hmac-sha256']) - self.cli_set(ddns + ['tsig-key-name', 'domain-lan-updates', 'secret', 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==']) - self.cli_set(ddns + ['tsig-key-name', 'reverse-0-168-192', 'algorithm', 'hmac-sha256']) - self.cli_set(ddns + ['tsig-key-name', 'reverse-0-168-192', 'secret', 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ==']) - self.cli_set(ddns + ['forward-ddns-domain-name', 'domain.lan', 'dns-server', '1', 'address', '192.168.0.1']) - self.cli_set(ddns + ['forward-ddns-domain-name', 'domain.lan', 'dns-server', '2', 'address', '100.100.0.1']) - self.cli_set(ddns + ['forward-ddns-domain-name', 'domain.lan', 'key-name', 'domain-lan-updates']) - self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'dns-server', '1', 'address', '192.168.0.1']) - self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'dns-server', '1', 'port', '1053']) - self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'dns-server', '2', 'address', '100.100.0.1']) - self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'dns-server', '2', 'port', '1153']) - self.cli_set(ddns + ['reverse-ddns-domain-name', '0.168.192.in-addr.arpa', 'key-name', 'reverse-0-168-192']) + self.cli_set(ddns + ['tsig-key', 'domain-lan-updates', 'algorithm', 'hmac-sha256']) + self.cli_set(ddns + ['tsig-key', 'domain-lan-updates', 'secret', 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==']) + self.cli_set(ddns + ['tsig-key', 'reverse-0-168-192', 'algorithm', 'hmac-sha256']) + self.cli_set(ddns + ['tsig-key', 'reverse-0-168-192', 'secret', 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ==']) + self.cli_set(ddns + ['forward-domain', 'domain.lan', 'dns-server', '1', 'address', '192.168.0.1']) + self.cli_set(ddns + ['forward-domain', 'domain.lan', 'dns-server', '2', 'address', '100.100.0.1']) + self.cli_set(ddns + ['forward-domain', 'domain.lan', 'key-name', 'domain-lan-updates']) + self.cli_set(ddns + ['reverse-domain', '0.168.192.in-addr.arpa', 'dns-server', '1', 'address', '192.168.0.1']) + self.cli_set(ddns + ['reverse-domain', '0.168.192.in-addr.arpa', 'dns-server', '1', 'port', '1053']) + self.cli_set(ddns + ['reverse-domain', '0.168.192.in-addr.arpa', 'dns-server', '2', 'address', '100.100.0.1']) + self.cli_set(ddns + ['reverse-domain', '0.168.192.in-addr.arpa', 'dns-server', '2', 'port', '1153']) + self.cli_set(ddns + ['reverse-domain', '0.168.192.in-addr.arpa', 'key-name', 'reverse-0-168-192']) shared = base_path + ['shared-network-name', shared_net_name] - self.cli_set(shared + ['dynamic-dns-update', 'send-updates']) + self.cli_set(shared + ['dynamic-dns-update', 'force-updates']) self.cli_set(shared + ['dynamic-dns-update', 'use-conflict-resolution']) self.cli_set(shared + ['dynamic-dns-update', 'ttl-percent', '75']) @@ -1162,7 +1162,7 @@ def test_dhcp_dynamic_dns_update(self): self.cli_set(pool + ['range', '0', 'start', range_0_start]) self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) - self.cli_set(pool + ['dynamic-dns-update', 'send-updates']) + self.cli_set(pool + ['dynamic-dns-update', 'force-updates']) self.cli_set(pool + ['dynamic-dns-update', 'generated-prefix', 'myfunnyprefix']) self.cli_set(pool + ['dynamic-dns-update', 'qualifying-suffix', 'suffix.lan']) self.cli_set(pool + ['dynamic-dns-update', 'hostname-char-set', 'xXyYzZ']) diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py index 9fd51db5b8..99c7e6a1fe 100755 --- a/src/conf_mode/service_dhcp-server.py +++ b/src/conf_mode/service_dhcp-server.py @@ -434,19 +434,19 @@ def verify(dhcp): if 'dynamic_dns_update' in dhcp: ddns = dhcp['dynamic_dns_update'] - if 'tsig_key_name' in ddns: + if 'tsig_key' in ddns: invalid_keys = [] - for tsig_key_name, tsig_key_config in ddns['tsig_key_name'].items(): + for tsig_key_name, tsig_key_config in ddns['tsig_key'].items(): if not ('algorithm' in tsig_key_config and 'secret' in tsig_key_config): invalid_keys.append(tsig_key_name) if len(invalid_keys) > 0: raise ConfigError(f'Both algorithm and secret need to be set for TSIG keys: {", ".join(invalid_keys)}') - if 'forward_ddns_domain_name' in ddns: - verify_ddns_domain_servers('Forward', ddns['forward_ddns_domain_name']) + if 'forward_domain' in ddns: + verify_ddns_domain_servers('Forward', ddns['forward_domain']) - if 'reverse_ddns_domain_name' in ddns: - verify_ddns_domain_servers('Reverse', ddns['reverse_ddns_domain_name']) + if 'reverse_domain' in ddns: + verify_ddns_domain_servers('Reverse', ddns['reverse_domain']) return None From cdef63106b08e8a7d47f06d618710ac22e1f7e79 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Tue, 26 Nov 2024 19:05:57 +1100 Subject: [PATCH 11/17] T6773: dhcp-server: ddns: Fix incorrect interface definition spec --- interface-definitions/include/dhcp/ddns-settings.xml.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/include/dhcp/ddns-settings.xml.i b/interface-definitions/include/dhcp/ddns-settings.xml.i index 263dd5c7cb..27f8af03ba 100644 --- a/interface-definitions/include/dhcp/ddns-settings.xml.i +++ b/interface-definitions/include/dhcp/ddns-settings.xml.i @@ -43,12 +43,12 @@ Use the name the client sent. If the client sent no name, generate one for the client - never (never|always|when-present|when-not-present) Invalid replace client name mode + never From 7da945fae693adbf47432d8c76ab71fa11af5e0e Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Tue, 26 Nov 2024 22:26:14 +1100 Subject: [PATCH 12/17] T6773: dhcp-server: ddns: Fix config load tests for the updated DDNS syntax --- smoketest/configs/basic-vyos | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos index 7dc5a16298..7ccb03d71e 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -100,7 +100,8 @@ protocols { service { dhcp-server { dynamic-dns-update { - forward-ddns-domain-name domain.lan { + force-updates + forward-domain domain.lan { dns-server 1 { address 192.168.0.1 } @@ -109,7 +110,7 @@ service { } key-name domain-lan-updates } - reverse-ddns-domain-name 0.168.192.in-addr.arpa { + reverse-domain 0.168.192.in-addr.arpa { dns-server 1 { address 192.168.0.1 } @@ -118,12 +119,11 @@ service { } key-name reverse-0-168-192 } - force-updates - tsig-key-name domain-lan-updates { + tsig-key domain-lan-updates { algorithm hmac-sha256 secret SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ== } - tsig-key-name reverse-0-168-192 { + tsig-key reverse-0-168-192 { algorithm hmac-sha256 secret VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ== } @@ -137,9 +137,9 @@ service { } subnet 192.168.0.0/24 { dynamic-dns-update { + force-updates generated-prefix myhost qualifying-suffix lan1.domain.lan - force-updates } option { default-router 192.168.0.1 From d286ec6373078aa28bcf9410f0bef4753309ccb6 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Wed, 27 Nov 2024 21:49:12 +1100 Subject: [PATCH 13/17] T6773: dhcp-server: ddns: Had to redo parts of the configuration language due to how Kea treats set/unset options in scopes --- .../include/dhcp/ddns-settings.xml.i | 93 ++++++++++++++++--- python/vyos/kea.py | 26 ++++-- python/vyos/template.py | 3 + smoketest/config-tests/basic-vyos | 8 +- smoketest/configs/basic-vyos | 8 +- .../scripts/cli/test_service_dhcp-server.py | 16 ++-- 6 files changed, 119 insertions(+), 35 deletions(-) diff --git a/interface-definitions/include/dhcp/ddns-settings.xml.i b/interface-definitions/include/dhcp/ddns-settings.xml.i index 27f8af03ba..9392c1805a 100644 --- a/interface-definitions/include/dhcp/ddns-settings.xml.i +++ b/interface-definitions/include/dhcp/ddns-settings.xml.i @@ -1,20 +1,62 @@ - + - Send updates for this scope - + Enable or disable updates for this scope + + enable disable + + + enable + Enable updates for this scope + + + disable + Disable updates for this scope + + + (enable|disable) + + Set it to either enable or disable - + Always update both forward and reverse DNS data, regardless of the client's request - + + enable disable + + + enable + Force update both forward and reverse DNS records + + + disable + Respect client request settings + + + (enable|disable) + + Set it to either enable or disable - + Perform a DDNS update, even if the client instructs the server not to - + + enable disable + + + enable + Force DDNS updates regardless of client request + + + disable + Respect client request settings + + + (enable|disable) + + Set it to either enable or disable @@ -48,7 +90,6 @@ Invalid replace client name mode - never @@ -71,13 +112,41 @@ Update DNS record on lease renew - + + enable disable + + + enable + Update DNS record on lease renew + + + disable + Do not update DNS record on lease renew + + + (enable|disable) + + Set it to either enable or disable - + - Defines DNS conflict resolution behavior - + DNS conflict resolution behavior + + enable disable + + + enable + Enable DNS conflict resolution + + + disable + Disable DNS conflict resolution + + + (enable|disable) + + Set it to either enable or disable diff --git a/python/vyos/kea.py b/python/vyos/kea.py index ca62ad231c..86d578231f 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -364,14 +364,26 @@ def kea_parse_tsig_algo(algo_spec): } return translate[algo_spec] +def kea_parse_enable_disable(value): + return True if value == 'enable' else False + def kea_parse_ddns_settings(config): - data = { - "ddns-send-updates": 'force_updates' in config, - "ddns-override-no-update": 'force_no_update' in config, - "ddns-override-client-update": 'force_client_update' in config, - "ddns-update-on-renew": 'update_on_renew' in config, - "ddns-use-conflict-resolution": 'use_conflict_resolution' in config, - } + data = {} + + if send_updates := config.get('send_updates'): + data['ddns-send-updates'] = kea_parse_enable_disable(send_updates) + + if update_both := config.get('force_update_both'): + data['ddns-override-client-update'] = kea_parse_enable_disable(force_update) + + if force_update := config.get('force_update'): + data['ddns-override-no-update'] = kea_parse_enable_disable(update_both) + + if update_on_renew := config.get('update_on_renew'): + data['ddns-update-on-renew'] = kea_parse_enable_disable(update_on_renew) + + if conflict_resolution := config.get('conflict_resolution'): + data['ddns-use-conflict-resolution'] = kea_parse_enable_disable(conflict_resolution) if 'replace_client_name' in config: data['ddns-replace-client-name'] = config['replace_client_name'] diff --git a/python/vyos/template.py b/python/vyos/template.py index 1b17ebb92b..d79e1183f9 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -866,6 +866,9 @@ def kea_dynamic_dns_update_main_json(config): data = kea_parse_ddns_settings(config) + if len(data) == 0: + return '' + return dumps(data, indent=8)[1:-1] + ',' @register_filter('kea_dynamic_dns_update_tsig_key_json') diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos index 2f222c3cdd..7bb6496ad5 100644 --- a/smoketest/config-tests/basic-vyos +++ b/smoketest/config-tests/basic-vyos @@ -28,8 +28,8 @@ set protocols static arp interface eth2.200.201 address 100.64.201.20 mac '00:50 set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50:00:00:00:30' set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40' set protocols static route 0.0.0.0/0 next-hop 100.64.0.1 -set service dhcp-server dynamic-dns-update force-updates -set service dhcp-server dynamic-dns-update use-conflict-resolution +set service dhcp-server dynamic-dns-update send-updates 'enable' +set service dhcp-server dynamic-dns-update conflict-resolution 'enable' set service dhcp-server dynamic-dns-update tsig-key domain-lan-updates algorithm 'hmac-sha256' set service dhcp-server dynamic-dns-update tsig-key domain-lan-updates secret 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==' set service dhcp-server dynamic-dns-update tsig-key reverse-0-168-192 algorithm 'hmac-sha256' @@ -41,7 +41,7 @@ set service dhcp-server dynamic-dns-update reverse-domain 0.168.192.in-addr.arpa set service dhcp-server dynamic-dns-update reverse-domain 0.168.192.in-addr.arpa dns-server 2 address '100.100.0.1' set service dhcp-server dynamic-dns-update reverse-domain 0.168.192.in-addr.arpa key-name 'reverse-0-168-192' set service dhcp-server shared-network-name LAN authoritative -set service dhcp-server shared-network-name LAN dynamic-dns-update force-updates +set service dhcp-server shared-network-name LAN dynamic-dns-update send-updates 'enable' set service dhcp-server shared-network-name LAN dynamic-dns-update ttl-percent '75' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net' @@ -60,7 +60,7 @@ set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-map set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 ip-address '192.168.0.21' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 mac '00:01:02:03:04:22' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1' -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update force-updates +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update send-updates 'enable' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update generated-prefix 'myhost' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 dynamic-dns-update qualifying-suffix 'lan1.domain.lan' set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 interface 'eth0' diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos index 7ccb03d71e..b58e1c58e8 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -100,7 +100,7 @@ protocols { service { dhcp-server { dynamic-dns-update { - force-updates + send-updates enable forward-domain domain.lan { dns-server 1 { address 192.168.0.1 @@ -127,17 +127,17 @@ service { algorithm hmac-sha256 secret VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ== } - use-conflict-resolution + conflict-resolution enable } shared-network-name LAN { authoritative dynamic-dns-update { - force-updates + send-updates enable ttl-percent 75 } subnet 192.168.0.0/24 { dynamic-dns-update { - force-updates + send-updates enable generated-prefix myhost qualifying-suffix lan1.domain.lan } diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index b18ee24efd..6da1a1efd6 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -1129,12 +1129,12 @@ def test_dhcp_dynamic_dns_update(self): ddns = base_path + ['dynamic-dns-update'] - self.cli_set(ddns + ['force-updates']) - self.cli_set(ddns + ['use-conflict-resolution']) - self.cli_set(ddns + ['force-no-update']) - self.cli_set(ddns + ['force-client-update']) + self.cli_set(ddns + ['send-updates', 'enable']) + self.cli_set(ddns + ['conflict-resolution', 'enable']) + self.cli_set(ddns + ['force-update', 'enable']) + self.cli_set(ddns + ['force-update-both', 'enable']) self.cli_set(ddns + ['replace-client-name', 'always']) - self.cli_set(ddns + ['update-on-renew']) + self.cli_set(ddns + ['update-on-renew', 'enable']) self.cli_set(ddns + ['tsig-key', 'domain-lan-updates', 'algorithm', 'hmac-sha256']) self.cli_set(ddns + ['tsig-key', 'domain-lan-updates', 'secret', 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==']) @@ -1151,8 +1151,8 @@ def test_dhcp_dynamic_dns_update(self): shared = base_path + ['shared-network-name', shared_net_name] - self.cli_set(shared + ['dynamic-dns-update', 'force-updates']) - self.cli_set(shared + ['dynamic-dns-update', 'use-conflict-resolution']) + self.cli_set(shared + ['dynamic-dns-update', 'send-updates', 'enable']) + self.cli_set(shared + ['dynamic-dns-update', 'conflict-resolution', 'enable']) self.cli_set(shared + ['dynamic-dns-update', 'ttl-percent', '75']) pool = shared + [ 'subnet', subnet] @@ -1162,7 +1162,7 @@ def test_dhcp_dynamic_dns_update(self): self.cli_set(pool + ['range', '0', 'start', range_0_start]) self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) - self.cli_set(pool + ['dynamic-dns-update', 'force-updates']) + self.cli_set(pool + ['dynamic-dns-update', 'send-updates']) self.cli_set(pool + ['dynamic-dns-update', 'generated-prefix', 'myfunnyprefix']) self.cli_set(pool + ['dynamic-dns-update', 'qualifying-suffix', 'suffix.lan']) self.cli_set(pool + ['dynamic-dns-update', 'hostname-char-set', 'xXyYzZ']) From 91ba8fad925faff233808301c63dba8a6b2eaa74 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Wed, 27 Nov 2024 23:09:28 +1100 Subject: [PATCH 14/17] T6773: dhcp-server: ddns: Fix tests --- smoketest/scripts/cli/test_service_dhcp-server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 6da1a1efd6..9699309ad6 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -1162,7 +1162,7 @@ def test_dhcp_dynamic_dns_update(self): self.cli_set(pool + ['range', '0', 'start', range_0_start]) self.cli_set(pool + ['range', '0', 'stop', range_0_stop]) - self.cli_set(pool + ['dynamic-dns-update', 'send-updates']) + self.cli_set(pool + ['dynamic-dns-update', 'send-updates', 'enable']) self.cli_set(pool + ['dynamic-dns-update', 'generated-prefix', 'myfunnyprefix']) self.cli_set(pool + ['dynamic-dns-update', 'qualifying-suffix', 'suffix.lan']) self.cli_set(pool + ['dynamic-dns-update', 'hostname-char-set', 'xXyYzZ']) From ccddf2b9a3733090d948f856400287ac3a8099d1 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Wed, 27 Nov 2024 23:55:41 +1100 Subject: [PATCH 15/17] T6773: dhcp-server: ddns: Fix a copypaste error --- python/vyos/kea.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/vyos/kea.py b/python/vyos/kea.py index 86d578231f..1be9fbab27 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -374,10 +374,10 @@ def kea_parse_ddns_settings(config): data['ddns-send-updates'] = kea_parse_enable_disable(send_updates) if update_both := config.get('force_update_both'): - data['ddns-override-client-update'] = kea_parse_enable_disable(force_update) + data['ddns-override-client-update'] = kea_parse_enable_disable(update_both) if force_update := config.get('force_update'): - data['ddns-override-no-update'] = kea_parse_enable_disable(update_both) + data['ddns-override-no-update'] = kea_parse_enable_disable(force_update) if update_on_renew := config.get('update_on_renew'): data['ddns-update-on-renew'] = kea_parse_enable_disable(update_on_renew) From ef7a3da3da92ed1aca3a6cebdc13028d2d4f8984 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Tue, 8 Apr 2025 18:57:18 +1000 Subject: [PATCH 16/17] T6773: dhcp-server: ddns: Add a safety check when selecting TSIG protocol --- python/vyos/kea.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/vyos/kea.py b/python/vyos/kea.py index 1be9fbab27..78684691bc 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -20,6 +20,7 @@ from datetime import datetime from datetime import timezone +from vyos import ConfigError from vyos.template import is_ipv6 from vyos.template import netmask_from_cidr from vyos.utils.dict import dict_search_args @@ -362,6 +363,8 @@ def kea_parse_tsig_algo(algo_spec): 'hmac-sha384': 'HMAC-SHA384', 'hmac-sha512': 'HMAC-SHA512' } + if algo_spec not in translate: + raise ConfigError(f'Unsupported TSIG algorithm: {algo_spec}') return translate[algo_spec] def kea_parse_enable_disable(value): From 6391c5ae2743aa0217812adfb281385532d77d88 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Tue, 15 Apr 2025 10:47:32 +1000 Subject: [PATCH 17/17] T6773: dhcp-server: ddns: Rename a few options in config to make it more sensible and closer to the original Kea philosophy --- .../include/dhcp/ddns-settings.xml.i | 4 ++-- .../service_dhcp-server.xml.in | 16 +++++++-------- python/vyos/kea.py | 20 +++++++++---------- smoketest/config-tests/basic-vyos | 4 ++-- smoketest/configs/basic-vyos | 4 ++-- .../scripts/cli/test_service_dhcp-server.py | 8 ++++---- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/interface-definitions/include/dhcp/ddns-settings.xml.i b/interface-definitions/include/dhcp/ddns-settings.xml.i index 9392c1805a..3e202685e5 100644 --- a/interface-definitions/include/dhcp/ddns-settings.xml.i +++ b/interface-definitions/include/dhcp/ddns-settings.xml.i @@ -19,7 +19,7 @@ Set it to either enable or disable - + Always update both forward and reverse DNS data, regardless of the client's request @@ -39,7 +39,7 @@ Set it to either enable or disable - + Perform a DDNS update, even if the client instructs the server not to diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in index ef826160ce..78f1cea4e5 100644 --- a/interface-definitions/service_dhcp-server.xml.in +++ b/interface-definitions/service_dhcp-server.xml.in @@ -29,34 +29,34 @@ TSIG key algorithm - hmac-md5 hmac-sha1 hmac-sha224 hmac-sha256 hmac-sha384 hmac-sha512 + md5 sha1 sha224 sha256 sha384 sha512 - hmac-md5 + md5 MD5 HMAC algorithm - hmac-sha1 + sha1 SHA1 HMAC algorithm - hmac-sha224 + sha224 SHA224 HMAC algorithm - hmac-sha256 + sha256 SHA256 HMAC algorithm - hmac-sha384 + sha384 SHA384 HMAC algorithm - hmac-sha512 + sha512 SHA512 HMAC algorithm - (hmac-md5|hmac-sha1|hmac-sha224|hmac-sha256|hmac-sha384|hmac-sha512) + (md5|sha1|sha224|sha256|sha384|sha512) Invalid TSIG key algorithm diff --git a/python/vyos/kea.py b/python/vyos/kea.py index 78684691bc..5eecbbaad0 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -356,12 +356,12 @@ def kea6_parse_subnet(subnet, config): def kea_parse_tsig_algo(algo_spec): translate = { - 'hmac-md5': 'HMAC-MD5', - 'hmac-sha1': 'HMAC-SHA1', - 'hmac-sha224': 'HMAC-SHA224', - 'hmac-sha256': 'HMAC-SHA256', - 'hmac-sha384': 'HMAC-SHA384', - 'hmac-sha512': 'HMAC-SHA512' + 'md5': 'HMAC-MD5', + 'sha1': 'HMAC-SHA1', + 'sha224': 'HMAC-SHA224', + 'sha256': 'HMAC-SHA256', + 'sha384': 'HMAC-SHA384', + 'sha512': 'HMAC-SHA512' } if algo_spec not in translate: raise ConfigError(f'Unsupported TSIG algorithm: {algo_spec}') @@ -376,11 +376,11 @@ def kea_parse_ddns_settings(config): if send_updates := config.get('send_updates'): data['ddns-send-updates'] = kea_parse_enable_disable(send_updates) - if update_both := config.get('force_update_both'): - data['ddns-override-client-update'] = kea_parse_enable_disable(update_both) + if override_client_update := config.get('override_client_update'): + data['ddns-override-client-update'] = kea_parse_enable_disable(override_client_update) - if force_update := config.get('force_update'): - data['ddns-override-no-update'] = kea_parse_enable_disable(force_update) + if override_no_update := config.get('override_no_update'): + data['ddns-override-no-update'] = kea_parse_enable_disable(override_no_update) if update_on_renew := config.get('update_on_renew'): data['ddns-update-on-renew'] = kea_parse_enable_disable(update_on_renew) diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos index 7bb6496ad5..aaf450e80c 100644 --- a/smoketest/config-tests/basic-vyos +++ b/smoketest/config-tests/basic-vyos @@ -30,9 +30,9 @@ set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50 set protocols static route 0.0.0.0/0 next-hop 100.64.0.1 set service dhcp-server dynamic-dns-update send-updates 'enable' set service dhcp-server dynamic-dns-update conflict-resolution 'enable' -set service dhcp-server dynamic-dns-update tsig-key domain-lan-updates algorithm 'hmac-sha256' +set service dhcp-server dynamic-dns-update tsig-key domain-lan-updates algorithm 'sha256' set service dhcp-server dynamic-dns-update tsig-key domain-lan-updates secret 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==' -set service dhcp-server dynamic-dns-update tsig-key reverse-0-168-192 algorithm 'hmac-sha256' +set service dhcp-server dynamic-dns-update tsig-key reverse-0-168-192 algorithm 'sha256' set service dhcp-server dynamic-dns-update tsig-key reverse-0-168-192 secret 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ==' set service dhcp-server dynamic-dns-update forward-domain domain.lan dns-server 1 address '192.168.0.1' set service dhcp-server dynamic-dns-update forward-domain domain.lan dns-server 2 address '100.100.0.1' diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos index b58e1c58e8..5f7a71237c 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -120,11 +120,11 @@ service { key-name reverse-0-168-192 } tsig-key domain-lan-updates { - algorithm hmac-sha256 + algorithm sha256 secret SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ== } tsig-key reverse-0-168-192 { - algorithm hmac-sha256 + algorithm sha256 secret VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ== } conflict-resolution enable diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 9699309ad6..e421f04d29 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -1131,14 +1131,14 @@ def test_dhcp_dynamic_dns_update(self): self.cli_set(ddns + ['send-updates', 'enable']) self.cli_set(ddns + ['conflict-resolution', 'enable']) - self.cli_set(ddns + ['force-update', 'enable']) - self.cli_set(ddns + ['force-update-both', 'enable']) + self.cli_set(ddns + ['override-no-update', 'enable']) + self.cli_set(ddns + ['override-client-update', 'enable']) self.cli_set(ddns + ['replace-client-name', 'always']) self.cli_set(ddns + ['update-on-renew', 'enable']) - self.cli_set(ddns + ['tsig-key', 'domain-lan-updates', 'algorithm', 'hmac-sha256']) + self.cli_set(ddns + ['tsig-key', 'domain-lan-updates', 'algorithm', 'sha256']) self.cli_set(ddns + ['tsig-key', 'domain-lan-updates', 'secret', 'SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ==']) - self.cli_set(ddns + ['tsig-key', 'reverse-0-168-192', 'algorithm', 'hmac-sha256']) + self.cli_set(ddns + ['tsig-key', 'reverse-0-168-192', 'algorithm', 'sha256']) self.cli_set(ddns + ['tsig-key', 'reverse-0-168-192', 'secret', 'VGhhbmsgR29kIGl0J3MgRnJpZGF5IQ==']) self.cli_set(ddns + ['forward-domain', 'domain.lan', 'dns-server', '1', 'address', '192.168.0.1']) self.cli_set(ddns + ['forward-domain', 'domain.lan', 'dns-server', '2', 'address', '100.100.0.1'])