From cfe88c97b5ecb905e82eeb5848804a8798623aad Mon Sep 17 00:00:00 2001 From: Alexander Sikomas Date: Wed, 28 Jan 2026 19:36:40 +0000 Subject: [PATCH] feat: support for FIB rules Introduced linux-raw-sys to pull constants for supporting FIB rules. Added relavant constants and structures from the Linux UAPI. Added an example fwmark. --- Cargo.toml | 1 + examples/fwmark.rs | 126 +++++++++++++++++++++++++++++++++++++++++++++ src/consts/rtnl.rs | 75 +++++++++++++++++++++++++++ src/rtnl.rs | 41 +++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 examples/fwmark.rs diff --git a/Cargo.toml b/Cargo.toml index 51b15bee..4791ba79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ byteorder = "1.3" derive_builder = "0.20" getset = "0.1.2" libc = "0.2.174" +linux-raw-sys = {version = "0.12.1", features = ["netlink"]} log = "0.4" [dependencies.neli-proc-macros] diff --git a/examples/fwmark.rs b/examples/fwmark.rs new file mode 100644 index 00000000..33824dc7 --- /dev/null +++ b/examples/fwmark.rs @@ -0,0 +1,126 @@ +use neli::{ + consts::{ + nl::{NlmF, Nlmsg}, + rtnl::{Frattr, Frf, RtAddrFamily, Rtm}, + socket::NlFamily, + }, + err::Nlmsgerr, + nl::{NlPayload, NlmsghdrBuilder}, + rtnl::{FibmsgBuilder, RtattrBuilder}, + socket::synchronous::NlSocketHandle, + types::RtBuffer, + utils::Groups, +}; + +/// Must either have network permissions (setcap) or run as sudo +/// +/// Will make the following rule: +/// [PRIORITY]: not from all fwmark 0xca6c lookup 246813579 +/// +/// Check via: ip rule show +/// Delete the rule via: sudo ip rule del priority [PRIORITY] +fn main() -> Result<(), Box> { + env_logger::init(); + + let socket = NlSocketHandle::connect(NlFamily::Route, None, Groups::empty())?; + socket.enable_ext_ack(true)?; + socket.enable_strict_checking(true)?; + + let mut attrs = RtBuffer::new(); + attrs.push( + RtattrBuilder::default() + .rta_type(Frattr::Fwmark) + .rta_payload(51820) + .build()?, + ); + + attrs.push( + RtattrBuilder::default() + .rta_type(Frattr::Table) + // avoid collisions with common table names + .rta_payload(246813579) + .build()?, + ); + + attrs.push( + RtattrBuilder::default() + .rta_type(Frattr::Priority) + .rta_payload(32765) + .build()?, + ); + let attrs_clone = attrs.clone(); + + let fibmsg = FibmsgBuilder::default() + .fib_family(RtAddrFamily::Inet) + .fib_dst_len(0) + .fib_src_len(0) + .fib_tos(0) + .fib_flags(Frf::INVERT) + .fib_table(neli::consts::rtnl::RtTable::Unspec) + .fib_action(neli::consts::rtnl::FrAct::FrActToTbl) + .rtattrs(attrs) + .build()?; + + let nlmsg = NlmsghdrBuilder::default() + .nl_type(Rtm::Newrule) + .nl_flags(NlmF::REQUEST | NlmF::ACK | NlmF::CREATE) + .nl_payload(NlPayload::Payload(fibmsg)) + .build()?; + + socket.send(&nlmsg)?; + + if let Ok(messages) = socket.recv::>() { + for msg in messages.0 { + match msg { + Ok(val) => { + if *val.nl_type() == Nlmsg::Error { + if let NlPayload::Ack(err) = val.nl_payload() { + if *err.error() == 0 { + println!("Successfully created routing rule"); + } else { + eprintln!( + "Failed to create routing rule with error code: {}", + err.error() + ); + } + } else { + eprintln!("Received an error message with an unexpected payload."); + } + } + } + Err(e) => { + eprintln!("Error: Have you set network permissions for the file?"); + return Err(Box::new(e)); + } + } + } + } else { + eprintln!("Failed to receive acknowledgment for Newrule."); + } + + // delete the table we just made + println!("\nDeleting the routing rule..."); + + let fibmsg = FibmsgBuilder::default() + .fib_family(RtAddrFamily::Inet) + .fib_dst_len(0) + .fib_src_len(0) + .fib_tos(0) + .fib_flags(Frf::INVERT) + .fib_table(neli::consts::rtnl::RtTable::Unspec) + .fib_action(neli::consts::rtnl::FrAct::FrActToTbl) + .rtattrs(attrs_clone) + .build()?; + + let nlmsg = NlmsghdrBuilder::default() + .nl_type(Rtm::Delrule) + .nl_flags(NlmF::REQUEST | NlmF::ACK) + .nl_payload(NlPayload::Payload(fibmsg)) + .build()?; + + // comment this out to see + // the rule get created + socket.send(&nlmsg)?; + + Ok(()) +} diff --git a/src/consts/rtnl.rs b/src/consts/rtnl.rs index 8a512810..ac20888c 100644 --- a/src/consts/rtnl.rs +++ b/src/consts/rtnl.rs @@ -1,3 +1,4 @@ +use linux_raw_sys::netlink; use neli_proc_macros::neli_enum; use crate as neli; @@ -93,6 +94,7 @@ impl_trait!( IflaInfo, IflaVlan, IflaVlanQos, + Frattr, ); /// Enum usable with [`Rtattr`][crate::rtnl::Rtattr] field, @@ -378,6 +380,64 @@ pub enum Ifa { Flags = libc::IFA_FLAGS, } +/// Enum usable with [`Rtattr`][crate::rtnl::Rtattr] field, `rta_type`. +/// +/// Values are fib rule attributes. Used with +/// [`Fibmsg`][crate::rtnl::Fibmsg]. +#[allow(missing_docs)] +#[neli_enum(serialized_type = "u16")] +pub enum Frattr { + Unspec = netlink::FRA_UNSPEC as u16, + Dst = netlink::FRA_DST as u16, + Src = netlink::FRA_SRC as u16, + Iifname = netlink::FRA_IIFNAME as u16, + Ifname = netlink::FRA_IIFNAME as u16, + Goto = netlink::FRA_GOTO as u16, + Unused2 = netlink::FRA_UNUSED2 as u16, + Priority = netlink::FRA_PRIORITY as u16, + Unused3 = netlink::FRA_UNUSED3 as u16, + Unused4 = netlink::FRA_UNUSED4 as u16, + Unused5 = netlink::FRA_UNUSED5 as u16, + Fwmark = netlink::FRA_FWMARK as u16, + Flow = netlink::FRA_FLOW as u16, + TunId = netlink::FRA_TUN_ID as u16, + SuppressIfgroup = netlink::FRA_SUPPRESS_IFGROUP as u16, + SuppressPrefixlen = netlink::FRA_SUPPRESS_PREFIXLEN as u16, + Table = netlink::FRA_TABLE as u16, + Fwmask = netlink::FRA_FWMASK as u16, + Oifname = netlink::FRA_OIFNAME as u16, + Pad = netlink::FRA_PAD as u16, + L3mdev = netlink::FRA_L3MDEV as u16, + UidRange = netlink::FRA_UID_RANGE as u16, + Protocol = netlink::FRA_PROTOCOL as u16, + IpProto = netlink::FRA_IP_PROTO as u16, + SportRange = netlink::FRA_SPORT_RANGE as u16, + DportRange = netlink::FRA_DPORT_RANGE as u16, + Dscp = netlink::FRA_DSCP as u16, + Flowlabel = netlink::FRA_FLOWLABEL as u16, + FlowlabelMask = netlink::FRA_FLOWLABEL_MASK as u16, + SportMask = netlink::FRA_SPORT_MASK as u16, + DportMask = netlink::FRA_DPORT_MASK as u16, + DscpMask = netlink::FRA_DSCP_MASK as u16, + Max = netlink::__FRA_MAX as u16, +} + +/// Action for a FIB rule. +#[allow(missing_docs)] +#[neli_enum(serialized_type = "u8")] +pub enum FrAct { + Unspec = netlink::FR_ACT_UNSPEC as u8, + FrActToTbl = netlink::FR_ACT_TO_TBL as u8, + FrActGoto = netlink::FR_ACT_GOTO as u8, + FrActNop = netlink::FR_ACT_NOP as u8, + FrActRes3 = netlink::FR_ACT_RES3 as u8, + FrActRes4 = netlink::FR_ACT_RES4 as u8, + FrActBlackhole = netlink::FR_ACT_BLACKHOLE as u8, + FrActUnreachable = netlink::FR_ACT_UNREACHABLE as u8, + FrActProhibit = netlink::FR_ACT_PROHIBIT as u8, + FrActMax = netlink::__FR_ACT_MAX as u8, +} + impl_flags!( /// Values for `ifi_flags` in /// [`Ifinfomsg`][crate::rtnl::Ifinfomsg]. @@ -479,3 +539,18 @@ impl_flags!( BRIDGE_BINDING = 0x10, } ); + +impl_flags!( + /// Values for `fib_flags` in + /// [`Fibmsg`][crate::rtnl::Fibmsg]. + #[allow(missing_docs)] + pub Frf: u32 { + PERMANENT = netlink::FIB_RULE_PERMANENT, + INVERT = netlink::FIB_RULE_INVERT, + UNRESOLVED = netlink::FIB_RULE_UNRESOLVED, + IIF_DETACHED = netlink::FIB_RULE_IIF_DETACHED, + DEV_DETACHED = netlink::FIB_RULE_DEV_DETACHED, + OIF_DETACHED = netlink::FIB_RULE_OIF_DETACHED, + FIND_SADDR = netlink::FIB_RULE_FIND_SADDR, + } +); diff --git a/src/rtnl.rs b/src/rtnl.rs index 754f47a3..ec253fcf 100644 --- a/src/rtnl.rs +++ b/src/rtnl.rs @@ -237,6 +237,47 @@ pub struct Tcmsg { rtattrs: RtBuffer, } +/// Routing rule message +#[derive(Builder, Getters, Debug, Size, ToBytes, FromBytesWithInput, Header)] +#[builder(pattern = "owned")] +pub struct Fibmsg { + /// Address family + #[getset(get = "pub")] + fib_family: RtAddrFamily, + /// Length of destination prefix + #[getset(get = "pub")] + fib_dst_len: libc::c_uchar, + /// Length of source prefix + #[getset(get = "pub")] + fib_src_len: libc::c_uchar, + /// Type of service + #[getset(get = "pub")] + fib_tos: libc::c_uchar, + /// Routing table identifier + #[getset(get = "pub")] + fib_table: RtTable, + /// Padding + #[builder(setter(skip))] + #[builder(default = "0")] + pad1: u8, + /// Padding + #[builder(setter(skip))] + #[builder(default = "0")] + pad2: u8, + /// Rule action + #[getset(get = "pub")] + fib_action: FrAct, + /// Rule flags + #[getset(get = "pub")] + #[builder(default = "Frf::empty()")] + fib_flags: Frf, + /// Payload of [`Frattr`]s + #[neli(input = "input.checked_sub(Self::header_size()).ok_or(DeError::InvalidInput(input))?")] + #[getset(get = "pub")] + #[builder(default = "RtBuffer::new()")] + rtattrs: RtBuffer, +} + /// Struct representing VLAN Flags #[derive(Builder, Getters, Debug, Size, ToBytes, FromBytes)] #[builder(pattern = "owned")]