Skip to content

Commit 9158ee1

Browse files
committed
vyos-netlinkd: T8047: add proof-of-concept for a netlink listener daemon
Implementing a daemon that listens for netlink messages in Python was discussed for many years. This is a proof-of-concept implementation how we can listen for netlink messages and process them in Python. Python 3.10 minimum is required due to the use of case statements which mimics C-style switch/case instructions. Add example: set interfaces ethernet eth1 vif 21 commit vyos-configd[4690]: Received message: {"type": "node", "last": true, "data": "VYOS_TAGNODE_VALUE=eth1/usr/libexec/vyos/conf_mode/interfaces_ethernet.py"} vyos-netlinkd[5955]: RTM_NEWLINK -> eth1, state=UP, mac=00:50:56:b3:38:c5 vyos-netlinkd[5955]: RTM_NEWLINK -> eth1.21, state=DOWN, mac=00:50:56:b3:38:c5 Remove example: delete interfaces ethernet eth1 vif commit vyos-configd[4690]: Received message: {"type": "node", "last": true, "data": "VYOS_TAGNODE_VALUE=eth1/usr/libexec/vyos/conf_mode/interfaces_ethernet.py"} vyos-netlinkd[6803]: Received event RTM_DELADDR - unhandled! vyos-netlinkd[6803]: RTM_NEWLINK -> eth1, state=UP, mac=00:50:56:b3:38:c5 vyos-netlinkd[6803]: RTM_NEWLINK -> eth1.21, state=DOWN, mac=00:50:56:b3:38:c5 Message parsing of course needs to be improved for real-world use!
1 parent b02565c commit 9158ee1

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

src/services/vyos-netlinkd

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License version 2 or later as
7+
# published by the Free Software Foundation.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import syslog
18+
import signal
19+
import select
20+
21+
from pyroute2 import IPRoute # pylint: disable = no-name-in-module
22+
from pyroute2 import NetlinkError # pylint: disable = no-name-in-module
23+
from sys import exit
24+
25+
running = True
26+
27+
def sigterm_handler(signo, frame):
28+
global running
29+
running = False
30+
sig = signal.Signals(signo)
31+
syslog.syslog(syslog.LOG_INFO, f'Received signal {sig.name} - shutting down...')
32+
33+
def main():
34+
syslog.openlog(ident="vyos-netlinkd", logoption=syslog.LOG_PID)
35+
syslog.syslog(syslog.LOG_INFO, "Netlink listener daemon started.")
36+
37+
ipr = IPRoute()
38+
ipr.bind()
39+
fd = ipr.fileno()
40+
41+
global running
42+
while running:
43+
try:
44+
# Wait for up to 1 second for a netlink message
45+
rlist, _, _ = select.select([fd], [], [], 1.0)
46+
if not rlist:
47+
# timeout - retry
48+
continue
49+
50+
# Receive and process any messages
51+
for message in ipr.get():
52+
event_type = message['event']
53+
match event_type:
54+
case 'RTM_NEWLINK':
55+
attrs = dict(message.get('attrs', []))
56+
iface = attrs.get('IFLA_IFNAME', '<unknown>')
57+
operstate = attrs.get('IFLA_OPERSTATE', '<unknown>')
58+
mac = attrs.get('IFLA_ADDRESS', '<unknown>')
59+
syslog.syslog(syslog.LOG_INFO,
60+
f'RTM_NEWLINK -> {iface}, state={operstate}, mac={mac}')
61+
62+
case 'RTM_DELLINK':
63+
attrs = dict(message.get('attrs', []))
64+
iface = attrs.get('IFLA_IFNAME', '<unknown>')
65+
syslog.syslog(syslog.LOG_INFO, f'RTM_DELLINK -> {iface}')
66+
67+
case _:
68+
syslog.syslog(syslog.LOG_INFO, f'Received event {event_type} - unhandled!')
69+
70+
except NetlinkError as e:
71+
syslog.syslog(syslog.LOG_ERR, f'Netlink error: {e}')
72+
except KeyboardInterrupt:
73+
break
74+
except Exception as e:
75+
syslog.syslog(syslog.LOG_ERR, f'Unhandled exception: {e}')
76+
77+
ipr.close()
78+
syslog.syslog(syslog.LOG_INFO, 'Netlink listener daemon stopped.')
79+
exit(0)
80+
81+
if __name__ == "__main__":
82+
signal.signal(signal.SIGTERM, sigterm_handler)
83+
signal.signal(signal.SIGINT, sigterm_handler)
84+
main()

src/systemd/vyos-netlinkd.service

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[Unit]
2+
Description=VyOS netlink daemon
3+
4+
# Without this option, lots of default dependencies are added,
5+
# among them network.target, which creates a dependency cycle
6+
DefaultDependencies=no
7+
8+
# Seemingly sensible way to say "as early as the system is ready"
9+
# All vyos-configd needs is read/write mounted root
10+
After=systemd-remount-fs.service
11+
Before=vyos-router.service
12+
13+
[Service]
14+
ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-netlinkd
15+
Type=idle
16+
17+
SyslogIdentifier=vyos-netlinkd
18+
SyslogFacility=daemon
19+
20+
Restart=on-failure
21+
22+
User=root
23+
Group=vyattacfg
24+
25+
[Install]
26+
WantedBy=vyos.target

0 commit comments

Comments
 (0)