diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-04-02 16:31:46 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-02 16:31:46 +0200 |
| commit | c6ec6ade63a48a7e6a2ec5378c863d6d3b5bd000 (patch) | |
| tree | b8f7188758322e97b5132768b0fdb8238abc6140 | |
| parent | 2ad37c4a8a963651bbc183d24662f2571c4682c6 (diff) | |
| parent | b84bdbf8dd25995aa564081632de6251ecf5ad4e (diff) | |
| download | vyos-1x-c6ec6ade63a48a7e6a2ec5378c863d6d3b5bd000.tar.gz vyos-1x-c6ec6ade63a48a7e6a2ec5378c863d6d3b5bd000.zip | |
Merge pull request #4872 from c-po/vyos-netlinkd
vyos-netlinkd: T8047: replacement of netplugd
| -rw-r--r-- | debian/control | 1 | ||||
| -rw-r--r-- | debian/vyos-1x.install | 1 | ||||
| -rw-r--r-- | debian/vyos-1x.links | 1 | ||||
| -rw-r--r-- | debian/vyos-1x.postinst | 5 | ||||
| -rw-r--r-- | debian/vyos-1x.preinst | 2 | ||||
| -rw-r--r-- | python/vyos/ifconfig/section.py | 8 | ||||
| -rwxr-xr-x | src/etc/netplug/linkup.d/vyos-python-helper | 4 | ||||
| -rwxr-xr-x | src/etc/netplug/netplug | 41 | ||||
| -rw-r--r-- | src/etc/netplug/netplugd.conf | 4 | ||||
| -rwxr-xr-x | src/etc/netplug/vyos-netplug-dhcp-client | 79 | ||||
| -rwxr-xr-x | src/init/vyos-router | 8 | ||||
| -rwxr-xr-x | src/services/vyos-netlinkd | 169 | ||||
| -rw-r--r-- | src/systemd/netplug.service | 9 | ||||
| -rw-r--r-- | src/systemd/vyos-netlinkd.service | 25 |
14 files changed, 209 insertions, 148 deletions
diff --git a/debian/control b/debian/control index f991d8f48..f9998a90e 100644 --- a/debian/control +++ b/debian/control @@ -117,7 +117,6 @@ Depends: ethtool (>= 6.10), lm-sensors, procps, - netplug, sed, ssl-cert, tuned, diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index 0fd5e3395..7732cc039 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -5,7 +5,6 @@ etc/default etc/dhcp etc/ipsec.d etc/logrotate.d -etc/netplug etc/modprobe.d etc/ppp etc/securetty diff --git a/debian/vyos-1x.links b/debian/vyos-1x.links index aef22555c..f410f428c 100644 --- a/debian/vyos-1x.links +++ b/debian/vyos-1x.links @@ -1,4 +1,3 @@ -/etc/netplug/linkup.d/vyos-python-helper /etc/netplug/linkdown.d/vyos-python-helper /usr/libexec/vyos/system/standalone_root_pw_reset /opt/vyatta/sbin/standalone_root_pw_reset /usr/libexec/vyos/vyconf_cli.py /usr/libexec/vyos/vyconf/bin/vy_commit /usr/libexec/vyos/vyconf_cli.py /usr/libexec/vyos/vyconf/bin/vy_in_session diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index a997ad439..4ca20d8e3 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -283,3 +283,8 @@ fi if systemctl is-active --quiet vyos-domain-resolver; then systemctl restart vyos-domain-resolver fi + +# Restart vyos-netlinkd if running +if systemctl is-active --quiet vyos-netlinkd; then + systemctl restart vyos-netlinkd +fi diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst index 657e2719d..d39cf7544 100644 --- a/debian/vyos-1x.preinst +++ b/debian/vyos-1x.preinst @@ -6,8 +6,6 @@ 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 dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.d/45-frr.conf dpkg-divert --package vyos-1x --add --no-rename /lib/udev/rules.d/99-systemd.rules dpkg-divert --package vyos-1x --add --no-rename /etc/cron.d/certbot diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 7f110de04..c3f803c76 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -186,7 +186,7 @@ class Section: return list(cls._prefixes.keys()) @classmethod - def get_config_path(cls, name): + def get_config_path(cls, name, delimiter=' '): """ get config path to interface with .vif or .vif-s.vif-c example: eth0.1.2 -> 'ethernet eth0 vif-s 1 vif-c 2' @@ -195,11 +195,11 @@ class Section: sect = cls.section(name) if sect: splinterface = name.split('.') - intfpath = f'{sect} {splinterface[0]}' + intfpath = f'{sect}{delimiter}{splinterface[0]}' if len(splinterface) == 2: - intfpath += f' vif {splinterface[1]}' + intfpath += f'{delimiter}vif{delimiter}{splinterface[1]}' elif len(splinterface) == 3: - intfpath += f' vif-s {splinterface[1]} vif-c {splinterface[2]}' + intfpath += f'{delimiter}vif-s{delimiter}{splinterface[1]}{delimiter}vif-c{delimiter}{splinterface[2]}' return intfpath else: return False diff --git a/src/etc/netplug/linkup.d/vyos-python-helper b/src/etc/netplug/linkup.d/vyos-python-helper deleted file mode 100755 index 9c59c58ad..000000000 --- a/src/etc/netplug/linkup.d/vyos-python-helper +++ /dev/null @@ -1,4 +0,0 @@ -#!/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 deleted file mode 100755 index 9f6ab74cc..000000000 --- a/src/etc/netplug/netplug +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh -# -# Copyright 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 deleted file mode 100644 index 7da3c67e8..000000000 --- a/src/etc/netplug/netplugd.conf +++ /dev/null @@ -1,4 +0,0 @@ -eth* -br* -bond* -wlan* diff --git a/src/etc/netplug/vyos-netplug-dhcp-client b/src/etc/netplug/vyos-netplug-dhcp-client deleted file mode 100755 index d538f8b52..000000000 --- a/src/etc/netplug/vyos-netplug-dhcp-client +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 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.config import Config -from vyos.ifconfig import Section -from vyos.utils.boot import boot_configuration_complete -from vyos.utils.process import cmd -from vyos.utils.process import is_systemd_service_active -from vyos.utils.commit import commit_in_progress -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) - -interface = sys.argv[1] - -while commit_in_progress(): - sleep(0.250) - -in_out = sys.argv[2] -config = Config() - -interface_path = ['interfaces'] + Section.get_config_path(interface).split() - -systemdV4_service = f'dhclient@{interface}.service' -systemdV6_service = f'dhcp6c@{interface}.service' -if in_out == 'out': - # Interface moved state to down - if is_systemd_service_active(systemdV4_service): - cmd(f'systemctl stop {systemdV4_service}') - if is_systemd_service_active(systemdV6_service): - cmd(f'systemctl stop {systemdV6_service}') -elif in_out == 'in': - v6_restart = False - - if config.exists_effective(interface_path + ['address']): - tmp = config.return_effective_values(interface_path + ['address']) - # Always (re-)start the DHCP(v6) client service. If the DHCP(v6) client - # is already running - which could happen if the interface is re- - # configured in operational down state, it will have a backoff - # time increasing while not receiving a DHCP(v6) reply. - # - # To make the interface instantly available, and as for a DHCP(v6) lease - # we will re-start the service and thus cancel the backoff time. - if 'dhcp' in tmp: - cmd(f'systemctl restart {systemdV4_service}') - if 'dhcpv6' in tmp: - v6_restart = True - - if config.exists_effective(interface_path + ['dhcpv6-options', 'pd']): - v6_restart = True - - if v6_restart: - cmd(f'systemctl restart {systemdV6_service}') diff --git a/src/init/vyos-router b/src/init/vyos-router index 735eb4213..234f9b9b9 100755 --- a/src/init/vyos-router +++ b/src/init/vyos-router @@ -683,13 +683,18 @@ start () fi # Start netplug daemon - systemctl start netplug.service + systemctl start vyos-netlinkd.service } stop() { local -i status=0 log_daemon_msg "Stopping VyOS router" + + # As vyos-netlinkd consumes a configuration, we need to stop it prior + # unmounting /config + systemctl stop vyos-netlinkd.service + for ((i=${#sub_inits[@]} - 1; i >= 0; i--)) ; do s=${subinit[$i]} log_progress_msg $s @@ -701,7 +706,6 @@ stop() umount ${vyatta_configdir} log_action_end_msg $? - systemctl stop netplug.service systemctl stop vyconfd.service systemctl stop frr.service diff --git a/src/services/vyos-netlinkd b/src/services/vyos-netlinkd new file mode 100755 index 000000000..647241276 --- /dev/null +++ b/src/services/vyos-netlinkd @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# +# Copyright VyOS maintainers and contributors <maintainers@vyos.io> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys +import syslog +import signal +import select + +from pyroute2 import IPRoute # pylint: disable = no-name-in-module +from pyroute2 import NetlinkError # pylint: disable = no-name-in-module +from time import sleep +from typing import Optional + +from vyos.configquery import op_mode_config_dict +from vyos.ifconfig import Section +from vyos.utils.boot import boot_configuration_complete +from vyos.utils.commit import commit_in_progress +from vyos.utils.dict import dict_search +from vyos.utils.process import cmd +from vyos.utils.process import is_systemd_service_active + +running = True + +# compile regex once during startup for fast match +IFACE_RE = re.compile(r"^(?:eth|br|bond|wlan)") + +def match_iface(ifname: str) -> bool: + """ Helper function returning true if interface name is a match for further + processing (e.g. restart of DHCP(v6) client) + """ + return IFACE_RE.match(ifname) is not None + +def sigterm_handler(signo, frame): + global running + running = False + sig = signal.Signals(signo) + syslog.syslog(syslog.LOG_INFO, f'Received signal {sig.name} - shutting down...') + +def _handle_dhcp_events(config_dict: dict, operstate: Optional[str], ifname: str) -> None: + systemdV4_service = f'dhclient@{ifname}.service' + systemdV6_service = f'dhcp6c@{ifname}.service' + + # Only handle explicit UP/DOWN state transitions; ignore other kernel states. + if operstate not in ['UP', 'DOWN']: + return None + + if operstate == 'DOWN': + # Interface moved state to down + if is_systemd_service_active(systemdV4_service): + syslog.syslog(syslog.LOG_DEBUG, f'Stopping {systemdV4_service}...') + cmd(f'systemctl stop {systemdV4_service}') + if is_systemd_service_active(systemdV6_service): + syslog.syslog(syslog.LOG_DEBUG, f'Stopping {systemdV6_service}...') + cmd(f'systemctl stop {systemdV6_service}') + + elif operstate == 'UP': + v6_restart = False + interface_path = Section.get_config_path(ifname, delimiter='.') + if tmp := dict_search(f'{interface_path}.address', config_dict): + # Always (re-)start the DHCP(v6) client service. If the DHCP(v6) client + # is already running - which could happen if the interface is re- + # configured in operational down state, it will have an exponential backoff + # time increasing while not receiving a DHCP(v6) reply. + # + # To make the interface instantly available, and as for a DHCP(v6) lease + # we will re-start the service and thus cancel the backoff time. + if 'dhcp' in tmp: + syslog.syslog(syslog.LOG_DEBUG, f'Restarting {systemdV4_service}...') + cmd(f'systemctl restart {systemdV4_service}') + if 'dhcpv6' in tmp: + v6_restart = True + + if dict_search(f'{interface_path}.dhcpv6_options.pd', config_dict): + v6_restart = True + + if v6_restart: + syslog.syslog(syslog.LOG_DEBUG, f'Restarting {systemdV6_service}...') + cmd(f'systemctl restart {systemdV6_service}') + + return None + +def main(): + syslog.openlog(ident="vyos-netlinkd", + logoption=syslog.LOG_PID, + facility=syslog.LOG_DAEMON) + syslog.syslog(syslog.LOG_INFO, "VyOS Netlink listener daemon started.") + + ipr = IPRoute() + ipr.bind() + fd = ipr.fileno() + + global running + while running: + if not boot_configuration_complete(): + syslog.syslog(syslog.LOG_INFO, 'System bootup not yet finished...') + sleep(5) + continue + + try: + # Wait for up to 1 second for a netlink message + rlist, _, _ = select.select([fd], [], [], 1.0) + if not rlist: + # timeout - retry + continue + + # Receive and process any messages + for message in ipr.get(): + # Wait for commit to complete + while commit_in_progress(): + sleep(0.250) + + config_dict = op_mode_config_dict(['interfaces'], + key_mangling=('-', '_'), + get_first_key=True) + + # Parse NETLINK message + match message['event']: + # Message received during interface creation or modification + # e.g. link up/down. + case 'RTM_NEWLINK': + attrs = dict(message.get('attrs', [])) + ifname = attrs.get('IFLA_IFNAME', None) + mac = attrs.get('IFLA_ADDRESS', '<unknown>') + operstate = attrs.get('IFLA_OPERSTATE', None) + syslog.syslog(syslog.LOG_DEBUG, + f'RTM_NEWLINK -> {ifname}, state={operstate}, mac={mac}') + + # Bail out early - no interface name in the message + if not ifname: + continue + # Bail out early - not interested in interface type + if not match_iface(ifname): + continue + + _handle_dhcp_events(config_dict, operstate, ifname) + + # Deletion of a network link which has been previously added to the kernel + case 'RTM_DELLINK': + pass + + except NetlinkError as e: + syslog.syslog(syslog.LOG_ERR, f'Netlink error: {e}') + except Exception as e: + syslog.syslog(syslog.LOG_ERR, f'Unhandled exception: {e}') + except KeyboardInterrupt: + break + + ipr.close() + syslog.syslog(syslog.LOG_INFO, 'Netlink listener daemon stopped.') + sys.exit(0) + +if __name__ == "__main__": + signal.signal(signal.SIGTERM, sigterm_handler) + signal.signal(signal.SIGINT, sigterm_handler) + main() diff --git a/src/systemd/netplug.service b/src/systemd/netplug.service deleted file mode 100644 index 928c553e8..000000000 --- a/src/systemd/netplug.service +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=Network cable hotplug management daemon -Documentation=man:netplugd(8) -After=vyos-router.service - -[Service] -Type=forking -PIDFile=/run/netplugd.pid -ExecStart=/sbin/netplugd -c /etc/netplug/netplugd.conf -p /run/netplugd.pid diff --git a/src/systemd/vyos-netlinkd.service b/src/systemd/vyos-netlinkd.service new file mode 100644 index 000000000..6c8a80878 --- /dev/null +++ b/src/systemd/vyos-netlinkd.service @@ -0,0 +1,25 @@ +[Unit] +Description=VyOS netlink daemon + +# Without this option, lots of default dependencies are added, +# among them network.target, which creates a dependency cycle +DefaultDependencies=no + +# Seemingly sensible way to say "as early as the system is ready" +# All vyos-netlinkd needs is read/write mounted root +After=systemd-remount-fs.service vyos-router.service + +[Service] +ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-netlinkd +Type=simple + +SyslogIdentifier=vyos-netlinkd +SyslogFacility=daemon + +Restart=on-failure + +User=root +Group=vyattacfg + +[Install] +WantedBy=vyos.target |
