+++ 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. ```bash 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. ```bash sudo systemctl daemon-reload sudo systemctl enable --now ferrix ``` 4. Verify that the service is running. ```bash 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](#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: ```bash 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](@/docs/guides/rpki.md). ### 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](@/docs/guides/import-policy.md). ## 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. ```bash sudo bgp --check /etc/ferrix/server.textpb.new ``` 2. Replace the active configuration. ```bash sudo mv /etc/ferrix/server.textpb.new /etc/ferrix/server.textpb ``` 3. Restart the daemon with a graceful restart. ```bash 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](@/docs/guides/graceful-restart.md). To upgrade Ferrix: 1. Install the new executable. ```bash 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. ```bash sudo bgp --check /etc/ferrix/server.textpb ``` 3. Send the SIGUSR1 signal to the daemon. ```bash 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. ```bash 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. ```bash 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.