use crate::error::{NucleusError, Result};
use std::net::Ipv4Addr;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
const NLMSG_ERROR: u16 = 2;
const NLMSG_DONE: u16 = 3;
const NLM_F_REQUEST: u16 = 0x01;
const NLM_F_ACK: u16 = 0x04;
const NLM_F_ROOT: u16 = 0x100;
const NLM_F_MATCH: u16 = 0x200;
const NLM_F_DUMP: u16 = NLM_F_ROOT | NLM_F_MATCH;
const NLM_F_EXCL: u16 = 0x200;
const NLM_F_CREATE: u16 = 0x400;
const RTM_NEWLINK: u16 = 16;
const RTM_DELLINK: u16 = 17;
const RTM_NEWADDR: u16 = 20;
const RTM_GETADDR: u16 = 22;
const RTM_NEWROUTE: u16 = 24;
const IFLA_IFNAME: u16 = 3;
const IFLA_MASTER: u16 = 10;
const IFLA_LINKINFO: u16 = 18;
const IFLA_NET_NS_PID: u16 = 19;
const IFLA_INFO_KIND: u16 = 1;
const IFLA_INFO_DATA: u16 = 2;
const VETH_INFO_PEER: u16 = 1;
const IFA_ADDRESS: u16 = 1;
const IFA_LOCAL: u16 = 2;
const RTA_GATEWAY: u16 = 5;
const RTN_UNICAST: u8 = 1;
const RT_SCOPE_UNIVERSE: u8 = 0;
const RT_TABLE_MAIN: u8 = 254;
const RTPROT_BOOT: u8 = 3;
const NLA_F_NESTED: u16 = 0x8000;
const IFF_UP: u32 = libc::IFF_UP as u32;
const NLMSGHDR_LEN: usize = 16;
const IFINFOMSG_LEN: usize = 16;
const IFADDRMSG_LEN: usize = 8;
const RTMSG_LEN: usize = 12;
const NLA_HDR_LEN: usize = 4;
fn align4(len: usize) -> usize {
(len + 3) & !3
}
struct NlMsg {
buf: Vec<u8>,
}
impl NlMsg {
fn new(msg_type: u16, flags: u16) -> Self {
let mut buf = vec![0u8; NLMSGHDR_LEN];
buf[4..6].copy_from_slice(&msg_type.to_ne_bytes());
buf[6..8].copy_from_slice(&flags.to_ne_bytes());
Self { buf }
}
fn set_seq(&mut self, seq: u32) {
self.buf[8..12].copy_from_slice(&seq.to_ne_bytes());
}
fn pad(&mut self) {
self.buf.resize(align4(self.buf.len()), 0);
}
fn put_ifinfomsg(&mut self, family: u8, index: i32, flags: u32, change: u32) {
let mut d = [0u8; IFINFOMSG_LEN];
d[0] = family;
d[4..8].copy_from_slice(&index.to_ne_bytes());
d[8..12].copy_from_slice(&flags.to_ne_bytes());
d[12..16].copy_from_slice(&change.to_ne_bytes());
self.buf.extend_from_slice(&d);
}
fn put_ifaddrmsg(&mut self, family: u8, prefix: u8, index: u32) {
let mut d = [0u8; IFADDRMSG_LEN];
d[0] = family;
d[1] = prefix;
d[4..8].copy_from_slice(&index.to_ne_bytes());
self.buf.extend_from_slice(&d);
}
fn put_rtmsg(
&mut self,
family: u8,
dst_len: u8,
table: u8,
protocol: u8,
scope: u8,
rt_type: u8,
) {
let mut d = [0u8; RTMSG_LEN];
d[0] = family;
d[1] = dst_len;
d[4] = table;
d[5] = protocol;
d[6] = scope;
d[7] = rt_type;
self.buf.extend_from_slice(&d);
}
fn put_attr(&mut self, attr_type: u16, data: &[u8]) {
let nla_len = (NLA_HDR_LEN + data.len()) as u16;
self.buf.extend_from_slice(&nla_len.to_ne_bytes());
self.buf.extend_from_slice(&attr_type.to_ne_bytes());
self.buf.extend_from_slice(data);
self.pad();
}
fn put_attr_str(&mut self, attr_type: u16, s: &str) {
let mut data = s.as_bytes().to_vec();
data.push(0); self.put_attr(attr_type, &data);
}
fn put_attr_u32(&mut self, attr_type: u16, val: u32) {
self.put_attr(attr_type, &val.to_ne_bytes());
}
fn begin_nested(&mut self, attr_type: u16) -> usize {
let offset = self.buf.len();
self.buf.extend_from_slice(&0u16.to_ne_bytes()); self.buf
.extend_from_slice(&(attr_type | NLA_F_NESTED).to_ne_bytes());
offset
}
fn end_nested(&mut self, offset: usize) {
let nla_len = (self.buf.len() - offset) as u16;
self.buf[offset..offset + 2].copy_from_slice(&nla_len.to_ne_bytes());
}
fn as_bytes(&mut self) -> &[u8] {
let len = self.buf.len() as u32;
self.buf[0..4].copy_from_slice(&len.to_ne_bytes());
&self.buf
}
}
struct NlSocket {
fd: OwnedFd,
seq: u32,
}
impl NlSocket {
fn open() -> Result<Self> {
let raw = unsafe {
libc::socket(
libc::AF_NETLINK,
libc::SOCK_RAW | libc::SOCK_CLOEXEC,
libc::NETLINK_ROUTE,
)
};
if raw < 0 {
return Err(NucleusError::NetworkError(format!(
"netlink socket: {}",
std::io::Error::last_os_error()
)));
}
let fd = unsafe { OwnedFd::from_raw_fd(raw) };
let mut addr: libc::sockaddr_nl = unsafe { std::mem::zeroed() };
addr.nl_family = libc::AF_NETLINK as u16;
let ret = unsafe {
libc::bind(
fd.as_raw_fd(),
&addr as *const _ as *const libc::sockaddr,
std::mem::size_of::<libc::sockaddr_nl>() as u32,
)
};
if ret < 0 {
return Err(NucleusError::NetworkError(format!(
"netlink bind: {}",
std::io::Error::last_os_error()
)));
}
Ok(Self { fd, seq: 1 })
}
fn next_seq(&mut self) -> u32 {
let s = self.seq;
self.seq = self.seq.wrapping_add(1);
s
}
fn send_raw(&self, buf: &[u8]) -> Result<()> {
let ret = unsafe {
libc::send(
self.fd.as_raw_fd(),
buf.as_ptr() as *const libc::c_void,
buf.len(),
0,
)
};
if ret < 0 {
return Err(NucleusError::NetworkError(format!(
"netlink send: {}",
std::io::Error::last_os_error()
)));
}
Ok(())
}
fn request(&mut self, msg: &mut NlMsg) -> Result<()> {
let seq = self.next_seq();
msg.set_seq(seq);
self.send_raw(msg.as_bytes())?;
self.recv_ack(seq)
}
fn recv_ack(&self, expected_seq: u32) -> Result<()> {
let mut buf = [0u8; 4096];
loop {
let n = self.recv_into(&mut buf)?;
let mut off = 0;
while off + NLMSGHDR_LEN <= n {
let (msg_len, msg_type, seq) = parse_nlmsghdr(&buf[off..]);
if msg_len < NLMSGHDR_LEN || off + msg_len > n {
break;
}
if seq == expected_seq && msg_type == NLMSG_ERROR {
return check_nl_error(&buf[off..off + msg_len]);
}
off += align4(msg_len);
}
}
}
fn dump(&mut self, msg: &mut NlMsg) -> Result<Vec<Vec<u8>>> {
let seq = self.next_seq();
msg.set_seq(seq);
self.send_raw(msg.as_bytes())?;
let mut result = Vec::new();
let mut buf = [0u8; 16384];
loop {
let n = self.recv_into(&mut buf)?;
let mut off = 0;
while off + NLMSGHDR_LEN <= n {
let (msg_len, msg_type, _seq) = parse_nlmsghdr(&buf[off..]);
if msg_len < NLMSGHDR_LEN || off + msg_len > n {
break;
}
match msg_type {
NLMSG_DONE => return Ok(result),
NLMSG_ERROR => {
check_nl_error(&buf[off..off + msg_len])?;
}
_ => {
result.push(buf[off..off + msg_len].to_vec());
}
}
off += align4(msg_len);
}
}
}
fn recv_into(&self, buf: &mut [u8]) -> Result<usize> {
let n = unsafe {
libc::recv(
self.fd.as_raw_fd(),
buf.as_mut_ptr() as *mut libc::c_void,
buf.len(),
0,
)
};
if n < 0 {
return Err(NucleusError::NetworkError(format!(
"netlink recv: {}",
std::io::Error::last_os_error()
)));
}
Ok(n as usize)
}
}
fn parse_nlmsghdr(buf: &[u8]) -> (usize, u16, u32) {
let len = u32::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
let msg_type = u16::from_ne_bytes([buf[4], buf[5]]);
let seq = u32::from_ne_bytes([buf[8], buf[9], buf[10], buf[11]]);
(len, msg_type, seq)
}
fn check_nl_error(msg: &[u8]) -> Result<()> {
if msg.len() < NLMSGHDR_LEN + 4 {
return Err(NucleusError::NetworkError(
"truncated netlink error".to_string(),
));
}
let errcode = i32::from_ne_bytes([
msg[NLMSGHDR_LEN],
msg[NLMSGHDR_LEN + 1],
msg[NLMSGHDR_LEN + 2],
msg[NLMSGHDR_LEN + 3],
]);
if errcode == 0 {
Ok(())
} else {
Err(NucleusError::NetworkError(format!(
"{}",
std::io::Error::from_raw_os_error(-errcode)
)))
}
}
fn ifindex(name: &str) -> Result<u32> {
let c_name = std::ffi::CString::new(name)
.map_err(|_| NucleusError::NetworkError(format!("invalid interface name: {}", name)))?;
let idx = unsafe { libc::if_nametoindex(c_name.as_ptr()) };
if idx == 0 {
return Err(NucleusError::NetworkError(format!(
"interface '{}' not found",
name
)));
}
Ok(idx)
}
pub fn link_exists(name: &str) -> bool {
let Ok(c_name) = std::ffi::CString::new(name) else {
return false;
};
unsafe { libc::if_nametoindex(c_name.as_ptr()) != 0 }
}
pub fn create_bridge(name: &str) -> Result<()> {
let mut sock = NlSocket::open()?;
let mut msg = NlMsg::new(
RTM_NEWLINK,
NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
);
msg.put_ifinfomsg(0, 0, 0, 0);
msg.put_attr_str(IFLA_IFNAME, name);
let li = msg.begin_nested(IFLA_LINKINFO);
msg.put_attr_str(IFLA_INFO_KIND, "bridge");
msg.end_nested(li);
sock.request(&mut msg)
.map_err(|e| NucleusError::NetworkError(format!("create bridge '{}': {}", name, e)))
}
pub fn create_veth(host_name: &str, peer_name: &str) -> Result<()> {
let mut sock = NlSocket::open()?;
let mut msg = NlMsg::new(
RTM_NEWLINK,
NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
);
msg.put_ifinfomsg(0, 0, 0, 0);
msg.put_attr_str(IFLA_IFNAME, host_name);
let li = msg.begin_nested(IFLA_LINKINFO);
msg.put_attr_str(IFLA_INFO_KIND, "veth");
let data = msg.begin_nested(IFLA_INFO_DATA);
let peer = msg.begin_nested(VETH_INFO_PEER);
msg.put_ifinfomsg(0, 0, 0, 0); msg.put_attr_str(IFLA_IFNAME, peer_name);
msg.end_nested(peer);
msg.end_nested(data);
msg.end_nested(li);
sock.request(&mut msg).map_err(|e| {
NucleusError::NetworkError(format!(
"create veth pair ({}, {}): {}",
host_name, peer_name, e
))
})
}
pub fn set_link_master(link: &str, master: &str) -> Result<()> {
let link_idx = ifindex(link)?;
let master_idx = ifindex(master)?;
let mut sock = NlSocket::open()?;
let mut msg = NlMsg::new(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK);
msg.put_ifinfomsg(0, link_idx as i32, 0, 0);
msg.put_attr_u32(IFLA_MASTER, master_idx);
sock.request(&mut msg).map_err(|e| {
NucleusError::NetworkError(format!("set master {} -> {}: {}", link, master, e))
})
}
pub fn set_link_up(name: &str) -> Result<()> {
let idx = ifindex(name)?;
let mut sock = NlSocket::open()?;
let mut msg = NlMsg::new(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK);
msg.put_ifinfomsg(0, idx as i32, IFF_UP, IFF_UP);
sock.request(&mut msg)
.map_err(|e| NucleusError::NetworkError(format!("set link up '{}': {}", name, e)))
}
pub fn set_link_netns(name: &str, pid: u32) -> Result<()> {
let idx = ifindex(name)?;
let mut sock = NlSocket::open()?;
let mut msg = NlMsg::new(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK);
msg.put_ifinfomsg(0, idx as i32, 0, 0);
msg.put_attr_u32(IFLA_NET_NS_PID, pid);
sock.request(&mut msg).map_err(|e| {
NucleusError::NetworkError(format!("set link netns '{}' -> PID {}: {}", name, pid, e))
})
}
pub fn add_addr(link: &str, addr: Ipv4Addr, prefix: u8) -> Result<()> {
let idx = ifindex(link)?;
let mut sock = NlSocket::open()?;
let mut msg = NlMsg::new(
RTM_NEWADDR,
NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
);
msg.put_ifaddrmsg(libc::AF_INET as u8, prefix, idx);
let octets = addr.octets();
msg.put_attr(IFA_LOCAL, &octets);
msg.put_attr(IFA_ADDRESS, &octets);
sock.request(&mut msg).map_err(|e| {
NucleusError::NetworkError(format!("add addr {}/{} to '{}': {}", addr, prefix, link, e))
})
}
pub fn add_default_route(gateway: Ipv4Addr) -> Result<()> {
let mut sock = NlSocket::open()?;
let mut msg = NlMsg::new(
RTM_NEWROUTE,
NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
);
msg.put_rtmsg(
libc::AF_INET as u8,
0, RT_TABLE_MAIN,
RTPROT_BOOT,
RT_SCOPE_UNIVERSE,
RTN_UNICAST,
);
msg.put_attr(RTA_GATEWAY, &gateway.octets());
sock.request(&mut msg).map_err(|e| {
NucleusError::NetworkError(format!("add default route via {}: {}", gateway, e))
})
}
pub fn del_link(name: &str) -> Result<()> {
let idx = match ifindex(name) {
Ok(i) => i,
Err(_) => return Ok(()), };
let mut sock = NlSocket::open()?;
let mut msg = NlMsg::new(RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK);
msg.put_ifinfomsg(0, idx as i32, 0, 0);
sock.request(&mut msg)
.map_err(|e| NucleusError::NetworkError(format!("del link '{}': {}", name, e)))
}
pub fn is_addr_in_use(ip: &Ipv4Addr) -> Result<bool> {
let mut sock = NlSocket::open()?;
let mut msg = NlMsg::new(RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP);
msg.put_ifaddrmsg(libc::AF_INET as u8, 0, 0);
let msgs = sock.dump(&mut msg)?;
let target = ip.octets();
for m in &msgs {
if m.len() < NLMSGHDR_LEN + IFADDRMSG_LEN {
continue;
}
let mut off = NLMSGHDR_LEN + IFADDRMSG_LEN;
while off + NLA_HDR_LEN <= m.len() {
let nla_len = u16::from_ne_bytes([m[off], m[off + 1]]) as usize;
let nla_type = u16::from_ne_bytes([m[off + 2], m[off + 3]]);
if nla_len < NLA_HDR_LEN || off + nla_len > m.len() {
break;
}
if nla_type == IFA_LOCAL
&& nla_len >= NLA_HDR_LEN + 4
&& m[off + NLA_HDR_LEN..off + NLA_HDR_LEN + 4] == target
{
return Ok(true);
}
off += align4(nla_len);
}
}
Ok(false)
}