netevd is a network event daemon that watches your Linux network interfaces and runs scripts when things change. Think of it as systemd path units, but purpose-built for networking: when an interface gets an IP, loses its link, or routes change, netevd executes your scripts with full context about what happened.
It bridges systemd-networkd, NetworkManager, and dhclient into a single, unified event system -- with automatic policy routing, a REST API, Prometheus metrics, and a defense-in-depth security model.
| Problem | netevd solution |
|---|---|
| Need scripts to run when network state changes | Drop scripts in /etc/netevd/routable.d/ -- done |
| Multi-homed server with broken return-path routing | Automatic per-interface routing tables and policy rules |
| Want real-time network events, not polling | Netlink multicast: sub-100ms latency, zero polling |
| Need to support multiple network managers | One daemon handles networkd, NetworkManager, and dhclient |
| Security concerns with network daemons | Privilege separation, CAP_NET_ADMIN only, input validation |
# Build and install
git clone https://github.com/ssahani/netevd.git && cd netevd
cargo build --release
sudo install -Dm755 target/release/netevd /usr/bin/netevd
sudo install -Dm644 systemd/netevd.service /lib/systemd/system/netevd.service
sudo install -Dm644 examples/netevd.yaml /etc/netevd/netevd.yaml
# Set up
sudo useradd -r -M -s /usr/bin/nologin netevd
sudo mkdir -p /etc/netevd/{carrier.d,no-carrier.d,configured.d,degraded.d,routable.d,activated.d,disconnected.d,manager.d,routes.d}
# Start
sudo systemctl daemon-reload
sudo systemctl enable --now netevdCreate your first script -- this runs whenever an interface becomes fully routable:
cat <<'EOF' | sudo tee /etc/netevd/routable.d/01-notify.sh && sudo chmod +x /etc/netevd/routable.d/01-notify.sh
#!/bin/bash
logger -t netevd "Interface $LINK is routable: $ADDRESSES"
EOFFor the full walkthrough, see the Quick Start Guide.
+------------------+
| Linux Kernel |
| Netlink events |
+--------+---------+
|
+-------------------+-------------------+
| | |
+-----------+ +-----------+ +-----------+
| Addresses | | Links | | Routes |
| watcher | | watcher | | watcher |
+-----+-----+ +-----+-----+ +-----+-----+
| | |
+-------------------+-------------------+
|
+--------+---------+
| NetworkState |
| (Arc<RwLock>) |
+--------+---------+
|
+--------------+--------------+
| | |
+-----+-----+ +----+----+ +------+------+
| Routing | | Script | | DBus |
| policy | | exec | | resolved/ |
| rules | | | | hostnamed |
+------------+ +---------+ +-------------+
Event sources -- netevd subscribes to kernel netlink multicast groups and listens for DBus signals from your chosen backend (systemd-networkd, NetworkManager) or watches dhclient lease files via inotify.
State management -- All state is held in a single NetworkState behind Arc<RwLock>, updated by concurrent Tokio tasks. Read locks for queries, write locks for mutations -- no races.
Actions -- On state changes, netevd configures routing policy rules, executes scripts from the matching event directory, and optionally pushes DNS/hostname updates via DBus.
# /etc/netevd/netevd.yaml
system:
log_level: "info"
backend: "systemd-networkd" # or "NetworkManager" or "dhclient"
monitoring:
interfaces: # empty = monitor all
- eth0
- eth1
routing:
policy_rules: # auto-create per-interface routing tables
- eth1
backends:
systemd_networkd:
emit_json: true # pass full JSON to scripts via $JSON
dhclient:
use_dns: false
use_domain: false
use_hostname: false
networkmanager: {}Full reference: Configuration Guide
Scripts are organized by the event that triggers them:
| Directory | Trigger | Backends |
|---|---|---|
carrier.d/ |
Cable connected | All |
no-carrier.d/ |
Cable disconnected | All |
configured.d/ |
Interface has IP | systemd-networkd |
degraded.d/ |
Partial configuration | systemd-networkd |
routable.d/ |
Full connectivity | systemd-networkd, dhclient |
activated.d/ |
Device activated | NetworkManager |
disconnected.d/ |
Device disconnected | NetworkManager |
manager.d/ |
Manager state change | All |
routes.d/ |
Routing table change | All |
Scripts run in alphabetical order. Use numeric prefixes (01-, 02-) to control ordering. Non-zero exit codes are logged but don't block other scripts.
Every script receives:
| Variable | Example |
|---|---|
$LINK |
eth0 |
$LINKINDEX |
2 |
$STATE |
routable |
$BACKEND |
systemd-networkd |
$ADDRESSES |
192.168.1.100 10.0.0.5 |
systemd-networkd adds $JSON with full interface data (MTU, driver, DNS, routes).
dhclient adds $DHCP_ADDRESS, $DHCP_GATEWAY, $DHCP_DNS, $DHCP_DOMAIN, $DHCP_HOSTNAME.
For multi-homed servers, netevd solves the classic "wrong interface" problem automatically. When you list an interface under routing.policy_rules, netevd:
- Creates a custom routing table (ID = 200 + interface index)
- Adds
from <ip> lookup <table>andto <ip> lookup <table>rules - Installs a default route via the interface's gateway in that table
- Cleans up automatically when addresses are removed
# After netevd configures eth1 (index 3, IP 192.168.1.100):
$ ip rule list
32765: from 192.168.1.100 lookup 203
32766: to 192.168.1.100 lookup 203
$ ip route show table 203
default via 192.168.1.1 dev eth1netevd follows a defense-in-depth model:
- Privilege separation -- Starts as root, immediately drops to the
netevduser viasetuid/setgid - Minimal capabilities -- Retains only
CAP_NET_ADMIN; child processes inherit nothing - Input validation -- All external data (interface names, IPs, hostnames) is validated; shell metacharacters are rejected
- No shell intermediary -- Scripts are executed directly, not via
sh -c - systemd hardening --
NoNewPrivileges,ProtectSystem=strict,PrivateTmp
Details: Security Policy
| Metric | Value |
|---|---|
| Memory (idle) | 3-5 MB RSS |
| CPU (idle) | < 1% |
| Event latency | < 100ms (netlink multicast) |
| Event-to-script | < 10ms |
| Throughput | 1000+ events/sec |
9 endpoints built on Axum for remote management and monitoring:
curl http://localhost:9090/api/v1/status # Daemon status
curl http://localhost:9090/api/v1/interfaces # List interfaces
curl http://localhost:9090/api/v1/routes # Routing table
curl http://localhost:9090/api/v1/events # Event history
curl http://localhost:9090/metrics # Prometheus metrics
curl http://localhost:9090/health # Health checkFull reference: API Documentation
| Guide | Description |
|---|---|
| Quick Start | Up and running in 5 minutes |
| Installation | All platforms and package managers |
| Configuration | Complete YAML reference |
| Examples | Multi-homing, VPN, HA, DDNS, containers |
| REST API | HTTP endpoints and data models |
| Metrics | Prometheus metrics and Grafana dashboards |
| Architecture | Internals, concurrency, event pipeline |
| Troubleshooting | Diagnosis and common fixes |
| Security | Threat model and hardening |
| Contributing | Dev setup and PR guidelines |
| Roadmap | Planned features and priorities |
| Changelog | Release history |
git clone https://github.com/ssahani/netevd.git && cd netevd
cargo build && cargo test && cargo clippy -- -D warningsSee CONTRIBUTING.md for the full guide.
LGPL-3.0-or-later -- Copyright 2026 Susant Sahani <ssahani@redhat.com>