From b83903b0ea6f640f8bfcce10197aba14197303c9 Mon Sep 17 00:00:00 2001 From: Shivang K Raghuvanshi Date: Sat, 8 Nov 2025 23:45:08 +0530 Subject: [PATCH] refactor(netlink): Generalize socket for multi-protocol support The existing `netlink.rs` was specific to `NETLINK_ROUTE`. To prepare for adding `NETLINK_NETFILTER` support for conntrack, this commit refactors the netlink socket implementation to be generic. - A new generic `Socket

` is introduced in `src/network/netlink.rs` to handle common send/receive logic. - All routing-specific code, types, and functions are moved to a new `src/network/netlink_route.rs`, which now uses `Socket`. - All imports and type signatures across the codebase have been updated to use this new structure. This is a pure refactoring with no functional changes. Signed-off-by: Shivang K Raghuvanshi --- examples/host-device-plugin.rs | 8 +- src/commands/setup.rs | 10 +- src/dhcp_proxy/dhcp_service.rs | 5 +- src/dhcp_proxy/ip.rs | 12 +- src/network/bridge.rs | 65 ++-- src/network/core_utils.rs | 28 +- src/network/dhcp.rs | 8 +- src/network/driver.rs | 8 +- src/network/internal_types.rs | 4 +- src/network/mod.rs | 3 + src/network/netlink.rs | 558 ++++----------------------------- src/network/netlink_route.rs | 488 ++++++++++++++++++++++++++++ src/network/plugin.rs | 6 +- src/network/vlan.rs | 24 +- src/test/netlink.rs | 20 +- 15 files changed, 654 insertions(+), 593 deletions(-) create mode 100644 src/network/netlink_route.rs diff --git a/examples/host-device-plugin.rs b/examples/host-device-plugin.rs index 3fafd1c..ff56927 100644 --- a/examples/host-device-plugin.rs +++ b/examples/host-device-plugin.rs @@ -5,7 +5,7 @@ use std::{collections::HashMap, os::fd::AsFd}; use netavark::{ network::{ core_utils::{open_netlink_sockets, CoreUtils}, - netlink, types, + netlink_route, types, }, new_error, plugin::{Info, Plugin, PluginExec, API_VERSION}, @@ -41,7 +41,9 @@ impl Plugin for Exec { let name = opts.network.network_interface.unwrap_or_default(); - let link = host.netlink.get_link(netlink::LinkID::Name(name.clone()))?; + let link = host + .netlink + .get_link(netlink_route::LinkID::Name(name.clone()))?; let mut mac_address = String::from(""); for nla in link.attributes { @@ -98,7 +100,7 @@ impl Plugin for Exec { let name = opts.network.network_interface.unwrap_or_default(); - let link = netns.netlink.get_link(netlink::LinkID::Name(name))?; + let link = netns.netlink.get_link(netlink_route::LinkID::Name(name))?; netns .netlink diff --git a/src/commands/setup.rs b/src/commands/setup.rs index d83c219..2768a3a 100644 --- a/src/commands/setup.rs +++ b/src/commands/setup.rs @@ -4,7 +4,8 @@ use crate::dns::aardvark::Aardvark; use crate::error::{NetavarkError, NetavarkResult}; use crate::firewall; use crate::network::driver::{get_network_driver, DriverInfo, NetworkDriver}; -use crate::network::netlink::{self, LinkID}; +use crate::network::netlink::Socket; +use crate::network::netlink_route::{LinkID, NetlinkRoute}; use crate::network::{self}; use crate::network::{core_utils, types}; @@ -160,8 +161,11 @@ impl Setup { } } -fn teardown_drivers<'a, I>(drivers: I, host: &mut netlink::Socket, netns: &mut netlink::Socket) -where +fn teardown_drivers<'a, I>( + drivers: I, + host: &mut Socket, + netns: &mut Socket, +) where I: Iterator>, { for driver in drivers { diff --git a/src/dhcp_proxy/dhcp_service.rs b/src/dhcp_proxy/dhcp_service.rs index 79bd82d..2acdf31 100644 --- a/src/dhcp_proxy/dhcp_service.rs +++ b/src/dhcp_proxy/dhcp_service.rs @@ -1,5 +1,6 @@ use std::{net::Ipv4Addr, sync::Arc}; +use crate::network::netlink_route::{LinkID, Route}; use log::debug; use mozim::{DhcpV4Client, DhcpV4Config, DhcpV4Lease as MozimV4Lease, DhcpV4State}; use tokio::sync::Mutex; @@ -11,7 +12,7 @@ use crate::{ lib::g_rpc::{Lease as NetavarkLease, NetworkConfig}, }, error::{ErrorWrap, NetavarkError, NetavarkResult}, - network::{core_utils, netlink::Route}, + network::core_utils, wrap, }; @@ -257,7 +258,7 @@ fn update_lease_ip( if new_net != old_net { let link = sock - .get_link(crate::network::netlink::LinkID::Name(interface.to_string())) + .get_link(LinkID::Name(interface.to_string())) .wrap("get interface in netns")?; sock.add_addr(link.header.index, &ipnet::IpNet::V4(new_net)) .wrap("add new addr")?; diff --git a/src/dhcp_proxy/ip.rs b/src/dhcp_proxy/ip.rs index 131bad5..19156e9 100644 --- a/src/dhcp_proxy/ip.rs +++ b/src/dhcp_proxy/ip.rs @@ -8,8 +8,8 @@ pub use crate::dhcp_proxy::lib::g_rpc::{Lease as NetavarkLease, Lease}; pub use crate::dhcp_proxy::types::{CustomErr, ProxyError}; use crate::network::core_utils; -use crate::network::netlink; use crate::network::netlink::Socket; +use crate::network::netlink_route::{LinkID, NetlinkRoute}; use ipnet::IpNet; use log::debug; use std::net::{IpAddr, Ipv4Addr}; @@ -34,8 +34,8 @@ trait Address { fn new(l: &Lease, interface: &str) -> Result where Self: Sized; - fn add_ip(&self, nls: &mut Socket) -> Result<(), ProxyError>; - fn add_gws(&self, nls: &mut Socket) -> Result<(), ProxyError>; + fn add_ip(&self, nls: &mut Socket) -> Result<(), ProxyError>; + fn add_gws(&self, nls: &mut Socket) -> Result<(), ProxyError>; } fn handle_gws(g: Vec, netmask: &str) -> Result, ProxyError> { @@ -112,10 +112,10 @@ impl Address for MacVLAN { } // add the ip address to the container namespace - fn add_ip(&self, nls: &mut Socket) -> Result<(), ProxyError> { + fn add_ip(&self, nls: &mut Socket) -> Result<(), ProxyError> { debug!("adding network information for {}", self.interface); let ip = IpNet::new(self.address, self.prefix_length)?; - let dev = nls.get_link(netlink::LinkID::Name(self.interface.clone()))?; + let dev = nls.get_link(LinkID::Name(self.interface.clone()))?; match nls.add_addr(dev.header.index, &ip) { Ok(_) => Ok(()), Err(e) => Err(ProxyError::new(e.to_string())), @@ -123,7 +123,7 @@ impl Address for MacVLAN { } // add one or more routes to the container namespace - fn add_gws(&self, nls: &mut Socket) -> Result<(), ProxyError> { + fn add_gws(&self, nls: &mut Socket) -> Result<(), ProxyError> { debug!("adding gateways to {}", self.interface); match core_utils::add_default_routes(nls, &self.gateways, None) { Ok(_) => Ok(()), diff --git a/src/network/bridge.rs b/src/network/bridge.rs index e5f6227..36f5544 100644 --- a/src/network/bridge.rs +++ b/src/network/bridge.rs @@ -3,6 +3,10 @@ use std::{collections::HashMap, fs, net::IpAddr, os::fd::BorrowedFd}; use crate::dns::aardvark::SafeString; use crate::network::core_utils::get_default_route_interface; use crate::network::dhcp::{dhcp_teardown, get_dhcp_lease}; +use crate::network::netlink::Socket; +use crate::network::netlink_route::{ + parse_create_link_options, CreateLinkOptions, LinkID, NetlinkRoute, +}; use crate::{ dns::aardvark::AardvarkEntry, error::{ErrorWrap, NetavarkError, NetavarkErrorList, NetavarkResult}, @@ -34,7 +38,7 @@ use super::{ IPAMAddresses, IsolateOption, PortForwardConfig, SetupNetwork, TearDownNetwork, TeardownPortForward, }, - netlink, sysctl, + sysctl, types::StatusBlock, }; @@ -158,7 +162,7 @@ impl driver::NetworkDriver for Bridge<'_> { fn setup( &self, - netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), + netlink_sockets: (&mut Socket, &mut Socket), ) -> NetavarkResult<(StatusBlock, Option>)> { let data = match &self.data { Some(d) => d, @@ -348,7 +352,7 @@ impl driver::NetworkDriver for Bridge<'_> { fn teardown( &self, - netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), + netlink_sockets: (&mut Socket, &mut Socket), ) -> NetavarkResult<()> { let mode: Option = parse_option(&self.info.network.options, OPTION_MODE)?; let mode = get_bridge_mode_from_string(mode.as_deref())?; @@ -592,8 +596,8 @@ const IPV6_FORWARD: &str = "net/ipv6/conf/all/forwarding"; /// returns the container veth mac address fn create_interfaces( - host: &mut netlink::Socket, - netns: &mut netlink::Socket, + host: &mut Socket, + netns: &mut Socket, data: &InternalData, internal: bool, rootless: bool, @@ -601,9 +605,9 @@ fn create_interfaces( netns_fd: BorrowedFd<'_>, ) -> NetavarkResult { let mut sysctl_writer = None; - let (bridge_index, mtu, mac) = match host.get_link(netlink::LinkID::Name( - data.bridge_interface_name.to_string(), - )) { + let (bridge_index, mtu, mac) = match host + .get_link(LinkID::Name(data.bridge_interface_name.to_string())) + { Ok(bridge) => { let (bridge_index, mtu) = validate_bridge_link( bridge, @@ -696,7 +700,7 @@ fn create_interfaces( // writer must be create before the bridge is created let sw = sysctl::SysctlDWriter::new(path, sysctls); - let mut create_link_opts = netlink::CreateLinkOptions::new( + let mut create_link_opts = CreateLinkOptions::new( data.bridge_interface_name.to_string(), InfoKind::Bridge, ); @@ -724,7 +728,7 @@ fn create_interfaces( } if let Some(vrf_name) = &data.vrf { - let vrf = match host.get_link(netlink::LinkID::Name(vrf_name.to_string())) { + let vrf = match host.get_link(LinkID::Name(vrf_name.to_string())) { Ok(vrf) => check_link_is_vrf(vrf, vrf_name)?, Err(err) => return Err(err).wrap("get vrf to set up bridge interface"), }; @@ -738,9 +742,7 @@ fn create_interfaces( sysctl_writer = Some(sw); let link = host - .get_link(netlink::LinkID::Name( - data.bridge_interface_name.to_string(), - )) + .get_link(LinkID::Name(data.bridge_interface_name.to_string())) .wrap("get bridge interface")?; let mut mac = None; @@ -761,7 +763,7 @@ fn create_interfaces( .wrap("add ip addr to bridge")?; } - host.set_up(netlink::LinkID::ID(link.header.index)) + host.set_up(LinkID::ID(link.header.index)) .wrap("set bridge up")?; (link.header.index, mtu, mac) @@ -791,8 +793,8 @@ fn create_interfaces( /// return the container veth mac address #[allow(clippy::too_many_arguments)] fn create_veth_pair<'fd>( - host: &mut netlink::Socket, - netns: &mut netlink::Socket, + host: &mut Socket, + netns: &mut Socket, data: &InternalData, primary_index: u32, bridge_mac: Option>, @@ -802,16 +804,15 @@ fn create_veth_pair<'fd>( mtu: u32, ) -> NetavarkResult { let mut peer_opts = - netlink::CreateLinkOptions::new(data.container_interface_name.to_string(), InfoKind::Veth); + CreateLinkOptions::new(data.container_interface_name.to_string(), InfoKind::Veth); peer_opts.mac = data.mac_address.clone().unwrap_or_default(); peer_opts.mtu = mtu; peer_opts.netns = Some(netns_fd); let mut peer = LinkMessage::default(); - netlink::parse_create_link_options(&mut peer, peer_opts); + parse_create_link_options(&mut peer, peer_opts); - let mut host_veth = - netlink::CreateLinkOptions::new(data.host_interface_name.clone(), InfoKind::Veth); + let mut host_veth = CreateLinkOptions::new(data.host_interface_name.clone(), InfoKind::Veth); host_veth.mtu = mtu; host_veth.primary_index = primary_index; host_veth.info_data = Some(InfoData::Veth(InfoVeth::Peer(peer))); @@ -835,9 +836,7 @@ fn create_veth_pair<'fd>( })?; let veth = netns - .get_link(netlink::LinkID::Name( - data.container_interface_name.to_string(), - )) + .get_link(LinkID::Name(data.container_interface_name.to_string())) .wrap("get container veth")?; let mut mac = String::from(""); @@ -890,7 +889,7 @@ fn create_veth_pair<'fd>( })?; if data.ipam.ipv6_enabled { - let host_veth = host.get_link(netlink::LinkID::ID(host_link))?; + let host_veth = host.get_link(LinkID::ID(host_link))?; for nla in host_veth.attributes.into_iter() { if let LinkAttribute::IfName(name) = nla { @@ -903,7 +902,7 @@ fn create_veth_pair<'fd>( } } - host.set_up(netlink::LinkID::ID(host_link)) + host.set_up(LinkID::ID(host_link)) .wrap("failed to set host veth up")?; // Ok this is extremely strange, by default the kernel will always choose the mac address with the @@ -916,7 +915,7 @@ fn create_veth_pair<'fd>( // connected otherwise no connectivity is possible at all and I have no idea why but CNI does it // also in the same way. if let Some(m) = bridge_mac { - host.set_mac_address(netlink::LinkID::ID(primary_index), m) + host.set_mac_address(LinkID::ID(primary_index), m) .wrap("set static mac on bridge")?; } @@ -927,7 +926,7 @@ fn create_veth_pair<'fd>( } netns - .set_up(netlink::LinkID::ID(veth.header.index)) + .set_up(LinkID::ID(veth.header.index)) .wrap("set container veth up")?; if !internal && !data.no_default_route { @@ -948,7 +947,7 @@ fn create_veth_pair<'fd>( fn validate_bridge_link( msg: LinkMessage, vlan: bool, - netlink: &mut netlink::Socket, + netlink: &mut Socket, br_name: &str, ) -> NetavarkResult<(u32, u32)> { let mut mtu: u32 = 0; @@ -1038,20 +1037,20 @@ fn check_link_is_vrf(msg: LinkMessage, vrf_name: &str) -> NetavarkResult, + netns: &mut Socket, mode: BridgeMode, br_name: &str, container_veth_name: &str, ) -> NetavarkResult { netns - .del_link(netlink::LinkID::Name(container_veth_name.to_string())) + .del_link(LinkID::Name(container_veth_name.to_string())) .wrap(format!( "failed to delete container veth {container_veth_name}" ))?; let br = host - .get_link(netlink::LinkID::Name(br_name.to_string())) + .get_link(LinkID::Name(br_name.to_string())) .wrap("failed to get bridge interface")?; let links = host @@ -1061,7 +1060,7 @@ fn remove_link( if links.is_empty() { if let BridgeMode::Managed = mode { log::info!("removing bridge {br_name}"); - host.del_link(netlink::LinkID::ID(br.header.index)) + host.del_link(LinkID::ID(br.header.index)) .wrap(format!("failed to delete bridge {container_veth_name}"))?; return Ok(true); } diff --git a/src/network/core_utils.rs b/src/network/core_utils.rs index ed1c1b9..a183604 100644 --- a/src/network/core_utils.rs +++ b/src/network/core_utils.rs @@ -18,6 +18,8 @@ use std::path::Path; use std::str::FromStr; use super::netlink; +use crate::network::netlink::Socket; +use crate::network::netlink_route::{LinkID, NetlinkRoute, Route}; use netlink_packet_route::link::LinkAttribute; @@ -126,7 +128,7 @@ pub fn get_ipam_addresses<'a>( }); } - let routes: Vec = match create_route_list(&network.routes) { + let routes: Vec = match create_route_list(&network.routes) { Ok(r) => r, Err(e) => { return Err(e); @@ -274,7 +276,7 @@ pub struct NamespaceOptions { /// Note we have to return the File object since the fd is only valid /// as long as the File object is valid pub file: File, - pub netlink: netlink::Socket, + pub netlink: Socket, } pub fn open_netlink_sockets( @@ -283,11 +285,11 @@ pub fn open_netlink_sockets( let netns = open_netlink_socket(netns_path).wrap("open container netns")?; let hostns = open_netlink_socket("/proc/self/ns/net").wrap("open host netns")?; - let host_socket = netlink::Socket::new().wrap("host netlink socket")?; + let host_socket = netlink::Socket::::new().wrap("host netlink socket")?; let netns_sock = exec_netns!( hostns.as_fd(), netns.as_fd(), - netlink::Socket::new().wrap("netns netlink socket") + netlink::Socket::::new().wrap("netns netlink socket") )?; Ok(( @@ -307,7 +309,7 @@ fn open_netlink_socket(netns_path: &str) -> NetavarkResult { } pub fn add_default_routes( - sock: &mut netlink::Socket, + sock: &mut Socket, gws: &[ipnet::IpNet], metric: Option, ) -> NetavarkResult<()> { @@ -321,7 +323,7 @@ pub fn add_default_routes( } ipv4 = true; - netlink::Route::Ipv4 { + Route::Ipv4 { dest: ipnet::Ipv4Net::new(Ipv4Addr::new(0, 0, 0, 0), 0)?, gw: v4.addr(), metric, @@ -333,7 +335,7 @@ pub fn add_default_routes( } ipv6 = true; - netlink::Route::Ipv6 { + Route::Ipv6 { dest: ipnet::Ipv6Net::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), 0)?, gw: v6.addr(), metric, @@ -346,9 +348,7 @@ pub fn add_default_routes( Ok(()) } -pub fn create_route_list( - routes: &Option>, -) -> NetavarkResult> { +pub fn create_route_list(routes: &Option>) -> NetavarkResult> { match routes { Some(rs) => rs .iter() @@ -357,12 +357,12 @@ pub fn create_route_list( let dst = r.destination; let mtr = r.metric; match (gw, dst) { - (IpAddr::V4(gw4), IpNet::V4(dst4)) => Ok(netlink::Route::Ipv4 { + (IpAddr::V4(gw4), IpNet::V4(dst4)) => Ok(Route::Ipv4 { dest: dst4, gw: gw4, metric: mtr, }), - (IpAddr::V6(gw6), IpNet::V6(dst6)) => Ok(netlink::Route::Ipv6 { + (IpAddr::V6(gw6), IpNet::V6(dst6)) => Ok(Route::Ipv6 { dest: dst6, gw: gw6, metric: mtr, @@ -398,7 +398,7 @@ pub fn is_using_systemd() -> bool { } /// Returns the *first* interface with a default route or an error if no default route interface exists. -pub fn get_default_route_interface(host: &mut netlink::Socket) -> NetavarkResult { +pub fn get_default_route_interface(host: &mut Socket) -> NetavarkResult { let routes = host.dump_routes().wrap("dump routes")?; for route in routes { @@ -416,7 +416,7 @@ pub fn get_default_route_interface(host: &mut netlink::Socket) -> NetavarkResult // if there is no dest we have a default route // return the output interface for this route if !dest && out_if > 0 { - return host.get_link(netlink::LinkID::ID(out_if)); + return host.get_link(LinkID::ID(out_if)); } } Err(NetavarkError::msg("failed to get default route interface")) diff --git a/src/network/dhcp.rs b/src/network/dhcp.rs index 0416860..4b6eb54 100644 --- a/src/network/dhcp.rs +++ b/src/network/dhcp.rs @@ -7,8 +7,10 @@ use std::str::FromStr; use crate::dhcp_proxy::lib::g_rpc::NetworkConfig; use crate::dhcp_proxy::proxy_conf::DEFAULT_UDS_PATH; +use super::core_utils; use super::driver::DriverInfo; -use super::{core_utils, netlink}; +use crate::network::netlink::Socket; +use crate::network::netlink_route::{LinkID, NetlinkRoute}; pub type DhcpLeaseInfo = (Vec, Option>, Option>); @@ -160,14 +162,14 @@ pub fn release_dhcp_lease( Ok(()) } -pub fn dhcp_teardown(info: &DriverInfo, sock: &mut netlink::Socket) -> NetavarkResult<()> { +pub fn dhcp_teardown(info: &DriverInfo, sock: &mut Socket) -> NetavarkResult<()> { let ipam = core_utils::get_ipam_addresses(info.per_network_opts, info.network)?; let if_name = info.per_network_opts.interface_name.clone(); // If we are using DHCP, we need to at least call to the proxy so that // the proxy's cache can get updated and the current lease can be released. if ipam.dhcp_enabled { - let dev = sock.get_link(netlink::LinkID::Name(if_name)).wrap(format!( + let dev = sock.get_link(LinkID::Name(if_name)).wrap(format!( "get container interface {}", &info.per_network_opts.interface_name ))?; diff --git a/src/network/driver.rs b/src/network/driver.rs index 478f254..119da0c 100644 --- a/src/network/driver.rs +++ b/src/network/driver.rs @@ -8,11 +8,13 @@ use std::{ffi::OsString, net::IpAddr, os::fd::BorrowedFd, path::Path}; use super::{ bridge::Bridge, - constants, netlink, + constants, plugin::PluginDriver, types::{Network, PerNetworkOptions, PortMapping, StatusBlock}, vlan::Vlan, }; +use crate::network::netlink::Socket; +use crate::network::netlink_route::NetlinkRoute; use std::os::unix::fs::PermissionsExt; pub struct DriverInfo<'a> { @@ -38,12 +40,12 @@ pub trait NetworkDriver { /// setup the network interfaces/firewall rules for this driver fn setup( &self, - netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), + netlink_sockets: (&mut Socket, &mut Socket), ) -> NetavarkResult<(StatusBlock, Option>)>; /// teardown the network interfaces/firewall rules for this driver fn teardown( &self, - netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), + netlink_sockets: (&mut Socket, &mut Socket), ) -> NetavarkResult<()>; /// return the network name diff --git a/src/network/internal_types.rs b/src/network/internal_types.rs index 84d4322..70075de 100644 --- a/src/network/internal_types.rs +++ b/src/network/internal_types.rs @@ -1,4 +1,4 @@ -use super::netlink; +use crate::network::netlink_route::Route; use crate::network::types; use std::net::IpAddr; @@ -103,7 +103,7 @@ pub struct IPAMAddresses { // if using macvlan and dhcp, then true pub dhcp_enabled: bool, pub gateway_addresses: Vec, - pub routes: Vec, + pub routes: Vec, pub ipv6_enabled: bool, // result for podman pub net_addresses: Vec, diff --git a/src/network/mod.rs b/src/network/mod.rs index e3253ca..ed79a9f 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -16,7 +16,10 @@ pub mod core_utils; mod dhcp; pub mod driver; pub mod internal_types; + pub mod netlink; +pub mod netlink_route; + pub mod plugin; pub mod sysctl; pub mod vlan; diff --git a/src/network/netlink.rs b/src/network/netlink.rs index ef4fa66..42c334f 100644 --- a/src/network/netlink.rs +++ b/src/network/netlink.rs @@ -1,95 +1,27 @@ -use std::{ - net::{Ipv4Addr, Ipv6Addr}, - os::fd::{AsFd, AsRawFd, BorrowedFd}, -}; +use std::marker::PhantomData; use crate::{ error::{ErrorWrap, NetavarkError, NetavarkResult}, - network::constants, wrap, }; -use log::{info, trace}; +use log::trace; use netlink_packet_core::{ - NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL, - NLM_F_REQUEST, + NetlinkDeserializable, NetlinkHeader, NetlinkMessage, NetlinkPayload, NetlinkSerializable, + NLM_F_DUMP, NLM_F_REQUEST, }; -use netlink_packet_route::{ - address::AddressMessage, - link::{ - AfSpecBridge, BridgeVlanInfo, BridgeVlanInfoFlags, InfoBridge, InfoData, InfoKind, - LinkAttribute, LinkFlags, LinkInfo, LinkMessage, - }, - route::{RouteAddress, RouteMessage, RouteProtocol, RouteScope, RouteType}, - AddressFamily, RouteNetlinkMessage, -}; -use netlink_sys::{protocols::NETLINK_ROUTE, SocketAddr}; +use netlink_sys::SocketAddr; -pub struct Socket { +pub trait NetlinkFamily { + const PROTOCOL: isize; + type Message; +} + +pub struct Socket { socket: netlink_sys::Socket, sequence_number: u32, /// buffer size for reading netlink messages, see NLMSG_GOODSIZE in the kernel buffer: [u8; 8192], -} - -#[derive(Clone)] -pub struct CreateLinkOptions<'fd> { - pub name: String, - kind: InfoKind, - pub info_data: Option, - pub mtu: u32, - pub primary_index: u32, - pub link: u32, - pub mac: Vec, - pub netns: Option>, -} - -pub enum LinkID { - ID(u32), - Name(String), -} - -pub enum Route { - Ipv4 { - dest: ipnet::Ipv4Net, - gw: Ipv4Addr, - metric: Option, - }, - Ipv6 { - dest: ipnet::Ipv6Net, - gw: Ipv6Addr, - metric: Option, - }, -} - -impl std::fmt::Display for Route { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let (dest, gw, metric) = match self { - Route::Ipv4 { dest, gw, metric } => ( - dest.to_string(), - gw.to_string(), - metric.unwrap_or(constants::DEFAULT_METRIC), - ), - Route::Ipv6 { dest, gw, metric } => ( - dest.to_string(), - gw.to_string(), - metric.unwrap_or(constants::DEFAULT_METRIC), - ), - }; - write!(f, "(dest: {dest} ,gw: {gw}, metric {metric})") - } -} - -macro_rules! expect_netlink_result { - ($result:expr, $count:expr) => { - if $result.len() != $count { - return Err(NetavarkError::msg(format!( - "{}: unexpected netlink result (got {} result(s), want {})", - function!(), - $result.len(), - $count - ))); - } - }; + _protocol: PhantomData

, } /// get the function name of the currently executed function @@ -109,10 +41,28 @@ macro_rules! function { } }}; } +pub(crate) use function; -impl Socket { - pub fn new() -> NetavarkResult { - let mut socket = wrap!(netlink_sys::Socket::new(NETLINK_ROUTE), "open")?; +macro_rules! expect_netlink_result { + ($result:expr, $count:expr) => { + if $result.len() != $count { + return Err(NetavarkError::msg(format!( + "{}: unexpected netlink result (got {} result(s), want {})", + function!(), + $result.len(), + $count + ))); + } + }; +} +pub(crate) use expect_netlink_result; + +impl

Socket

+where + P: NetlinkFamily, +{ + pub fn new() -> NetavarkResult> { + let mut socket = wrap!(netlink_sys::Socket::new(P::PROTOCOL), "open")?; let addr = &SocketAddr::new(0, 0); // Needs to be enabled for dump filtering to work socket.set_netlink_get_strict_chk(true)?; @@ -123,370 +73,15 @@ impl Socket { socket, sequence_number: 0, buffer: [0; 8192], + _protocol: PhantomData, }) } - pub fn get_link(&mut self, id: LinkID) -> NetavarkResult { - let mut msg = LinkMessage::default(); - - match id { - LinkID::ID(id) => msg.header.index = id, - LinkID::Name(name) => msg.attributes.push(LinkAttribute::IfName(name)), - } - - let mut result = self.make_netlink_request(RouteNetlinkMessage::GetLink(msg), 0)?; - expect_netlink_result!(result, 1); - match result.remove(0) { - RouteNetlinkMessage::NewLink(m) => Ok(m), - m => Err(NetavarkError::Message(format!( - "unexpected netlink message type: {}", - m.message_type() - ))), - } - } - - pub fn create_link(&mut self, options: CreateLinkOptions) -> NetavarkResult<()> { - let mut msg = LinkMessage::default(); - parse_create_link_options(&mut msg, options); - let result = self.make_netlink_request( - RouteNetlinkMessage::NewLink(msg), - NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE, - )?; - expect_netlink_result!(result, 0); - - Ok(()) - } - - pub fn set_link_name(&mut self, id: u32, name: String) -> NetavarkResult<()> { - let mut msg = LinkMessage::default(); - msg.header.index = id; - msg.attributes.push(LinkAttribute::IfName(name)); - let result = self.make_netlink_request(RouteNetlinkMessage::SetLink(msg), NLM_F_ACK)?; - expect_netlink_result!(result, 0); - - Ok(()) - } - - pub fn del_link(&mut self, id: LinkID) -> NetavarkResult<()> { - let mut msg = LinkMessage::default(); - - match id { - LinkID::ID(id) => msg.header.index = id, - LinkID::Name(name) => msg.attributes.push(LinkAttribute::IfName(name)), - } - - let result = self.make_netlink_request(RouteNetlinkMessage::DelLink(msg), NLM_F_ACK)?; - expect_netlink_result!(result, 0); - Ok(()) - } - - pub fn set_link_ns(&mut self, link_id: u32, netns: Fd) -> NetavarkResult<()> { - let mut msg = LinkMessage::default(); - msg.header.index = link_id; - msg.attributes - .push(LinkAttribute::NetNsFd(netns.as_fd().as_raw_fd())); - - let result = self.make_netlink_request(RouteNetlinkMessage::SetLink(msg), NLM_F_ACK)?; - expect_netlink_result!(result, 0); - Ok(()) - } - - /// set the vlan_filtering attribute on a bridge - pub fn set_vlan_filtering(&mut self, link_id: u32, vlan_filtering: bool) -> NetavarkResult<()> { - let mut msg = LinkMessage::default(); - msg.header.index = link_id; - msg.attributes.push(LinkAttribute::LinkInfo(vec![ - LinkInfo::Kind(InfoKind::Bridge), - LinkInfo::Data(InfoData::Bridge(vec![InfoBridge::VlanFiltering( - vlan_filtering, - )])), - ])); - - // Now idea why this must use NewLink not SetLink, I strace'd ip route - // and they use newlink and which setlink here it does not error but also does not set the setting. - let result = self.make_netlink_request(RouteNetlinkMessage::NewLink(msg), NLM_F_ACK)?; - expect_netlink_result!(result, 0); - Ok(()) - } - - /// set the vlan id for an interface which is attached to the bridge with vlan_filtering - /// Performs the equivalent of "bridge vlan add dev test vid [flags]" - pub fn set_vlan_id( - &mut self, - link_id: u32, - // vlan id - vid: u16, - // flags for the vlan config - flags: BridgeVlanInfoFlags, - ) -> NetavarkResult<()> { - let mut msg = LinkMessage::default(); - msg.header.interface_family = AddressFamily::Bridge; - // msg.header.link_layer_type = LinkLayerType::Netrom; - msg.header.index = link_id; - msg.attributes - .push(LinkAttribute::AfSpecBridge(vec![AfSpecBridge::VlanInfo( - BridgeVlanInfo { flags, vid }, - )])); - - let result = self.make_netlink_request(RouteNetlinkMessage::SetLink(msg), NLM_F_ACK)?; - expect_netlink_result!(result, 0); - Ok(()) - } - - fn create_addr_msg(link_id: u32, addr: &ipnet::IpNet) -> AddressMessage { - let mut msg = AddressMessage::default(); - msg.header.index = link_id; - - match addr { - ipnet::IpNet::V4(v4) => { - msg.header.family = AddressFamily::Inet; - msg.attributes - .push(netlink_packet_route::address::AddressAttribute::Broadcast( - v4.broadcast(), - )); - } - ipnet::IpNet::V6(_) => { - msg.header.family = AddressFamily::Inet6; - } - }; - - msg.header.prefix_len = addr.prefix_len(); - msg.attributes - .push(netlink_packet_route::address::AddressAttribute::Local( - addr.addr(), - )); - msg - } - - pub fn add_addr(&mut self, link_id: u32, addr: &ipnet::IpNet) -> NetavarkResult<()> { - let msg = Self::create_addr_msg(link_id, addr); - let result = match self.make_netlink_request( - RouteNetlinkMessage::NewAddress(msg), - NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE, - ) { - Ok(result) => result, - Err(err) => match err { - // kernel returns EACCES when we try to add an ipv6 but ipv6 is disabled in the kernel - NetavarkError::Netlink(ref e) if -e.raw_code() == libc::EACCES => match addr { - ipnet::IpNet::V6(_) => { - return Err(NetavarkError::wrap( - "failed to add ipv6 address, is ipv6 enabled in the kernel?", - err, - )); - } - _ => return Err(err), - }, - err => return Err(err), - }, - }; - expect_netlink_result!(result, 0); - - Ok(()) - } - - pub fn del_addr(&mut self, link_id: u32, addr: &ipnet::IpNet) -> NetavarkResult<()> { - let msg = Self::create_addr_msg(link_id, addr); - let result = self.make_netlink_request(RouteNetlinkMessage::DelAddress(msg), NLM_F_ACK)?; - expect_netlink_result!(result, 0); - - Ok(()) - } - - fn create_route_msg(route: &Route) -> RouteMessage { - let mut msg = RouteMessage::default(); - - msg.header.table = libc::RT_TABLE_MAIN; - msg.header.protocol = RouteProtocol::Static; - msg.header.scope = RouteScope::Universe; - msg.header.kind = RouteType::Unicast; - - let (dest, dest_prefix, gateway, final_metric) = match route { - Route::Ipv4 { dest, gw, metric } => { - msg.header.address_family = AddressFamily::Inet; - ( - RouteAddress::Inet(dest.addr()), - dest.prefix_len(), - RouteAddress::Inet(*gw), - metric.unwrap_or(constants::DEFAULT_METRIC), - ) - } - Route::Ipv6 { dest, gw, metric } => { - msg.header.address_family = AddressFamily::Inet6; - ( - RouteAddress::Inet6(dest.addr()), - dest.prefix_len(), - RouteAddress::Inet6(*gw), - metric.unwrap_or(constants::DEFAULT_METRIC), - ) - } - }; - - msg.header.destination_prefix_length = dest_prefix; - msg.attributes - .push(netlink_packet_route::route::RouteAttribute::Destination( - dest, - )); - msg.attributes - .push(netlink_packet_route::route::RouteAttribute::Gateway( - gateway, - )); - msg.attributes - .push(netlink_packet_route::route::RouteAttribute::Priority( - final_metric, - )); - msg - } - - pub fn add_route(&mut self, route: &Route) -> NetavarkResult<()> { - let msg = Self::create_route_msg(route); - info!("Adding route {route}"); - - let result = self - .make_netlink_request(RouteNetlinkMessage::NewRoute(msg), NLM_F_ACK | NLM_F_CREATE)?; - expect_netlink_result!(result, 0); - - Ok(()) - } - - pub fn del_route(&mut self, route: &Route) -> NetavarkResult<()> { - let msg = Self::create_route_msg(route); - info!("Deleting route {route}"); - - let result = self.make_netlink_request(RouteNetlinkMessage::DelRoute(msg), NLM_F_ACK)?; - expect_netlink_result!(result, 0); - - Ok(()) - } - - pub fn dump_routes(&mut self) -> NetavarkResult> { - let mut msg = RouteMessage::default(); - - msg.header.table = libc::RT_TABLE_MAIN; - msg.header.protocol = RouteProtocol::Unspec; - msg.header.scope = RouteScope::Universe; - msg.header.kind = RouteType::Unicast; - - let results = - self.make_netlink_request(RouteNetlinkMessage::GetRoute(msg), NLM_F_DUMP | NLM_F_ACK)?; - - let mut routes = Vec::with_capacity(results.len()); - - for res in results { - match res { - RouteNetlinkMessage::NewRoute(m) => routes.push(m), - m => { - return Err(NetavarkError::Message(format!( - "unexpected netlink message type: {}", - m.message_type() - ))) - } - }; - } - Ok(routes) - } - - pub fn dump_links( - &mut self, - nlas: &mut Vec, - ) -> NetavarkResult> { - let mut msg = LinkMessage::default(); - msg.attributes.append(nlas); - - let results = - self.make_netlink_request(RouteNetlinkMessage::GetLink(msg), NLM_F_DUMP | NLM_F_ACK)?; - - let mut links = Vec::with_capacity(results.len()); - - for res in results { - match res { - RouteNetlinkMessage::NewLink(m) => links.push(m), - m => { - return Err(NetavarkError::Message(format!( - "unexpected netlink message type: {}", - m.message_type() - ))) - } - }; - } - Ok(links) - } - - // If filtering options are supplied, then only the ip addresses satisfying the filter are returned. Otherwise all ip addresses of all interfaces are returned - pub fn dump_addresses( - &mut self, - interface_id_filter: Option, - ) -> NetavarkResult> { - let mut msg = AddressMessage::default(); - - if let Some(id) = interface_id_filter { - msg.header.index = id; - } - - let results = - self.make_netlink_request(RouteNetlinkMessage::GetAddress(msg), NLM_F_DUMP)?; - - let mut addresses = Vec::with_capacity(results.len()); - - for res in results { - match res { - RouteNetlinkMessage::NewAddress(m) => addresses.push(m), - m => { - return Err(NetavarkError::Message(format!( - "unexpected netlink message type: {}", - m.message_type() - ))) - } - }; - } - Ok(addresses) - } - - pub fn set_up(&mut self, id: LinkID) -> NetavarkResult<()> { - let mut msg = LinkMessage::default(); - - match id { - LinkID::ID(id) => msg.header.index = id, - LinkID::Name(name) => msg.attributes.push(LinkAttribute::IfName(name)), - } - - msg.header.flags = LinkFlags::Up; - msg.header.change_mask = LinkFlags::Up; - - let result = self.make_netlink_request( - RouteNetlinkMessage::SetLink(msg), - NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE, - )?; - expect_netlink_result!(result, 0); - - Ok(()) - } - - pub fn set_mac_address(&mut self, id: LinkID, mac: Vec) -> NetavarkResult<()> { - let mut msg = LinkMessage::default(); - - match id { - LinkID::ID(id) => msg.header.index = id, - LinkID::Name(name) => msg.attributes.push(LinkAttribute::IfName(name)), - } - - msg.attributes.push(LinkAttribute::Address(mac)); - - let result = self.make_netlink_request(RouteNetlinkMessage::SetLink(msg), NLM_F_ACK)?; - expect_netlink_result!(result, 0); - - Ok(()) - } - - fn make_netlink_request( - &mut self, - msg: RouteNetlinkMessage, - flags: u16, - ) -> NetavarkResult> { - self.send(msg, flags).wrap("send to netlink")?; - self.recv(flags & NLM_F_DUMP == NLM_F_DUMP) - } - - fn send(&mut self, msg: RouteNetlinkMessage, flags: u16) -> NetavarkResult<()> { + fn send(&mut self, msg: P::Message, flags: u16) -> NetavarkResult<()> + where + P::Message: NetlinkSerializable + std::fmt::Debug, + NetlinkPayload: From, + { let mut packet = NetlinkMessage::new(NetlinkHeader::default(), NetlinkPayload::from(msg)); packet.header.flags = NLM_F_REQUEST | flags; packet.header.sequence_number = { @@ -502,7 +97,10 @@ impl Socket { Ok(()) } - fn recv(&mut self, multi: bool) -> NetavarkResult> { + fn recv(&mut self, multi: bool) -> NetavarkResult> + where + P::Message: NetlinkDeserializable + std::fmt::Debug, + { let mut offset = 0; let mut result = Vec::new(); @@ -515,8 +113,8 @@ impl Socket { loop { let bytes = &self.buffer[offset..]; - let rx_packet: NetlinkMessage = - NetlinkMessage::deserialize(bytes).map_err(|e| { + let rx_packet: NetlinkMessage = NetlinkMessage::deserialize(bytes) + .map_err(|e| { NetavarkError::Message(format!( "failed to deserialize netlink message: {e}", )) @@ -565,60 +163,16 @@ impl Socket { } } } -} - -impl CreateLinkOptions<'_> { - pub fn new(name: String, kind: InfoKind) -> Self { - CreateLinkOptions { - name, - kind, - info_data: None, - mtu: 0, - primary_index: 0, - link: 0, - mac: vec![], - netns: None, - } - } -} - -pub fn parse_create_link_options(msg: &mut LinkMessage, options: CreateLinkOptions) { - // add link specific data - let mut link_info_nlas = vec![LinkInfo::Kind(options.kind)]; - if let Some(data) = options.info_data { - link_info_nlas.push(LinkInfo::Data(data)); - } - msg.attributes.push(LinkAttribute::LinkInfo(link_info_nlas)); - - // add name - if !options.name.is_empty() { - msg.attributes.push(LinkAttribute::IfName(options.name)); - } - - // add mtu - if options.mtu != 0 { - msg.attributes.push(LinkAttribute::Mtu(options.mtu)); - } - - // add mac address - if !options.mac.is_empty() { - msg.attributes.push(LinkAttribute::Address(options.mac)); - } - - // add primary device - if options.primary_index != 0 { - msg.attributes - .push(LinkAttribute::Controller(options.primary_index)); - } - - // add link device - if options.link != 0 { - msg.attributes.push(LinkAttribute::Link(options.link)); - } - - // add netnsfd - if let Some(netns) = options.netns { - msg.attributes - .push(LinkAttribute::NetNsFd(netns.as_raw_fd())); + pub fn make_netlink_request( + &mut self, + msg: P::Message, + flags: u16, + ) -> NetavarkResult> + where + P::Message: NetlinkSerializable + NetlinkDeserializable + std::fmt::Debug, + NetlinkPayload: From, + { + self.send(msg, flags).wrap("send to netlink")?; + self.recv(flags & NLM_F_DUMP == NLM_F_DUMP) } } diff --git a/src/network/netlink_route.rs b/src/network/netlink_route.rs new file mode 100644 index 0000000..e7ff36c --- /dev/null +++ b/src/network/netlink_route.rs @@ -0,0 +1,488 @@ +use std::{ + net::{Ipv4Addr, Ipv6Addr}, + os::fd::{AsFd, AsRawFd, BorrowedFd}, +}; + +use crate::{ + error::{NetavarkError, NetavarkResult}, + network::{ + constants, + netlink::{expect_netlink_result, function, NetlinkFamily, Socket}, + }, +}; +use log::info; +use netlink_packet_core::{NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL}; +use netlink_packet_route::{ + address::AddressMessage, + link::{ + AfSpecBridge, BridgeVlanInfo, BridgeVlanInfoFlags, InfoBridge, InfoData, InfoKind, + LinkAttribute, LinkFlags, LinkInfo, LinkMessage, + }, + route::{RouteAddress, RouteMessage, RouteProtocol, RouteScope, RouteType}, + AddressFamily, RouteNetlinkMessage, +}; +use netlink_sys::protocols::NETLINK_ROUTE; + +#[derive(Clone)] +pub struct CreateLinkOptions<'fd> { + pub name: String, + kind: InfoKind, + pub info_data: Option, + pub mtu: u32, + pub primary_index: u32, + pub link: u32, + pub mac: Vec, + pub netns: Option>, +} + +pub enum LinkID { + ID(u32), + Name(String), +} + +pub enum Route { + Ipv4 { + dest: ipnet::Ipv4Net, + gw: Ipv4Addr, + metric: Option, + }, + Ipv6 { + dest: ipnet::Ipv6Net, + gw: Ipv6Addr, + metric: Option, + }, +} + +impl std::fmt::Display for Route { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let (dest, gw, metric) = match self { + Route::Ipv4 { dest, gw, metric } => ( + dest.to_string(), + gw.to_string(), + metric.unwrap_or(constants::DEFAULT_METRIC), + ), + Route::Ipv6 { dest, gw, metric } => ( + dest.to_string(), + gw.to_string(), + metric.unwrap_or(constants::DEFAULT_METRIC), + ), + }; + write!(f, "(dest: {dest} ,gw: {gw}, metric {metric})") + } +} + +pub struct NetlinkRoute; + +impl NetlinkFamily for NetlinkRoute { + const PROTOCOL: isize = NETLINK_ROUTE; + type Message = RouteNetlinkMessage; +} + +impl Socket { + pub fn get_link(&mut self, id: LinkID) -> NetavarkResult { + let mut msg = LinkMessage::default(); + + match id { + LinkID::ID(id) => msg.header.index = id, + LinkID::Name(name) => msg.attributes.push(LinkAttribute::IfName(name)), + } + + let mut result = self.make_netlink_request(RouteNetlinkMessage::GetLink(msg), 0)?; + expect_netlink_result!(result, 1); + match result.remove(0) { + RouteNetlinkMessage::NewLink(m) => Ok(m), + m => Err(NetavarkError::Message(format!( + "unexpected netlink message type: {}", + m.message_type() + ))), + } + } + + pub fn create_link(&mut self, options: CreateLinkOptions) -> NetavarkResult<()> { + let mut msg = LinkMessage::default(); + parse_create_link_options(&mut msg, options); + let result = self.make_netlink_request( + RouteNetlinkMessage::NewLink(msg), + NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE, + )?; + expect_netlink_result!(result, 0); + + Ok(()) + } + + pub fn set_link_name(&mut self, id: u32, name: String) -> NetavarkResult<()> { + let mut msg = LinkMessage::default(); + msg.header.index = id; + msg.attributes.push(LinkAttribute::IfName(name)); + let result = self.make_netlink_request(RouteNetlinkMessage::SetLink(msg), NLM_F_ACK)?; + expect_netlink_result!(result, 0); + + Ok(()) + } + + pub fn del_link(&mut self, id: LinkID) -> NetavarkResult<()> { + let mut msg = LinkMessage::default(); + + match id { + LinkID::ID(id) => msg.header.index = id, + LinkID::Name(name) => msg.attributes.push(LinkAttribute::IfName(name)), + } + + let result = self.make_netlink_request(RouteNetlinkMessage::DelLink(msg), NLM_F_ACK)?; + expect_netlink_result!(result, 0); + Ok(()) + } + + pub fn set_link_ns(&mut self, link_id: u32, netns: Fd) -> NetavarkResult<()> { + let mut msg = LinkMessage::default(); + msg.header.index = link_id; + msg.attributes + .push(LinkAttribute::NetNsFd(netns.as_fd().as_raw_fd())); + + let result = self.make_netlink_request(RouteNetlinkMessage::SetLink(msg), NLM_F_ACK)?; + expect_netlink_result!(result, 0); + Ok(()) + } + + /// set the vlan_filtering attribute on a bridge + pub fn set_vlan_filtering(&mut self, link_id: u32, vlan_filtering: bool) -> NetavarkResult<()> { + let mut msg = LinkMessage::default(); + msg.header.index = link_id; + msg.attributes.push(LinkAttribute::LinkInfo(vec![ + LinkInfo::Kind(InfoKind::Bridge), + LinkInfo::Data(InfoData::Bridge(vec![InfoBridge::VlanFiltering( + vlan_filtering, + )])), + ])); + + // Now idea why this must use NewLink not SetLink, I strace'd ip route + // and they use newlink and which setlink here it does not error but also does not set the setting. + let result = self.make_netlink_request(RouteNetlinkMessage::NewLink(msg), NLM_F_ACK)?; + expect_netlink_result!(result, 0); + Ok(()) + } + + /// set the vlan id for an interface which is attached to the bridge with vlan_filtering + /// Performs the equivalent of "bridge vlan add dev test vid [flags]" + pub fn set_vlan_id( + &mut self, + link_id: u32, + // vlan id + vid: u16, + // flags for the vlan config + flags: BridgeVlanInfoFlags, + ) -> NetavarkResult<()> { + let mut msg = LinkMessage::default(); + msg.header.interface_family = AddressFamily::Bridge; + // msg.header.link_layer_type = LinkLayerType::Netrom; + msg.header.index = link_id; + msg.attributes + .push(LinkAttribute::AfSpecBridge(vec![AfSpecBridge::VlanInfo( + BridgeVlanInfo { flags, vid }, + )])); + + let result = self.make_netlink_request(RouteNetlinkMessage::SetLink(msg), NLM_F_ACK)?; + expect_netlink_result!(result, 0); + Ok(()) + } + + fn create_addr_msg(link_id: u32, addr: &ipnet::IpNet) -> AddressMessage { + let mut msg = AddressMessage::default(); + msg.header.index = link_id; + + match addr { + ipnet::IpNet::V4(v4) => { + msg.header.family = AddressFamily::Inet; + msg.attributes + .push(netlink_packet_route::address::AddressAttribute::Broadcast( + v4.broadcast(), + )); + } + ipnet::IpNet::V6(_) => { + msg.header.family = AddressFamily::Inet6; + } + }; + + msg.header.prefix_len = addr.prefix_len(); + msg.attributes + .push(netlink_packet_route::address::AddressAttribute::Local( + addr.addr(), + )); + msg + } + + pub fn add_addr(&mut self, link_id: u32, addr: &ipnet::IpNet) -> NetavarkResult<()> { + let msg = Self::create_addr_msg(link_id, addr); + let result = match self.make_netlink_request( + RouteNetlinkMessage::NewAddress(msg), + NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE, + ) { + Ok(result) => result, + Err(err) => match err { + // kernel returns EACCES when we try to add an ipv6 but ipv6 is disabled in the kernel + NetavarkError::Netlink(ref e) if -e.raw_code() == libc::EACCES => match addr { + ipnet::IpNet::V6(_) => { + return Err(NetavarkError::wrap( + "failed to add ipv6 address, is ipv6 enabled in the kernel?", + err, + )); + } + _ => return Err(err), + }, + err => return Err(err), + }, + }; + expect_netlink_result!(result, 0); + + Ok(()) + } + + pub fn del_addr(&mut self, link_id: u32, addr: &ipnet::IpNet) -> NetavarkResult<()> { + let msg = Self::create_addr_msg(link_id, addr); + let result = self.make_netlink_request(RouteNetlinkMessage::DelAddress(msg), NLM_F_ACK)?; + expect_netlink_result!(result, 0); + + Ok(()) + } + + fn create_route_msg(route: &Route) -> RouteMessage { + let mut msg = RouteMessage::default(); + + msg.header.table = libc::RT_TABLE_MAIN; + msg.header.protocol = RouteProtocol::Static; + msg.header.scope = RouteScope::Universe; + msg.header.kind = RouteType::Unicast; + + let (dest, dest_prefix, gateway, final_metric) = match route { + Route::Ipv4 { dest, gw, metric } => { + msg.header.address_family = AddressFamily::Inet; + ( + RouteAddress::Inet(dest.addr()), + dest.prefix_len(), + RouteAddress::Inet(*gw), + metric.unwrap_or(constants::DEFAULT_METRIC), + ) + } + Route::Ipv6 { dest, gw, metric } => { + msg.header.address_family = AddressFamily::Inet6; + ( + RouteAddress::Inet6(dest.addr()), + dest.prefix_len(), + RouteAddress::Inet6(*gw), + metric.unwrap_or(constants::DEFAULT_METRIC), + ) + } + }; + + msg.header.destination_prefix_length = dest_prefix; + msg.attributes + .push(netlink_packet_route::route::RouteAttribute::Destination( + dest, + )); + msg.attributes + .push(netlink_packet_route::route::RouteAttribute::Gateway( + gateway, + )); + msg.attributes + .push(netlink_packet_route::route::RouteAttribute::Priority( + final_metric, + )); + msg + } + + pub fn add_route(&mut self, route: &Route) -> NetavarkResult<()> { + let msg = Self::create_route_msg(route); + info!("Adding route {route}"); + + let result = self + .make_netlink_request(RouteNetlinkMessage::NewRoute(msg), NLM_F_ACK | NLM_F_CREATE)?; + expect_netlink_result!(result, 0); + + Ok(()) + } + + pub fn del_route(&mut self, route: &Route) -> NetavarkResult<()> { + let msg = Self::create_route_msg(route); + info!("Deleting route {route}"); + + let result = self.make_netlink_request(RouteNetlinkMessage::DelRoute(msg), NLM_F_ACK)?; + expect_netlink_result!(result, 0); + + Ok(()) + } + + pub fn dump_routes(&mut self) -> NetavarkResult> { + let mut msg = RouteMessage::default(); + + msg.header.table = libc::RT_TABLE_MAIN; + msg.header.protocol = RouteProtocol::Unspec; + msg.header.scope = RouteScope::Universe; + msg.header.kind = RouteType::Unicast; + + let results = + self.make_netlink_request(RouteNetlinkMessage::GetRoute(msg), NLM_F_DUMP | NLM_F_ACK)?; + + let mut routes = Vec::with_capacity(results.len()); + + for res in results { + match res { + RouteNetlinkMessage::NewRoute(m) => routes.push(m), + m => { + return Err(NetavarkError::Message(format!( + "unexpected netlink message type: {}", + m.message_type() + ))) + } + }; + } + Ok(routes) + } + + pub fn dump_links( + &mut self, + nlas: &mut Vec, + ) -> NetavarkResult> { + let mut msg = LinkMessage::default(); + msg.attributes.append(nlas); + + let results = + self.make_netlink_request(RouteNetlinkMessage::GetLink(msg), NLM_F_DUMP | NLM_F_ACK)?; + + let mut links = Vec::with_capacity(results.len()); + + for res in results { + match res { + RouteNetlinkMessage::NewLink(m) => links.push(m), + m => { + return Err(NetavarkError::Message(format!( + "unexpected netlink message type: {}", + m.message_type() + ))) + } + }; + } + Ok(links) + } + + // If filtering options are supplied, then only the ip addresses satisfying the filter are returned. Otherwise all ip addresses of all interfaces are returned + pub fn dump_addresses( + &mut self, + interface_id_filter: Option, + ) -> NetavarkResult> { + let mut msg = AddressMessage::default(); + + if let Some(id) = interface_id_filter { + msg.header.index = id; + } + + let results = + self.make_netlink_request(RouteNetlinkMessage::GetAddress(msg), NLM_F_DUMP)?; + + let mut addresses = Vec::with_capacity(results.len()); + + for res in results { + match res { + RouteNetlinkMessage::NewAddress(m) => addresses.push(m), + m => { + return Err(NetavarkError::Message(format!( + "unexpected netlink message type: {}", + m.message_type() + ))) + } + }; + } + Ok(addresses) + } + + pub fn set_up(&mut self, id: LinkID) -> NetavarkResult<()> { + let mut msg = LinkMessage::default(); + + match id { + LinkID::ID(id) => msg.header.index = id, + LinkID::Name(name) => msg.attributes.push(LinkAttribute::IfName(name)), + } + + msg.header.flags = LinkFlags::Up; + msg.header.change_mask = LinkFlags::Up; + + let result = self.make_netlink_request( + RouteNetlinkMessage::SetLink(msg), + NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE, + )?; + expect_netlink_result!(result, 0); + + Ok(()) + } + + pub fn set_mac_address(&mut self, id: LinkID, mac: Vec) -> NetavarkResult<()> { + let mut msg = LinkMessage::default(); + + match id { + LinkID::ID(id) => msg.header.index = id, + LinkID::Name(name) => msg.attributes.push(LinkAttribute::IfName(name)), + } + + msg.attributes.push(LinkAttribute::Address(mac)); + + let result = self.make_netlink_request(RouteNetlinkMessage::SetLink(msg), NLM_F_ACK)?; + expect_netlink_result!(result, 0); + + Ok(()) + } +} + +impl CreateLinkOptions<'_> { + pub fn new(name: String, kind: InfoKind) -> Self { + CreateLinkOptions { + name, + kind, + info_data: None, + mtu: 0, + primary_index: 0, + link: 0, + mac: vec![], + netns: None, + } + } +} + +pub fn parse_create_link_options(msg: &mut LinkMessage, options: CreateLinkOptions) { + // add link specific data + let mut link_info_nlas = vec![LinkInfo::Kind(options.kind)]; + if let Some(data) = options.info_data { + link_info_nlas.push(LinkInfo::Data(data)); + } + msg.attributes.push(LinkAttribute::LinkInfo(link_info_nlas)); + + // add name + if !options.name.is_empty() { + msg.attributes.push(LinkAttribute::IfName(options.name)); + } + + // add mtu + if options.mtu != 0 { + msg.attributes.push(LinkAttribute::Mtu(options.mtu)); + } + + // add mac address + if !options.mac.is_empty() { + msg.attributes.push(LinkAttribute::Address(options.mac)); + } + + // add primary device + if options.primary_index != 0 { + msg.attributes + .push(LinkAttribute::Controller(options.primary_index)); + } + + // add link device + if options.link != 0 { + msg.attributes.push(LinkAttribute::Link(options.link)); + } + + // add netnsfd + if let Some(netns) = options.netns { + msg.attributes + .push(LinkAttribute::NetNsFd(netns.as_raw_fd())); + } +} diff --git a/src/network/plugin.rs b/src/network/plugin.rs index dc0e753..9620ac6 100644 --- a/src/network/plugin.rs +++ b/src/network/plugin.rs @@ -14,6 +14,8 @@ use super::{ driver::{DriverInfo, NetworkDriver}, types, }; +use crate::network::netlink::Socket; +use crate::network::netlink_route::NetlinkRoute; pub struct PluginDriver<'a> { path: PathBuf, @@ -36,7 +38,7 @@ impl NetworkDriver for PluginDriver<'_> { fn setup( &self, - _netlink_sockets: (&mut super::netlink::Socket, &mut super::netlink::Socket), + _netlink_sockets: (&mut Socket, &mut Socket), ) -> NetavarkResult<(types::StatusBlock, Option>)> { let result = self.exec_plugin(true, self.info.netns_path).wrap(format!( "plugin {:?} failed", @@ -49,7 +51,7 @@ impl NetworkDriver for PluginDriver<'_> { fn teardown( &self, - _netlink_sockets: (&mut super::netlink::Socket, &mut super::netlink::Socket), + _netlink_sockets: (&mut Socket, &mut Socket), ) -> NetavarkResult<()> { self.exec_plugin(false, self.info.netns_path).wrap(format!( "plugin {:?} failed", diff --git a/src/network/vlan.rs b/src/network/vlan.rs index 831121c..36216c5 100644 --- a/src/network/vlan.rs +++ b/src/network/vlan.rs @@ -2,6 +2,8 @@ use log::{debug, error}; use std::os::fd::BorrowedFd; use std::{collections::HashMap, net::IpAddr}; +use crate::network::netlink::Socket; +use crate::network::netlink_route::{CreateLinkOptions, LinkID, NetlinkRoute}; use netlink_packet_route::link::{ InfoData, InfoIpVlan, InfoKind, InfoMacVlan, IpVlanMode, MacVlanMode, }; @@ -25,7 +27,6 @@ use super::{ core_utils::{self, get_ipam_addresses, get_mac_address, parse_option, CoreUtils}, driver::{self, DriverInfo}, internal_types::IPAMAddresses, - netlink::{self, CreateLinkOptions}, types::{NetInterface, StatusBlock}, }; @@ -145,7 +146,7 @@ impl driver::NetworkDriver for Vlan<'_> { fn setup( &self, - netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), + netlink_sockets: (&mut Socket, &mut Socket), ) -> Result<(StatusBlock, Option>), NetavarkError> { let data = match &self.data { Some(d) => d, @@ -218,7 +219,7 @@ impl driver::NetworkDriver for Vlan<'_> { fn teardown( &self, - netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket), + netlink_sockets: (&mut Socket, &mut Socket), ) -> NetavarkResult<()> { dhcp_teardown(&self.info, netlink_sockets.1)?; @@ -227,7 +228,7 @@ impl driver::NetworkDriver for Vlan<'_> { netlink_sockets.1.del_route(route)?; } - netlink_sockets.1.del_link(netlink::LinkID::Name( + netlink_sockets.1.del_link(LinkID::Name( self.info.per_network_opts.interface_name.to_string(), ))?; Ok(()) @@ -235,8 +236,8 @@ impl driver::NetworkDriver for Vlan<'_> { } fn setup( - host: &mut netlink::Socket, - netns: &mut netlink::Socket, + host: &mut Socket, + netns: &mut Socket, if_name: &str, data: &InternalData, hostns_fd: BorrowedFd<'_>, @@ -245,7 +246,7 @@ fn setup( ) -> NetavarkResult { let link = match data.host_interface_name.as_ref() { "" => get_default_route_interface(host)?, - host_name => host.get_link(netlink::LinkID::Name(host_name.to_string()))?, + host_name => host.get_link(LinkID::Name(host_name.to_string()))?, }; let opts = match kind_data { @@ -307,7 +308,7 @@ fn setup( } let link = netns - .get_link(netlink::LinkID::Name(tmp_name.clone())) + .get_link(LinkID::Name(tmp_name.clone())) .wrap(format!("get tmp {kind_data} interface"))?; netns .set_link_name(link.header.index, if_name.to_string()) @@ -315,8 +316,7 @@ fn setup( .inspect_err(|_| { // If there is an error here most likely the name in the netns is already used, // make sure to delete the tmp interface. - if let Err(err) = netns.del_link(netlink::LinkID::ID(link.header.index)) - { + if let Err(err) = netns.del_link(LinkID::ID(link.header.index)) { error!("failed to delete tmp {kind_data} link {tmp_name}: {err}"); }; })?; @@ -332,7 +332,7 @@ fn setup( exec_netns!(hostns_fd, netns_fd, { disable_ipv6_autoconf(if_name) })?; let dev = netns - .get_link(netlink::LinkID::Name(if_name.to_string())) + .get_link(LinkID::Name(if_name.to_string())) .wrap(format!("get {kind_data} interface"))?; for addr in &data.ipam.container_addresses { @@ -342,7 +342,7 @@ fn setup( } netns - .set_up(netlink::LinkID::ID(dev.header.index)) + .set_up(LinkID::ID(dev.header.index)) .wrap(format!("set {kind_data} up"))?; if !data.no_default_route { diff --git a/src/test/netlink.rs b/src/test/netlink.rs index a13a972..85907c7 100644 --- a/src/test/netlink.rs +++ b/src/test/netlink.rs @@ -2,7 +2,8 @@ mod tests { use std::net::{IpAddr, Ipv4Addr}; - use netavark::network::netlink::*; + use netavark::network::netlink::Socket; + use netavark::network::netlink_route::{CreateLinkOptions, LinkID, NetlinkRoute, Route}; use netlink_packet_route::{address, link::InfoKind}; macro_rules! test_setup { @@ -28,13 +29,16 @@ mod tests { #[test] fn test_socket_new() { test_setup!(); - assert!(Socket::new().is_ok(), "Netlink Socket::new() should work"); + assert!( + Socket::::new().is_ok(), + "Netlink Socket::new() should work" + ); } #[test] fn test_add_link() { test_setup!(); - let mut sock = Socket::new().expect("Socket::new()"); + let mut sock = Socket::::new().expect("Socket::new()"); let name = String::from("test1"); sock.create_link(CreateLinkOptions::new(name.clone(), InfoKind::Dummy)) @@ -49,7 +53,7 @@ mod tests { #[test] fn test_add_addr() { test_setup!(); - let mut sock = Socket::new().expect("Socket::new()"); + let mut sock = Socket::::new().expect("Socket::new()"); let out = run_command!("ip", "link", "add", "test1", "type", "dummy"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); @@ -72,7 +76,7 @@ mod tests { #[test] fn test_del_addr() { test_setup!(); - let mut sock = Socket::new().expect("Socket::new()"); + let mut sock = Socket::::new().expect("Socket::new()"); let out = run_command!("ip", "link", "add", "test1", "type", "dummy"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); @@ -110,7 +114,7 @@ mod tests { #[ignore] fn test_del_route() { test_setup!(); - let mut sock = Socket::new().expect("Socket::new()"); + let mut sock = Socket::::new().expect("Socket::new()"); let out = run_command!("ip", "link", "add", "test1", "type", "dummy"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); @@ -159,7 +163,7 @@ mod tests { #[test] fn test_dump_addr() { test_setup!(); - let mut sock = Socket::new().expect("Socket::new()"); + let mut sock = Socket::::new().expect("Socket::new()"); let out = run_command!("ip", "link", "add", "test1", "type", "dummy"); eprintln!("{}", String::from_utf8(out.stderr).unwrap()); @@ -190,7 +194,7 @@ mod tests { #[test] fn test_dump_addr_filter() { test_setup!(); - let mut sock = Socket::new().expect("Socket::new()"); + let mut sock = Socket::::new().expect("Socket::new()"); let out = run_command!("ip", "link", "add", "test1", "type", "dummy"); eprintln!("{}", String::from_utf8(out.stderr).unwrap());