From 929a23868aee7bf5be28e2643fd08b2f1b599f76 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Wed, 2 Oct 2024 16:51:20 +1000 Subject: [PATCH 01/10] 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 cb5f9a8043..02138e5988 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 14c54ee2b2ebeb1014b089c69fc317df4648aade Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Fri, 4 Oct 2024 13:12:19 +1000 Subject: [PATCH 02/10] T6773: dhcp-server: ddns: Dynamic DNS support for KEA WIP --- .../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 | 11 ++ python/vyos/template.py | 70 ++++++++ 6 files changed, 280 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 bf37b94f66..5cf4097110 100644 --- a/data/templates/dhcp-server/kea-dhcp4.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2 @@ -50,6 +50,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 02138e5988..42f84c0970 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 addfdba496..c0391e930d 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -295,6 +295,17 @@ 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 be9f781a61..a21760569f 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -871,6 +871,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 3f50c2d9c8e6da92453d7c2e86f8f3b1f07a3a5d Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Fri, 4 Oct 2024 20:16:22 +1000 Subject: [PATCH 03/10] T6773: dhcp-server: ddns: Configure Kea D2 and make some 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 | 85 +++++++++++++++++++ src/conf_mode/service_dhcp-server.py | 29 +++++++ .../override.conf | 7 ++ 9 files changed, 187 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 5cf4097110..9a3f4acf6f 100644 --- a/data/templates/dhcp-server/kea-dhcp4.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2 @@ -61,7 +61,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 a21760569f..59a8f6508f 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -898,11 +898,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, @@ -911,13 +916,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, @@ -928,9 +938,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'] @@ -939,7 +949,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 6ff28ec2eb..d73d112bd5 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 e95d7458f9..6eb7e95035 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -78,6 +78,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 46c4e25a1f..4c3ba268bf 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -28,8 +28,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' base_path = ['service', 'dhcp-server'] interface = 'dum8765' @@ -801,6 +803,89 @@ def test_dhcp_high_availability_standby(self): self.assertTrue(process_named_running(PROCESS_NAME)) self.assertTrue(process_named_running(CTRL_PROCESS_NAME)) + 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 e89448e2d1..2b93b6afc8 100755 --- a/src/conf_mode/service_dhcp-server.py +++ b/src/conf_mode/service_dhcp-server.py @@ -43,6 +43,7 @@ ctrl_config_file = '/run/kea/kea-ctrl-agent.conf' 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*' systemd_override = r'/run/systemd/system/kea-ctrl-agent.service.d/10-override.conf' @@ -155,6 +156,16 @@ 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 if not dhcp or 'disable' in dhcp: @@ -341,6 +352,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 def generate(dhcp): @@ -391,6 +418,8 @@ def generate(dhcp): render(ctrl_config_file, 'dhcp-server/kea-ctrl-agent.conf.j2', dhcp, user=user_group, group=user_group) render(config_file, 'dhcp-server/kea-dhcp4.conf.j2', 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 58c0c6fc8a262df4717456e581aa9492f10cfa6c Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sat, 5 Oct 2024 21:07:40 +1000 Subject: [PATCH 04/10] 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 9a3f4acf6f..5cf4097110 100644 --- a/data/templates/dhcp-server/kea-dhcp4.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2 @@ -61,7 +61,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 42f84c0970..76b32b1b9c 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 @@ -336,6 +237,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 c0391e930d..72534ad134 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -172,6 +172,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 def kea6_parse_options(config): @@ -306,6 +309,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 59a8f6508f..b54a715ef7 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -873,29 +873,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): @@ -943,7 +926,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 @@ -955,6 +938,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 = [] @@ -968,6 +952,9 @@ def kea_shared_network_json(shared_networks): 'subnet4': [] } + 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 4c3ba268bf..cac89acabf 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -804,6 +804,29 @@ def test_dhcp_high_availability_standby(self): self.assertTrue(process_named_running(CTRL_PROCESS_NAME)) 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']) @@ -839,47 +862,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 c9947095bf0461c3ac307c590f2c8249ba3410b0 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sun, 6 Oct 2024 14:22:18 +1100 Subject: [PATCH 05/10] 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 72534ad134..2cbc065753 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -324,6 +324,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 cac89acabf..50b8b61a21 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -804,28 +804,18 @@ def test_dhcp_high_availability_standby(self): self.assertTrue(process_named_running(CTRL_PROCESS_NAME)) 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 248b18ce01be571c484714907546306368bd99d5 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sun, 6 Oct 2024 14:47:30 +1100 Subject: [PATCH 06/10] 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 50b8b61a21..592504c2ab 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -811,20 +811,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']) @@ -843,6 +833,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) @@ -851,7 +860,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', @@ -859,16 +868,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 cfc438e53dd278fc65ff5f5c323a2e18b92fe979 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sat, 12 Oct 2024 11:33:34 +1100 Subject: [PATCH 07/10] 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 5cf4097110..241c0a3cad 100644 --- a/data/templates/dhcp-server/kea-dhcp4.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2 @@ -50,7 +50,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 592504c2ab..34e6de2004 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -878,15 +878,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( @@ -929,7 +929,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 3a5d9d67bd37d97cbc9410802df3a68cab17c8d0 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sat, 12 Oct 2024 20:46:13 +1100 Subject: [PATCH 08/10] 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 d73d112bd5..ac4b32b171 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 6eb7e95035..506b13e522 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -79,7 +79,6 @@ protocols { service { dhcp-server { dynamic-dns-update { - enable-updates forward-ddns-domain-name domain.lan { dns-server 1 { address 192.168.0.1 @@ -98,6 +97,7 @@ service { } key-name reverse-0-168-192 } + send-updates tsig-key-name domain-lan-updates { algorithm hmac-sha256 secret SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ== @@ -106,34 +106,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 9f76ec027c2abf72e2cff4788f76b8e028a6829f Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Sat, 12 Oct 2024 22:10:41 +1100 Subject: [PATCH 09/10] 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 ac4b32b171..d74eb13488 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 885427ec93e7746c8accb7248037a11dd33a4f97 Mon Sep 17 00:00:00 2001 From: Alex Bukharov Date: Wed, 16 Oct 2024 15:54:05 +1100 Subject: [PATCH 10/10] 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 76b32b1b9c..c1ce26085f 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 2cbc065753..1d0864056e 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -311,9 +311,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 b54a715ef7..2079a845b5 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -886,10 +886,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 d74eb13488..dc9d775e83 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 506b13e522..6706e3342f 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -97,7 +97,7 @@ service { } key-name reverse-0-168-192 } - send-updates + force-updates tsig-key-name domain-lan-updates { algorithm hmac-sha256 secret SXQncyBXZWRuZXNkYXkgbWFoIGR1ZGVzIQ== @@ -111,14 +111,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 34e6de2004..1922a39f54 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -813,29 +813,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']) @@ -846,7 +846,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 2b93b6afc8..e066e4c65c 100755 --- a/src/conf_mode/service_dhcp-server.py +++ b/src/conf_mode/service_dhcp-server.py @@ -354,19 +354,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