A firewall for the router itself
A router needs a firewall for itself (the input chain) as much as any server. BGP listens on TCP 179 on every address, and anyone who can open a session with it can inject routes. The ruleset follows the house style of nftables and iptables on Debian: drop by default, accept replies, then exactly what the router needs.
- BGP: TCP 179, only from the two ISPs' addresses and only on their interfaces. Connections that
edgeitself opens are covered by the established rule. - OSPF: IP protocol 89 (it doesn't use TCP or UDP), only on the inside interface. The rule uses the number because the protocol name needs
/etc/protocols, which minimal systems may not have. - SSH from inside only, and the usual ICMP.
The forward chain lets routed traffic through, with one anti-spoofing rule: a packet arriving from an ISP with an office source address can't be genuine. ISP 1 tried, and the counter caught both packets.
Loading the ruleset doesn't disturb anything: the BGP sessions and the OSPF adjacency carry on, because their packets match the established rule or their own accept rules.
The rule you forget
Leave OSPF out, and nothing happens at first. Then, when OSPF's dead timer runs out (40 seconds by default from the last hello that got through), the adjacency dies: the journal logs Full -> Deleted (InactivityTimer), the policy counter shows the dropped hellos, and the core loses its default route, so the whole office is offline. Putting the rule back brings OSPF up again within seconds. The same thing happens to BGP if you forget TCP 179, but slower: an existing session survives until its hold timer expires, and then can't come back.
#!/usr/sbin/nft -f
# /etc/nftables.conf on edge: protect the router itself, let routed traffic through.
flush ruleset
define UPSTREAMS = { "eth0", "eth1" }
define INSIDE = "eth2"
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif lo accept
icmp type { echo-request, destination-unreachable, time-exceeded } accept
iifname $UPSTREAMS ip saddr { 192.0.2.0, 192.0.2.2 } tcp dport 179 accept comment "BGP from our two upstreams"
iifname $INSIDE ip protocol 89 accept comment "OSPF from the core"
iifname $INSIDE tcp dport 22 accept comment "SSH from inside"
counter comment "dropped by the policy"
}
chain forward {
type filter hook forward priority filter; policy accept;
iifname $UPSTREAMS ip saddr 203.0.113.0/24 counter drop comment "our own addresses can't arrive from outside"
}
}- Line 10: Drop anything to the router itself unless a rule allows it.
- Line 15: BGP: TCP 179, only from the two ISPs, only on their links.
- Line 16: OSPF is IP protocol 89, not TCP or UDP.
- Line 17: SSH only from inside.
- Lines 22–23: Routed traffic passes, except packets claiming to come from the office's own addresses.
root@edge:~# cp nftables.conf /etc/nftables.conf && nft -c -f /etc/nftables.conf && systemctl enable --now nftables 2>&1 | grep -c "Created symlink"; nft list chain inet filter input
1
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif "lo" accept
icmp type { destination-unreachable, echo-request, time-exceeded } accept
iifname { "eth0", "eth1" } ip saddr { 192.0.2.0, 192.0.2.2 } tcp dport 179 accept comment "BGP from our two upstreams"
iifname "eth2" ip protocol 89 accept comment "OSPF from the core"
iifname "eth2" tcp dport 22 accept comment "SSH from inside"
counter packets 0 bytes 0 comment "dropped by the policy"
}
}
root@edge:~# vtysh -c "show bgp summary" | grep -A3 "^Neighbor"; vtysh -c "show ip ospf neighbor" | grep Full
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc
192.0.2.0 4 64501 6 4 3 0 0 00:00:15 2 1 isp1 (primary)
192.0.2.2 4 64502 7 4 3 0 0 00:00:15 2 1 isp2 (backup)
10.255.0.2 1 Full/- 9.900s 30.098s 10.0.0.2 eth2:10.0.0.1 0 0 0
root@isp1:~# ip addr add 203.0.113.99/32 dev lo && ping -c 2 -W 1 -I 203.0.113.99 203.0.113.10 | tail -2; ip addr del 203.0.113.99/32 dev lo
--- 203.0.113.10 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss
root@edge:~# nft list chain inet filter forward | grep "our own"
iifname { "eth0", "eth1" } ip saddr 203.0.113.0/24 counter packets 2 bytes 168 drop comment "our own addresses can't arrive from outside"- Lines 1–15: Checked, loaded, and enabled at every boot.
- Lines 17–22: BGP and OSPF are unaffected.
- Lines 24–26: ISP 1 tries to send with an office source address…
- Lines 28–29: …and the forward chain drops it.
root@edge:~# nft -a list chain inet filter input | grep OSPF
iifname "eth2" ip protocol 89 accept comment "OSPF from the core" # handle 11
root@edge:~# date +%T; nft delete rule inet filter input handle 11
22:08:28
root@edge:~# date +%T; vtysh -c "show ip ospf neighbor"; nft list chain inet filter input | grep "dropped by"
22:09:13
Neighbor ID Pri State Up Time Dead Time Address Interface RXmtL RqstL DBsmL
counter packets 5 bytes 340 comment "dropped by the policy"
root@edge:~# journalctl -u frr --no-pager -o short-precise | grep "AdjChg" | tail -1
Nov 04 22:09:01.521323 edge ospfd[753]: [Y05P2-YJVXY] AdjChg: Nbr 10.255.0.2, NbrIP 10.0.0.2 (default) on eth2:10.0.0.1: Full -> Deleted (InactivityTimer)
root@core:~# vtysh -c "show ip route ospf" | grep "^O"
O 10.0.0.0/30 [110/10] is directly connected, eth0, weight 1, 00:07:12
O 10.255.0.2/32 [110/0] is directly connected, lo, weight 1, 00:07:12
O 203.0.113.0/26 [110/10] is directly connected, eth1, weight 1, 00:07:12
root@edge:~# systemctl reload nftables && sleep 12 && vtysh -c "show ip ospf neighbor" | grep -E "Neighbor|Full"
Neighbor ID Pri State Up Time Dead Time Address Interface RXmtL RqstL DBsmL
10.255.0.2 1 Full/- 4.083s 35.915s 10.0.0.2 eth2:10.0.0.1 1 0 0- Lines 4–5: The OSPF rule is deleted at 22:08:28.
- Lines 7–12: 45 seconds later: no OSPF neighbour, and dropped hellos on the counter.
- Lines 14–15: At 22:09:01, the dead timer ran out: Full -> Deleted.
- Lines 17–20: The core has lost its default route: no internet for the office.
- Lines 22–24: The rule is back, and the adjacency is Full again.
Check your understanding
You add a firewall to a running FRR router and forget TCP 179. When do the BGP sessions notice?
- Not at first: established sessions match
ct state established. They fail if they ever reset, and then can't come back. - Immediately, all of them.
- Never: BGP uses UDP.
- After OSPF's dead timer.