use anyhow::Result; use libc::if_nametoindex; use log::{debug, info}; use netlink_packet_route::route::RouteAddress; use std::ffi::CString; use std::net::Ipv4Addr; const MAIN_TABLE_ID: u8 = 254; // Route metrics - higher priority = lower number const FAILOVER_METRIC: u32 = 5; const PRIMARY_METRIC: u32 = 10; const SECONDARY_METRIC: u32 = 20; #[derive(Debug, Clone)] pub struct RouteInfo { pub gateway: Ipv4Addr, pub interface: String, pub metric: u32, } pub struct RouteManager { routes: Vec, } impl RouteManager { pub fn new() -> Self { RouteManager { routes: Vec::new() } } pub fn add_route(&mut self, gateway: Ipv4Addr, interface: String, metric: u32) -> Result<()> { let route_info = RouteInfo { gateway, interface: interface.clone(), metric, }; self.add_default_route_internal(&route_info)?; self.routes.push(route_info); info!( "Added route: {} via {} dev {} metric {}", Ipv4Addr::new(0, 0, 0, 0), gateway, interface, metric ); Ok(()) } pub fn remove_route(&mut self, gateway: Ipv4Addr, interface: &str, metric: u32) -> Result<()> { self.delete_default_route_internal(gateway, interface, metric)?; self.routes .retain(|r| !(r.gateway == gateway && r.interface == interface && r.metric == metric)); info!( "Removed route: {} via {} dev {} metric {}", Ipv4Addr::new(0, 0, 0, 0), gateway, interface, metric ); Ok(()) } pub fn set_primary_route(&mut self, gateway: Ipv4Addr, interface: String) -> Result<()> { // Remove existing routes for this interface if any if let Some(pos) = self.routes.iter().position(|r| r.interface == interface) { let existing_route = self.routes[pos].clone(); self.remove_route( existing_route.gateway, &existing_route.interface, existing_route.metric, )?; } // Add as primary route self.add_route(gateway, interface, PRIMARY_METRIC)?; Ok(()) } pub fn setup_initial_routes( &mut self, primary_gateway: Ipv4Addr, primary_interface: String, secondary_gateway: Ipv4Addr, secondary_interface: String, ) -> Result<()> { // Set primary route with metric 10 (default) self.set_primary_route(primary_gateway, primary_interface)?; // Set secondary route with metric 20 (lower priority) self.add_route(secondary_gateway, secondary_interface, SECONDARY_METRIC)?; Ok(()) } pub fn add_failover_route(&mut self, gateway: Ipv4Addr, interface: String) -> Result<()> { self.add_route(gateway, interface, FAILOVER_METRIC)?; Ok(()) } pub fn remove_failover_route(&mut self, gateway: Ipv4Addr, interface: String) -> Result<()> { self.remove_route(gateway, &interface, FAILOVER_METRIC)?; Ok(()) } #[allow(dead_code)] pub fn get_routes(&self) -> &[RouteInfo] { &self.routes } fn add_default_route_internal(&self, route_info: &RouteInfo) -> Result<()> { let index = get_interface_index(&route_info.interface)?; if index == 0 { return Err(anyhow::anyhow!( "Interface '{}' not found", route_info.interface )); } use netlink_packet_core::{ NLM_F_ACK, NLM_F_CREATE, NLM_F_REQUEST, NetlinkHeader, NetlinkMessage, NetlinkPayload, }; use netlink_packet_route::{ AddressFamily, RouteNetlinkMessage, route::RouteProtocol, route::RouteScope, route::{RouteAttribute, RouteHeader, RouteMessage, RouteType}, }; use netlink_sys::{Socket, SocketAddr, protocols::NETLINK_ROUTE}; let mut socket = Socket::new(NETLINK_ROUTE)?; let _port_number = socket.bind_auto()?.port_number(); socket.connect(&SocketAddr::new(0, 0))?; let route_msg_hdr = RouteHeader { address_family: AddressFamily::Inet, table: MAIN_TABLE_ID, destination_prefix_length: 0, // Default route protocol: RouteProtocol::Boot, scope: RouteScope::Universe, kind: RouteType::Unicast, ..Default::default() }; let mut route_msg = RouteMessage::default(); route_msg.header = route_msg_hdr; route_msg.attributes = vec![ RouteAttribute::Gateway(RouteAddress::Inet(route_info.gateway)), RouteAttribute::Oif(index), RouteAttribute::Priority(route_info.metric), ]; let mut nl_hdr = NetlinkHeader::default(); nl_hdr.flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; // Remove NLM_F_EXCL to allow updates let mut msg = NetlinkMessage::new( nl_hdr, NetlinkPayload::from(RouteNetlinkMessage::NewRoute(route_msg)), ); msg.finalize(); let mut buf = vec![0; 1024 * 8]; msg.serialize(&mut buf[..msg.buffer_len()]); // Debug: Log the netlink message being sent debug!("Netlink message being sent: {:?}", &buf[..msg.buffer_len()]); debug!( "Route addition attempt: gateway={}, interface={}, metric={}, interface_index={}", route_info.gateway, route_info.interface, route_info.metric, index ); socket.send(&buf, 0)?; let mut receive_buffer = Vec::with_capacity(256); let size = socket.recv(&mut receive_buffer, 0)?; if size == 0 { return Err(anyhow::anyhow!("No response from netlink")); } let bytes = &receive_buffer[..size]; let rx_packet = >::deserialize(bytes); match rx_packet { Ok(rx_packet) => { if let NetlinkPayload::Error(error_msg) = rx_packet.payload { if let Some(code) = error_msg.code { let error_code = code.get(); if error_code == -17 { // EEXIST - Route already exists, treat as success info!( "Route already exists: {} via {} dev {} metric {}", Ipv4Addr::new(0, 0, 0, 0), route_info.gateway, route_info.interface, route_info.metric ); } else { let error_str = match error_code { -1 => "EPERM - Operation not permitted (need root privileges)", -2 => "ENOENT - No such file or directory", -13 => "EACCES - Permission denied", -22 => "EINVAL - Invalid argument", _ => "Unknown error", }; return Err(anyhow::anyhow!( "Failed to add route: {} (code: {}): {:?}", error_str, error_code, error_msg )); } } debug!("Route added successfully"); } } Err(e) => { return Err(anyhow::anyhow!( "Failed to deserialize netlink message: {}", e )); } } Ok(()) } fn delete_default_route_internal( &self, gateway: Ipv4Addr, interface: &str, metric: u32, ) -> Result<()> { let index = get_interface_index(interface)?; if index == 0 { return Err(anyhow::anyhow!("Interface '{}' not found", interface)); } use netlink_packet_core::{ NLM_F_ACK, NLM_F_REQUEST, NetlinkHeader, NetlinkMessage, NetlinkPayload, }; use netlink_packet_route::{ AddressFamily, RouteNetlinkMessage, route::RouteProtocol, route::RouteScope, route::{RouteAttribute, RouteHeader, RouteMessage, RouteType}, }; use netlink_sys::{Socket, SocketAddr, protocols::NETLINK_ROUTE}; let mut socket = Socket::new(NETLINK_ROUTE)?; let _port_number = socket.bind_auto()?.port_number(); socket.connect(&SocketAddr::new(0, 0))?; let route_msg_hdr = RouteHeader { address_family: AddressFamily::Inet, table: MAIN_TABLE_ID, destination_prefix_length: 0, // Default route protocol: RouteProtocol::Boot, scope: RouteScope::Universe, kind: RouteType::Unicast, ..Default::default() }; let mut route_msg = RouteMessage::default(); route_msg.header = route_msg_hdr; route_msg.attributes = vec![ RouteAttribute::Gateway(RouteAddress::Inet(gateway)), RouteAttribute::Oif(index), RouteAttribute::Priority(metric), ]; let mut nl_hdr = NetlinkHeader::default(); nl_hdr.flags = NLM_F_REQUEST | NLM_F_ACK; let mut msg = NetlinkMessage::new( nl_hdr, NetlinkPayload::from(RouteNetlinkMessage::DelRoute(route_msg)), ); msg.finalize(); let mut buf = vec![0; 1024 * 8]; msg.serialize(&mut buf[..msg.buffer_len()]); // Debug: Log the netlink message being sent debug!( "Netlink delete message being sent: {:?}", &buf[..msg.buffer_len()] ); debug!( "Route deletion attempt: gateway={}, interface={}, metric={}, interface_index={}", gateway, interface, metric, index ); socket.send(&buf, 0)?; let mut receive_buffer = Vec::with_capacity(256); let size = socket.recv(&mut receive_buffer, 0)?; if size == 0 { return Err(anyhow::anyhow!("No response from netlink")); } let bytes = &receive_buffer[..size]; let rx_packet = >::deserialize(bytes); match rx_packet { Ok(rx_packet) => { if let NetlinkPayload::Error(error_msg) = rx_packet.payload { if error_msg.code.is_some() { return Err(anyhow::anyhow!("Failed to delete route: {:?}", error_msg)); } debug!("Route deleted successfully"); } } Err(e) => { return Err(anyhow::anyhow!( "Failed to deserialize netlink message: {}", e )); } } Ok(()) } } fn get_interface_index(iface_name: &str) -> Result { let c_name = CString::new(iface_name)?; let index = unsafe { if_nametoindex(c_name.as_ptr()) }; if index == 0 { Err(anyhow::anyhow!("Interface '{}' not found", iface_name)) } else { Ok(index) } }