Two races could prevent DHCP-learned default routes from being installed at boot: 1. The signal from the DHCP client script could be lost leaving conf.d updated but frr.conf stale. 2. Even when the signal was received, 'vtysh -b' could fail because the FRR daemon chain (mgmtd→zebra→staticd) writes PID files before being fully operational, causing netd to give up with no retry. Fix both by refactoring netd to use libev: - Use an inotify watch of CONF_DIR, so netd reacts directly to file changes without depending on signal delivery. - On backend_apply() failure, schedule a retry in 5s and call pidfile() unconditionally so dependent services are not blocked waiting for the Finit condition 'pid/netd' to be satisfied. We also take this opportunity to rename /etc/netd/conf.d/ to /etc/net.d/ The extra /etc/netd/ directory level served no purpose — nothing else lives there. Flatten to /etc/net.d/ which reads more naturally as the drop-in directory for network configuration snippets. Also, reduce logging a bit since each netd backend already logs success or fail which is sufficient to know that a configuration change has been applied or not. Finally, with the new inotify processing in netd it's redundant to call 'initctl reload netd' from the udhcpc script, in fact it will only cause unnecessary overhead, so we drop it. Fixes #1438 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
netd - Network Daemon for Static Routes and RIP
A lightweight routing daemon that manages static routes and RIP routing protocol.
Features
- Static Routes - IPv4 and IPv6 route management
- RIP - Routing Information Protocol (RIPv2) support
- Dual Backend - FRR integration or standalone Linux kernel routing
- Simple Config - INI-style section-based configuration format
- Hot Reload - SIGHUP support for configuration updates
Building
With FRR Integration (Default)
Full routing support with FRR gRPC northbound API:
./configure
make
make install
Requirements:
- FRR with gRPC northbound support
- protobuf >= 3.0.0
- grpc++ >= 1.16.0
Note: The FRR gRPC protocol definition (grpc/frr-northbound.proto) is included in the netd source tree.
Features:
- Static routes via FRR staticd
- RIP routing protocol
- OSPF (via separate FRR config)
- System command execution
Standalone Linux Backend
Direct kernel routing without FRR:
./configure --without-frr
make
make install
Requirements:
- Linux kernel with rtnetlink support
Features:
- Static routes via rtnetlink
- No external dependencies
Limitations:
- No RIP/OSPF support
- No system commands
Configuration
netd uses libconfuse for configuration parsing, providing a clean and structured format.
Configuration files are placed in /etc/net.d/ with the .conf extension. Files are processed in alphabetical order.
Configuration Format
The configuration uses libconfuse syntax with sections and key-value pairs:
route {
prefix = "10.0.0.0/24"
nexthop = "192.168.1.1"
distance = 1
}
rip {
enabled = true
network = ["eth0", "eth1"]
}
Static Routes
Define static routes using route sections. Multiple route sections can be specified.
Route Section:
route {
prefix = "PREFIX/LEN"
nexthop = "NEXTHOP"
distance = DISTANCE
tag = TAG
}
Parameters:
prefix(required) - Network prefix with CIDR notation- IPv4:
"10.0.0.0/24" - IPv6:
"2001:db8::/32"
- IPv4:
nexthop(required) - Next hop specification- IP address:
"192.168.1.1"or"fe80::1" - Interface name:
"eth0" - Blackhole:
"blackhole","reject", or"Null0"
- IP address:
distance(optional, default: 1) - Administrative distance (1-255)tag(optional, default: 0) - Route tag (0-4294967295), used for route filtering/redistribution
Examples:
IPv4 route via gateway:
route {
prefix = "10.0.0.0/24"
nexthop = "192.168.1.1"
distance = 10
}
IPv6 route via interface:
route {
prefix = "2001:db8::/32"
nexthop = "eth0"
distance = 1
}
Blackhole route:
route {
prefix = "192.0.2.0/24"
nexthop = "blackhole"
}
RIP Configuration
Configure RIP routing protocol (requires FRR backend).
RIP Section:
rip {
enabled = BOOL
default-metric = VALUE
distance = VALUE
default-route = BOOL
network = [LIST]
passive = [LIST]
neighbor = [LIST]
redistribute = [LIST]
timers {
update = SECONDS
invalid = SECONDS
flush = SECONDS
}
debug-events = BOOL
debug-packet = BOOL
debug-kernel = BOOL
system = [LIST]
}
Parameters:
enabled(optional, default: false) - Enable RIP routingdefault-metric(optional, default: 1) - Default route metric (1-16)distance(optional, default: 120) - Administrative distance (1-255)default-route(optional, default: false) - Originate default routenetwork(optional) - List of interfaces to enable RIP onpassive(optional) - List of passive interfaces (receive only)neighbor(optional) - List of static RIP neighbor addressesredistribute(optional) - List of route types to redistribute- Valid types:
"connected","static","kernel","ospf"
- Valid types:
timers(optional) - RIP timer configuration subsectionupdate(default: 30) - Update timer in secondsinvalid(default: 180) - Invalid timer in secondsflush(default: 240) - Flush timer in seconds
debug-events(optional, default: false) - Enable RIP event debuggingdebug-packet(optional, default: false) - Enable RIP packet debuggingdebug-kernel(optional, default: false) - Enable RIP kernel debuggingsystem(optional) - List of system commands to execute after config application
Examples:
Basic RIP configuration:
rip {
enabled = true
network = ["eth0", "eth1"]
redistribute = ["connected"]
}
RIP with passive interface:
rip {
enabled = true
network = ["eth0", "eth1"]
passive = ["eth1"]
}
RIP with custom timers:
rip {
enabled = true
network = ["eth0"]
timers {
update = 15
invalid = 90
flush = 120
}
}
Configuration Files
Configuration files must be placed in /etc/net.d/ with the .conf extension:
/etc/net.d/
├── 10-static.conf # Static routes
├── 20-rip.conf # RIP configuration
└── 99-local.conf # Local overrides
Files are processed in alphabetical order. Use numeric prefixes to control processing order.
Lines starting with # are comments:
# This is a comment
route {
# This is also a comment
prefix = "10.0.0.0/24" # Inline comment
nexthop = "192.168.1.1"
}
Reloading Configuration
Signal netd to reload configuration:
# Using killall
sudo killall -HUP netd
netd validates configuration on reload. Check syslog for errors.
Architecture
┌─────────┐
│ confd │ Writes /etc/net.d/confd.conf
└────┬────┘
│ SIGHUP
▼
┌─────────┐
│ netd │ Parses config files
└────┬────┘
│
├──► FRR Backend (gRPC)
│ ├─► mgmtd
│ ├─► staticd (static routes)
│ └─► ripd (RIP protocol)
│
└──► Linux Backend (rtnetlink)
└─► Kernel routing table
FRR Backend Flow
- netd parses config files
- Builds JSON config for FRR
- Sends via gRPC to mgmtd
- mgmtd distributes to backend daemons
- Executes system commands (if any)
Linux Backend Flow
- netd parses config files
- Opens rtnetlink socket
- Sends RTM_NEWROUTE/RTM_DELROUTE
- Kernel updates routing table
Files
src/netd/
├── src/
│ ├── netd.c/h - Main daemon and data structures
│ ├── config.c/h - Config parser
│ ├── json_builder.c/h - FRR JSON config builder
│ ├── grpc_backend.cc/h - FRR gRPC backend
│ └── linux_backend.c/h - Linux rtnetlink backend
├── grpc/
│ └── frr-northbound.proto - gRPC protocol definition (copied from FRR)
├── configure.ac - Build configuration
├── Makefile.am - Build rules
├── netd.conf - Sample configuration
└── README.md - This file
API
Configuration Format
- libconfuse syntax with sections
route { }- Static route entries (multiple allowed)rip { }- RIP configuration (single section)
Supported Routes
- IPv4 and IPv6
- Gateway, interface, blackhole nexthops
- Administrative distance
- Route tags
RIP Features
- Network interfaces
- Passive interfaces
- Static neighbors
- Route redistribution
- Timer configuration
- Default route origination
- Debug commands
Logging
netd logs to syslog facility daemon:
# Debug mode (stderr)
netd -d
Log levels:
INFO- Configuration changes, route operationsERROR- Failures, errorsDEBUG- Detailed operation info (with-d)
Signal Handling
SIGHUP- Reload configurationSIGTERM/SIGINT- Graceful shutdown
License
BSD-3-Clause
See Also
- FRR Documentation - https://docs.frrouting.org/
- rtnetlink(7) - Linux routing socket API
- libconfuse Documentation - https://github.com/martinh/libconfuse