Files
infix/src/netd
Joachim Wiberg 221fea13a2 netd: watch conf.d with inotify, retry on backend failure
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>
2026-03-12 12:07:49 +01:00
..
2026-02-24 06:59:07 +01:00
2026-02-24 06:59:07 +01:00
2026-02-24 06:59:07 +01:00
2026-02-24 06:59:07 +01:00

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"
  • nexthop (required) - Next hop specification
    • IP address: "192.168.1.1" or "fe80::1"
    • Interface name: "eth0"
    • Blackhole: "blackhole", "reject", or "Null0"
  • 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 routing
  • default-metric (optional, default: 1) - Default route metric (1-16)
  • distance (optional, default: 120) - Administrative distance (1-255)
  • default-route (optional, default: false) - Originate default route
  • network (optional) - List of interfaces to enable RIP on
  • passive (optional) - List of passive interfaces (receive only)
  • neighbor (optional) - List of static RIP neighbor addresses
  • redistribute (optional) - List of route types to redistribute
    • Valid types: "connected", "static", "kernel", "ospf"
  • timers (optional) - RIP timer configuration subsection
    • update (default: 30) - Update timer in seconds
    • invalid (default: 180) - Invalid timer in seconds
    • flush (default: 240) - Flush timer in seconds
  • debug-events (optional, default: false) - Enable RIP event debugging
  • debug-packet (optional, default: false) - Enable RIP packet debugging
  • debug-kernel (optional, default: false) - Enable RIP kernel debugging
  • system (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

  1. netd parses config files
  2. Builds JSON config for FRR
  3. Sends via gRPC to mgmtd
  4. mgmtd distributes to backend daemons
  5. Executes system commands (if any)

Linux Backend Flow

  1. netd parses config files
  2. Opens rtnetlink socket
  3. Sends RTM_NEWROUTE/RTM_DELROUTE
  4. 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 operations
  • ERROR - Failures, errors
  • DEBUG - Detailed operation info (with -d)

Signal Handling

  • SIGHUP - Reload configuration
  • SIGTERM / SIGINT - Graceful shutdown

License

BSD-3-Clause

See Also