Files
Rayhaan JaufeerallyandClaude Opus 5.5 65f136048e Graceful restart (RFC 4724, RFC 8538)
A peer that restarts keeps its routes with us, and we keep ours with our
peers while we restart, so that a restart withdraws nothing across the
network and the machine goes on forwarding. On by default for every
peer; `graceful_restart` sets the restart time we give, caps how long a
peer's routes are kept, or turns it off.

When a peer restarts: its routes are marked stale in the routing table,
and chosen as before, for at most its restart time. When it is back, what
it sends again is fresh, and the stale routes of a family go at its
End-of-RIB marker, or after the cap; at once for a family whose
forwarding it did not keep. Only a connection that failed counts as a
restart, and, with RFC 8538 agreed, an expired hold timer or a
NOTIFICATION other than a Hard Reset. The stale mark is a bit of the
route's word that selection never looks at.

When we restart: SIGUSR1 leaves the kernel's routes where they are,
closes the sessions without a word where graceful restart is agreed, and
exits with status 75, on which the systemd unit starts us again. The next
start finds the routes in the kernel, says so in its OPENs (R, and F where
forwarding was kept), and has them go once every peer has sent its
End-of-RIB. Our own End-of-RIB markers for learned routes wait for that
too. SIGTERM still stops for good, now with a Hard Reset where RFC 8538 is
agreed, as are errors of ours.

The status page and API show what was agreed with each peer, the routes
kept while a peer restarts, and which routes are stale. Tested against
FRR and BIRD in a new phase of the interoperability lab, and with routes
kept in a real kernel over a restart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:43:47 +02:00

7.9 KiB

+++ title = "Deploying Ferrix" description = "Describes how to run Ferrix as a systemd service, secure the hosts and interfaces around it, change its configuration, and restart or upgrade it without withdrawing its routes." weight = 1

[extra] nav = "Deployment" +++

This topic describes how to deploy Ferrix on a Linux router as a systemd service, and the security measures that a production deployment requires.

Running Ferrix as a systemd Service

The provided systemd unit runs the daemon as an unprivileged user, with only the capabilities that it requires, and restricts its access to the rest of the system.

To install the service:

  1. Create the system user and the configuration directory.

    sudo useradd --system --no-create-home --shell /usr/sbin/nologin ferrix
    sudo install -d -m 0750 -o root -g ferrix /etc/ferrix
    sudo install -m 0640 -o root -g ferrix server.textpb /etc/ferrix/server.textpb
    
  2. Install the unit file as /etc/systemd/system/ferrix.service.

    {{ file(name="ferrix.service", lang="ini") }}

  3. Enable and start the service.

    sudo systemctl daemon-reload
    sudo systemctl enable --now ferrix
    
  4. Verify that the service is running.

    systemctl status ferrix
    journalctl -u ferrix -f
    

The ExecStartPre command validates the configuration before the daemon starts. If the configuration is invalid, the service does not start, and the journal contains the error.

When the daemon exits with status 75, after a graceful restart, systemd starts it again (RestartForceExitStatus=75). See Upgrading Ferrix.

The sandboxing options in the unit have been tested with kernel route installation enabled. The command systemd-analyze security ferrix rates the unit's exposure level as 2.1 (OK).

Securing the Deployment

HTTP Interface

Caution

The status page and HTTP API do not require authentication. They expose the configuration, all received routes, and the state of every neighbor. Bind http_endpoint to a loopback address, or place it behind a reverse proxy that requires authentication.

BGP Port Filtering

Ferrix closes connections from addresses that are not configured neighbors before it reads from them. Ferrix does not support TCP-AO or TCP MD5 authentication. Configure the host firewall to accept connections to TCP port 179 only from neighbor addresses.

The following nftables rules, in the input chain of an inet filter table, drop BGP connections from any other address:

nft add rule inet filter input tcp dport 179 ip saddr != { 203.0.113.0, 203.0.113.2 } drop
nft add rule inet filter input tcp dport 179 ip6 saddr != { 2001:db8:ffff:1::1 } drop

RPKI Cache Connection

The RTR connection is not authenticated. Run the RPKI validator on the same host or on a trusted network. See RPKI.

Metrics Collection

Collect metrics from /metricz over the loopback interface, or through the authenticating reverse proxy.

Neighbor Policy

Configure max_prefixes and role for every EBGP neighbor, and prefix and origin AS lists for every neighbor whose routes are known. See Import Policy.

Logging

The daemon writes its log to standard output. Under systemd, the log is stored in the journal. When standard output is not a terminal, the log contains no color codes.

The --level option sets the minimum level for the daemon's own messages. The RUST_LOG environment variable sets the level for other components. Table 1 lists the messages at each level.

Level Messages
info (default) Session state changes, the first rejected route from each neighbor and at most one per minute thereafter, and kernel route installation state
debug Additionally, one message for each UPDATE received and each route advertised

Note

At the debug level, a full routing table produces millions of log messages.

Ferrix rate-limits log messages that a neighbor can trigger, such as messages about malformed attributes or rejected connections. Each occurrence is counted in a metric. The log contains at most one message for each interval, with the number of suppressed occurrences.

Changing the Configuration

Ferrix reads its configuration at startup only. To change the configuration:

  1. Validate the new configuration.

    sudo bgp --check /etc/ferrix/server.textpb.new
    
  2. Replace the active configuration.

    sudo mv /etc/ferrix/server.textpb.new /etc/ferrix/server.textpb
    
  3. Restart the daemon with a graceful restart.

    sudo systemctl kill --signal=SIGUSR1 ferrix
    

Upgrading Ferrix

A graceful restart (RFC 4724) restarts the daemon without withdrawing its routes. The router continues to forward traffic during the restart, and neighbors on which graceful restart is in use retain the routes of Ferrix. See Graceful Restart.

To upgrade Ferrix:

  1. Install the new executable.

    sudo install -m 0755 bgp /usr/local/bin/bgp.new
    sudo mv /usr/local/bin/bgp.new /usr/local/bin/bgp
    

    The mv command replaces the executable in one step, while the running daemon continues to use the previous one.

  2. Validate the configuration with the new executable.

    sudo bgp --check /etc/ferrix/server.textpb
    
  3. Send the SIGUSR1 signal to the daemon.

    sudo systemctl kill --signal=SIGUSR1 ferrix
    

    The daemon leaves its routes in the kernel, closes its sessions, and exits with status 75. systemd starts the new executable after RestartSec (5 seconds).

  4. Verify that the sessions are reestablished.

    journalctl -u ferrix -f
    

    On the status page, the neighbor table shows the restarted sessions, and the kernel section shows the routes kept from the previous run until every neighbor has sent its routes.

The restart must complete within the restart time that Ferrix advertises (restart_time_seconds, default: 120 seconds). Otherwise, the neighbors remove the routes of Ferrix.

Restart Behavior

Table 2 lists how the daemon stops, and the effect on its neighbors and on forwarding.

Event Neighbors Kernel routes
SIGUSR1 Neighbors on which graceful restart is in use retain the routes of Ferrix for the restart time. Other neighbors receive a Cease NOTIFICATION message with the subcode Administrative Shutdown. Retained
SIGTERM (systemctl stop, systemctl restart) Every neighbor receives a Cease NOTIFICATION message, with the subcode Hard Reset where the neighbor supports RFC 8538, and withdraws the routes of Ferrix. Removed
Unexpected termination Neighbors on which graceful restart is in use retain the routes of Ferrix for the restart time. Retained

At startup, Ferrix retains the routes that a previous run left in the kernel until every neighbor has sent its routes, or for stale_seconds (default: 120 seconds), and then removes the routes that no neighbor advertised again.

Warning

A stop with SIGTERM interrupts forwarding through the router until the sessions are reestablished and the routing tables are exchanged again. For a full routing table, this takes several seconds. To restart the daemon, use SIGUSR1.

Running Ferrix in a Container

The container image built from the repository's Dockerfile runs bgp /etc/ferrix/server.textpb --level info. The container requires the host network namespace and the configuration directory. If kernel route installation is enabled, it also requires the NET_ADMIN capability.

docker run -d --name ferrix --restart unless-stopped \
  --network host --cap-add NET_ADMIN \
  -v /etc/ferrix:/etc/ferrix:ro \
  ferrix

Note

The container image runs the daemon as root. Because the container uses the host network namespace, the systemd service is the recommended deployment method on a router.