Skip to content

Conversation

@c-po
Copy link
Member

@c-po c-po commented Nov 26, 2025

Change summary

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.

NOTE 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!

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes)
  • Migration from an old Vyatta component to vyos-1x, please link to related PR inside obsoleted component
  • Other (please describe):

Related Task(s)

Related PR(s)

Checklist:

  • I have read the CONTRIBUTING document
  • I have linked this PR to one or more Phabricator Task(s)
  • I have run the components SMOKETESTS if applicable
  • My commit headlines contain a valid Task id
  • My change requires a change to the documentation
  • I have updated the documentation accordingly

@github-actions
Copy link

github-actions bot commented Nov 26, 2025

👍
No issues in PR Title / Commit Title

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!
@github-actions
Copy link

CI integration ❌ failed!

Details

CI logs

  • CLI Smoketests (no interfaces) ❌ failed
  • CLI Smoketests VPP 👍 passed
  • CLI Smoketests (interfaces only) 👍 passed
  • Config tests 👍 passed
  • Config tests VPP 👍 passed
  • RAID1 tests 👍 passed
  • TPM tests 👍 passed

Comment on lines +59 to +60
syslog.syslog(syslog.LOG_INFO,
f'RTM_NEWLINK -> {iface}, state={operstate}, mac={mac}')
Copy link
Member

@sever-sever sever-sever Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will PPPoE interfaces also log?
For example, for BRAS. There could be thousands of interfaces.

Copilot finished reviewing on behalf of sever-sever November 28, 2025 12:35
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a proof-of-concept netlink listener daemon (vyos-netlinkd) that monitors and logs network interface changes via netlink messages in Python, leveraging Python 3.10+ match/case statements.

  • Adds a new systemd service unit for the netlink daemon
  • Implements basic netlink event handling for RTM_NEWLINK and RTM_DELLINK messages
  • Provides a foundation for future netlink-based monitoring capabilities

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/systemd/vyos-netlinkd.service Defines systemd service configuration for the netlink daemon with early startup ordering
src/services/vyos-netlinkd Implements the main daemon logic with netlink message processing and signal handling

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +37 to +79
ipr = IPRoute()
ipr.bind()
fd = ipr.fileno()

global running
while running:
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():
event_type = message['event']
match event_type:
case 'RTM_NEWLINK':
attrs = dict(message.get('attrs', []))
iface = attrs.get('IFLA_IFNAME', '<unknown>')
operstate = attrs.get('IFLA_OPERSTATE', '<unknown>')
mac = attrs.get('IFLA_ADDRESS', '<unknown>')
syslog.syslog(syslog.LOG_INFO,
f'RTM_NEWLINK -> {iface}, state={operstate}, mac={mac}')

case 'RTM_DELLINK':
attrs = dict(message.get('attrs', []))
iface = attrs.get('IFLA_IFNAME', '<unknown>')
syslog.syslog(syslog.LOG_INFO, f'RTM_DELLINK -> {iface}')

case _:
syslog.syslog(syslog.LOG_INFO, f'Received event {event_type} - unhandled!')

except NetlinkError as e:
syslog.syslog(syslog.LOG_ERR, f'Netlink error: {e}')
except KeyboardInterrupt:
break
except Exception as e:
syslog.syslog(syslog.LOG_ERR, f'Unhandled exception: {e}')

ipr.close()
syslog.syslog(syslog.LOG_INFO, 'Netlink listener daemon stopped.')
exit(0)
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IPRoute socket is closed outside the main loop, but if an exception occurs during initialization (lines 37-39) or within the loop before setting running=False, the socket may not be properly closed. Consider using a try-finally block or context manager to ensure cleanup happens reliably.

Suggested change
ipr = IPRoute()
ipr.bind()
fd = ipr.fileno()
global running
while running:
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():
event_type = message['event']
match event_type:
case 'RTM_NEWLINK':
attrs = dict(message.get('attrs', []))
iface = attrs.get('IFLA_IFNAME', '<unknown>')
operstate = attrs.get('IFLA_OPERSTATE', '<unknown>')
mac = attrs.get('IFLA_ADDRESS', '<unknown>')
syslog.syslog(syslog.LOG_INFO,
f'RTM_NEWLINK -> {iface}, state={operstate}, mac={mac}')
case 'RTM_DELLINK':
attrs = dict(message.get('attrs', []))
iface = attrs.get('IFLA_IFNAME', '<unknown>')
syslog.syslog(syslog.LOG_INFO, f'RTM_DELLINK -> {iface}')
case _:
syslog.syslog(syslog.LOG_INFO, f'Received event {event_type} - unhandled!')
except NetlinkError as e:
syslog.syslog(syslog.LOG_ERR, f'Netlink error: {e}')
except KeyboardInterrupt:
break
except Exception as e:
syslog.syslog(syslog.LOG_ERR, f'Unhandled exception: {e}')
ipr.close()
syslog.syslog(syslog.LOG_INFO, 'Netlink listener daemon stopped.')
exit(0)
ipr = None
try:
ipr = IPRoute()
ipr.bind()
fd = ipr.fileno()
global running
while running:
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():
event_type = message['event']
match event_type:
case 'RTM_NEWLINK':
attrs = dict(message.get('attrs', []))
iface = attrs.get('IFLA_IFNAME', '<unknown>')
operstate = attrs.get('IFLA_OPERSTATE', '<unknown>')
mac = attrs.get('IFLA_ADDRESS', '<unknown>')
syslog.syslog(syslog.LOG_INFO,
f'RTM_NEWLINK -> {iface}, state={operstate}, mac={mac}')
case 'RTM_DELLINK':
attrs = dict(message.get('attrs', []))
iface = attrs.get('IFLA_IFNAME', '<unknown>')
syslog.syslog(syslog.LOG_INFO, f'RTM_DELLINK -> {iface}')
case _:
syslog.syslog(syslog.LOG_INFO, f'Received event {event_type} - unhandled!')
except NetlinkError as e:
syslog.syslog(syslog.LOG_ERR, f'Netlink error: {e}')
except KeyboardInterrupt:
break
except Exception as e:
syslog.syslog(syslog.LOG_ERR, f'Unhandled exception: {e}')
finally:
if ipr is not None:
ipr.close()
syslog.syslog(syslog.LOG_INFO, 'Netlink listener daemon stopped.')
exit(0)

Copilot uses AI. Check for mistakes.

ipr.close()
syslog.syslog(syslog.LOG_INFO, 'Netlink listener daemon stopped.')
exit(0)
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using exit(0) from the sys module is unnecessary here. The function can simply return, or use sys.exit(0) explicitly if needed. Since exit was imported from sys, this should be sys.exit(0) for clarity, or preferably just return from main().

Copilot uses AI. Check for mistakes.

[Service]
ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-netlinkd
Type=idle
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type=idle is typically used for services that should run after all other jobs are dispatched. For a long-running daemon that listens for netlink events, Type=simple (the default) or Type=notify would be more appropriate. Type=idle may cause unnecessary delays in service startup.

Suggested change
Type=idle
Type=simple

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

2 participants