diff options
-rw-r--r-- | data/templates/dhcp-server/kea-dhcp6.conf.j2 | 9 | ||||
-rw-r--r-- | interface-definitions/include/listen-interface-multi-broadcast.xml.i | 2 | ||||
-rw-r--r-- | interface-definitions/service_dhcpv6-server.xml.in | 31 | ||||
-rw-r--r-- | python/vyos/kea.py | 10 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_service_dhcpv6-server.py | 11 | ||||
-rwxr-xr-x | src/conf_mode/service_dhcpv6-server.py | 17 | ||||
-rw-r--r-- | src/etc/sudoers.d/vyos | 3 | ||||
-rwxr-xr-x | src/system/on-dhcp-event.sh | 24 | ||||
-rwxr-xr-x | src/system/on-dhcpv6-event.sh | 87 |
9 files changed, 182 insertions, 12 deletions
diff --git a/data/templates/dhcp-server/kea-dhcp6.conf.j2 b/data/templates/dhcp-server/kea-dhcp6.conf.j2 index 3ab21551b..2f0de6b30 100644 --- a/data/templates/dhcp-server/kea-dhcp6.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp6.conf.j2 @@ -19,6 +19,15 @@ "name": "{{ lease_file }}" }, "hooks-libraries": [ +{% if disable_route_autoinstall is not vyos_defined %} + { + "library": "/usr/lib/{{ machine }}-linux-gnu/kea/hooks/libdhcp_run_script.so", + "parameters": { + "name": "/usr/libexec/vyos/system/on-dhcpv6-event.sh", + "sync": false + } + }, +{% endif %} { "library": "/usr/lib/{{ machine }}-linux-gnu/kea/hooks/libdhcp_lease_cmds.so", "parameters": {} diff --git a/interface-definitions/include/listen-interface-multi-broadcast.xml.i b/interface-definitions/include/listen-interface-multi-broadcast.xml.i index b3d5a3ecc..00bd45e6e 100644 --- a/interface-definitions/include/listen-interface-multi-broadcast.xml.i +++ b/interface-definitions/include/listen-interface-multi-broadcast.xml.i @@ -1,7 +1,7 @@ <!-- include start from listen-interface-multi-broadcast.xml.i --> <leafNode name="listen-interface"> <properties> - <help>Interface for DHCP Relay Agent to listen for requests</help> + <help>Interface to listen on</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces --broadcast</script> </completionHelp> diff --git a/interface-definitions/service_dhcpv6-server.xml.in b/interface-definitions/service_dhcpv6-server.xml.in index 07cbfc85d..28b97a64b 100644 --- a/interface-definitions/service_dhcpv6-server.xml.in +++ b/interface-definitions/service_dhcpv6-server.xml.in @@ -10,6 +10,12 @@ <children> #include <include/generic-disable-node.xml.i> #include <include/listen-interface-multi-broadcast.xml.i> + <leafNode name="disable-route-autoinstall"> + <properties> + <help>Do not install routes for delegated prefixes</help> + <valueless/> + </properties> + </leafNode> <node name="global-parameters"> <properties> <help>Additional global parameters for DHCPv6 server</help> @@ -225,6 +231,31 @@ <constraintErrorMessage>Delegated prefix length must be between 32 and 96</constraintErrorMessage> </properties> </leafNode> + <leafNode name="excluded-prefix"> + <properties> + <help>IPv6 prefix to be excluded from prefix delegation</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 prefix excluded from prefix delegation</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="excluded-prefix-length"> + <properties> + <help>Length in bits of excluded prefix</help> + <valueHelp> + <format>u32:33-64</format> + <description>Excluded prefix length (33-128)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 33-128"/> + </constraint> + <constraintErrorMessage>Prefix length must be between 33 and 128</constraintErrorMessage> + </properties> + </leafNode> </children> </tagNode> </children> diff --git a/python/vyos/kea.py b/python/vyos/kea.py index fb5afc2ce..720bebec3 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -237,11 +237,17 @@ def kea6_parse_subnet(subnet, config): if 'prefix' in config['prefix_delegation']: for prefix, pd_conf in config['prefix_delegation']['prefix'].items(): - pd_pools.append({ + pd_pool = { 'prefix': prefix, 'prefix-len': int(pd_conf['prefix_length']), 'delegated-len': int(pd_conf['delegated_length']) - }) + } + + if 'excluded_prefix' in pd_conf: + pd_pool['excluded-prefix'] = pd_conf['excluded_prefix'] + pd_pool['excluded-prefix-len'] = int(pd_conf['excluded_prefix_length']) + + pd_pools.append(pd_pool) out['pd-pools'] = pd_pools diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py index dcce30f55..5a831b8a0 100755 --- a/smoketest/scripts/cli/test_service_dhcpv6-server.py +++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py @@ -215,6 +215,7 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): delegate_start = '2001:db8:ee::' delegate_len = '64' prefix_len = '56' + exclude_len = '66' pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] self.cli_set(pool + ['subnet-id', '1']) @@ -222,6 +223,8 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): self.cli_set(pool + ['range', '1', 'stop', range_stop]) self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'delegated-length', delegate_len]) self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'prefix-length', prefix_len]) + self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'excluded-prefix', delegate_start]) + self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'excluded-prefix-length', exclude_len]) # commit changes self.cli_commit() @@ -241,7 +244,13 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): self.verify_config_object( obj, ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pd-pools'], - {'prefix': delegate_start, 'prefix-len': int(prefix_len), 'delegated-len': int(delegate_len)}) + { + 'prefix': delegate_start, + 'prefix-len': int(prefix_len), + 'delegated-len': int(delegate_len), + 'excluded-prefix': delegate_start, + 'excluded-prefix-len': int(exclude_len) + }) # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) diff --git a/src/conf_mode/service_dhcpv6-server.py b/src/conf_mode/service_dhcpv6-server.py index 214531904..add83eb0d 100755 --- a/src/conf_mode/service_dhcpv6-server.py +++ b/src/conf_mode/service_dhcpv6-server.py @@ -144,6 +144,23 @@ def verify(dhcpv6): if prefix_config['prefix_length'] > prefix_config['delegated_length']: raise ConfigError('Length of delegated IPv6 prefix must be within parent prefix') + if 'excluded_prefix' in prefix_config: + if 'excluded_prefix_length' not in prefix_config: + raise ConfigError('Length of excluded IPv6 prefix must be configured') + + prefix_len = prefix_config['prefix_length'] + prefix_obj = ip_network(f'{prefix}/{prefix_len}') + + excluded_prefix = prefix_config['excluded_prefix'] + excluded_len = prefix_config['excluded_prefix_length'] + excluded_obj = ip_network(f'{excluded_prefix}/{excluded_len}') + + if excluded_len <= prefix_config['delegated_length']: + raise ConfigError('Excluded IPv6 prefix must be smaller than delegated prefix') + + if not excluded_obj.subnet_of(prefix_obj): + raise ConfigError(f'Excluded prefix "{excluded_prefix}" does not exist in the prefix') + # Static mappings don't require anything (but check if IP is in subnet if it's set) if 'static_mapping' in subnet_config: for mapping, mapping_config in subnet_config['static_mapping'].items(): diff --git a/src/etc/sudoers.d/vyos b/src/etc/sudoers.d/vyos index c099446ba..63a944f41 100644 --- a/src/etc/sudoers.d/vyos +++ b/src/etc/sudoers.d/vyos @@ -44,6 +44,8 @@ Cmnd_Alias DIAGNOSTICS = /bin/ip vrf exec * /bin/ping *, \ /bin/ip vrf exec * /bin/traceroute *, \ /bin/ip vrf exec * /usr/bin/mtr *, \ /usr/libexec/vyos/op_mode/* +Cmnd_Alias KEA_IP6_ROUTES = /sbin/ip -6 route replace *,\ + /sbin/ip -6 route del * %operator ALL=NOPASSWD: DATE, IPTABLES, ETHTOOL, IPFLUSH, HWINFO, \ PPPOE_CMDS, PCAPTURE, /usr/sbin/wanpipemon, \ DMIDECODE, DISK, CONNTRACK, IP6TABLES, \ @@ -55,3 +57,4 @@ Cmnd_Alias DIAGNOSTICS = /bin/ip vrf exec * /bin/ping *, \ # Allow members of group sudo to execute any command %sudo ALL=NOPASSWD: ALL +_kea ALL=NOPASSWD: KEA_IP6_ROUTES diff --git a/src/system/on-dhcp-event.sh b/src/system/on-dhcp-event.sh index 52fadd428..47c276270 100755 --- a/src/system/on-dhcp-event.sh +++ b/src/system/on-dhcp-event.sh @@ -1,12 +1,20 @@ #!/bin/bash - -# This script came from ubnt.com forum user "bradd" in the following post -# http://community.ubnt.com/t5/EdgeMAX/Automatic-DNS-resolution-of-DHCP-client-names/td-p/651311 -# It has been modified by Ubiquiti to update the /etc/host file -# instead of adding to the CLI. -# Thanks to forum user "itsmarcos" for bug fix & improvements -# Thanks to forum user "ruudboon" for multiple domain fix -# Thanks to forum user "chibby85" for expire patch and static-mapping +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# if [ $# -lt 1 ]; then echo Invalid args diff --git a/src/system/on-dhcpv6-event.sh b/src/system/on-dhcpv6-event.sh new file mode 100755 index 000000000..cbb370999 --- /dev/null +++ b/src/system/on-dhcpv6-event.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# +# Copyright (C) 2024 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +if [ $# -lt 1 ]; then + echo Invalid args + logger -s -t on-dhcpv6-event "Invalid args \"$@\"" + exit 1 +fi + +action=$1 + +case "$action" in + lease6_renew|lease6_recover) + exit 0 + ;; + + lease6_release|lease6_expire|lease6_decline) + ifname=$QUERY6_IFACE_NAME + lease_addr=$LEASE6_ADDRESS + lease_prefix_len=$LEASE6_PREFIX_LEN + + if [[ "$LEASE6_TYPE" != "IA_PD" ]]; then + exit 0 + fi + + logger -s -t on-dhcpv6-event "Processing route deletion for ${lease_addr}/${lease_prefix_len}" + route_cmd="sudo -n /sbin/ip -6 route del ${lease_addr}/${lease_prefix_len}" + + # the ifname is not always present, like in LEASE6_VALID_LIFETIME=0 updates, + # but 'route del' works either way. Use interface only if there is one. + if [[ "$ifname" != "" ]]; then + route_cmd+=" dev ${ifname}" + fi + route_cmd+=" proto static" + eval "$route_cmd" + + exit 0 + ;; + + leases6_committed) + for ((i = 0; i < $LEASES6_SIZE; i++)); do + ifname=$QUERY6_IFACE_NAME + requester_link_local=$QUERY6_REMOTE_ADDR + lease_type_var="LEASES6_AT${i}_TYPE" + lease_ip_var="LEASES6_AT${i}_ADDRESS" + lease_prefix_len_var="LEASES6_AT${i}_PREFIX_LEN" + + lease_type=${!lease_type_var} + + if [[ "$lease_type" != "IA_PD" ]]; then + continue + fi + + lease_ip=${!lease_ip_var} + lease_prefix_len=${!lease_prefix_len_var} + + logger -s -t on-dhcpv6-event "Processing PD route for ${lease_addr}/${lease_prefix_len}. Link local: ${requester_link_local} ifname: ${ifname}" + + sudo -n /sbin/ip -6 route replace ${lease_ip}/${lease_prefix_len} \ + via ${requester_link_local} \ + dev ${ifname} \ + proto static + done + + exit 0 + ;; + + *) + logger -s -t on-dhcpv6-event "Invalid command \"$1\"" + exit 1 + ;; +esac |