summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/dhcp-client/override.conf.j22
-rw-r--r--data/templates/frr/bgpd.frr.j23
-rw-r--r--data/templates/openvpn/server.conf.j26
-rw-r--r--data/vyos-firewall-init.conf4
-rw-r--r--debian/vyos-1x.links1
-rw-r--r--debian/vyos-1x.preinst2
-rw-r--r--interface-definitions/include/bgp/afi-label.xml.i13
-rw-r--r--interface-definitions/include/interface/macsec-key.xml.i15
-rw-r--r--interface-definitions/include/interface/per-client-thread.xml.i8
-rw-r--r--interface-definitions/include/radius-server-key.xml.i8
-rw-r--r--interface-definitions/interfaces-macsec.xml.in22
-rw-r--r--interface-definitions/interfaces-wireguard.xml.in2
-rw-r--r--interface-definitions/interfaces-wireless.xml.in1
-rw-r--r--interface-definitions/system-login.xml.in7
-rw-r--r--op-mode-definitions/monitor-log.xml.in6
-rw-r--r--op-mode-definitions/show-log.xml.in6
-rw-r--r--python/vyos/ifconfig/interface.py39
-rw-r--r--python/vyos/ifconfig/macsec.py22
-rw-r--r--python/vyos/ifconfig/wireguard.py4
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_macsec.py72
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireguard.py37
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py2
-rwxr-xr-xsrc/conf_mode/dhcp_server.py4
-rwxr-xr-xsrc/conf_mode/firewall.py35
-rwxr-xr-xsrc/conf_mode/interfaces-macsec.py74
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py26
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py6
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py2
-rwxr-xr-xsrc/etc/netplug/linkdown.d/dhclient65
-rwxr-xr-xsrc/etc/netplug/linkup.d/dhclient64
-rwxr-xr-xsrc/etc/netplug/linkup.d/vyos-python-helper4
-rwxr-xr-xsrc/etc/netplug/netplug41
-rw-r--r--src/etc/netplug/netplugd.conf3
-rwxr-xr-xsrc/etc/netplug/vyos-netplug-dhcp-client62
-rwxr-xr-xsrc/op_mode/pki.py3
35 files changed, 461 insertions, 210 deletions
diff --git a/data/templates/dhcp-client/override.conf.j2 b/data/templates/dhcp-client/override.conf.j2
index 03fd71bf1..d09320270 100644
--- a/data/templates/dhcp-client/override.conf.j2
+++ b/data/templates/dhcp-client/override.conf.j2
@@ -10,6 +10,6 @@ ConditionPathExists={{ isc_dhclient_dir }}/dhclient_%i.conf
ExecStart=
ExecStart={{ vrf_command }}/sbin/dhclient -4 {{ dhclient_options }} {{ ifname }}
ExecStop=
-ExecStop=/sbin/dhclient -4 -r {{ dhclient_options }} {{ ifname }}
+ExecStop={{ vrf_command }}/sbin/dhclient -4 -r {{ dhclient_options }} {{ ifname }}
WorkingDirectory={{ isc_dhclient_dir }}
PIDFile={{ isc_dhclient_dir }}/dhclient_%i.pid
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 4535758da..7fa974254 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -349,6 +349,9 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% if afi_config.label.vpn.export is vyos_defined %}
label vpn export {{ afi_config.label.vpn.export }}
{% endif %}
+{% if afi_config.label.vpn.allocation_mode.per_nexthop is vyos_defined %}
+ label vpn export allocation-mode per-nexthop
+{% endif %}
{% if afi_config.local_install is vyos_defined %}
{% for interface in afi_config.local_install.interface %}
local-install {{ interface }}
diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2
index a9bd45370..f76fbbe79 100644
--- a/data/templates/openvpn/server.conf.j2
+++ b/data/templates/openvpn/server.conf.j2
@@ -185,7 +185,7 @@ tls-version-min {{ tls.tls_version_min }}
{% endif %}
{% if tls.dh_params is vyos_defined %}
dh /run/openvpn/{{ ifname }}_dh.pem
-{% elif mode is vyos_defined('server') and tls.private_key is vyos_defined %}
+{% else %}
dh none
{% endif %}
{% if tls.auth_key is vyos_defined %}
@@ -201,9 +201,9 @@ tls-client
tls-server
{% endif %}
-{% if peer_fingerprint is vyos_defined %}
+{% if tls.peer_fingerprint is vyos_defined %}
<peer-fingerprint>
-{% for fp in peer_fingerprint %}
+{% for fp in tls.peer_fingerprint %}
{{ fp }}
{% endfor %}
</peer-fingerprint>
diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf
index 11a5bc7bf..36d92fe93 100644
--- a/data/vyos-firewall-init.conf
+++ b/data/vyos-firewall-init.conf
@@ -20,7 +20,7 @@ table raw {
}
chain PREROUTING {
- type filter hook prerouting priority -200; policy accept;
+ type filter hook prerouting priority -300; policy accept;
counter jump VYOS_CT_IGNORE
counter jump VYOS_CT_TIMEOUT
counter jump VYOS_CT_PREROUTING_HOOK
@@ -29,7 +29,7 @@ table raw {
}
chain OUTPUT {
- type filter hook output priority -200; policy accept;
+ type filter hook output priority -300; policy accept;
counter jump VYOS_CT_IGNORE
counter jump VYOS_CT_TIMEOUT
counter jump VYOS_CT_OUTPUT_HOOK
diff --git a/debian/vyos-1x.links b/debian/vyos-1x.links
new file mode 100644
index 000000000..0e2d1b841
--- /dev/null
+++ b/debian/vyos-1x.links
@@ -0,0 +1 @@
+/etc/netplug/linkup.d/vyos-python-helper /etc/netplug/linkdown.d/vyos-python-helper
diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst
index 92037a915..e355ffa84 100644
--- a/debian/vyos-1x.preinst
+++ b/debian/vyos-1x.preinst
@@ -8,3 +8,5 @@ dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.conf
dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.bashrc
dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.profile
dpkg-divert --package vyos-1x --add --no-rename /etc/sysctl.d/80-vpp.conf
+dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplugd.conf
+dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplug
diff --git a/interface-definitions/include/bgp/afi-label.xml.i b/interface-definitions/include/bgp/afi-label.xml.i
index 9535d19e8..2c5eed18b 100644
--- a/interface-definitions/include/bgp/afi-label.xml.i
+++ b/interface-definitions/include/bgp/afi-label.xml.i
@@ -29,6 +29,19 @@
</constraint>
</properties>
</leafNode>
+ <node name="allocation-mode">
+ <properties>
+ <help>Label allocation mode</help>
+ </properties>
+ <children>
+ <leafNode name="per-nexthop">
+ <properties>
+ <help>Allocate a label per connected next-hop in the VRF</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/interface-definitions/include/interface/macsec-key.xml.i b/interface-definitions/include/interface/macsec-key.xml.i
new file mode 100644
index 000000000..5a857a612
--- /dev/null
+++ b/interface-definitions/include/interface/macsec-key.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from interface/macsec-key.xml.i -->
+<leafNode name="key">
+ <properties>
+ <help>MACsec static key</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>16-byte (128-bit) hex-string (32 hex-digits) for gcm-aes-128 or 32-byte (256-bit) hex-string (64 hex-digits) for gcm-aes-256</description>
+ </valueHelp>
+ <constraint>
+ <regex>[A-Fa-f0-9]{32}</regex>
+ <regex>[A-Fa-f0-9]{64}</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/per-client-thread.xml.i b/interface-definitions/include/interface/per-client-thread.xml.i
new file mode 100644
index 000000000..2fd19b5ce
--- /dev/null
+++ b/interface-definitions/include/interface/per-client-thread.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from interface/per-client-thread.xml.i -->
+<leafNode name="per-client-thread">
+ <properties>
+ <help>Process traffic from each client in a dedicated thread</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/radius-server-key.xml.i b/interface-definitions/include/radius-server-key.xml.i
index c6301646b..dd5cdb0c6 100644
--- a/interface-definitions/include/radius-server-key.xml.i
+++ b/interface-definitions/include/radius-server-key.xml.i
@@ -2,6 +2,14 @@
<leafNode name="key">
<properties>
<help>Shared secret key</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Password string (key)</description>
+ </valueHelp>
+ <constraint>
+ <regex>[[:ascii:]]{1,128}</regex>
+ </constraint>
+ <constraintErrorMessage>Password must be less then 128 characters</constraintErrorMessage>
</properties>
</leafNode>
<!-- include end -->
diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in
index 6bc28e44b..766b0bede 100644
--- a/interface-definitions/interfaces-macsec.xml.in
+++ b/interface-definitions/interfaces-macsec.xml.in
@@ -52,6 +52,28 @@
<valueless/>
</properties>
</leafNode>
+ <node name="static">
+ <properties>
+ <help>Use static keys for MACsec [static Secure Authentication Key (SAK) mode]</help>
+ </properties>
+ <children>
+ #include <include/interface/macsec-key.xml.i>
+ <tagNode name="peer">
+ <properties>
+ <help>MACsec peer name</help>
+ <constraint>
+ <regex>[^ ]{1,100}</regex>
+ </constraint>
+ <constraintErrorMessage>MACsec peer name exceeds limit of 100 characters</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ #include <include/interface/mac.xml.i>
+ #include <include/interface/macsec-key.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
<node name="mka">
<properties>
<help>MACsec Key Agreement protocol (MKA)</help>
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index dd1e8e511..3c79cef28 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -59,6 +59,7 @@
</properties>
<children>
#include <include/generic-disable-node.xml.i>
+ #include <include/generic-description.xml.i>
<leafNode name="public-key">
<properties>
<help>base64 encoded public key</help>
@@ -119,6 +120,7 @@
</children>
</tagNode>
#include <include/interface/redirect.xml.i>
+ #include <include/interface/per-client-thread.xml.i>
#include <include/interface/vrf.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index a9538d577..421d46c6e 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -778,6 +778,7 @@
</properties>
<defaultValue>monitor</defaultValue>
</leafNode>
+ #include <include/interface/per-client-thread.xml.i>
#include <include/interface/redirect.xml.i>
#include <include/interface/vif.xml.i>
#include <include/interface/vif-s.xml.i>
diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in
index d772c7821..71db8b1d6 100644
--- a/interface-definitions/system-login.xml.in
+++ b/interface-definitions/system-login.xml.in
@@ -184,6 +184,13 @@
<leafNode name="home-directory">
<properties>
<help>Home directory</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Path to home directory</description>
+ </valueHelp>
+ <constraint>
+ <regex>\/$|(\/[a-zA-Z_0-9-.]+)+</regex>
+ </constraint>
</properties>
</leafNode>
</children>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index ee52a7eb8..52b5b85d4 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -36,6 +36,12 @@
</properties>
<command>journalctl --no-hostname --follow --boot --unit conntrackd.service</command>
</leafNode>
+ <leafNode name="console-server">
+ <properties>
+ <help>Monitor last lines of console server log</help>
+ </properties>
+ <command>journalctl --no-hostname --follow --boot --unit conserver-server.service</command>
+ </leafNode>
<node name="dhcp">
<properties>
<help>Monitor last lines of Dynamic Host Control Protocol log</help>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index 925a780ac..747622db6 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -50,6 +50,12 @@
</properties>
<command>journalctl --no-hostname --boot --unit conntrackd.service</command>
</leafNode>
+ <leafNode name="console-server">
+ <properties>
+ <help>Show log for console server</help>
+ </properties>
+ <command>journalctl --no-hostname --boot --unit conserver-server.service</command>
+ </leafNode>
<node name="ids">
<properties>
<help>Show log for for Intrusion Detection System</help>
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index cc6149428..ddac387e7 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -224,6 +224,10 @@ class Interface(Control):
'validate': lambda link: assert_range(link,0,3),
'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter',
},
+ 'per_client_thread': {
+ 'validate': assert_boolean,
+ 'location': '/sys/class/net/{ifname}/threaded',
+ },
}
_sysfs_get = {
@@ -275,6 +279,10 @@ class Interface(Control):
'link_detect': {
'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter',
},
+ 'per_client_thread': {
+ 'validate': assert_boolean,
+ 'location': '/sys/class/net/{ifname}/threaded',
+ },
}
@classmethod
@@ -1386,6 +1394,30 @@ class Interface(Control):
f'egress redirect dev {target_if}')
if err: print('tc filter add for redirect failed')
+ def set_per_client_thread(self, enable):
+ """
+ Per-device control to enable/disable the threaded mode for all the napi
+ instances of the given network device, without the need for a device up/down.
+
+ User sets it to 1 or 0 to enable or disable threaded mode.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('wg1').set_per_client_thread(1)
+ """
+ # In the case of a "virtual" interface like wireguard, the sysfs
+ # node is only created once there is a peer configured. We can now
+ # add a verify() code-path for this or make this dynamic without
+ # nagging the user
+ tmp = self._sysfs_get['per_client_thread']['location']
+ if not os.path.exists(tmp):
+ return None
+
+ tmp = self.get_interface('per_client_thread')
+ if tmp == enable:
+ return None
+ self.set_interface('per_client_thread', enable)
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -1601,6 +1633,11 @@ class Interface(Control):
# configure interface mirror or redirection target
self.set_mirror_redirect()
+ # enable/disable NAPI threading mode
+ tmp = dict_search('per_client_thread', config)
+ value = '1' if (tmp != None) else '0'
+ self.set_per_client_thread(value)
+
# Enable/Disable of an interface must always be done at the end of the
# derived class to make use of the ref-counting set_admin_state()
# function. We will only enable the interface if 'up' was called as
diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py
index 1a78d18d8..9329c5ee7 100644
--- a/python/vyos/ifconfig/macsec.py
+++ b/python/vyos/ifconfig/macsec.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -41,10 +41,30 @@ class MACsecIf(Interface):
Create MACsec interface in OS kernel. Interface is administrative
down by default.
"""
+
# create tunnel interface
cmd = 'ip link add link {source_interface} {ifname} type {type}'.format(**self.config)
cmd += f' cipher {self.config["security"]["cipher"]}'
self._cmd(cmd)
+ # Check if using static keys
+ if 'static' in self.config["security"]:
+ # Set static TX key
+ cmd = 'ip macsec add {ifname} tx sa 0 pn 1 on key 00'.format(**self.config)
+ cmd += f' {self.config["security"]["static"]["key"]}'
+ self._cmd(cmd)
+
+ for peer, peer_config in self.config["security"]["static"]["peer"].items():
+ if 'disable' in peer_config:
+ continue
+
+ # Create the address
+ cmd = 'ip macsec add {ifname} rx port 1 address'.format(**self.config)
+ cmd += f' {peer_config["mac"]}'
+ self._cmd(cmd)
+ # Add the rx-key to the address
+ cmd += f' sa 0 pn 1 on key 01 {peer_config["key"]}'
+ self._cmd(cmd)
+
# interface is always A/D down. It needs to be enabled explicitly
self.set_admin_state('down')
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index fe5e9c519..4aac103ec 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -25,6 +25,7 @@ from hurry.filesize import alternative
from vyos.ifconfig import Interface
from vyos.ifconfig import Operational
from vyos.template import is_ipv6
+from vyos.base import Warning
class WireGuardOperational(Operational):
def _dump(self):
@@ -184,7 +185,6 @@ class WireGuardIf(Interface):
base_cmd += f' private-key {tmp_file.name}'
base_cmd = base_cmd.format(**config)
-
if 'peer' in config:
for peer, peer_config in config['peer'].items():
# T4702: No need to configure this peer when it was explicitly
diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py
index b32a6f524..30d1ad659 100755
--- a/smoketest/scripts/cli/test_interfaces_macsec.py
+++ b/smoketest/scripts/cli/test_interfaces_macsec.py
@@ -208,5 +208,77 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase):
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ def test_macsec_static_keys(self):
+ src_interface = 'eth0'
+ interface = 'macsec5'
+ cipher1 = 'gcm-aes-128'
+ cipher2 = 'gcm-aes-256'
+ tx_key_1 = '71a82a48eddfa12c08a19792ca20c4bb'
+ tx_key_2 = 'dd487b2958e855ea35a5d43a5ecb3dcfbe7889ffcb877770252feb13b734478d'
+ rx_key_1 = '0022d00f57e75241a230cdf7118dfcc5'
+ rx_key_2 = 'b7d6d7ad075e02323fdeb845217b884d3f93ff36b2cdaf6b07eeb189b877245f'
+ peer_mac = '00:11:22:33:44:55'
+ self.cli_set(self._base_path + [interface])
+
+ # Encrypt link
+ self.cli_set(self._base_path + [interface, 'security', 'encrypt'])
+
+ # check validate() - source interface is mandatory
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'source-interface', src_interface])
+
+ # check validate() - cipher is mandatory
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher1])
+
+ # check validate() - only static or mka config is allowed
+ self.cli_set(self._base_path + [interface, 'security', 'static'])
+ self.cli_set(self._base_path + [interface, 'security', 'mka', 'cak', tx_key_1])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(self._base_path + [interface, 'security', 'mka'])
+
+ # check validate() - tx-key required
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ # check validate() - tx-key length must match cipher
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_2])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_1])
+
+ # check validate() - at least one peer must be defined
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ # check validate() - enabled peer must have both rx-key and MAC defined
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac', peer_mac])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac'])
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'key', rx_key_1])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac', peer_mac])
+
+ # check validate() - peer rx-key length must match cipher
+ self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher2])
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_2])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'key', rx_key_2])
+
+ # final commit and verify
+ self.cli_commit()
+ self.assertIn(interface, interfaces())
+ self.assertEqual(cipher2, get_cipher(interface))
+ self.assertTrue(os.path.isdir(f'/sys/class/net/{interface}'))
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py
index f84ce159d..48c7cb6a1 100755
--- a/smoketest/scripts/cli/test_interfaces_wireguard.py
+++ b/smoketest/scripts/cli/test_interfaces_wireguard.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -19,6 +19,7 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
+from vyos.utils.file import read_file
base_path = ['interfaces', 'wireguard']
@@ -35,7 +36,7 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
- def test_wireguard_peer(self):
+ def test_01_wireguard_peer(self):
# Create WireGuard interfaces with associated peers
for intf in self._interfaces:
peer = 'foo-' + intf
@@ -62,7 +63,7 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.assertTrue(os.path.isdir(f'/sys/class/net/{intf}'))
- def test_wireguard_add_remove_peer(self):
+ def test_02_wireguard_add_remove_peer(self):
# T2939: Create WireGuard interfaces with associated peers.
# Remove one of the configured peers.
# T4774: Test prevention of duplicate peer public keys
@@ -100,10 +101,9 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path + [interface, 'peer', 'PEER01'])
self.cli_commit()
- def test_wireguard_same_public_key(self):
- # T2939: Create WireGuard interfaces with associated peers.
- # Remove one of the configured peers.
- # T4774: Test prevention of duplicate peer public keys
+ def test_03_wireguard_same_public_key(self):
+ # T5413: Test prevention of equality interface public key and peer's
+ # public key
interface = 'wg0'
port = '12345'
privkey = 'OOjcXGfgQlAuM6q8Z9aAYduCua7pxf7UKYvIqoUPoGQ='
@@ -129,5 +129,28 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface}'))
+ def test_04_wireguard_threaded(self):
+ # T5409: Test adding threaded option on interface.
+ # Test prevention for adding threaded
+ # if no enabled peer is configured.
+ interface = 'wg0'
+ port = '12345'
+ privkey = 'OOjcXGfgQlAuM6q8Z9aAYduCua7pxf7UKYvIqoUPoGQ='
+ pubkey = 'ebFx/1G0ti8tvuZd94sEIosAZZIznX+dBAKG/8DFm0I='
+
+ self.cli_set(base_path + [interface, 'address', '172.16.0.1/24'])
+ self.cli_set(base_path + [interface, 'private-key', privkey])
+
+ self.cli_set(base_path + [interface, 'peer', 'PEER01', 'port', port])
+ self.cli_set(base_path + [interface, 'peer', 'PEER01', 'public-key', pubkey])
+ self.cli_set(base_path + [interface, 'peer', 'PEER01', 'allowed-ips', '10.205.212.10/32'])
+ self.cli_set(base_path + [interface, 'peer', 'PEER01', 'address', '192.0.2.1'])
+ self.cli_set(base_path + [interface, 'per-client-thread'])
+
+ # Commit peers
+ self.cli_commit()
+ tmp = read_file(f'/sys/class/net/{interface}/threaded')
+ self.assertTrue(tmp, "1")
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 5b247a413..77952d8d9 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -868,6 +868,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['address-family', afi, 'export', 'vpn'])
self.cli_set(base_path + ['address-family', afi, 'import', 'vpn'])
self.cli_set(base_path + ['address-family', afi, 'label', 'vpn', 'export', label])
+ self.cli_set(base_path + ['address-family', afi, 'label', 'vpn', 'allocation-mode', 'per-nexthop'])
self.cli_set(base_path + ['address-family', afi, 'rd', 'vpn', 'export', rd])
self.cli_set(base_path + ['address-family', afi, 'route-map', 'vpn', 'export', route_map_out])
self.cli_set(base_path + ['address-family', afi, 'route-map', 'vpn', 'import', route_map_in])
@@ -887,6 +888,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' export vpn', afi_config)
self.assertIn(f' import vpn', afi_config)
self.assertIn(f' label vpn export {label}', afi_config)
+ self.assertIn(f' label vpn export allocation-mode per-nexthop', afi_config)
self.assertIn(f' rd vpn export {rd}', afi_config)
self.assertIn(f' route-map vpn export {route_map_out}', afi_config)
self.assertIn(f' route-map vpn import {route_map_in}', afi_config)
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index 280057f04..c4c72aae9 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -296,6 +296,10 @@ def generate(dhcp):
render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp,
formater=lambda _: _.replace("&quot;", '"'))
+ # Clean up configuration test file
+ if os.path.exists(tmp_file):
+ os.unlink(tmp_file)
+
return None
def apply(dhcp):
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index e946704b3..8ad3f27fc 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -351,39 +351,6 @@ def apply_sysfs(firewall):
with open(path, 'w') as f:
f.write(value)
-def post_apply_trap(firewall):
- if 'first_install' in firewall:
- return None
-
- if not process_named_running('snmpd'):
- return None
-
- trap_username = os.getlogin()
-
- for host, target_conf in firewall['trap_targets'].items():
- community = target_conf['community'] if 'community' in target_conf else 'public'
- port = int(target_conf['port']) if 'port' in target_conf else 162
-
- base_cmd = f'snmptrap -v2c -c {community} {host}:{port} 0 {snmp_trap_mib}::{snmp_trap_name} '
-
- for change_type, changes in firewall['trap_diff'].items():
- for path_str, value in changes.items():
- objects = [
- f'mgmtEventUser s "{trap_username}"',
- f'mgmtEventSource i {snmp_event_source}',
- f'mgmtEventType i {snmp_change_type[change_type]}'
- ]
-
- if change_type == 'add':
- objects.append(f'mgmtEventCurrCfg s "{path_str} {value}"')
- elif change_type == 'delete':
- objects.append(f'mgmtEventPrevCfg s "{path_str} {value}"')
- elif change_type == 'change':
- objects.append(f'mgmtEventPrevCfg s "{path_str} {value[0]}"')
- objects.append(f'mgmtEventCurrCfg s "{path_str} {value[1]}"')
-
- cmd(base_cmd + ' '.join(objects))
-
def apply(firewall):
install_result, output = rc_cmd(f'nft -f {nftables_conf}')
if install_result == 1:
@@ -408,8 +375,6 @@ def apply(firewall):
print('Updating GeoIP. Please wait...')
geoip_update(firewall)
- post_apply_trap(firewall)
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py
index 3f86e2638..0a927ac88 100755
--- a/src/conf_mode/interfaces-macsec.py
+++ b/src/conf_mode/interfaces-macsec.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2022 VyOS maintainers and contributors
+# Copyright (C) 2020-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -43,6 +43,14 @@ airbag.enable()
# XXX: wpa_supplicant works on the source interface
wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf'
+# Constants
+## gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit
+GCM_AES_128_LEN: int = 32
+GCM_128_KEY_ERROR = 'gcm-aes-128 requires a 128bit long key!'
+## gcm-aes-256 requires a 256bit long key - 64 characters (string) = 32byte = 256bit
+GCM_AES_256_LEN: int = 64
+GCM_256_KEY_ERROR = 'gcm-aes-256 requires a 256bit long key!'
+
def get_config(config=None):
"""
Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
@@ -89,18 +97,54 @@ def verify(macsec):
raise ConfigError('Cipher suite must be set for MACsec "{ifname}"'.format(**macsec))
if dict_search('security.encrypt', macsec) != None:
- if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None:
- raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!')
+ # Check that only static or MKA config is present
+ if dict_search('security.static', macsec) != None and (dict_search('security.mka.cak', macsec) != None or dict_search('security.mka.ckn', macsec) != None):
+ raise ConfigError('Only static or MKA can be used!')
+
+ # Logic to check static configuration
+ if dict_search('security.static', macsec) != None:
+ # tx-key must be defined
+ if dict_search('security.static.key', macsec) == None:
+ raise ConfigError('Static MACsec tx-key must be defined.')
+
+ tx_len = len(dict_search('security.static.key', macsec))
+
+ if dict_search('security.cipher', macsec) == 'gcm-aes-128' and tx_len != GCM_AES_128_LEN:
+ raise ConfigError(GCM_128_KEY_ERROR)
+
+ if dict_search('security.cipher', macsec) == 'gcm-aes-256' and tx_len != GCM_AES_256_LEN:
+ raise ConfigError(GCM_256_KEY_ERROR)
+
+ # Make sure at least one peer is defined
+ if 'peer' not in macsec['security']['static']:
+ raise ConfigError('Must have at least one peer defined for static MACsec')
+
+ # For every enabled peer, make sure a MAC and rx-key is defined
+ for peer, peer_config in macsec['security']['static']['peer'].items():
+ if 'disable' not in peer_config and ('mac' not in peer_config or 'key' not in peer_config):
+ raise ConfigError('Every enabled MACsec static peer must have a MAC address and rx-key defined.')
+
+ # check rx-key length against cipher suite
+ rx_len = len(peer_config['key'])
+
+ if dict_search('security.cipher', macsec) == 'gcm-aes-128' and rx_len != GCM_AES_128_LEN:
+ raise ConfigError(GCM_128_KEY_ERROR)
+
+ if dict_search('security.cipher', macsec) == 'gcm-aes-256' and rx_len != GCM_AES_256_LEN:
+ raise ConfigError(GCM_256_KEY_ERROR)
+
+ # Logic to check MKA configuration
+ else:
+ if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None:
+ raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!')
- cak_len = len(dict_search('security.mka.cak', macsec))
+ cak_len = len(dict_search('security.mka.cak', macsec))
- if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != 32:
- # gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit
- raise ConfigError('gcm-aes-128 requires a 128bit long key!')
+ if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != GCM_AES_128_LEN:
+ raise ConfigError(GCM_128_KEY_ERROR)
- elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != 64:
- # gcm-aes-128 requires a 128bit long key - 64 characters (string) = 32byte = 256bit
- raise ConfigError('gcm-aes-128 requires a 256bit long key!')
+ elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != GCM_AES_256_LEN:
+ raise ConfigError(GCM_256_KEY_ERROR)
if 'source_interface' in macsec:
# MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad
@@ -115,7 +159,9 @@ def verify(macsec):
def generate(macsec):
- render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec)
+ # Only generate wpa_supplicant config if using MKA
+ if dict_search('security.mka.cak', macsec):
+ render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec)
return None
@@ -142,8 +188,10 @@ def apply(macsec):
i = MACsecIf(**macsec)
i.update(macsec)
- if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec:
- call(f'systemctl reload-or-restart {systemd_service}')
+ # Only reload/restart if using MKA
+ if dict_search('security.mka.cak', macsec):
+ if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec:
+ call(f'systemctl reload-or-restart {systemd_service}')
return None
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 26b217d98..1d0feb56f 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -166,17 +166,23 @@ def verify_pki(openvpn):
raise ConfigError(f'Invalid shared-secret on openvpn interface {interface}')
if tls:
- if 'ca_certificate' not in tls:
- raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface}')
-
- for ca_name in tls['ca_certificate']:
- if ca_name not in pki['ca']:
- raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
+ if (mode in ['server', 'client']) and ('ca_certificate' not in tls):
+ raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface},\
+ it is required in server and client modes')
+ else:
+ if ('ca_certificate' not in tls) and ('peer_fingerprint' not in tls):
+ raise ConfigError('Either "tls ca-certificate" or "tls peer-fingerprint" is required\
+ on openvpn interface {interface} in site-to-site mode')
- if len(tls['ca_certificate']) > 1:
- sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca'])
- if not verify_ca_chain(sorted_chain, pki['ca']):
- raise ConfigError(f'CA certificates are not a valid chain')
+ if 'ca_certificate' in tls:
+ for ca_name in tls['ca_certificate']:
+ if ca_name not in pki['ca']:
+ raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
+
+ if len(tls['ca_certificate']) > 1:
+ sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca'])
+ if not verify_ca_chain(sorted_chain, pki['ca']):
+ raise ConfigError(f'CA certificates are not a valid chain')
if mode != 'client' and 'auth_key' not in tls:
if 'certificate' not in tls:
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 446399255..122d9589a 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -90,7 +90,6 @@ def verify(wireguard):
# run checks on individual configured WireGuard peer
public_keys = []
-
for tmp in wireguard['peer']:
peer = wireguard['peer'][tmp]
@@ -107,8 +106,9 @@ def verify(wireguard):
if peer['public_key'] in public_keys:
raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"')
- if 'disable' not in peer and is_wireguard_key_pair(wireguard['private_key'], peer['public_key']):
- raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"')
+ if 'disable' not in peer:
+ if is_wireguard_key_pair(wireguard['private_key'], peer['public_key']):
+ raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"')
public_keys.append(peer['public_key'])
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index e49ad25ac..29ab9713f 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -116,7 +116,7 @@ def verify(wifi):
raise ConfigError('You must specify a WiFi mode')
if 'ssid' not in wifi and wifi['type'] != 'monitor':
- raise ConfigError('SSID must be configured')
+ raise ConfigError('SSID must be configured unless type is set to "monitor"!')
if wifi['type'] == 'access-point':
if 'country_code' not in wifi:
diff --git a/src/etc/netplug/linkdown.d/dhclient b/src/etc/netplug/linkdown.d/dhclient
deleted file mode 100755
index 555ff9134..000000000
--- a/src/etc/netplug/linkdown.d/dhclient
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/perl
-#
-# Module: dhclient
-#
-# **** License ****
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# A copy of the GNU General Public License is available as
-# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution
-# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'.
-# You can also obtain it by writing to the Free Software Foundation,
-# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
-# MA 02110-1301, USA.
-#
-# This code was originally developed by Vyatta, Inc.
-# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc.
-# All Rights Reserved.
-#
-# Author: Mohit Mehta
-# Date: November 2008
-# Description: Script to release lease on link down
-#
-# **** End License ****
-#
-
-use lib "/opt/vyatta/share/perl5/";
-use Vyatta::Config;
-use Vyatta::Misc;
-
-use strict;
-use warnings;
-
-sub stop_dhclient {
- my $intf = shift;
- my $dhcp_daemon = '/sbin/dhclient';
- my ($intf_config_file, $intf_process_id_file, $intf_leases_file) = Vyatta::Misc::generate_dhclient_intf_files($intf);
- my $release_cmd = "sudo $dhcp_daemon -q -cf $intf_config_file -pf $intf_process_id_file -lf $intf_leases_file -r $intf 2> /dev/null;";
- $release_cmd .= "sudo rm -f $intf_process_id_file 2> /dev/null";
- system ($release_cmd);
-}
-
-
-#
-# main
-#
-
-my $dev=shift;
-
-# only do this if interface is configured to use dhcp for getting IP address
-if (Vyatta::Misc::is_dhcp_enabled($dev, "outside_cli")) {
- # do a dhcp lease release for interface
- stop_dhclient($dev);
-}
-
-exit 0;
-
-# end of file
-
diff --git a/src/etc/netplug/linkup.d/dhclient b/src/etc/netplug/linkup.d/dhclient
deleted file mode 100755
index 8e50715fd..000000000
--- a/src/etc/netplug/linkup.d/dhclient
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/perl
-#
-# Module: dhclient
-#
-# **** License ****
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# A copy of the GNU General Public License is available as
-# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution
-# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'.
-# You can also obtain it by writing to the Free Software Foundation,
-# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
-# MA 02110-1301, USA.
-#
-# This code was originally developed by Vyatta, Inc.
-# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc.
-# All Rights Reserved.
-#
-# Author: Mohit Mehta
-# Date: November 2008
-# Description: Script to renew lease on link up
-#
-# **** End License ****
-#
-
-use lib "/opt/vyatta/share/perl5/";
-use Vyatta::Config;
-use Vyatta::Misc;
-
-use strict;
-use warnings;
-
-sub run_dhclient {
- my $intf = shift;
- my $dhcp_daemon = '/sbin/dhclient';
- my ($intf_config_file, $intf_process_id_file, $intf_leases_file) = Vyatta::Misc::generate_dhclient_intf_files($intf);
- my $cmd = "sudo $dhcp_daemon -pf $intf_process_id_file -x $intf 2> /dev/null; sudo rm -f $intf_process_id_file 2> /dev/null;";
- $cmd .= "sudo $dhcp_daemon -q -nw -cf $intf_config_file -pf $intf_process_id_file -lf $intf_leases_file $intf 2> /dev/null &";
- system ($cmd);
-}
-
-#
-# main
-#
-
-my $dev=shift;
-
-# only do this if interface is configured to use dhcp for getting IP address
-if (Vyatta::Misc::is_dhcp_enabled($dev, "outside_cli")) {
- # do a dhcp lease renew for interface
- run_dhclient($dev);
-}
-
-exit 0;
-
-# end of file
-
diff --git a/src/etc/netplug/linkup.d/vyos-python-helper b/src/etc/netplug/linkup.d/vyos-python-helper
new file mode 100755
index 000000000..9c59c58ad
--- /dev/null
+++ b/src/etc/netplug/linkup.d/vyos-python-helper
@@ -0,0 +1,4 @@
+#!/bin/sh
+PYTHON3=$(which python3)
+# Call the real python script and forward commandline arguments
+$PYTHON3 /etc/netplug/vyos-netplug-dhcp-client "${@:1}"
diff --git a/src/etc/netplug/netplug b/src/etc/netplug/netplug
new file mode 100755
index 000000000..60b65e8c9
--- /dev/null
+++ b/src/etc/netplug/netplug
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+dev="$1"
+action="$2"
+
+case "$action" in
+in)
+ run-parts --arg $dev --arg in /etc/netplug/linkup.d
+ ;;
+out)
+ run-parts --arg $dev --arg out /etc/netplug/linkdown.d
+ ;;
+
+# probe loads and initialises the driver for the interface and brings the
+# interface into the "up" state, so that it can generate netlink(7) events.
+# This interferes with "admin down" for an interface. Thus, commented out. An
+# "admin up" is treated as a "link up" and thus, "link up" action is executed.
+# To execute "link down" action on "admin down", run appropriate script in
+# /etc/netplug/linkdown.d
+#probe)
+# ;;
+
+*)
+ exit 1
+ ;;
+esac
diff --git a/src/etc/netplug/netplugd.conf b/src/etc/netplug/netplugd.conf
new file mode 100644
index 000000000..ab4d826d6
--- /dev/null
+++ b/src/etc/netplug/netplugd.conf
@@ -0,0 +1,3 @@
+eth*
+br*
+bond*
diff --git a/src/etc/netplug/vyos-netplug-dhcp-client b/src/etc/netplug/vyos-netplug-dhcp-client
new file mode 100755
index 000000000..55d15a163
--- /dev/null
+++ b/src/etc/netplug/vyos-netplug-dhcp-client
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+from time import sleep
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.ifconfig import Section
+from vyos.utils.boot import boot_configuration_complete
+from vyos.utils.commit import commit_in_progress
+from vyos.utils.process import call
+from vyos import airbag
+airbag.enable()
+
+if len(sys.argv) < 3:
+ airbag.noteworthy("Must specify both interface and link status!")
+ sys.exit(1)
+
+if not boot_configuration_complete():
+ airbag.noteworthy("System bootup not yet finished...")
+ sys.exit(1)
+
+while commit_in_progress():
+ sleep(1)
+
+interface = sys.argv[1]
+in_out = sys.argv[2]
+config = ConfigTreeQuery()
+
+interface_path = ['interfaces'] + Section.get_config_path(interface).split()
+
+for _, interface_config in config.get_config_dict(interface_path).items():
+ # Bail out early if we do not have an IP address configured
+ if 'address' not in interface_config:
+ continue
+ # Bail out early if interface ist administrative down
+ if 'disable' in interface_config:
+ continue
+ systemd_action = 'start'
+ if in_out == 'out':
+ systemd_action = 'stop'
+ # Start/Stop DHCP service
+ if 'dhcp' in interface_config['address']:
+ call(f'systemctl {systemd_action} dhclient@{interface}.service')
+ # Start/Stop DHCPv6 service
+ if 'dhcpv6' in interface_config['address']:
+ call(f'systemctl {systemd_action} dhcp6c@{interface}.service')
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index aff4ad1ae..35c7ce0e2 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -25,7 +25,6 @@ from cryptography import x509
from cryptography.x509.oid import ExtendedKeyUsageOID
from vyos.config import Config
-from vyos.configquery import ConfigTreeQuery
from vyos.pki import encode_certificate, encode_public_key, encode_private_key, encode_dh_parameters
from vyos.pki import get_certificate_fingerprint
from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list
@@ -43,7 +42,7 @@ CERT_REQ_END = '-----END CERTIFICATE REQUEST-----'
auth_dir = '/config/auth'
# Helper Functions
-conf = ConfigTreeQuery()
+conf = Config()
def get_default_values():
# Fetch default x509 values
base = ['pki', 'x509', 'default']