Kernel events → your scripts.
netevd turns Linux netlink (and opt-in eBPF) into drop-in hook directories — across systemd-networkd, NetworkManager, and dhclient — with policy routing, REST, and Prometheus.
Install · User guide · eBPF · Config example
| Backends | systemd-networkd · NetworkManager · dhclient |
| Hooks | 17 directories (carrier, routable, link/address, eBPF drops…) |
| Latency | <100 ms netlink · zero polling |
| eBPF | Opt-in observe-only: drops / TCP retransmit / TCP reset |
| Ops | Policy routing · REST :9090 · Prometheus /metrics |
| License | Apache-2.0 |
| You have… | netevd gives you… |
|---|---|
| Scripts that should run when the network changes | Drop a file in /etc/netevd/routable.d/ |
| Multi-homed return-path breakage | Automatic per-interface tables + policy rules |
NetworkManager dispatcher.d only |
One contract for networkd, NM, and dhclient |
Cron polling ip addr |
Real netlink multicast (<100 ms) |
Silent kfree_skb / TCP RST netlink never sees |
Opt-in eBPF → drops.d / tcp-reset.d |
| Hand-rolled netlink + debounce + safe exec | Already done — with validated $LINK / $JSON |
sudo tee /etc/netevd/routable.d/01-notify.sh >/dev/null <<'EOF'
#!/bin/bash
logger -t netevd "$LINK is routable: $ADDRESSES"
EOF
sudo chmod +x /etc/netevd/routable.d/01-notify.shWhen the interface becomes fully routable, that script runs. Same idea for carrier, link add/remove, routes — and, with eBPF, packet drops and TCP resets.
curl -LO https://github.com/zyvorai/netevd/releases/download/v0.4.1/netevd-0.4.1-linux-amd64.tar.gz
tar xzf netevd-*-linux-amd64.tar.gz && cd netevd-*-linux-amd64
sudo ./install.sh && sudo systemctl enable --now netevdgit clone https://github.com/zyvorai/netevd.git && cd netevd
cargo build --release
# optional: make -C ebpf && cargo build --release --features ebpf
sudo install -Dm755 target/release/netevd /usr/bin/netevd
sudo install -Dm644 systemd/netevd.service /lib/systemd/system/netevd.service
sudo install -Dm644 config/netevd.example.yaml /etc/netevd/netevd.yaml
sudo systemctl enable --now netevddocker pull ghcr.io/zyvorai/netevd:latest-ubuntu # or :latest-alpineImages ship on every main push. Hook scripts have bash and ip; D-Bus uses zbus (no dbus/systemd in the image).
./scripts/deploy-remote.sh <host> [user] # builds, installs, veth + eBPF attach checkNetlink covers link, address, and route. It does not see silent stack drops or TCP retransmit/RST. With --features ebpf, the same hook contract gets three more sources — no XDP/TC deny, no DNS/SNI, no process attribution.
| Kernel source | Hook directory |
|---|---|
skb:kfree_skb |
/etc/netevd/drops.d/ |
tcp:tcp_retransmit_skb |
/etc/netevd/tcp-retransmit.d/ |
tcp:tcp_*_reset |
/etc/netevd/tcp-reset.d/ |
make -C ebpf
cargo build --release --features ebpf
sudo install -Dm644 ebpf/netevd-ebpf.o /usr/lib/netevd/netevd-ebpf.o
sudo install -Dm644 systemd/netevd-ebpf.conf /etc/systemd/system/netevd.service.d/ebpf.confebpf:
enabled: true
drops: true
tcp_reset: true
min_count: 8
reasons_deny: ["NO_SOCKET"]Scripts get $DROP_REASON, $PROTOCOL, $SPORT/$DPORT, $COUNT, $JSON. Metrics: netevd_ebpf_*. Full guide: docs/user/ebpf.md.
flowchart LR
Kernel[Netlink_plus_eBPF] --> State[NetworkState]
State --> Hooks[Hook_scripts]
State --> Routing[Policy_routing]
State --> API[REST_and_metrics]
- Sources — netlink multicast, backend D-Bus (or dhclient leases), optional eBPF ringbuf
- State — one
NetworkStatebehindArc<RwLock>, updated by Tokio tasks - Actions — matching
/etc/netevd/<event>.d/scripts, policy rules, optional DNS/hostname via D-Bus
| Directory | Fires when |
|---|---|
carrier.d/ / no-carrier.d/ |
Link up / down |
routable.d/ |
Full L3 connectivity |
link-added.d/ / link-removed.d/ |
Interface appears / disappears |
address-added.d/ |
Address configured |
drops.d/ / tcp-reset.d/ |
eBPF observe-only (opt-in) |
All 17 directories, env vars, and netevd.event.v1 JSON: docs/hooks-contract.md. Use 01- / 02- prefixes for order; non-zero exits are logged and do not block siblings.
Every script gets $LINK, $LINKINDEX, $STATE, $BACKEND, $ADDRESSES (plus $JSON / DHCP fields by backend).
List an interface under routing.policy_rules and netevd:
- Creates table
200 + ifindex - Adds
from <ip>/to <ip>lookup rules - Installs a default via that interface’s gateway
- Tears it down when addresses leave
$ ip rule list
32765: from 192.168.1.100 lookup 203
$ ip route show table 203
default via 192.168.1.1 dev eth1Minimal shape — full template: config/netevd.example.yaml.
system:
backend: "systemd-networkd" # or NetworkManager | dhclient
monitoring:
match_patterns: ["eth*", "wg*"]
exclude: ["lo", "docker*", "veth*"]
hooks:
debounce_ms: 50
routing:
policy_rules: ["eth1"]- Starts as root, drops to user
netevd CAP_NET_ADMINby default; eBPF builds also keepCAP_BPF,CAP_PERFMON,CAP_DAC_READ_SEARCH- Validated interface names / IPs / hostnames — no shell metacharacters
- Scripts exec’d directly (not
sh -c) - systemd hardening (
NoNewPrivileges,ProtectSystem=strict, …); eBPF re-allowsbpf/perf_event_open
Details: SECURITY.md.
| Metric | Typical |
|---|---|
| RSS idle | 3–5 MB |
| CPU idle | <1 % |
| Netlink event latency | <100 ms |
| Event → script | <10 ms |
| Throughput | 1000+ events/s |
Same port as Prometheus (default 9090):
curl -s localhost:9090/api/v1/status
curl -s localhost:9090/api/v1/interfaces
curl -s localhost:9090/api/v1/events
curl -s localhost:9090/metrics
curl -s localhost:9090/healthcargo build && cargo test && cargo clippy -- -D warnings
# eBPF unit tests (no CAP_BPF): cargo test --lib ebpf::| Community (this repo) | Enterprise | |
|---|---|---|
| Support | GitHub Issues | SLA · sales@zyvor.dev |
| Scope | Self-hosted hooks + policy routing | Production rollouts, platform integration |
| Platform | netevd | netctl, cloud-netconfig, HyperSDK |
Demo · Pricing · Contact · docs/enterprise.md
Maintained by Susant Sahani · Zyvor AI Labs. Community help: Issues · SECURITY.md.
Apache-2.0 — use, modify, and run in production subject to the LICENSE. Enterprise support and Zyvor products are licensed separately (sales@zyvor.dev).
