use netlink_bindings::{rt_link, traits::NetlinkRequest};
use netlink_socket2::{MulticastSocketRaw, NetlinkSocket};
#[cfg_attr(not(feature = "async"), maybe_async::maybe_async)]
#[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 protonum = rt_link::PROTONUM ;
let mut multicast_sock = MulticastSocketRaw::new(protonum).unwrap();
multicast_sock.listen(libc::RTNLGRP_LINK).unwrap();
for i in 0..100 {
if multicast_sock.listen(i).is_ok() {
println!("Group {i}: {}", group_from_id(i).unwrap_or("(unknown)"));
}
}
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 multicast_group = recv.multicast_group;
let value = recv.message_type;
let value_str = format!(
"{} ({})",
rt_op_from_id(value).unwrap_or("(unknown)"),
value
);
let group = group_from_id(multicast_group).unwrap_or("(unknown)");
println!("Message from group={group} value={value_str}");
match value & !0b11 {
libc::RTM_NEWLINK => {
let (_header, attrs) = rt_link::OpGetlinkDo::decode_reply(buf);
let op = rt_op_from_id(value).unwrap().strip_prefix("RTM_").unwrap();
let link = attrs.get_ifname().unwrap();
let r#type = attrs
.get_linkinfo()
.unwrap_or_default()
.get_kind()
.unwrap_or(c"(unspecified)");
println!("{op} link {link:?} of type {type:?}");
if std::env::var("TESTING").is_ok() && op == "DELLINK" {
return;
}
}
_ => {}
}
}
}
macro_rules! to_from_enum {
(
fn $from_id:ident;
fn $to_id:ident;
#[repr($type:ident)]
enum $enum:ident {
$($ent:ident $(= $val:expr)?,)*
}
) => {
#[repr($type)]
#[allow(non_camel_case_types)]
enum $enum {
$($ent $(= $val)?,)*
}
fn $from_id(val: $type) -> Option<&'static str> {
$(
if val == $enum::$ent as $type {
return Some(stringify!($ent));
}
)*
None
}
#[allow(unused)]
fn $to_id(val: &str) -> Option<$type> {
let res = match val {
$(stringify!($ent) => $enum::$ent as $type,)*
_ => return None,
};
Some(res)
}
};
}
to_from_enum! {
fn group_from_id;
fn group_to_id;
#[repr(u32)]
enum Group {
RTNLGRP_NONE,
RTNLGRP_LINK,
RTNLGRP_NOTIFY,
RTNLGRP_NEIGH,
RTNLGRP_TC,
RTNLGRP_IPV4_IFADDR,
RTNLGRP_IPV4_MROUTE,
RTNLGRP_IPV4_ROUTE,
RTNLGRP_IPV4_RULE,
RTNLGRP_IPV6_IFADDR,
RTNLGRP_IPV6_MROUTE,
RTNLGRP_IPV6_ROUTE,
RTNLGRP_IPV6_IFINFO,
RTNLGRP_DECnet_IFADDR,
RTNLGRP_NOP2,
RTNLGRP_DECnet_ROUTE,
RTNLGRP_DECnet_RULE,
RTNLGRP_NOP4,
RTNLGRP_IPV6_PREFIX,
RTNLGRP_IPV6_RULE,
RTNLGRP_ND_USEROPT,
RTNLGRP_PHONET_IFADDR,
RTNLGRP_PHONET_ROUTE,
RTNLGRP_DCB,
RTNLGRP_IPV4_NETCONF,
RTNLGRP_IPV6_NETCONF,
RTNLGRP_MDB,
RTNLGRP_MPLS_ROUTE,
RTNLGRP_NSID,
RTNLGRP_MPLS_NETCONF,
RTNLGRP_IPV4_MROUTE_R,
RTNLGRP_IPV6_MROUTE_R,
RTNLGRP_NEXTHOP,
RTNLGRP_BRVLAN,
RTNLGRP_MCTP_IFADDR,
RTNLGRP_TUNNEL,
RTNLGRP_STATS,
RTNLGRP_IPV4_MCADDR,
RTNLGRP_IPV6_MCADDR,
RTNLGRP_IPV6_ACADDR,
}
}
to_from_enum! {
fn rt_op_from_id;
fn rt_op_to_id;
#[repr(u16)]
enum RtOp {
RTM_NEWLINK = 16,
RTM_DELLINK,
RTM_GETLINK,
RTM_SETLINK,
RTM_NEWADDR = 20,
RTM_DELADDR,
RTM_GETADDR,
RTM_NEWROUTE = 24,
RTM_DELROUTE,
RTM_GETROUTE,
RTM_NEWNEIGH = 28,
RTM_DELNEIGH,
RTM_GETNEIGH,
RTM_NEWRULE = 32,
RTM_DELRULE,
RTM_GETRULE,
RTM_NEWQDISC = 36,
RTM_DELQDISC,
RTM_GETQDISC,
RTM_NEWTCLASS = 40,
RTM_DELTCLASS,
RTM_GETTCLASS,
RTM_NEWTFILTER = 44,
RTM_DELTFILTER,
RTM_GETTFILTER,
RTM_NEWACTION = 48,
RTM_DELACTION,
RTM_GETACTION,
RTM_NEWPREFIX = 52,
RTM_NEWMULTICAST = 56,
RTM_DELMULTICAST,
RTM_GETMULTICAST,
RTM_NEWANYCAST = 60,
RTM_DELANYCAST,
RTM_GETANYCAST,
RTM_NEWNEIGHTBL = 64,
RTM_GETNEIGHTBL = 66,
RTM_SETNEIGHTBL,
RTM_NEWNDUSEROPT = 68,
RTM_NEWADDRLABEL = 72,
RTM_DELADDRLABEL,
RTM_GETADDRLABEL,
RTM_GETDCB = 78,
RTM_SETDCB,
RTM_NEWNETCONF = 80,
RTM_DELNETCONF,
RTM_GETNETCONF = 82,
RTM_NEWMDB = 84,
RTM_DELMDB = 85,
RTM_GETMDB = 86,
RTM_NEWNSID = 88,
RTM_DELNSID = 89,
RTM_GETNSID = 90,
RTM_NEWSTATS = 92,
RTM_GETSTATS = 94,
RTM_SETSTATS,
RTM_NEWCACHEREPORT = 96,
RTM_NEWCHAIN = 100,
RTM_DELCHAIN,
RTM_GETCHAIN,
RTM_NEWNEXTHOP = 104,
RTM_DELNEXTHOP,
RTM_GETNEXTHOP,
RTM_NEWLINKPROP = 108,
RTM_DELLINKPROP,
RTM_GETLINKPROP,
RTM_NEWVLAN = 112,
RTM_DELVLAN,
RTM_GETVLAN,
RTM_NEWNEXTHOPBUCKET = 116,
RTM_DELNEXTHOPBUCKET,
RTM_GETNEXTHOPBUCKET,
RTM_NEWTUNNEL = 120,
RTM_DELTUNNEL,
RTM_GETTUNNEL,
}
}
#[cfg_attr(not(feature = "async"), maybe_async::maybe_async)]
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();
iter.recv_ack().await.unwrap();
}
#[cfg_attr(not(feature = "async"), maybe_async::maybe_async)]
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();
iter.recv_ack().await.unwrap();
}