diff --git a/src/handle.rs b/src/handle.rs index d320cdf..ff0a4eb 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -6,7 +6,8 @@ use netlink_packet_route::RouteNetlinkMessage; use netlink_proto::{sys::SocketAddr, ConnectionHandle}; use crate::{ - AddressHandle, Error, LinkHandle, NeighbourHandle, RouteHandle, RuleHandle, + AddressHandle, Error, LinkHandle, NeighbourHandle, NexthopHandle, + RouteHandle, RuleHandle, }; #[cfg(not(target_os = "freebsd"))] use crate::{ @@ -71,6 +72,12 @@ impl Handle { NeighbourHandle::new(self.clone()) } + /// Create a new handle, specifically for nexthop requests + /// (equivalent to `ip nexthop` commands) + pub fn nexthop(&self) -> NexthopHandle { + NexthopHandle::new(self.clone()) + } + /// Create a new handle, specifically for traffic control qdisc requests /// (equivalent to `tc qdisc show` commands) #[cfg(not(target_os = "freebsd"))] diff --git a/src/lib.rs b/src/lib.rs index 009c9a3..ae53d2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ mod link; mod macros; mod multicast; mod neighbour; +mod nexthop; #[cfg(not(target_os = "freebsd"))] mod ns; mod route; @@ -60,6 +61,10 @@ pub use crate::{ NeighbourAddRequest, NeighbourDelRequest, NeighbourGetRequest, NeighbourHandle, }, + nexthop::{ + NexthopAddRequest, NexthopDelRequest, NexthopGetRequest, NexthopHandle, + NexthopMessageBuilder, + }, route::{ IpVersion, RouteAddRequest, RouteDelRequest, RouteGetRequest, RouteHandle, RouteMessageBuilder, RouteNextHopBuilder, diff --git a/src/link/bond.rs b/src/link/bond.rs index cf1c96d..27d2380 100644 --- a/src/link/bond.rs +++ b/src/link/bond.rs @@ -219,7 +219,9 @@ impl LinkMessageBuilder { /// This is equivalent to `ip link add name NAME type bond ad_select /// AD_SELECT`. pub fn ad_select(self, ad_select: u8) -> Self { - self.append_info_data(InfoBond::AdSelect(ad_select)) + self.append_info_data(InfoBond::AdSelect( + netlink_packet_route::link::BondAdSelect::Other(ad_select), + )) } /// Adds the `ad_actor_sys_prio` attribute to the bond diff --git a/src/link/bridge_port.rs b/src/link/bridge_port.rs index 6022535..79c8803 100644 --- a/src/link/bridge_port.rs +++ b/src/link/bridge_port.rs @@ -148,7 +148,7 @@ impl LinkMessageBuilder { /// `ip link set name NAME type bridge_slave \ /// neigh_vlan_suppress { on | off }`. pub fn neigh_vlan_suppress(self, v: bool) -> Self { - self.append_info_data(InfoBridgePort::NeighVlanSupress(v)) + self.append_info_data(InfoBridgePort::NeighVlanSuppress(v)) } /// This is equivalent to diff --git a/src/nexthop/add.rs b/src/nexthop/add.rs new file mode 100644 index 0000000..cf64baf --- /dev/null +++ b/src/nexthop/add.rs @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT + +use crate::{try_nl, Error, Handle}; +use futures_util::stream::StreamExt; +use netlink_packet_core::{ + NetlinkMessage, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE, + NLM_F_REQUEST, +}; +use netlink_packet_route::{nexthop::NexthopMessage, RouteNetlinkMessage}; + +/// A request to create a new nexthop. This is equivalent to the `ip nexthop add` +/// commands. +#[derive(Debug)] +pub struct NexthopAddRequest { + handle: Handle, + message: NexthopMessage, + replace: bool, +} + +impl NexthopAddRequest { + pub(crate) fn new(handle: Handle, message: NexthopMessage) -> Self { + NexthopAddRequest { + handle, + message, + replace: false, + } + } + + /// Replace existing matching nexthop. + pub fn replace(self) -> Self { + Self { + replace: true, + ..self + } + } + + /// Execute the request. + pub async fn execute(self) -> Result<(), Error> { + let NexthopAddRequest { + mut handle, // Need mut for handle.request + message, + replace, + } = self; + let mut req = + NetlinkMessage::from(RouteNetlinkMessage::NewNexthop(message)); + let replace = if replace { NLM_F_REPLACE } else { NLM_F_EXCL }; + req.header.flags = NLM_F_REQUEST | NLM_F_ACK | replace | NLM_F_CREATE; + + let mut response = handle.request(req)?; + while let Some(message) = response.next().await { + try_nl!(message); + } + Ok(()) + } + + pub fn message_mut(&mut self) -> &mut NexthopMessage { + &mut self.message + } +} diff --git a/src/nexthop/builder.rs b/src/nexthop/builder.rs new file mode 100644 index 0000000..eb2ece7 --- /dev/null +++ b/src/nexthop/builder.rs @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_route::{ + nexthop::{ + NexthopAttribute, NexthopFlags, NexthopGroupEntry, NexthopMessage, + }, + route::{RouteProtocol, RouteScope}, + AddressFamily, +}; +use std::{ + marker::PhantomData, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, +}; + +/// A builder for [`NexthopMessage`] +#[derive(Debug)] +pub struct NexthopMessageBuilder { + message: NexthopMessage, + _phantom: PhantomData, +} + +impl NexthopMessageBuilder { + /// Create a new builder without specifying address family + fn new_no_address_family() -> Self { + let mut message = NexthopMessage::default(); + message.header.protocol = u8::from(RouteProtocol::Static); + message.header.scope = u8::from(RouteScope::Universe); + Self { + message, + _phantom: PhantomData, + } + } + + /// Set the nexthop ID + pub fn id(mut self, id: u32) -> Self { + self.message.nlas.push(NexthopAttribute::Id(id)); + self + } + + /// Set the interface index + pub fn oif(mut self, index: u32) -> Self { + self.message.nlas.push(NexthopAttribute::Oif(index)); + self + } + + /// Set the nexthop as blackhole + pub fn blackhole(mut self) -> Self { + self.message.nlas.push(NexthopAttribute::Blackhole); + self + } + + /// Set the nexthop group + pub fn group(mut self, entries: Vec<(u32, u8)>) -> Self { + let group_entries = entries + .into_iter() + .map(|(id, weight)| NexthopGroupEntry { + id, + weight, + resvd1: 0, + resvd2: 0, + }) + .collect(); + self.message + .nlas + .push(NexthopAttribute::Group(group_entries)); + self + } + + /// Set flags + pub fn flags(mut self, flags: NexthopFlags) -> Self { + self.message.header.flags = flags; + self + } + + /// Set the nexthop protocol + pub fn protocol(mut self, protocol: RouteProtocol) -> Self { + self.message.header.protocol = u8::from(protocol); + self + } + + /// Set the nexthop scope + pub fn scope(mut self, scope: RouteScope) -> Self { + self.message.header.scope = u8::from(scope); + self + } + + /// Build the message + pub fn build(self) -> NexthopMessage { + self.message + } +} + +impl Default for NexthopMessageBuilder { + fn default() -> Self { + Self::new() + } +} + +impl NexthopMessageBuilder { + /// Create a new builder for IPv4 + pub fn new() -> Self { + let mut builder = Self::new_no_address_family(); + builder.message.header.family = AddressFamily::Inet; + builder + } + + /// Set the gateway IP address + pub fn gateway(mut self, addr: Ipv4Addr) -> Self { + self.message + .nlas + .push(NexthopAttribute::Gateway(addr.octets().to_vec())); + self + } +} + +impl Default for NexthopMessageBuilder { + fn default() -> Self { + Self::new() + } +} + +impl NexthopMessageBuilder { + /// Create a new builder for IPv6 + pub fn new() -> Self { + let mut builder = Self::new_no_address_family(); + builder.message.header.family = AddressFamily::Inet6; + builder + } + + /// Set the gateway IP address + pub fn gateway(mut self, addr: Ipv6Addr) -> Self { + self.message + .nlas + .push(NexthopAttribute::Gateway(addr.octets().to_vec())); + self + } +} + +impl Default for NexthopMessageBuilder { + fn default() -> Self { + Self::new() + } +} + +impl NexthopMessageBuilder { + /// Create a new builder for any IP address family + pub fn new() -> Self { + Self::new_no_address_family() + } + + /// Set the gateway IP address + pub fn gateway(mut self, addr: IpAddr) -> Self { + let (family, bytes) = match addr { + IpAddr::V4(addr) => (AddressFamily::Inet, addr.octets().to_vec()), + IpAddr::V6(addr) => (AddressFamily::Inet6, addr.octets().to_vec()), + }; + // Only set family if not already set or if explicitly different (though usually we trust the caller) + if self.message.header.family == AddressFamily::Unspec { + self.message.header.family = family; + } + self.message.nlas.push(NexthopAttribute::Gateway(bytes)); + self + } +} diff --git a/src/nexthop/del.rs b/src/nexthop/del.rs new file mode 100644 index 0000000..c5bd34f --- /dev/null +++ b/src/nexthop/del.rs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT + +use crate::{try_nl, Error, Handle}; +use futures_util::stream::StreamExt; +use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}; +use netlink_packet_route::{ + nexthop::{NexthopAttribute, NexthopMessage}, + RouteNetlinkMessage, +}; + +/// A request to delete a nexthop. This is equivalent to the `ip nexthop del` +/// commands. +#[derive(Debug)] +pub struct NexthopDelRequest { + handle: Handle, + message: NexthopMessage, +} + +impl NexthopDelRequest { + pub(crate) fn new(handle: Handle, id: u32) -> Self { + let mut message = NexthopMessage::default(); + message.nlas.push(NexthopAttribute::Id(id)); + NexthopDelRequest { handle, message } + } + + /// Execute the request. + pub async fn execute(self) -> Result<(), Error> { + let NexthopDelRequest { + mut handle, + message, + } = self; + let mut req = + NetlinkMessage::from(RouteNetlinkMessage::DelNexthop(message)); + req.header.flags = NLM_F_REQUEST | NLM_F_ACK; + + let mut response = handle.request(req)?; + while let Some(message) = response.next().await { + try_nl!(message); + } + Ok(()) + } + + pub fn message_mut(&mut self) -> &mut NexthopMessage { + &mut self.message + } +} diff --git a/src/nexthop/get.rs b/src/nexthop/get.rs new file mode 100644 index 0000000..317f821 --- /dev/null +++ b/src/nexthop/get.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +use futures_util::{ + future::{self, Either}, + stream::{Stream, StreamExt}, + FutureExt, +}; +use netlink_packet_core::{NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST}; +use netlink_packet_route::{nexthop::NexthopMessage, RouteNetlinkMessage}; + +use crate::{try_rtnl, Error, Handle}; + +/// A request to get nexthops. This is equivalent to the `ip nexthop show` commands. +#[derive(Debug)] +pub struct NexthopGetRequest { + handle: Handle, + message: NexthopMessage, +} + +impl NexthopGetRequest { + pub(crate) fn new(handle: Handle) -> Self { + NexthopGetRequest { + handle, + message: NexthopMessage::default(), + } + } + + /// Execute the request. + pub fn execute(self) -> impl Stream> { + let NexthopGetRequest { + mut handle, + message, + } = self; + let mut req = + NetlinkMessage::from(RouteNetlinkMessage::GetNexthop(message)); + req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; + + match handle.request(req) { + Ok(response) => Either::Left(response.map(move |msg| { + Ok(try_rtnl!(msg, RouteNetlinkMessage::NewNexthop)) + })), + Err(e) => Either::Right( + future::err::(e).into_stream(), + ), + } + } + + pub fn message_mut(&mut self) -> &mut NexthopMessage { + &mut self.message + } +} diff --git a/src/nexthop/handle.rs b/src/nexthop/handle.rs new file mode 100644 index 0000000..3dd9561 --- /dev/null +++ b/src/nexthop/handle.rs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +use crate::{Handle, NexthopAddRequest, NexthopDelRequest, NexthopGetRequest}; +use netlink_packet_route::nexthop::NexthopMessage; + +#[derive(Clone, Debug)] +pub struct NexthopHandle(Handle); + +impl NexthopHandle { + pub fn new(handle: Handle) -> Self { + NexthopHandle(handle) + } + + /// List nexthops (equivalent to `ip nexthop show`) + pub fn get(&self) -> NexthopGetRequest { + NexthopGetRequest::new(self.0.clone()) + } + + /// Add a nexthop (equivalent to `ip nexthop add`) + pub fn add(&self, message: NexthopMessage) -> NexthopAddRequest { + NexthopAddRequest::new(self.0.clone(), message) + } + + /// Delete a nexthop (equivalent to `ip nexthop del`) + pub fn del(&self, id: u32) -> NexthopDelRequest { + NexthopDelRequest::new(self.0.clone(), id) + } +} diff --git a/src/nexthop/mod.rs b/src/nexthop/mod.rs new file mode 100644 index 0000000..af9ebb6 --- /dev/null +++ b/src/nexthop/mod.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +mod add; +mod builder; +mod del; +mod get; +mod handle; + +pub use self::{ + add::NexthopAddRequest, builder::NexthopMessageBuilder, + del::NexthopDelRequest, get::NexthopGetRequest, handle::NexthopHandle, +}; diff --git a/src/route/builder.rs b/src/route/builder.rs index 9a97734..027ef39 100644 --- a/src/route/builder.rs +++ b/src/route/builder.rs @@ -115,6 +115,12 @@ impl RouteMessageBuilder { self } + /// Sets the nexthop ID. + pub fn nexthop_id(mut self, id: u32) -> Self { + self.message.attributes.push(RouteAttribute::NextHopId(id)); + self + } + #[cfg(not(target_os = "android"))] /// Sets mark value on route. pub fn mark(mut self, mark: u32) -> Self {