From 09cfa1950dda326d77277b6e7bf8b3e78009a960 Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Tue, 9 Jul 2024 14:35:43 +0800 Subject: [PATCH] Introduce LinkMessageBuilder **API BREAK** To remove the duplicate error of `LinkAddRequest` and `LinkSetRequest`, this patch introduce `LinkMessageBuilder` to generate `LinkMessage` for link handlers to use. The `impl LinkMessageBuilder` holds the common functions available for all interface types. The `impl LinkMessageBuilder` holds VLAN specific functions. The LinkMessageBuilder is designed for advanced user, wrapper functions are created for common use cases. For example, to create a VLAN interface: ```rus let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); handle .link() .add( LinkVlan::new("vlan100", 10, 100) .up() .build() ) .execute() .await .map_err(|e| format!("{e}")) ``` These patches included these new structures to generate matching LinkMessageBuilder: * `LinkUnspec` (for matching existing interface) * `LinkDummy` * `LinkVeth` * `LinkVlan` * `LinkVxlan` * `LinkMacVlan` * `LinkMacVtap` * `LinkWireguard` * `LinkBondPort` Special note: * Due to confliction, the previous VxLAN `link()` function changed to `dev()`. The `link()` is the share function for all interface types. The API is matching with `ip link` command line option for vxlan. Please check `examples/create_vxlan.rs` for usage. * The `LinkHandler::set_bond_port()` changed to `LinkHandler::set_port()`, please check `examples/set_bond_port.rs` for usage. * The `message_mut()` function is removed from `LinkSetRequest` and `LinkAddRequest`, user should modify the LinkMessage before sent to handler. Signed-off-by: Gris Ge --- examples/create_bond.rs | 15 +- examples/create_bridge.rs | 6 +- examples/create_dummy.rs | 16 + examples/create_macvlan.rs | 28 +- examples/create_macvtap.rs | 28 +- examples/create_veth.rs | 7 +- examples/create_vlan.rs | 10 +- examples/create_vrf.rs | 16 + examples/create_vxlan.rs | 16 +- examples/create_wireguard.rs | 16 + examples/create_xfrm.rs | 52 ++ examples/get_bond_port_settings.rs | 10 +- examples/set_bond_port.rs | 116 +++++ examples/set_bond_port_settings.rs | 61 --- examples/set_link_down.rs | 5 +- src/lib.rs | 7 +- src/link/add.rs | 809 +---------------------------- src/link/bond.rs | 277 ++++++++++ src/link/bond_port.rs | 46 ++ src/link/bridge.rs | 40 ++ src/link/builder.rs | 255 +++++++++ src/link/dummy.rs | 40 ++ src/link/handle.rs | 26 +- src/link/mac_vlan.rs | 69 +++ src/link/mac_vtap.rs | 69 +++ src/link/mod.rs | 32 +- src/link/set.rs | 147 +----- src/link/set_bond_port.rs | 75 --- src/link/test.rs | 42 +- src/link/veth.rs | 53 ++ src/link/vlan.rs | 104 ++++ src/link/vrf.rs | 62 +++ src/link/vxlan.rs | 239 +++++++++ src/link/wireguard.rs | 42 ++ src/link/xfrm.rs | 72 +++ src/traffic_control/add_filter.rs | 44 +- src/traffic_control/add_qdisc.rs | 24 +- 37 files changed, 1784 insertions(+), 1192 deletions(-) create mode 100644 examples/create_dummy.rs create mode 100644 examples/create_vrf.rs create mode 100644 examples/create_wireguard.rs create mode 100644 examples/create_xfrm.rs create mode 100644 examples/set_bond_port.rs delete mode 100644 examples/set_bond_port_settings.rs create mode 100644 src/link/bond.rs create mode 100644 src/link/bond_port.rs create mode 100644 src/link/bridge.rs create mode 100644 src/link/builder.rs create mode 100644 src/link/dummy.rs create mode 100644 src/link/mac_vlan.rs create mode 100644 src/link/mac_vtap.rs delete mode 100644 src/link/set_bond_port.rs create mode 100644 src/link/veth.rs create mode 100644 src/link/vlan.rs create mode 100644 src/link/vrf.rs create mode 100644 src/link/vxlan.rs create mode 100644 src/link/wireguard.rs create mode 100644 src/link/xfrm.rs diff --git a/examples/create_bond.rs b/examples/create_bond.rs index 3381b9c..56d85ba 100644 --- a/examples/create_bond.rs +++ b/examples/create_bond.rs @@ -1,17 +1,15 @@ // SPDX-License-Identifier: MIT -use netlink_packet_route::link::BondMode; -use rtnetlink::new_connection; use std::net::{Ipv4Addr, Ipv6Addr}; +use rtnetlink::{new_connection, packet_route::link::BondMode, LinkBond}; + #[tokio::main] async fn main() -> Result<(), String> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); - handle - .link() - .add() - .bond("my-bond".into()) + + let message = LinkBond::new("my-bond") .mode(BondMode::ActiveBackup) .miimon(100) .updelay(100) @@ -26,6 +24,11 @@ async fn main() -> Result<(), String> { Ipv6Addr::new(0xfd02, 0, 0, 0, 0, 0, 0, 2), ]) .up() + .build(); + + handle + .link() + .add(message) .execute() .await .map_err(|e| format!("{e}")) diff --git a/examples/create_bridge.rs b/examples/create_bridge.rs index 5861053..ab8782d 100644 --- a/examples/create_bridge.rs +++ b/examples/create_bridge.rs @@ -1,15 +1,15 @@ // SPDX-License-Identifier: MIT -use rtnetlink::new_connection; +use rtnetlink::{new_connection, LinkBridge}; #[tokio::main] async fn main() -> Result<(), String> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); + handle .link() - .add() - .bridge("my-bridge-1".into()) + .add(LinkBridge::new("my-bridge").build()) .execute() .await .map_err(|e| format!("{e}")) diff --git a/examples/create_dummy.rs b/examples/create_dummy.rs new file mode 100644 index 0000000..51e393e --- /dev/null +++ b/examples/create_dummy.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +use rtnetlink::{new_connection, LinkDummy}; + +#[tokio::main] +async fn main() -> Result<(), String> { + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + + handle + .link() + .add(LinkDummy::new("dummy0").build()) + .execute() + .await + .map_err(|e| format!("{e}")) +} diff --git a/examples/create_macvlan.rs b/examples/create_macvlan.rs index acb2df8..472bd15 100644 --- a/examples/create_macvlan.rs +++ b/examples/create_macvlan.rs @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT -use futures::stream::TryStreamExt; -use macaddr::MacAddr; -use rtnetlink::{new_connection, Error, Handle}; use std::{env, str::FromStr}; -use netlink_packet_route::link::{LinkAttribute, MacVlanMode}; +use futures::stream::TryStreamExt; +use macaddr::MacAddr; +use rtnetlink::{ + new_connection, packet_route::link::MacVlanMode, Error, Handle, LinkMacVlan, +}; #[tokio::main] async fn main() -> Result<(), String> { @@ -37,19 +38,20 @@ async fn create_macvlan( link_name: String, mac_address: Option>, ) -> Result<(), Error> { - let mut links = handle.link().get().match_name(link_name.clone()).execute(); - if let Some(link) = links.try_next().await? { - let mut request = handle.link().add().macvlan( - "test_macvlan".into(), - link.header.index, + let mut parent_links = + handle.link().get().match_name(link_name.clone()).execute(); + if let Some(parent) = parent_links.try_next().await? { + let mut builder = LinkMacVlan::new( + "my-macvlan", + parent.header.index, MacVlanMode::Bridge, ); if let Some(mac) = mac_address { - request - .message_mut() - .attributes - .push(LinkAttribute::Address(mac)); + builder = builder.address(mac); } + let message = builder.build(); + let request = handle.link().add(message); + request.execute().await? } else { println!("no link {link_name} found"); diff --git a/examples/create_macvtap.rs b/examples/create_macvtap.rs index 008fdd2..2a5e6a1 100644 --- a/examples/create_macvtap.rs +++ b/examples/create_macvtap.rs @@ -1,10 +1,12 @@ // SPDX-License-Identifier: MIT -use futures::stream::TryStreamExt; -use netlink_packet_route::link::MacVtapMode; -use rtnetlink::{new_connection, Error, Handle}; use std::env; +use futures::stream::TryStreamExt; +use rtnetlink::{ + new_connection, packet_route::link::MacVtapMode, Error, Handle, LinkMacVtap, +}; + #[tokio::main] async fn main() -> Result<(), String> { let args: Vec = env::args().collect(); @@ -24,18 +26,22 @@ async fn main() -> Result<(), String> { async fn create_macvtap( handle: Handle, - veth_name: String, + link_name: String, ) -> Result<(), Error> { - let mut links = handle.link().get().match_name(veth_name.clone()).execute(); - if let Some(link) = links.try_next().await? { - let request = handle.link().add().macvtap( - "test_macvtap".into(), - link.header.index, + let mut parent_links = + handle.link().get().match_name(link_name.clone()).execute(); + if let Some(parent) = parent_links.try_next().await? { + let message = LinkMacVtap::new( + "test_macvtap", + parent.header.index, MacVtapMode::Bridge, - ); + ) + .build(); + + let request = handle.link().add(message); request.execute().await? } else { - println!("no link link {veth_name} found"); + println!("no link link {link_name} found"); } Ok(()) } diff --git a/examples/create_veth.rs b/examples/create_veth.rs index a438fc3..48eceb3 100644 --- a/examples/create_veth.rs +++ b/examples/create_veth.rs @@ -1,15 +1,14 @@ // SPDX-License-Identifier: MIT -use rtnetlink::new_connection; - +use rtnetlink::{new_connection, LinkVeth}; #[tokio::main] async fn main() -> Result<(), String> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); + handle .link() - .add() - .veth("veth-rs-1".into(), "veth-rs-2".into()) + .add(LinkVeth::new("veth1", "veth1-peer").build()) .execute() .await .map_err(|e| format!("{e}")) diff --git a/examples/create_vlan.rs b/examples/create_vlan.rs index 55dac5a..668ada0 100644 --- a/examples/create_vlan.rs +++ b/examples/create_vlan.rs @@ -2,7 +2,7 @@ use std::{env, error::Error as StdError, str::FromStr}; -use rtnetlink::{new_connection, QosMapping}; +use rtnetlink::{new_connection, LinkVlan, QosMapping}; fn parse_mapping(parameter: &str) -> Result> { let (from, to) = parameter @@ -109,10 +109,14 @@ async fn main() -> Result<(), String> { let (connection, handle, _) = new_connection().unwrap(); tokio::spawn(connection); + let message = LinkVlan::new(&name, base, id) + .up() + .qos(ingress, egress) + .build(); + handle .link() - .add() - .vlan_with_qos(name, base, id, ingress, egress) + .add(message) .execute() .await .map_err(|err| format!("Netlink request failed: {err}")) diff --git a/examples/create_vrf.rs b/examples/create_vrf.rs new file mode 100644 index 0000000..037cd00 --- /dev/null +++ b/examples/create_vrf.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +use rtnetlink::{new_connection, LinkVrf}; + +#[tokio::main] +async fn main() -> Result<(), String> { + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + + handle + .link() + .add(LinkVrf::new("my-vrf", 101).build()) + .execute() + .await + .map_err(|e| format!("{e}")) +} diff --git a/examples/create_vxlan.rs b/examples/create_vxlan.rs index e8e66e8..0564e98 100644 --- a/examples/create_vxlan.rs +++ b/examples/create_vxlan.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; -use rtnetlink::{new_connection, Error, Handle}; +use rtnetlink::{new_connection, Error, Handle, LinkVxlan}; use std::env; #[tokio::main] @@ -24,15 +24,13 @@ async fn main() -> Result<(), String> { async fn create_vxlan(handle: Handle, name: String) -> Result<(), Error> { let mut links = handle.link().get().match_name(name.clone()).execute(); if let Some(link) = links.try_next().await? { - handle - .link() - .add() - .vxlan("vxlan0".into(), 10u32) - .link(link.header.index) - .port(4789) + let message = LinkVxlan::new("vxlan0", 10) + .dev(link.header.index) .up() - .execute() - .await? + .port(4789) + .build(); + + handle.link().add(message).execute().await? } else { println!("no link link {name} found"); } diff --git a/examples/create_wireguard.rs b/examples/create_wireguard.rs new file mode 100644 index 0000000..c60e7e8 --- /dev/null +++ b/examples/create_wireguard.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +use rtnetlink::{new_connection, LinkWireguard}; + +#[tokio::main] +async fn main() -> Result<(), String> { + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + + handle + .link() + .add(LinkWireguard::new("my-wg").build()) + .execute() + .await + .map_err(|e| format!("{e}")) +} diff --git a/examples/create_xfrm.rs b/examples/create_xfrm.rs new file mode 100644 index 0000000..2731f92 --- /dev/null +++ b/examples/create_xfrm.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +use futures::stream::TryStreamExt; +use rtnetlink::{new_connection, Error, Handle, LinkXfrm}; + +#[tokio::main] +async fn main() -> Result<(), String> { + let args: Vec = std::env::args().collect(); + if args.len() != 2 { + usage(); + return Ok(()); + } + let link_name = &args[1]; + + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + + create_xfrm(handle, link_name.to_string()) + .await + .map_err(|e| format!("{e}")) +} + +async fn create_xfrm(handle: Handle, link_name: String) -> Result<(), Error> { + let mut parent_links = + handle.link().get().match_name(link_name.clone()).execute(); + if let Some(parent) = parent_links.try_next().await? { + let request = handle + .link() + .add(LinkXfrm::new("my-xfrm", parent.header.index, 0x08).build()); + + request.execute().await? + } else { + println!("no link {link_name} found"); + } + Ok(()) +} + +fn usage() { + eprintln!( + "usage: + cargo run --example create_xfrm -- + +Note that you need to run this program as root. Instead of running cargo as +root, build the example normally: + + cargo build --example create_xfrm + +Then find the binary in the target directory: + + cd target/debug/examples ; sudo ./create_xfrm " + ); +} diff --git a/examples/get_bond_port_settings.rs b/examples/get_bond_port_settings.rs index 12ee4de..d72534f 100644 --- a/examples/get_bond_port_settings.rs +++ b/examples/get_bond_port_settings.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; -use rtnetlink::{new_connection, Error, Handle}; +use rtnetlink::{ + new_connection, packet_route::link::LinkAttribute, Error, Handle, +}; use std::env; #[tokio::main] @@ -35,7 +37,11 @@ async fn dump_bond_port_settings( let mut link_messgage = handle.link().get().match_name(linkname).execute(); while let Some(msg) = link_messgage.try_next().await? { - println!("{msg:?}"); + for nla in msg.attributes { + if let LinkAttribute::LinkInfo(i) = &nla { + println!("{:?}", i); + } + } } Ok(()) } else { diff --git a/examples/set_bond_port.rs b/examples/set_bond_port.rs new file mode 100644 index 0000000..2f4b51c --- /dev/null +++ b/examples/set_bond_port.rs @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT + +use futures::stream::TryStreamExt; +use rtnetlink::{ + new_connection, + packet_route::link::{BondMode, LinkAttribute}, + Handle, LinkBond, LinkBondPort, LinkDummy, LinkUnspec, +}; + +async fn create_bond_and_get_index(handle: &Handle) -> Result { + handle + .link() + .add( + LinkBond::new("my-bond0") + .mode(BondMode::ActiveBackup) + .up() + .build(), + ) + .execute() + .await + .map_err(|e| format!("{e}"))?; + + let mut bond_links = handle + .link() + .get() + .match_name("my-bond0".to_string()) + .execute(); + if let Some(bond_link) = + bond_links.try_next().await.map_err(|e| format!("{e}"))? + { + Ok(bond_link.header.index) + } else { + Err("failed to find my-bond0".into()) + } +} + +async fn create_dummy_and_attach_to_bond( + handle: &Handle, + bond_index: u32, +) -> Result { + handle + .link() + .add(LinkDummy::new("my-dummy0").build()) + .execute() + .await + .map_err(|e| format!("{e}"))?; + + handle + .link() + .set( + LinkUnspec::new_with_name("my-dummy0") + .controller(bond_index) + .down() + .build(), + ) + .execute() + .await + .map_err(|e| format!("{e}"))?; + + let mut dummy_links = handle + .link() + .get() + .match_name("my-dummy0".to_string()) + .execute(); + if let Some(dummy_link) = + dummy_links.try_next().await.map_err(|e| format!("{e}"))? + { + Ok(dummy_link.header.index) + } else { + Err("failed to find my-bond0".into()) + } +} + +async fn set_bond_port(handle: &Handle, port_index: u32) -> Result<(), String> { + let message = LinkBondPort::new(port_index).queue_id(1).prio(2).build(); + + handle + .link() + .set_port(message) + .execute() + .await + .map_err(|e| format!("{e}"))?; + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), String> { + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + + let bond_index = create_bond_and_get_index(&handle).await?; + + let port_index = + create_dummy_and_attach_to_bond(&handle, bond_index).await?; + set_bond_port(&handle, port_index) + .await + .map_err(|e| e.to_string())?; + + let mut dummy_links = handle + .link() + .get() + .match_name("my-dummy0".to_string()) + .execute(); + if let Some(dummy_link) = + dummy_links.try_next().await.map_err(|e| format!("{e}"))? + { + for nla in dummy_link.attributes { + if let LinkAttribute::LinkInfo(i) = &nla { + println!("{:?}", i); + } + } + Ok(()) + } else { + Err("failed to find my-bond0".into()) + } +} diff --git a/examples/set_bond_port_settings.rs b/examples/set_bond_port_settings.rs deleted file mode 100644 index 4064cd0..0000000 --- a/examples/set_bond_port_settings.rs +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MIT - -use futures::stream::TryStreamExt; -use rtnetlink::{new_connection, Error, Handle}; -use std::env; - -#[tokio::main] -async fn main() -> Result<(), String> { - let args: Vec = env::args().collect(); - if args.len() != 2 { - usage(); - return Ok(()); - } - let link_name = &args[1]; - - let (connection, handle, _) = new_connection().unwrap(); - tokio::spawn(connection); - - set_bond_port_settings(handle, link_name.to_string()) - .await - .map_err(|e| format!("{e}")) -} -// The bond port priority is only supported to set when bonding mode is -// active-backup(1) or balance-tlb (5) or balance-alb (6) -async fn set_bond_port_settings( - handle: Handle, - name: String, -) -> Result<(), Error> { - let mut links = handle.link().get().match_name(name.clone()).execute(); - if let Some(link) = links.try_next().await? { - // This is equivalent to `ip link set name NAME type bond_slave queue_id - // 0 prio 1`. The port priority setting is supported in kernel - // since v6.0 - handle - .link() - .set_bond_port(link.header.index) - .queue_id(0) - .prio(1) - .execute() - .await? - } else { - println!("no link link {name} found"); - } - Ok(()) -} - -fn usage() { - eprintln!( - "usage: - cargo run --example set_bond_port_settings -- - -Note that you need to run this program as root. Instead of running cargo as root, -build the example normally: - - cd netlink-ip ; cargo build --example set_bond_port_settings - -Then find the binary in the target directory: - - cd ../target/debug/example ; sudo ./set_bond_port_settings " - ); -} diff --git a/examples/set_link_down.rs b/examples/set_link_down.rs index d63d00a..f3408c5 100644 --- a/examples/set_link_down.rs +++ b/examples/set_link_down.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; -use rtnetlink::{new_connection, Error, Handle}; +use rtnetlink::{new_connection, Error, Handle, LinkUnspec}; use std::env; #[tokio::main] @@ -26,8 +26,7 @@ async fn set_link_down(handle: Handle, name: String) -> Result<(), Error> { if let Some(link) = links.try_next().await? { handle .link() - .set(link.header.index) - .down() + .set(LinkUnspec::new_with_index(link.header.index).down().build()) .execute() .await? } else { diff --git a/src/lib.rs b/src/lib.rs index bfb8a91..5d830dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,9 +35,10 @@ pub use crate::connection::new_connection_with_socket; pub use crate::errors::Error; pub use crate::handle::Handle; pub use crate::link::{ - BondAddRequest, BondPortSetRequest, LinkAddRequest, LinkDelPropRequest, - LinkDelRequest, LinkGetRequest, LinkHandle, LinkNewPropRequest, - LinkSetRequest, QosMapping, VxlanAddRequest, + LinkAddRequest, LinkBond, LinkBondPort, LinkBridge, LinkDelPropRequest, + LinkDelRequest, LinkDummy, LinkGetRequest, LinkHandle, LinkMacVlan, + LinkMacVtap, LinkMessageBuilder, LinkSetRequest, LinkUnspec, LinkVeth, + LinkVlan, LinkVrf, LinkVxlan, LinkWireguard, LinkXfrm, QosMapping, }; pub use crate::neighbour::{ NeighbourAddRequest, NeighbourDelRequest, NeighbourGetRequest, diff --git a/src/link/add.rs b/src/link/add.rs index 06e7c6a..9e9bb5f 100644 --- a/src/link/add.rs +++ b/src/link/add.rs @@ -1,534 +1,15 @@ // SPDX-License-Identifier: MIT -use std::{ - iter::empty, - net::{Ipv4Addr, Ipv6Addr}, -}; - use futures::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::{ - link::{ - BondMode, InfoBond, InfoData, InfoKind, InfoMacVlan, InfoMacVtap, - InfoVeth, InfoVlan, InfoVrf, InfoVxlan, InfoXfrm, LinkAttribute, - LinkFlags, LinkInfo, LinkMessage, MacVlanMode, MacVtapMode, - VlanQosMapping, - }, - RouteNetlinkMessage, -}; +use netlink_packet_route::{link::LinkMessage, RouteNetlinkMessage}; use crate::{try_nl, Error, Handle}; -pub struct BondAddRequest { - request: LinkAddRequest, - info_data: Vec, -} - -impl BondAddRequest { - /// Execute the request. - pub async fn execute(self) -> Result<(), Error> { - let s = self - .request - .link_info(InfoKind::Bond, Some(InfoData::Bond(self.info_data))); - s.execute().await - } - - /// Sets the interface up - /// This is equivalent to `ip link set up dev NAME`. - pub fn up(mut self) -> Self { - self.request = self.request.up(); - self - } - - /// Adds the `mode` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond mode MODE`. - pub fn mode(mut self, mode: BondMode) -> Self { - self.info_data.push(InfoBond::Mode(mode)); - self - } - - #[deprecated(note = "Please use `active_port` instead")] - pub fn active_slave(mut self, active_port: u32) -> Self { - self.info_data.push(InfoBond::ActivePort(active_port)); - self - } - - /// Adds the `active_port` attribute to the bond, where `active_port` - /// is the ifindex of an interface attached to the bond. - /// This is equivalent to `ip link add name NAME type bond active_slave - /// ACTIVE_PORT_NAME`. - pub fn active_port(mut self, active_port: u32) -> Self { - self.info_data.push(InfoBond::ActivePort(active_port)); - self - } - - /// Adds the `miimon` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond miimon MIIMON`. - pub fn miimon(mut self, miimon: u32) -> Self { - self.info_data.push(InfoBond::MiiMon(miimon)); - self - } - - /// Adds the `updelay` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond updelay UPDELAY`. - pub fn updelay(mut self, updelay: u32) -> Self { - self.info_data.push(InfoBond::UpDelay(updelay)); - self - } - - /// Adds the `downdelay` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond downdelay - /// DOWNDELAY`. - pub fn downdelay(mut self, downdelay: u32) -> Self { - self.info_data.push(InfoBond::DownDelay(downdelay)); - self - } - - /// Adds the `use_carrier` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond use_carrier - /// USE_CARRIER`. - pub fn use_carrier(mut self, use_carrier: u8) -> Self { - self.info_data.push(InfoBond::UseCarrier(use_carrier)); - self - } - - /// Adds the `arp_interval` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond arp_interval - /// ARP_INTERVAL`. - pub fn arp_interval(mut self, arp_interval: u32) -> Self { - self.info_data.push(InfoBond::ArpInterval(arp_interval)); - self - } - - /// Adds the `arp_validate` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond arp_validate - /// ARP_VALIDATE`. - pub fn arp_validate(mut self, arp_validate: u32) -> Self { - self.info_data.push(InfoBond::ArpValidate(arp_validate)); - self - } - - /// Adds the `arp_all_targets` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond arp_all_targets - /// ARP_ALL_TARGETS` - pub fn arp_all_targets(mut self, arp_all_targets: u32) -> Self { - self.info_data - .push(InfoBond::ArpAllTargets(arp_all_targets)); - self - } - - /// Adds the `primary` attribute to the bond, where `primary` is the ifindex - /// of an interface. - /// This is equivalent to `ip link add name NAME type bond primary - /// PRIMARY_NAME` - pub fn primary(mut self, primary: u32) -> Self { - self.info_data.push(InfoBond::Primary(primary)); - self - } - - /// Adds the `primary_reselect` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond primary_reselect - /// PRIMARY_RESELECT`. - pub fn primary_reselect(mut self, primary_reselect: u8) -> Self { - self.info_data - .push(InfoBond::PrimaryReselect(primary_reselect)); - self - } - - /// Adds the `fail_over_mac` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond fail_over_mac - /// FAIL_OVER_MAC`. - pub fn fail_over_mac(mut self, fail_over_mac: u8) -> Self { - self.info_data.push(InfoBond::FailOverMac(fail_over_mac)); - self - } - - /// Adds the `xmit_hash_policy` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond xmit_hash_policy - /// XMIT_HASH_POLICY`. - pub fn xmit_hash_policy(mut self, xmit_hash_policy: u8) -> Self { - self.info_data - .push(InfoBond::XmitHashPolicy(xmit_hash_policy)); - self - } - - /// Adds the `resend_igmp` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond resend_igmp - /// RESEND_IGMP`. - pub fn resend_igmp(mut self, resend_igmp: u32) -> Self { - self.info_data.push(InfoBond::ResendIgmp(resend_igmp)); - self - } - - /// Adds the `num_peer_notif` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond num_peer_notif - /// NUM_PEER_NOTIF`. - pub fn num_peer_notif(mut self, num_peer_notif: u8) -> Self { - self.info_data.push(InfoBond::NumPeerNotif(num_peer_notif)); - self - } - - #[deprecated(note = "Please use `all_ports_active` instead")] - pub fn all_slaves_active(mut self, all_ports_active: u8) -> Self { - self.info_data - .push(InfoBond::AllPortsActive(all_ports_active)); - self - } - - /// Adds the `all_ports_active` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond all_slaves_active - /// ALL_PORTS_ACTIVE`. - pub fn all_ports_active(mut self, all_ports_active: u8) -> Self { - self.info_data - .push(InfoBond::AllPortsActive(all_ports_active)); - self - } - - /// Adds the `min_links` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond min_links - /// MIN_LINKS`. - pub fn min_links(mut self, min_links: u32) -> Self { - self.info_data.push(InfoBond::MinLinks(min_links)); - self - } - - /// Adds the `lp_interval` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond lp_interval - /// LP_INTERVAL`. - pub fn lp_interval(mut self, lp_interval: u32) -> Self { - self.info_data.push(InfoBond::LpInterval(lp_interval)); - self - } - - /// Adds the `packets_per_port` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond packets_per_slave - /// PACKETS_PER_PORT`. - pub fn packets_per_port(mut self, packets_per_port: u32) -> Self { - self.info_data - .push(InfoBond::PacketsPerPort(packets_per_port)); - self - } - - /// Adds the `ad_lacp_rate` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond ad_lacp_rate - /// AD_LACP_RATE`. - pub fn ad_lacp_rate(mut self, ad_lacp_rate: u8) -> Self { - self.info_data.push(InfoBond::AdLacpRate(ad_lacp_rate)); - self - } - - /// Adds the `ad_select` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond ad_select - /// AD_SELECT`. - pub fn ad_select(mut self, ad_select: u8) -> Self { - self.info_data.push(InfoBond::AdSelect(ad_select)); - self - } - - /// Adds the `ad_actor_sys_prio` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond ad_actor_sys_prio - /// AD_ACTOR_SYS_PRIO`. - pub fn ad_actor_sys_prio(mut self, ad_actor_sys_prio: u16) -> Self { - self.info_data - .push(InfoBond::AdActorSysPrio(ad_actor_sys_prio)); - self - } - - /// Adds the `ad_user_port_key` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond ad_user_port_key - /// AD_USER_PORT_KEY`. - pub fn ad_user_port_key(mut self, ad_user_port_key: u16) -> Self { - self.info_data - .push(InfoBond::AdUserPortKey(ad_user_port_key)); - self - } - - /// Adds the `ad_actor_system` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond ad_actor_system - /// AD_ACTOR_SYSTEM`. - pub fn ad_actor_system(mut self, ad_actor_system: [u8; 6]) -> Self { - self.info_data - .push(InfoBond::AdActorSystem(ad_actor_system)); - self - } - - /// Adds the `tlb_dynamic_lb` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond tlb_dynamic_lb - /// TLB_DYNAMIC_LB`. - pub fn tlb_dynamic_lb(mut self, tlb_dynamic_lb: u8) -> Self { - self.info_data.push(InfoBond::TlbDynamicLb(tlb_dynamic_lb)); - self - } - - /// Adds the `peer_notif_delay` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond peer_notif_delay - /// PEER_NOTIF_DELAY`. - pub fn peer_notif_delay(mut self, peer_notif_delay: u32) -> Self { - self.info_data - .push(InfoBond::PeerNotifDelay(peer_notif_delay)); - self - } - - /// Adds the `ad_lacp_active` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond ad_lacp_active - /// AD_LACP_ACTIVE`. - pub fn ad_lacp_active(mut self, ad_lacp_active: u8) -> Self { - self.info_data.push(InfoBond::AdLacpActive(ad_lacp_active)); - self - } - - /// Adds the `missed_max` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond missed_max - /// MISSED_MAX`. - pub fn missed_max(mut self, missed_max: u8) -> Self { - self.info_data.push(InfoBond::MissedMax(missed_max)); - self - } - - /// Adds the `arp_ip_target` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond arp_ip_target - /// LIST`. - pub fn arp_ip_target(mut self, arp_ip_target: Vec) -> Self { - self.info_data.push(InfoBond::ArpIpTarget(arp_ip_target)); - self - } - - /// Adds the `ns_ip6_target` attribute to the bond - /// This is equivalent to `ip link add name NAME type bond ns_ip6_target - /// LIST`. - pub fn ns_ip6_target(mut self, ns_ip6_target: Vec) -> Self { - self.info_data.push(InfoBond::NsIp6Target(ns_ip6_target)); - self - } -} - -/// A request to create a new vxlan link. -/// This is equivalent to `ip link add NAME vxlan id ID ...` commands. -/// It provides methods to customize the creation of the vxlan interface -/// It provides almost all parameters that are listed by `man ip link`. -pub struct VxlanAddRequest { - request: LinkAddRequest, - info_data: Vec, -} - -impl VxlanAddRequest { - /// Execute the request. - pub async fn execute(self) -> Result<(), Error> { - let s = self - .request - .link_info(InfoKind::Vxlan, Some(InfoData::Vxlan(self.info_data))); - s.execute().await - } - - /// Sets the interface up - /// This is equivalent to `ip link set up dev NAME`. - pub fn up(mut self) -> Self { - self.request = self.request.up(); - self - } - - /// Adds the `dev` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI dev - /// LINK`, dev LINK - specifies the physical device to use - /// for tunnel endpoint communication. - /// But instead of specifing a link name (`LINK`), we specify a link index. - pub fn link(mut self, index: u32) -> Self { - self.info_data.push(InfoVxlan::Link(index)); - self - } - - /// Adds the `dstport` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI dstport - /// PORT`. dstport PORT - specifies the UDP destination port to - /// communicate to the remote VXLAN tunnel endpoint. - pub fn port(mut self, port: u16) -> Self { - self.info_data.push(InfoVxlan::Port(port)); - self - } - - /// Adds the `group` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI group - /// IPADDR`, group IPADDR - specifies the multicast IP address to join. - /// This function takes an IPv4 address - /// WARNING: only one between `remote` and `group` can be present. - pub fn group(mut self, addr: std::net::Ipv4Addr) -> Self { - self.info_data - .push(InfoVxlan::Group(addr.octets().to_vec())); - self - } - - /// Adds the `group` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI group - /// IPADDR`, group IPADDR - specifies the multicast IP address to join. - /// This function takes an IPv6 address - /// WARNING: only one between `remote` and `group` can be present. - pub fn group6(mut self, addr: std::net::Ipv6Addr) -> Self { - self.info_data - .push(InfoVxlan::Group6(addr.octets().to_vec())); - self - } - - /// Adds the `remote` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI remote - /// IPADDR`, remote IPADDR - specifies the unicast destination IP - /// address to use in outgoing packets when the - /// destination link layer address is not known in the - /// VXLAN device forwarding database. - /// This function takes an IPv4 address. - /// WARNING: only one between `remote` and `group` can be present. - pub fn remote(self, addr: std::net::Ipv4Addr) -> Self { - self.group(addr) - } - - /// Adds the `remote` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI remote - /// IPADDR`, remote IPADDR - specifies the unicast destination IP - /// address to use in outgoing packets when the - /// destination link layer address is not known in the - /// VXLAN device forwarding database. - /// This function takes an IPv6 address. - /// WARNING: only one between `remote` and `group` can be present. - pub fn remote6(self, addr: std::net::Ipv6Addr) -> Self { - self.group6(addr) - } - - /// Adds the `local` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI local - /// IPADDR`, local IPADDR - specifies the source IP address to use in - /// outgoing packets. This function takes an IPv4 address. - pub fn local(mut self, addr: std::net::Ipv4Addr) -> Self { - self.info_data - .push(InfoVxlan::Local(addr.octets().to_vec())); - self - } - - /// Adds the `local` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI local - /// IPADDR`, local IPADDR - specifies the source IP address to use in - /// outgoing packets. This function takes an IPv6 address. - pub fn local6(mut self, addr: std::net::Ipv6Addr) -> Self { - self.info_data - .push(InfoVxlan::Local6(addr.octets().to_vec())); - self - } - - /// Adds the `tos` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI tos TOS`. - /// tos TOS - specifies the TOS value to use in outgoing packets. - pub fn tos(mut self, tos: u8) -> Self { - self.info_data.push(InfoVxlan::Tos(tos)); - self - } - - /// Adds the `ttl` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI ttl TTL`. - /// ttl TTL - specifies the TTL value to use in outgoing packets. - pub fn ttl(mut self, ttl: u8) -> Self { - self.info_data.push(InfoVxlan::Ttl(ttl)); - self - } - - /// Adds the `flowlabel` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI flowlabel - /// LABEL`. flowlabel LABEL - specifies the flow label to use in - /// outgoing packets. - pub fn label(mut self, label: u32) -> Self { - self.info_data.push(InfoVxlan::Label(label)); - self - } - - /// Adds the `learning` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI - /// \[no\]learning`. \[no\]learning - specifies if unknown source link layer - /// addresses and IP addresses are entered into the VXLAN - /// device forwarding database. - pub fn learning(mut self, learning: bool) -> Self { - self.info_data.push(InfoVxlan::Learning(learning)); - self - } - - /// Adds the `ageing` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI ageing - /// SECONDS`. ageing SECONDS - specifies the lifetime in seconds of - /// FDB entries learnt by the kernel. - pub fn ageing(mut self, seconds: u32) -> Self { - self.info_data.push(InfoVxlan::Ageing(seconds)); - self - } - - /// Adds the `maxaddress` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI - /// maxaddress LIMIT`. maxaddress LIMIT - specifies the maximum number - /// of FDB entries. - pub fn limit(mut self, limit: u32) -> Self { - self.info_data.push(InfoVxlan::Limit(limit)); - self - } - - /// Adds the `srcport` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI srcport - /// MIN MAX`. srcport MIN MAX - specifies the range of port numbers - /// to use as UDP source ports to communicate to the - /// remote VXLAN tunnel endpoint. - pub fn port_range(mut self, min: u16, max: u16) -> Self { - self.info_data.push(InfoVxlan::PortRange((min, max))); - self - } - - /// Adds the `proxy` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI - /// [no]proxy`. \[no\]proxy - specifies ARP proxy is turned on. - pub fn proxy(mut self, proxy: bool) -> Self { - self.info_data.push(InfoVxlan::Proxy(proxy)); - self - } - - /// Adds the `rsc` attribute to the VXLAN This is equivalent to - /// `ip link add name NAME type vxlan id VNI [no]rsc`. - /// \[no\]rsc - specifies if route short circuit is turned on. - pub fn rsc(mut self, rsc: bool) -> Self { - self.info_data.push(InfoVxlan::Rsc(rsc)); - self - } - - // Adds the `l2miss` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI - /// [no]l2miss`. \[no\]l2miss - specifies if netlink LLADDR miss - /// notifications are generated. - pub fn l2miss(mut self, l2miss: bool) -> Self { - self.info_data.push(InfoVxlan::L2Miss(l2miss)); - self - } - - // Adds the `l3miss` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI - /// [no]l3miss`. \[no\]l3miss - specifies if netlink IP ADDR - /// miss notifications are generated. - pub fn l3miss(mut self, l3miss: bool) -> Self { - self.info_data.push(InfoVxlan::L3Miss(l3miss)); - self - } - - pub fn collect_metadata(mut self, collect_metadata: bool) -> Self { - self.info_data - .push(InfoVxlan::CollectMetadata(collect_metadata)); - self - } - - // Adds the `udp_csum` attribute to the VXLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI - /// [no]udp_csum`. \[no\]udpcsum - specifies if UDP checksum is - /// calculated for transmitted packets over IPv4. - pub fn udp_csum(mut self, udp_csum: bool) -> Self { - self.info_data.push(InfoVxlan::UDPCsum(udp_csum)); - self - } -} - /// A request to create a new link. This is equivalent to the `ip link add` /// commands. /// @@ -538,43 +19,43 @@ impl VxlanAddRequest { pub struct LinkAddRequest { handle: Handle, message: LinkMessage, - replace: bool, -} - -/// A quality-of-service mapping between the internal priority `from` to the -/// external vlan priority `to`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct QosMapping { - pub from: u32, - pub to: u32, -} - -impl From for VlanQosMapping { - fn from(QosMapping { from, to }: QosMapping) -> Self { - Self::Mapping(from, to) - } + flags: u16, } impl LinkAddRequest { - pub(crate) fn new(handle: Handle) -> Self { + pub(crate) fn new(handle: Handle, message: LinkMessage) -> Self { LinkAddRequest { handle, - message: LinkMessage::default(), - replace: false, + message, + flags: NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL, } } + /// Replace existing matching link. + pub fn replace(self) -> Self { + let mut ret = self; + ret.flags -= NLM_F_EXCL; + ret.flags |= NLM_F_REPLACE; + ret + } + + /// Setting arbitrary [NetlinkHeader] flags + pub fn set_flags(self, flags: u16) -> Self { + let mut ret = self; + ret.flags = flags; + ret + } + /// Execute the request. pub async fn execute(self) -> Result<(), Error> { let LinkAddRequest { mut handle, message, - replace, + flags, } = self; let mut req = NetlinkMessage::from(RouteNetlinkMessage::NewLink(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; + req.header.flags = flags; let mut response = handle.request(req)?; while let Some(message) = response.next().await { @@ -582,250 +63,4 @@ impl LinkAddRequest { } Ok(()) } - - /// Return a mutable reference to the request message. - /// - /// # Example - /// - /// Let's say we want to create a vlan interface on a link with id 6. By - /// default, the [`vlan()`](#method.vlan) method would create a request - /// with the `LinkFlags::Up` link set, so that the interface is up after - /// creation. If we want to create a interface that is down by default we - /// could do: - /// - /// ```rust,no_run - /// use futures::Future; - /// use netlink_packet_route::link::LinkFlags; - /// use rtnetlink::{Handle, new_connection}; - /// - /// async fn run(handle: Handle) -> Result<(), String> { - /// let vlan_id = 100; - /// let link_id = 6; - /// let mut request = handle.link().add().vlan("my-vlan-itf".into(), - /// link_id, vlan_id); - /// request.message_mut().header.flags.remove(LinkFlags::Up); - /// request.message_mut().header.change_mask.remove(LinkFlags::Up); - /// // send the request - /// request.execute().await.map_err(|e| format!("{}", e)) - /// } - pub fn message_mut(&mut self) -> &mut LinkMessage { - &mut self.message - } - - /// Create a dummy link. - /// This is equivalent to `ip link add NAME type dummy`. - pub fn dummy(self, name: String) -> Self { - self.name(name).link_info(InfoKind::Dummy, None).up() - } - - /// Create a veth pair. - /// This is equivalent to `ip link add NAME1 type veth peer name NAME2`. - pub fn veth(self, name: String, peer_name: String) -> Self { - // NOTE: `name` is the name of the peer in the netlink message (ie the - // link created via the InfoVeth::Peer attribute, and - // `peer_name` is the name in the main netlink message. - // This is a bit weird, but it's all hidden from the user. - - let mut peer = LinkMessage::default(); - // FIXME: we get a -107 (ENOTCONN) (???) when trying to set `name` up. - // peer.header.flags |= LinkFlags::Up; - // peer.header.change_mask |= LinkFlag::Up; - peer.attributes.push(LinkAttribute::IfName(name)); - let link_info_data = InfoData::Veth(InfoVeth::Peer(peer)); - self.name(peer_name) - .up() // iproute2 does not set this one up - .link_info(InfoKind::Veth, Some(link_info_data)) - } - - /// Create VLAN on a link. - /// This is equivalent to `ip link add link LINK name NAME type vlan id - /// VLAN_ID`, but instead of specifying a link name (`LINK`), we specify - /// a link index. - pub fn vlan(self, name: String, index: u32, vlan_id: u16) -> Self { - self.vlan_with_qos(name, index, vlan_id, empty(), empty()) - } - - /// Create VLAN on a link with ingress and egress qos mappings. - /// This is equivalent to `ip link add link LINK name NAME type vlan id - /// VLAN_ID ingress-qos-mapping INGRESS_QOS egress-qos-mapping EGRESS_QOS`, - /// but instead of specifying a link name (`LINK`), we specify a link index. - pub fn vlan_with_qos< - I: IntoIterator, - E: IntoIterator, - >( - self, - name: String, - index: u32, - vlan_id: u16, - ingress_qos: I, - egress_qos: E, - ) -> Self { - let mut info = vec![InfoVlan::Id(vlan_id)]; - - let ingress: Vec<_> = - ingress_qos.into_iter().map(VlanQosMapping::from).collect(); - if !ingress.is_empty() { - info.push(InfoVlan::IngressQos(ingress)); - } - - let egress: Vec<_> = - egress_qos.into_iter().map(VlanQosMapping::from).collect(); - if !egress.is_empty() { - info.push(InfoVlan::EgressQos(egress)); - } - - self.name(name) - .link_info(InfoKind::Vlan, Some(InfoData::Vlan(info))) - .append_nla(LinkAttribute::Link(index)) - .up() - } - - /// Create macvlan on a link. - /// This is equivalent to `ip link add name NAME link LINK type macvlan mode - /// MACVLAN_MODE`, but instead of specifying a link name (`LINK`), we - /// specify a link index. The MACVLAN_MODE is an integer consisting of - /// flags from MACVLAN_MODE (netlink-packet-route/src/rtnl/constants.rs) - /// being: _PRIVATE, _VEPA, _BRIDGE, _PASSTHRU, _SOURCE, which can be - /// *combined*. - pub fn macvlan(self, name: String, index: u32, mode: MacVlanMode) -> Self { - self.name(name) - .link_info( - InfoKind::MacVlan, - Some(InfoData::MacVlan(vec![InfoMacVlan::Mode(mode)])), - ) - .append_nla(LinkAttribute::Link(index)) - .up() - } - - /// Create macvtap on a link. - /// This is equivalent to `ip link add name NAME link LINK type macvtap mode - /// MACVTAP_MODE`, but instead of specifying a link name (`LINK`), we - /// specify a link index. The MACVTAP_MODE is an integer consisting of - /// flags from MACVTAP_MODE (netlink-packet-route/src/rtnl/constants.rs) - /// being: _PRIVATE, _VEPA, _BRIDGE, _PASSTHRU, _SOURCE, which can be - /// *combined*. - pub fn macvtap(self, name: String, index: u32, mode: MacVtapMode) -> Self { - self.name(name) - .link_info( - InfoKind::MacVtap, - Some(InfoData::MacVtap(vec![InfoMacVtap::Mode(mode)])), - ) - .append_nla(LinkAttribute::Link(index)) - .up() - } - - /// Create a VxLAN - /// This is equivalent to `ip link add name NAME type vxlan id VNI`, - /// it returns a VxlanAddRequest to further customize the vxlan - /// interface creation. - pub fn vxlan(self, name: String, vni: u32) -> VxlanAddRequest { - let s = self.name(name); - VxlanAddRequest { - request: s, - info_data: vec![InfoVxlan::Id(vni)], - } - } - - /// Create xfrm tunnel - /// This is equivalent to `ip link add name NAME type xfrm if_id NUMBER`, - /// The NUMBER is a XFRM if_id which may be connected to IPsec policy - #[deprecated(note = "Please use `xfrmtun_link` instead")] - pub fn xfrmtun(self, name: String, ifid: u32) -> Self { - self.name(name) - .link_info( - InfoKind::Xfrm, - Some(InfoData::Xfrm(vec![InfoXfrm::IfId(ifid)])), - ) - .up() - } - - /// Create xfrm tunnel - /// This is equivalent to `ip link add name NAME type xfrm if_id NUMBER dev - /// LINK`. The NUMBER is a XFRM if_id which may be connected to IPsec - /// policy. The LINK is the underlying link ID to attach the tunnel to, - /// but instead of specifying a link name (`LINK`), we specify a link - /// index. A LINK of `0` can be used to specify `NONE`. - pub fn xfrmtun_link(self, name: String, ifid: u32, link: u32) -> Self { - self.name(name) - .link_info( - InfoKind::Xfrm, - Some(InfoData::Xfrm(vec![ - InfoXfrm::IfId(ifid), - InfoXfrm::Link(link), - ])), - ) - .up() - } - - /// Create a new bond. - /// This is equivalent to `ip link add link NAME type bond`. - pub fn bond(self, name: String) -> BondAddRequest { - let s = self.name(name); - BondAddRequest { - request: s, - info_data: vec![], - } - } - - /// Create a new bridge. - /// This is equivalent to `ip link add link NAME type bridge`. - pub fn bridge(self, name: String) -> Self { - self.name(name.clone()) - .link_info(InfoKind::Bridge, None) - .append_nla(LinkAttribute::IfName(name)) - } - - /// Create a wireguard link. - /// This is equivalent to `ip link add NAME type wireguard`. - pub fn wireguard(self, name: String) -> Self { - let mut request = self.name(name).link_info(InfoKind::Wireguard, None); - request.message_mut().header.flags.remove(LinkFlags::Up); - request - } - - pub fn vrf(self, name: String, table_id: u32) -> Self { - self.name(name.clone()) - .link_info( - InfoKind::Vrf, - Some(InfoData::Vrf(vec![InfoVrf::TableId(table_id)])), - ) - .append_nla(LinkAttribute::IfName(name)) - } - - /// Replace existing matching link. - pub fn replace(self) -> Self { - Self { - replace: true, - ..self - } - } - - fn up(mut self) -> Self { - self.message.header.flags |= LinkFlags::Up; - self.message.header.change_mask |= LinkFlags::Up; - self - } - - fn link_info(self, kind: InfoKind, data: Option) -> Self { - let mut link_info_nlas = vec![LinkInfo::Kind(kind)]; - if let Some(data) = data { - link_info_nlas.push(LinkInfo::Data(data)); - } - self.append_nla(LinkAttribute::LinkInfo(link_info_nlas)) - } - - pub fn name(self, name: String) -> Self { - self.append_nla(LinkAttribute::IfName(name)) - } - - /// Define the hardware address of the link when creating it (equivalent to - /// `ip link add NAME address ADDRESS`) - pub fn address(self, address: Vec) -> Self { - self.append_nla(LinkAttribute::Address(address)) - } - - fn append_nla(mut self, nla: LinkAttribute) -> Self { - self.message.attributes.push(nla); - self - } } diff --git a/src/link/bond.rs b/src/link/bond.rs new file mode 100644 index 0000000..1d6a30b --- /dev/null +++ b/src/link/bond.rs @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: MIT + +use std::net::{Ipv4Addr, Ipv6Addr}; + +use crate::{ + link::LinkMessageBuilder, + packet_route::link::{BondMode, InfoBond, InfoData, InfoKind}, +}; + +/// +/// Represent bond interface. +/// Example code on creating a bond interface +/// ```no_run +/// use rtnetlink::{new_connection, LinkBond, packet_route::link::BondMode}; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().map_err(|e| +/// format!("{e}"))?; +/// tokio::spawn(connection); +/// +/// let message = LinkBond::new("bond0") +/// .mode(BondMode::ActiveBackup) +/// .miimon(100) +/// .updelay(100) +/// .downdelay(100) +/// .min_links(2) +/// .up() +/// .build(); +/// +/// handle +/// .link() +/// .add(message) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkBond; + +impl LinkBond { + /// Equal to `LinkMessageBuilder::::new()` + pub fn new(name: &str) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for bond + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::Bond) + .name(name.to_string()) + } + + pub fn append_info_data(self, info: InfoBond) -> Self { + let mut ret = self; + if let InfoData::Bond(infos) = ret + .info_data + .get_or_insert_with(|| InfoData::Bond(Vec::new())) + { + infos.push(info); + } + ret + } + + /// Adds the `mode` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond mode MODE`. + pub fn mode(self, mode: BondMode) -> Self { + self.append_info_data(InfoBond::Mode(mode)) + } + + /// Adds the `active_port` attribute to the bond, where `active_port` + /// is the ifindex of an interface attached to the bond. + /// This is equivalent to `ip link add name NAME type bond active_slave + /// ACTIVE_PORT_NAME`. + pub fn active_port(self, active_port: u32) -> Self { + self.append_info_data(InfoBond::ActivePort(active_port)) + } + + /// Adds the `miimon` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond miimon MIIMON`. + pub fn miimon(self, miimon: u32) -> Self { + self.append_info_data(InfoBond::MiiMon(miimon)) + } + + /// Adds the `updelay` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond updelay UPDELAY`. + pub fn updelay(self, updelay: u32) -> Self { + self.append_info_data(InfoBond::UpDelay(updelay)) + } + + /// Adds the `downdelay` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond downdelay + /// DOWNDELAY`. + pub fn downdelay(self, downdelay: u32) -> Self { + self.append_info_data(InfoBond::DownDelay(downdelay)) + } + + /// Adds the `use_carrier` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond use_carrier + /// USE_CARRIER`. + pub fn use_carrier(self, use_carrier: u8) -> Self { + self.append_info_data(InfoBond::UseCarrier(use_carrier)) + } + + /// Adds the `arp_interval` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond arp_interval + /// ARP_INTERVAL`. + pub fn arp_interval(self, arp_interval: u32) -> Self { + self.append_info_data(InfoBond::ArpInterval(arp_interval)) + } + + /// Adds the `arp_validate` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond arp_validate + /// ARP_VALIDATE`. + pub fn arp_validate(self, arp_validate: u32) -> Self { + self.append_info_data(InfoBond::ArpValidate(arp_validate)) + } + + /// Adds the `arp_all_targets` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond arp_all_targets + /// ARP_ALL_TARGETS` + pub fn arp_all_targets(self, arp_all_targets: u32) -> Self { + self.append_info_data(InfoBond::ArpAllTargets(arp_all_targets)) + } + + /// Adds the `primary` attribute to the bond, where `primary` is the ifindex + /// of an interface. + /// This is equivalent to `ip link add name NAME type bond primary + /// PRIMARY_NAME` + pub fn primary(self, primary: u32) -> Self { + self.append_info_data(InfoBond::Primary(primary)) + } + + /// Adds the `primary_reselect` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond primary_reselect + /// PRIMARY_RESELECT`. + pub fn primary_reselect(self, primary_reselect: u8) -> Self { + self.append_info_data(InfoBond::PrimaryReselect(primary_reselect)) + } + + /// Adds the `fail_over_mac` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond fail_over_mac + /// FAIL_OVER_MAC`. + pub fn fail_over_mac(self, fail_over_mac: u8) -> Self { + self.append_info_data(InfoBond::FailOverMac(fail_over_mac)) + } + + /// Adds the `xmit_hash_policy` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond xmit_hash_policy + /// XMIT_HASH_POLICY`. + pub fn xmit_hash_policy(self, xmit_hash_policy: u8) -> Self { + self.append_info_data(InfoBond::XmitHashPolicy(xmit_hash_policy)) + } + + /// Adds the `resend_igmp` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond resend_igmp + /// RESEND_IGMP`. + pub fn resend_igmp(self, resend_igmp: u32) -> Self { + self.append_info_data(InfoBond::ResendIgmp(resend_igmp)) + } + + /// Adds the `num_peer_notif` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond num_peer_notif + /// NUM_PEER_NOTIF`. + pub fn num_peer_notif(self, num_peer_notif: u8) -> Self { + self.append_info_data(InfoBond::NumPeerNotif(num_peer_notif)) + } + + /// Adds the `all_ports_active` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond all_slaves_active + /// ALL_PORTS_ACTIVE`. + pub fn all_ports_active(self, all_ports_active: u8) -> Self { + self.append_info_data(InfoBond::AllPortsActive(all_ports_active)) + } + + /// Adds the `min_links` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond min_links + /// MIN_LINKS`. + pub fn min_links(self, min_links: u32) -> Self { + self.append_info_data(InfoBond::MinLinks(min_links)) + } + + /// Adds the `lp_interval` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond lp_interval + /// LP_INTERVAL`. + pub fn lp_interval(self, lp_interval: u32) -> Self { + self.append_info_data(InfoBond::LpInterval(lp_interval)) + } + + /// Adds the `packets_per_port` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond packets_per_slave + /// PACKETS_PER_PORT`. + pub fn packets_per_port(self, packets_per_port: u32) -> Self { + self.append_info_data(InfoBond::PacketsPerPort(packets_per_port)) + } + + /// Adds the `ad_lacp_rate` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond ad_lacp_rate + /// AD_LACP_RATE`. + pub fn ad_lacp_rate(self, ad_lacp_rate: u8) -> Self { + self.append_info_data(InfoBond::AdLacpRate(ad_lacp_rate)) + } + + /// Adds the `ad_select` attribute to the bond + /// 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)) + } + + /// Adds the `ad_actor_sys_prio` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond ad_actor_sys_prio + /// AD_ACTOR_SYS_PRIO`. + pub fn ad_actor_sys_prio(self, ad_actor_sys_prio: u16) -> Self { + self.append_info_data(InfoBond::AdActorSysPrio(ad_actor_sys_prio)) + } + + /// Adds the `ad_user_port_key` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond ad_user_port_key + /// AD_USER_PORT_KEY`. + pub fn ad_user_port_key(self, ad_user_port_key: u16) -> Self { + self.append_info_data(InfoBond::AdUserPortKey(ad_user_port_key)) + } + + /// Adds the `ad_actor_system` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond ad_actor_system + /// AD_ACTOR_SYSTEM`. + pub fn ad_actor_system(self, ad_actor_system: [u8; 6]) -> Self { + self.append_info_data(InfoBond::AdActorSystem(ad_actor_system)) + } + + /// Adds the `tlb_dynamic_lb` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond tlb_dynamic_lb + /// TLB_DYNAMIC_LB`. + pub fn tlb_dynamic_lb(self, tlb_dynamic_lb: u8) -> Self { + self.append_info_data(InfoBond::TlbDynamicLb(tlb_dynamic_lb)) + } + + /// Adds the `peer_notif_delay` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond peer_notif_delay + /// PEER_NOTIF_DELAY`. + pub fn peer_notif_delay(self, peer_notif_delay: u32) -> Self { + self.append_info_data(InfoBond::PeerNotifDelay(peer_notif_delay)) + } + + /// Adds the `ad_lacp_active` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond ad_lacp_active + /// AD_LACP_ACTIVE`. + pub fn ad_lacp_active(self, ad_lacp_active: u8) -> Self { + self.append_info_data(InfoBond::AdLacpActive(ad_lacp_active)) + } + + /// Adds the `missed_max` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond missed_max + /// MISSED_MAX`. + pub fn missed_max(self, missed_max: u8) -> Self { + self.append_info_data(InfoBond::MissedMax(missed_max)) + } + + /// Adds the `arp_ip_target` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond arp_ip_target + /// LIST`. + pub fn arp_ip_target(self, arp_ip_target: Vec) -> Self { + self.append_info_data(InfoBond::ArpIpTarget(arp_ip_target)) + } + + /// Adds the `ns_ip6_target` attribute to the bond + /// This is equivalent to `ip link add name NAME type bond ns_ip6_target + /// LIST`. + pub fn ns_ip6_target(self, ns_ip6_target: Vec) -> Self { + self.append_info_data(InfoBond::NsIp6Target(ns_ip6_target)) + } +} diff --git a/src/link/bond_port.rs b/src/link/bond_port.rs new file mode 100644 index 0000000..a6bec2d --- /dev/null +++ b/src/link/bond_port.rs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + packet_route::link::{InfoBondPort, InfoPortData, InfoPortKind}, + LinkMessageBuilder, +}; + +#[derive(Debug)] +pub struct LinkBondPort; + +impl LinkBondPort { + pub fn new(port_index: u32) -> LinkMessageBuilder { + LinkMessageBuilder::::default() + .index(port_index) + .set_port_kind(InfoPortKind::Bond) + } +} + +impl LinkMessageBuilder { + /// Append arbitrary [InfoBondPort] + pub fn append_info_data(self, info: InfoBondPort) -> Self { + let mut ret = self; + + if let InfoPortData::BondPort(infos) = ret + .port_data + .get_or_insert_with(|| InfoPortData::BondPort(Vec::new())) + { + infos.push(info); + } + + ret + } + + /// Adds the `queue_id` attribute to the bond port + /// This is equivalent to + /// `ip link set name NAME type bond_slave queue_id QUEUE_ID`. + pub fn queue_id(self, queue_id: u16) -> Self { + self.append_info_data(InfoBondPort::QueueId(queue_id)) + } + + /// Adds the `prio` attribute to the bond port + /// This is equivalent to `ip link set name NAME type bond_slave prio PRIO`. + pub fn prio(self, prio: i32) -> Self { + self.append_info_data(InfoBondPort::Prio(prio)) + } +} diff --git a/src/link/bridge.rs b/src/link/bridge.rs new file mode 100644 index 0000000..5ba8793 --- /dev/null +++ b/src/link/bridge.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +use crate::{link::LinkMessageBuilder, packet_route::link::InfoKind}; + +/// Represent dummy interface. +/// Example code on creating a linux bridge interface +/// ```no_run +/// use rtnetlink::{new_connection, LinkBridge}; +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add(LinkBridge::new("br0").build()) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkBridge; + +impl LinkBridge { + /// Equal to `LinkMessageBuilder::::new().up()` + pub fn new(name: &str) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name).up() + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for linux bridge + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::Bridge) + .name(name.to_string()) + } +} diff --git a/src/link/builder.rs b/src/link/builder.rs new file mode 100644 index 0000000..6922965 --- /dev/null +++ b/src/link/builder.rs @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: MIT + +use std::marker::PhantomData; +use std::os::fd::RawFd; + +use crate::packet_route::link::{ + InfoData, InfoKind, InfoPortData, InfoPortKind, LinkAttribute, LinkFlags, + LinkHeader, LinkInfo, LinkMessage, +}; + +/// Generic interface without interface type +/// Could be used to match interface by interface name or index. +/// Example on attaching a interface to controller +/// ```no_run +/// use rtnetlink::{new_connection, LinkUnspec}; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// let controller_index = 63u32; +/// +/// handle +/// .link() +/// .set( +/// LinkUnspec::new_with_name("my-nic") +/// .controller(controller_index) +/// .build(), +/// ) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +#[derive(Debug)] +pub struct LinkUnspec; + +impl LinkUnspec { + /// Equal to `LinkMessageBuilder::::default().index()` + pub fn new_with_index(index: u32) -> LinkMessageBuilder { + LinkMessageBuilder::::default().index(index) + } + + /// Equal to `LinkMessageBuilder::::default().name()` + pub fn new_with_name(name: &str) -> LinkMessageBuilder { + LinkMessageBuilder::::default().name(name.to_string()) + } +} + +#[derive(Debug)] +/// Helper struct for building [LinkMessage]. +/// The [LinkMessageBuilder] is designed for advanced user, wrapper +/// structs/functions are created +pub struct LinkMessageBuilder { + pub(crate) header: LinkHeader, + pub(crate) info_kind: Option, + pub(crate) info_data: Option, + pub(crate) port_kind: Option, + pub(crate) port_data: Option, + extra_attriutes: Vec, + _phantom: PhantomData, +} + +impl Default for LinkMessageBuilder { + fn default() -> Self { + Self { + header: Default::default(), + info_kind: None, + info_data: Default::default(), + extra_attriutes: Default::default(), + port_kind: None, + port_data: None, + _phantom: Default::default(), + } + } +} + +impl LinkMessageBuilder { + pub fn new_with_info_kind(info_kind: InfoKind) -> Self { + Self { + info_kind: Some(info_kind), + ..Default::default() + } + } + + /// Set arbitrary [LinkHeader] + pub fn set_header(self, header: LinkHeader) -> Self { + let mut ret = self; + ret.header = header; + ret + } + + /// Append arbitrary [LinkAttribute] + pub fn append_extra_attribute(self, link_attr: LinkAttribute) -> Self { + let mut ret = self; + ret.extra_attriutes.push(link_attr); + ret + } + + /// Set arbitrary [InfoData] + pub fn set_info_data(self, info_data: InfoData) -> Self { + let mut ret = self; + ret.info_data = Some(info_data); + ret + } + + /// Set the link up (equivalent to `ip link set dev DEV up`) + pub fn up(self) -> Self { + let mut ret = self; + ret.header.flags |= LinkFlags::Up; + ret.header.change_mask |= LinkFlags::Up; + ret + } + + /// Set the link down (equivalent to `ip link set dev DEV down`) + pub fn down(self) -> Self { + let mut ret = self; + ret.header.flags.remove(LinkFlags::Up); + ret.header.change_mask |= LinkFlags::Up; + ret + } + + /// Enable or disable promiscious mode of the link with the given index + /// (equivalent to `ip link set dev DEV promisc on/off`) + pub fn promiscuous(self, enable: bool) -> Self { + let mut ret = self; + if enable { + ret.header.flags |= LinkFlags::Promisc; + } else { + ret.header.flags.remove(LinkFlags::Promisc); + } + ret.header.change_mask |= LinkFlags::Promisc; + ret + } + + /// Enable or disable the ARP protocol of the link with the given index + /// (equivalent to `ip link set dev DEV arp on/off`) + pub fn arp(self, enable: bool) -> Self { + let mut ret = self; + if enable { + ret.header.flags.remove(LinkFlags::Noarp); + } else { + ret.header.flags |= LinkFlags::Noarp; + } + ret.header.change_mask |= LinkFlags::Noarp; + ret + } + + pub fn name(self, name: String) -> Self { + self.append_extra_attribute(LinkAttribute::IfName(name)) + } + + /// Set the mtu of the link with the given index (equivalent to + /// `ip link set DEV mtu MTU`) + pub fn mtu(self, mtu: u32) -> Self { + self.append_extra_attribute(LinkAttribute::Mtu(mtu)) + } + + /// Kernel index number of interface, used for querying, modifying or + /// deleting existing interface. + pub fn index(self, index: u32) -> Self { + let mut ret = self; + ret.header.index = index; + ret + } + + /// Define the hardware address of the link when creating it (equivalent to + /// `ip link add NAME address ADDRESS`) + pub fn address(self, address: Vec) -> Self { + self.append_extra_attribute(LinkAttribute::Address(address)) + } + + /// Move this network device into the network namespace of the process with + /// the given `pid`. + pub fn setns_by_pid(self, pid: u32) -> Self { + self.append_extra_attribute(LinkAttribute::NetNsPid(pid)) + } + + /// Move this network device into the network namespace corresponding to the + /// given file descriptor. + pub fn setns_by_fd(self, fd: RawFd) -> Self { + self.append_extra_attribute(LinkAttribute::NetNsFd(fd)) + } + + /// The physical device to act operate on. (e.g. the parent interface of + /// VLAN/VxLAN) + pub fn link(self, index: u32) -> Self { + self.append_extra_attribute(LinkAttribute::Link(index)) + } + + /// Define controller interface index (similar to + /// ip link set NAME master CONTROLLER_NAME) + pub fn controller(self, ctrl_index: u32) -> Self { + self.append_extra_attribute(LinkAttribute::Controller(ctrl_index)) + } + + /// Detach the link from its _controller_. This is equivalent to `ip link + /// set LINK nomaster`. To succeed, the link that is being detached must be + /// UP. + pub fn nocontroller(self) -> Self { + self.append_extra_attribute(LinkAttribute::Controller(0)) + } + + pub fn set_port_kind(self, port_kind: InfoPortKind) -> Self { + let mut ret = self; + ret.port_kind = Some(port_kind); + ret + } + + /// Include port settings. + /// The [LinkBondPort] and [LinkBridgePort] are the helper + pub fn set_port_data(self, port_data: InfoPortData) -> Self { + let mut ret = self; + ret.port_data = Some(port_data); + ret + } + + pub fn build(self) -> LinkMessage { + let mut message = LinkMessage::default(); + message.header = self.header; + + if !self.extra_attriutes.is_empty() { + message.attributes = self.extra_attriutes; + } + + let mut link_infos: Vec = Vec::new(); + if let Some(info_kind) = self.info_kind { + link_infos.push(LinkInfo::Kind(info_kind)); + } + if let Some(info_data) = self.info_data { + link_infos.push(LinkInfo::Data(info_data)); + } + + if let Some(port_kind) = self.port_kind { + link_infos.push(LinkInfo::PortKind(port_kind)); + } + + if let Some(port_data) = self.port_data { + link_infos.push(LinkInfo::PortData(port_data)); + } + + if !link_infos.is_empty() { + message.attributes.push(LinkAttribute::LinkInfo(link_infos)); + } + + message + } +} + +impl LinkMessageBuilder { + pub fn new() -> Self { + Self::default() + } +} diff --git a/src/link/dummy.rs b/src/link/dummy.rs new file mode 100644 index 0000000..4d3016f --- /dev/null +++ b/src/link/dummy.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +use crate::{link::LinkMessageBuilder, packet_route::link::InfoKind}; + +/// Represent dummy interface. +/// Example code on creating a dummy interface +/// ```no_run +/// use rtnetlink::{new_connection, LinkDummy}; +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add(LinkDummy::new("dummy0").build()) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkDummy; + +impl LinkDummy { + /// Equal to `LinkMessageBuilder::::new()` + pub fn new(name: &str) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for dummy interface type + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::Dummy) + .name(name.to_string()) + } +} diff --git a/src/link/handle.rs b/src/link/handle.rs index df18a04..4844a18 100644 --- a/src/link/handle.rs +++ b/src/link/handle.rs @@ -1,10 +1,14 @@ // SPDX-License-Identifier: MIT use super::{ - BondPortSetRequest, LinkAddRequest, LinkDelPropRequest, LinkDelRequest, - LinkGetRequest, LinkNewPropRequest, LinkSetRequest, + LinkAddRequest, LinkDelPropRequest, LinkDelRequest, LinkGetRequest, + LinkNewPropRequest, LinkSetRequest, +}; +use crate::{ + packet_core::{NLM_F_ACK, NLM_F_REQUEST}, + packet_route::link::LinkMessage, + Handle, }; -use crate::Handle; pub struct LinkHandle(Handle); @@ -13,12 +17,12 @@ impl LinkHandle { LinkHandle(handle) } - pub fn set(&self, index: u32) -> LinkSetRequest { - LinkSetRequest::new(self.0.clone(), index) + pub fn set(&self, message: LinkMessage) -> LinkSetRequest { + LinkSetRequest::new(self.0.clone(), message) } - pub fn add(&self) -> LinkAddRequest { - LinkAddRequest::new(self.0.clone()) + pub fn add(&self, message: LinkMessage) -> LinkAddRequest { + LinkAddRequest::new(self.0.clone(), message) } pub fn property_add(&self, index: u32) -> LinkNewPropRequest { @@ -38,7 +42,11 @@ impl LinkHandle { LinkGetRequest::new(self.0.clone()) } - pub fn set_bond_port(&mut self, index: u32) -> BondPortSetRequest { - BondPortSetRequest::new(self.0.clone(), index) + /// The `LinkHandle::set()` cannot be used for setting bond or bridge port + /// configuration, `RTM_NEWLINK` and `NLM_F_REQUEST|NLM_F_ACK` are required, + /// Equal to `LinkAddRequest::new().set_flags(NLM_F_REQUEST | NLM_F_ACK)` + pub fn set_port(&self, message: LinkMessage) -> LinkAddRequest { + LinkAddRequest::new(self.0.clone(), message) + .set_flags(NLM_F_REQUEST | NLM_F_ACK) } } diff --git a/src/link/mac_vlan.rs b/src/link/mac_vlan.rs new file mode 100644 index 0000000..f267f37 --- /dev/null +++ b/src/link/mac_vlan.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + link::LinkMessageBuilder, + packet_route::link::{InfoData, InfoKind, InfoMacVlan, MacVlanMode}, +}; + +/// Represent MAC VLAN interface. +/// Example code on creating a MAC VLAN interface +/// ```no_run +/// use rtnetlink::{new_connection, packet_route::link::MacVlanMode, +/// LinkMacVlan}; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add( +/// LinkMacVlan::new("macvlan100", 10, MacVlanMode::Bridge) +/// .up() +/// .build(), +/// ) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkMacVlan; + +impl LinkMacVlan { + /// Wrapper of `LinkMessageBuilder::::new().link().mode()` + pub fn new( + name: &str, + base_iface_index: u32, + mode: MacVlanMode, + ) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name) + .link(base_iface_index) + .mode(mode) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for MAC VLAN + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::MacVlan) + .name(name.to_string()) + } + + pub fn append_info_data(mut self, info: InfoMacVlan) -> Self { + if let InfoData::MacVlan(infos) = self + .info_data + .get_or_insert_with(|| InfoData::MacVlan(Vec::new())) + { + infos.push(info); + } + self + } + + pub fn mode(self, mode: MacVlanMode) -> Self { + self.append_info_data(InfoMacVlan::Mode(mode)) + } +} diff --git a/src/link/mac_vtap.rs b/src/link/mac_vtap.rs new file mode 100644 index 0000000..70a3ed0 --- /dev/null +++ b/src/link/mac_vtap.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + link::LinkMessageBuilder, + packet_route::link::{InfoData, InfoKind, InfoMacVtap, MacVtapMode}, +}; + +/// Represent MAC VTAP interface. +/// Example code on creating a MAC VTAP interface +/// ```no_run +/// use rtnetlink::{new_connection, packet_route::link::MacVtapMode, +/// LinkMacVtap}; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add( +/// LinkMacVtap::new("macvtap100", 10, MacVtapMode::Bridge) +/// .up() +/// .build(), +/// ) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkMacVtap; + +impl LinkMacVtap { + /// Wrapper of `LinkMessageBuilder::::new().link().mode()` + pub fn new( + name: &str, + base_iface_index: u32, + mode: MacVtapMode, + ) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name) + .link(base_iface_index) + .mode(mode) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for Mac VTAP interface + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::MacVtap) + .name(name.to_string()) + } + + pub fn append_info_data(mut self, info: InfoMacVtap) -> Self { + if let InfoData::MacVtap(infos) = self + .info_data + .get_or_insert_with(|| InfoData::MacVtap(Vec::new())) + { + infos.push(info); + } + self + } + + pub fn mode(self, mode: MacVtapMode) -> Self { + self.append_info_data(InfoMacVtap::Mode(mode)) + } +} diff --git a/src/link/mod.rs b/src/link/mod.rs index f184e4b..dfb42f8 100644 --- a/src/link/mod.rs +++ b/src/link/mod.rs @@ -1,24 +1,46 @@ // SPDX-License-Identifier: MIT mod add; +mod bond; +mod bond_port; +mod bridge; +mod builder; mod del; +mod dummy; mod get; mod handle; +mod mac_vlan; +mod mac_vtap; mod property_add; mod property_del; mod set; -mod set_bond_port; +mod veth; +mod vlan; +mod vrf; +mod vxlan; +mod wireguard; +mod xfrm; -pub use self::add::{ - BondAddRequest, LinkAddRequest, QosMapping, VxlanAddRequest, -}; +pub use self::add::LinkAddRequest; +pub use self::bond::LinkBond; +pub use self::bond_port::LinkBondPort; +pub use self::bridge::LinkBridge; +pub use self::builder::{LinkMessageBuilder, LinkUnspec}; pub use self::del::LinkDelRequest; +pub use self::dummy::LinkDummy; pub use self::get::LinkGetRequest; pub use self::handle::LinkHandle; +pub use self::mac_vlan::LinkMacVlan; +pub use self::mac_vtap::LinkMacVtap; pub use self::property_add::LinkNewPropRequest; pub use self::property_del::LinkDelPropRequest; pub use self::set::LinkSetRequest; -pub use self::set_bond_port::BondPortSetRequest; +pub use self::veth::LinkVeth; +pub use self::vlan::{LinkVlan, QosMapping}; +pub use self::vrf::LinkVrf; +pub use self::vxlan::LinkVxlan; +pub use self::wireguard::LinkWireguard; +pub use self::xfrm::LinkXfrm; #[cfg(test)] mod test; diff --git a/src/link/set.rs b/src/link/set.rs index 3d78831..1f5f19c 100644 --- a/src/link/set.rs +++ b/src/link/set.rs @@ -1,15 +1,10 @@ // SPDX-License-Identifier: MIT -use std::os::unix::io::RawFd; - use futures::stream::StreamExt; use netlink_packet_core::{ NetlinkMessage, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REQUEST, }; -use netlink_packet_route::{ - link::{LinkAttribute, LinkFlags, LinkMessage}, - RouteNetlinkMessage, -}; +use netlink_packet_route::{link::LinkMessage, RouteNetlinkMessage}; use crate::{try_nl, Error, Handle}; @@ -19,9 +14,7 @@ pub struct LinkSetRequest { } impl LinkSetRequest { - pub(crate) fn new(handle: Handle, index: u32) -> Self { - let mut message = LinkMessage::default(); - message.header.index = index; + pub(crate) fn new(handle: Handle, message: LinkMessage) -> Self { LinkSetRequest { handle, message } } @@ -42,140 +35,4 @@ impl LinkSetRequest { } Ok(()) } - - /// Return a mutable reference to the request - pub fn message_mut(&mut self) -> &mut LinkMessage { - &mut self.message - } - - /// Attach the link to a bridge (its _master_). This is equivalent to `ip - /// link set LINK master BRIDGE`. To succeed, both the bridge and the - /// link that is being attached must be UP. - /// - /// To Remove a link from a bridge, set its master to zero. - /// This is equvalent to `ip link set LINK nomaster` - #[deprecated( - since = "0.14.0", - note = "Please use `LinkSetRequest::controller()` instead" - )] - pub fn master(mut self, ctrl_index: u32) -> Self { - self.message - .attributes - .push(LinkAttribute::Controller(ctrl_index)); - self - } - - /// Attach the link to a bridge (its _controller_). This is equivalent to - /// `ip link set LINK master BRIDGE`. To succeed, both the bridge and the - /// link that is being attached must be UP. - /// - /// To Remove a link from a bridge, set its master to zero. - /// This is equvalent to `ip link set LINK nomaster` - pub fn controller(mut self, ctrl_index: u32) -> Self { - self.message - .attributes - .push(LinkAttribute::Controller(ctrl_index)); - self - } - - /// Detach the link from its _master_. This is equivalent to `ip link set - /// LINK nomaster`. To succeed, the link that is being detached must be - /// UP. - #[deprecated( - since = "0.14.0", - note = "Please use `LinkSetRequest::nocontroller()` instead" - )] - pub fn nomaster(mut self) -> Self { - self.message - .attributes - .push(LinkAttribute::Controller(0u32)); - self - } - - /// Detach the link from its _controller_. This is equivalent to `ip link - /// set LINK nomaster`. To succeed, the link that is being detached must be - /// UP. - pub fn nocontroller(mut self) -> Self { - self.message - .attributes - .push(LinkAttribute::Controller(0u32)); - self - } - - /// Set the link with the given index up (equivalent to `ip link set dev DEV - /// up`) - pub fn up(mut self) -> Self { - self.message.header.flags |= LinkFlags::Up; - self.message.header.change_mask |= LinkFlags::Up; - self - } - - /// Set the link with the given index down (equivalent to `ip link set dev - /// DEV down`) - pub fn down(mut self) -> Self { - self.message.header.flags.remove(LinkFlags::Up); - self.message.header.change_mask |= LinkFlags::Up; - self - } - - /// Enable or disable promiscious mode of the link with the given index - /// (equivalent to `ip link set dev DEV promisc on/off`) - pub fn promiscuous(mut self, enable: bool) -> Self { - if enable { - self.message.header.flags |= LinkFlags::Promisc; - } else { - self.message.header.flags.remove(LinkFlags::Promisc); - } - self.message.header.change_mask |= LinkFlags::Promisc; - self - } - - /// Enable or disable the ARP protocol of the link with the given index - /// (equivalent to `ip link set dev DEV arp on/off`) - pub fn arp(mut self, enable: bool) -> Self { - if enable { - self.message.header.flags.remove(LinkFlags::Noarp); - } else { - self.message.header.flags |= LinkFlags::Noarp; - } - self.message.header.change_mask |= LinkFlags::Noarp; - self - } - - /// Set the name of the link with the given index (equivalent to `ip link - /// set DEV name NAME`) - pub fn name(mut self, name: String) -> Self { - self.message.attributes.push(LinkAttribute::IfName(name)); - self - } - - /// Set the mtu of the link with the given index (equivalent to `ip link set - /// DEV mtu MTU`) - pub fn mtu(mut self, mtu: u32) -> Self { - self.message.attributes.push(LinkAttribute::Mtu(mtu)); - self - } - - /// Set the hardware address of the link with the given index (equivalent to - /// `ip link set DEV address ADDRESS`) - pub fn address(mut self, address: Vec) -> Self { - self.message - .attributes - .push(LinkAttribute::Address(address)); - self - } - - /// Move this network device into the network namespace of the process with - /// the given `pid`. - pub fn setns_by_pid(mut self, pid: u32) -> Self { - self.message.attributes.push(LinkAttribute::NetNsPid(pid)); - self - } - - /// Move this network device into the network namespace corresponding to the - /// given file descriptor. - pub fn setns_by_fd(mut self, fd: RawFd) -> Self { - self.message.attributes.push(LinkAttribute::NetNsFd(fd)); - self - } } diff --git a/src/link/set_bond_port.rs b/src/link/set_bond_port.rs deleted file mode 100644 index 85c0e5d..0000000 --- a/src/link/set_bond_port.rs +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT - -use futures::stream::StreamExt; -use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}; -use netlink_packet_route::{ - link::{ - InfoBondPort, InfoPortData, InfoPortKind, LinkAttribute, LinkInfo, - LinkMessage, - }, - RouteNetlinkMessage, -}; - -use crate::{try_nl, Error, Handle}; - -pub struct BondPortSetRequest { - handle: Handle, - index: u32, - port_nlas: Vec, -} - -impl BondPortSetRequest { - pub(crate) fn new(handle: Handle, index: u32) -> Self { - BondPortSetRequest { - handle, - index, - port_nlas: Vec::new(), - } - } - - /// Execute the request. - pub async fn execute(self) -> Result<(), Error> { - let BondPortSetRequest { - mut handle, - index, - port_nlas, - } = self; - - let mut message = LinkMessage::default(); - message.header.index = index; - message.attributes.push(LinkAttribute::LinkInfo(vec![ - LinkInfo::PortKind(InfoPortKind::Bond), - LinkInfo::PortData(InfoPortData::BondPort(port_nlas)), - ])); - - let mut req = - NetlinkMessage::from(RouteNetlinkMessage::NewLink(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(()) - } - - /// Return a mutable reference to the `Vec` - pub fn info_port_nlas_mut(&mut self) -> &mut Vec { - &mut self.port_nlas - } - - /// Adds the `queue_id` attribute to the bond port - /// This is equivalent to - /// `ip link set name NAME type bond_slave queue_id QUEUE_ID`. - pub fn queue_id(mut self, queue_id: u16) -> Self { - self.port_nlas.push(InfoBondPort::QueueId(queue_id)); - self - } - - /// Adds the `prio` attribute to the bond port - /// This is equivalent to `ip link set name NAME type bond_slave prio PRIO`. - pub fn prio(mut self, prio: i32) -> Self { - self.port_nlas.push(InfoBondPort::Prio(prio)); - self - } -} diff --git a/src/link/test.rs b/src/link/test.rs index 274e55c..03a2d6c 100644 --- a/src/link/test.rs +++ b/src/link/test.rs @@ -1,13 +1,16 @@ // SPDX-License-Identifier: MIT use futures::stream::TryStreamExt; -use netlink_packet_route::link::{ - InfoData, InfoKind, InfoMacVlan, InfoVrf, LinkAttribute, LinkInfo, - LinkMessage, MacVlanMode, -}; use tokio::runtime::Runtime; -use crate::{new_connection, Error, LinkHandle}; +use crate::{ + new_connection, + packet_route::link::{ + InfoData, InfoKind, InfoMacVlan, InfoVrf, LinkAttribute, LinkInfo, + LinkMessage, MacVlanMode, + }, + Error, LinkHandle, LinkMacVlan, LinkVrf, LinkWireguard, +}; const IFACE_NAME: &str = "wg142"; // rand? @@ -37,7 +40,7 @@ fn create_get_delete_macvlan() { let rt = Runtime::new().unwrap(); let handle = rt.block_on(_create_macvlan( - &MACVLAN_IFACE_NAME.to_owned(), + MACVLAN_IFACE_NAME, LOWER_DEVICE_IDX, /* assuming there's always a network interface in * the system ... */ MACVLAN_MODE, @@ -114,15 +117,10 @@ async fn _create_wg() -> Result { let (conn, handle, _) = new_connection().unwrap(); tokio::spawn(conn); let link_handle = handle.link(); - let mut req = link_handle.add(); - let mutator = req.message_mut(); - let info = - LinkAttribute::LinkInfo(vec![LinkInfo::Kind(InfoKind::Wireguard)]); - mutator.attributes.push(info); - mutator - .attributes - .push(LinkAttribute::IfName(IFACE_NAME.to_owned())); - req.execute().await?; + link_handle + .add(LinkWireguard::new(IFACE_NAME).build()) + .execute() + .await?; Ok(link_handle) } @@ -140,7 +138,7 @@ async fn _del_iface(handle: &mut LinkHandle, index: u32) -> Result<(), Error> { } async fn _create_macvlan( - name: &String, + name: &str, lower_device_index: u32, mode: MacVlanMode, mac: Vec, @@ -148,11 +146,11 @@ async fn _create_macvlan( let (conn, handle, _) = new_connection().unwrap(); tokio::spawn(conn); let link_handle = handle.link(); - let req = link_handle - .add() - .macvlan(name.to_string(), lower_device_index, mode) - .name(name.to_owned()) - .address(mac); + let req = link_handle.add( + LinkMacVlan::new(name, lower_device_index, mode) + .address(mac) + .build(), + ); req.execute().await?; Ok(link_handle) } @@ -161,7 +159,7 @@ async fn _create_vrf(name: &str, table: u32) -> Result { let (conn, handle, _) = new_connection().unwrap(); tokio::spawn(conn); let link_handle = handle.link(); - let req = link_handle.add().vrf(name.to_string(), table); + let req = link_handle.add(LinkVrf::new(name, table).build()); req.execute().await?; Ok(link_handle) } diff --git a/src/link/veth.rs b/src/link/veth.rs new file mode 100644 index 0000000..c963f08 --- /dev/null +++ b/src/link/veth.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + packet_route::link::{InfoData, InfoKind, InfoVeth}, + LinkMessageBuilder, LinkUnspec, +}; + +#[derive(Debug)] +/// Represent virtual ethernet interface. +/// Example code on creating a veth pair +/// ```no_run +/// use rtnetlink::{new_connection, LinkVeth}; +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add(LinkVeth::new("veth1", "veth1-peer").build()) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +pub struct LinkVeth; + +impl LinkVeth { + /// Equal to `LinkMessageBuilder::::new(name, peer)` + pub fn new(name: &str, peer: &str) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name, peer) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for VETH + pub fn new(name: &str, peer: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::Veth) + .name(name.to_string()) + .peer(peer) + } + + pub fn peer(mut self, peer: &str) -> Self { + let peer_msg = LinkMessageBuilder::::new() + .name(peer.to_string()) + .build(); + + self.info_data = Some(InfoData::Veth(InfoVeth::Peer(peer_msg))); + self + } +} diff --git a/src/link/vlan.rs b/src/link/vlan.rs new file mode 100644 index 0000000..9597fb9 --- /dev/null +++ b/src/link/vlan.rs @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + packet_route::link::{InfoData, InfoKind, InfoVlan, VlanQosMapping}, + LinkMessageBuilder, +}; + +/// A quality-of-service mapping between the internal priority `from` to the +/// external vlan priority `to`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct QosMapping { + pub from: u32, + pub to: u32, +} + +impl From for VlanQosMapping { + fn from(QosMapping { from, to }: QosMapping) -> Self { + Self::Mapping(from, to) + } +} + +/// Represent VLAN interface. +/// Example code on creating a VLAN interface +/// ```no_run +/// use rtnetlink::{new_connection, LinkVlan}; +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add( +/// LinkVlan::new("vlan100", 10, 100) +/// .up() +/// .build() +/// ) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkVlan; + +impl LinkVlan { + /// Wrapper of `LinkMessageBuilder::::new().id().dev()` + pub fn new( + name: &str, + base_iface_index: u32, + vlan_id: u16, + ) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name) + .id(vlan_id) + .link(base_iface_index) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for VLAN + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::Vlan) + .name(name.to_string()) + } + + pub fn append_info_data(mut self, info: InfoVlan) -> Self { + if let InfoData::Vlan(infos) = self + .info_data + .get_or_insert_with(|| InfoData::Vlan(Vec::new())) + { + infos.push(info); + } + self + } + + /// VLAN ID + pub fn id(self, vlan_id: u16) -> Self { + self.append_info_data(InfoVlan::Id(vlan_id)) + } + + /// ingress QoS and egress QoS + pub fn qos(self, ingress_qos: I, egress_qos: E) -> Self + where + I: IntoIterator, + E: IntoIterator, + { + let mut ret = self; + let ingress: Vec<_> = + ingress_qos.into_iter().map(VlanQosMapping::from).collect(); + if !ingress.is_empty() { + ret = ret.append_info_data(InfoVlan::IngressQos(ingress)); + } + + let egress: Vec<_> = + egress_qos.into_iter().map(VlanQosMapping::from).collect(); + + if !egress.is_empty() { + ret = ret.append_info_data(InfoVlan::EgressQos(egress)); + } + ret + } +} diff --git a/src/link/vrf.rs b/src/link/vrf.rs new file mode 100644 index 0000000..5b22f5a --- /dev/null +++ b/src/link/vrf.rs @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + packet_route::link::{InfoData, InfoKind, InfoVrf}, + LinkMessageBuilder, +}; + +/// Represent VRF interface. +/// Example code on creating a VRF interface +/// ```no_run +/// use rtnetlink::{new_connection, LinkVrf}; +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add( +/// LinkVrf::new("my-vrf", 100) +/// .up() +/// .build() +/// ) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkVrf; + +impl LinkVrf { + /// Wrapper of `LinkMessageBuilder::::new().table_id()` + pub fn new(name: &str, table_id: u32) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name).table_id(table_id) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for VRF + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::Vrf) + .name(name.to_string()) + } + + pub fn append_info_data(mut self, info: InfoVrf) -> Self { + if let InfoData::Vrf(infos) = self + .info_data + .get_or_insert_with(|| InfoData::Vrf(Vec::new())) + { + infos.push(info); + } + self + } + + /// VRF table ID + pub fn table_id(self, table_id: u32) -> Self { + self.append_info_data(InfoVrf::TableId(table_id)) + } +} diff --git a/src/link/vxlan.rs b/src/link/vxlan.rs new file mode 100644 index 0000000..ebff3bf --- /dev/null +++ b/src/link/vxlan.rs @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + packet_route::link::{InfoData, InfoKind, InfoVxlan}, + LinkMessageBuilder, +}; + +/// Represent VxLAN interface. +/// Example code on creating a VxLAN interface +/// ```no_run +/// use rtnetlink::{new_connection, LinkVxlan}; +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add(LinkVxlan::new("vxlan100", 100) +/// .dev(10) +/// .port(4789) +/// .up() +/// .build()) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkVxlan; + +impl LinkVxlan { + /// Wrapper of `LinkMessageBuilder::::new().id()` + pub fn new(name: &str, vni: u32) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name).id(vni) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for VLAN + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::Vxlan) + .name(name.to_string()) + } + + pub fn append_info_data(self, info: InfoVxlan) -> Self { + let mut ret = self; + if let InfoData::Vxlan(infos) = ret + .info_data + .get_or_insert_with(|| InfoData::Vxlan(Vec::new())) + { + infos.push(info); + } + ret + } + + /// VNI + pub fn id(self, id: u32) -> Self { + self.append_info_data(InfoVxlan::Id(id)) + } + + /// This is equivalent to `devPHYS_DEV` for ip link vxlan. + /// Adds the `dev` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI dev + /// LINK`, dev LINK - specifies the physical device to use + /// for tunnel endpoint communication. + /// But instead of specifing a link name (`LINK`), we specify a link index. + /// Please be aware the `LinkMessageBuilder::link()` will not works for + /// VxLAN. + pub fn dev(self, index: u32) -> Self { + self.append_info_data(InfoVxlan::Link(index)) + } + + /// Adds the `dstport` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI dstport + /// PORT`. dstport PORT - specifies the UDP destination port to + /// communicate to the remote VXLAN tunnel endpoint. + pub fn port(self, port: u16) -> Self { + self.append_info_data(InfoVxlan::Port(port)) + } + + /// Adds the `group` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI group + /// IPADDR`, group IPADDR - specifies the multicast IP address to join. + /// This function takes an IPv4 address + /// WARNING: only one between `remote` and `group` can be present. + pub fn group(self, addr: std::net::Ipv4Addr) -> Self { + self.append_info_data(InfoVxlan::Group(addr.octets().to_vec())) + } + + /// Adds the `group` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI group + /// IPADDR`, group IPADDR - specifies the multicast IP address to join. + /// This function takes an IPv6 address + /// WARNING: only one between `remote` and `group` can be present. + pub fn group6(self, addr: std::net::Ipv6Addr) -> Self { + self.append_info_data(InfoVxlan::Group6(addr.octets().to_vec())) + } + + /// Adds the `remote` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI remote + /// IPADDR`, remote IPADDR - specifies the unicast destination IP + /// address to use in outgoing packets when the + /// destination link layer address is not known in the + /// VXLAN device forwarding database. + /// This function takes an IPv4 address. + /// WARNING: only one between `remote` and `group` can be present. + pub fn remote(self, addr: std::net::Ipv4Addr) -> Self { + self.group(addr) + } + + /// Adds the `remote` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI remote + /// IPADDR`, remote IPADDR - specifies the unicast destination IP + /// address to use in outgoing packets when the + /// destination link layer address is not known in the + /// VXLAN device forwarding database. + /// This function takes an IPv6 address. + /// WARNING: only one between `remote` and `group` can be present. + pub fn remote6(self, addr: std::net::Ipv6Addr) -> Self { + self.group6(addr) + } + + /// Adds the `local` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI local + /// IPADDR`, local IPADDR - specifies the source IP address to use in + /// outgoing packets. This function takes an IPv4 address. + pub fn local(self, addr: std::net::Ipv4Addr) -> Self { + self.append_info_data(InfoVxlan::Local(addr.octets().to_vec())) + } + + /// Adds the `local` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI local + /// IPADDR`, local IPADDR - specifies the source IP address to use in + /// outgoing packets. This function takes an IPv6 address. + pub fn local6(self, addr: std::net::Ipv6Addr) -> Self { + self.append_info_data(InfoVxlan::Local6(addr.octets().to_vec())) + } + + /// Adds the `tos` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI tos TOS`. + /// tos TOS - specifies the TOS value to use in outgoing packets. + pub fn tos(self, tos: u8) -> Self { + self.append_info_data(InfoVxlan::Tos(tos)) + } + + /// Adds the `ttl` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI ttl TTL`. + /// ttl TTL - specifies the TTL value to use in outgoing packets. + pub fn ttl(self, ttl: u8) -> Self { + self.append_info_data(InfoVxlan::Ttl(ttl)) + } + + /// Adds the `flowlabel` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI flowlabel + /// LABEL`. flowlabel LABEL - specifies the flow label to use in + /// outgoing packets. + pub fn label(self, label: u32) -> Self { + self.append_info_data(InfoVxlan::Label(label)) + } + + /// Adds the `learning` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI + /// \[no\]learning`. \[no\]learning - specifies if unknown source link layer + /// addresses and IP addresses are entered into the VXLAN + /// device forwarding database. + pub fn learning(self, learning: bool) -> Self { + self.append_info_data(InfoVxlan::Learning(learning)) + } + + /// Adds the `ageing` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI ageing + /// SECONDS`. ageing SECONDS - specifies the lifetime in seconds of + /// FDB entries learnt by the kernel. + pub fn ageing(self, seconds: u32) -> Self { + self.append_info_data(InfoVxlan::Ageing(seconds)) + } + + /// Adds the `maxaddress` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI + /// maxaddress LIMIT`. maxaddress LIMIT - specifies the maximum number + /// of FDB entries. + pub fn limit(self, limit: u32) -> Self { + self.append_info_data(InfoVxlan::Limit(limit)) + } + + /// Adds the `srcport` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI srcport + /// MIN MAX`. srcport MIN MAX - specifies the range of port numbers + /// to use as UDP source ports to communicate to the + /// remote VXLAN tunnel endpoint. + pub fn port_range(self, min: u16, max: u16) -> Self { + self.append_info_data(InfoVxlan::PortRange((min, max))) + } + + /// Adds the `proxy` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI + /// [no]proxy`. \[no\]proxy - specifies ARP proxy is turned on. + pub fn proxy(self, proxy: bool) -> Self { + self.append_info_data(InfoVxlan::Proxy(proxy)) + } + + /// Adds the `rsc` attribute to the VXLAN This is equivalent to + /// `ip link add name NAME type vxlan id VNI [no]rsc`. + /// \[no\]rsc - specifies if route short circuit is turned on. + pub fn rsc(self, rsc: bool) -> Self { + self.append_info_data(InfoVxlan::Rsc(rsc)) + } + + // Adds the `l2miss` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI + /// [no]l2miss`. \[no\]l2miss - specifies if netlink LLADDR miss + /// notifications are generated. + pub fn l2miss(self, l2miss: bool) -> Self { + self.append_info_data(InfoVxlan::L2Miss(l2miss)) + } + + // Adds the `l3miss` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI + /// [no]l3miss`. \[no\]l3miss - specifies if netlink IP ADDR + /// miss notifications are generated. + pub fn l3miss(self, l3miss: bool) -> Self { + self.append_info_data(InfoVxlan::L3Miss(l3miss)) + } + + pub fn collect_metadata(self, collect_metadata: bool) -> Self { + self.append_info_data(InfoVxlan::CollectMetadata(collect_metadata)) + } + + // Adds the `udp_csum` attribute to the VXLAN + /// This is equivalent to `ip link add name NAME type vxlan id VNI + /// [no]udp_csum`. \[no\]udpcsum - specifies if UDP checksum is + /// calculated for transmitted packets over IPv4. + pub fn udp_csum(self, udp_csum: bool) -> Self { + self.append_info_data(InfoVxlan::UDPCsum(udp_csum)) + } +} diff --git a/src/link/wireguard.rs b/src/link/wireguard.rs new file mode 100644 index 0000000..b096311 --- /dev/null +++ b/src/link/wireguard.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +use crate::{link::LinkMessageBuilder, packet_route::link::InfoKind}; + +/// Represent wireguard interface. +/// Example code on creating a wireguard interface +/// ```no_run +/// use rtnetlink::{new_connection, LinkWireguard}; +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add(LinkWireguard::new("wg0").build()) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkWireguard; + +impl LinkWireguard { + /// Equal to `LinkMessageBuilder::::new()` + pub fn new(name: &str) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for wireguard + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind( + InfoKind::Wireguard, + ) + .name(name.to_string()) + } +} diff --git a/src/link/xfrm.rs b/src/link/xfrm.rs new file mode 100644 index 0000000..f2f864e --- /dev/null +++ b/src/link/xfrm.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +use crate::{ + link::LinkMessageBuilder, + packet_route::link::{InfoData, InfoKind, InfoXfrm}, +}; + +/// Represent XFRM interface. +/// Example code on creating a XFRM interface +/// ```no_run +/// use rtnetlink::{new_connection, LinkXfrm}; +/// #[tokio::main] +/// async fn main() -> Result<(), String> { +/// let (connection, handle, _) = new_connection().unwrap(); +/// tokio::spawn(connection); +/// +/// handle +/// .link() +/// .add(LinkXfrm::new("xfrm8", 9, 0x08).build()) +/// .execute() +/// .await +/// .map_err(|e| format!("{e}")) +/// } +/// ``` +/// +/// Please check LinkMessageBuilder:: for more detail. +#[derive(Debug)] +pub struct LinkXfrm; + +impl LinkXfrm { + /// Equal to `LinkMessageBuilder::::new().dev().if_id()` + pub fn new( + name: &str, + base_iface_index: u32, + if_id: u32, + ) -> LinkMessageBuilder { + LinkMessageBuilder::::new(name) + .dev(base_iface_index) + .if_id(if_id) + } +} + +impl LinkMessageBuilder { + /// Create [LinkMessageBuilder] for XFRM + pub fn new(name: &str) -> Self { + LinkMessageBuilder::::new_with_info_kind(InfoKind::Xfrm) + .name(name.to_string()) + } + + pub fn append_info_data(mut self, info: InfoXfrm) -> Self { + if let InfoData::Xfrm(infos) = self + .info_data + .get_or_insert_with(|| InfoData::Xfrm(Vec::new())) + { + infos.push(info); + } + self + } + + /// This is equivalent to the `if_id IF_ID` in command + /// `ip link add name NAME type xfrm if_id IF_ID`. + pub fn if_id(self, if_id: u32) -> Self { + self.append_info_data(InfoXfrm::IfId(if_id)) + } + + /// This is equivalent to the `dev PHYS_DEV` in command + /// `ip link add name NAME type xfm dev PHYS_DEV`, only take the interface + /// index. + pub fn dev(self, iface_index: u32) -> Self { + self.append_info_data(InfoXfrm::Link(iface_index)) + } +} diff --git a/src/traffic_control/add_filter.rs b/src/traffic_control/add_filter.rs index 225bae1..a72d30a 100644 --- a/src/traffic_control/add_filter.rs +++ b/src/traffic_control/add_filter.rs @@ -1,20 +1,22 @@ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; -use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}; -use netlink_packet_route::{ - tc::{ - TcAction, TcActionAttribute, TcActionGeneric, TcActionMirror, - TcActionMirrorOption, TcActionOption, TcActionType, TcAttribute, - TcFilterU32, TcFilterU32Option, TcHandle, TcHeader, TcMessage, - TcMirror, TcMirrorActionType, TcOption, TcU32Key, TcU32Selector, - TcU32SelectorFlags, + +use crate::{ + packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}, + packet_route::{ + tc::{ + TcAction, TcActionAttribute, TcActionGeneric, TcActionMirror, + TcActionMirrorOption, TcActionOption, TcActionType, TcAttribute, + TcFilterU32, TcFilterU32Option, TcHandle, TcHeader, TcMessage, + TcMirror, TcMirrorActionType, TcOption, TcU32Key, TcU32Selector, + TcU32SelectorFlags, + }, + RouteNetlinkMessage, }, - RouteNetlinkMessage, + try_nl, Error, Handle, }; -use crate::{try_nl, Error, Handle}; - pub struct TrafficFilterNewRequest { handle: Handle, message: TcMessage, @@ -177,18 +179,19 @@ mod test { use std::{fs::File, os::fd::AsFd, path::Path}; use futures::stream::TryStreamExt; - use netlink_packet_route::{ - link::LinkMessage, - tc::{ - TcAttribute, TcFilterU32, TcFilterU32Option, TcOption, TcU32Key, - TcU32SelectorFlags, - }, - }; use nix::sched::{setns, CloneFlags}; use tokio::runtime::Runtime; use crate::{ - new_connection, Handle, NetworkNamespace, NETNS_PATH, SELF_NS_PATH, + new_connection, + packet_route::{ + link::LinkMessage, + tc::{ + TcAttribute, TcFilterU32, TcFilterU32Option, TcOption, + TcU32Key, TcU32SelectorFlags, + }, + }, + Handle, LinkVeth, NetworkNamespace, NETNS_PATH, SELF_NS_PATH, }; const TEST_NS: &str = "netlink_test_filter_ns"; @@ -246,8 +249,7 @@ mod test { tokio::spawn(connection); handle .link() - .add() - .veth(TEST_VETH_1.to_string(), TEST_VETH_2.to_string()) + .add(LinkVeth::new(TEST_VETH_1, TEST_VETH_2).build()) .execute() .await .unwrap(); diff --git a/src/traffic_control/add_qdisc.rs b/src/traffic_control/add_qdisc.rs index bd72dd9..0fe0a91 100644 --- a/src/traffic_control/add_qdisc.rs +++ b/src/traffic_control/add_qdisc.rs @@ -1,13 +1,15 @@ // SPDX-License-Identifier: MIT use futures::stream::StreamExt; -use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}; -use netlink_packet_route::{ - tc::{TcAttribute, TcHandle, TcMessage}, - RouteNetlinkMessage, -}; -use crate::{try_nl, Error, Handle}; +use crate::{ + packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}, + packet_route::{ + tc::{TcAttribute, TcHandle, TcMessage}, + RouteNetlinkMessage, + }, + try_nl, Error, Handle, +}; pub struct QDiscNewRequest { handle: Handle, @@ -82,8 +84,11 @@ mod test { use tokio::runtime::Runtime; use super::*; - use crate::{new_connection, NetworkNamespace, NETNS_PATH, SELF_NS_PATH}; - use netlink_packet_route::{link::LinkMessage, AddressFamily}; + use crate::{ + new_connection, + packet_route::{link::LinkMessage, AddressFamily}, + LinkDummy, NetworkNamespace, NETNS_PATH, SELF_NS_PATH, + }; const TEST_NS: &str = "netlink_test_qdisc_ns"; const TEST_DUMMY: &str = "test_dummy"; @@ -139,8 +144,7 @@ mod test { tokio::spawn(connection); handle .link() - .add() - .dummy(TEST_DUMMY.to_string()) + .add(LinkDummy::new(TEST_DUMMY).up().build()) .execute() .await .unwrap();