summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Vølker <david@voelker.dk>2025-08-15 09:31:49 +0200
committerDavid Vølker <david@voelker.dk>2025-08-15 09:31:49 +0200
commit974f9a5d5e13241e2b4ea54152bf835db6b7feff (patch)
treeb816d2c6c6bea3e670f44e880f32b10d311d353e
parent862817a7207c5b5d17c67451bac602d296b88230 (diff)
downloadvyos-1x-974f9a5d5e13241e2b4ea54152bf835db6b7feff.tar.gz
vyos-1x-974f9a5d5e13241e2b4ea54152bf835db6b7feff.zip
kea: T6211: add VRF support for KEA dhcp server
-rw-r--r--data/templates/dhcp-server/kea-dhcp-ddns.conf.j25
-rw-r--r--data/templates/dhcp-server/kea-dhcp4.conf.j25
-rw-r--r--data/templates/dhcp-server/kea-dhcp6.conf.j25
-rw-r--r--interface-definitions/include/dhcp/dhcp-server-common-config.xml.i342
-rw-r--r--interface-definitions/include/dhcp/dhcpv6-server-common-config.xml.i276
-rw-r--r--interface-definitions/service_dhcp-server.xml.in341
-rw-r--r--interface-definitions/service_dhcpv6-server.xml.in275
-rw-r--r--interface-definitions/vrf.xml.in25
-rw-r--r--op-mode-definitions/dhcp.xml.in277
-rw-r--r--op-mode-definitions/monitor-log.xml.in16
-rwxr-xr-xop-mode-definitions/show-log.xml.in16
-rw-r--r--python/vyos/kea.py30
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py2
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py393
-rwxr-xr-xsrc/conf_mode/service_dhcp-server.py84
-rwxr-xr-xsrc/conf_mode/service_dhcpv6-server.py73
-rw-r--r--src/etc/sudoers.d/vyos3
-rw-r--r--src/etc/systemd/system/kea-dhcp-ddns-server@.service22
-rw-r--r--src/etc/systemd/system/kea-dhcp4-server@.service22
-rw-r--r--src/etc/systemd/system/kea-dhcp6-server@.service22
-rwxr-xr-xsrc/op_mode/dhcp.py119
-rwxr-xr-xsrc/system/on-dhcp-event.sh2
-rwxr-xr-xsrc/system/sync-dhcp-lease-to-hosts.py6
23 files changed, 1661 insertions, 700 deletions
diff --git a/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2 b/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2
index 7b0394a88..e734a37f3 100644
--- a/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2
+++ b/data/templates/dhcp-server/kea-dhcp-ddns.conf.j2
@@ -3,8 +3,13 @@
"ip-address": "127.0.0.1",
"port": 53001,
"control-socket": {
+{% if vrf_context is vyos_defined %}
+ "socket-type": "unix",
+ "socket-name": "/run/kea/kea-ddns-{{ vrf_context }}-ctrl-socket"
+{% else %}
"socket-type": "unix",
"socket-name": "/run/kea/kea-ddns-ctrl-socket"
+{% endif %}
},
"tsig-keys": {{ dynamic_dns_update | kea_dynamic_dns_update_tsig_key_json }},
"forward-ddns" : {
diff --git a/data/templates/dhcp-server/kea-dhcp4.conf.j2 b/data/templates/dhcp-server/kea-dhcp4.conf.j2
index d08ca0eaa..60c9cf9b2 100644
--- a/data/templates/dhcp-server/kea-dhcp4.conf.j2
+++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2
@@ -15,8 +15,13 @@
"service-sockets-retry-wait-time": 5000
},
"control-socket": {
+{% if vrf_context is vyos_defined %}
+ "socket-type": "unix",
+ "socket-name": "/run/kea/dhcp4-{{ vrf_context }}-ctrl-socket"
+{% else %}
"socket-type": "unix",
"socket-name": "/run/kea/dhcp4-ctrl-socket"
+{% endif %}
},
"lease-database": {
"type": "memfile",
diff --git a/data/templates/dhcp-server/kea-dhcp6.conf.j2 b/data/templates/dhcp-server/kea-dhcp6.conf.j2
index 4745d693c..4352bbae7 100644
--- a/data/templates/dhcp-server/kea-dhcp6.conf.j2
+++ b/data/templates/dhcp-server/kea-dhcp6.conf.j2
@@ -10,8 +10,13 @@
"service-sockets-retry-wait-time": 5000
},
"control-socket": {
+{% if vrf_context is vyos_defined %}
+ "socket-type": "unix",
+ "socket-name": "/run/kea/dhcp6-{{ vrf_context }}-ctrl-socket"
+{% else %}
"socket-type": "unix",
"socket-name": "/run/kea/dhcp6-ctrl-socket"
+{% endif %}
},
"lease-database": {
"type": "memfile",
diff --git a/interface-definitions/include/dhcp/dhcp-server-common-config.xml.i b/interface-definitions/include/dhcp/dhcp-server-common-config.xml.i
new file mode 100644
index 000000000..60eb01489
--- /dev/null
+++ b/interface-definitions/include/dhcp/dhcp-server-common-config.xml.i
@@ -0,0 +1,342 @@
+<!-- include start from dhcp/dhcp-server-common-config.xml.i -->
+#include <include/generic-disable-node.xml.i>
+<node name="dynamic-dns-update">
+ <properties>
+ <help>Dynamically update Domain Name System (RFC4702)</help>
+ </properties>
+ <children>
+ #include <include/dhcp/ddns-settings.xml.i>
+ <tagNode name="tsig-key">
+ <properties>
+ <help>TSIG key definition for DNS updates</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid TSIG key name. May only contain letters, numbers, hyphen and underscore</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="algorithm">
+ <properties>
+ <help>TSIG key algorithm</help>
+ <completionHelp>
+ <list>md5 sha1 sha224 sha256 sha384 sha512</list>
+ </completionHelp>
+ <valueHelp>
+ <format>md5</format>
+ <description>MD5 HMAC algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha1</format>
+ <description>SHA1 HMAC algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha224</format>
+ <description>SHA224 HMAC algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha256</format>
+ <description>SHA256 HMAC algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha384</format>
+ <description>SHA384 HMAC algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha512</format>
+ <description>SHA512 HMAC algorithm</description>
+ </valueHelp>
+ <constraint>
+ <regex>(md5|sha1|sha224|sha256|sha384|sha512)</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid TSIG key algorithm</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="secret">
+ <properties>
+ <help>TSIG key secret (base64-encoded)</help>
+ <constraint>
+ <validator name="base64"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="forward-domain">
+ <properties>
+ <help>Forward DNS domain name</help>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ <constraintErrorMessage>Invalid forward DNS domain name</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="key-name">
+ <properties>
+ <help>TSIG key name for forward DNS updates</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid TSIG key name. May only contain letters, numbers, numbers, hyphen and underscore</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ #include <include/dhcp/ddns-dns-server.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="reverse-domain">
+ <properties>
+ <help>Reverse DNS domain name</help>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ <constraintErrorMessage>Invalid reverse DNS domain name</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="key-name">
+ <properties>
+ <help>TSIG key name for reverse DNS updates</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid TSIG key name. May only contain letters, numbers, numbers, hyphen and underscore</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ #include <include/dhcp/ddns-dns-server.xml.i>
+ </children>
+ </tagNode>
+ </children>
+</node>
+<node name="high-availability">
+ <properties>
+ <help>DHCP high availability configuration</help>
+ </properties>
+ <children>
+ #include <include/source-address-ipv4.xml.i>
+ <leafNode name="mode">
+ <properties>
+ <help>Configure high availability mode</help>
+ <completionHelp>
+ <list>active-active active-passive</list>
+ </completionHelp>
+ <valueHelp>
+ <format>active-active</format>
+ <description>Both server attend DHCP requests</description>
+ </valueHelp>
+ <valueHelp>
+ <format>active-passive</format>
+ <description>Only primary server attends DHCP requests</description>
+ </valueHelp>
+ <constraint>
+ <regex>(active-active|active-passive)</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid DHCP high availability mode</constraintErrorMessage>
+ </properties>
+ <defaultValue>active-active</defaultValue>
+ </leafNode>
+ <leafNode name="remote">
+ <properties>
+ <help>IPv4 remote address used for connection</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address of high availability peer</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="name">
+ <properties>
+ <help>Peer name used to identify connection</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid failover peer name. May only contain letters, numbers and .-_</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="status">
+ <properties>
+ <help>High availability hierarchy</help>
+ <completionHelp>
+ <list>primary secondary</list>
+ </completionHelp>
+ <valueHelp>
+ <format>primary</format>
+ <description>Configure this server to be the primary node</description>
+ </valueHelp>
+ <valueHelp>
+ <format>secondary</format>
+ <description>Configure this server to be the secondary node</description>
+ </valueHelp>
+ <constraint>
+ <regex>(primary|secondary)</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid DHCP high availability peer status</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ #include <include/pki/ca-certificate.xml.i>
+ #include <include/pki/certificate.xml.i>
+ </children>
+</node>
+<leafNode name="hostfile-update">
+ <properties>
+ <help>Updating /etc/hosts file (per client lease)</help>
+ <valueless/>
+ </properties>
+</leafNode>
+#include <include/listen-address-ipv4.xml.i>
+#include <include/listen-interface-multi-broadcast.xml.i>
+<tagNode name="shared-network-name">
+ <properties>
+ <help>Name of DHCP shared network</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid shared network name. May only contain letters, numbers and .-_</constraintErrorMessage>
+ </properties>
+ <children>
+ <node name="dynamic-dns-update">
+ <properties>
+ <help>Dynamically update Domain Name System (RFC4702)</help>
+ </properties>
+ <children>
+ #include <include/dhcp/ddns-settings.xml.i>
+ </children>
+ </node>
+ <leafNode name="authoritative">
+ <properties>
+ <help>Option to make DHCP server authoritative for this physical network</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ #include <include/dhcp/option-v4.xml.i>
+ #include <include/dhcp/ping-check.xml.i>
+ #include <include/generic-description.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ <tagNode name="subnet">
+ <properties>
+ <help>DHCP subnet for shared network</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 address and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ </constraint>
+ <constraintErrorMessage>Invalid IPv4 subnet definition</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/dhcp/option-v4.xml.i>
+ #include <include/dhcp/ping-check.xml.i>
+ #include <include/generic-description.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ <node name="dynamic-dns-update">
+ <properties>
+ <help>Dynamically update Domain Name System (RFC4702)</help>
+ </properties>
+ <children>
+ #include <include/dhcp/ddns-settings.xml.i>
+ </children>
+ </node>
+ <leafNode name="exclude">
+ <properties>
+ <help>IP address to exclude from DHCP lease range</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address to exclude from lease range</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="ignore-client-id">
+ <properties>
+ <help>Ignore client identifier for lease lookups</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="lease">
+ <properties>
+ <help>Lease timeout in seconds</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>DHCP lease time in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ <constraintErrorMessage>DHCP lease time must be between 0 and 4294967295 (49 days)</constraintErrorMessage>
+ </properties>
+ <defaultValue>86400</defaultValue>
+ </leafNode>
+ <tagNode name="range">
+ <properties>
+ <help>DHCP lease range</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid range name, may only be alphanumeric, dot and hyphen</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/dhcp/option-v4.xml.i>
+ <leafNode name="start">
+ <properties>
+ <help>First IP address for DHCP lease range</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 start address of pool</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="stop">
+ <properties>
+ <help>Last IP address for DHCP lease range</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 end address of pool</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="static-mapping">
+ <properties>
+ <help>Hostname for static mapping reservation</help>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ <constraintErrorMessage>Invalid static mapping hostname</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/dhcp/option-v4.xml.i>
+ #include <include/generic-description.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ #include <include/ip-address.xml.i>
+ #include <include/interface/mac.xml.i>
+ #include <include/interface/duid.xml.i>
+ </children>
+ </tagNode>
+ <leafNode name="subnet-id">
+ <properties>
+ <help>Unique ID mapped to leases in the lease file</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Unique subnet ID</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/include/dhcp/dhcpv6-server-common-config.xml.i b/interface-definitions/include/dhcp/dhcpv6-server-common-config.xml.i
new file mode 100644
index 000000000..798ecac4b
--- /dev/null
+++ b/interface-definitions/include/dhcp/dhcpv6-server-common-config.xml.i
@@ -0,0 +1,276 @@
+<!-- include start from dhcp/dhcpv6-server-common-config.xml.i -->
+#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>
+ </properties>
+ <children>
+ #include <include/name-server-ipv6.xml.i>
+ </children>
+</node>
+<leafNode name="preference">
+ <properties>
+ <help>Preference of this DHCPv6 server compared with others</help>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>DHCPv6 server preference (0-255)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ <constraintErrorMessage>Preference must be between 0 and 255</constraintErrorMessage>
+ </properties>
+</leafNode>
+<tagNode name="shared-network-name">
+ <properties>
+ <help>DHCPv6 shared network name</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid DHCPv6 shared network name. May only contain letters, numbers and .-_</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ #include <include/generic-description.xml.i>
+ #include <include/generic-interface.xml.i>
+ #include <include/dhcp/option-v6.xml.i>
+ <tagNode name="subnet">
+ <properties>
+ <help>IPv6 DHCP subnet for this shared network</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/dhcp/option-v6.xml.i>
+ #include <include/generic-interface.xml.i>
+ <tagNode name="range">
+ <properties>
+ <help>Parameters setting ranges for assigning IPv6 addresses</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
+ </constraint>
+ <constraintErrorMessage>Invalid range name, may only be alphanumeric, dot and hyphen</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/dhcp/option-v6.xml.i>
+ <leafNode name="prefix">
+ <properties>
+ <help>IPv6 prefix defining range of addresses to assign</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="start">
+ <properties>
+ <help>First in range of consecutive IPv6 addresses to assign</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="stop">
+ <properties>
+ <help>Last in range of consecutive IPv6 addresses</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="lease-time">
+ <properties>
+ <help>Parameters relating to the lease time</help>
+ </properties>
+ <children>
+ <leafNode name="default">
+ <properties>
+ <help>Default time (in seconds) that will be assigned to a lease</help>
+ <valueHelp>
+ <format>u32:1-4294967295</format>
+ <description>DHCPv6 valid lifetime</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="maximum">
+ <properties>
+ <help>Maximum time (in seconds) that will be assigned to a lease</help>
+ <valueHelp>
+ <format>u32:1-4294967295</format>
+ <description>Maximum lease time in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="minimum">
+ <properties>
+ <help>Minimum time (in seconds) that will be assigned to a lease</help>
+ <valueHelp>
+ <format>u32:1-4294967295</format>
+ <description>Minimum lease time in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="prefix-delegation">
+ <properties>
+ <help>Parameters relating to IPv6 prefix delegation</help>
+ </properties>
+ <children>
+ <tagNode name="prefix">
+ <properties>
+ <help>IPv6 prefix to be used in prefix delegation</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 prefix used in prefix delegation</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="prefix-length">
+ <properties>
+ <help>Length in bits of prefix</help>
+ <valueHelp>
+ <format>u32:32-64</format>
+ <description>Prefix length (32-64)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 32-64"/>
+ </constraint>
+ <constraintErrorMessage>Prefix length must be between 32 and 64</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="delegated-length">
+ <properties>
+ <help>Length in bits of prefixes to be delegated</help>
+ <valueHelp>
+ <format>u32:32-64</format>
+ <description>Delegated prefix length (32-64)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 32-96"/>
+ </constraint>
+ <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>
+ </node>
+ <tagNode name="static-mapping">
+ <properties>
+ <help>Hostname for static mapping reservation</help>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ <constraintErrorMessage>Invalid static mapping hostname</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/dhcp/option-v6.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ #include <include/interface/mac.xml.i>
+ #include <include/interface/duid.xml.i>
+ <leafNode name="ipv6-address">
+ <properties>
+ <help>Client IPv6 address for this static mapping</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address for this static mapping</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-prefix">
+ <properties>
+ <help>Client IPv6 prefix for this static mapping</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 prefix for this static mapping</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="subnet-id">
+ <properties>
+ <help>Unique ID mapped to leases in the lease file</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Unique subnet ID</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in
index 78f1cea4e..4d1172e03 100644
--- a/interface-definitions/service_dhcp-server.xml.in
+++ b/interface-definitions/service_dhcp-server.xml.in
@@ -9,346 +9,7 @@
<priority>911</priority>
</properties>
<children>
- #include <include/generic-disable-node.xml.i>
- <node name="dynamic-dns-update">
- <properties>
- <help>Dynamically update Domain Name System (RFC4702)</help>
- </properties>
- <children>
- #include <include/dhcp/ddns-settings.xml.i>
- <tagNode name="tsig-key">
- <properties>
- <help>TSIG key definition for DNS updates</help>
- <constraint>
- #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
- </constraint>
- <constraintErrorMessage>Invalid TSIG key name. May only contain letters, numbers, hyphen and underscore</constraintErrorMessage>
- </properties>
- <children>
- <leafNode name="algorithm">
- <properties>
- <help>TSIG key algorithm</help>
- <completionHelp>
- <list>md5 sha1 sha224 sha256 sha384 sha512</list>
- </completionHelp>
- <valueHelp>
- <format>md5</format>
- <description>MD5 HMAC algorithm</description>
- </valueHelp>
- <valueHelp>
- <format>sha1</format>
- <description>SHA1 HMAC algorithm</description>
- </valueHelp>
- <valueHelp>
- <format>sha224</format>
- <description>SHA224 HMAC algorithm</description>
- </valueHelp>
- <valueHelp>
- <format>sha256</format>
- <description>SHA256 HMAC algorithm</description>
- </valueHelp>
- <valueHelp>
- <format>sha384</format>
- <description>SHA384 HMAC algorithm</description>
- </valueHelp>
- <valueHelp>
- <format>sha512</format>
- <description>SHA512 HMAC algorithm</description>
- </valueHelp>
- <constraint>
- <regex>(md5|sha1|sha224|sha256|sha384|sha512)</regex>
- </constraint>
- <constraintErrorMessage>Invalid TSIG key algorithm</constraintErrorMessage>
- </properties>
- </leafNode>
- <leafNode name="secret">
- <properties>
- <help>TSIG key secret (base64-encoded)</help>
- <constraint>
- <validator name="base64"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- <tagNode name="forward-domain">
- <properties>
- <help>Forward DNS domain name</help>
- <constraint>
- <validator name="fqdn"/>
- </constraint>
- <constraintErrorMessage>Invalid forward DNS domain name</constraintErrorMessage>
- </properties>
- <children>
- <leafNode name="key-name">
- <properties>
- <help>TSIG key name for forward DNS updates</help>
- <constraint>
- #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
- </constraint>
- <constraintErrorMessage>Invalid TSIG key name. May only contain letters, numbers, numbers, hyphen and underscore</constraintErrorMessage>
- </properties>
- </leafNode>
- #include <include/dhcp/ddns-dns-server.xml.i>
- </children>
- </tagNode>
- <tagNode name="reverse-domain">
- <properties>
- <help>Reverse DNS domain name</help>
- <constraint>
- <validator name="fqdn"/>
- </constraint>
- <constraintErrorMessage>Invalid reverse DNS domain name</constraintErrorMessage>
- </properties>
- <children>
- <leafNode name="key-name">
- <properties>
- <help>TSIG key name for reverse DNS updates</help>
- <constraint>
- #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
- </constraint>
- <constraintErrorMessage>Invalid TSIG key name. May only contain letters, numbers, numbers, hyphen and underscore</constraintErrorMessage>
- </properties>
- </leafNode>
- #include <include/dhcp/ddns-dns-server.xml.i>
- </children>
- </tagNode>
- </children>
- </node>
- <node name="high-availability">
- <properties>
- <help>DHCP high availability configuration</help>
- </properties>
- <children>
- #include <include/source-address-ipv4.xml.i>
- <leafNode name="mode">
- <properties>
- <help>Configure high availability mode</help>
- <completionHelp>
- <list>active-active active-passive</list>
- </completionHelp>
- <valueHelp>
- <format>active-active</format>
- <description>Both server attend DHCP requests</description>
- </valueHelp>
- <valueHelp>
- <format>active-passive</format>
- <description>Only primary server attends DHCP requests</description>
- </valueHelp>
- <constraint>
- <regex>(active-active|active-passive)</regex>
- </constraint>
- <constraintErrorMessage>Invalid DHCP high availability mode</constraintErrorMessage>
- </properties>
- <defaultValue>active-active</defaultValue>
- </leafNode>
- <leafNode name="remote">
- <properties>
- <help>IPv4 remote address used for connection</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address of high availability peer</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="name">
- <properties>
- <help>Peer name used to identify connection</help>
- <constraint>
- #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
- </constraint>
- <constraintErrorMessage>Invalid failover peer name. May only contain letters, numbers and .-_</constraintErrorMessage>
- </properties>
- </leafNode>
- <leafNode name="status">
- <properties>
- <help>High availability hierarchy</help>
- <completionHelp>
- <list>primary secondary</list>
- </completionHelp>
- <valueHelp>
- <format>primary</format>
- <description>Configure this server to be the primary node</description>
- </valueHelp>
- <valueHelp>
- <format>secondary</format>
- <description>Configure this server to be the secondary node</description>
- </valueHelp>
- <constraint>
- <regex>(primary|secondary)</regex>
- </constraint>
- <constraintErrorMessage>Invalid DHCP high availability peer status</constraintErrorMessage>
- </properties>
- </leafNode>
- #include <include/pki/ca-certificate.xml.i>
- #include <include/pki/certificate.xml.i>
- </children>
- </node>
- <leafNode name="hostfile-update">
- <properties>
- <help>Updating /etc/hosts file (per client lease)</help>
- <valueless/>
- </properties>
- </leafNode>
- #include <include/listen-address-ipv4.xml.i>
- #include <include/listen-interface-multi-broadcast.xml.i>
- <tagNode name="shared-network-name">
- <properties>
- <help>Name of DHCP shared network</help>
- <constraint>
- #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
- </constraint>
- <constraintErrorMessage>Invalid shared network name. May only contain letters, numbers and .-_</constraintErrorMessage>
- </properties>
- <children>
- <node name="dynamic-dns-update">
- <properties>
- <help>Dynamically update Domain Name System (RFC4702)</help>
- </properties>
- <children>
- #include <include/dhcp/ddns-settings.xml.i>
- </children>
- </node>
- <leafNode name="authoritative">
- <properties>
- <help>Option to make DHCP server authoritative for this physical network</help>
- <valueless/>
- </properties>
- </leafNode>
- #include <include/dhcp/option-v4.xml.i>
- #include <include/dhcp/ping-check.xml.i>
- #include <include/generic-description.xml.i>
- #include <include/generic-disable-node.xml.i>
- <tagNode name="subnet">
- <properties>
- <help>DHCP subnet for shared network</help>
- <valueHelp>
- <format>ipv4net</format>
- <description>IPv4 address and prefix length</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-prefix"/>
- </constraint>
- <constraintErrorMessage>Invalid IPv4 subnet definition</constraintErrorMessage>
- </properties>
- <children>
- #include <include/dhcp/option-v4.xml.i>
- #include <include/dhcp/ping-check.xml.i>
- #include <include/generic-description.xml.i>
- #include <include/generic-disable-node.xml.i>
- <node name="dynamic-dns-update">
- <properties>
- <help>Dynamically update Domain Name System (RFC4702)</help>
- </properties>
- <children>
- #include <include/dhcp/ddns-settings.xml.i>
- </children>
- </node>
- <leafNode name="exclude">
- <properties>
- <help>IP address to exclude from DHCP lease range</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address to exclude from lease range</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- <multi/>
- </properties>
- </leafNode>
- <leafNode name="ignore-client-id">
- <properties>
- <help>Ignore client identifier for lease lookups</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="lease">
- <properties>
- <help>Lease timeout in seconds</help>
- <valueHelp>
- <format>u32</format>
- <description>DHCP lease time in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-4294967295"/>
- </constraint>
- <constraintErrorMessage>DHCP lease time must be between 0 and 4294967295 (49 days)</constraintErrorMessage>
- </properties>
- <defaultValue>86400</defaultValue>
- </leafNode>
- <tagNode name="range">
- <properties>
- <help>DHCP lease range</help>
- <constraint>
- #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
- </constraint>
- <constraintErrorMessage>Invalid range name, may only be alphanumeric, dot and hyphen</constraintErrorMessage>
- </properties>
- <children>
- #include <include/dhcp/option-v4.xml.i>
- <leafNode name="start">
- <properties>
- <help>First IP address for DHCP lease range</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 start address of pool</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="stop">
- <properties>
- <help>Last IP address for DHCP lease range</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 end address of pool</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- <tagNode name="static-mapping">
- <properties>
- <help>Hostname for static mapping reservation</help>
- <constraint>
- <validator name="fqdn"/>
- </constraint>
- <constraintErrorMessage>Invalid static mapping hostname</constraintErrorMessage>
- </properties>
- <children>
- #include <include/dhcp/option-v4.xml.i>
- #include <include/generic-description.xml.i>
- #include <include/generic-disable-node.xml.i>
- #include <include/ip-address.xml.i>
- #include <include/interface/mac.xml.i>
- #include <include/interface/duid.xml.i>
- </children>
- </tagNode>
- <leafNode name="subnet-id">
- <properties>
- <help>Unique ID mapped to leases in the lease file</help>
- <valueHelp>
- <format>u32</format>
- <description>Unique subnet ID</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </tagNode>
+ #include <include/dhcp/dhcp-server-common-config.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/service_dhcpv6-server.xml.in b/interface-definitions/service_dhcpv6-server.xml.in
index a6763a345..d2b96061d 100644
--- a/interface-definitions/service_dhcpv6-server.xml.in
+++ b/interface-definitions/service_dhcpv6-server.xml.in
@@ -8,280 +8,7 @@
<priority>900</priority>
</properties>
<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>
- </properties>
- <children>
- #include <include/name-server-ipv6.xml.i>
- </children>
- </node>
- <leafNode name="preference">
- <properties>
- <help>Preference of this DHCPv6 server compared with others</help>
- <valueHelp>
- <format>u32:0-255</format>
- <description>DHCPv6 server preference (0-255)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-255"/>
- </constraint>
- <constraintErrorMessage>Preference must be between 0 and 255</constraintErrorMessage>
- </properties>
- </leafNode>
- <tagNode name="shared-network-name">
- <properties>
- <help>DHCPv6 shared network name</help>
- <constraint>
- #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
- </constraint>
- <constraintErrorMessage>Invalid DHCPv6 shared network name. May only contain letters, numbers and .-_</constraintErrorMessage>
- </properties>
- <children>
- #include <include/generic-disable-node.xml.i>
- #include <include/generic-description.xml.i>
- #include <include/generic-interface.xml.i>
- #include <include/dhcp/option-v6.xml.i>
- <tagNode name="subnet">
- <properties>
- <help>IPv6 DHCP subnet for this shared network</help>
- <valueHelp>
- <format>ipv6net</format>
- <description>IPv6 address and prefix length</description>
- </valueHelp>
- <constraint>
- <validator name="ipv6-prefix"/>
- </constraint>
- </properties>
- <children>
- #include <include/dhcp/option-v6.xml.i>
- #include <include/generic-interface.xml.i>
- <tagNode name="range">
- <properties>
- <help>Parameters setting ranges for assigning IPv6 addresses</help>
- <constraint>
- #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
- </constraint>
- <constraintErrorMessage>Invalid range name, may only be alphanumeric, dot and hyphen</constraintErrorMessage>
- </properties>
- <children>
- #include <include/dhcp/option-v6.xml.i>
- <leafNode name="prefix">
- <properties>
- <help>IPv6 prefix defining range of addresses to assign</help>
- <valueHelp>
- <format>ipv6net</format>
- <description>IPv6 address and prefix length</description>
- </valueHelp>
- <constraint>
- <validator name="ipv6-prefix"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="start">
- <properties>
- <help>First in range of consecutive IPv6 addresses to assign</help>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv6-address"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="stop">
- <properties>
- <help>Last in range of consecutive IPv6 addresses</help>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv6-address"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- <node name="lease-time">
- <properties>
- <help>Parameters relating to the lease time</help>
- </properties>
- <children>
- <leafNode name="default">
- <properties>
- <help>Default time (in seconds) that will be assigned to a lease</help>
- <valueHelp>
- <format>u32:1-4294967295</format>
- <description>DHCPv6 valid lifetime</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="maximum">
- <properties>
- <help>Maximum time (in seconds) that will be assigned to a lease</help>
- <valueHelp>
- <format>u32:1-4294967295</format>
- <description>Maximum lease time in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="minimum">
- <properties>
- <help>Minimum time (in seconds) that will be assigned to a lease</help>
- <valueHelp>
- <format>u32:1-4294967295</format>
- <description>Minimum lease time in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </node>
- <node name="prefix-delegation">
- <properties>
- <help>Parameters relating to IPv6 prefix delegation</help>
- </properties>
- <children>
- <tagNode name="prefix">
- <properties>
- <help>IPv6 prefix to be used in prefix delegation</help>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 prefix used in prefix delegation</description>
- </valueHelp>
- <constraint>
- <validator name="ipv6-address"/>
- </constraint>
- </properties>
- <children>
- <leafNode name="prefix-length">
- <properties>
- <help>Length in bits of prefix</help>
- <valueHelp>
- <format>u32:32-64</format>
- <description>Prefix length (32-64)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 32-64"/>
- </constraint>
- <constraintErrorMessage>Prefix length must be between 32 and 64</constraintErrorMessage>
- </properties>
- </leafNode>
- <leafNode name="delegated-length">
- <properties>
- <help>Length in bits of prefixes to be delegated</help>
- <valueHelp>
- <format>u32:32-64</format>
- <description>Delegated prefix length (32-64)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 32-96"/>
- </constraint>
- <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>
- </node>
- <tagNode name="static-mapping">
- <properties>
- <help>Hostname for static mapping reservation</help>
- <constraint>
- <validator name="fqdn"/>
- </constraint>
- <constraintErrorMessage>Invalid static mapping hostname</constraintErrorMessage>
- </properties>
- <children>
- #include <include/dhcp/option-v6.xml.i>
- #include <include/generic-disable-node.xml.i>
- #include <include/interface/mac.xml.i>
- #include <include/interface/duid.xml.i>
- <leafNode name="ipv6-address">
- <properties>
- <help>Client IPv6 address for this static mapping</help>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 address for this static mapping</description>
- </valueHelp>
- <constraint>
- <validator name="ipv6-address"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="ipv6-prefix">
- <properties>
- <help>Client IPv6 prefix for this static mapping</help>
- <valueHelp>
- <format>ipv6net</format>
- <description>IPv6 prefix for this static mapping</description>
- </valueHelp>
- <constraint>
- <validator name="ipv6-prefix"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- <leafNode name="subnet-id">
- <properties>
- <help>Unique ID mapped to leases in the lease file</help>
- <valueHelp>
- <format>u32</format>
- <description>Unique subnet ID</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-4294967295"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </tagNode>
+ #include <include/dhcp/dhcpv6-server-common-config.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in
index 03128cb99..3fa95076e 100644
--- a/interface-definitions/vrf.xml.in
+++ b/interface-definitions/vrf.xml.in
@@ -116,6 +116,31 @@
</node>
</children>
</node>
+ <node name="service">
+ <properties>
+ <help>Enable services in the vrf itself</help>
+ </properties>
+ <children>
+ <node name="dhcp-server" owner="${vyos_conf_scripts_dir}/service_dhcp-server.py $VAR(../../@)">
+ <properties>
+ <help>Dynamic Host Configuration Protocol (DHCP) for DHCP server</help>
+ <priority>912</priority>
+ </properties>
+ <children>
+ #include <include/dhcp/dhcp-server-common-config.xml.i>
+ </children>
+ </node>
+ <node name="dhcpv6-server" owner="${vyos_conf_scripts_dir}/service_dhcpv6-server.py $VAR(../../@)">
+ <properties>
+ <help>DHCP for IPv6 (DHCPv6) server</help>
+ <priority>901</priority>
+ </properties>
+ <children>
+ #include <include/dhcp/dhcpv6-server-common-config.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
<leafNode name="table">
<properties>
<help>Routing table associated with this instance</help>
diff --git a/op-mode-definitions/dhcp.xml.in b/op-mode-definitions/dhcp.xml.in
index 2da3bb5dc..da14359f2 100644
--- a/op-mode-definitions/dhcp.xml.in
+++ b/op-mode-definitions/dhcp.xml.in
@@ -13,6 +13,22 @@
</properties>
<command>${vyos_op_scripts_dir}/dhcp.py clear_dhcp_server_lease --family inet --address $4</command>
</tagNode>
+ <tagNode name="vrf">
+ <properties>
+ <help>Clear DHCP server lease for vrf</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="lease">
+ <properties>
+ <help>DHCP server lease</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py clear_dhcp_server_lease --family inet --vrf $4 --address $6</command>
+ </tagNode>
+ </children>
+ </tagNode>
</children>
</node>
<node name="dhcpv6-server">
@@ -26,6 +42,22 @@
</properties>
<command>${vyos_op_scripts_dir}/dhcp.py clear_dhcp_server_lease --family inet6 --address $4</command>
</tagNode>
+ <tagNode name="vrf">
+ <properties>
+ <help>Clear DHCPv6 server lease for vrf</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="lease">
+ <properties>
+ <help>DHCPv6 server lease</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py clear_dhcp_server_lease --family inet6 --vrf $4 --address $6</command>
+ </tagNode>
+ </children>
+ </tagNode>
</children>
</node>
</children>
@@ -70,7 +102,7 @@
<properties>
<help>Show DHCP server leases</help>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --vrf ''</command>
<children>
<tagNode name="origin">
<properties>
@@ -79,7 +111,7 @@
<list>local remote</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --origin $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --vrf '' --origin $6</command>
</tagNode>
<tagNode name="pool">
<properties>
@@ -88,7 +120,7 @@
<path>service dhcp-server shared-network-name</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --pool $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --vrf '' --pool $6</command>
</tagNode>
<tagNode name="sort">
<properties>
@@ -97,7 +129,7 @@
<list>end hostname ip mac pool remaining start state</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --sort $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --vrf '' --sort $6</command>
</tagNode>
<tagNode name="state">
<properties>
@@ -106,7 +138,7 @@
<list>abandoned active all backup expired free released reset</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --state $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --vrf '' --state $6</command>
</tagNode>
</children>
</node>
@@ -114,7 +146,7 @@
<properties>
<help>Show DHCP server static mappings</help>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --vrf ''</command>
<children>
<tagNode name="pool">
<properties>
@@ -123,7 +155,7 @@
<path>service dhcp-server shared-network-name</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --pool $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --vrf '' --pool $6</command>
</tagNode>
<tagNode name="sort">
<properties>
@@ -132,7 +164,7 @@
<list>ip mac duid pool</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --sort $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --vrf '' --sort $6</command>
</tagNode>
</children>
</node>
@@ -140,7 +172,7 @@
<properties>
<help>Show DHCP server statistics</help>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet --vrf ''</command>
<children>
<tagNode name="pool">
<properties>
@@ -149,10 +181,107 @@
<path>service dhcp-server shared-network-name</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet --pool $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet --vrf '' --pool $6</command>
</tagNode>
</children>
</node>
+ <tagNode name="vrf">
+ <properties>
+ <help>Show DHCP (Dynamic Host Configuration Protocol) information for vrf</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <node name="leases">
+ <properties>
+ <help>Show DHCP server leases</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --vrf $5</command>
+ <children>
+ <tagNode name="origin">
+ <properties>
+ <help>Show DHCP server leases granted by local or remote DHCP server</help>
+ <completionHelp>
+ <list>local remote</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --vrf $5 --origin $8</command>
+ </tagNode>
+ <tagNode name="pool">
+ <properties>
+ <help>Show DHCP server leases for a specific pool</help>
+ <completionHelp>
+ <path>service dhcp-server shared-network-name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --vrf $5 --pool $8</command>
+ </tagNode>
+ <tagNode name="sort">
+ <properties>
+ <help>Show DHCP server leases sorted by the specified key</help>
+ <completionHelp>
+ <list>end hostname ip mac pool remaining start state</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --vrf $5 --sort $8</command>
+ </tagNode>
+ <tagNode name="state">
+ <properties>
+ <help>Show DHCP server leases with a specific state (can be multiple, comma-separated)</help>
+ <completionHelp>
+ <list>abandoned active all backup expired free released reset</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet --vrf $5 --state $8</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="static-mappings">
+ <properties>
+ <help>Show DHCP server static mappings</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --vrf $5</command>
+ <children>
+ <tagNode name="pool">
+ <properties>
+ <help>Show DHCP server static mappings for a specific pool</help>
+ <completionHelp>
+ <path>service dhcp-server shared-network-name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --vrf $5 --pool $8</command>
+ </tagNode>
+ <tagNode name="sort">
+ <properties>
+ <help>Show DHCP server static mappings sorted by the specified key</help>
+ <completionHelp>
+ <list>ip mac duid pool</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet --vrf $5 --sort $8</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="statistics">
+ <properties>
+ <help>Show DHCP server statistics</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet --vrf $5</command>
+ <children>
+ <tagNode name="pool">
+ <properties>
+ <help>Show DHCP server statistics for a specific pool</help>
+ <completionHelp>
+ <path>service dhcp-server shared-network-name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet --vrf $5 --pool $8</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
</children>
</node>
</children>
@@ -171,7 +300,7 @@
<properties>
<help>Show DHCPv6 server leases</help>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --vrf ''</command>
<children>
<tagNode name="pool">
<properties>
@@ -180,7 +309,7 @@
<path>service dhcpv6-server shared-network-name</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --pool $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --vrf '' --pool $6</command>
</tagNode>
<tagNode name="sort">
<properties>
@@ -189,7 +318,7 @@
<list>end duid ip last_communication pool remaining state type</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --sort $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --vrf '' --sort $6</command>
</tagNode>
<tagNode name="state">
<properties>
@@ -198,7 +327,7 @@
<list>abandoned active all backup expired free released reset</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --state $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --vrf '' --state $6</command>
</tagNode>
</children>
</node>
@@ -206,7 +335,7 @@
<properties>
<help>Show DHCPv6 server static mappings</help>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --vrf ''</command>
<children>
<tagNode name="pool">
<properties>
@@ -215,7 +344,7 @@
<path>service dhcp-server shared-network-name</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --pool $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --vrf '' --pool $6</command>
</tagNode>
<tagNode name="sort">
<properties>
@@ -224,7 +353,7 @@
<list>ip mac duid pool</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --sort $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --vrf '' --sort $6</command>
</tagNode>
</children>
</node>
@@ -232,7 +361,7 @@
<properties>
<help>Show DHCPv6 server statistics</help>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet6 --vrf ''</command>
<children>
<tagNode name="pool">
<properties>
@@ -241,10 +370,98 @@
<path>service dhcpv6-server shared-network-name</path>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet6 --pool $6</command>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet6 --vrf '' --pool $6</command>
</tagNode>
</children>
</node>
+ <tagNode name="vrf">
+ <properties>
+ <help>Show DHCPv6 (IPv6 Dynamic Host Configuration Protocol) information for vrf</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <node name="leases">
+ <properties>
+ <help>Show DHCPv6 server leases</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --vrf $5</command>
+ <children>
+ <tagNode name="pool">
+ <properties>
+ <help>Show DHCPv6 server leases for a specific pool</help>
+ <completionHelp>
+ <path>service dhcpv6-server shared-network-name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --vrf $5 --pool $8</command>
+ </tagNode>
+ <tagNode name="sort">
+ <properties>
+ <help>Show DHCPv6 server leases sorted by the specified key</help>
+ <completionHelp>
+ <list>end duid ip last_communication pool remaining state type</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --vrf $5 --sort $8</command>
+ </tagNode>
+ <tagNode name="state">
+ <properties>
+ <help>Show DHCPv6 server leases with a specific state (can be multiple, comma-separated)</help>
+ <completionHelp>
+ <list>abandoned active all backup expired free released reset</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_leases --family inet6 --vrf $5 --state $8</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="static-mappings">
+ <properties>
+ <help>Show DHCPv6 server static mappings</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --vrf $5</command>
+ <children>
+ <tagNode name="pool">
+ <properties>
+ <help>Show DHCPv6 server static mappings for a specific pool</help>
+ <completionHelp>
+ <path>service dhcp-server shared-network-name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --vrf $5 --pool $8</command>
+ </tagNode>
+ <tagNode name="sort">
+ <properties>
+ <help>Show DHCPv6 server static mappings sorted by the specified key</help>
+ <completionHelp>
+ <list>ip mac duid pool</list>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_static_mappings --family inet6 --vrf $5 --sort $8</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="statistics">
+ <properties>
+ <help>Show DHCPv6 server statistics</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet6 --vrf $5</command>
+ <children>
+ <tagNode name="pool">
+ <properties>
+ <help>Show DHCPv6 server statistics for a specific pool</help>
+ <completionHelp>
+ <path>service dhcpv6-server shared-network-name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/dhcp.py show_server_pool_statistics --family inet6 --vrf $5 --pool $8</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
</children>
</node>
</children>
@@ -263,6 +480,17 @@
<help>Restart DHCP server</help>
</properties>
<command>${vyos_op_scripts_dir}/restart.py restart_service --name dhcp</command>
+ <children>
+ <tagNode name="vrf">
+ <properties>
+ <help>Restart DHCP server on vrf</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/restart.py restart_service --name dhcp --vrf $5</command>
+ </tagNode>
+ </children>
</node>
<node name="relay-agent">
<properties>
@@ -282,6 +510,17 @@
<help>Restart DHCPv6 server</help>
</properties>
<command>${vyos_op_scripts_dir}/restart.py restart_service --name dhcpv6</command>
+ <children>
+ <tagNode name="vrf">
+ <properties>
+ <help>Restart DHCPv6 server on vrf</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/restart.py restart_service --name dhcpv6 --vrf $5</command>
+ </tagNode>
+ </children>
</node>
<node name="relay-agent">
<properties>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index 721460be5..86c07fb02 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -45,6 +45,14 @@
<help>Monitor last lines of DHCP server log</help>
</properties>
<command>journalctl --no-hostname --follow --boot --unit kea-dhcp4-server.service</command>
+ <children>
+ <tagNode name="vrf">
+ <properties>
+ <help>Monitor last lines of DHCP server log on specific vrf</help>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit "kea-dhcp4-server@$6.service"</command>
+ </tagNode>
+ </children>
</node>
<node name="client">
<properties>
@@ -75,6 +83,14 @@
<help>Monitor last lines of DHCPv6 server log</help>
</properties>
<command>journalctl --no-hostname --follow --boot --unit kea-dhcp6-server.service</command>
+ <children>
+ <tagNode name="vrf">
+ <properties>
+ <help>Monitor last lines of DHCPv6 server log on specific vrf</help>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit "kea-dhcp6-server@$6.service"</command>
+ </tagNode>
+ </children>
</node>
<node name="client">
<properties>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index 499e7f84b..b301f3401 100755
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -105,6 +105,14 @@
<help>Show log for DHCP server</help>
</properties>
<command>journalctl --no-hostname --boot --unit kea-dhcp4-server.service</command>
+ <children>
+ <tagNode name="vrf">
+ <properties>
+ <help>Monitor last lines of DHCP server log on specific vrf</help>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit "kea-dhcp4-server@$6.service"</command>
+ </tagNode>
+ </children>
</node>
<node name="client">
<properties>
@@ -135,6 +143,14 @@
<help>Show log for DHCPv6 server</help>
</properties>
<command>journalctl --no-hostname --boot --unit kea-dhcp6-server.service</command>
+ <children>
+ <tagNode name="vrf">
+ <properties>
+ <help>Monitor last lines of DHCPv6 server log on specific vrf</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit "kea-dhcp6-server@$6.service"</command>
+ </tagNode>
+ </children>
</node>
<node name="client">
<properties>
diff --git a/python/vyos/kea.py b/python/vyos/kea.py
index 5eecbbaad..4a8a8b4c8 100644
--- a/python/vyos/kea.py
+++ b/python/vyos/kea.py
@@ -60,7 +60,7 @@ kea6_options = {
'capwap_controller': 'capwap-ac-v6',
}
-kea_ctrl_socket = '/run/kea/dhcp{inet}-ctrl-socket'
+kea_ctrl_socket = '/run/kea/dhcp{inet}{vrf_append}-ctrl-socket'
def _format_hex_string(in_str):
@@ -403,8 +403,13 @@ def kea_parse_ddns_settings(config):
return data
-def _ctrl_socket_command(inet, command, args=None):
- path = kea_ctrl_socket.format(inet=inet)
+def _ctrl_socket_command(inet, vrf_name, command, args=None):
+ if vrf_name:
+ vrf_append = f'-{vrf_name}'
+ else:
+ vrf_append = ''
+
+ path = kea_ctrl_socket.format(inet=inet, vrf_append=vrf_append)
if not os.path.exists(path):
return None
@@ -430,8 +435,8 @@ def _ctrl_socket_command(inet, command, args=None):
return json.loads(result.decode('utf-8'))
-def kea_get_leases(inet):
- leases = _ctrl_socket_command(inet, f'lease{inet}-get-all')
+def kea_get_leases(inet, vrf_name):
+ leases = _ctrl_socket_command(inet, vrf_name, f'lease{inet}-get-all')
if not leases or 'result' not in leases or leases['result'] != 0:
return []
@@ -441,6 +446,7 @@ def kea_get_leases(inet):
def kea_add_lease(
inet,
+ vrf_name,
ip_address,
host_name=None,
mac_address=None,
@@ -466,7 +472,7 @@ def kea_add_lease(
if inet == '6' and iaid:
args['iaid'] = iaid
- result = _ctrl_socket_command(inet, f'lease{inet}-add', args)
+ result = _ctrl_socket_command(inet, vrf_name, f'lease{inet}-add', args)
if result and 'result' in result:
return result['result'] == 0
@@ -474,10 +480,10 @@ def kea_add_lease(
return False
-def kea_delete_lease(inet, ip_address):
+def kea_delete_lease(inet, ip_address, vrf_name=''):
args = {'ip-address': ip_address}
- result = _ctrl_socket_command(inet, f'lease{inet}-del', args)
+ result = _ctrl_socket_command(inet, vrf_name, f'lease{inet}-del', args)
if result and 'result' in result:
return result['result'] == 0
@@ -485,8 +491,8 @@ def kea_delete_lease(inet, ip_address):
return False
-def kea_get_active_config(inet):
- config = _ctrl_socket_command(inet, 'config-get')
+def kea_get_active_config(inet, vrf_name):
+ config = _ctrl_socket_command(inet, vrf_name, 'config-get')
if not config or 'result' not in config or config['result'] != 0:
return None
@@ -580,12 +586,12 @@ def kea_get_static_mappings(config, inet, pools=[]) -> list:
return mappings
-def kea_get_server_leases(config, inet, pools=[], state=[], origin=None) -> list:
+def kea_get_server_leases(config, inet, vrf_name, pools=[], state=[], origin=None) -> list:
"""
Get DHCP server leases from active Kea DHCPv4 or DHCPv6 configuration
:return list
"""
- leases = kea_get_leases(inet)
+ leases = kea_get_leases(inet, vrf_name)
data = []
for lease in leases:
diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py
index e421f04d2..d275ad2ea 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -1415,7 +1415,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
client = f'client{seq}'
mac = f'00:50:00:00:00:{seq:02}'
ip = inc_ip(subnet, seq)
- kea_add_lease(4, ip, host_name=client, mac_address=mac)
+ kea_add_lease(4, '', ip, host_name=client, mac_address=mac)
# 2. Verify that leases are not available in vyos-hostsd
tag_regex = re.escape(f'dhcp-server-{subnet.rsplit(".", 1)[0]}')
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index 30980f9ec..bba75f98d 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -34,6 +34,8 @@ from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.network import interface_exists
from vyos.utils.process import cmd
from vyos.utils.system import sysctl_read
+from vyos.template import inc_ip
+from vyos.utils.process import process_named_running
base_path = ['vrf']
vrfs = ['red', 'green', 'blue', 'foo-bar', 'baz_foo']
@@ -72,6 +74,42 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
for vrf in vrfs:
self.assertFalse(interface_exists(vrf))
+ def walk_path(self, obj, path):
+ current = obj
+
+ for i, key in enumerate(path):
+ if isinstance(key, str):
+ self.assertTrue(isinstance(current, dict), msg=f'Failed path: {path}')
+ self.assertTrue(key in current, msg=f'Failed path: {path}')
+ elif isinstance(key, int):
+ self.assertTrue(isinstance(current, list), msg=f'Failed path: {path}')
+ self.assertTrue(0 <= key < len(current), msg=f'Failed path: {path}')
+ else:
+ assert False, 'Invalid type'
+
+ current = current[key]
+
+ return current
+
+ def verify_config_object(self, obj, path, value):
+ base_obj = self.walk_path(obj, path)
+ self.assertTrue(isinstance(base_obj, list))
+ self.assertTrue(any(True for v in base_obj if v == value))
+
+ def verify_config_value(self, obj, path, key, value):
+ base_obj = self.walk_path(obj, path)
+ if isinstance(base_obj, list):
+ self.assertTrue(any(True for v in base_obj if key in v and v[key] == value))
+ elif isinstance(base_obj, dict):
+ self.assertTrue(key in base_obj)
+ self.assertEqual(base_obj[key], value)
+
+ def verify_kea_service_running(self, process_name):
+ tmp = cmd('tail -n 100 /var/log/messages')
+ self.assertTrue(
+ process_named_running(process_name), msg=f'Service not running, log: {tmp}'
+ )
+
def test_vrf_vni_and_table_id(self):
base_table = '1000'
table = base_table
@@ -601,5 +639,360 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.cli_delete(['nat'])
+ def test_dhcp_single_pool(self):
+ # Prepare the vrf and options
+ table = '100'
+ vrf = 'dhcp_smoke'
+ interface = 'dum8888'
+ subnet = '192.0.2.0/25'
+ router = inc_ip(subnet, 1)
+ dns_1 = inc_ip(subnet, 2)
+ dns_2 = inc_ip(subnet, 3)
+ domain_name = 'vyos.net'
+
+ # declare fiels
+ process_name = 'kea-dhcp4'
+ kea4_conf = f'/run/kea/kea-{vrf}-dhcp4.conf'
+
+ # create interface
+ cidr_mask = subnet.split('/')[-1]
+ self.cli_set(
+ ['interfaces', 'dummy', interface, 'address', f'{router}/{cidr_mask}']
+ )
+ self.cli_set(['interfaces', 'dummy', interface, 'vrf', f'{vrf}'])
+
+ # create the vrf with table
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['table', table])
+
+ # set the dhcp scope
+ base = base_path + ['name', vrf, 'service', 'dhcp-server']
+ 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 + ['listen-interface', interface])
+
+ self.cli_set(base + ['shared-network-name', shared_net_name, 'ping-check'])
+
+ pool = base + ['shared-network-name', shared_net_name, 'subnet', subnet]
+ self.cli_set(pool + ['subnet-id', '1'])
+ self.cli_set(pool + ['ignore-client-id'])
+ self.cli_set(pool + ['ping-check'])
+ # 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])
+
+ # check validate() - No DHCP address range or active static-mapping set
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ 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])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(kea4_conf)
+ obj = loads(config)
+
+ self.verify_config_value(
+ obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [interface]
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name
+ )
+ 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'], 'match-client-id', False
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400
+ )
+ self.verify_config_value(
+ obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400
+ )
+
+ # Verify ping-check
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'user-context'],
+ 'enable-ping-check',
+ True,
+ )
+
+ self.verify_config_value(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'user-context'],
+ 'enable-ping-check',
+ True,
+ )
+
+ # Verify options
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name', 'data': domain_name},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'domain-name-servers', 'data': f'{dns_1}, {dns_2}'},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'option-data'],
+ {'name': 'routers', 'data': router},
+ )
+
+ # Verify pools
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_0_start} - {range_0_stop}'},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp4', 'shared-networks', 0, 'subnet4', 0, 'pools'],
+ {'pool': f'{range_1_start} - {range_1_stop}'},
+ )
+
+ # Check for running process
+ self.verify_kea_service_running(process_name)
+
+ # perform cleanup
+ self.cli_delete(['interfaces', 'dummy', interface, 'address'])
+ self.cli_delete(['interfaces', 'dummy', interface, 'vrf'])
+ self.cli_delete(base)
+ self.cli_commit()
+
+ def test_dhcpv6_single_pool(self):
+ # Prepare the vrf and other options
+ table = '100'
+ vrf = 'dhcp_smoke'
+ subnet = '2001:db8:f00::/64'
+ dns_1 = '2001:db8::1'
+ dns_2 = '2001:db8::2'
+ domain = 'vyos.net'
+ nis_servers = ['2001:db8:ffff::1', '2001:db8:ffff::2']
+ interface = 'dum8888'
+ interface_addr = inc_ip(subnet, 1) + '/64'
+
+ # declare fiels
+ process_name = 'kea-dhcp6'
+ kea6_conf = f'/run/kea/kea-{vrf}-dhcp6.conf'
+
+ # create interface
+ self.cli_set(['interfaces', 'dummy', interface, 'address', f'{interface_addr}'])
+ self.cli_set(['interfaces', 'dummy', interface, 'vrf', f'{vrf}'])
+
+ # create the vrf with table
+ base = base_path + ['name', vrf]
+ self.cli_set(base + ['table', table])
+
+ # set the dhcp scope
+ base = base_path + ['name', vrf, 'service', 'dhcpv6-server']
+
+ shared_net_name = 'SMOKE-1'
+ search_domains = ['foo.vyos.net', 'bar.vyos.net']
+ lease_time = '1200'
+ max_lease_time = '72000'
+ min_lease_time = '600'
+ preference = '10'
+ sip_server = 'sip.vyos.net'
+ sntp_server = inc_ip(subnet, 100)
+ range_start = inc_ip(subnet, 256) # ::100
+ range_stop = inc_ip(subnet, 65535) # ::ffff
+
+ pool = base + ['shared-network-name', shared_net_name, 'subnet', subnet]
+
+ self.cli_set(base + ['preference', preference])
+ self.cli_set(pool + ['interface', interface])
+ self.cli_set(pool + ['subnet-id', '1'])
+ # we use the first subnet IP address as default gateway
+ self.cli_set(pool + ['lease-time', 'default', lease_time])
+ self.cli_set(pool + ['lease-time', 'maximum', max_lease_time])
+ self.cli_set(pool + ['lease-time', 'minimum', min_lease_time])
+ self.cli_set(pool + ['option', 'capwap-controller', dns_1])
+ self.cli_set(pool + ['option', 'name-server', dns_1])
+ self.cli_set(pool + ['option', 'name-server', dns_2])
+ self.cli_set(pool + ['option', 'name-server', dns_2])
+ self.cli_set(pool + ['option', 'nis-domain', domain])
+ self.cli_set(pool + ['option', 'nisplus-domain', domain])
+ self.cli_set(pool + ['option', 'sip-server', sip_server])
+ self.cli_set(pool + ['option', 'sntp-server', sntp_server])
+ self.cli_set(pool + ['range', '1', 'start', range_start])
+ self.cli_set(pool + ['range', '1', 'stop', range_stop])
+
+ for server in nis_servers:
+ self.cli_set(pool + ['option', 'nis-server', server])
+ self.cli_set(pool + ['option', 'nisplus-server', server])
+
+ for search_domain in search_domains:
+ self.cli_set(pool + ['option', 'domain-search', search_domain])
+
+ client_base = 1
+ for client in ['client1', 'client2', 'client3']:
+ duid = f'00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{client_base:02}'
+ self.cli_set(pool + ['static-mapping', client, 'duid', duid])
+ self.cli_set(
+ pool
+ + [
+ 'static-mapping',
+ client,
+ 'ipv6-address',
+ inc_ip(subnet, client_base),
+ ]
+ )
+ self.cli_set(
+ pool
+ + [
+ 'static-mapping',
+ client,
+ 'ipv6-prefix',
+ inc_ip(subnet, client_base << 64) + '/64',
+ ]
+ )
+ client_base += 1
+
+ # cannot have both mac-address and duid set
+ with self.assertRaises(ConfigSessionError):
+ self.cli_set(
+ pool + ['static-mapping', 'client1', 'mac', '00:50:00:00:00:11']
+ )
+ self.cli_commit()
+ self.cli_delete(pool + ['static-mapping', 'client1', 'mac'])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(kea6_conf)
+ obj = loads(config)
+
+ self.verify_config_value(
+ obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name
+ )
+ self.verify_config_value(
+ obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet
+ )
+ self.verify_config_value(
+ obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'interface', interface
+ )
+ self.verify_config_value(
+ obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'id', 1
+ )
+ self.verify_config_value(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6'],
+ 'valid-lifetime',
+ int(lease_time),
+ )
+ self.verify_config_value(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6'],
+ 'min-valid-lifetime',
+ int(min_lease_time),
+ )
+ self.verify_config_value(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6'],
+ 'max-valid-lifetime',
+ int(max_lease_time),
+ )
+
+ # Verify options
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'capwap-ac-v6', 'data': dns_1},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'dns-servers', 'data': f'{dns_1}, {dns_2}'},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'domain-search', 'data': ', '.join(search_domains)},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'nis-domain-name', 'data': domain},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'nis-servers', 'data': ', '.join(nis_servers)},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'nisp-domain-name', 'data': domain},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'nisp-servers', 'data': ', '.join(nis_servers)},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'sntp-servers', 'data': sntp_server},
+ )
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'option-data'],
+ {'name': 'sip-server-dns', 'data': sip_server},
+ )
+
+ # Verify pools
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'pools'],
+ {'pool': f'{range_start} - {range_stop}'},
+ )
+
+ client_base = 1
+ for client in ['client1', 'client2', 'client3']:
+ duid = f'00:01:00:01:12:34:56:78:aa:bb:cc:dd:ee:{client_base:02}'
+ ip = inc_ip(subnet, client_base)
+ prefix = inc_ip(subnet, client_base << 64) + '/64'
+
+ self.verify_config_object(
+ obj,
+ ['Dhcp6', 'shared-networks', 0, 'subnet6', 0, 'reservations'],
+ {
+ 'hostname': client,
+ 'duid': duid,
+ 'ip-addresses': [ip],
+ 'prefixes': [prefix],
+ },
+ )
+
+ client_base += 1
+
+ # Check for running process
+ self.verify_kea_service_running(process_name)
+
+ # perform cleanup
+ self.cli_delete(['interfaces', 'dummy', interface, 'address'])
+ self.cli_delete(['interfaces', 'dummy', interface, 'vrf'])
+ self.cli_delete(base)
+ self.cli_commit()
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py
index 99c7e6a1f..ef95d5d80 100755
--- a/src/conf_mode/service_dhcp-server.py
+++ b/src/conf_mode/service_dhcp-server.py
@@ -16,11 +16,13 @@
import os
+from sys import exit
+from sys import argv
+
from glob import glob
from ipaddress import ip_address
from ipaddress import ip_network
from netaddr import IPRange
-from sys import exit
from vyos.config import Config
from vyos.pki import wrap_certificate
@@ -41,16 +43,53 @@ from vyos import airbag
airbag.enable()
-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*'
+ctrl_socket = ''
+config_file = ''
+config_file_d2 = ''
+lease_file = ''
+lease_file_glob = ''
+
+ca_cert_file = ''
+cert_file = ''
+cert_key_file = ''
+
user_group = '_kea'
-ca_cert_file = '/run/kea/kea-failover-ca.pem'
-cert_file = '/run/kea/kea-failover.pem'
-cert_key_file = '/run/kea/kea-failover-key.pem'
+
+def _override_for_vrf(vrf_name):
+ """
+ This function is intended to override global vars when vrf is enabled
+ """
+ global ctrl_socket, config_file, config_file_d2, lease_file, lease_file_glob
+ global ca_cert_file, cert_file, cert_key_file
+
+ ctrl_socket = f'/run/kea/dhcp4-{vrf_name}-ctrl-socket'
+ config_file = f'/run/kea/kea-{vrf_name}-dhcp4.conf'
+ config_file_d2 = f'/run/kea/kea-{vrf_name}-dhcp-ddns.conf'
+ lease_file = f'/config/dhcp/dhcp4-{vrf_name}-leases.csv'
+ lease_file_glob = f'/config/dhcp/dhcp4-{vrf_name}-leases*'
+
+ ca_cert_file = f'/run/kea/kea-{vrf_name}-failover-ca.pem'
+ cert_file = f'/run/kea/kea-{vrf_name}-failover.pem'
+ cert_key_file = f'/run/kea/kea-{vrf_name}-failover-key.pem'
+
+
+def _reset_vars():
+ """
+ This function is intended to reset global vars when vrf is not enabled
+ """
+ global ctrl_socket, config_file, config_file_d2, lease_file, lease_file_glob
+ global ca_cert_file, cert_file, cert_key_file
+
+ 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*'
+
+ ca_cert_file = '/run/kea/kea-failover-ca.pem'
+ cert_file = '/run/kea/kea-failover.pem'
+ cert_key_file = '/run/kea/kea-failover-key.pem'
def dhcp_slice_range(exclude_list, range_dict):
@@ -125,7 +164,19 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- base = ['service', 'dhcp-server']
+
+ # if running in vrf, set base diffrently
+ if argv and len(argv) > 1:
+ vrf_name = argv[1]
+ base = ['vrf', 'name', vrf_name, 'service', 'dhcp-server']
+
+ # vrf is defined, override other vars aswell
+ _override_for_vrf(vrf_name)
+ else:
+ base = ['service', 'dhcp-server']
+
+ # vrf is not defined reset vars
+ _reset_vars()
if not conf.exists(base):
return None
@@ -137,6 +188,10 @@ def get_config(config=None):
with_recursive_defaults=True,
)
+ # add vrf context if present
+ if argv and len(argv) > 1:
+ dhcp['vrf_context'] = argv[1]
+
if 'shared_network_name' in dhcp:
for network, network_config in dhcp['shared_network_name'].items():
if 'subnet' in network_config:
@@ -524,7 +579,12 @@ def generate(dhcp):
def apply(dhcp):
- services = ['kea-dhcp4-server', 'kea-dhcp-ddns-server']
+ # if running in vrf, set base diffrently
+ if argv and len(argv) > 1:
+ vrf_name = argv[1]
+ services = [f'kea-dhcp4-server@{vrf_name}', f'kea-dhcp-ddns-server@{vrf_name}']
+ else:
+ services = ['kea-dhcp4-server', 'kea-dhcp-ddns-server']
if not dhcp or 'disable' in dhcp:
for service in services:
@@ -538,7 +598,7 @@ def apply(dhcp):
for service in services:
action = 'restart'
- if service == 'kea-dhcp-ddns-server' and 'dynamic_dns_update' not in dhcp:
+ if 'kea-dhcp-ddns-server' in service and 'dynamic_dns_update' not in dhcp:
action = 'stop'
call(f'systemctl {action} {service}.service')
diff --git a/src/conf_mode/service_dhcpv6-server.py b/src/conf_mode/service_dhcpv6-server.py
index 7af88007c..01f3abfe9 100755
--- a/src/conf_mode/service_dhcpv6-server.py
+++ b/src/conf_mode/service_dhcpv6-server.py
@@ -16,10 +16,12 @@
import os
+from sys import exit
+from sys import argv
+
from glob import glob
from ipaddress import ip_address
from ipaddress import ip_network
-from sys import exit
from vyos.config import Config
from vyos.template import render
@@ -32,26 +34,71 @@ from vyos.utils.dict import dict_search
from vyos.utils.network import is_subnet_connected
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
-config_file = '/run/kea/kea-dhcp6.conf'
-ctrl_socket = '/run/kea/dhcp6-ctrl-socket'
-lease_file = '/config/dhcp/dhcp6-leases.csv'
-lease_file_glob = '/config/dhcp/dhcp6-leases*'
+
+config_file = ''
+ctrl_socket = ''
+lease_file = ''
+lease_file_glob = ''
+
user_group = '_kea'
+
+def _override_for_vrf(vrf_name):
+ """
+ This function is intended to override some of the global vars
+ """
+ global ctrl_socket, config_file, lease_file, lease_file_glob
+
+ config_file = f'/run/kea/kea-{vrf_name}-dhcp6.conf'
+ ctrl_socket = f'/run/kea/dhcp6-{vrf_name}-ctrl-socket'
+ lease_file = f'/config/dhcp/dhcp6-{vrf_name}-leases.csv'
+ lease_file_glob = f'/config/dhcp/dhcp6-{vrf_name}-leases*'
+
+
+def _reset_vars():
+ """
+ This function is intended to reset global vars when vrf is not enabled
+ """
+ global ctrl_socket, config_file, lease_file, lease_file_glob
+
+ config_file = '/run/kea/kea-dhcp6.conf'
+ ctrl_socket = '/run/kea/dhcp6-ctrl-socket'
+ lease_file = '/config/dhcp/dhcp6-leases.csv'
+ lease_file_glob = '/config/dhcp/dhcp6-leases*'
+
+
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
- base = ['service', 'dhcpv6-server']
+
+ # if running in vrf, set base diffrently
+ if argv and len(argv) > 1:
+ vrf_name = argv[1]
+ base = ['vrf', 'name', vrf_name, 'service', 'dhcpv6-server']
+
+ # vrf is defined, override other vars aswell
+ _override_for_vrf(vrf_name)
+ else:
+ base = ['service', 'dhcpv6-server']
+
+ # vrf is not defined reset vars
+ _reset_vars()
if not conf.exists(base):
return None
- dhcpv6 = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
+ dhcpv6 = conf.get_config_dict(
+ base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True
+ )
+
+ # add vrf context if present
+ if argv and len(argv) > 1:
+ dhcpv6['vrf_context'] = argv[1]
+
return dhcpv6
def verify(dhcpv6):
@@ -239,8 +286,14 @@ def generate(dhcpv6):
return None
def apply(dhcpv6):
+ # if running in vrf, set base diffrently
+ if argv and len(argv) > 1:
+ vrf_name = argv[1]
+ service_name = f'kea-dhcp6-server@{vrf_name}.service'
+ else:
+ service_name = 'kea-dhcp6-server.service'
+
# bail out early - looks like removal from running config
- service_name = 'kea-dhcp6-server.service'
if not dhcpv6 or 'disable' in dhcpv6:
# DHCP server is removed in the commit
call(f'systemctl stop {service_name}')
diff --git a/src/etc/sudoers.d/vyos b/src/etc/sudoers.d/vyos
index 198b9b9aa..2dd8850c4 100644
--- a/src/etc/sudoers.d/vyos
+++ b/src/etc/sudoers.d/vyos
@@ -47,6 +47,8 @@ Cmnd_Alias DIAGNOSTICS = /bin/ip vrf exec * /bin/ping *, \
/usr/libexec/vyos/op_mode/*
Cmnd_Alias KEA_IP6_ROUTES = /sbin/ip -6 route replace *,\
/sbin/ip -6 route del *
+Cmnd_Alias KEA_DHCP = /bin/ip vrf exec * /usr/sbin/kea-dhcp*,\
+ /usr/bin/chown * /run/kea/*
%operator ALL=NOPASSWD: DATE, IPTABLES, ETHTOOL, IPFLUSH, HWINFO, \
PPPOE_CMDS, PCAPTURE, /usr/sbin/wanpipemon, \
DMIDECODE, DISK, CONNTRACK, IP6TABLES, \
@@ -62,3 +64,4 @@ Cmnd_Alias KEA_IP6_ROUTES = /sbin/ip -6 route replace *,\
%sudo ALL=NOPASSWD: /usr/bin/mokutil
_kea ALL=NOPASSWD: KEA_IP6_ROUTES
+_kea ALL=NOPASSWD: KEA_DHCP
diff --git a/src/etc/systemd/system/kea-dhcp-ddns-server@.service b/src/etc/systemd/system/kea-dhcp-ddns-server@.service
new file mode 100644
index 000000000..b6360e573
--- /dev/null
+++ b/src/etc/systemd/system/kea-dhcp-ddns-server@.service
@@ -0,0 +1,22 @@
+[Unit]
+Description=Kea DDNS Service
+Documentation=man:kea-dhcp-ddns(8)
+Wants=network-online.target
+After=vyos-router.service
+
+[Service]
+User=_kea
+AmbientCapabilities=CAP_NET_BIND_SERVICE
+Environment="KEA_LOCKFILE_DIR=/run/lock/kea"
+ConfigurationDirectory=kea
+RuntimeDirectory=kea lock/kea
+RuntimeDirectoryPreserve=yes
+LogsDirectory=kea
+LogsDirectoryMode=0750
+StateDirectory=kea
+ExecStart=sudo /bin/ip vrf exec %i /usr/sbin/kea-dhcp-ddns -c /run/kea/kea-%i-dhcp-ddns.conf
+ExecStartPost=/bin/sh -c 'sleep 10 && sudo /usr/bin/chown _kea:_kea /run/kea/kea-%i-dhcp-ddns* && sudo /usr/bin/chown _kea:_kea /run/kea/logger_lockfile'
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/etc/systemd/system/kea-dhcp4-server@.service b/src/etc/systemd/system/kea-dhcp4-server@.service
new file mode 100644
index 000000000..f63364275
--- /dev/null
+++ b/src/etc/systemd/system/kea-dhcp4-server@.service
@@ -0,0 +1,22 @@
+[Unit]
+Description=Kea IPv4 DHCP daemon
+Documentation=man:kea-dhcp4(8)
+Wants=network-online.target
+After=vyos-router.service
+
+[Service]
+User=_kea
+AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW
+Environment="KEA_LOCKFILE_DIR=/run/lock/kea"
+ConfigurationDirectory=kea
+RuntimeDirectory=kea lock/kea
+RuntimeDirectoryPreserve=yes
+LogsDirectory=kea
+LogsDirectoryMode=0750
+StateDirectory=kea
+ExecStart=sudo /bin/ip vrf exec %i /usr/sbin/kea-dhcp4 -c /run/kea/kea-%i-dhcp4.conf
+ExecStartPost=/bin/sh -c 'sleep 10 && sudo /usr/bin/chown _kea:_kea /run/kea/dhcp4-%i-ctrl* && sudo /usr/bin/chown _kea:_kea /run/kea/kea-%i-dhcp4* && sudo /usr/bin/chown _kea:_kea /run/kea/logger_lockfile'
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/etc/systemd/system/kea-dhcp6-server@.service b/src/etc/systemd/system/kea-dhcp6-server@.service
new file mode 100644
index 000000000..1d2330f74
--- /dev/null
+++ b/src/etc/systemd/system/kea-dhcp6-server@.service
@@ -0,0 +1,22 @@
+[Unit]
+Description=Kea IPv6 DHCP daemon
+Documentation=man:kea-dhcp6(8)
+Wants=network-online.target
+After=vyos-router.service
+
+[Service]
+User=_kea
+AmbientCapabilities=CAP_NET_BIND_SERVICE
+Environment="KEA_LOCKFILE_DIR=/run/lock/kea"
+ConfigurationDirectory=kea
+RuntimeDirectory=kea lock/kea
+RuntimeDirectoryPreserve=yes
+LogsDirectory=kea
+LogsDirectoryMode=0750
+StateDirectory=kea
+ExecStart=sudo /bin/ip vrf exec %i /usr/sbin/kea-dhcp6 -c /run/kea/kea-%i-dhcp6.conf
+ExecStartPost=/bin/sh -c 'sleep 10 && sudo /usr/bin/chown _kea:_kea /run/kea/dhcp6-%i-ctrl* && sudo /usr/bin/chown _kea:_kea /run/kea/kea-%i-dhcp6* && sudo /usr/bin/chown _kea:_kea /run/kea/logger_lockfile'
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py
index 725bfc75b..f4b7ac582 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -83,12 +83,12 @@ ArgOrigin = typing.Literal['local', 'remote']
def _get_raw_server_leases(
- config, family='inet', pool=None, sorted=None, state=[], origin=None
+ config, family='inet', vrf='', pool=None, sorted=None, state=[], origin=None
) -> list:
inet_suffix = '6' if family == 'inet6' else '4'
pools = [pool] if pool else kea_get_dhcp_pools(config, inet_suffix)
- mappings = kea_get_server_leases(config, inet_suffix, pools, state, origin)
+ mappings = kea_get_server_leases(config, inet_suffix, vrf, pools, state, origin)
if sorted:
if sorted == 'ip':
@@ -166,9 +166,13 @@ def _get_formatted_server_leases(raw_data, family='inet'):
return output
-def _get_pool_size(pool, family='inet'):
+def _get_pool_size(pool, family='inet', vrf=''):
v = 'v6' if family == 'inet6' else ''
- base = f'service dhcp{v}-server shared-network-name {pool}'
+ # if vrf is set get the correct base for config
+ if vrf:
+ base = f'vrf name {vrf} service dhcp{v}-server shared-network-name {pool}'
+ else:
+ base = f'service dhcp{v}-server shared-network-name {pool}'
size = 0
subnets = config.list_nodes(f'{base} subnet')
for subnet in subnets:
@@ -185,14 +189,14 @@ def _get_pool_size(pool, family='inet'):
return size
-def _get_raw_server_pool_statistics(config, family='inet', pool=None):
+def _get_raw_server_pool_statistics(config, family='inet', vrf='', pool=None):
inet_suffix = '6' if family == 'inet6' else '4'
pools = [pool] if pool else kea_get_dhcp_pools(config, inet_suffix)
stats = []
for p in pools:
- size = _get_pool_size(family=family, pool=p)
- leases = len(_get_raw_server_leases(config, family=family, pool=p))
+ size = _get_pool_size(family=family, vrf=vrf, pool=p)
+ leases = len(_get_raw_server_leases(config, family=family, vrf=vrf, pool=p))
use_percentage = round(leases / size * 100) if size != 0 else 0
pool_stats = {
'pool': p,
@@ -269,11 +273,20 @@ def _verify_server(func):
def _wrapper(*args, **kwargs):
config = ConfigTreeQuery()
family = kwargs.get('family')
+ vrf = kwargs.get('vrf')
v = 'v6' if family == 'inet6' else ''
- unconf_message = f'DHCP{v} server is not configured'
+
# Check if config does not exist
- if not config.exists(f'service dhcp{v}-server'):
- raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ if vrf:
+ unconf_message = f'DHCP{v} server is not configured for VRF {vrf}'
+ if not config.exists(f'vrf name {vrf} service dhcp{v}-server'):
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ else:
+ unconf_message = f'DHCP{v} server is not configured'
+ if not config.exists(f'service dhcp{v}-server'):
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+
+ # return
return func(*args, **kwargs)
return _wrapper
@@ -303,25 +316,42 @@ def _verify_client(func):
@_verify_server
def show_server_pool_statistics(
- raw: bool, family: ArgFamily, pool: typing.Optional[str]
+ raw: bool, family: ArgFamily, vrf: str, pool: typing.Optional[str]
):
v = 'v6' if family == 'inet6' else ''
inet_suffix = '6' if family == 'inet6' else '4'
- if not is_systemd_service_running(f'kea-dhcp{inet_suffix}-server.service'):
+ if vrf:
+ service = f'kea-dhcp{inet_suffix}-server@{vrf}.service'
+ else:
+ service = f'kea-dhcp{inet_suffix}-server.service'
+
+ if not is_systemd_service_running(service):
Warning(stale_warn_msg)
try:
- active_config = kea_get_active_config(inet_suffix)
+ active_config = kea_get_active_config(inet_suffix, vrf)
except Exception:
- raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration')
+ if vrf:
+ raise vyos.opmode.DataUnavailable(
+ f'Cannot fetch DHCP server configuration for VRF {vrf}'
+ )
+ else:
+ raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration')
active_pools = kea_get_dhcp_pools(active_config, inet_suffix)
if pool and active_pools and pool not in active_pools:
- raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!')
+ if vrf:
+ raise vyos.opmode.IncorrectValue(
+ f'DHCP{v} pool "{pool}" does not exist for VRF {vrf}!'
+ )
+ else:
+ raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!')
- pool_data = _get_raw_server_pool_statistics(active_config, family=family, pool=pool)
+ pool_data = _get_raw_server_pool_statistics(
+ active_config, family=family, vrf=vrf, pool=pool
+ )
if raw:
return pool_data
else:
@@ -332,6 +362,7 @@ def show_server_pool_statistics(
def show_server_leases(
raw: bool,
family: ArgFamily,
+ vrf: str,
pool: typing.Optional[str],
sorted: typing.Optional[str],
state: typing.Optional[ArgState],
@@ -340,18 +371,33 @@ def show_server_leases(
v = 'v6' if family == 'inet6' else ''
inet_suffix = '6' if family == 'inet6' else '4'
- if not is_systemd_service_running(f'kea-dhcp{inet_suffix}-server.service'):
+ if vrf:
+ service = f'kea-dhcp{inet_suffix}-server@{vrf}.service'
+ else:
+ service = f'kea-dhcp{inet_suffix}-server.service'
+
+ if not is_systemd_service_running(service):
Warning(stale_warn_msg)
try:
- active_config = kea_get_active_config(inet_suffix)
+ active_config = kea_get_active_config(inet_suffix, vrf)
except Exception:
- raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration')
+ if vrf:
+ raise vyos.opmode.DataUnavailable(
+ f'Cannot fetch DHCP server configuration for VRF {vrf}'
+ )
+ else:
+ raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration')
active_pools = kea_get_dhcp_pools(active_config, inet_suffix)
if pool and active_pools and pool not in active_pools:
- raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!')
+ if vrf:
+ raise vyos.opmode.IncorrectValue(
+ f'DHCP{v} pool "{pool}" does not exist for VRF {vrf}!'
+ )
+ else:
+ raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!')
sort_valid = sort_valid_inet6 if family == 'inet6' else sort_valid_inet
if sorted and sorted not in sort_valid:
@@ -363,6 +409,7 @@ def show_server_leases(
lease_data = _get_raw_server_leases(
config=active_config,
family=family,
+ vrf=vrf,
pool=pool,
sorted=sorted,
state=state,
@@ -378,24 +425,40 @@ def show_server_leases(
def show_server_static_mappings(
raw: bool,
family: ArgFamily,
+ vrf: str,
pool: typing.Optional[str],
sorted: typing.Optional[str],
):
v = 'v6' if family == 'inet6' else ''
inet_suffix = '6' if family == 'inet6' else '4'
- if not is_systemd_service_running(f'kea-dhcp{inet_suffix}-server.service'):
+ if vrf:
+ service = f'kea-dhcp{inet_suffix}-server@{vrf}.service'
+ else:
+ service = f'kea-dhcp{inet_suffix}-server.service'
+
+ if not is_systemd_service_running(service):
Warning(stale_warn_msg)
try:
- active_config = kea_get_active_config(inet_suffix)
+ active_config = kea_get_active_config(inet_suffix, vrf)
except Exception:
- raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration')
+ if vrf:
+ raise vyos.opmode.DataUnavailable(
+ f'Cannot fetch DHCP server configuration for VRF {vrf}'
+ )
+ else:
+ raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration')
active_pools = kea_get_dhcp_pools(active_config, inet_suffix)
if pool and active_pools and pool not in active_pools:
- raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!')
+ if vrf:
+ raise vyos.opmode.IncorrectValue(
+ f'DHCP{v} pool "{pool}" does not exist for VRF {vrf}!'
+ )
+ else:
+ raise vyos.opmode.IncorrectValue(f'DHCP{v} pool "{pool}" does not exist!')
if sorted and sorted not in mapping_sort_valid:
raise vyos.opmode.IncorrectValue(f'DHCP{v} sort "{sorted}" is invalid!')
@@ -409,13 +472,13 @@ def show_server_static_mappings(
return _get_formatted_server_static_mappings(static_mappings)
-def _lease_valid(inet, address):
- leases = kea_get_leases(inet)
+def _lease_valid(inet, vrf, address):
+ leases = kea_get_leases(inet, vrf)
return any(lease['ip-address'] == address for lease in leases)
@_verify_server
-def clear_dhcp_server_lease(family: ArgFamily, address: str):
+def clear_dhcp_server_lease(family: ArgFamily, vrf: str, address: str):
v = 'v6' if family == 'inet6' else ''
inet = '6' if family == 'inet6' else '4'
@@ -423,7 +486,7 @@ def clear_dhcp_server_lease(family: ArgFamily, address: str):
print(f'Lease not found on DHCP{v} server')
return None
- if not kea_delete_lease(inet, address):
+ if not kea_delete_lease(inet, vrf, address):
print(f'Failed to clear lease for "{address}"')
return None
diff --git a/src/system/on-dhcp-event.sh b/src/system/on-dhcp-event.sh
index 47c276270..556d7dbcb 100755
--- a/src/system/on-dhcp-event.sh
+++ b/src/system/on-dhcp-event.sh
@@ -30,7 +30,7 @@ get_subnet_domain_name () {
from vyos.kea import kea_get_active_config
from vyos.utils.dict import dict_search_args
-config = kea_get_active_config('4')
+config = kea_get_active_config('4', '')
shared_networks = dict_search_args(config, 'arguments', f'Dhcp4', 'shared-networks')
found = False
diff --git a/src/system/sync-dhcp-lease-to-hosts.py b/src/system/sync-dhcp-lease-to-hosts.py
index 5c8b18faf..8baa6992a 100755
--- a/src/system/sync-dhcp-lease-to-hosts.py
+++ b/src/system/sync-dhcp-lease-to-hosts.py
@@ -33,17 +33,17 @@ logs_handler = logging.StreamHandler()
logger.addHandler(logs_handler)
-def _get_all_server_leases(inet_suffix='4') -> list:
+def _get_all_server_leases(inet_suffix='4', vrf='') -> list:
mappings = []
try:
- active_config = kea_get_active_config(inet_suffix)
+ active_config = kea_get_active_config(inet_suffix, vrf)
except Exception:
raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server configuration')
try:
pools = kea_get_dhcp_pools(active_config, inet_suffix)
mappings = kea_get_server_leases(
- active_config, inet_suffix, pools, state=[], origin=None
+ active_config, inet_suffix, vrf, pools, state=[], origin=None
)
except Exception:
raise vyos.opmode.DataUnavailable('Cannot fetch DHCP server leases')