Files
klaster/ansible/roles/openwrt/tasks/bird.yml
T
Lumpiasty 5b026593ce
ci/woodpecker/push/flux-reconcile-source Pipeline was successful
ci/woodpecker/cron/renovate Pipeline was successful
lte failover
2026-05-27 23:40:33 +02:00

154 lines
5.4 KiB
YAML

---
# Configures BIRD2 on the D-Link as an iBGP peer of the MikroTik CRS418.
#
# Route exchange:
# D-Link → CRS: announces 0.0.0.0/0 and 2000::/3 when wwan0 is up.
# CRS installs these at BGP distance 200 (below the GPON
# static default at distance 1 — activates only on GPON failure).
#
# CRS → D-Link: announces connected routes (VLAN subnets), static routes
# (Tailscale, GPON default), and reflects k8s BGP routes.
# BIRD2 installs all of these into the kernel at metric 10.
#
# D-Link's own routing:
# - Kernel metric 10: BGP-learned routes from CRS (preferred)
# - Kernel metric 100: wwan QMI-assigned routes (fallback)
# No static default gateway on uplink — the default comes from BGP.
# When GPON fails, CRS withdraws the BGP default; D-Link falls back to wwan.
- name: Write BIRD2 configuration
community.openwrt.copy:
dest: /etc/bird.conf
mode: '0640'
owner: root
group: root
content: |
# BIRD2 — LTE failover BGP peer for MikroTik CRS418
# iBGP session, AS 65000, peer: 192.168.6.1 (CRS vlan6)
router id 192.168.6.2;
protocol device {
# Tracks interface up/down state via netlink.
# scan time is a periodic reconciliation fallback; real events are
# netlink-driven and processed immediately.
scan time 5;
}
# Announce directly connected prefixes into BIRD2's RIB so that
# next-hop resolution works for BGP routes received from CRS.
# Without this, 192.168.6.1 (CRS uplink) is unresolvable and all
# IPv4 BGP routes appear unreachable. Same for IPv6 uplink prefix.
protocol direct {
ipv4;
ipv6;
interface "eth0.6";
}
# Install BGP-learned routes from CRS into the kernel at metric 10.
# This is lower than the wwan QMI default (metric 100), so D-Link
# prefers the CRS path for its own outbound traffic when GPON is up.
# import none: BIRD2 does not read the kernel table, preventing
# wwan kernel routes from leaking into BGP.
protocol kernel k4 {
ipv4 {
import none;
export filter {
if proto = "crs" then {
krt_metric = 10;
accept;
}
reject;
};
};
}
protocol kernel k6 {
ipv6 {
import none;
export filter {
if proto = "crs" then {
krt_metric = 10;
accept;
}
reject;
};
};
}
# LTE default routes — exist only while wwan0 is up.
# BIRD2's device protocol tracks wwan0 via netlink; when the interface
# goes down the routes become unreachable and BGP withdraws them.
# Uses interface-name routing (no explicit gateway IP) which is correct
# for QMI raw-ip POINTOPOINT NOARP interfaces.
#
# Preference 50 is below BGP's default of 100 — these routes are only
# used by BIRD2 internally as a presence signal for BGP export, NOT for
# installing into the kernel as our active default route. The kernel
# already gets the wwan default at metric 100 via netifd/qmi.sh, and
# we want the BGP-learned default via CRS (kernel metric 10) to be
# preferred for D-Link's own outbound traffic when GPON is up.
protocol static lte_default {
ipv4 {
preference 50;
};
route 0.0.0.0/0 via "wwan0";
}
protocol static lte_default6 {
ipv6 {
preference 50;
};
route 2000::/3 via "wwan0";
}
protocol bgp crs {
description "MikroTik CRS418 — LTE failover signalling";
local 192.168.6.2 as 65000;
neighbor 192.168.6.1 as 65000;
hold time 30;
keepalive time 10;
ipv4 {
# Import all prefixes CRS announces (VLAN subnets, static routes,
# k8s BGP routes reflected via RR). Installed into kernel via k4.
import all;
# Export only the wwan-sourced LTE default route.
# BGP-learned CRS routes are never re-exported (iBGP split-horizon
# applies; BIRD2 also does not import CRS routes into its RIB from
# the kernel, so they cannot appear here).
export where proto = "lte_default";
};
ipv6 {
# CRS uses Extended Next Hop (RFC 5549) for IPv6 routes, advertising
# them with the IPv4 next-hop 192.168.6.1. The Linux kernel cannot
# install IPv6 routes with IPv4 next-hops. Accept the routes from BGP
# (we negotiated ENHE via "extended next hop yes") but rewrite the
# next-hop in the import filter to the CRS's native IPv6 address on
# vlan6 before they reach the kernel.
extended next hop yes;
import filter {
gw = 2001:470:61a3:600::1;
accept;
};
# Force our own native IPv6 address as the next-hop when advertising
# to CRS, otherwise BIRD2 also uses ENHE and CRS receives a route
# with ::ffff:192.168.6.2 which it can't resolve as an IPv6 next-hop.
export filter {
if proto = "lte_default6" then {
bgp_next_hop = 2001:470:61a3:600::2;
accept;
}
reject;
};
};
}
notify: Reload bird
- name: Enable and start BIRD2 service
community.openwrt.service:
name: bird
enabled: true
state: started