1
0
mirror of https://github.com/containers/netavark.git synced 2026-02-05 15:45:47 +01:00

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<P>` 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<NetlinkRoute>`.
- 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 <shivangraghuvanshi2005@gmail.com>
This commit is contained in:
Shivang K Raghuvanshi
2025-11-08 23:45:08 +05:30
parent 4c794dc3c8
commit b83903b0ea
15 changed files with 654 additions and 593 deletions

View File

@@ -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

View File

@@ -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<NetlinkRoute>,
netns: &mut Socket<NetlinkRoute>,
) where
I: Iterator<Item = &'a Box<dyn NetworkDriver + 'a>>,
{
for driver in drivers {

View File

@@ -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")?;

View File

@@ -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<T> {
fn new(l: &Lease, interface: &str) -> Result<Self, ProxyError>
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<NetlinkRoute>) -> Result<(), ProxyError>;
fn add_gws(&self, nls: &mut Socket<NetlinkRoute>) -> Result<(), ProxyError>;
}
fn handle_gws(g: Vec<String>, netmask: &str) -> Result<Vec<IpNet>, ProxyError> {
@@ -112,10 +112,10 @@ impl Address<Ipv4Addr> 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<NetlinkRoute>) -> 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<Ipv4Addr> 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<NetlinkRoute>) -> Result<(), ProxyError> {
debug!("adding gateways to {}", self.interface);
match core_utils::add_default_routes(nls, &self.gateways, None) {
Ok(_) => Ok(()),

View File

@@ -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<NetlinkRoute>, &mut Socket<NetlinkRoute>),
) -> NetavarkResult<(StatusBlock, Option<AardvarkEntry<'_>>)> {
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<NetlinkRoute>, &mut Socket<NetlinkRoute>),
) -> NetavarkResult<()> {
let mode: Option<String> = 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<NetlinkRoute>,
netns: &mut Socket<NetlinkRoute>,
data: &InternalData,
internal: bool,
rootless: bool,
@@ -601,9 +605,9 @@ fn create_interfaces(
netns_fd: BorrowedFd<'_>,
) -> NetavarkResult<CreateInterfacesResult> {
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<NetlinkRoute>,
netns: &mut Socket<NetlinkRoute>,
data: &InternalData,
primary_index: u32,
bridge_mac: Option<Vec<u8>>,
@@ -802,16 +804,15 @@ fn create_veth_pair<'fd>(
mtu: u32,
) -> NetavarkResult<String> {
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<NetlinkRoute>,
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<LinkMes
}
fn remove_link(
host: &mut netlink::Socket,
netns: &mut netlink::Socket,
host: &mut Socket<NetlinkRoute>,
netns: &mut Socket<NetlinkRoute>,
mode: BridgeMode,
br_name: &str,
container_veth_name: &str,
) -> NetavarkResult<bool> {
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);
}

View File

@@ -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<netlink::Route> = match create_route_list(&network.routes) {
let routes: Vec<Route> = 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<NetlinkRoute>,
}
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::<NetlinkRoute>::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::<NetlinkRoute>::new().wrap("netns netlink socket")
)?;
Ok((
@@ -307,7 +309,7 @@ fn open_netlink_socket(netns_path: &str) -> NetavarkResult<File> {
}
pub fn add_default_routes(
sock: &mut netlink::Socket,
sock: &mut Socket<NetlinkRoute>,
gws: &[ipnet::IpNet],
metric: Option<u32>,
) -> 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<Vec<types::Route>>,
) -> NetavarkResult<Vec<netlink::Route>> {
pub fn create_route_list(routes: &Option<Vec<types::Route>>) -> NetavarkResult<Vec<Route>> {
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<LinkMessage> {
pub fn get_default_route_interface(host: &mut Socket<NetlinkRoute>) -> NetavarkResult<LinkMessage> {
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"))

View File

@@ -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<NetAddress>, Option<Vec<IpAddr>>, Option<Vec<String>>);
@@ -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<NetlinkRoute>) -> 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
))?;

View File

@@ -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<NetlinkRoute>, &mut Socket<NetlinkRoute>),
) -> NetavarkResult<(StatusBlock, Option<AardvarkEntry<'_>>)>;
/// teardown the network interfaces/firewall rules for this driver
fn teardown(
&self,
netlink_sockets: (&mut netlink::Socket, &mut netlink::Socket),
netlink_sockets: (&mut Socket<NetlinkRoute>, &mut Socket<NetlinkRoute>),
) -> NetavarkResult<()>;
/// return the network name

View File

@@ -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<ipnet::IpNet>,
pub routes: Vec<netlink::Route>,
pub routes: Vec<Route>,
pub ipv6_enabled: bool,
// result for podman
pub net_addresses: Vec<types::NetAddress>,

View File

@@ -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;

View File

@@ -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<P: NetlinkFamily> {
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<InfoData>,
pub mtu: u32,
pub primary_index: u32,
pub link: u32,
pub mac: Vec<u8>,
pub netns: Option<BorrowedFd<'fd>>,
}
pub enum LinkID {
ID(u32),
Name(String),
}
pub enum Route {
Ipv4 {
dest: ipnet::Ipv4Net,
gw: Ipv4Addr,
metric: Option<u32>,
},
Ipv6 {
dest: ipnet::Ipv6Net,
gw: Ipv6Addr,
metric: Option<u32>,
},
}
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<P>,
}
/// 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<Socket> {
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<P> Socket<P>
where
P: NetlinkFamily,
{
pub fn new() -> NetavarkResult<Socket<P>> {
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<LinkMessage> {
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<Fd: AsFd>(&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 <num> [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<Vec<RouteMessage>> {
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<LinkAttribute>,
) -> NetavarkResult<Vec<LinkMessage>> {
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<u32>,
) -> NetavarkResult<Vec<AddressMessage>> {
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<u8>) -> 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<Vec<RouteNetlinkMessage>> {
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<P::Message>: From<P::Message>,
{
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<Vec<RouteNetlinkMessage>> {
fn recv(&mut self, multi: bool) -> NetavarkResult<Vec<P::Message>>
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<RouteNetlinkMessage> =
NetlinkMessage::deserialize(bytes).map_err(|e| {
let rx_packet: NetlinkMessage<P::Message> = 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<Vec<P::Message>>
where
P::Message: NetlinkSerializable + NetlinkDeserializable + std::fmt::Debug,
NetlinkPayload<P::Message>: From<P::Message>,
{
self.send(msg, flags).wrap("send to netlink")?;
self.recv(flags & NLM_F_DUMP == NLM_F_DUMP)
}
}

View File

@@ -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<InfoData>,
pub mtu: u32,
pub primary_index: u32,
pub link: u32,
pub mac: Vec<u8>,
pub netns: Option<BorrowedFd<'fd>>,
}
pub enum LinkID {
ID(u32),
Name(String),
}
pub enum Route {
Ipv4 {
dest: ipnet::Ipv4Net,
gw: Ipv4Addr,
metric: Option<u32>,
},
Ipv6 {
dest: ipnet::Ipv6Net,
gw: Ipv6Addr,
metric: Option<u32>,
},
}
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<NetlinkRoute> {
pub fn get_link(&mut self, id: LinkID) -> NetavarkResult<LinkMessage> {
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<Fd: AsFd>(&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 <num> [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<Vec<RouteMessage>> {
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<LinkAttribute>,
) -> NetavarkResult<Vec<LinkMessage>> {
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<u32>,
) -> NetavarkResult<Vec<AddressMessage>> {
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<u8>) -> 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()));
}
}

View File

@@ -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<NetlinkRoute>, &mut Socket<NetlinkRoute>),
) -> NetavarkResult<(types::StatusBlock, Option<AardvarkEntry<'_>>)> {
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<NetlinkRoute>, &mut Socket<NetlinkRoute>),
) -> NetavarkResult<()> {
self.exec_plugin(false, self.info.netns_path).wrap(format!(
"plugin {:?} failed",

View File

@@ -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<NetlinkRoute>, &mut Socket<NetlinkRoute>),
) -> Result<(StatusBlock, Option<AardvarkEntry<'_>>), 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<NetlinkRoute>, &mut Socket<NetlinkRoute>),
) -> 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<NetlinkRoute>,
netns: &mut Socket<NetlinkRoute>,
if_name: &str,
data: &InternalData,
hostns_fd: BorrowedFd<'_>,
@@ -245,7 +246,7 @@ fn setup(
) -> NetavarkResult<String> {
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 {

View File

@@ -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::<NetlinkRoute>::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::<NetlinkRoute>::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::<NetlinkRoute>::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::<NetlinkRoute>::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::<NetlinkRoute>::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::<NetlinkRoute>::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::<NetlinkRoute>::new().expect("Socket::new()");
let out = run_command!("ip", "link", "add", "test1", "type", "dummy");
eprintln!("{}", String::from_utf8(out.stderr).unwrap());