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>
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:
-
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 -
Install the unit file as
/etc/systemd/system/ferrix.service.{{ file(name="ferrix.service", lang="ini") }}
-
Enable and start the service.
sudo systemctl daemon-reload sudo systemctl enable --now ferrix -
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_endpointto 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
debuglevel, 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:
-
Validate the new configuration.
sudo bgp --check /etc/ferrix/server.textpb.new -
Replace the active configuration.
sudo mv /etc/ferrix/server.textpb.new /etc/ferrix/server.textpb -
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:
-
Install the new executable.
sudo install -m 0755 bgp /usr/local/bin/bgp.new sudo mv /usr/local/bin/bgp.new /usr/local/bin/bgpThe
mvcommand replaces the executable in one step, while the running daemon continues to use the previous one. -
Validate the configuration with the new executable.
sudo bgp --check /etc/ferrix/server.textpb -
Send the SIGUSR1 signal to the daemon.
sudo systemctl kill --signal=SIGUSR1 ferrixThe daemon leaves its routes in the kernel, closes its sessions, and exits with status 75. systemd starts the new executable after
RestartSec(5 seconds). -
Verify that the sessions are reestablished.
journalctl -u ferrix -fOn 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.