use std::net::IpAddr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum NetlinkError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("link '{0}' not found")]
NotFound(String),
#[error("netlink operation failed: {0}")]
Netlink(String),
}
pub async fn link_exists(name: &str) -> Result<bool, NetlinkError> {
use futures_util::stream::TryStreamExt;
let (connection, handle, _) = rtnetlink::new_connection()
.map_err(|e| NetlinkError::Netlink(format!("new_connection failed: {e}")))?;
tokio::spawn(connection);
let lookup = handle
.link()
.get()
.match_name(name.to_string())
.execute()
.try_next()
.await;
match lookup {
Ok(Some(_)) => Ok(true),
Ok(None) => Ok(false),
Err(rtnetlink::Error::NetlinkError(err)) => {
let msg = err.to_string();
let is_enodev = err.code.is_some_and(|c| c.get().unsigned_abs() == 19);
if is_enodev || msg.contains("No such device") {
Ok(false)
} else {
Err(NetlinkError::Netlink(format!(
"link lookup failed for {name}: {msg}"
)))
}
}
Err(e) => {
let msg = e.to_string();
if msg.contains("No such device") {
Ok(false)
} else {
Err(NetlinkError::Netlink(format!(
"link lookup failed for {name}: {msg}"
)))
}
}
}
}
pub async fn delete_link_by_name(name: &str) -> Result<(), NetlinkError> {
use futures_util::stream::TryStreamExt;
let (connection, handle, _) = rtnetlink::new_connection()
.map_err(|e| NetlinkError::Netlink(format!("new_connection failed: {e}")))?;
tokio::spawn(connection);
let lookup = handle
.link()
.get()
.match_name(name.to_string())
.execute()
.try_next()
.await;
let link = match lookup {
Ok(Some(link)) => link,
Ok(None) => return Ok(()),
Err(rtnetlink::Error::NetlinkError(err)) => {
let msg = err.to_string();
let is_enodev = err.code.is_some_and(|c| c.get().unsigned_abs() == 19);
if is_enodev || msg.contains("No such device") {
return Ok(());
}
return Err(NetlinkError::Netlink(format!(
"link lookup failed for {name}: {msg}"
)));
}
Err(e) => {
let msg = e.to_string();
if msg.contains("No such device") {
return Ok(());
}
return Err(NetlinkError::Netlink(format!(
"link lookup failed for {name}: {msg}"
)));
}
};
let index = link.header.index;
handle
.link()
.del(index)
.execute()
.await
.map_err(|e| NetlinkError::Netlink(format!("link delete failed for {name}: {e}")))
}
pub async fn set_link_up_by_name(name: &str) -> Result<(), NetlinkError> {
use futures_util::stream::TryStreamExt;
let (connection, handle, _) = rtnetlink::new_connection()
.map_err(|e| NetlinkError::Netlink(format!("new_connection failed: {e}")))?;
tokio::spawn(connection);
let link = handle
.link()
.get()
.match_name(name.to_string())
.execute()
.try_next()
.await
.map_err(|e| {
let msg = e.to_string();
if msg.contains("No such device") {
NetlinkError::NotFound(name.to_string())
} else {
NetlinkError::Netlink(format!("link lookup failed for {name}: {msg}"))
}
})?
.ok_or_else(|| NetlinkError::NotFound(name.to_string()))?;
let index = link.header.index;
handle
.link()
.set(index)
.up()
.execute()
.await
.map_err(|e| NetlinkError::Netlink(format!("link set up failed for {name}: {e}")))
}
pub async fn add_address_to_link(
name: &str,
addr: IpAddr,
prefix_len: u8,
) -> Result<(), NetlinkError> {
use futures_util::stream::TryStreamExt;
let (connection, handle, _) = rtnetlink::new_connection()
.map_err(|e| NetlinkError::Netlink(format!("new_connection failed: {e}")))?;
tokio::spawn(connection);
let link = handle
.link()
.get()
.match_name(name.to_string())
.execute()
.try_next()
.await
.map_err(|e| {
let msg = e.to_string();
if msg.contains("No such device") {
NetlinkError::NotFound(name.to_string())
} else {
NetlinkError::Netlink(format!("link lookup failed for {name}: {msg}"))
}
})?
.ok_or_else(|| NetlinkError::NotFound(name.to_string()))?;
let index = link.header.index;
handle
.address()
.add(index, addr, prefix_len)
.execute()
.await
.map_err(|e| {
NetlinkError::Netlink(format!(
"address add failed for {name} ({addr}/{prefix_len}): {e}"
))
})
}
pub async fn add_route_via_dev(
dest: IpAddr,
prefix_len: u8,
dev_name: &str,
) -> Result<(), NetlinkError> {
use futures_util::stream::TryStreamExt;
use netlink_packet_route::route::RouteScope;
let (connection, handle, _) = rtnetlink::new_connection()
.map_err(|e| NetlinkError::Netlink(format!("new_connection failed: {e}")))?;
tokio::spawn(connection);
let link = handle
.link()
.get()
.match_name(dev_name.to_string())
.execute()
.try_next()
.await
.map_err(|e| {
let msg = e.to_string();
if msg.contains("No such device") {
NetlinkError::NotFound(dev_name.to_string())
} else {
NetlinkError::Netlink(format!("link lookup failed for {dev_name}: {msg}"))
}
})?
.ok_or_else(|| NetlinkError::NotFound(dev_name.to_string()))?;
let oif_idx = link.header.index;
match dest {
IpAddr::V4(d) => handle
.route()
.add()
.v4()
.destination_prefix(d, prefix_len)
.output_interface(oif_idx)
.scope(RouteScope::Link)
.execute()
.await
.map_err(|e| {
NetlinkError::Netlink(format!(
"route add v4 {d}/{prefix_len} dev {dev_name} failed: {e}"
))
}),
IpAddr::V6(d) => handle
.route()
.add()
.v6()
.destination_prefix(d, prefix_len)
.output_interface(oif_idx)
.scope(RouteScope::Link)
.execute()
.await
.map_err(|e| {
NetlinkError::Netlink(format!(
"route add v6 {d}/{prefix_len} dev {dev_name} failed: {e}"
))
}),
}
}