Install best routes in the Linux kernel over netlink
New crate bgp-fib. It follows the routing table's best routes and keeps
the kernel's routing table the same, and learns the machine's interfaces
and addresses from the kernel, which is what says whether a next hop can
be reached.
- netlink: the rtnetlink messages used (routes, addresses, links, errors),
as bytes and back. No I/O; property tested and fuzzed.
- interfaces: what is directly reachable, and from which interface.
- plan: what to ask the kernel for, given the best routes and what the
kernel has. No I/O; tested against a model kernel.
- kernel (Linux only): the sockets and the task. Requests go in batches;
routes of ours found at startup are kept if still right and removed if
not; a change of interfaces has every route worked out again; a clean
stop removes our routes.
Only routes marked proto bgp with the configured table and metric are
ever touched. A best route is installed if its next hop is a neighbor;
next hops further away are not resolved through other routes yet, and
such prefixes are counted and shown instead.
Configured with `kernel { table metric }`; off by default. The daemon
needs CAP_NET_ADMIN and stops if the kernel refuses it. On shutdown it
closes sessions and then removes its routes.
/api/kernel and the status page show installed, unresolved and refused
counts, the last error and the interfaces; five bgp_kernel_* metrics.
Testing: scripts/kernel-tests.sh runs, in network namespaces of their
own and with only the capabilities the daemon needs, the installer
against `ip route`, the daemon with a peer, and two daemons that route
for each other with pings across over IPv4 and IPv6.
scripts/linux-vm.sh runs that, or anything else, in a Lima VM from
macOS. CI runs the kernel tests and fuzzes the netlink decoder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
1f84235e87
commit
1fc44798b7
38 files changed
+3745
-16
No files matched your search
@@ -171,6 +171,28 @@ impl AttrSet {
|
||||
&self.wire
|
||||
}
|
||||
|
||||
/// The route's next hop: the one that came in MP_REACH_NLRI, or else
|
||||
/// the NEXT_HOP attribute. Read off the stored bytes without building
|
||||
/// anything else, for those who ask it of every route.
|
||||
pub fn next_hop(&self) -> Option<NlriNextHop> {
|
||||
let mut plain = None;
|
||||
let mut wire = &*self.wire;
|
||||
while let Ok((attribute, rest)) = split_attribute(wire) {
|
||||
match attribute.type_code {
|
||||
attr_type::MP_REACH_NLRI => {
|
||||
// Kept as the next hop's length and the next hop.
|
||||
let (length, address) = attribute.value.split_first()?;
|
||||
return NlriNextHop::from_bytes(address)
|
||||
.filter(|_| usize::from(*length) == address.len());
|
||||
}
|
||||
attr_type::NEXT_HOP => plain = NlriNextHop::from_bytes(attribute.value),
|
||||
_ => {}
|
||||
}
|
||||
wire = rest;
|
||||
}
|
||||
plain
|
||||
}
|
||||
|
||||
/// Puts the AS numbers of the route's path in `out`, in place of what
|
||||
/// was there: in the order of the path, the neighbor's first and the
|
||||
/// origin's last, sets and all. Returns whether the path has a set in
|
||||
@@ -511,6 +533,30 @@ mod tests {
|
||||
assert_eq!(store.intern(&attrs, None).unwrap().key().med, Some(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_next_hop_is_read_off_a_set() {
|
||||
let store = AttrStore::new();
|
||||
let plain = store.intern(&attributes(vec![3333]), None).unwrap();
|
||||
assert_eq!(
|
||||
plain.next_hop(),
|
||||
Some(NlriNextHop::Ipv4(Ipv4Addr::new(192, 0, 2, 1)))
|
||||
);
|
||||
// The one that came with the routes counts, where there is one.
|
||||
let with_link_local = NlriNextHop::Ipv6WithLl {
|
||||
global: "2001:db8::1".parse().unwrap(),
|
||||
link_local: "fe80::1".parse().unwrap(),
|
||||
};
|
||||
let v6 = store
|
||||
.intern(&attributes(vec![3333]), Some(&with_link_local))
|
||||
.unwrap();
|
||||
assert_eq!(v6.next_hop(), Some(with_link_local));
|
||||
// A route of our own making may have none.
|
||||
let none = store
|
||||
.intern(&[PathAttribute::Origin(OriginPathAttribute::IGP)], None)
|
||||
.unwrap();
|
||||
assert_eq!(none.next_hop(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_path_is_read_off_a_set_as_it_really_is() {
|
||||
use bgp_packet::message::{AsPathAttribute, AsPathSegment};
|
||||
|
||||
@@ -352,6 +352,13 @@ impl RibTable {
|
||||
/// with it, and may then be given to another. Whoever was given it must
|
||||
/// not use it after that: the table cannot tell a late announcement
|
||||
/// from one of the number's next owner.
|
||||
/// What is known of the peer with this number, if it is in use. Not to
|
||||
/// be called from a visitor that is given routes with a shard held.
|
||||
pub fn peer(&self, index: PeerIndex) -> Option<PeerInfo> {
|
||||
let peers = self.peers.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
peers.get(index.as_usize()).copied().flatten()
|
||||
}
|
||||
|
||||
pub fn add_peer(&self, info: PeerInfo) -> Result<PeerIndex, TooManyPeers> {
|
||||
let mut peers = self.peers.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
let index = match peers.iter().position(Option::is_none) {
|
||||
|
||||
Reference in new issue
Block a user