use async_trait::async_trait; use bgp_packet::{constants::AddressFamilyIdentifier, nlri::NLRI}; use eyre::{eyre, Result}; use futures::TryStreamExt; use netlink_packet_route::route::RouteAddress; use netlink_packet_route::route::RouteAttribute; use netlink_packet_route::route::RouteHeader; use netlink_packet_route::route::RouteMessage; use netlink_packet_route::route::RouteProtocol; use netlink_packet_route::route::RouteType; use netlink_packet_route::AddressFamily as NetlinkAddressFamily; use rtnetlink::IpVersion; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::{convert::TryInto, io::ErrorKind}; use super::southbound_interface::SouthboundInterface; /// NetlinkConnector implements methods to read/update Linux networking stuff including /// routes and link level info. pub struct NetlinkConnector { handle: rtnetlink::Handle, table: Option, } #[async_trait] impl SouthboundInterface for NetlinkConnector { async fn route_add( &mut self, address_family: AddressFamilyIdentifier, prefix: NLRI, nexthop: IpAddr, ) -> Result<()> { let route = self.handle.route(); match address_family { AddressFamilyIdentifier::Ipv6 => { let prefix_len = prefix.prefixlen; let addr: Ipv6Addr = match prefix.try_into()? { IpAddr::V6(addr) => addr, _ => { return Err(eyre::Error::from(std::io::Error::new( ErrorKind::InvalidInput, "Got non-IPv6 address from NLRI", ))) } }; let gw_addr: Ipv6Addr = match nexthop.clone().try_into()? { IpAddr::V6(addr) => addr, _ => { return Err(eyre::Error::from(std::io::Error::new( ErrorKind::InvalidInput, "Got non-IPv6 gateway for IPv6 NLRI", ))) } }; let mut mutation = route .add() .v6() .destination_prefix(addr, prefix_len) .gateway(gw_addr); if let Some(table_id) = self.table { mutation = mutation.table_id(table_id.try_into().unwrap()); } mutation.execute().await.map_err(|e| eyre::Error::from(e)) } AddressFamilyIdentifier::Ipv4 => { let prefix_len = prefix.prefixlen; let addr: Ipv4Addr = match prefix.clone().try_into()? { IpAddr::V4(addr) => addr, _ => { return Err(eyre::Error::from(std::io::Error::new( ErrorKind::InvalidInput, "Got non-IPv4 address from NLRI", ))) } }; let gw_addr = match nexthop.clone().try_into()? { IpAddr::V4(addr) => addr, _ => { return Err(eyre::Error::from(std::io::Error::new( ErrorKind::InvalidInput, "Got non-IPv4 gateway for IPv4 NLRI", ))) } }; let mut mutation = route .add() .v4() .destination_prefix(addr, prefix_len) .gateway(gw_addr); if let Some(table_id) = self.table { mutation = mutation.table_id(table_id.try_into().unwrap()); } mutation.execute().await.map_err(|e| eyre::Error::from(e)) } } } async fn route_del(&mut self, prefix: NLRI, nexthop: IpAddr) -> Result<()> { let rt_handle = self.handle.route(); let destination = match prefix.afi { AddressFamilyIdentifier::Ipv4 => { RouteAddress::Inet(prefix.clone().try_into().map_err(|e: String| eyre!(e))?) } AddressFamilyIdentifier::Ipv6 => { RouteAddress::Inet6(prefix.clone().try_into().map_err(|e: String| eyre!(e))?) } }; let nexthop = match nexthop { IpAddr::V4(ipv4) => RouteAddress::Inet(ipv4), IpAddr::V6(ipv6) => RouteAddress::Inet6(ipv6), }; let header = RouteHeader { address_family: match prefix.afi { AddressFamilyIdentifier::Ipv4 => NetlinkAddressFamily::Inet, AddressFamilyIdentifier::Ipv6 => NetlinkAddressFamily::Inet6, }, destination_prefix_length: prefix.prefixlen, table: self.table.unwrap_or(0) as u8, protocol: RouteProtocol::Bgp, kind: RouteType::Unicast, ..Default::default() }; let mut rt_msg: RouteMessage = Default::default(); rt_msg.header = header; rt_msg.attributes = vec![ RouteAttribute::Destination(destination), RouteAttribute::Gateway(nexthop), ]; rt_handle .del(rt_msg) .execute() .await .map_err(|e| eyre::Error::from(e)) } } impl NetlinkConnector { pub async fn new(table: Option) -> Result { let (connection, handle, _) = rtnetlink::new_connection()?; tokio::spawn(connection); Ok(NetlinkConnector { handle, table }) } pub async fn dump_routes( &mut self, address_family: AddressFamilyIdentifier, table: Option, ) -> Result, rtnetlink::Error> { let mut req = self.handle.route().get(match address_family { AddressFamilyIdentifier::Ipv4 => IpVersion::V4, AddressFamilyIdentifier::Ipv6 => IpVersion::V6, }); if let Some(table_id) = table { req.message_mut() .attributes .push(RouteAttribute::Table(table_id)); } req.execute().try_collect().await } }