Into the kernel, and OSPF inside
FRR's routing decisions only matter once they're in the kernel, because the kernel forwards the packets. Zebra installs every selected route, and marks each with where it came from: ip route shows proto bgp for the default and the web server's network, proto ospf for the server LAN and the core's loopback, proto static for the blackhole, and proto kernel for the directly connected links. ip route get answers the everyday question "where would a packet to this address go?" with the kernel's actual answer.
So ordinary Linux tools work on an FRR router: ip route, ping, traceroute and tcpdump see exactly what the routing protocols set up.
OSPF towards the core
Inside the office, edge and core are OSPF neighbours on a point-to-point link (no designated router election, so it reaches Full quickly). The core announces its server LAN; edge announces a default route because it has one from BGP. On core, that default shows as an OSPF route via edge.
The result, end to end: the server's traceroute to the web server goes through the core, edge, and ISP 1, and the web server's log shows the request arriving from the server's own address, 203.0.113.10. No NAT anywhere: the office's addresses are its own, and the internet routes them back by BGP.
root@edge:~# ip route
default nhid 13 via 192.0.2.0 dev eth0 proto bgp metric 20
10.0.0.0/30 dev eth2 proto kernel scope link src 10.0.0.1
10.255.0.2 nhid 15 via 10.0.0.2 dev eth2 proto ospf metric 20
192.0.2.0/31 dev eth0 proto kernel scope link src 192.0.2.1
192.0.2.2/31 dev eth1 proto kernel scope link src 192.0.2.3
198.51.100.0/24 nhid 13 via 192.0.2.0 dev eth0 proto bgp metric 20
203.0.113.0/26 nhid 15 via 10.0.0.2 dev eth2 proto ospf metric 20
blackhole 203.0.113.0/24 proto static metric 20
root@edge:~# ip route show proto bgp; ip route get 198.51.100.80
default nhid 13 via 192.0.2.0 dev eth0 metric 20
198.51.100.0/24 nhid 13 via 192.0.2.0 dev eth0 metric 20
198.51.100.80 via 192.0.2.0 dev eth0 src 192.0.2.1 uid 0
cache - Line 2: The default route from BGP, via ISP 1.
- Line 4: The core's loopback, from OSPF.
- Line 8: The server LAN, from OSPF.
- Line 9: The blackhole for the office /24, from staticd.
- Lines 11–15: Only the BGP routes, and the kernel's answer for one address.
root@edge:~# vtysh -c "show ip ospf neighbor"
Neighbor ID Pri State Up Time Dead Time Address Interface RXmtL RqstL DBsmL
10.255.0.2 1 Full/- 6.406s 33.592s 10.0.0.2 eth2:10.0.0.1 0 0 0
root@core:~# vtysh -c "show ip route ospf"
Codes: K - kernel route, C - connected, L - local, S - static,
R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR,
f - OpenFabric, t - Table-Direct,
> - selected route, * - FIB route, q - queued, r - rejected, b - backup
t - trapped, o - offload failure
O>* 0.0.0.0/0 [110/10] via 10.0.0.1, eth0, weight 1, 00:00:01
O 10.0.0.0/30 [110/10] is directly connected, eth0, weight 1, 00:00:37
O 10.255.0.2/32 [110/0] is directly connected, lo, weight 1, 00:00:37
O 203.0.113.0/26 [110/10] is directly connected, eth1, weight 1, 00:00:37
root@server:~# traceroute -n -q 1 -w 1 198.51.100.80; curl -s -o /dev/null -w "%{http_code}\n" http://198.51.100.80/
traceroute to 198.51.100.80 (198.51.100.80), 30 hops max, 60 byte packets
1 203.0.113.1 0.088 ms
2 10.0.0.1 0.032 ms
3 192.0.2.0 0.025 ms
4 198.51.100.80 0.051 ms
200
root@web:~# tail -1 /tmp/web.log
203.0.113.10 - - [04/Nov/2026 22:02:38] "GET / HTTP/1.1" 200 -- Line 4: The core is a full neighbour on the point-to-point link.
- Line 15: On core: a default route via edge, an external OSPF route.
- Lines 20–26: From the server: core, edge, ISP 1, the web server. The page loads.
- Lines 28–29: The web server sees the server's own address: no NAT.
Check your understanding
ip route on edge shows proto bgp on the default route. What put it there?
- Zebra, FRR's daemon that installs FRR's chosen routes in the kernel.
- systemd-networkd, from the .network files.
- bgpd writes directly into the kernel.
- The ISP pushed it with DHCP.