use netlink_bindings::{builtin::BuiltinNfgenmsg, netdev, nlctrl, rt_link, traits::NetlinkRequest};
use netlink_socket2::{MulticastSocketRaw, NetlinkSocket, ReplyError};
#[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
#[cfg_attr(feature = "tokio", tokio::main(flavor = "current_thread"))]
#[cfg_attr(feature = "smol", macro_rules_attribute::apply(smol_macros::main))]
async fn main() {
let mut sock = NetlinkSocket::new();
let mut multicast_sock = MulticastSocketRaw::new(nlctrl::PROTONUM).unwrap();
match resolve_genl_group_id(&mut sock, netdev::PROTONAME, netdev::NotifGroup::MGMT).await {
Ok(group_id) => multicast_sock.listen(group_id).unwrap(),
Err(err) => {
println!("Can't resolve group id: {err}");
println!("Netdev notifications were added in Linux 6.3. The current kernel is older");
if std::env::var("TESTING").is_ok()
&& err.as_io_error().kind() == std::io::ErrorKind::NotFound
{
return;
}
std::process::exit(1);
}
}
for i in 0..100 {
if multicast_sock.listen(i).is_ok() {
let group = lookup_genl_group(&mut sock, i).await;
println!("Group {i}: {group}");
}
}
let link = "example-link";
link_add(&mut sock, link).await;
link_del(&mut sock, link).await;
loop {
let (recv, buf) = multicast_sock.recv().await.unwrap();
let _family_id = recv.message_type;
let multicast_group = recv.multicast_group;
let group = lookup_genl_group(&mut sock, multicast_group).await;
let BuiltinNfgenmsg { cmd, version, .. } = BuiltinNfgenmsg::new_from_zeroed(buf);
println!("Message from group={group:?} cmd={cmd:?} version={version:?}");
match group.as_str() {
"netdev: mgmt" => {
let attrs = netdev::OpDevGetDo::decode_reply(buf);
dbg!(attrs);
let op = match cmd {
netdev::OpDevAddNotif::CMD => Some("creating"),
netdev::OpDevDelNotif::CMD => Some("deleting"),
netdev::OpDevChangeNotif::CMD => Some("changing"),
_ => None,
};
let ifindex = attrs.get_ifindex().unwrap();
if let Some(op) = op {
println!("Caught {op} device with ifindex={ifindex}");
}
if std::env::var("TESTING").is_ok() && op == Some("deleting") {
return;
}
}
"netdev: page-pool" => {
dbg!(netlink_bindings::netdev::OpPagePoolGetDo::decode_reply(buf));
}
_ if group.starts_with("nl80211") => {}
_ => {}
}
}
}
#[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
async fn resolve_genl_group_id(
sock: &mut NetlinkSocket,
family: &str,
group_name: &str,
) -> Result<u32, ReplyError> {
let mut request = nlctrl::Request::new().op_getfamily_do();
request.encode().push_family_name_bytes(family.as_bytes());
let mut iter = sock.request(&request).await?;
let attrs = iter.recv_one().await?;
for group in attrs.get_mcast_groups()? {
if group.get_name()?.to_bytes() == group_name.as_bytes() {
return Ok(group.get_id()?);
}
}
panic!("Couldn't resolve group id by group_name={group_name:?}")
}
#[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
async fn lookup_genl_group(sock: &mut NetlinkSocket, group_id: u32) -> String {
let request = nlctrl::Request::new().op_getfamily_dump();
let mut iter = sock.request(&request).await.unwrap();
while let Some(res) = iter.recv().await {
let attrs = res.unwrap();
for group in attrs.get_mcast_groups().unwrap_or_default() {
if group.get_id().unwrap() == group_id {
let family = attrs.get_family_name().unwrap().to_str().unwrap();
let group = group.get_name().unwrap().to_str().unwrap();
return format!("{family}: {group}");
}
}
}
"(unknown)".to_string()
}
#[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
async fn link_add(sock: &mut NetlinkSocket, ifname: &str) {
let mut request = rt_link::Request::new()
.set_create()
.set_excl()
.op_newlink_do(&rt_link::Ifinfomsg::new());
request
.encode()
.push_ifname_bytes(ifname.as_bytes())
.nested_linkinfo()
.push_kind(c"dummy");
let mut iter = sock.request(&request).await.unwrap();
let _ = iter.recv_ack().await;
}
#[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
async fn link_del(sock: &mut NetlinkSocket, ifname: &str) {
let mut request = rt_link::Request::new().op_dellink_do(&Default::default());
request.encode().push_ifname_bytes(ifname.as_bytes());
let mut iter = sock.request(&request).await.unwrap();
let _ = iter.recv_ack().await;
}