use std::ffi::CString;
use std::io::{self, Read, Write};
use std::mem;
use std::net::{IpAddr, Ipv4Addr};
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use ipnetwork::IpNetwork;
use crate::error::{NetError, Result};
const NETLINK_ROUTE: i32 = 0;
const RTM_NEWLINK: u16 = 16;
const RTM_DELLINK: u16 = 17;
const RTM_GETLINK: u16 = 18;
const RTM_NEWADDR: u16 = 20;
const RTM_DELADDR: u16 = 21;
const RTM_NEWROUTE: u16 = 24;
const RTM_DELROUTE: u16 = 25;
const NLM_F_REQUEST: u16 = 0x0001;
const NLM_F_ACK: u16 = 0x0004;
const NLM_F_CREATE: u16 = 0x0400;
const NLM_F_EXCL: u16 = 0x0200;
const IFF_UP: u32 = 0x1;
const IFLA_IFNAME: u16 = 3;
const IFLA_MTU: u16 = 4;
const IFLA_LINK: u16 = 5;
const IFLA_MASTER: u16 = 10;
const IFLA_LINKINFO: u16 = 18;
const IFLA_INFO_KIND: u16 = 1;
const IFLA_ADDRESS: u16 = 1;
const IFA_ADDRESS: u16 = 1;
const IFA_LOCAL: u16 = 2;
const RTA_DST: u16 = 1;
const RTA_GATEWAY: u16 = 5;
const RTA_OIF: u16 = 4;
const RTA_PRIORITY: u16 = 6;
const RT_TABLE_MAIN: u8 = 254;
const RTPROT_BOOT: u8 = 3;
const RT_SCOPE_UNIVERSE: u8 = 0;
const RTN_UNICAST: u8 = 1;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct NlMsgHdr {
nlmsg_len: u32,
nlmsg_type: u16,
nlmsg_flags: u16,
nlmsg_seq: u32,
nlmsg_pid: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct IfInfoMsg {
ifi_family: u8,
_pad: u8,
ifi_type: u16,
ifi_index: i32,
ifi_flags: u32,
ifi_change: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct IfAddrMsg {
ifa_family: u8,
ifa_prefixlen: u8,
ifa_flags: u8,
ifa_scope: u8,
ifa_index: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct RtMsg {
rtm_family: u8,
rtm_dst_len: u8,
rtm_src_len: u8,
rtm_tos: u8,
rtm_table: u8,
rtm_protocol: u8,
rtm_scope: u8,
rtm_type: u8,
rtm_flags: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct NlAttr {
nla_len: u16,
nla_type: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkType {
Bridge,
Tap,
Veth,
Dummy,
}
impl LinkType {
fn kind(&self) -> &'static str {
match self {
Self::Bridge => "bridge",
Self::Tap => "tun",
Self::Veth => "veth",
Self::Dummy => "dummy",
}
}
}
#[derive(Debug, Clone)]
pub struct LinkConfig {
pub name: String,
pub link_type: LinkType,
pub mtu: Option<u16>,
pub mac: Option<[u8; 6]>,
}
#[derive(Debug, Clone)]
pub struct Route {
pub destination: IpNetwork,
pub gateway: Option<IpAddr>,
pub ifindex: u32,
pub metric: Option<u32>,
}
pub struct NetlinkHandle {
fd: OwnedFd,
seq: u32,
}
impl NetlinkHandle {
pub fn new() -> Result<Self> {
let fd = unsafe {
libc::socket(
libc::AF_NETLINK,
libc::SOCK_RAW | libc::SOCK_CLOEXEC,
NETLINK_ROUTE,
)
};
if fd < 0 {
return Err(NetError::Netlink(format!(
"failed to create netlink socket: {}",
io::Error::last_os_error()
)));
}
let addr = libc::sockaddr_nl {
nl_family: libc::AF_NETLINK as u16,
nl_pad: 0,
nl_pid: 0, nl_groups: 0,
};
let ret = unsafe {
libc::bind(
fd,
&addr as *const _ as *const libc::sockaddr,
mem::size_of::<libc::sockaddr_nl>() as u32,
)
};
if ret < 0 {
unsafe { libc::close(fd) };
return Err(NetError::Netlink(format!(
"failed to bind netlink socket: {}",
io::Error::last_os_error()
)));
}
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
Ok(Self { fd, seq: 0 })
}
fn next_seq(&mut self) -> u32 {
self.seq = self.seq.wrapping_add(1);
self.seq
}
fn send_and_ack(&mut self, msg: &[u8]) -> Result<()> {
let ret = unsafe {
libc::send(
self.fd.as_raw_fd(),
msg.as_ptr() as *const libc::c_void,
msg.len(),
0,
)
};
if ret < 0 {
return Err(NetError::Netlink(format!(
"failed to send netlink message: {}",
io::Error::last_os_error()
)));
}
let mut buf = [0u8; 4096];
let len = unsafe {
libc::recv(
self.fd.as_raw_fd(),
buf.as_mut_ptr() as *mut libc::c_void,
buf.len(),
0,
)
};
if len < 0 {
return Err(NetError::Netlink(format!(
"failed to receive netlink response: {}",
io::Error::last_os_error()
)));
}
if len >= mem::size_of::<NlMsgHdr>() as isize {
let hdr = unsafe { &*(buf.as_ptr() as *const NlMsgHdr) };
if hdr.nlmsg_type == libc::NLMSG_ERROR as u16 {
if len >= (mem::size_of::<NlMsgHdr>() + 4) as isize {
let error_code =
unsafe { *(buf.as_ptr().add(mem::size_of::<NlMsgHdr>()) as *const i32) };
if error_code != 0 {
return Err(NetError::Netlink(format!(
"netlink error: {}",
io::Error::from_raw_os_error(-error_code)
)));
}
}
}
}
Ok(())
}
pub fn create_link(&mut self, config: &LinkConfig) -> Result<u32> {
let seq = self.next_seq();
let mut msg = Vec::with_capacity(256);
msg.extend_from_slice(&[0u8; mem::size_of::<NlMsgHdr>()]);
let ifinfo = IfInfoMsg {
ifi_family: libc::AF_UNSPEC as u8,
_pad: 0,
ifi_type: 0,
ifi_index: 0,
ifi_flags: 0,
ifi_change: 0,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(
&ifinfo as *const _ as *const u8,
mem::size_of::<IfInfoMsg>(),
)
});
self.add_attr_string(&mut msg, IFLA_IFNAME, &config.name);
let linkinfo_start = msg.len();
msg.extend_from_slice(&[0u8; mem::size_of::<NlAttr>()]); self.add_attr_string(&mut msg, IFLA_INFO_KIND, config.link_type.kind());
let linkinfo_len = (msg.len() - linkinfo_start) as u16;
let linkinfo_attr = NlAttr {
nla_len: linkinfo_len,
nla_type: IFLA_LINKINFO | (1 << 15), };
msg[linkinfo_start..linkinfo_start + mem::size_of::<NlAttr>()].copy_from_slice(unsafe {
std::slice::from_raw_parts(
&linkinfo_attr as *const _ as *const u8,
mem::size_of::<NlAttr>(),
)
});
if let Some(mtu) = config.mtu {
self.add_attr_u32(&mut msg, IFLA_MTU, u32::from(mtu));
}
if let Some(mac) = config.mac {
self.add_attr_bytes(&mut msg, IFLA_ADDRESS, &mac);
}
let hdr = NlMsgHdr {
nlmsg_len: msg.len() as u32,
nlmsg_type: RTM_NEWLINK,
nlmsg_flags: NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
nlmsg_seq: seq,
nlmsg_pid: 0,
};
msg[..mem::size_of::<NlMsgHdr>()].copy_from_slice(unsafe {
std::slice::from_raw_parts(&hdr as *const _ as *const u8, mem::size_of::<NlMsgHdr>())
});
self.send_and_ack(&msg)?;
self.get_ifindex(&config.name)
}
pub fn delete_link(&mut self, ifindex: u32) -> Result<()> {
let seq = self.next_seq();
let mut msg = Vec::with_capacity(64);
msg.extend_from_slice(&[0u8; mem::size_of::<NlMsgHdr>()]);
let ifinfo = IfInfoMsg {
ifi_family: libc::AF_UNSPEC as u8,
_pad: 0,
ifi_type: 0,
ifi_index: ifindex as i32,
ifi_flags: 0,
ifi_change: 0,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(
&ifinfo as *const _ as *const u8,
mem::size_of::<IfInfoMsg>(),
)
});
let hdr = NlMsgHdr {
nlmsg_len: msg.len() as u32,
nlmsg_type: RTM_DELLINK,
nlmsg_flags: NLM_F_REQUEST | NLM_F_ACK,
nlmsg_seq: seq,
nlmsg_pid: 0,
};
msg[..mem::size_of::<NlMsgHdr>()].copy_from_slice(unsafe {
std::slice::from_raw_parts(&hdr as *const _ as *const u8, mem::size_of::<NlMsgHdr>())
});
self.send_and_ack(&msg)
}
pub fn set_link_state(&mut self, ifindex: u32, up: bool) -> Result<()> {
let seq = self.next_seq();
let mut msg = Vec::with_capacity(64);
msg.extend_from_slice(&[0u8; mem::size_of::<NlMsgHdr>()]);
let flags = if up { IFF_UP } else { 0 };
let ifinfo = IfInfoMsg {
ifi_family: libc::AF_UNSPEC as u8,
_pad: 0,
ifi_type: 0,
ifi_index: ifindex as i32,
ifi_flags: flags,
ifi_change: IFF_UP,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(
&ifinfo as *const _ as *const u8,
mem::size_of::<IfInfoMsg>(),
)
});
let hdr = NlMsgHdr {
nlmsg_len: msg.len() as u32,
nlmsg_type: RTM_NEWLINK,
nlmsg_flags: NLM_F_REQUEST | NLM_F_ACK,
nlmsg_seq: seq,
nlmsg_pid: 0,
};
msg[..mem::size_of::<NlMsgHdr>()].copy_from_slice(unsafe {
std::slice::from_raw_parts(&hdr as *const _ as *const u8, mem::size_of::<NlMsgHdr>())
});
self.send_and_ack(&msg)
}
pub fn set_link_master(&mut self, ifindex: u32, master_ifindex: u32) -> Result<()> {
let seq = self.next_seq();
let mut msg = Vec::with_capacity(64);
msg.extend_from_slice(&[0u8; mem::size_of::<NlMsgHdr>()]);
let ifinfo = IfInfoMsg {
ifi_family: libc::AF_UNSPEC as u8,
_pad: 0,
ifi_type: 0,
ifi_index: ifindex as i32,
ifi_flags: 0,
ifi_change: 0,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(
&ifinfo as *const _ as *const u8,
mem::size_of::<IfInfoMsg>(),
)
});
self.add_attr_u32(&mut msg, IFLA_MASTER, master_ifindex);
let hdr = NlMsgHdr {
nlmsg_len: msg.len() as u32,
nlmsg_type: RTM_NEWLINK,
nlmsg_flags: NLM_F_REQUEST | NLM_F_ACK,
nlmsg_seq: seq,
nlmsg_pid: 0,
};
msg[..mem::size_of::<NlMsgHdr>()].copy_from_slice(unsafe {
std::slice::from_raw_parts(&hdr as *const _ as *const u8, mem::size_of::<NlMsgHdr>())
});
self.send_and_ack(&msg)
}
pub fn add_address(&mut self, ifindex: u32, addr: IpNetwork) -> Result<()> {
let seq = self.next_seq();
let mut msg = Vec::with_capacity(64);
msg.extend_from_slice(&[0u8; mem::size_of::<NlMsgHdr>()]);
let family = match addr {
IpNetwork::V4(_) => libc::AF_INET as u8,
IpNetwork::V6(_) => libc::AF_INET6 as u8,
};
let ifaddr = IfAddrMsg {
ifa_family: family,
ifa_prefixlen: addr.prefix(),
ifa_flags: 0,
ifa_scope: 0,
ifa_index: ifindex,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(
&ifaddr as *const _ as *const u8,
mem::size_of::<IfAddrMsg>(),
)
});
match addr {
IpNetwork::V4(v4) => {
let ip_bytes = v4.ip().octets();
self.add_attr_bytes(&mut msg, IFA_LOCAL, &ip_bytes);
self.add_attr_bytes(&mut msg, IFA_ADDRESS, &ip_bytes);
}
IpNetwork::V6(v6) => {
let ip_bytes = v6.ip().octets();
self.add_attr_bytes(&mut msg, IFA_LOCAL, &ip_bytes);
self.add_attr_bytes(&mut msg, IFA_ADDRESS, &ip_bytes);
}
}
let hdr = NlMsgHdr {
nlmsg_len: msg.len() as u32,
nlmsg_type: RTM_NEWADDR,
nlmsg_flags: NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
nlmsg_seq: seq,
nlmsg_pid: 0,
};
msg[..mem::size_of::<NlMsgHdr>()].copy_from_slice(unsafe {
std::slice::from_raw_parts(&hdr as *const _ as *const u8, mem::size_of::<NlMsgHdr>())
});
self.send_and_ack(&msg)
}
pub fn delete_address(&mut self, ifindex: u32, addr: IpNetwork) -> Result<()> {
let seq = self.next_seq();
let mut msg = Vec::with_capacity(64);
msg.extend_from_slice(&[0u8; mem::size_of::<NlMsgHdr>()]);
let family = match addr {
IpNetwork::V4(_) => libc::AF_INET as u8,
IpNetwork::V6(_) => libc::AF_INET6 as u8,
};
let ifaddr = IfAddrMsg {
ifa_family: family,
ifa_prefixlen: addr.prefix(),
ifa_flags: 0,
ifa_scope: 0,
ifa_index: ifindex,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(
&ifaddr as *const _ as *const u8,
mem::size_of::<IfAddrMsg>(),
)
});
match addr {
IpNetwork::V4(v4) => {
let ip_bytes = v4.ip().octets();
self.add_attr_bytes(&mut msg, IFA_LOCAL, &ip_bytes);
}
IpNetwork::V6(v6) => {
let ip_bytes = v6.ip().octets();
self.add_attr_bytes(&mut msg, IFA_LOCAL, &ip_bytes);
}
}
let hdr = NlMsgHdr {
nlmsg_len: msg.len() as u32,
nlmsg_type: RTM_DELADDR,
nlmsg_flags: NLM_F_REQUEST | NLM_F_ACK,
nlmsg_seq: seq,
nlmsg_pid: 0,
};
msg[..mem::size_of::<NlMsgHdr>()].copy_from_slice(unsafe {
std::slice::from_raw_parts(&hdr as *const _ as *const u8, mem::size_of::<NlMsgHdr>())
});
self.send_and_ack(&msg)
}
pub fn add_route(&mut self, route: &Route) -> Result<()> {
let seq = self.next_seq();
let mut msg = Vec::with_capacity(128);
msg.extend_from_slice(&[0u8; mem::size_of::<NlMsgHdr>()]);
let family = match route.destination {
IpNetwork::V4(_) => libc::AF_INET as u8,
IpNetwork::V6(_) => libc::AF_INET6 as u8,
};
let rtmsg = RtMsg {
rtm_family: family,
rtm_dst_len: route.destination.prefix(),
rtm_src_len: 0,
rtm_tos: 0,
rtm_table: RT_TABLE_MAIN,
rtm_protocol: RTPROT_BOOT,
rtm_scope: RT_SCOPE_UNIVERSE,
rtm_type: RTN_UNICAST,
rtm_flags: 0,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(&rtmsg as *const _ as *const u8, mem::size_of::<RtMsg>())
});
match route.destination {
IpNetwork::V4(v4) => {
if route.destination.prefix() > 0 {
let ip_bytes = v4.ip().octets();
self.add_attr_bytes(&mut msg, RTA_DST, &ip_bytes);
}
}
IpNetwork::V6(v6) => {
if route.destination.prefix() > 0 {
let ip_bytes = v6.ip().octets();
self.add_attr_bytes(&mut msg, RTA_DST, &ip_bytes);
}
}
}
if let Some(gateway) = route.gateway {
match gateway {
IpAddr::V4(v4) => {
self.add_attr_bytes(&mut msg, RTA_GATEWAY, &v4.octets());
}
IpAddr::V6(v6) => {
self.add_attr_bytes(&mut msg, RTA_GATEWAY, &v6.octets());
}
}
}
self.add_attr_u32(&mut msg, RTA_OIF, route.ifindex);
if let Some(metric) = route.metric {
self.add_attr_u32(&mut msg, RTA_PRIORITY, metric);
}
let hdr = NlMsgHdr {
nlmsg_len: msg.len() as u32,
nlmsg_type: RTM_NEWROUTE,
nlmsg_flags: NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
nlmsg_seq: seq,
nlmsg_pid: 0,
};
msg[..mem::size_of::<NlMsgHdr>()].copy_from_slice(unsafe {
std::slice::from_raw_parts(&hdr as *const _ as *const u8, mem::size_of::<NlMsgHdr>())
});
self.send_and_ack(&msg)
}
pub fn delete_route(&mut self, route: &Route) -> Result<()> {
let seq = self.next_seq();
let mut msg = Vec::with_capacity(128);
msg.extend_from_slice(&[0u8; mem::size_of::<NlMsgHdr>()]);
let family = match route.destination {
IpNetwork::V4(_) => libc::AF_INET as u8,
IpNetwork::V6(_) => libc::AF_INET6 as u8,
};
let rtmsg = RtMsg {
rtm_family: family,
rtm_dst_len: route.destination.prefix(),
rtm_src_len: 0,
rtm_tos: 0,
rtm_table: RT_TABLE_MAIN,
rtm_protocol: RTPROT_BOOT,
rtm_scope: RT_SCOPE_UNIVERSE,
rtm_type: RTN_UNICAST,
rtm_flags: 0,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(&rtmsg as *const _ as *const u8, mem::size_of::<RtMsg>())
});
match route.destination {
IpNetwork::V4(v4) => {
if route.destination.prefix() > 0 {
self.add_attr_bytes(&mut msg, RTA_DST, &v4.ip().octets());
}
}
IpNetwork::V6(v6) => {
if route.destination.prefix() > 0 {
self.add_attr_bytes(&mut msg, RTA_DST, &v6.ip().octets());
}
}
}
let hdr = NlMsgHdr {
nlmsg_len: msg.len() as u32,
nlmsg_type: RTM_DELROUTE,
nlmsg_flags: NLM_F_REQUEST | NLM_F_ACK,
nlmsg_seq: seq,
nlmsg_pid: 0,
};
msg[..mem::size_of::<NlMsgHdr>()].copy_from_slice(unsafe {
std::slice::from_raw_parts(&hdr as *const _ as *const u8, mem::size_of::<NlMsgHdr>())
});
self.send_and_ack(&msg)
}
pub fn get_ifindex(&self, name: &str) -> Result<u32> {
let c_name = CString::new(name).map_err(|e| NetError::Netlink(e.to_string()))?;
let ifindex = unsafe { libc::if_nametoindex(c_name.as_ptr()) };
if ifindex == 0 {
return Err(NetError::Netlink(format!("interface not found: {name}")));
}
Ok(ifindex)
}
pub fn get_ifname(&self, ifindex: u32) -> Result<String> {
let mut buf = [0i8; libc::IF_NAMESIZE];
let ret = unsafe { libc::if_indextoname(ifindex, buf.as_mut_ptr()) };
if ret.is_null() {
return Err(NetError::Netlink(format!(
"interface index not found: {ifindex}"
)));
}
let len = buf
.iter()
.position(|&c| c == 0)
.unwrap_or(libc::IF_NAMESIZE);
let name_bytes: Vec<u8> = buf[..len].iter().map(|&c| c as u8).collect();
String::from_utf8(name_bytes).map_err(|e| NetError::Netlink(e.to_string()))
}
fn add_attr_string(&self, msg: &mut Vec<u8>, attr_type: u16, value: &str) {
let value_bytes = value.as_bytes();
let attr_len = mem::size_of::<NlAttr>() + value_bytes.len() + 1; let padded_len = (attr_len + 3) & !3;
let attr = NlAttr {
nla_len: attr_len as u16,
nla_type: attr_type,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(&attr as *const _ as *const u8, mem::size_of::<NlAttr>())
});
msg.extend_from_slice(value_bytes);
msg.push(0);
let padding = padded_len - attr_len;
msg.extend(std::iter::repeat(0).take(padding));
}
fn add_attr_u32(&self, msg: &mut Vec<u8>, attr_type: u16, value: u32) {
let attr_len = mem::size_of::<NlAttr>() + mem::size_of::<u32>();
let attr = NlAttr {
nla_len: attr_len as u16,
nla_type: attr_type,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(&attr as *const _ as *const u8, mem::size_of::<NlAttr>())
});
msg.extend_from_slice(&value.to_ne_bytes());
}
fn add_attr_bytes(&self, msg: &mut Vec<u8>, attr_type: u16, value: &[u8]) {
let attr_len = mem::size_of::<NlAttr>() + value.len();
let padded_len = (attr_len + 3) & !3;
let attr = NlAttr {
nla_len: attr_len as u16,
nla_type: attr_type,
};
msg.extend_from_slice(unsafe {
std::slice::from_raw_parts(&attr as *const _ as *const u8, mem::size_of::<NlAttr>())
});
msg.extend_from_slice(value);
let padding = padded_len - attr_len;
msg.extend(std::iter::repeat(0).take(padding));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_netlink_handle_creation() {
if unsafe { libc::geteuid() } != 0 {
eprintln!("Skipping test: requires root privileges");
return;
}
let handle = NetlinkHandle::new();
assert!(handle.is_ok());
}
#[test]
fn test_get_ifindex_loopback() {
if unsafe { libc::geteuid() } != 0 {
eprintln!("Skipping test: requires root privileges");
return;
}
let handle = NetlinkHandle::new().unwrap();
let ifindex = handle.get_ifindex("lo");
assert!(ifindex.is_ok());
assert!(ifindex.unwrap() > 0);
}
}