341 lines
11 KiB
Rust
341 lines
11 KiB
Rust
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<RouteInfo>,
|
|
}
|
|
|
|
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 = <NetlinkMessage<RouteNetlinkMessage>>::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 = <NetlinkMessage<RouteNetlinkMessage>>::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<u32> {
|
|
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)
|
|
}
|
|
}
|