├── .github └── workflows │ ├── clippy-rustfmt.yml │ ├── license.yml │ └── main.yml ├── .gitignore ├── .licenserc.yaml ├── .rustfmt.toml ├── CHANGELOG ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── examples ├── add_address.rs ├── add_neighbour.rs ├── add_netns.rs ├── add_netns_async.rs ├── add_route.rs ├── add_route_mpls.rs ├── add_route_pref_src.rs ├── add_rule.rs ├── add_tc_qdisc_ingress.rs ├── create_bond.rs ├── create_bridge.rs ├── create_dummy.rs ├── create_macvlan.rs ├── create_macvtap.rs ├── create_veth.rs ├── create_vlan.rs ├── create_vrf.rs ├── create_vxlan.rs ├── create_wireguard.rs ├── create_xfrm.rs ├── del_link.rs ├── del_netns.rs ├── del_netns_async.rs ├── flush_addresses.rs ├── get_address.rs ├── get_bond_port_settings.rs ├── get_links.rs ├── get_links_async.rs ├── get_links_thread_builder.rs ├── get_neighbours.rs ├── get_route.rs ├── get_route_kernel_filter.rs ├── get_rule.rs ├── ip_monitor.rs ├── listen.rs ├── property_altname.rs ├── set_bond_port.rs └── set_link_down.rs └── src ├── addr ├── add.rs ├── del.rs ├── get.rs ├── handle.rs └── mod.rs ├── connection.rs ├── constants.rs ├── errors.rs ├── handle.rs ├── lib.rs ├── link ├── add.rs ├── bond.rs ├── bond_port.rs ├── bridge.rs ├── builder.rs ├── del.rs ├── dummy.rs ├── get.rs ├── handle.rs ├── mac_vlan.rs ├── mac_vtap.rs ├── mod.rs ├── property_add.rs ├── property_del.rs ├── set.rs ├── test.rs ├── veth.rs ├── vlan.rs ├── vrf.rs ├── vxlan.rs ├── wireguard.rs └── xfrm.rs ├── macros.rs ├── neighbour ├── add.rs ├── del.rs ├── get.rs ├── handle.rs └── mod.rs ├── ns.rs ├── route ├── add.rs ├── builder.rs ├── del.rs ├── get.rs ├── handle.rs └── mod.rs ├── rule ├── add.rs ├── del.rs ├── get.rs ├── handle.rs └── mod.rs └── traffic_control ├── add_filter.rs ├── add_qdisc.rs ├── del_filter.rs ├── del_qdisc.rs ├── get.rs ├── handle.rs ├── mod.rs └── test.rs /.github/workflows/clippy-rustfmt.yml: -------------------------------------------------------------------------------- 1 | name: Rustfmt and clippy check 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | rustfmt_clippy: 12 | strategy: 13 | fail-fast: true 14 | 15 | name: Rustfmt and clippy check 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Install Rust Nightly 22 | run: | 23 | rustup override set nightly 24 | rustup update nightly 25 | rustup component add rustfmt clippy 26 | 27 | - name: rustfmt 28 | run: cargo fmt --all -- --check 29 | 30 | - name: clippy-tokio-socket 31 | run: cargo clippy 32 | 33 | - name: clippy-smol-socket 34 | run: cargo clippy --no-default-features --features smol_socket 35 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | name: license 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | check-license: 12 | name: Check License 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 3 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Check License Header 19 | uses: apache/skywalking-eyes@v0.3.0 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | concurrency: 4 | group: ci-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | pull_request: 9 | types: [opened, synchronize, reopened] 10 | push: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | ci: 16 | name: CI (stable) 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: Install necessary dependencies 23 | run: | 24 | sudo apt update 25 | sudo apt install "linux-modules-extra-$(uname -r)" 26 | sudo modprobe vrf 27 | 28 | - name: Install Rust Stable 29 | run: | 30 | rustup override set stable 31 | rustup update stable 32 | 33 | - name: Test with default feature 34 | env: 35 | # Needed for the `link::test::create_get_delete_w` test to pass. 36 | CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: "sudo -E" 37 | run: cargo test 38 | 39 | - name: Test with tokio feature 40 | env: 41 | # Needed for the `link::test::create_get_delete_w` test to pass. 42 | CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: "sudo -E" 43 | run: cargo test --features tokio_socket 44 | 45 | - name: Test with smol_socket feature 46 | env: 47 | # Needed for the `link::test::create_get_delete_w` test to pass. 48 | CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: "sudo -E" 49 | run: cargo test --features smol_socket 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | vendor/ 4 | tags 5 | *.swp 6 | -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | header: 2 | license: 3 | content: | 4 | SPDX-License-Identifier: MIT 5 | paths-ignore: 6 | - 'target' 7 | - '**/*.toml' 8 | - '**/*.lock' 9 | - '**/*.yml' 10 | - '**/*.md' 11 | - 'CHANGELOG' 12 | - 'LICENSE-MIT' 13 | - '.gitignore' 14 | 15 | comment: on-failure 16 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | wrap_comments = true 3 | reorder_imports = true 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## [0.17.0] - 2025-05-29 3 | ### Breaking changes 4 | - Please check `netlink-packet-route` 0.24.0 breaking changes. 5 | 6 | ### New features 7 | - route: Implement support for the ONLINK flag. (30aa30f) 8 | - route: Support for MPLS routes and nexthop label stacks. (e7e7344) 9 | - route: Support for multipath routes. (0236751) 10 | 11 | ### Bug fixes 12 | - N/A 13 | 14 | ## [0.16.0] - 2025-03-10 15 | ### Breaking changes 16 | - N/A 17 | 18 | ### New features 19 | - Add support of tc filter del command. (51cfceb) 20 | 21 | ### Bug fixes 22 | - Fix error on decoding empty `IFLA_VFINFO_LIST`. (581b4ac) 23 | 24 | ## [0.15.0] - 2025-03-10 25 | ### Breaking changes 26 | - Deprecated `LinkAddRequest::xfrmtun()` in the favor of 27 | `LinkAddRequest::xfrmtun_link`. (de62338) 28 | - Changed `RouteGetRequest::new()` to use `RouteMessageBuilder`. (9be24c6) 29 | - Changed `LinkAddRequest()` and `LinkSetRequest` to use 30 | `LinkMessageBuilder`. (230a729) 31 | 32 | ### New features 33 | - Support specifying link when crating xfrm interface. (de62338) 34 | - Support creating VRF link. (6863102) 35 | - Introducing `RouteAddRequest` for building netlink message for adding 36 | routes. (b5e0cb6, 24bf04f) 37 | - Reexport `netlink-*` packages. (a4d2611) 38 | - impl Debug, Clone for all requests and handles. (515471f) 39 | 40 | ### Bug fixes 41 | - N/A 42 | 43 | ## [0.14.1] - 2024-02-01 44 | ### Breaking changes 45 | - N/A 46 | 47 | ### New features 48 | - FreeBSD support. (eb04e60) 49 | - Support specifying MAC address in `LinkAddRequest`. (d76171c) 50 | - Support creating wireguard link in `LinkAddRequest`. (24982ec) 51 | - Support setting priority in `RouteAddRequest`. (c840e78) 52 | 53 | ### Bug fixes 54 | - Fixing docs of AddressGetRequest::set_address_filter. (006a348) 55 | 56 | ## [0.14.0] - 2023-12-05 57 | ### Breaking changes 58 | - Many `VxlanAddRequest` functions changed from u8 to bool. (ba4825a) 59 | - Deprecated `LinkSetRequest::master()` in the favor of 60 | `LinkSetRequest::controller()`. (ba4825a) 61 | - Deprecated `LinkSetRequest::nomaster()` in the favor of 62 | `LinkSetRequest::nocontroller()`. (ba4825a) 63 | - Many `NeighbourAddRequest` functions changed from u8/u16 to enum. (ba4825a) 64 | - Many `TrafficFilterNewRequest` functions changed from u8/u16 to enum. 65 | (ba4825a) 66 | 67 | ### New features 68 | - Rule: function to set fw_mark when adding rule. (dabef43) 69 | 70 | ### Bug fixes 71 | - N/A 72 | 73 | ## [0.13.1] - 2023-07-18 74 | ### Breaking changes 75 | - Deprecated `BondAddRequest::active_slave()` in the favor of 76 | `BondAddRequest::active_port()`. (9b67c97, bf6dbf0) 77 | - Deprecated `BondAddRequest::all_slaves_active()` in the favor of 78 | `BondAddRequest::all_ports_active()`. (9b67c97, bf6dbf0) 79 | 80 | ### New features 81 | - Support bond port setting. (7afe563) 82 | - Support VLAN QOS setting. (78a58db) 83 | 84 | ### Bug fixes 85 | - N/A 86 | 87 | ## [0.13.0] - 2023-07-10 88 | ### Breaking changes 89 | - `TrafficFilterNewRequest::u32()` changed to return `Result`. (b7f8c73) 90 | - `TrafficFilterNewRequest::redirect() changed to return `Result`. (b7f8c73) 91 | - Deprecated `RouteAddRequest::table` in the favor of 92 | `RouteAddRequest::table_id` in order to support table ID bigger than 255. 93 | (0a8eddd) 94 | 95 | ### New features 96 | - Support route table ID bigger than 255. (0a8eddd) 97 | - Support creating xfrm tunnel. (5252908) 98 | 99 | ### Bug fixes 100 | - Removed assers. (e6bcf3e) 101 | 102 | ## [0.12.0] - 2023-01-29 103 | ### Breaking changes 104 | - Removed these reexports. (2d58a54) 105 | * `rtnetlink::packet` 106 | * `rtnetlink::proto` 107 | * `rtnetlink::sys` 108 | 109 | ### New features 110 | - Allow adding macvtap on a link. (ad1207f) 111 | - Support setting priority when adding rules. (b771ffd) 112 | 113 | ### Bug fixes 114 | - Fix ip_monitor example. (b12f061) 115 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rtnetlink" 3 | version = "0.17.0" 4 | authors = ["Corentin Henry "] 5 | edition = "2018" 6 | homepage = "https://github.com/rust-netlink/rtnetlink" 7 | keywords = ["netlink", "ip", "linux"] 8 | license = "MIT" 9 | readme = "README.md" 10 | repository = "https://github.com/rust-netlink/rtnetlink" 11 | description = "manipulate linux networking resources via netlink" 12 | rust-version = "1.66.1" 13 | 14 | [features] 15 | test_as_root = [] 16 | default = ["tokio_socket"] 17 | tokio_socket = ["netlink-proto/tokio_socket", "tokio"] 18 | smol_socket = ["netlink-proto/smol_socket", "async-global-executor"] 19 | 20 | [dependencies] 21 | futures = "0.3.11" 22 | log = "0.4.8" 23 | thiserror = "1" 24 | netlink-sys = { version = "0.8" } 25 | netlink-packet-utils = { version = "0.5" } 26 | netlink-packet-route = { version = "0.24" } 27 | netlink-packet-core = { version = "0.7" } 28 | netlink-proto = { default-features = false, version = "0.11" } 29 | nix = { version = "0.29.0", default-features = false, features = ["fs", "mount", "sched", "signal"] } 30 | tokio = { version = "1.0.1", features = ["rt"], optional = true} 31 | async-global-executor = { version = "2.0.2", optional = true } 32 | 33 | [dev-dependencies] 34 | env_logger = "0.11.0" 35 | ipnetwork = "0.20.0" 36 | tokio = { version = "1.0.1", features = ["macros", "rt", "rt-multi-thread"] } 37 | async-std = { version = "1.9.0", features = ["attributes"]} 38 | macaddr = "1.0" 39 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of 2 | this software and associated documentation files (the "Software"), to deal in 3 | the Software without restriction, including without limitation the rights to 4 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 5 | of the Software, and to permit persons to whom the Software is furnished to do 6 | so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | Distributions of all or part of the Software intended to be used by the 12 | recipients as they would use the unmodified Software, containing modifications 13 | that substantially alter, remove, or disable functionality of the Software, 14 | outside of the documented configuration mechanisms provided by the Software, 15 | shall be modified such that the Original Author's bug reporting email addresses 16 | and urls are either replaced with the contact information of the parties 17 | responsible for the changes, or removed entirely. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust crate for rtnetlink protocol 2 | 3 | This crate provides methods to manipulate networking resources (links, 4 | addresses, arp tables, route tables) via the [netlink route protocol][1]. 5 | 6 | Rust crate document could be found at [docs.rs][2]. 7 | 8 | [1]: https://www.man7.org/linux/man-pages/man7/rtnetlink.7.html 9 | [2]: https://docs.rs/rtnetlink 10 | -------------------------------------------------------------------------------- /examples/add_address.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use std::env; 5 | 6 | use ipnetwork::IpNetwork; 7 | use rtnetlink::{new_connection, Error, Handle}; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<(), ()> { 11 | let args: Vec = env::args().collect(); 12 | if args.len() != 3 { 13 | usage(); 14 | return Ok(()); 15 | } 16 | 17 | let link_name = &args[1]; 18 | let ip: IpNetwork = args[2].parse().unwrap_or_else(|_| { 19 | eprintln!("invalid address"); 20 | std::process::exit(1); 21 | }); 22 | 23 | let (connection, handle, _) = new_connection().unwrap(); 24 | tokio::spawn(connection); 25 | 26 | if let Err(e) = add_address(link_name, ip, handle.clone()).await { 27 | eprintln!("{e}"); 28 | } 29 | Ok(()) 30 | } 31 | 32 | async fn add_address( 33 | link_name: &str, 34 | ip: IpNetwork, 35 | handle: Handle, 36 | ) -> Result<(), Error> { 37 | let mut links = handle 38 | .link() 39 | .get() 40 | .match_name(link_name.to_string()) 41 | .execute(); 42 | if let Some(link) = links.try_next().await? { 43 | handle 44 | .address() 45 | .add(link.header.index, ip.ip(), ip.prefix()) 46 | .execute() 47 | .await? 48 | } 49 | Ok(()) 50 | } 51 | 52 | fn usage() { 53 | eprintln!( 54 | "usage: 55 | cargo run --example add_address -- 56 | 57 | Note that you need to run this program as root. Instead of running cargo as root, 58 | build the example normally: 59 | 60 | cd rtnetlink ; cargo build --example add_address 61 | 62 | Then find the binary in the target directory: 63 | 64 | cd ../target/debug/example ; sudo ./add_address " 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /examples/add_neighbour.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{new_connection, Error, Handle}; 5 | use std::{convert::TryFrom, env, net::IpAddr}; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), ()> { 9 | let args: Vec = env::args().collect(); 10 | if args.len() != 4 { 11 | usage(); 12 | return Ok(()); 13 | } 14 | 15 | let link_name = &args[1]; 16 | let ip: IpAddr = args[2].parse().unwrap_or_else(|_| { 17 | eprintln!("invalid IP address"); 18 | std::process::exit(1); 19 | }); 20 | 21 | let link_local_parts: Vec = args[3] 22 | .split(':') 23 | .map(|b| { 24 | u8::from_str_radix(b, 16).unwrap_or_else(|e| { 25 | eprintln!("invalid part of mac {}: {}", b, e); 26 | std::process::exit(1); 27 | }) 28 | }) 29 | .collect(); 30 | 31 | let link_local_address = <[u8; 6]>::try_from(link_local_parts).unwrap_or_else(|_| { 32 | eprintln!("invalid mac address, please give it in the format of 56:78:90:ab:cd:ef"); 33 | std::process::exit(1); 34 | }); 35 | 36 | let (connection, handle, _) = new_connection().unwrap(); 37 | tokio::spawn(connection); 38 | 39 | if let Err(e) = 40 | add_neighbour(link_name, ip, link_local_address, handle.clone()).await 41 | { 42 | eprintln!("{e}"); 43 | } 44 | Ok(()) 45 | } 46 | 47 | async fn add_neighbour( 48 | link_name: &str, 49 | ip: IpAddr, 50 | link_local_address: [u8; 6], 51 | handle: Handle, 52 | ) -> Result<(), Error> { 53 | let mut links = handle 54 | .link() 55 | .get() 56 | .match_name(link_name.to_string()) 57 | .execute(); 58 | if let Some(link) = links.try_next().await? { 59 | handle 60 | .neighbours() 61 | .add(link.header.index, ip) 62 | .link_local_address(&link_local_address) 63 | .execute() 64 | .await?; 65 | println!("Done"); 66 | } 67 | 68 | Ok(()) 69 | } 70 | 71 | fn usage() { 72 | eprintln!( 73 | "usage: 74 | cargo run --example add_neighbour -- 75 | 76 | Note that you need to run this program as root. Instead of running cargo as root, 77 | build the example normally: 78 | 79 | cd rtnetlink ; cargo build --example add_neighbour 80 | 81 | Then find the binary in the target directory: 82 | 83 | cd ../target/debug/example ; 84 | sudo ./add_neighbour " 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /examples/add_netns.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #[cfg(not(target_os = "freebsd"))] 4 | use rtnetlink::NetworkNamespace; 5 | use std::env; 6 | 7 | #[cfg(target_os = "freebsd")] 8 | fn main() -> () {} 9 | 10 | #[cfg(not(target_os = "freebsd"))] 11 | #[tokio::main] 12 | async fn main() -> Result<(), String> { 13 | env_logger::init(); 14 | let args: Vec = env::args().collect(); 15 | if args.len() != 2 { 16 | usage(); 17 | return Ok(()); 18 | } 19 | let ns_name = &args[1]; 20 | 21 | NetworkNamespace::add(ns_name.to_string()) 22 | .await 23 | .map_err(|e| format!("{e}")) 24 | } 25 | 26 | fn usage() { 27 | eprintln!( 28 | "usage: 29 | cargo run --example add_netns -- 30 | 31 | Note that you need to run this program as root. Instead of running cargo as root, 32 | build the example normally: 33 | 34 | cd netlink-ip ; cargo build --example add_netns 35 | 36 | Then find the binary in the target directory: 37 | 38 | cd ../target/debug/example ; sudo ./add_netns " 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /examples/add_netns_async.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #[cfg(not(target_os = "freebsd"))] 4 | use rtnetlink::NetworkNamespace; 5 | use std::env; 6 | 7 | #[cfg(target_os = "freebsd")] 8 | fn main() -> () {} 9 | 10 | #[cfg(not(target_os = "freebsd"))] 11 | #[async_std::main] 12 | async fn main() -> Result<(), String> { 13 | env_logger::init(); 14 | let args: Vec = env::args().collect(); 15 | if args.len() != 2 { 16 | usage(); 17 | return Ok(()); 18 | } 19 | let ns_name = &args[1]; 20 | 21 | NetworkNamespace::add(ns_name.to_string()) 22 | .await 23 | .map_err(|e| format!("{e}")) 24 | } 25 | 26 | fn usage() { 27 | eprintln!( 28 | "usage: 29 | cargo run --example add_netns -- 30 | 31 | Note that you need to run this program as root. Instead of running cargo as root, 32 | build the example normally: 33 | 34 | cd netlink-ip ; cargo build --example add_netns 35 | 36 | Then find the binary in the target directory: 37 | 38 | cd ../target/debug/example ; sudo ./add_netns " 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /examples/add_route.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::{env, net::Ipv4Addr}; 4 | 5 | use ipnetwork::Ipv4Network; 6 | use rtnetlink::{new_connection, Error, Handle, RouteMessageBuilder}; 7 | 8 | const TEST_TABLE_ID: u32 = 299; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<(), ()> { 12 | let args: Vec = env::args().collect(); 13 | if args.len() != 3 { 14 | usage(); 15 | return Ok(()); 16 | } 17 | 18 | let dest: Ipv4Network = args[1].parse().unwrap_or_else(|_| { 19 | eprintln!("invalid destination"); 20 | std::process::exit(1); 21 | }); 22 | let gateway: Ipv4Network = args[2].parse().unwrap_or_else(|_| { 23 | eprintln!("invalid gateway"); 24 | std::process::exit(1); 25 | }); 26 | 27 | let (connection, handle, _) = new_connection().unwrap(); 28 | tokio::spawn(connection); 29 | 30 | if let Err(e) = add_route(&dest, &gateway, handle.clone()).await { 31 | eprintln!("{e}"); 32 | } else { 33 | println!("Route has been added to table {TEST_TABLE_ID}"); 34 | } 35 | Ok(()) 36 | } 37 | 38 | async fn add_route( 39 | dest: &Ipv4Network, 40 | gateway: &Ipv4Network, 41 | handle: Handle, 42 | ) -> Result<(), Error> { 43 | let route = RouteMessageBuilder::::new() 44 | .destination_prefix(dest.ip(), dest.prefix()) 45 | .gateway(gateway.ip()) 46 | .table_id(TEST_TABLE_ID) 47 | .build(); 48 | handle.route().add(route).execute().await?; 49 | Ok(()) 50 | } 51 | 52 | fn usage() { 53 | eprintln!( 54 | "\ 55 | usage: 56 | cargo run --example add_route -- / 57 | 58 | Note that you need to run this program as root: 59 | 60 | env CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER='sudo -E' \\ 61 | cargo run --example add_route -- / \ 62 | " 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /examples/add_route_mpls.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::env; 4 | 5 | use ipnetwork::IpNetwork; 6 | use netlink_packet_route::route::MplsLabel; 7 | use rtnetlink::{new_connection, Error, Handle, RouteMessageBuilder}; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<(), ()> { 11 | let args: Vec = env::args().collect(); 12 | if args.len() != 4 { 13 | usage(); 14 | return Ok(()); 15 | } 16 | 17 | let input_label = args[1] 18 | .parse::() 19 | .map(|label| MplsLabel { 20 | label, 21 | traffic_class: 0, 22 | bottom_of_stack: true, 23 | ttl: 0, 24 | }) 25 | .unwrap_or_else(|_| { 26 | eprintln!("invalid MPLS input label"); 27 | std::process::exit(1); 28 | }); 29 | 30 | let gateway: IpNetwork = args[2].parse().unwrap_or_else(|_| { 31 | eprintln!("invalid gateway"); 32 | std::process::exit(1); 33 | }); 34 | 35 | let output_label = args[3] 36 | .parse::() 37 | .map(|label| MplsLabel { 38 | label, 39 | traffic_class: 0, 40 | bottom_of_stack: true, 41 | ttl: 0, 42 | }) 43 | .unwrap_or_else(|_| { 44 | eprintln!("invalid MPLS output label"); 45 | std::process::exit(1); 46 | }); 47 | 48 | let (connection, handle, _) = new_connection().unwrap(); 49 | tokio::spawn(connection); 50 | 51 | if let Err(e) = 52 | add_route_mpls(input_label, &gateway, output_label, handle.clone()) 53 | .await 54 | { 55 | eprintln!("{e}"); 56 | } else { 57 | println!("Route has been added"); 58 | } 59 | Ok(()) 60 | } 61 | 62 | async fn add_route_mpls( 63 | input_label: MplsLabel, 64 | gateway: &IpNetwork, 65 | output_label: MplsLabel, 66 | handle: Handle, 67 | ) -> Result<(), Error> { 68 | let route = RouteMessageBuilder::::new() 69 | .label(input_label) 70 | .via(gateway.ip().into()) 71 | .output_mpls(vec![output_label]) 72 | .build(); 73 | handle.route().add(route).execute().await?; 74 | Ok(()) 75 | } 76 | 77 | fn usage() { 78 | eprintln!( 79 | "\ 80 | usage: 81 | cargo run --example add_route_mpls -- 82 | 83 | Note that you need to run this program as root: 84 | 85 | env CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER='sudo -E' \\ 86 | cargo run --example add_route_mpls -- \ 87 | " 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /examples/add_route_pref_src.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::TryStreamExt; 4 | use std::{env, net::Ipv4Addr}; 5 | 6 | use ipnetwork::Ipv4Network; 7 | use rtnetlink::{new_connection, Error, Handle, RouteMessageBuilder}; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<(), ()> { 11 | let args: Vec = env::args().collect(); 12 | if args.len() != 4 { 13 | usage(); 14 | return Ok(()); 15 | } 16 | 17 | let dest: Ipv4Network = args[1].parse().unwrap_or_else(|_| { 18 | eprintln!("invalid destination"); 19 | std::process::exit(1); 20 | }); 21 | let iface: String = args[2].parse().unwrap_or_else(|_| { 22 | eprintln!("invalid interface"); 23 | std::process::exit(1); 24 | }); 25 | let source: Ipv4Addr = args[3].parse().unwrap_or_else(|_| { 26 | eprintln!("invalid source"); 27 | std::process::exit(1); 28 | }); 29 | 30 | let (connection, handle, _) = new_connection().unwrap(); 31 | tokio::spawn(connection); 32 | 33 | if let Err(e) = add_route(&dest, iface, source, handle.clone()).await { 34 | eprintln!("{e}"); 35 | } 36 | Ok(()) 37 | } 38 | 39 | async fn add_route( 40 | dest: &Ipv4Network, 41 | iface: String, 42 | source: Ipv4Addr, 43 | handle: Handle, 44 | ) -> Result<(), Error> { 45 | let iface_idx = handle 46 | .link() 47 | .get() 48 | .match_name(iface) 49 | .execute() 50 | .try_next() 51 | .await? 52 | .unwrap() 53 | .header 54 | .index; 55 | 56 | let route = RouteMessageBuilder::::new() 57 | .destination_prefix(dest.ip(), dest.prefix()) 58 | .output_interface(iface_idx) 59 | .pref_source(source) 60 | .build(); 61 | handle.route().add(route).execute().await?; 62 | Ok(()) 63 | } 64 | 65 | fn usage() { 66 | eprintln!( 67 | "usage: 68 | cargo run --example add_route_pref_src -- / 69 | 70 | Note that you need to run this program as root. Instead of running cargo as root, 71 | build the example normally: 72 | 73 | cd rtnetlink ; cargo build --example add_route_pref_src 74 | 75 | Then find the binary in the target directory: 76 | 77 | cd ../target/debug/example ; sudo ./add_route_pref_src / " 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /examples/add_rule.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::env; 4 | 5 | use ipnetwork::Ipv4Network; 6 | use rtnetlink::{new_connection, Error, Handle}; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<(), ()> { 10 | let args: Vec = env::args().collect(); 11 | if args.len() != 3 { 12 | usage(); 13 | return Ok(()); 14 | } 15 | 16 | let dst: Ipv4Network = args[1].parse().unwrap_or_else(|_| { 17 | eprintln!("invalid destination"); 18 | std::process::exit(1); 19 | }); 20 | 21 | let table_id: u32 = args[2].parse().unwrap_or_else(|_| { 22 | eprintln!("invalid route table ID"); 23 | std::process::exit(1); 24 | }); 25 | 26 | let (connection, handle, _) = new_connection().unwrap(); 27 | tokio::spawn(connection); 28 | 29 | if let Err(e) = add_rule(&dst, table_id, handle.clone()).await { 30 | eprintln!("{e}"); 31 | } else { 32 | println!("Route rule has been added for {dst} and lookup {table_id}") 33 | } 34 | Ok(()) 35 | } 36 | 37 | async fn add_rule( 38 | dst: &Ipv4Network, 39 | table_id: u32, 40 | handle: Handle, 41 | ) -> Result<(), Error> { 42 | let rule = handle.rule(); 43 | rule.add() 44 | .v4() 45 | .destination_prefix(dst.ip(), dst.prefix()) 46 | .table_id(table_id) 47 | .execute() 48 | .await?; 49 | 50 | Ok(()) 51 | } 52 | 53 | fn usage() { 54 | eprintln!( 55 | "\ 56 | usage: 57 | cargo run --example add_rule -- / 58 | 59 | Note that you need to run this program as root: 60 | 61 | env CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER='sudo -E' \\ 62 | cargo run --example add_rule -- / \ 63 | " 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /examples/add_tc_qdisc_ingress.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::env; 4 | 5 | use rtnetlink::new_connection; 6 | 7 | #[cfg(target_os = "freebsd")] 8 | fn main() -> () {} 9 | 10 | #[cfg(not(target_os = "freebsd"))] 11 | #[tokio::main] 12 | async fn main() -> Result<(), ()> { 13 | env_logger::init(); 14 | let args: Vec = env::args().collect(); 15 | if args.len() != 2 { 16 | usage(); 17 | return Ok(()); 18 | } 19 | 20 | let index: u32 = args[1].parse().unwrap_or_else(|_| { 21 | eprintln!("invalid index"); 22 | std::process::exit(1); 23 | }); 24 | 25 | let (connection, handle, _) = new_connection().unwrap(); 26 | tokio::spawn(connection); 27 | 28 | if let Err(e) = handle.qdisc().add(index as i32).ingress().execute().await { 29 | eprintln!("{e}"); 30 | } 31 | 32 | Ok(()) 33 | } 34 | 35 | fn usage() { 36 | eprintln!( 37 | "usage: 38 | cargo run --example add_tc_qdisc_ingress -- 39 | 40 | Note that you need to run this program as root. Instead of running cargo as root, 41 | build the example normally: 42 | 43 | cd rtnetlink ; cargo build --example add_tc_qdisc_ingress 44 | 45 | Then find the binary in the target directory: 46 | 47 | cd ../target/debug/example ; sudo ./add_tc_qdisc_ingress " 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /examples/create_bond.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::net::{Ipv4Addr, Ipv6Addr}; 4 | 5 | use rtnetlink::{new_connection, packet_route::link::BondMode, LinkBond}; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), String> { 9 | let (connection, handle, _) = new_connection().unwrap(); 10 | tokio::spawn(connection); 11 | 12 | let message = LinkBond::new("my-bond") 13 | .mode(BondMode::ActiveBackup) 14 | .miimon(100) 15 | .updelay(100) 16 | .downdelay(100) 17 | .min_links(2) 18 | .arp_ip_target(vec![ 19 | Ipv4Addr::new(6, 6, 7, 7), 20 | Ipv4Addr::new(8, 8, 9, 10), 21 | ]) 22 | .ns_ip6_target(vec![ 23 | Ipv6Addr::new(0xfd01, 0, 0, 0, 0, 0, 0, 1), 24 | Ipv6Addr::new(0xfd02, 0, 0, 0, 0, 0, 0, 2), 25 | ]) 26 | .up() 27 | .build(); 28 | 29 | handle 30 | .link() 31 | .add(message) 32 | .execute() 33 | .await 34 | .map_err(|e| format!("{e}")) 35 | } 36 | -------------------------------------------------------------------------------- /examples/create_bridge.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use rtnetlink::{new_connection, LinkBridge}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), String> { 7 | let (connection, handle, _) = new_connection().unwrap(); 8 | tokio::spawn(connection); 9 | 10 | handle 11 | .link() 12 | .add(LinkBridge::new("my-bridge").build()) 13 | .execute() 14 | .await 15 | .map_err(|e| format!("{e}")) 16 | } 17 | -------------------------------------------------------------------------------- /examples/create_dummy.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use rtnetlink::{new_connection, LinkDummy}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), String> { 7 | let (connection, handle, _) = new_connection().unwrap(); 8 | tokio::spawn(connection); 9 | 10 | handle 11 | .link() 12 | .add(LinkDummy::new("dummy0").build()) 13 | .execute() 14 | .await 15 | .map_err(|e| format!("{e}")) 16 | } 17 | -------------------------------------------------------------------------------- /examples/create_macvlan.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::{env, str::FromStr}; 4 | 5 | use futures::stream::TryStreamExt; 6 | use macaddr::MacAddr; 7 | use rtnetlink::{ 8 | new_connection, packet_route::link::MacVlanMode, Error, Handle, LinkMacVlan, 9 | }; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<(), String> { 13 | let args: Vec = env::args().collect(); 14 | if args.len() != 2 && args.len() != 3 { 15 | usage(); 16 | return Ok(()); 17 | } 18 | let link_name = &args[1]; 19 | let mac: Option> = if args.len() == 3 { 20 | let mac_address_arg = args[2].to_string(); 21 | let mac_address = MacAddr::from_str(mac_address_arg.as_str()) 22 | .map_err(|e| format!("{e}"))?; 23 | Some(mac_address.as_bytes().into()) 24 | } else { 25 | None 26 | }; 27 | 28 | let (connection, handle, _) = new_connection().unwrap(); 29 | tokio::spawn(connection); 30 | 31 | create_macvlan(handle, link_name.to_string(), mac) 32 | .await 33 | .map_err(|e| format!("{e}")) 34 | } 35 | 36 | async fn create_macvlan( 37 | handle: Handle, 38 | link_name: String, 39 | mac_address: Option>, 40 | ) -> Result<(), Error> { 41 | let mut parent_links = 42 | handle.link().get().match_name(link_name.clone()).execute(); 43 | if let Some(parent) = parent_links.try_next().await? { 44 | let mut builder = LinkMacVlan::new( 45 | "my-macvlan", 46 | parent.header.index, 47 | MacVlanMode::Bridge, 48 | ); 49 | if let Some(mac) = mac_address { 50 | builder = builder.address(mac); 51 | } 52 | let message = builder.build(); 53 | let request = handle.link().add(message); 54 | 55 | request.execute().await? 56 | } else { 57 | println!("no link {link_name} found"); 58 | } 59 | Ok(()) 60 | } 61 | 62 | fn usage() { 63 | eprintln!( 64 | "usage: 65 | cargo run --example create_macvlan -- [mac address] 66 | 67 | Note that you need to run this program as root. Instead of running cargo as root, 68 | build the example normally: 69 | 70 | cargo build --example create_macvlan 71 | 72 | Then find the binary in the target directory: 73 | 74 | cd target/debug/examples ; sudo ./create_macvlan [mac address]" 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /examples/create_macvtap.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::env; 4 | 5 | use futures::stream::TryStreamExt; 6 | use rtnetlink::{ 7 | new_connection, packet_route::link::MacVtapMode, Error, Handle, LinkMacVtap, 8 | }; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<(), String> { 12 | let args: Vec = env::args().collect(); 13 | if args.len() != 2 { 14 | usage(); 15 | return Ok(()); 16 | } 17 | let link_name = &args[1]; 18 | 19 | let (connection, handle, _) = new_connection().unwrap(); 20 | tokio::spawn(connection); 21 | 22 | create_macvtap(handle, link_name.to_string()) 23 | .await 24 | .map_err(|e| format!("{e}")) 25 | } 26 | 27 | async fn create_macvtap( 28 | handle: Handle, 29 | link_name: String, 30 | ) -> Result<(), Error> { 31 | let mut parent_links = 32 | handle.link().get().match_name(link_name.clone()).execute(); 33 | if let Some(parent) = parent_links.try_next().await? { 34 | let message = LinkMacVtap::new( 35 | "test_macvtap", 36 | parent.header.index, 37 | MacVtapMode::Bridge, 38 | ) 39 | .build(); 40 | 41 | let request = handle.link().add(message); 42 | request.execute().await? 43 | } else { 44 | println!("no link link {link_name} found"); 45 | } 46 | Ok(()) 47 | } 48 | 49 | fn usage() { 50 | eprintln!( 51 | "usage: 52 | cargo run --example create_macvtap -- 53 | 54 | Note that you need to run this program as root. Instead of running cargo as root, 55 | build the example normally: 56 | 57 | cd rtnetlink; cargo build --example create_macvtap 58 | 59 | Then find the binary in the target directory: 60 | 61 | cd ../target/debug/example ; sudo ./create_macvtap " 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /examples/create_veth.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use rtnetlink::{new_connection, LinkVeth}; 4 | #[tokio::main] 5 | async fn main() -> Result<(), String> { 6 | let (connection, handle, _) = new_connection().unwrap(); 7 | tokio::spawn(connection); 8 | 9 | handle 10 | .link() 11 | .add(LinkVeth::new("veth1", "veth1-peer").build()) 12 | .execute() 13 | .await 14 | .map_err(|e| format!("{e}")) 15 | } 16 | -------------------------------------------------------------------------------- /examples/create_vlan.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::{env, error::Error as StdError, str::FromStr}; 4 | 5 | use rtnetlink::{new_connection, LinkVlan, QosMapping}; 6 | 7 | fn parse_mapping(parameter: &str) -> Result> { 8 | let (from, to) = parameter 9 | .split_once(':') 10 | .ok_or("Failed to parse mapping..")?; 11 | 12 | Ok(QosMapping { 13 | from: u32::from_str(from)?, 14 | to: u32::from_str(to)?, 15 | }) 16 | } 17 | 18 | const ARG_BASE: &str = "--base"; 19 | const ARG_NAME: &str = "--name"; 20 | const ARG_ID: &str = "--id"; 21 | const ARG_INGRESS_QOS: &str = "--ingress-qos-mapping"; 22 | const ARG_EGRESS_QOS: &str = "--egress-qos-mapping"; 23 | 24 | enum ParsingMode { 25 | None, 26 | Base, 27 | Name, 28 | Id, 29 | Ingress, 30 | Egress, 31 | } 32 | 33 | #[tokio::main] 34 | async fn main() -> Result<(), String> { 35 | let mut args: Vec = env::args().collect(); 36 | 37 | let mut base_interface = None; 38 | let mut name = None; 39 | let mut id = None; 40 | let mut ingress = Vec::new(); 41 | let mut egress = Vec::new(); 42 | 43 | let mut mode = ParsingMode::None; 44 | for argument in args.drain(1..) { 45 | fn match_argument(argument: String) -> Result { 46 | match argument.to_lowercase().as_str() { 47 | ARG_BASE => Ok(ParsingMode::Base), 48 | ARG_NAME => Ok(ParsingMode::Name), 49 | ARG_ID => Ok(ParsingMode::Id), 50 | ARG_INGRESS_QOS => Ok(ParsingMode::Ingress), 51 | ARG_EGRESS_QOS => Ok(ParsingMode::Egress), 52 | other => { 53 | usage(); 54 | Err(format!("Unexpected argument: {other}")) 55 | } 56 | } 57 | } 58 | 59 | mode = match mode { 60 | ParsingMode::None => match_argument(argument)?, 61 | ParsingMode::Base => { 62 | base_interface = u32::from_str(&argument).ok(); 63 | ParsingMode::None 64 | } 65 | ParsingMode::Name => { 66 | name = Some(argument); 67 | ParsingMode::None 68 | } 69 | ParsingMode::Id => { 70 | id = u16::from_str(&argument).ok(); 71 | ParsingMode::None 72 | } 73 | mode @ ParsingMode::Ingress => match parse_mapping(&argument) { 74 | Ok(mapping) => { 75 | ingress.push(mapping); 76 | mode 77 | } 78 | Err(_) => match_argument(argument)?, 79 | }, 80 | mode @ ParsingMode::Egress => match parse_mapping(&argument) { 81 | Ok(mapping) => { 82 | egress.push(mapping); 83 | mode 84 | } 85 | Err(_) => match_argument(argument)?, 86 | }, 87 | } 88 | } 89 | 90 | let Some(base) = base_interface else { 91 | usage(); 92 | return Err( 93 | "Missing or invalid argument for base interface!".to_owned() 94 | ); 95 | }; 96 | 97 | let Some(name) = name else { 98 | usage(); 99 | return Err( 100 | "Missing or invalid argument for new interface name!".to_owned() 101 | ); 102 | }; 103 | 104 | let Some(id) = id else { 105 | usage(); 106 | return Err("Missing or invalid argument for vlan id!".to_owned()); 107 | }; 108 | 109 | let (connection, handle, _) = new_connection().unwrap(); 110 | tokio::spawn(connection); 111 | 112 | let message = LinkVlan::new(&name, base, id) 113 | .up() 114 | .qos(ingress, egress) 115 | .build(); 116 | 117 | handle 118 | .link() 119 | .add(message) 120 | .execute() 121 | .await 122 | .map_err(|err| format!("Netlink request failed: {err}")) 123 | } 124 | 125 | fn usage() { 126 | eprintln!( 127 | "usage: 128 | cargo run --example create_vlan -- --base --name --id [--ingress-qos-mapping : ..>] [--egress-qos-mapping : ..>] 129 | 130 | Note that you need to run this program as root. Instead of running cargo as root, 131 | build the example normally: 132 | 133 | cd netlink-ip ; cargo build --example create_vlan 134 | 135 | Then find the binary in the target directory: 136 | 137 | cd ../target/debug/example ; sudo ./create_vlan " 138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /examples/create_vrf.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use rtnetlink::{new_connection, LinkVrf}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), String> { 7 | let (connection, handle, _) = new_connection().unwrap(); 8 | tokio::spawn(connection); 9 | 10 | handle 11 | .link() 12 | .add(LinkVrf::new("my-vrf", 101).build()) 13 | .execute() 14 | .await 15 | .map_err(|e| format!("{e}")) 16 | } 17 | -------------------------------------------------------------------------------- /examples/create_vxlan.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{new_connection, Error, Handle, LinkVxlan}; 5 | use std::env; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), String> { 9 | let args: Vec = env::args().collect(); 10 | if args.len() != 2 { 11 | usage(); 12 | return Ok(()); 13 | } 14 | let link_name = &args[1]; 15 | 16 | let (connection, handle, _) = new_connection().unwrap(); 17 | tokio::spawn(connection); 18 | 19 | create_vxlan(handle, link_name.to_string()) 20 | .await 21 | .map_err(|e| format!("{e}")) 22 | } 23 | 24 | async fn create_vxlan(handle: Handle, name: String) -> Result<(), Error> { 25 | let mut links = handle.link().get().match_name(name.clone()).execute(); 26 | if let Some(link) = links.try_next().await? { 27 | let message = LinkVxlan::new("vxlan0", 10) 28 | .dev(link.header.index) 29 | .up() 30 | .port(4789) 31 | .build(); 32 | 33 | handle.link().add(message).execute().await? 34 | } else { 35 | println!("no link link {name} found"); 36 | } 37 | Ok(()) 38 | } 39 | 40 | fn usage() { 41 | eprintln!( 42 | "usage: 43 | cargo run --example create_vxlan -- 44 | 45 | Note that you need to run this program as root. Instead of running cargo as root, 46 | build the example normally: 47 | 48 | cd netlink-ip ; cargo build --example create_vxlan 49 | 50 | Then find the binary in the target directory: 51 | 52 | cd ../target/debug/example ; sudo ./create_vxlan " 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /examples/create_wireguard.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use rtnetlink::{new_connection, LinkWireguard}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), String> { 7 | let (connection, handle, _) = new_connection().unwrap(); 8 | tokio::spawn(connection); 9 | 10 | handle 11 | .link() 12 | .add(LinkWireguard::new("my-wg").build()) 13 | .execute() 14 | .await 15 | .map_err(|e| format!("{e}")) 16 | } 17 | -------------------------------------------------------------------------------- /examples/create_xfrm.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{new_connection, Error, Handle, LinkXfrm}; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), String> { 8 | let args: Vec = std::env::args().collect(); 9 | if args.len() != 2 { 10 | usage(); 11 | return Ok(()); 12 | } 13 | let link_name = &args[1]; 14 | 15 | let (connection, handle, _) = new_connection().unwrap(); 16 | tokio::spawn(connection); 17 | 18 | create_xfrm(handle, link_name.to_string()) 19 | .await 20 | .map_err(|e| format!("{e}")) 21 | } 22 | 23 | async fn create_xfrm(handle: Handle, link_name: String) -> Result<(), Error> { 24 | let mut parent_links = 25 | handle.link().get().match_name(link_name.clone()).execute(); 26 | if let Some(parent) = parent_links.try_next().await? { 27 | let request = handle 28 | .link() 29 | .add(LinkXfrm::new("my-xfrm", parent.header.index, 0x08).build()); 30 | 31 | request.execute().await? 32 | } else { 33 | println!("no link {link_name} found"); 34 | } 35 | Ok(()) 36 | } 37 | 38 | fn usage() { 39 | eprintln!( 40 | "usage: 41 | cargo run --example create_xfrm -- 42 | 43 | Note that you need to run this program as root. Instead of running cargo as 44 | root, build the example normally: 45 | 46 | cargo build --example create_xfrm 47 | 48 | Then find the binary in the target directory: 49 | 50 | cd target/debug/examples ; sudo ./create_xfrm " 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /examples/del_link.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{new_connection, Error, Handle}; 5 | use std::env; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), ()> { 9 | let args: Vec = env::args().collect(); 10 | if args.len() != 2 { 11 | usage(); 12 | return Ok(()); 13 | } 14 | let link_name = &args[1]; 15 | let (connection, handle, _) = new_connection().unwrap(); 16 | tokio::spawn(connection); 17 | 18 | if let Err(e) = del_link(handle, link_name.to_string()).await { 19 | eprintln!("{e}"); 20 | } 21 | 22 | Ok(()) 23 | } 24 | 25 | async fn del_link(handle: Handle, name: String) -> Result<(), Error> { 26 | let mut links = handle.link().get().match_name(name.clone()).execute(); 27 | if let Some(link) = links.try_next().await? { 28 | handle.link().del(link.header.index).execute().await 29 | } else { 30 | eprintln!("link {name} not found"); 31 | Ok(()) 32 | } 33 | } 34 | 35 | fn usage() { 36 | eprintln!( 37 | "usage: 38 | cargo run --example del_link -- 39 | 40 | Note that you need to run this program as root. Instead of running cargo as root, 41 | build the example normally: 42 | 43 | cd rtnetlink ; cargo build --example del_link 44 | 45 | Then find the binary in the target directory: 46 | 47 | cd ../target/debug/example ; sudo ./del_link " 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /examples/del_netns.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #[cfg(not(target_os = "freebsd"))] 4 | use rtnetlink::NetworkNamespace; 5 | use std::env; 6 | 7 | #[cfg(target_os = "freebsd")] 8 | fn main() -> () {} 9 | 10 | #[cfg(not(target_os = "freebsd"))] 11 | #[tokio::main] 12 | async fn main() -> Result<(), String> { 13 | let args: Vec = env::args().collect(); 14 | if args.len() != 2 { 15 | usage(); 16 | return Ok(()); 17 | } 18 | let ns_name = &args[1]; 19 | 20 | NetworkNamespace::del(ns_name.to_string()) 21 | .await 22 | .map_err(|e| format!("{e}")) 23 | } 24 | 25 | fn usage() { 26 | eprintln!( 27 | "usage: 28 | cargo run --example del_netns -- 29 | 30 | Note that you need to run this program as root. Instead of running cargo as root, 31 | build the example normally: 32 | 33 | cd netlink-ip ; cargo build --example del_netns 34 | 35 | Then find the binary in the target directory: 36 | 37 | cd ../target/debug/example ; sudo ./del_netns " 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /examples/del_netns_async.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #[cfg(not(target_os = "freebsd"))] 4 | use rtnetlink::NetworkNamespace; 5 | use std::env; 6 | 7 | #[cfg(target_os = "freebsd")] 8 | fn main() -> () {} 9 | 10 | #[cfg(not(target_os = "freebsd"))] 11 | #[async_std::main] 12 | async fn main() -> Result<(), String> { 13 | let args: Vec = env::args().collect(); 14 | if args.len() != 2 { 15 | usage(); 16 | return Ok(()); 17 | } 18 | let ns_name = &args[1]; 19 | 20 | NetworkNamespace::del(ns_name.to_string()) 21 | .await 22 | .map_err(|e| format!("{e}")) 23 | } 24 | 25 | fn usage() { 26 | eprintln!( 27 | "usage: 28 | cargo run --example del_netns -- 29 | 30 | Note that you need to run this program as root. Instead of running cargo as root, 31 | build the example normally: 32 | 33 | cd netlink-ip ; cargo build --example del_netns 34 | 35 | Then find the binary in the target directory: 36 | 37 | cd ../target/debug/example ; sudo ./del_netns " 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /examples/flush_addresses.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{new_connection, Error, Handle}; 5 | use std::env; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), ()> { 9 | let args: Vec = env::args().collect(); 10 | if args.len() != 2 { 11 | usage(); 12 | return Ok(()); 13 | } 14 | let link_name = &args[1]; 15 | 16 | let (connection, handle, _) = new_connection().unwrap(); 17 | tokio::spawn(connection); 18 | 19 | if let Err(e) = flush_addresses(handle, link_name.to_string()).await { 20 | eprintln!("{e}"); 21 | } 22 | 23 | Ok(()) 24 | } 25 | 26 | async fn flush_addresses(handle: Handle, link: String) -> Result<(), Error> { 27 | let mut links = handle.link().get().match_name(link.clone()).execute(); 28 | if let Some(link) = links.try_next().await? { 29 | // We should have received only one message 30 | assert!(links.try_next().await?.is_none()); 31 | 32 | let mut addresses = handle 33 | .address() 34 | .get() 35 | .set_link_index_filter(link.header.index) 36 | .execute(); 37 | while let Some(addr) = addresses.try_next().await? { 38 | handle.address().del(addr).execute().await?; 39 | } 40 | Ok(()) 41 | } else { 42 | eprintln!("link {link} not found"); 43 | Ok(()) 44 | } 45 | } 46 | 47 | fn usage() { 48 | eprintln!( 49 | "usage: 50 | cargo run --example flush_addresses -- 51 | 52 | Note that you need to run this program as root. Instead of running cargo as root, 53 | build the example normally: 54 | 55 | cd rtnetlink ; cargo build --example flush_addresses 56 | 57 | Then find the binary in the target directory: 58 | 59 | cd ../target/debug/example ; sudo ./flush_addresses " 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /examples/get_address.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{new_connection, Error, Handle}; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), ()> { 8 | let (connection, handle, _) = new_connection().unwrap(); 9 | tokio::spawn(connection); 10 | 11 | let link = "lo".to_string(); 12 | println!("dumping address for link \"{link}\""); 13 | 14 | if let Err(e) = dump_addresses(handle, link).await { 15 | eprintln!("{e}"); 16 | } 17 | 18 | Ok(()) 19 | } 20 | 21 | async fn dump_addresses(handle: Handle, link: String) -> Result<(), Error> { 22 | let mut links = handle.link().get().match_name(link.clone()).execute(); 23 | if let Some(link) = links.try_next().await? { 24 | let mut addresses = handle 25 | .address() 26 | .get() 27 | .set_link_index_filter(link.header.index) 28 | .execute(); 29 | while let Some(msg) = addresses.try_next().await? { 30 | println!("{msg:?}"); 31 | } 32 | Ok(()) 33 | } else { 34 | eprintln!("link {link} not found"); 35 | Ok(()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/get_bond_port_settings.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{ 5 | new_connection, packet_route::link::LinkAttribute, Error, Handle, 6 | }; 7 | use std::env; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<(), ()> { 11 | let args: Vec = env::args().collect(); 12 | if args.len() != 2 { 13 | usage(); 14 | return Ok(()); 15 | } 16 | let link_name = &args[1]; 17 | 18 | let (connection, handle, _) = new_connection().unwrap(); 19 | tokio::spawn(connection); 20 | 21 | let linkname = link_name.to_string(); 22 | println!("dumping bond port settings for link \"{linkname}\""); 23 | 24 | if let Err(e) = dump_bond_port_settings(handle, linkname).await { 25 | eprintln!("{e}"); 26 | } 27 | 28 | Ok(()) 29 | } 30 | 31 | async fn dump_bond_port_settings( 32 | handle: Handle, 33 | linkname: String, 34 | ) -> Result<(), Error> { 35 | let mut links = handle.link().get().match_name(linkname.clone()).execute(); 36 | if let Some(_link) = links.try_next().await? { 37 | let mut link_messgage = 38 | handle.link().get().match_name(linkname).execute(); 39 | while let Some(msg) = link_messgage.try_next().await? { 40 | for nla in msg.attributes { 41 | if let LinkAttribute::LinkInfo(i) = &nla { 42 | println!("{:?}", i); 43 | } 44 | } 45 | } 46 | Ok(()) 47 | } else { 48 | eprintln!("link {linkname} not found"); 49 | Ok(()) 50 | } 51 | } 52 | 53 | fn usage() { 54 | eprintln!( 55 | "usage: 56 | cargo run --example get_bond_port_settings -- 57 | 58 | Note that you need to run this program as root. Instead of running cargo as root, 59 | build the example normally: 60 | 61 | cd netlink-ip ; cargo build --example get_bond_port_settings 62 | 63 | Then find the binary in the target directory: 64 | 65 | cd ../target/debug/example ; sudo ./get_bond_port_settings " 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /examples/get_links.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use netlink_packet_route::{ 5 | link::{LinkAttribute, LinkExtentMask}, 6 | AddressFamily, 7 | }; 8 | use rtnetlink::{new_connection, Error, Handle}; 9 | 10 | #[cfg(target_os = "freebsd")] 11 | fn main() -> () {} 12 | 13 | #[cfg(not(target_os = "freebsd"))] 14 | #[tokio::main] 15 | async fn main() -> Result<(), ()> { 16 | env_logger::init(); 17 | let (connection, handle, _) = new_connection().unwrap(); 18 | tokio::spawn(connection); 19 | 20 | // Fetch a link by its index 21 | let index = 1; 22 | println!("*** retrieving link with index {index} ***"); 23 | if let Err(e) = get_link_by_index(handle.clone(), index).await { 24 | eprintln!("{e}"); 25 | } 26 | 27 | // Fetch a link by its name 28 | let name = "lo"; 29 | println!("*** retrieving link named \"{name}\" ***"); 30 | if let Err(e) = get_link_by_name(handle.clone(), name.to_string()).await { 31 | eprintln!("{e}"); 32 | } 33 | 34 | // Dump all the links and print their index and name 35 | println!("*** dumping links ***"); 36 | if let Err(e) = dump_links(handle.clone()).await { 37 | eprintln!("{e}"); 38 | } 39 | 40 | // Dump all the bridge vlan information 41 | if let Err(e) = dump_bridge_filter_info(handle.clone()).await { 42 | eprintln!("{e}"); 43 | } 44 | 45 | Ok(()) 46 | } 47 | 48 | async fn get_link_by_index(handle: Handle, index: u32) -> Result<(), Error> { 49 | let mut links = handle.link().get().match_index(index).execute(); 50 | let msg = if let Some(msg) = links.try_next().await? { 51 | msg 52 | } else { 53 | eprintln!("no link with index {index} found"); 54 | return Ok(()); 55 | }; 56 | // We should have received only one message 57 | assert!(links.try_next().await?.is_none()); 58 | 59 | for nla in msg.attributes.into_iter() { 60 | if let LinkAttribute::IfName(name) = nla { 61 | println!("found link with index {index} (name = {name})"); 62 | return Ok(()); 63 | } 64 | } 65 | eprintln!( 66 | "found link with index {index}, but this link does not have a name" 67 | ); 68 | Ok(()) 69 | } 70 | 71 | async fn get_link_by_name(handle: Handle, name: String) -> Result<(), Error> { 72 | let mut links = handle.link().get().match_name(name.clone()).execute(); 73 | if (links.try_next().await?).is_some() { 74 | println!("found link {name}"); 75 | // We should only have one link with that name 76 | assert!(links.try_next().await?.is_none()); 77 | } else { 78 | println!("no link link {name} found"); 79 | } 80 | Ok(()) 81 | } 82 | 83 | async fn dump_links(handle: Handle) -> Result<(), Error> { 84 | let mut links = handle.link().get().execute(); 85 | 'outer: while let Some(msg) = links.try_next().await? { 86 | for nla in msg.attributes.into_iter() { 87 | if let LinkAttribute::IfName(name) = nla { 88 | println!("found link {} ({})", msg.header.index, name); 89 | continue 'outer; 90 | } 91 | } 92 | eprintln!("found link {}, but the link has no name", msg.header.index); 93 | } 94 | Ok(()) 95 | } 96 | 97 | #[cfg(not(target_os = "freebsd"))] 98 | async fn dump_bridge_filter_info(handle: Handle) -> Result<(), Error> { 99 | let mut links = handle 100 | .link() 101 | .get() 102 | .set_filter_mask(AddressFamily::Bridge, vec![LinkExtentMask::Brvlan]) 103 | .execute(); 104 | 'outer: while let Some(msg) = links.try_next().await? { 105 | for nla in msg.attributes.into_iter() { 106 | if let LinkAttribute::AfSpecBridge(data) = nla { 107 | println!( 108 | "found interface {} with AfSpecBridge data {:?})", 109 | msg.header.index, data 110 | ); 111 | continue 'outer; 112 | } 113 | } 114 | } 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /examples/get_links_async.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use netlink_packet_route::{ 5 | link::{LinkAttribute, LinkExtentMask}, 6 | AddressFamily, 7 | }; 8 | use rtnetlink::{new_connection, Error, Handle}; 9 | 10 | #[cfg(target_os = "freebsd")] 11 | fn main() -> () {} 12 | 13 | #[cfg(not(target_os = "freebsd"))] 14 | #[async_std::main] 15 | async fn main() -> Result<(), ()> { 16 | env_logger::init(); 17 | let (connection, handle, _) = new_connection().unwrap(); 18 | async_std::task::spawn(connection); 19 | 20 | // Fetch a link by its index 21 | let index = 1; 22 | println!("*** retrieving link with index {index} ***"); 23 | if let Err(e) = get_link_by_index(handle.clone(), index).await { 24 | eprintln!("{e}"); 25 | } 26 | 27 | // Fetch a link by its name 28 | let name = "lo"; 29 | println!("*** retrieving link named \"{name}\" ***"); 30 | if let Err(e) = get_link_by_name(handle.clone(), name.to_string()).await { 31 | eprintln!("{e}"); 32 | } 33 | 34 | // Dump all the links and print their index and name 35 | println!("*** dumping links ***"); 36 | if let Err(e) = dump_links(handle.clone()).await { 37 | eprintln!("{e}"); 38 | } 39 | 40 | // Dump all the bridge vlan information 41 | if let Err(e) = dump_bridge_filter_info(handle.clone()).await { 42 | eprintln!("{e}"); 43 | } 44 | 45 | Ok(()) 46 | } 47 | 48 | async fn get_link_by_index(handle: Handle, index: u32) -> Result<(), Error> { 49 | let mut links = handle.link().get().match_index(index).execute(); 50 | let msg = if let Some(msg) = links.try_next().await? { 51 | msg 52 | } else { 53 | eprintln!("no link with index {index} found"); 54 | return Ok(()); 55 | }; 56 | // We should have received only one message 57 | assert!(links.try_next().await?.is_none()); 58 | 59 | for nla in msg.attributes.into_iter() { 60 | if let LinkAttribute::IfName(name) = nla { 61 | println!("found link with index {index} (name = {name})"); 62 | return Ok(()); 63 | } 64 | } 65 | eprintln!( 66 | "found link with index {index}, but this link does not have a name" 67 | ); 68 | Ok(()) 69 | } 70 | 71 | async fn get_link_by_name(handle: Handle, name: String) -> Result<(), Error> { 72 | let mut links = handle.link().get().match_name(name.clone()).execute(); 73 | if (links.try_next().await?).is_some() { 74 | println!("found link {name}"); 75 | // We should only have one link with that name 76 | assert!(links.try_next().await?.is_none()); 77 | } else { 78 | println!("no link link {name} found"); 79 | } 80 | Ok(()) 81 | } 82 | 83 | async fn dump_links(handle: Handle) -> Result<(), Error> { 84 | let mut links = handle.link().get().execute(); 85 | 'outer: while let Some(msg) = links.try_next().await? { 86 | for nla in msg.attributes.into_iter() { 87 | if let LinkAttribute::IfName(name) = nla { 88 | println!("found link {} ({})", msg.header.index, name); 89 | continue 'outer; 90 | } 91 | } 92 | eprintln!("found link {}, but the link has no name", msg.header.index); 93 | } 94 | Ok(()) 95 | } 96 | 97 | #[cfg(not(target_os = "freebsd"))] 98 | async fn dump_bridge_filter_info(handle: Handle) -> Result<(), Error> { 99 | let mut links = handle 100 | .link() 101 | .get() 102 | .set_filter_mask(AddressFamily::Bridge, vec![LinkExtentMask::Brvlan]) 103 | .execute(); 104 | 'outer: while let Some(msg) = links.try_next().await? { 105 | for nla in msg.attributes.into_iter() { 106 | if let LinkAttribute::AfSpecBridge(data) = nla { 107 | println!( 108 | "found interface {} with AfSpecBridge data {:?})", 109 | msg.header.index, data 110 | ); 111 | continue 'outer; 112 | } 113 | } 114 | } 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /examples/get_links_thread_builder.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use netlink_packet_route::{ 5 | link::{LinkAttribute, LinkExtentMask}, 6 | AddressFamily, 7 | }; 8 | #[cfg(not(target_os = "freebsd"))] 9 | use rtnetlink::new_connection; 10 | use rtnetlink::{Error, Handle}; 11 | 12 | #[cfg(not(target_os = "freebsd"))] 13 | async fn do_it(rt: &tokio::runtime::Runtime) -> Result<(), ()> { 14 | env_logger::init(); 15 | let (connection, handle, _) = new_connection().unwrap(); 16 | rt.spawn(connection); 17 | 18 | // Fetch a link by its index 19 | let index = 1; 20 | println!("*** retrieving link with index {index} ***"); 21 | if let Err(e) = get_link_by_index(handle.clone(), index).await { 22 | eprintln!("{e}"); 23 | } 24 | 25 | // Fetch a link by its name 26 | let name = "lo"; 27 | println!("*** retrieving link named \"{name}\" ***"); 28 | if let Err(e) = get_link_by_name(handle.clone(), name.to_string()).await { 29 | eprintln!("{e}"); 30 | } 31 | 32 | // Dump all the links and print their index and name 33 | println!("*** dumping links ***"); 34 | if let Err(e) = dump_links(handle.clone()).await { 35 | eprintln!("{e}"); 36 | } 37 | 38 | // Dump all the bridge vlan information 39 | if let Err(e) = dump_bridge_filter_info(handle.clone()).await { 40 | eprintln!("{e}"); 41 | } 42 | 43 | Ok(()) 44 | } 45 | 46 | async fn get_link_by_index(handle: Handle, index: u32) -> Result<(), Error> { 47 | let mut links = handle.link().get().match_index(index).execute(); 48 | let msg = if let Some(msg) = links.try_next().await? { 49 | msg 50 | } else { 51 | eprintln!("no link with index {index} found"); 52 | return Ok(()); 53 | }; 54 | // We should have received only one message 55 | assert!(links.try_next().await?.is_none()); 56 | 57 | for nla in msg.attributes.into_iter() { 58 | if let LinkAttribute::IfName(name) = nla { 59 | println!("found link with index {index} (name = {name})"); 60 | return Ok(()); 61 | } 62 | } 63 | eprintln!( 64 | "found link with index {index}, but this link does not have a name" 65 | ); 66 | Ok(()) 67 | } 68 | 69 | async fn get_link_by_name(handle: Handle, name: String) -> Result<(), Error> { 70 | let mut links = handle.link().get().match_name(name.clone()).execute(); 71 | if (links.try_next().await?).is_some() { 72 | println!("found link {name}"); 73 | // We should only have one link with that name 74 | assert!(links.try_next().await?.is_none()); 75 | } else { 76 | println!("no link link {name} found"); 77 | } 78 | Ok(()) 79 | } 80 | 81 | async fn dump_links(handle: Handle) -> Result<(), Error> { 82 | let mut links = handle.link().get().execute(); 83 | 'outer: while let Some(msg) = links.try_next().await? { 84 | for nla in msg.attributes.into_iter() { 85 | if let LinkAttribute::IfName(name) = nla { 86 | println!("found link {} ({})", msg.header.index, name); 87 | continue 'outer; 88 | } 89 | } 90 | eprintln!("found link {}, but the link has no name", msg.header.index); 91 | } 92 | Ok(()) 93 | } 94 | 95 | #[cfg(not(target_os = "freebsd"))] 96 | async fn dump_bridge_filter_info(handle: Handle) -> Result<(), Error> { 97 | let mut links = handle 98 | .link() 99 | .get() 100 | .set_filter_mask(AddressFamily::Bridge, vec![LinkExtentMask::Brvlan]) 101 | .execute(); 102 | 'outer: while let Some(msg) = links.try_next().await? { 103 | for nla in msg.attributes.into_iter() { 104 | if let LinkAttribute::AfSpecBridge(data) = nla { 105 | println!( 106 | "found interface {} with AfSpecBridge data {:?})", 107 | msg.header.index, data 108 | ); 109 | continue 'outer; 110 | } 111 | } 112 | } 113 | Ok(()) 114 | } 115 | 116 | #[cfg(target_os = "freebsd")] 117 | fn main() -> () {} 118 | 119 | #[cfg(not(target_os = "freebsd"))] 120 | fn main() -> Result<(), String> { 121 | let rt = tokio::runtime::Builder::new_multi_thread() 122 | .enable_io() 123 | .build() 124 | .unwrap(); 125 | 126 | let future = do_it(&rt); 127 | println!("blocking in main"); 128 | rt.handle().block_on(future).unwrap(); 129 | Ok(()) 130 | } 131 | -------------------------------------------------------------------------------- /examples/get_neighbours.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{new_connection, Error, Handle, IpVersion}; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), ()> { 8 | let (connection, handle, _) = new_connection().unwrap(); 9 | tokio::spawn(connection); 10 | 11 | println!("dumping neighbours"); 12 | if let Err(e) = dump_neighbours(handle.clone()).await { 13 | eprintln!("{e}"); 14 | } 15 | println!(); 16 | 17 | Ok(()) 18 | } 19 | 20 | async fn dump_neighbours(handle: Handle) -> Result<(), Error> { 21 | let mut neighbours = handle 22 | .neighbours() 23 | .get() 24 | .set_family(IpVersion::V4) 25 | .execute(); 26 | while let Some(route) = neighbours.try_next().await? { 27 | println!("{route:?}"); 28 | } 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /examples/get_route.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::net::{Ipv4Addr, Ipv6Addr}; 4 | 5 | use futures::stream::TryStreamExt; 6 | use rtnetlink::{ 7 | new_connection, Error, Handle, IpVersion, RouteMessageBuilder, 8 | }; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<(), ()> { 12 | let (connection, handle, _) = new_connection().unwrap(); 13 | tokio::spawn(connection); 14 | 15 | println!("dumping routes for IPv4"); 16 | if let Err(e) = dump_addresses(handle.clone(), IpVersion::V4).await { 17 | eprintln!("{e}"); 18 | } 19 | println!(); 20 | 21 | println!("dumping routes for IPv6"); 22 | if let Err(e) = dump_addresses(handle.clone(), IpVersion::V6).await { 23 | eprintln!("{e}"); 24 | } 25 | println!(); 26 | 27 | Ok(()) 28 | } 29 | 30 | async fn dump_addresses( 31 | handle: Handle, 32 | ip_version: IpVersion, 33 | ) -> Result<(), Error> { 34 | let route = match ip_version { 35 | IpVersion::V4 => RouteMessageBuilder::::new().build(), 36 | IpVersion::V6 => RouteMessageBuilder::::new().build(), 37 | }; 38 | let mut routes = handle.route().get(route).execute(); 39 | while let Some(route) = routes.try_next().await? { 40 | println!("{route:?}"); 41 | } 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /examples/get_route_kernel_filter.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::net::Ipv4Addr; 4 | 5 | use futures::stream::TryStreamExt; 6 | use rtnetlink::{ 7 | new_connection, 8 | packet_route::route::{RouteProtocol, RouteScope, RouteType}, 9 | sys::AsyncSocket, 10 | RouteMessageBuilder, 11 | }; 12 | 13 | /// Dump IPv4 unicast routes with protocol boot(e.g. route created by ip 14 | /// route)on table 254 only 15 | #[tokio::main] 16 | async fn main() -> Result<(), Box> { 17 | let (mut connection, handle, _) = new_connection().unwrap(); 18 | 19 | connection 20 | .socket_mut() 21 | .socket_mut() 22 | .set_netlink_get_strict_chk(true)?; 23 | 24 | tokio::spawn(connection); 25 | 26 | println!("dumping routes for IPv4 in table 254"); 27 | let route = RouteMessageBuilder::::new() 28 | .table_id(254) 29 | .protocol(RouteProtocol::Boot) 30 | .scope(RouteScope::Universe) 31 | .kind(RouteType::Unicast) 32 | .build(); 33 | let mut routes = handle.route().get(route).execute(); 34 | while let Some(route) = routes.try_next().await? { 35 | println!("{route:?}"); 36 | } 37 | 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /examples/get_rule.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{new_connection, Error, Handle, IpVersion}; 5 | 6 | #[tokio::main] 7 | async fn main() -> Result<(), ()> { 8 | let (connection, handle, _) = new_connection().unwrap(); 9 | tokio::spawn(connection); 10 | 11 | println!("dumping rules for IPv4"); 12 | if let Err(e) = dump_addresses(handle.clone(), IpVersion::V4).await { 13 | eprintln!("{e}"); 14 | } 15 | println!(); 16 | 17 | println!("dumping rules for IPv6"); 18 | if let Err(e) = dump_addresses(handle.clone(), IpVersion::V6).await { 19 | eprintln!("{e}"); 20 | } 21 | println!(); 22 | 23 | Ok(()) 24 | } 25 | 26 | async fn dump_addresses( 27 | handle: Handle, 28 | ip_version: IpVersion, 29 | ) -> Result<(), Error> { 30 | let mut rules = handle.rule().get(ip_version).execute(); 31 | while let Some(rule) = rules.try_next().await? { 32 | println!("{rule:?}"); 33 | } 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /examples/ip_monitor.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | 5 | use netlink_sys::{AsyncSocket, SocketAddr}; 6 | use rtnetlink::new_connection; 7 | 8 | const RTNLGRP_LINK: u32 = 1; 9 | const RTNLGRP_NEIGH: u32 = 3; 10 | const RTNLGRP_IPV4_IFADDR: u32 = 5; 11 | const RTNLGRP_IPV4_MROUTE: u32 = 6; 12 | const RTNLGRP_IPV4_ROUTE: u32 = 7; 13 | const RTNLGRP_IPV4_RULE: u32 = 8; 14 | const RTNLGRP_IPV6_IFADDR: u32 = 9; 15 | const RTNLGRP_IPV6_MROUTE: u32 = 10; 16 | const RTNLGRP_IPV6_ROUTE: u32 = 11; 17 | const RTNLGRP_IPV6_RULE: u32 = 19; 18 | const RTNLGRP_IPV4_NETCONF: u32 = 24; 19 | const RTNLGRP_IPV6_NETCONF: u32 = 25; 20 | const RTNLGRP_MPLS_ROUTE: u32 = 27; 21 | const RTNLGRP_NSID: u32 = 28; 22 | const RTNLGRP_MPLS_NETCONF: u32 = 29; 23 | 24 | const fn nl_mgrp(group: u32) -> u32 { 25 | if group > 31 { 26 | panic!("use netlink_sys::Socket::add_membership() for this group"); 27 | } 28 | if group == 0 { 29 | 0 30 | } else { 31 | 1 << (group - 1) 32 | } 33 | } 34 | #[tokio::main] 35 | async fn main() -> Result<(), String> { 36 | // conn - `Connection` that has a netlink socket which is a `Future` that 37 | // polls the socket and thus must have an event loop 38 | // 39 | // handle - `Handle` to the `Connection`. Used to send/recv netlink 40 | // messages. 41 | // 42 | // messages - A channel receiver. 43 | let (mut conn, mut _handle, mut messages) = 44 | new_connection().map_err(|e| format!("{e}"))?; 45 | 46 | // These flags specify what kinds of broadcast messages we want to listen 47 | // for. 48 | let groups = nl_mgrp(RTNLGRP_LINK) 49 | | nl_mgrp(RTNLGRP_IPV4_IFADDR) 50 | | nl_mgrp(RTNLGRP_IPV6_IFADDR) 51 | | nl_mgrp(RTNLGRP_IPV4_ROUTE) 52 | | nl_mgrp(RTNLGRP_IPV6_ROUTE) 53 | | nl_mgrp(RTNLGRP_MPLS_ROUTE) 54 | | nl_mgrp(RTNLGRP_IPV4_MROUTE) 55 | | nl_mgrp(RTNLGRP_IPV6_MROUTE) 56 | | nl_mgrp(RTNLGRP_NEIGH) 57 | | nl_mgrp(RTNLGRP_IPV4_NETCONF) 58 | | nl_mgrp(RTNLGRP_IPV6_NETCONF) 59 | | nl_mgrp(RTNLGRP_IPV4_RULE) 60 | | nl_mgrp(RTNLGRP_IPV6_RULE) 61 | | nl_mgrp(RTNLGRP_NSID) 62 | | nl_mgrp(RTNLGRP_MPLS_NETCONF); 63 | 64 | let addr = SocketAddr::new(0, groups); 65 | conn.socket_mut() 66 | .socket_mut() 67 | .bind(&addr) 68 | .expect("Failed to bind"); 69 | 70 | // Spawn `Connection` to start polling netlink socket. 71 | tokio::spawn(conn); 72 | 73 | // Use `Handle` to send request to kernel to start multicasting rtnetlink 74 | // events. 75 | tokio::spawn(async move { 76 | // Create message to enable 77 | }); 78 | 79 | // Start receiving events through `messages` channel. 80 | while let Some((message, _)) = messages.next().await { 81 | let payload = message.payload; 82 | println!("{payload:?}"); 83 | } 84 | Ok(()) 85 | } 86 | -------------------------------------------------------------------------------- /examples/listen.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | //! This example opens a netlink socket, registers for IPv4 and IPv6 routing 4 | //! changes, listens for said changes and prints the received messages. 5 | 6 | use futures::stream::StreamExt; 7 | use netlink_sys::{AsyncSocket, SocketAddr}; 8 | use rtnetlink::{ 9 | constants::{ 10 | RTMGRP_IPV4_IFADDR, RTMGRP_IPV4_ROUTE, RTMGRP_IPV6_IFADDR, 11 | RTMGRP_IPV6_ROUTE, RTMGRP_LINK, 12 | }, 13 | new_connection, 14 | }; 15 | 16 | #[tokio::main] 17 | async fn main() -> Result<(), String> { 18 | // Open the netlink socket 19 | let (mut connection, _, mut messages) = 20 | new_connection().map_err(|e| format!("{e}"))?; 21 | 22 | // These flags specify what kinds of broadcast messages we want to listen 23 | // for. 24 | let mgroup_flags = RTMGRP_LINK 25 | | RTMGRP_IPV4_IFADDR 26 | | RTMGRP_IPV4_ROUTE 27 | | RTMGRP_IPV6_IFADDR 28 | | RTMGRP_IPV6_ROUTE; 29 | 30 | // A netlink socket address is created with said flags. 31 | let addr = SocketAddr::new(0, mgroup_flags); 32 | // Said address is bound so new conenctions and thus new message broadcasts 33 | // can be received. 34 | connection 35 | .socket_mut() 36 | .socket_mut() 37 | .bind(&addr) 38 | .expect("failed to bind"); 39 | tokio::spawn(connection); 40 | 41 | while let Some((message, _)) = messages.next().await { 42 | let payload = message.payload; 43 | println!("Route change message - {payload:?}"); 44 | } 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /examples/property_altname.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use netlink_packet_route::link::{LinkAttribute, LinkMessage, Prop}; 5 | use rtnetlink::{new_connection, Error, Handle}; 6 | use std::env; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<(), ()> { 10 | let args: Vec = env::args().collect(); 11 | if args.len() < 3 { 12 | usage(); 13 | return Ok(()); 14 | } 15 | 16 | let link_name = &args[1]; 17 | let action = &args[2]; 18 | let alt_ifnames: Vec<&str> = args[3..].iter().map(String::as_str).collect(); 19 | 20 | let (connection, handle, _) = new_connection().unwrap(); 21 | tokio::spawn(connection); 22 | 23 | match action.as_str() { 24 | "add" => { 25 | if let Err(e) = add_property_alt_ifnames( 26 | link_name, 27 | alt_ifnames.clone(), 28 | handle.clone(), 29 | ) 30 | .await 31 | { 32 | eprintln!("{e}"); 33 | } 34 | } 35 | 36 | "del" => { 37 | if let Err(e) = del_property_alt_ifnames( 38 | link_name, 39 | alt_ifnames.clone(), 40 | handle.clone(), 41 | ) 42 | .await 43 | { 44 | eprintln!("{e}"); 45 | } 46 | } 47 | 48 | "show" => { 49 | if let Err(e) = 50 | show_property_alt_ifnames(link_name, handle.clone()).await 51 | { 52 | eprintln!("{e}"); 53 | } 54 | } 55 | 56 | _ => panic!("Unknown action {:?}", action), 57 | } 58 | 59 | Ok(()) 60 | } 61 | 62 | async fn show_property_alt_ifnames( 63 | link_name: &str, 64 | handle: Handle, 65 | ) -> Result<(), Error> { 66 | for nla in get_link(link_name, handle).await?.attributes.into_iter() { 67 | if let LinkAttribute::PropList(ref prop_list) = nla { 68 | for prop in prop_list { 69 | if let Prop::AltIfName(altname) = prop { 70 | println!("altname: {altname}"); 71 | } 72 | } 73 | } 74 | } 75 | 76 | Ok(()) 77 | } 78 | 79 | async fn add_property_alt_ifnames( 80 | link_name: &str, 81 | alt_ifnames: Vec<&str>, 82 | handle: Handle, 83 | ) -> Result<(), Error> { 84 | let link_index = get_link_index(link_name, handle.clone()).await?; 85 | 86 | handle 87 | .link() 88 | .property_add(link_index) 89 | .alt_ifname(&alt_ifnames) 90 | .execute() 91 | .await?; 92 | 93 | Ok(()) 94 | } 95 | 96 | async fn del_property_alt_ifnames( 97 | link_name: &str, 98 | alt_ifnames: Vec<&str>, 99 | handle: Handle, 100 | ) -> Result<(), Error> { 101 | let link_index = get_link_index(link_name, handle.clone()).await?; 102 | 103 | handle 104 | .link() 105 | .property_del(link_index) 106 | .alt_ifname(&alt_ifnames) 107 | .execute() 108 | .await?; 109 | 110 | Ok(()) 111 | } 112 | 113 | async fn get_link( 114 | link_name: &str, 115 | handle: Handle, 116 | ) -> Result { 117 | let mut links = handle 118 | .link() 119 | .get() 120 | .match_name(link_name.to_string()) 121 | .execute(); 122 | 123 | match links.try_next().await? { 124 | Some(msg) => Ok(msg), 125 | _ => { 126 | eprintln!("Interface {link_name} not found"); 127 | Err(Error::RequestFailed) 128 | } 129 | } 130 | } 131 | 132 | async fn get_link_index(link_name: &str, handle: Handle) -> Result { 133 | Ok(get_link(link_name, handle.clone()).await?.header.index) 134 | } 135 | 136 | fn usage() { 137 | eprintln!( 138 | "usage: 139 | cargo run --example property_altname -- [add | del | show] ALTNAME [ALTNAME ...] 140 | 141 | Note that you need to run this program as root for add and del. Instead of running cargo as root, 142 | build the example normally: 143 | 144 | cd rtnetlink ; cargo build --example property_altname 145 | 146 | Then find the binary in the target directory: 147 | 148 | cd ../target/debug/example ; sudo ./property_altname " 149 | ); 150 | } 151 | -------------------------------------------------------------------------------- /examples/set_bond_port.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{ 5 | new_connection, 6 | packet_route::link::{BondMode, LinkAttribute}, 7 | Handle, LinkBond, LinkBondPort, LinkDummy, LinkUnspec, 8 | }; 9 | 10 | async fn create_bond_and_get_index(handle: &Handle) -> Result { 11 | handle 12 | .link() 13 | .add( 14 | LinkBond::new("my-bond0") 15 | .mode(BondMode::ActiveBackup) 16 | .up() 17 | .build(), 18 | ) 19 | .execute() 20 | .await 21 | .map_err(|e| format!("{e}"))?; 22 | 23 | let mut bond_links = handle 24 | .link() 25 | .get() 26 | .match_name("my-bond0".to_string()) 27 | .execute(); 28 | if let Some(bond_link) = 29 | bond_links.try_next().await.map_err(|e| format!("{e}"))? 30 | { 31 | Ok(bond_link.header.index) 32 | } else { 33 | Err("failed to find my-bond0".into()) 34 | } 35 | } 36 | 37 | async fn create_dummy_and_attach_to_bond( 38 | handle: &Handle, 39 | bond_index: u32, 40 | ) -> Result { 41 | handle 42 | .link() 43 | .add(LinkDummy::new("my-dummy0").build()) 44 | .execute() 45 | .await 46 | .map_err(|e| format!("{e}"))?; 47 | 48 | handle 49 | .link() 50 | .set( 51 | LinkUnspec::new_with_name("my-dummy0") 52 | .controller(bond_index) 53 | .down() 54 | .build(), 55 | ) 56 | .execute() 57 | .await 58 | .map_err(|e| format!("{e}"))?; 59 | 60 | let mut dummy_links = handle 61 | .link() 62 | .get() 63 | .match_name("my-dummy0".to_string()) 64 | .execute(); 65 | if let Some(dummy_link) = 66 | dummy_links.try_next().await.map_err(|e| format!("{e}"))? 67 | { 68 | Ok(dummy_link.header.index) 69 | } else { 70 | Err("failed to find my-bond0".into()) 71 | } 72 | } 73 | 74 | async fn set_bond_port(handle: &Handle, port_index: u32) -> Result<(), String> { 75 | let message = LinkBondPort::new(port_index).queue_id(1).prio(2).build(); 76 | 77 | handle 78 | .link() 79 | .set_port(message) 80 | .execute() 81 | .await 82 | .map_err(|e| format!("{e}"))?; 83 | Ok(()) 84 | } 85 | 86 | #[tokio::main] 87 | async fn main() -> Result<(), String> { 88 | let (connection, handle, _) = new_connection().unwrap(); 89 | tokio::spawn(connection); 90 | 91 | let bond_index = create_bond_and_get_index(&handle).await?; 92 | 93 | let port_index = 94 | create_dummy_and_attach_to_bond(&handle, bond_index).await?; 95 | set_bond_port(&handle, port_index) 96 | .await 97 | .map_err(|e| e.to_string())?; 98 | 99 | let mut dummy_links = handle 100 | .link() 101 | .get() 102 | .match_name("my-dummy0".to_string()) 103 | .execute(); 104 | if let Some(dummy_link) = 105 | dummy_links.try_next().await.map_err(|e| format!("{e}"))? 106 | { 107 | for nla in dummy_link.attributes { 108 | if let LinkAttribute::LinkInfo(i) = &nla { 109 | println!("{:?}", i); 110 | } 111 | } 112 | Ok(()) 113 | } else { 114 | Err("failed to find my-bond0".into()) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /examples/set_link_down.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use rtnetlink::{new_connection, Error, Handle, LinkUnspec}; 5 | use std::env; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), String> { 9 | let args: Vec = env::args().collect(); 10 | if args.len() != 2 { 11 | usage(); 12 | return Ok(()); 13 | } 14 | let link_name = &args[1]; 15 | 16 | let (connection, handle, _) = new_connection().unwrap(); 17 | tokio::spawn(connection); 18 | 19 | set_link_down(handle, link_name.to_string()) 20 | .await 21 | .map_err(|e| format!("{e}")) 22 | } 23 | 24 | async fn set_link_down(handle: Handle, name: String) -> Result<(), Error> { 25 | let mut links = handle.link().get().match_name(name.clone()).execute(); 26 | if let Some(link) = links.try_next().await? { 27 | handle 28 | .link() 29 | .set(LinkUnspec::new_with_index(link.header.index).down().build()) 30 | .execute() 31 | .await? 32 | } else { 33 | println!("no link link {name} found"); 34 | } 35 | Ok(()) 36 | } 37 | 38 | fn usage() { 39 | eprintln!( 40 | "usage: 41 | cargo run --example set_link_down -- 42 | 43 | Note that you need to run this program as root. Instead of running cargo as root, 44 | build the example normally: 45 | 46 | cd netlink-ip ; cargo build --example set_link_down 47 | 48 | Then find the binary in the target directory: 49 | 50 | cd ../target/debug/example ; sudo ./set_link_down " 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/addr/add.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use std::net::{IpAddr, Ipv4Addr}; 5 | 6 | use netlink_packet_core::{ 7 | NetlinkMessage, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE, 8 | NLM_F_REQUEST, 9 | }; 10 | 11 | use netlink_packet_route::{ 12 | address::{AddressAttribute, AddressMessage}, 13 | AddressFamily, RouteNetlinkMessage, 14 | }; 15 | 16 | use crate::{try_nl, Error, Handle}; 17 | 18 | /// A request to create a new address. This is equivalent to the `ip address 19 | /// add` commands. 20 | pub struct AddressAddRequest { 21 | handle: Handle, 22 | message: AddressMessage, 23 | replace: bool, 24 | } 25 | 26 | impl AddressAddRequest { 27 | pub(crate) fn new( 28 | handle: Handle, 29 | index: u32, 30 | address: IpAddr, 31 | prefix_len: u8, 32 | ) -> Self { 33 | let mut message = AddressMessage::default(); 34 | 35 | message.header.prefix_len = prefix_len; 36 | message.header.index = index; 37 | 38 | message.header.family = match address { 39 | IpAddr::V4(_) => AddressFamily::Inet, 40 | IpAddr::V6(_) => AddressFamily::Inet6, 41 | }; 42 | 43 | if address.is_multicast() { 44 | if let IpAddr::V6(a) = address { 45 | message.attributes.push(AddressAttribute::Multicast(a)); 46 | } 47 | } else { 48 | message.attributes.push(AddressAttribute::Address(address)); 49 | 50 | // for IPv4 the IFA_LOCAL address can be set to the same value as 51 | // IFA_ADDRESS 52 | message.attributes.push(AddressAttribute::Local(address)); 53 | 54 | // set the IFA_BROADCAST address as well (IPv6 does not support 55 | // broadcast) 56 | if let IpAddr::V4(a) = address { 57 | if prefix_len == 32 { 58 | message.attributes.push(AddressAttribute::Broadcast(a)); 59 | } else { 60 | let ip_addr = u32::from(a); 61 | let brd = Ipv4Addr::from( 62 | (0xffff_ffff_u32) >> u32::from(prefix_len) | ip_addr, 63 | ); 64 | message.attributes.push(AddressAttribute::Broadcast(brd)); 65 | }; 66 | } 67 | } 68 | AddressAddRequest { 69 | handle, 70 | message, 71 | replace: false, 72 | } 73 | } 74 | 75 | /// Replace existing matching address. 76 | pub fn replace(self) -> Self { 77 | Self { 78 | replace: true, 79 | ..self 80 | } 81 | } 82 | 83 | /// Execute the request. 84 | pub async fn execute(self) -> Result<(), Error> { 85 | let AddressAddRequest { 86 | mut handle, 87 | message, 88 | replace, 89 | } = self; 90 | let mut req = 91 | NetlinkMessage::from(RouteNetlinkMessage::NewAddress(message)); 92 | let replace = if replace { NLM_F_REPLACE } else { NLM_F_EXCL }; 93 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK | replace | NLM_F_CREATE; 94 | 95 | let mut response = handle.request(req)?; 96 | while let Some(message) = response.next().await { 97 | try_nl!(message); 98 | } 99 | Ok(()) 100 | } 101 | 102 | /// Return a mutable reference to the request message. 103 | pub fn message_mut(&mut self) -> &mut AddressMessage { 104 | &mut self.message 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/addr/del.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}; 5 | use netlink_packet_route::{address::AddressMessage, RouteNetlinkMessage}; 6 | 7 | use crate::{try_nl, Error, Handle}; 8 | 9 | pub struct AddressDelRequest { 10 | handle: Handle, 11 | message: AddressMessage, 12 | } 13 | 14 | impl AddressDelRequest { 15 | pub(crate) fn new(handle: Handle, message: AddressMessage) -> Self { 16 | AddressDelRequest { handle, message } 17 | } 18 | 19 | /// Execute the request 20 | pub async fn execute(self) -> Result<(), Error> { 21 | let AddressDelRequest { 22 | mut handle, 23 | message, 24 | } = self; 25 | 26 | let mut req = 27 | NetlinkMessage::from(RouteNetlinkMessage::DelAddress(message)); 28 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK; 29 | let mut response = handle.request(req)?; 30 | while let Some(msg) = response.next().await { 31 | try_nl!(msg); 32 | } 33 | Ok(()) 34 | } 35 | 36 | pub fn message_mut(&mut self) -> &mut AddressMessage { 37 | &mut self.message 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/addr/get.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::{ 4 | future::{self, Either}, 5 | stream::{StreamExt, TryStream, TryStreamExt}, 6 | FutureExt, 7 | }; 8 | use std::net::IpAddr; 9 | 10 | use netlink_packet_core::{NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST}; 11 | use netlink_packet_route::{ 12 | address::{AddressAttribute, AddressMessage}, 13 | RouteNetlinkMessage, 14 | }; 15 | 16 | use crate::{try_rtnl, Error, Handle}; 17 | 18 | pub struct AddressGetRequest { 19 | handle: Handle, 20 | message: AddressMessage, 21 | filter_builder: AddressFilterBuilder, 22 | } 23 | 24 | impl AddressGetRequest { 25 | pub(crate) fn new(handle: Handle) -> Self { 26 | AddressGetRequest { 27 | handle, 28 | message: AddressMessage::default(), 29 | filter_builder: AddressFilterBuilder::new(), 30 | } 31 | } 32 | 33 | pub fn message_mut(&mut self) -> &mut AddressMessage { 34 | &mut self.message 35 | } 36 | 37 | pub fn execute(self) -> impl TryStream { 38 | let AddressGetRequest { 39 | mut handle, 40 | message, 41 | filter_builder, 42 | } = self; 43 | 44 | let mut req = 45 | NetlinkMessage::from(RouteNetlinkMessage::GetAddress(message)); 46 | req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; 47 | 48 | let filter = filter_builder.build(); 49 | match handle.request(req) { 50 | Ok(response) => Either::Left( 51 | response 52 | .map(move |msg| { 53 | Ok(try_rtnl!(msg, RouteNetlinkMessage::NewAddress)) 54 | }) 55 | .try_filter(move |msg| future::ready(filter(msg))), 56 | ), 57 | Err(e) => Either::Right( 58 | future::err::(e).into_stream(), 59 | ), 60 | } 61 | } 62 | 63 | /// Return only the addresses of the given interface. 64 | pub fn set_link_index_filter(mut self, index: u32) -> Self { 65 | self.filter_builder.index = Some(index); 66 | self 67 | } 68 | 69 | /// Return only the addresses of the given prefix length. 70 | pub fn set_prefix_length_filter(mut self, prefix: u8) -> Self { 71 | self.filter_builder.prefix_len = Some(prefix); 72 | self 73 | } 74 | 75 | /// Return only AddressMessages filtered by the given address. 76 | pub fn set_address_filter(mut self, address: IpAddr) -> Self { 77 | self.filter_builder.address = Some(address); 78 | self 79 | } 80 | } 81 | 82 | // The reason for having filters, is that we cannot retrieve addresses 83 | // that match the given message, like we do for links. 84 | // 85 | // See: 86 | // https://lists.infradead.org/pipermail/libnl/2013-June/001014.html 87 | // https://patchwork.ozlabs.org/patch/133440/ 88 | #[derive(Default)] 89 | struct AddressFilterBuilder { 90 | index: Option, 91 | address: Option, 92 | prefix_len: Option, 93 | } 94 | 95 | impl AddressFilterBuilder { 96 | fn new() -> Self { 97 | Default::default() 98 | } 99 | 100 | fn build(self) -> impl Fn(&AddressMessage) -> bool { 101 | use AddressAttribute::*; 102 | 103 | move |msg: &AddressMessage| { 104 | if let Some(index) = self.index { 105 | if msg.header.index != index { 106 | return false; 107 | } 108 | } 109 | 110 | if let Some(prefix_len) = self.prefix_len { 111 | if msg.header.prefix_len != prefix_len { 112 | return false; 113 | } 114 | } 115 | 116 | if let Some(address) = self.address { 117 | for nla in msg.attributes.iter() { 118 | if let Address(x) | Local(x) = nla { 119 | if x == &address { 120 | return true; 121 | } 122 | } else if let Multicast(x) | Anycast(x) = nla { 123 | if IpAddr::V6(*x) == address { 124 | return true; 125 | } 126 | } 127 | } 128 | return false; 129 | } 130 | true 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/addr/handle.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::net::IpAddr; 4 | 5 | use super::{AddressAddRequest, AddressDelRequest, AddressGetRequest}; 6 | use crate::Handle; 7 | 8 | use netlink_packet_route::address::AddressMessage; 9 | 10 | pub struct AddressHandle(Handle); 11 | 12 | impl AddressHandle { 13 | pub fn new(handle: Handle) -> Self { 14 | AddressHandle(handle) 15 | } 16 | 17 | /// Retrieve the list of ip addresses (equivalent to `ip addr show`) 18 | pub fn get(&self) -> AddressGetRequest { 19 | AddressGetRequest::new(self.0.clone()) 20 | } 21 | 22 | /// Add an ip address on an interface (equivalent to `ip addr add`) 23 | pub fn add( 24 | &self, 25 | index: u32, 26 | address: IpAddr, 27 | prefix_len: u8, 28 | ) -> AddressAddRequest { 29 | AddressAddRequest::new(self.0.clone(), index, address, prefix_len) 30 | } 31 | 32 | /// Delete the given address 33 | pub fn del(&self, address: AddressMessage) -> AddressDelRequest { 34 | AddressDelRequest::new(self.0.clone(), address) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/addr/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | mod add; 4 | mod del; 5 | mod get; 6 | mod handle; 7 | 8 | pub use self::add::AddressAddRequest; 9 | pub use self::del::AddressDelRequest; 10 | pub use self::get::AddressGetRequest; 11 | pub use self::handle::AddressHandle; 12 | -------------------------------------------------------------------------------- /src/connection.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::io; 4 | 5 | use futures::channel::mpsc::UnboundedReceiver; 6 | use netlink_packet_core::NetlinkMessage; 7 | use netlink_packet_route::RouteNetlinkMessage; 8 | use netlink_proto::Connection; 9 | use netlink_sys::{protocols::NETLINK_ROUTE, AsyncSocket, SocketAddr}; 10 | 11 | use crate::Handle; 12 | 13 | #[cfg(feature = "tokio_socket")] 14 | #[allow(clippy::type_complexity)] 15 | pub fn new_connection() -> io::Result<( 16 | Connection, 17 | Handle, 18 | UnboundedReceiver<(NetlinkMessage, SocketAddr)>, 19 | )> { 20 | new_connection_with_socket() 21 | } 22 | 23 | #[allow(clippy::type_complexity)] 24 | pub fn new_connection_with_socket() -> io::Result<( 25 | Connection, 26 | Handle, 27 | UnboundedReceiver<(NetlinkMessage, SocketAddr)>, 28 | )> 29 | where 30 | S: AsyncSocket, 31 | { 32 | let (conn, handle, messages) = 33 | netlink_proto::new_connection_with_socket(NETLINK_ROUTE)?; 34 | Ok((conn, Handle::new(handle), messages)) 35 | } 36 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pub const RTMGRP_LINK: u32 = 1; 4 | pub const RTMGRP_NOTIFY: u32 = 2; 5 | pub const RTMGRP_NEIGH: u32 = 4; 6 | pub const RTMGRP_TC: u32 = 8; 7 | pub const RTMGRP_IPV4_IFADDR: u32 = 16; 8 | pub const RTMGRP_IPV4_MROUTE: u32 = 32; 9 | pub const RTMGRP_IPV4_ROUTE: u32 = 64; 10 | pub const RTMGRP_IPV4_RULE: u32 = 128; 11 | pub const RTMGRP_IPV6_IFADDR: u32 = 256; 12 | pub const RTMGRP_IPV6_MROUTE: u32 = 512; 13 | pub const RTMGRP_IPV6_ROUTE: u32 = 1024; 14 | pub const RTMGRP_IPV6_IFINFO: u32 = 2048; 15 | pub const RTMGRP_DECNET_IFADDR: u32 = 4096; 16 | pub const RTMGRP_DECNET_ROUTE: u32 = 16_384; 17 | pub const RTMGRP_IPV6_PREFIX: u32 = 131_072; 18 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use netlink_packet_core::{ErrorMessage, NetlinkMessage}; 4 | use netlink_packet_route::RouteNetlinkMessage; 5 | use thiserror::Error; 6 | 7 | #[derive(Clone, Eq, PartialEq, Debug, Error)] 8 | pub enum Error { 9 | #[error("Received an unexpected message {0:?}")] 10 | UnexpectedMessage(NetlinkMessage), 11 | 12 | #[error("Received a netlink error message {0}")] 13 | NetlinkError(ErrorMessage), 14 | 15 | #[error("A netlink request failed")] 16 | RequestFailed, 17 | 18 | #[error("Namespace error {0}")] 19 | NamespaceError(String), 20 | 21 | #[error( 22 | "Received a link message (RTM_GETLINK, RTM_NEWLINK, RTM_SETLINK or RTMGETLINK) with an invalid hardware address attribute: {0:?}." 23 | )] 24 | InvalidHardwareAddress(Vec), 25 | 26 | #[error("Failed to parse an IP address: {0:?}")] 27 | InvalidIp(Vec), 28 | 29 | #[error("Failed to parse a network address (IP and mask): {0:?}/{1:?}")] 30 | InvalidAddress(Vec, Vec), 31 | 32 | #[error("Attempting to set and Invalid NLA: {0}")] 33 | InvalidNla(String), 34 | } 35 | -------------------------------------------------------------------------------- /src/handle.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::Stream; 4 | use netlink_packet_core::NetlinkMessage; 5 | use netlink_packet_route::RouteNetlinkMessage; 6 | use netlink_proto::{sys::SocketAddr, ConnectionHandle}; 7 | 8 | use crate::{ 9 | AddressHandle, Error, LinkHandle, NeighbourHandle, RouteHandle, RuleHandle, 10 | }; 11 | #[cfg(not(target_os = "freebsd"))] 12 | use crate::{ 13 | QDiscHandle, TrafficChainHandle, TrafficClassHandle, TrafficFilterHandle, 14 | }; 15 | 16 | #[derive(Clone, Debug)] 17 | pub struct Handle(ConnectionHandle); 18 | 19 | impl Handle { 20 | pub(crate) fn new(conn: ConnectionHandle) -> Self { 21 | Handle(conn) 22 | } 23 | 24 | pub fn request( 25 | &mut self, 26 | message: NetlinkMessage, 27 | ) -> Result>, Error> 28 | { 29 | self.0 30 | .request(message, SocketAddr::new(0, 0)) 31 | .map_err(|_| Error::RequestFailed) 32 | } 33 | 34 | pub fn notify( 35 | &mut self, 36 | msg: NetlinkMessage, 37 | ) -> Result<(), Error> { 38 | self.0 39 | .notify(msg, SocketAddr::new(0, 0)) 40 | .map_err(|_| Error::RequestFailed)?; 41 | Ok(()) 42 | } 43 | 44 | /// Create a new handle, specifically for link requests (equivalent to `ip 45 | /// link` commands) 46 | pub fn link(&self) -> LinkHandle { 47 | LinkHandle::new(self.clone()) 48 | } 49 | 50 | /// Create a new handle, specifically for address requests (equivalent to 51 | /// `ip addr` commands) 52 | pub fn address(&self) -> AddressHandle { 53 | AddressHandle::new(self.clone()) 54 | } 55 | 56 | /// Create a new handle, specifically for routing table requests (equivalent 57 | /// to `ip route` commands) 58 | pub fn route(&self) -> RouteHandle { 59 | RouteHandle::new(self.clone()) 60 | } 61 | 62 | /// Create a new handle, specifically for routing rule requests (equivalent 63 | /// to `ip rule` commands) 64 | pub fn rule(&self) -> RuleHandle { 65 | RuleHandle::new(self.clone()) 66 | } 67 | 68 | /// Create a new handle, specifically for routing neighbours requests 69 | /// (equivalent to `ip neighbour` commands) 70 | pub fn neighbours(&self) -> NeighbourHandle { 71 | NeighbourHandle::new(self.clone()) 72 | } 73 | 74 | /// Create a new handle, specifically for traffic control qdisc requests 75 | /// (equivalent to `tc qdisc show` commands) 76 | #[cfg(not(target_os = "freebsd"))] 77 | pub fn qdisc(&self) -> QDiscHandle { 78 | QDiscHandle::new(self.clone()) 79 | } 80 | 81 | /// Create a new handle, specifically for traffic control class requests 82 | /// (equivalent to `tc class show dev ` commands) 83 | #[cfg(not(target_os = "freebsd"))] 84 | pub fn traffic_class(&self, ifindex: i32) -> TrafficClassHandle { 85 | TrafficClassHandle::new(self.clone(), ifindex) 86 | } 87 | 88 | /// Create a new handle, specifically for traffic control filter requests 89 | /// (equivalent to `tc filter show dev ` commands) 90 | #[cfg(not(target_os = "freebsd"))] 91 | pub fn traffic_filter(&self, ifindex: i32) -> TrafficFilterHandle { 92 | TrafficFilterHandle::new(self.clone(), ifindex) 93 | } 94 | 95 | /// Create a new handle, specifically for traffic control chain requests 96 | /// (equivalent to `tc chain show dev ` commands) 97 | #[cfg(not(target_os = "freebsd"))] 98 | pub fn traffic_chain(&self, ifindex: i32) -> TrafficChainHandle { 99 | TrafficChainHandle::new(self.clone(), ifindex) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | //! This crate provides methods to manipulate networking resources (links, 4 | //! addresses, arp tables, route tables) via the netlink protocol. 5 | 6 | #![allow(clippy::module_inception)] 7 | 8 | pub use netlink_packet_core as packet_core; 9 | pub use netlink_packet_route as packet_route; 10 | pub use netlink_packet_utils as packet_utils; 11 | pub use netlink_proto as proto; 12 | pub use netlink_sys as sys; 13 | 14 | mod addr; 15 | mod connection; 16 | pub mod constants; 17 | mod errors; 18 | mod handle; 19 | mod link; 20 | mod macros; 21 | mod neighbour; 22 | #[cfg(not(target_os = "freebsd"))] 23 | mod ns; 24 | mod route; 25 | mod rule; 26 | #[cfg(not(target_os = "freebsd"))] 27 | mod traffic_control; 28 | 29 | pub use crate::addr::{ 30 | AddressAddRequest, AddressDelRequest, AddressGetRequest, AddressHandle, 31 | }; 32 | #[cfg(feature = "tokio_socket")] 33 | pub use crate::connection::new_connection; 34 | pub use crate::connection::new_connection_with_socket; 35 | pub use crate::errors::Error; 36 | pub use crate::handle::Handle; 37 | pub use crate::link::{ 38 | LinkAddRequest, LinkBond, LinkBondPort, LinkBridge, LinkDelPropRequest, 39 | LinkDelRequest, LinkDummy, LinkGetRequest, LinkHandle, LinkMacVlan, 40 | LinkMacVtap, LinkMessageBuilder, LinkSetRequest, LinkUnspec, LinkVeth, 41 | LinkVlan, LinkVrf, LinkVxlan, LinkWireguard, LinkXfrm, QosMapping, 42 | }; 43 | pub use crate::neighbour::{ 44 | NeighbourAddRequest, NeighbourDelRequest, NeighbourGetRequest, 45 | NeighbourHandle, 46 | }; 47 | #[cfg(not(target_os = "freebsd"))] 48 | pub use crate::ns::{NetworkNamespace, NETNS_PATH, NONE_FS, SELF_NS_PATH}; 49 | pub use crate::route::{ 50 | IpVersion, RouteAddRequest, RouteDelRequest, RouteGetRequest, RouteHandle, 51 | RouteMessageBuilder, RouteNextHopBuilder, 52 | }; 53 | pub use crate::rule::{ 54 | RuleAddRequest, RuleDelRequest, RuleGetRequest, RuleHandle, 55 | }; 56 | #[cfg(not(target_os = "freebsd"))] 57 | pub use crate::traffic_control::{ 58 | QDiscDelRequest, QDiscGetRequest, QDiscHandle, QDiscNewRequest, 59 | TrafficChainGetRequest, TrafficChainHandle, TrafficClassGetRequest, 60 | TrafficClassHandle, TrafficFilterGetRequest, TrafficFilterHandle, 61 | TrafficFilterNewRequest, 62 | }; 63 | -------------------------------------------------------------------------------- /src/link/add.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use netlink_packet_core::{ 5 | NetlinkMessage, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE, 6 | NLM_F_REQUEST, 7 | }; 8 | 9 | use netlink_packet_route::{link::LinkMessage, RouteNetlinkMessage}; 10 | 11 | use crate::{try_nl, Error, Handle}; 12 | 13 | /// A request to create a new link. This is equivalent to the `ip link add` 14 | /// commands. 15 | /// 16 | /// A few methods for common actions (creating a veth pair, creating a vlan 17 | /// interface, etc.) are provided, but custom requests can be made using the 18 | /// [`message_mut()`](#method.message_mut) accessor. 19 | pub struct LinkAddRequest { 20 | handle: Handle, 21 | message: LinkMessage, 22 | flags: u16, 23 | } 24 | 25 | impl LinkAddRequest { 26 | pub(crate) fn new(handle: Handle, message: LinkMessage) -> Self { 27 | LinkAddRequest { 28 | handle, 29 | message, 30 | flags: NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL, 31 | } 32 | } 33 | 34 | /// Replace existing matching link. 35 | pub fn replace(self) -> Self { 36 | let mut ret = self; 37 | ret.flags -= NLM_F_EXCL; 38 | ret.flags |= NLM_F_REPLACE; 39 | ret 40 | } 41 | 42 | /// Setting arbitrary [NetlinkHeader] flags 43 | pub fn set_flags(self, flags: u16) -> Self { 44 | let mut ret = self; 45 | ret.flags = flags; 46 | ret 47 | } 48 | 49 | /// Execute the request. 50 | pub async fn execute(self) -> Result<(), Error> { 51 | let LinkAddRequest { 52 | mut handle, 53 | message, 54 | flags, 55 | } = self; 56 | let mut req = 57 | NetlinkMessage::from(RouteNetlinkMessage::NewLink(message)); 58 | req.header.flags = flags; 59 | 60 | let mut response = handle.request(req)?; 61 | while let Some(message) = response.next().await { 62 | try_nl!(message); 63 | } 64 | Ok(()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/link/bond_port.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{ 4 | packet_route::link::{InfoBondPort, InfoPortData, InfoPortKind}, 5 | LinkMessageBuilder, 6 | }; 7 | 8 | #[derive(Debug)] 9 | pub struct LinkBondPort; 10 | 11 | impl LinkBondPort { 12 | pub fn new(port_index: u32) -> LinkMessageBuilder { 13 | LinkMessageBuilder::::default() 14 | .index(port_index) 15 | .set_port_kind(InfoPortKind::Bond) 16 | } 17 | } 18 | 19 | impl LinkMessageBuilder { 20 | /// Append arbitrary [InfoBondPort] 21 | pub fn append_info_data(self, info: InfoBondPort) -> Self { 22 | let mut ret = self; 23 | 24 | if let InfoPortData::BondPort(infos) = ret 25 | .port_data 26 | .get_or_insert_with(|| InfoPortData::BondPort(Vec::new())) 27 | { 28 | infos.push(info); 29 | } 30 | 31 | ret 32 | } 33 | 34 | /// Adds the `queue_id` attribute to the bond port 35 | /// This is equivalent to 36 | /// `ip link set name NAME type bond_slave queue_id QUEUE_ID`. 37 | pub fn queue_id(self, queue_id: u16) -> Self { 38 | self.append_info_data(InfoBondPort::QueueId(queue_id)) 39 | } 40 | 41 | /// Adds the `prio` attribute to the bond port 42 | /// This is equivalent to `ip link set name NAME type bond_slave prio PRIO`. 43 | pub fn prio(self, prio: i32) -> Self { 44 | self.append_info_data(InfoBondPort::Prio(prio)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/link/bridge.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{link::LinkMessageBuilder, packet_route::link::InfoKind}; 4 | 5 | /// Represent dummy interface. 6 | /// Example code on creating a linux bridge interface 7 | /// ```no_run 8 | /// use rtnetlink::{new_connection, LinkBridge}; 9 | /// #[tokio::main] 10 | /// async fn main() -> Result<(), String> { 11 | /// let (connection, handle, _) = new_connection().unwrap(); 12 | /// tokio::spawn(connection); 13 | /// 14 | /// handle 15 | /// .link() 16 | /// .add(LinkBridge::new("br0").build()) 17 | /// .execute() 18 | /// .await 19 | /// .map_err(|e| format!("{e}")) 20 | /// } 21 | /// ``` 22 | /// 23 | /// Please check LinkMessageBuilder:: for more detail. 24 | #[derive(Debug)] 25 | pub struct LinkBridge; 26 | 27 | impl LinkBridge { 28 | /// Equal to `LinkMessageBuilder::::new().up()` 29 | pub fn new(name: &str) -> LinkMessageBuilder { 30 | LinkMessageBuilder::::new(name).up() 31 | } 32 | } 33 | 34 | impl LinkMessageBuilder { 35 | /// Create [LinkMessageBuilder] for linux bridge 36 | pub fn new(name: &str) -> Self { 37 | LinkMessageBuilder::::new_with_info_kind(InfoKind::Bridge) 38 | .name(name.to_string()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/link/builder.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::marker::PhantomData; 4 | use std::os::fd::RawFd; 5 | 6 | use crate::packet_route::link::{ 7 | InfoData, InfoKind, InfoPortData, InfoPortKind, LinkAttribute, LinkFlags, 8 | LinkHeader, LinkInfo, LinkMessage, 9 | }; 10 | 11 | /// Generic interface without interface type 12 | /// Could be used to match interface by interface name or index. 13 | /// Example on attaching a interface to controller 14 | /// ```no_run 15 | /// use rtnetlink::{new_connection, LinkUnspec}; 16 | /// 17 | /// #[tokio::main] 18 | /// async fn main() -> Result<(), String> { 19 | /// let (connection, handle, _) = new_connection().unwrap(); 20 | /// tokio::spawn(connection); 21 | /// 22 | /// let controller_index = 63u32; 23 | /// 24 | /// handle 25 | /// .link() 26 | /// .set( 27 | /// LinkUnspec::new_with_name("my-nic") 28 | /// .controller(controller_index) 29 | /// .build(), 30 | /// ) 31 | /// .execute() 32 | /// .await 33 | /// .map_err(|e| format!("{e}")) 34 | /// } 35 | /// ``` 36 | #[derive(Debug)] 37 | pub struct LinkUnspec; 38 | 39 | impl LinkUnspec { 40 | /// Equal to `LinkMessageBuilder::::default().index()` 41 | pub fn new_with_index(index: u32) -> LinkMessageBuilder { 42 | LinkMessageBuilder::::default().index(index) 43 | } 44 | 45 | /// Equal to `LinkMessageBuilder::::default().name()` 46 | pub fn new_with_name(name: &str) -> LinkMessageBuilder { 47 | LinkMessageBuilder::::default().name(name.to_string()) 48 | } 49 | } 50 | 51 | #[derive(Debug)] 52 | /// Helper struct for building [LinkMessage]. 53 | /// The [LinkMessageBuilder] is designed for advanced user, wrapper 54 | /// structs/functions are created 55 | pub struct LinkMessageBuilder { 56 | pub(crate) header: LinkHeader, 57 | pub(crate) info_kind: Option, 58 | pub(crate) info_data: Option, 59 | pub(crate) port_kind: Option, 60 | pub(crate) port_data: Option, 61 | extra_attriutes: Vec, 62 | _phantom: PhantomData, 63 | } 64 | 65 | impl Default for LinkMessageBuilder { 66 | fn default() -> Self { 67 | Self { 68 | header: Default::default(), 69 | info_kind: None, 70 | info_data: Default::default(), 71 | extra_attriutes: Default::default(), 72 | port_kind: None, 73 | port_data: None, 74 | _phantom: Default::default(), 75 | } 76 | } 77 | } 78 | 79 | impl LinkMessageBuilder { 80 | pub fn new_with_info_kind(info_kind: InfoKind) -> Self { 81 | Self { 82 | info_kind: Some(info_kind), 83 | ..Default::default() 84 | } 85 | } 86 | 87 | /// Set arbitrary [LinkHeader] 88 | pub fn set_header(self, header: LinkHeader) -> Self { 89 | let mut ret = self; 90 | ret.header = header; 91 | ret 92 | } 93 | 94 | /// Append arbitrary [LinkAttribute] 95 | pub fn append_extra_attribute(self, link_attr: LinkAttribute) -> Self { 96 | let mut ret = self; 97 | ret.extra_attriutes.push(link_attr); 98 | ret 99 | } 100 | 101 | /// Set arbitrary [InfoData] 102 | pub fn set_info_data(self, info_data: InfoData) -> Self { 103 | let mut ret = self; 104 | ret.info_data = Some(info_data); 105 | ret 106 | } 107 | 108 | /// Set the link up (equivalent to `ip link set dev DEV up`) 109 | pub fn up(self) -> Self { 110 | let mut ret = self; 111 | ret.header.flags |= LinkFlags::Up; 112 | ret.header.change_mask |= LinkFlags::Up; 113 | ret 114 | } 115 | 116 | /// Set the link down (equivalent to `ip link set dev DEV down`) 117 | pub fn down(self) -> Self { 118 | let mut ret = self; 119 | ret.header.flags.remove(LinkFlags::Up); 120 | ret.header.change_mask |= LinkFlags::Up; 121 | ret 122 | } 123 | 124 | /// Enable or disable promiscious mode of the link with the given index 125 | /// (equivalent to `ip link set dev DEV promisc on/off`) 126 | pub fn promiscuous(self, enable: bool) -> Self { 127 | let mut ret = self; 128 | if enable { 129 | ret.header.flags |= LinkFlags::Promisc; 130 | } else { 131 | ret.header.flags.remove(LinkFlags::Promisc); 132 | } 133 | ret.header.change_mask |= LinkFlags::Promisc; 134 | ret 135 | } 136 | 137 | /// Enable or disable the ARP protocol of the link with the given index 138 | /// (equivalent to `ip link set dev DEV arp on/off`) 139 | pub fn arp(self, enable: bool) -> Self { 140 | let mut ret = self; 141 | if enable { 142 | ret.header.flags.remove(LinkFlags::Noarp); 143 | } else { 144 | ret.header.flags |= LinkFlags::Noarp; 145 | } 146 | ret.header.change_mask |= LinkFlags::Noarp; 147 | ret 148 | } 149 | 150 | pub fn name(self, name: String) -> Self { 151 | self.append_extra_attribute(LinkAttribute::IfName(name)) 152 | } 153 | 154 | /// Set the mtu of the link with the given index (equivalent to 155 | /// `ip link set DEV mtu MTU`) 156 | pub fn mtu(self, mtu: u32) -> Self { 157 | self.append_extra_attribute(LinkAttribute::Mtu(mtu)) 158 | } 159 | 160 | /// Kernel index number of interface, used for querying, modifying or 161 | /// deleting existing interface. 162 | pub fn index(self, index: u32) -> Self { 163 | let mut ret = self; 164 | ret.header.index = index; 165 | ret 166 | } 167 | 168 | /// Define the hardware address of the link when creating it (equivalent to 169 | /// `ip link add NAME address ADDRESS`) 170 | pub fn address(self, address: Vec) -> Self { 171 | self.append_extra_attribute(LinkAttribute::Address(address)) 172 | } 173 | 174 | /// Move this network device into the network namespace of the process with 175 | /// the given `pid`. 176 | pub fn setns_by_pid(self, pid: u32) -> Self { 177 | self.append_extra_attribute(LinkAttribute::NetNsPid(pid)) 178 | } 179 | 180 | /// Move this network device into the network namespace corresponding to the 181 | /// given file descriptor. 182 | pub fn setns_by_fd(self, fd: RawFd) -> Self { 183 | self.append_extra_attribute(LinkAttribute::NetNsFd(fd)) 184 | } 185 | 186 | /// The physical device to act operate on. (e.g. the parent interface of 187 | /// VLAN/VxLAN) 188 | pub fn link(self, index: u32) -> Self { 189 | self.append_extra_attribute(LinkAttribute::Link(index)) 190 | } 191 | 192 | /// Define controller interface index (similar to 193 | /// ip link set NAME master CONTROLLER_NAME) 194 | pub fn controller(self, ctrl_index: u32) -> Self { 195 | self.append_extra_attribute(LinkAttribute::Controller(ctrl_index)) 196 | } 197 | 198 | /// Detach the link from its _controller_. This is equivalent to `ip link 199 | /// set LINK nomaster`. To succeed, the link that is being detached must be 200 | /// UP. 201 | pub fn nocontroller(self) -> Self { 202 | self.append_extra_attribute(LinkAttribute::Controller(0)) 203 | } 204 | 205 | pub fn set_port_kind(self, port_kind: InfoPortKind) -> Self { 206 | let mut ret = self; 207 | ret.port_kind = Some(port_kind); 208 | ret 209 | } 210 | 211 | /// Include port settings. 212 | /// The [LinkBondPort] and [LinkBridgePort] are the helper 213 | pub fn set_port_data(self, port_data: InfoPortData) -> Self { 214 | let mut ret = self; 215 | ret.port_data = Some(port_data); 216 | ret 217 | } 218 | 219 | pub fn build(self) -> LinkMessage { 220 | let mut message = LinkMessage::default(); 221 | message.header = self.header; 222 | 223 | if !self.extra_attriutes.is_empty() { 224 | message.attributes = self.extra_attriutes; 225 | } 226 | 227 | let mut link_infos: Vec = Vec::new(); 228 | if let Some(info_kind) = self.info_kind { 229 | link_infos.push(LinkInfo::Kind(info_kind)); 230 | } 231 | if let Some(info_data) = self.info_data { 232 | link_infos.push(LinkInfo::Data(info_data)); 233 | } 234 | 235 | if let Some(port_kind) = self.port_kind { 236 | link_infos.push(LinkInfo::PortKind(port_kind)); 237 | } 238 | 239 | if let Some(port_data) = self.port_data { 240 | link_infos.push(LinkInfo::PortData(port_data)); 241 | } 242 | 243 | if !link_infos.is_empty() { 244 | message.attributes.push(LinkAttribute::LinkInfo(link_infos)); 245 | } 246 | 247 | message 248 | } 249 | } 250 | 251 | impl LinkMessageBuilder { 252 | pub fn new() -> Self { 253 | Self::default() 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/link/del.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}; 5 | use netlink_packet_route::{link::LinkMessage, RouteNetlinkMessage}; 6 | 7 | use crate::{try_nl, Error, Handle}; 8 | 9 | pub struct LinkDelRequest { 10 | handle: Handle, 11 | message: LinkMessage, 12 | } 13 | 14 | impl LinkDelRequest { 15 | pub(crate) fn new(handle: Handle, index: u32) -> Self { 16 | let mut message = LinkMessage::default(); 17 | message.header.index = index; 18 | LinkDelRequest { handle, message } 19 | } 20 | 21 | /// Execute the request 22 | pub async fn execute(self) -> Result<(), Error> { 23 | let LinkDelRequest { 24 | mut handle, 25 | message, 26 | } = self; 27 | let mut req = 28 | NetlinkMessage::from(RouteNetlinkMessage::DelLink(message)); 29 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK; 30 | 31 | let mut response = handle.request(req)?; 32 | while let Some(message) = response.next().await { 33 | try_nl!(message) 34 | } 35 | Ok(()) 36 | } 37 | 38 | /// Return a mutable reference to the request 39 | pub fn message_mut(&mut self) -> &mut LinkMessage { 40 | &mut self.message 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/link/dummy.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{link::LinkMessageBuilder, packet_route::link::InfoKind}; 4 | 5 | /// Represent dummy interface. 6 | /// Example code on creating a dummy interface 7 | /// ```no_run 8 | /// use rtnetlink::{new_connection, LinkDummy}; 9 | /// #[tokio::main] 10 | /// async fn main() -> Result<(), String> { 11 | /// let (connection, handle, _) = new_connection().unwrap(); 12 | /// tokio::spawn(connection); 13 | /// 14 | /// handle 15 | /// .link() 16 | /// .add(LinkDummy::new("dummy0").build()) 17 | /// .execute() 18 | /// .await 19 | /// .map_err(|e| format!("{e}")) 20 | /// } 21 | /// ``` 22 | /// 23 | /// Please check LinkMessageBuilder:: for more detail. 24 | #[derive(Debug)] 25 | pub struct LinkDummy; 26 | 27 | impl LinkDummy { 28 | /// Equal to `LinkMessageBuilder::::new()` 29 | pub fn new(name: &str) -> LinkMessageBuilder { 30 | LinkMessageBuilder::::new(name) 31 | } 32 | } 33 | 34 | impl LinkMessageBuilder { 35 | /// Create [LinkMessageBuilder] for dummy interface type 36 | pub fn new(name: &str) -> Self { 37 | LinkMessageBuilder::::new_with_info_kind(InfoKind::Dummy) 38 | .name(name.to_string()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/link/get.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::{ 4 | future::{self, Either}, 5 | stream::{StreamExt, TryStream}, 6 | FutureExt, 7 | }; 8 | use netlink_packet_core::{NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST}; 9 | use netlink_packet_route::{ 10 | link::{LinkAttribute, LinkExtentMask, LinkMessage}, 11 | AddressFamily, RouteNetlinkMessage, 12 | }; 13 | 14 | use crate::{try_rtnl, Error, Handle}; 15 | 16 | pub struct LinkGetRequest { 17 | handle: Handle, 18 | message: LinkMessage, 19 | // There are two ways to retrieve links: we can either dump them 20 | // all and filter the result, or if we already know the index or 21 | // the name of the link we're looking for, we can just retrieve 22 | // that one. If `dump` is `true`, all the links are fetched. 23 | // Otherwise, only the link that match the given index or name 24 | // is fetched. 25 | dump: bool, 26 | } 27 | 28 | impl LinkGetRequest { 29 | pub(crate) fn new(handle: Handle) -> Self { 30 | LinkGetRequest { 31 | handle, 32 | message: LinkMessage::default(), 33 | dump: true, 34 | } 35 | } 36 | 37 | /// Setting filter mask 38 | pub fn set_filter_mask( 39 | mut self, 40 | family: AddressFamily, 41 | filter_mask: Vec, 42 | ) -> Self { 43 | self.message.header.interface_family = family; 44 | self.message 45 | .attributes 46 | .push(LinkAttribute::ExtMask(filter_mask)); 47 | self 48 | } 49 | 50 | /// Execute the request 51 | pub fn execute(self) -> impl TryStream { 52 | let LinkGetRequest { 53 | mut handle, 54 | message, 55 | dump, 56 | } = self; 57 | 58 | let mut req = 59 | NetlinkMessage::from(RouteNetlinkMessage::GetLink(message)); 60 | 61 | if dump { 62 | req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; 63 | } else { 64 | req.header.flags = NLM_F_REQUEST; 65 | } 66 | 67 | match handle.request(req) { 68 | Ok(response) => Either::Left(response.map(move |msg| { 69 | Ok(try_rtnl!(msg, RouteNetlinkMessage::NewLink)) 70 | })), 71 | Err(e) => Either::Right( 72 | future::err::(e).into_stream(), 73 | ), 74 | } 75 | } 76 | 77 | /// Return a mutable reference to the request 78 | pub fn message_mut(&mut self) -> &mut LinkMessage { 79 | &mut self.message 80 | } 81 | 82 | /// Lookup a link by index 83 | pub fn match_index(mut self, index: u32) -> Self { 84 | self.dump = false; 85 | self.message.header.index = index; 86 | self 87 | } 88 | 89 | /// Lookup a link by name 90 | /// 91 | /// This function requires support from your kernel (>= 2.6.33). If yours is 92 | /// older, consider filtering the resulting stream of links. 93 | pub fn match_name(mut self, name: String) -> Self { 94 | self.dump = false; 95 | self.message.attributes.push(LinkAttribute::IfName(name)); 96 | self 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/link/handle.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use super::{ 4 | LinkAddRequest, LinkDelPropRequest, LinkDelRequest, LinkGetRequest, 5 | LinkNewPropRequest, LinkSetRequest, 6 | }; 7 | use crate::{ 8 | packet_core::{NLM_F_ACK, NLM_F_REQUEST}, 9 | packet_route::link::LinkMessage, 10 | Handle, 11 | }; 12 | 13 | pub struct LinkHandle(Handle); 14 | 15 | impl LinkHandle { 16 | pub fn new(handle: Handle) -> Self { 17 | LinkHandle(handle) 18 | } 19 | 20 | pub fn set(&self, message: LinkMessage) -> LinkSetRequest { 21 | LinkSetRequest::new(self.0.clone(), message) 22 | } 23 | 24 | pub fn add(&self, message: LinkMessage) -> LinkAddRequest { 25 | LinkAddRequest::new(self.0.clone(), message) 26 | } 27 | 28 | pub fn property_add(&self, index: u32) -> LinkNewPropRequest { 29 | LinkNewPropRequest::new(self.0.clone(), index) 30 | } 31 | 32 | pub fn property_del(&self, index: u32) -> LinkDelPropRequest { 33 | LinkDelPropRequest::new(self.0.clone(), index) 34 | } 35 | 36 | pub fn del(&mut self, index: u32) -> LinkDelRequest { 37 | LinkDelRequest::new(self.0.clone(), index) 38 | } 39 | 40 | /// Retrieve the list of links (equivalent to `ip link show`) 41 | pub fn get(&mut self) -> LinkGetRequest { 42 | LinkGetRequest::new(self.0.clone()) 43 | } 44 | 45 | /// The `LinkHandle::set()` cannot be used for setting bond or bridge port 46 | /// configuration, `RTM_NEWLINK` and `NLM_F_REQUEST|NLM_F_ACK` are required, 47 | /// Equal to `LinkAddRequest::new().set_flags(NLM_F_REQUEST | NLM_F_ACK)` 48 | pub fn set_port(&self, message: LinkMessage) -> LinkAddRequest { 49 | LinkAddRequest::new(self.0.clone(), message) 50 | .set_flags(NLM_F_REQUEST | NLM_F_ACK) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/link/mac_vlan.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{ 4 | link::LinkMessageBuilder, 5 | packet_route::link::{InfoData, InfoKind, InfoMacVlan, MacVlanMode}, 6 | }; 7 | 8 | /// Represent MAC VLAN interface. 9 | /// Example code on creating a MAC VLAN interface 10 | /// ```no_run 11 | /// use rtnetlink::{new_connection, packet_route::link::MacVlanMode, 12 | /// LinkMacVlan}; 13 | /// 14 | /// #[tokio::main] 15 | /// async fn main() -> Result<(), String> { 16 | /// let (connection, handle, _) = new_connection().unwrap(); 17 | /// tokio::spawn(connection); 18 | /// 19 | /// handle 20 | /// .link() 21 | /// .add( 22 | /// LinkMacVlan::new("macvlan100", 10, MacVlanMode::Bridge) 23 | /// .up() 24 | /// .build(), 25 | /// ) 26 | /// .execute() 27 | /// .await 28 | /// .map_err(|e| format!("{e}")) 29 | /// } 30 | /// ``` 31 | /// 32 | /// Please check LinkMessageBuilder:: for more detail. 33 | #[derive(Debug)] 34 | pub struct LinkMacVlan; 35 | 36 | impl LinkMacVlan { 37 | /// Wrapper of `LinkMessageBuilder::::new().link().mode()` 38 | pub fn new( 39 | name: &str, 40 | base_iface_index: u32, 41 | mode: MacVlanMode, 42 | ) -> LinkMessageBuilder { 43 | LinkMessageBuilder::::new(name) 44 | .link(base_iface_index) 45 | .mode(mode) 46 | } 47 | } 48 | 49 | impl LinkMessageBuilder { 50 | /// Create [LinkMessageBuilder] for MAC VLAN 51 | pub fn new(name: &str) -> Self { 52 | LinkMessageBuilder::::new_with_info_kind(InfoKind::MacVlan) 53 | .name(name.to_string()) 54 | } 55 | 56 | pub fn append_info_data(mut self, info: InfoMacVlan) -> Self { 57 | if let InfoData::MacVlan(infos) = self 58 | .info_data 59 | .get_or_insert_with(|| InfoData::MacVlan(Vec::new())) 60 | { 61 | infos.push(info); 62 | } 63 | self 64 | } 65 | 66 | pub fn mode(self, mode: MacVlanMode) -> Self { 67 | self.append_info_data(InfoMacVlan::Mode(mode)) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/link/mac_vtap.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{ 4 | link::LinkMessageBuilder, 5 | packet_route::link::{InfoData, InfoKind, InfoMacVtap, MacVtapMode}, 6 | }; 7 | 8 | /// Represent MAC VTAP interface. 9 | /// Example code on creating a MAC VTAP interface 10 | /// ```no_run 11 | /// use rtnetlink::{new_connection, packet_route::link::MacVtapMode, 12 | /// LinkMacVtap}; 13 | /// 14 | /// #[tokio::main] 15 | /// async fn main() -> Result<(), String> { 16 | /// let (connection, handle, _) = new_connection().unwrap(); 17 | /// tokio::spawn(connection); 18 | /// 19 | /// handle 20 | /// .link() 21 | /// .add( 22 | /// LinkMacVtap::new("macvtap100", 10, MacVtapMode::Bridge) 23 | /// .up() 24 | /// .build(), 25 | /// ) 26 | /// .execute() 27 | /// .await 28 | /// .map_err(|e| format!("{e}")) 29 | /// } 30 | /// ``` 31 | /// 32 | /// Please check LinkMessageBuilder:: for more detail. 33 | #[derive(Debug)] 34 | pub struct LinkMacVtap; 35 | 36 | impl LinkMacVtap { 37 | /// Wrapper of `LinkMessageBuilder::::new().link().mode()` 38 | pub fn new( 39 | name: &str, 40 | base_iface_index: u32, 41 | mode: MacVtapMode, 42 | ) -> LinkMessageBuilder { 43 | LinkMessageBuilder::::new(name) 44 | .link(base_iface_index) 45 | .mode(mode) 46 | } 47 | } 48 | 49 | impl LinkMessageBuilder { 50 | /// Create [LinkMessageBuilder] for Mac VTAP interface 51 | pub fn new(name: &str) -> Self { 52 | LinkMessageBuilder::::new_with_info_kind(InfoKind::MacVtap) 53 | .name(name.to_string()) 54 | } 55 | 56 | pub fn append_info_data(mut self, info: InfoMacVtap) -> Self { 57 | if let InfoData::MacVtap(infos) = self 58 | .info_data 59 | .get_or_insert_with(|| InfoData::MacVtap(Vec::new())) 60 | { 61 | infos.push(info); 62 | } 63 | self 64 | } 65 | 66 | pub fn mode(self, mode: MacVtapMode) -> Self { 67 | self.append_info_data(InfoMacVtap::Mode(mode)) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/link/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | mod add; 4 | mod bond; 5 | mod bond_port; 6 | mod bridge; 7 | mod builder; 8 | mod del; 9 | mod dummy; 10 | mod get; 11 | mod handle; 12 | mod mac_vlan; 13 | mod mac_vtap; 14 | mod property_add; 15 | mod property_del; 16 | mod set; 17 | mod veth; 18 | mod vlan; 19 | mod vrf; 20 | mod vxlan; 21 | mod wireguard; 22 | mod xfrm; 23 | 24 | pub use self::add::LinkAddRequest; 25 | pub use self::bond::LinkBond; 26 | pub use self::bond_port::LinkBondPort; 27 | pub use self::bridge::LinkBridge; 28 | pub use self::builder::{LinkMessageBuilder, LinkUnspec}; 29 | pub use self::del::LinkDelRequest; 30 | pub use self::dummy::LinkDummy; 31 | pub use self::get::LinkGetRequest; 32 | pub use self::handle::LinkHandle; 33 | pub use self::mac_vlan::LinkMacVlan; 34 | pub use self::mac_vtap::LinkMacVtap; 35 | pub use self::property_add::LinkNewPropRequest; 36 | pub use self::property_del::LinkDelPropRequest; 37 | pub use self::set::LinkSetRequest; 38 | pub use self::veth::LinkVeth; 39 | pub use self::vlan::{LinkVlan, QosMapping}; 40 | pub use self::vrf::LinkVrf; 41 | pub use self::vxlan::LinkVxlan; 42 | pub use self::wireguard::LinkWireguard; 43 | pub use self::xfrm::LinkXfrm; 44 | 45 | #[cfg(test)] 46 | mod test; 47 | -------------------------------------------------------------------------------- /src/link/property_add.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use netlink_packet_core::{ 5 | NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_APPEND, NLM_F_CREATE, 6 | NLM_F_EXCL, NLM_F_REQUEST, 7 | }; 8 | use netlink_packet_route::{ 9 | link::{LinkAttribute, LinkMessage, Prop}, 10 | RouteNetlinkMessage, 11 | }; 12 | 13 | use crate::{Error, Handle}; 14 | 15 | pub struct LinkNewPropRequest { 16 | handle: Handle, 17 | message: LinkMessage, 18 | } 19 | 20 | impl LinkNewPropRequest { 21 | pub(crate) fn new(handle: Handle, index: u32) -> Self { 22 | let mut message = LinkMessage::default(); 23 | message.header.index = index; 24 | LinkNewPropRequest { handle, message } 25 | } 26 | 27 | /// Execute the request 28 | pub async fn execute(self) -> Result<(), Error> { 29 | let LinkNewPropRequest { 30 | mut handle, 31 | message, 32 | } = self; 33 | let mut req = 34 | NetlinkMessage::from(RouteNetlinkMessage::NewLinkProp(message)); 35 | req.header.flags = NLM_F_REQUEST 36 | | NLM_F_ACK 37 | | NLM_F_EXCL 38 | | NLM_F_CREATE 39 | | NLM_F_APPEND; 40 | 41 | let mut response = handle.request(req)?; 42 | while let Some(message) = response.next().await { 43 | if let NetlinkPayload::Error(err) = message.payload { 44 | return Err(Error::NetlinkError(err)); 45 | } 46 | } 47 | Ok(()) 48 | } 49 | 50 | /// Return a mutable reference to the request 51 | pub fn message_mut(&mut self) -> &mut LinkMessage { 52 | &mut self.message 53 | } 54 | 55 | /// Add alternative name to the link. This is equivalent to `ip link 56 | /// property add altname ALT_IFNAME dev LINK`. 57 | pub fn alt_ifname(mut self, alt_ifnames: &[&str]) -> Self { 58 | let mut props = Vec::new(); 59 | for alt_ifname in alt_ifnames { 60 | props.push(Prop::AltIfName(alt_ifname.to_string())); 61 | } 62 | 63 | self.message.attributes.push(LinkAttribute::PropList(props)); 64 | self 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/link/property_del.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use netlink_packet_core::{ 5 | NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_EXCL, NLM_F_REQUEST, 6 | }; 7 | use netlink_packet_route::{ 8 | link::{LinkAttribute, LinkMessage, Prop}, 9 | RouteNetlinkMessage, 10 | }; 11 | 12 | use crate::{Error, Handle}; 13 | 14 | pub struct LinkDelPropRequest { 15 | handle: Handle, 16 | message: LinkMessage, 17 | } 18 | 19 | impl LinkDelPropRequest { 20 | pub(crate) fn new(handle: Handle, index: u32) -> Self { 21 | let mut message = LinkMessage::default(); 22 | message.header.index = index; 23 | LinkDelPropRequest { handle, message } 24 | } 25 | 26 | /// Execute the request 27 | pub async fn execute(self) -> Result<(), Error> { 28 | let LinkDelPropRequest { 29 | mut handle, 30 | message, 31 | } = self; 32 | let mut req = 33 | NetlinkMessage::from(RouteNetlinkMessage::DelLinkProp(message)); 34 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL; 35 | 36 | let mut response = handle.request(req)?; 37 | while let Some(message) = response.next().await { 38 | if let NetlinkPayload::Error(err) = message.payload { 39 | return Err(Error::NetlinkError(err)); 40 | } 41 | } 42 | Ok(()) 43 | } 44 | 45 | /// Return a mutable reference to the request 46 | pub fn message_mut(&mut self) -> &mut LinkMessage { 47 | &mut self.message 48 | } 49 | 50 | /// Remove alternative name to the link. This is equivalent to `ip link 51 | /// property del altname ALT_IFNAME dev LINK`. 52 | pub fn alt_ifname(mut self, alt_ifnames: &[&str]) -> Self { 53 | let mut props = Vec::new(); 54 | for alt_ifname in alt_ifnames { 55 | props.push(Prop::AltIfName(alt_ifname.to_string())); 56 | } 57 | 58 | self.message.attributes.push(LinkAttribute::PropList(props)); 59 | self 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/link/set.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use netlink_packet_core::{ 5 | NetlinkMessage, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REQUEST, 6 | }; 7 | use netlink_packet_route::{link::LinkMessage, RouteNetlinkMessage}; 8 | 9 | use crate::{try_nl, Error, Handle}; 10 | 11 | pub struct LinkSetRequest { 12 | handle: Handle, 13 | message: LinkMessage, 14 | } 15 | 16 | impl LinkSetRequest { 17 | pub(crate) fn new(handle: Handle, message: LinkMessage) -> Self { 18 | LinkSetRequest { handle, message } 19 | } 20 | 21 | /// Execute the request 22 | pub async fn execute(self) -> Result<(), Error> { 23 | let LinkSetRequest { 24 | mut handle, 25 | message, 26 | } = self; 27 | let mut req = 28 | NetlinkMessage::from(RouteNetlinkMessage::SetLink(message)); 29 | req.header.flags = 30 | NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE; 31 | 32 | let mut response = handle.request(req)?; 33 | while let Some(message) = response.next().await { 34 | try_nl!(message); 35 | } 36 | Ok(()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/link/test.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::TryStreamExt; 4 | use tokio::runtime::Runtime; 5 | 6 | use crate::{ 7 | new_connection, 8 | packet_route::link::{ 9 | InfoData, InfoKind, InfoMacVlan, InfoVrf, LinkAttribute, LinkInfo, 10 | LinkMessage, MacVlanMode, 11 | }, 12 | Error, LinkHandle, LinkMacVlan, LinkVrf, LinkWireguard, 13 | }; 14 | 15 | const IFACE_NAME: &str = "wg142"; // rand? 16 | 17 | #[test] 18 | fn create_get_delete_wg() { 19 | let rt = Runtime::new().unwrap(); 20 | let handle = rt.block_on(_create_wg()); 21 | assert!(handle.is_ok()); 22 | let mut handle = handle.unwrap(); 23 | let msg = rt.block_on(_get_iface(&mut handle, IFACE_NAME.to_owned())); 24 | assert!(msg.is_ok()); 25 | let msg = msg.unwrap(); 26 | assert!(has_nla( 27 | &msg, 28 | &LinkAttribute::LinkInfo(vec![LinkInfo::Kind(InfoKind::Wireguard)]) 29 | )); 30 | rt.block_on(_del_iface(&mut handle, msg.header.index)) 31 | .unwrap(); 32 | } 33 | 34 | #[test] 35 | fn create_get_delete_macvlan() { 36 | const MACVLAN_IFACE_NAME: &str = "mvlan1"; 37 | const LOWER_DEVICE_IDX: u32 = 2; 38 | const MACVLAN_MODE: MacVlanMode = MacVlanMode::Bridge; 39 | let mac_address = [2u8, 0, 0, 0, 0, 1]; 40 | 41 | let rt = Runtime::new().unwrap(); 42 | let handle = rt.block_on(_create_macvlan( 43 | MACVLAN_IFACE_NAME, 44 | LOWER_DEVICE_IDX, /* assuming there's always a network interface in 45 | * the system ... */ 46 | MACVLAN_MODE, 47 | mac_address.to_vec(), 48 | )); 49 | assert!(handle.is_ok()); 50 | 51 | let mut handle = handle.unwrap(); 52 | let msg = 53 | rt.block_on(_get_iface(&mut handle, MACVLAN_IFACE_NAME.to_owned())); 54 | assert!(msg.is_ok()); 55 | assert!(has_nla( 56 | msg.as_ref().unwrap(), 57 | &LinkAttribute::LinkInfo(vec![ 58 | LinkInfo::Kind(InfoKind::MacVlan), 59 | LinkInfo::Data(InfoData::MacVlan(vec![ 60 | InfoMacVlan::Mode(MACVLAN_MODE), 61 | InfoMacVlan::Flags(0), // defaulted by the kernel 62 | InfoMacVlan::MacAddrCount(0), // defaulted by the kernel 63 | InfoMacVlan::BcQueueLen(1000), // defaulted by the kernel 64 | InfoMacVlan::BcQueueLenUsed(1000) // defaulted by the kernel 65 | ])) 66 | ]) 67 | )); 68 | assert!(has_nla( 69 | msg.as_ref().unwrap(), 70 | &LinkAttribute::IfName(MACVLAN_IFACE_NAME.to_string()) 71 | )); 72 | assert!(has_nla( 73 | msg.as_ref().unwrap(), 74 | &LinkAttribute::Link(LOWER_DEVICE_IDX) 75 | )); 76 | assert!(has_nla( 77 | msg.as_ref().unwrap(), 78 | &LinkAttribute::Address(mac_address.to_vec()) 79 | )); 80 | 81 | rt.block_on(_del_iface(&mut handle, msg.unwrap().header.index)) 82 | .unwrap(); 83 | } 84 | 85 | #[test] 86 | fn create_delete_vrf() { 87 | const VRF_IFACE_NAME: &str = "vrf2222"; 88 | const VRF_TABLE: u32 = 2222; 89 | let rt = Runtime::new().unwrap(); 90 | let handle = rt.block_on(_create_vrf(VRF_IFACE_NAME, VRF_TABLE)); 91 | assert!(handle.is_ok()); 92 | 93 | let mut handle = handle.unwrap(); 94 | let msg = rt.block_on(_get_iface(&mut handle, VRF_IFACE_NAME.to_owned())); 95 | assert!(msg.is_ok()); 96 | assert!(has_nla( 97 | msg.as_ref().unwrap(), 98 | &LinkAttribute::IfName(VRF_IFACE_NAME.to_string()) 99 | )); 100 | assert!(has_nla( 101 | msg.as_ref().unwrap(), 102 | &LinkAttribute::LinkInfo(vec![ 103 | LinkInfo::Kind(InfoKind::Vrf), 104 | LinkInfo::Data(InfoData::Vrf(vec![InfoVrf::TableId(VRF_TABLE),])) 105 | ]) 106 | )); 107 | 108 | rt.block_on(_del_iface(&mut handle, msg.unwrap().header.index)) 109 | .unwrap(); 110 | } 111 | 112 | fn has_nla(msg: &LinkMessage, nla: &LinkAttribute) -> bool { 113 | msg.attributes.iter().any(|x| x == nla) 114 | } 115 | 116 | async fn _create_wg() -> Result { 117 | let (conn, handle, _) = new_connection().unwrap(); 118 | tokio::spawn(conn); 119 | let link_handle = handle.link(); 120 | link_handle 121 | .add(LinkWireguard::new(IFACE_NAME).build()) 122 | .execute() 123 | .await?; 124 | Ok(link_handle) 125 | } 126 | 127 | async fn _get_iface( 128 | handle: &mut LinkHandle, 129 | iface_name: String, 130 | ) -> Result { 131 | let mut links = handle.get().match_name(iface_name).execute(); 132 | let msg = links.try_next().await?; 133 | msg.ok_or(Error::RequestFailed) 134 | } 135 | 136 | async fn _del_iface(handle: &mut LinkHandle, index: u32) -> Result<(), Error> { 137 | handle.del(index).execute().await 138 | } 139 | 140 | async fn _create_macvlan( 141 | name: &str, 142 | lower_device_index: u32, 143 | mode: MacVlanMode, 144 | mac: Vec, 145 | ) -> Result { 146 | let (conn, handle, _) = new_connection().unwrap(); 147 | tokio::spawn(conn); 148 | let link_handle = handle.link(); 149 | let req = link_handle.add( 150 | LinkMacVlan::new(name, lower_device_index, mode) 151 | .address(mac) 152 | .build(), 153 | ); 154 | req.execute().await?; 155 | Ok(link_handle) 156 | } 157 | 158 | async fn _create_vrf(name: &str, table: u32) -> Result { 159 | let (conn, handle, _) = new_connection().unwrap(); 160 | tokio::spawn(conn); 161 | let link_handle = handle.link(); 162 | let req = link_handle.add(LinkVrf::new(name, table).build()); 163 | req.execute().await?; 164 | Ok(link_handle) 165 | } 166 | -------------------------------------------------------------------------------- /src/link/veth.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{ 4 | packet_route::link::{InfoData, InfoKind, InfoVeth}, 5 | LinkMessageBuilder, LinkUnspec, 6 | }; 7 | 8 | #[derive(Debug)] 9 | /// Represent virtual ethernet interface. 10 | /// Example code on creating a veth pair 11 | /// ```no_run 12 | /// use rtnetlink::{new_connection, LinkVeth}; 13 | /// #[tokio::main] 14 | /// async fn main() -> Result<(), String> { 15 | /// let (connection, handle, _) = new_connection().unwrap(); 16 | /// tokio::spawn(connection); 17 | /// 18 | /// handle 19 | /// .link() 20 | /// .add(LinkVeth::new("veth1", "veth1-peer").build()) 21 | /// .execute() 22 | /// .await 23 | /// .map_err(|e| format!("{e}")) 24 | /// } 25 | /// ``` 26 | /// 27 | /// Please check LinkMessageBuilder:: for more detail. 28 | pub struct LinkVeth; 29 | 30 | impl LinkVeth { 31 | /// Equal to `LinkMessageBuilder::::new(name, peer)` 32 | pub fn new(name: &str, peer: &str) -> LinkMessageBuilder { 33 | LinkMessageBuilder::::new(name, peer) 34 | } 35 | } 36 | 37 | impl LinkMessageBuilder { 38 | /// Create [LinkMessageBuilder] for VETH 39 | pub fn new(name: &str, peer: &str) -> Self { 40 | LinkMessageBuilder::::new_with_info_kind(InfoKind::Veth) 41 | .name(name.to_string()) 42 | .peer(peer) 43 | } 44 | 45 | pub fn peer(mut self, peer: &str) -> Self { 46 | let peer_msg = LinkMessageBuilder::::new() 47 | .name(peer.to_string()) 48 | .build(); 49 | 50 | self.info_data = Some(InfoData::Veth(InfoVeth::Peer(peer_msg))); 51 | self 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/link/vlan.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{ 4 | packet_route::link::{InfoData, InfoKind, InfoVlan, VlanQosMapping}, 5 | LinkMessageBuilder, 6 | }; 7 | 8 | /// A quality-of-service mapping between the internal priority `from` to the 9 | /// external vlan priority `to`. 10 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 11 | pub struct QosMapping { 12 | pub from: u32, 13 | pub to: u32, 14 | } 15 | 16 | impl From for VlanQosMapping { 17 | fn from(QosMapping { from, to }: QosMapping) -> Self { 18 | Self::Mapping(from, to) 19 | } 20 | } 21 | 22 | /// Represent VLAN interface. 23 | /// Example code on creating a VLAN interface 24 | /// ```no_run 25 | /// use rtnetlink::{new_connection, LinkVlan}; 26 | /// #[tokio::main] 27 | /// async fn main() -> Result<(), String> { 28 | /// let (connection, handle, _) = new_connection().unwrap(); 29 | /// tokio::spawn(connection); 30 | /// 31 | /// handle 32 | /// .link() 33 | /// .add( 34 | /// LinkVlan::new("vlan100", 10, 100) 35 | /// .up() 36 | /// .build() 37 | /// ) 38 | /// .execute() 39 | /// .await 40 | /// .map_err(|e| format!("{e}")) 41 | /// } 42 | /// ``` 43 | /// 44 | /// Please check LinkMessageBuilder:: for more detail. 45 | #[derive(Debug)] 46 | pub struct LinkVlan; 47 | 48 | impl LinkVlan { 49 | /// Wrapper of `LinkMessageBuilder::::new().id().dev()` 50 | pub fn new( 51 | name: &str, 52 | base_iface_index: u32, 53 | vlan_id: u16, 54 | ) -> LinkMessageBuilder { 55 | LinkMessageBuilder::::new(name) 56 | .id(vlan_id) 57 | .link(base_iface_index) 58 | } 59 | } 60 | 61 | impl LinkMessageBuilder { 62 | /// Create [LinkMessageBuilder] for VLAN 63 | pub fn new(name: &str) -> Self { 64 | LinkMessageBuilder::::new_with_info_kind(InfoKind::Vlan) 65 | .name(name.to_string()) 66 | } 67 | 68 | pub fn append_info_data(mut self, info: InfoVlan) -> Self { 69 | if let InfoData::Vlan(infos) = self 70 | .info_data 71 | .get_or_insert_with(|| InfoData::Vlan(Vec::new())) 72 | { 73 | infos.push(info); 74 | } 75 | self 76 | } 77 | 78 | /// VLAN ID 79 | pub fn id(self, vlan_id: u16) -> Self { 80 | self.append_info_data(InfoVlan::Id(vlan_id)) 81 | } 82 | 83 | /// ingress QoS and egress QoS 84 | pub fn qos(self, ingress_qos: I, egress_qos: E) -> Self 85 | where 86 | I: IntoIterator, 87 | E: IntoIterator, 88 | { 89 | let mut ret = self; 90 | let ingress: Vec<_> = 91 | ingress_qos.into_iter().map(VlanQosMapping::from).collect(); 92 | if !ingress.is_empty() { 93 | ret = ret.append_info_data(InfoVlan::IngressQos(ingress)); 94 | } 95 | 96 | let egress: Vec<_> = 97 | egress_qos.into_iter().map(VlanQosMapping::from).collect(); 98 | 99 | if !egress.is_empty() { 100 | ret = ret.append_info_data(InfoVlan::EgressQos(egress)); 101 | } 102 | ret 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/link/vrf.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{ 4 | packet_route::link::{InfoData, InfoKind, InfoVrf}, 5 | LinkMessageBuilder, 6 | }; 7 | 8 | /// Represent VRF interface. 9 | /// Example code on creating a VRF interface 10 | /// ```no_run 11 | /// use rtnetlink::{new_connection, LinkVrf}; 12 | /// #[tokio::main] 13 | /// async fn main() -> Result<(), String> { 14 | /// let (connection, handle, _) = new_connection().unwrap(); 15 | /// tokio::spawn(connection); 16 | /// 17 | /// handle 18 | /// .link() 19 | /// .add( 20 | /// LinkVrf::new("my-vrf", 100) 21 | /// .up() 22 | /// .build() 23 | /// ) 24 | /// .execute() 25 | /// .await 26 | /// .map_err(|e| format!("{e}")) 27 | /// } 28 | /// ``` 29 | /// 30 | /// Please check LinkMessageBuilder:: for more detail. 31 | #[derive(Debug)] 32 | pub struct LinkVrf; 33 | 34 | impl LinkVrf { 35 | /// Wrapper of `LinkMessageBuilder::::new().table_id()` 36 | pub fn new(name: &str, table_id: u32) -> LinkMessageBuilder { 37 | LinkMessageBuilder::::new(name).table_id(table_id) 38 | } 39 | } 40 | 41 | impl LinkMessageBuilder { 42 | /// Create [LinkMessageBuilder] for VRF 43 | pub fn new(name: &str) -> Self { 44 | LinkMessageBuilder::::new_with_info_kind(InfoKind::Vrf) 45 | .name(name.to_string()) 46 | } 47 | 48 | pub fn append_info_data(mut self, info: InfoVrf) -> Self { 49 | if let InfoData::Vrf(infos) = self 50 | .info_data 51 | .get_or_insert_with(|| InfoData::Vrf(Vec::new())) 52 | { 53 | infos.push(info); 54 | } 55 | self 56 | } 57 | 58 | /// VRF table ID 59 | pub fn table_id(self, table_id: u32) -> Self { 60 | self.append_info_data(InfoVrf::TableId(table_id)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/link/vxlan.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{ 4 | packet_route::link::{InfoData, InfoKind, InfoVxlan}, 5 | LinkMessageBuilder, 6 | }; 7 | 8 | /// Represent VxLAN interface. 9 | /// Example code on creating a VxLAN interface 10 | /// ```no_run 11 | /// use rtnetlink::{new_connection, LinkVxlan}; 12 | /// #[tokio::main] 13 | /// async fn main() -> Result<(), String> { 14 | /// let (connection, handle, _) = new_connection().unwrap(); 15 | /// tokio::spawn(connection); 16 | /// 17 | /// handle 18 | /// .link() 19 | /// .add(LinkVxlan::new("vxlan100", 100) 20 | /// .dev(10) 21 | /// .port(4789) 22 | /// .up() 23 | /// .build()) 24 | /// .execute() 25 | /// .await 26 | /// .map_err(|e| format!("{e}")) 27 | /// } 28 | /// ``` 29 | /// 30 | /// Please check LinkMessageBuilder:: for more detail. 31 | #[derive(Debug)] 32 | pub struct LinkVxlan; 33 | 34 | impl LinkVxlan { 35 | /// Wrapper of `LinkMessageBuilder::::new().id()` 36 | pub fn new(name: &str, vni: u32) -> LinkMessageBuilder { 37 | LinkMessageBuilder::::new(name).id(vni) 38 | } 39 | } 40 | 41 | impl LinkMessageBuilder { 42 | /// Create [LinkMessageBuilder] for VLAN 43 | pub fn new(name: &str) -> Self { 44 | LinkMessageBuilder::::new_with_info_kind(InfoKind::Vxlan) 45 | .name(name.to_string()) 46 | } 47 | 48 | pub fn append_info_data(self, info: InfoVxlan) -> Self { 49 | let mut ret = self; 50 | if let InfoData::Vxlan(infos) = ret 51 | .info_data 52 | .get_or_insert_with(|| InfoData::Vxlan(Vec::new())) 53 | { 54 | infos.push(info); 55 | } 56 | ret 57 | } 58 | 59 | /// VNI 60 | pub fn id(self, id: u32) -> Self { 61 | self.append_info_data(InfoVxlan::Id(id)) 62 | } 63 | 64 | /// This is equivalent to `devPHYS_DEV` for ip link vxlan. 65 | /// Adds the `dev` attribute to the VXLAN 66 | /// This is equivalent to `ip link add name NAME type vxlan id VNI dev 67 | /// LINK`, dev LINK - specifies the physical device to use 68 | /// for tunnel endpoint communication. 69 | /// But instead of specifing a link name (`LINK`), we specify a link index. 70 | /// Please be aware the `LinkMessageBuilder::link()` will not works for 71 | /// VxLAN. 72 | pub fn dev(self, index: u32) -> Self { 73 | self.append_info_data(InfoVxlan::Link(index)) 74 | } 75 | 76 | /// Adds the `dstport` attribute to the VXLAN 77 | /// This is equivalent to `ip link add name NAME type vxlan id VNI dstport 78 | /// PORT`. dstport PORT - specifies the UDP destination port to 79 | /// communicate to the remote VXLAN tunnel endpoint. 80 | pub fn port(self, port: u16) -> Self { 81 | self.append_info_data(InfoVxlan::Port(port)) 82 | } 83 | 84 | /// Adds the `group` attribute to the VXLAN 85 | /// This is equivalent to `ip link add name NAME type vxlan id VNI group 86 | /// IPADDR`, group IPADDR - specifies the multicast IP address to join. 87 | /// This function takes an IPv4 address 88 | /// WARNING: only one between `remote` and `group` can be present. 89 | pub fn group(self, addr: std::net::Ipv4Addr) -> Self { 90 | self.append_info_data(InfoVxlan::Group(addr)) 91 | } 92 | 93 | /// Adds the `group` attribute to the VXLAN 94 | /// This is equivalent to `ip link add name NAME type vxlan id VNI group 95 | /// IPADDR`, group IPADDR - specifies the multicast IP address to join. 96 | /// This function takes an IPv6 address 97 | /// WARNING: only one between `remote` and `group` can be present. 98 | pub fn group6(self, addr: std::net::Ipv6Addr) -> Self { 99 | self.append_info_data(InfoVxlan::Group6(addr)) 100 | } 101 | 102 | /// Adds the `remote` attribute to the VXLAN 103 | /// This is equivalent to `ip link add name NAME type vxlan id VNI remote 104 | /// IPADDR`, remote IPADDR - specifies the unicast destination IP 105 | /// address to use in outgoing packets when the 106 | /// destination link layer address is not known in the 107 | /// VXLAN device forwarding database. 108 | /// This function takes an IPv4 address. 109 | /// WARNING: only one between `remote` and `group` can be present. 110 | pub fn remote(self, addr: std::net::Ipv4Addr) -> Self { 111 | self.group(addr) 112 | } 113 | 114 | /// Adds the `remote` attribute to the VXLAN 115 | /// This is equivalent to `ip link add name NAME type vxlan id VNI remote 116 | /// IPADDR`, remote IPADDR - specifies the unicast destination IP 117 | /// address to use in outgoing packets when the 118 | /// destination link layer address is not known in the 119 | /// VXLAN device forwarding database. 120 | /// This function takes an IPv6 address. 121 | /// WARNING: only one between `remote` and `group` can be present. 122 | pub fn remote6(self, addr: std::net::Ipv6Addr) -> Self { 123 | self.group6(addr) 124 | } 125 | 126 | /// Adds the `local` attribute to the VXLAN 127 | /// This is equivalent to `ip link add name NAME type vxlan id VNI local 128 | /// IPADDR`, local IPADDR - specifies the source IP address to use in 129 | /// outgoing packets. This function takes an IPv4 address. 130 | pub fn local(self, addr: std::net::Ipv4Addr) -> Self { 131 | self.append_info_data(InfoVxlan::Local(addr)) 132 | } 133 | 134 | /// Adds the `local` attribute to the VXLAN 135 | /// This is equivalent to `ip link add name NAME type vxlan id VNI local 136 | /// IPADDR`, local IPADDR - specifies the source IP address to use in 137 | /// outgoing packets. This function takes an IPv6 address. 138 | pub fn local6(self, addr: std::net::Ipv6Addr) -> Self { 139 | self.append_info_data(InfoVxlan::Local6(addr)) 140 | } 141 | 142 | /// Adds the `tos` attribute to the VXLAN 143 | /// This is equivalent to `ip link add name NAME type vxlan id VNI tos TOS`. 144 | /// tos TOS - specifies the TOS value to use in outgoing packets. 145 | pub fn tos(self, tos: u8) -> Self { 146 | self.append_info_data(InfoVxlan::Tos(tos)) 147 | } 148 | 149 | /// Adds the `ttl` attribute to the VXLAN 150 | /// This is equivalent to `ip link add name NAME type vxlan id VNI ttl TTL`. 151 | /// ttl TTL - specifies the TTL value to use in outgoing packets. 152 | pub fn ttl(self, ttl: u8) -> Self { 153 | self.append_info_data(InfoVxlan::Ttl(ttl)) 154 | } 155 | 156 | /// Adds the `flowlabel` attribute to the VXLAN 157 | /// This is equivalent to `ip link add name NAME type vxlan id VNI flowlabel 158 | /// LABEL`. flowlabel LABEL - specifies the flow label to use in 159 | /// outgoing packets. 160 | pub fn label(self, label: u32) -> Self { 161 | self.append_info_data(InfoVxlan::Label(label)) 162 | } 163 | 164 | /// Adds the `learning` attribute to the VXLAN 165 | /// This is equivalent to `ip link add name NAME type vxlan id VNI 166 | /// \[no\]learning`. \[no\]learning - specifies if unknown source link layer 167 | /// addresses and IP addresses are entered into the VXLAN 168 | /// device forwarding database. 169 | pub fn learning(self, learning: bool) -> Self { 170 | self.append_info_data(InfoVxlan::Learning(learning)) 171 | } 172 | 173 | /// Adds the `ageing` attribute to the VXLAN 174 | /// This is equivalent to `ip link add name NAME type vxlan id VNI ageing 175 | /// SECONDS`. ageing SECONDS - specifies the lifetime in seconds of 176 | /// FDB entries learnt by the kernel. 177 | pub fn ageing(self, seconds: u32) -> Self { 178 | self.append_info_data(InfoVxlan::Ageing(seconds)) 179 | } 180 | 181 | /// Adds the `maxaddress` attribute to the VXLAN 182 | /// This is equivalent to `ip link add name NAME type vxlan id VNI 183 | /// maxaddress LIMIT`. maxaddress LIMIT - specifies the maximum number 184 | /// of FDB entries. 185 | pub fn limit(self, limit: u32) -> Self { 186 | self.append_info_data(InfoVxlan::Limit(limit)) 187 | } 188 | 189 | /// Adds the `srcport` attribute to the VXLAN 190 | /// This is equivalent to `ip link add name NAME type vxlan id VNI srcport 191 | /// MIN MAX`. srcport MIN MAX - specifies the range of port numbers 192 | /// to use as UDP source ports to communicate to the 193 | /// remote VXLAN tunnel endpoint. 194 | pub fn port_range(self, min: u16, max: u16) -> Self { 195 | self.append_info_data(InfoVxlan::PortRange((min, max))) 196 | } 197 | 198 | /// Adds the `proxy` attribute to the VXLAN 199 | /// This is equivalent to `ip link add name NAME type vxlan id VNI 200 | /// [no]proxy`. \[no\]proxy - specifies ARP proxy is turned on. 201 | pub fn proxy(self, proxy: bool) -> Self { 202 | self.append_info_data(InfoVxlan::Proxy(proxy)) 203 | } 204 | 205 | /// Adds the `rsc` attribute to the VXLAN This is equivalent to 206 | /// `ip link add name NAME type vxlan id VNI [no]rsc`. 207 | /// \[no\]rsc - specifies if route short circuit is turned on. 208 | pub fn rsc(self, rsc: bool) -> Self { 209 | self.append_info_data(InfoVxlan::Rsc(rsc)) 210 | } 211 | 212 | // Adds the `l2miss` attribute to the VXLAN 213 | /// This is equivalent to `ip link add name NAME type vxlan id VNI 214 | /// [no]l2miss`. \[no\]l2miss - specifies if netlink LLADDR miss 215 | /// notifications are generated. 216 | pub fn l2miss(self, l2miss: bool) -> Self { 217 | self.append_info_data(InfoVxlan::L2Miss(l2miss)) 218 | } 219 | 220 | // Adds the `l3miss` attribute to the VXLAN 221 | /// This is equivalent to `ip link add name NAME type vxlan id VNI 222 | /// [no]l3miss`. \[no\]l3miss - specifies if netlink IP ADDR 223 | /// miss notifications are generated. 224 | pub fn l3miss(self, l3miss: bool) -> Self { 225 | self.append_info_data(InfoVxlan::L3Miss(l3miss)) 226 | } 227 | 228 | pub fn collect_metadata(self, collect_metadata: bool) -> Self { 229 | self.append_info_data(InfoVxlan::CollectMetadata(collect_metadata)) 230 | } 231 | 232 | // Adds the `udp_csum` attribute to the VXLAN 233 | /// This is equivalent to `ip link add name NAME type vxlan id VNI 234 | /// [no]udp_csum`. \[no\]udpcsum - specifies if UDP checksum is 235 | /// calculated for transmitted packets over IPv4. 236 | pub fn udp_csum(self, udp_csum: bool) -> Self { 237 | self.append_info_data(InfoVxlan::UDPCsum(udp_csum)) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/link/wireguard.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{link::LinkMessageBuilder, packet_route::link::InfoKind}; 4 | 5 | /// Represent wireguard interface. 6 | /// Example code on creating a wireguard interface 7 | /// ```no_run 8 | /// use rtnetlink::{new_connection, LinkWireguard}; 9 | /// #[tokio::main] 10 | /// async fn main() -> Result<(), String> { 11 | /// let (connection, handle, _) = new_connection().unwrap(); 12 | /// tokio::spawn(connection); 13 | /// 14 | /// handle 15 | /// .link() 16 | /// .add(LinkWireguard::new("wg0").build()) 17 | /// .execute() 18 | /// .await 19 | /// .map_err(|e| format!("{e}")) 20 | /// } 21 | /// ``` 22 | /// 23 | /// Please check LinkMessageBuilder:: for more detail. 24 | #[derive(Debug)] 25 | pub struct LinkWireguard; 26 | 27 | impl LinkWireguard { 28 | /// Equal to `LinkMessageBuilder::::new()` 29 | pub fn new(name: &str) -> LinkMessageBuilder { 30 | LinkMessageBuilder::::new(name) 31 | } 32 | } 33 | 34 | impl LinkMessageBuilder { 35 | /// Create [LinkMessageBuilder] for wireguard 36 | pub fn new(name: &str) -> Self { 37 | LinkMessageBuilder::::new_with_info_kind( 38 | InfoKind::Wireguard, 39 | ) 40 | .name(name.to_string()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/link/xfrm.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{ 4 | link::LinkMessageBuilder, 5 | packet_route::link::{InfoData, InfoKind, InfoXfrm}, 6 | }; 7 | 8 | /// Represent XFRM interface. 9 | /// Example code on creating a XFRM interface 10 | /// ```no_run 11 | /// use rtnetlink::{new_connection, LinkXfrm}; 12 | /// #[tokio::main] 13 | /// async fn main() -> Result<(), String> { 14 | /// let (connection, handle, _) = new_connection().unwrap(); 15 | /// tokio::spawn(connection); 16 | /// 17 | /// handle 18 | /// .link() 19 | /// .add(LinkXfrm::new("xfrm8", 9, 0x08).build()) 20 | /// .execute() 21 | /// .await 22 | /// .map_err(|e| format!("{e}")) 23 | /// } 24 | /// ``` 25 | /// 26 | /// Please check LinkMessageBuilder:: for more detail. 27 | #[derive(Debug)] 28 | pub struct LinkXfrm; 29 | 30 | impl LinkXfrm { 31 | /// Equal to `LinkMessageBuilder::::new().dev().if_id()` 32 | pub fn new( 33 | name: &str, 34 | base_iface_index: u32, 35 | if_id: u32, 36 | ) -> LinkMessageBuilder { 37 | LinkMessageBuilder::::new(name) 38 | .dev(base_iface_index) 39 | .if_id(if_id) 40 | } 41 | } 42 | 43 | impl LinkMessageBuilder { 44 | /// Create [LinkMessageBuilder] for XFRM 45 | pub fn new(name: &str) -> Self { 46 | LinkMessageBuilder::::new_with_info_kind(InfoKind::Xfrm) 47 | .name(name.to_string()) 48 | } 49 | 50 | pub fn append_info_data(mut self, info: InfoXfrm) -> Self { 51 | if let InfoData::Xfrm(infos) = self 52 | .info_data 53 | .get_or_insert_with(|| InfoData::Xfrm(Vec::new())) 54 | { 55 | infos.push(info); 56 | } 57 | self 58 | } 59 | 60 | /// This is equivalent to the `if_id IF_ID` in command 61 | /// `ip link add name NAME type xfrm if_id IF_ID`. 62 | pub fn if_id(self, if_id: u32) -> Self { 63 | self.append_info_data(InfoXfrm::IfId(if_id)) 64 | } 65 | 66 | /// This is equivalent to the `dev PHYS_DEV` in command 67 | /// `ip link add name NAME type xfm dev PHYS_DEV`, only take the interface 68 | /// index. 69 | pub fn dev(self, iface_index: u32) -> Self { 70 | self.append_info_data(InfoXfrm::Link(iface_index)) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #[macro_export] 4 | macro_rules! try_rtnl { 5 | ($msg: expr, $message_type:path) => {{ 6 | use netlink_packet_core::{NetlinkMessage, NetlinkPayload}; 7 | use netlink_packet_route::RouteNetlinkMessage; 8 | use $crate::Error; 9 | 10 | let (header, payload) = $msg.into_parts(); 11 | match payload { 12 | NetlinkPayload::InnerMessage($message_type(msg)) => msg, 13 | NetlinkPayload::Error(err) => return Err(Error::NetlinkError(err)), 14 | _ => { 15 | return Err(Error::UnexpectedMessage(NetlinkMessage::new( 16 | header, payload, 17 | ))) 18 | } 19 | } 20 | }}; 21 | } 22 | 23 | #[macro_export] 24 | macro_rules! try_nl { 25 | ($msg: expr) => {{ 26 | use netlink_packet_core::NetlinkPayload; 27 | use $crate::Error; 28 | if let NetlinkPayload::Error(err) = $msg.payload { 29 | return Err(Error::NetlinkError(err)); 30 | } 31 | }}; 32 | } 33 | -------------------------------------------------------------------------------- /src/neighbour/add.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | 5 | use netlink_packet_core::{ 6 | NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, 7 | NLM_F_REPLACE, NLM_F_REQUEST, 8 | }; 9 | use netlink_packet_route::{ 10 | neighbour::{ 11 | NeighbourAddress, NeighbourAttribute, NeighbourFlags, NeighbourMessage, 12 | NeighbourState, 13 | }, 14 | route::RouteType, 15 | AddressFamily, RouteNetlinkMessage, 16 | }; 17 | 18 | use crate::{Error, Handle}; 19 | use std::net::IpAddr; 20 | 21 | pub struct NeighbourAddRequest { 22 | handle: Handle, 23 | message: NeighbourMessage, 24 | replace: bool, 25 | } 26 | 27 | impl NeighbourAddRequest { 28 | pub(crate) fn new(handle: Handle, index: u32, destination: IpAddr) -> Self { 29 | let mut message = NeighbourMessage::default(); 30 | 31 | message.header.family = match destination { 32 | IpAddr::V4(_) => AddressFamily::Inet, 33 | IpAddr::V6(_) => AddressFamily::Inet6, 34 | }; 35 | 36 | message.header.ifindex = index; 37 | message.header.state = NeighbourState::Permanent; 38 | message.header.kind = RouteType::Unspec; 39 | 40 | message.attributes.push(NeighbourAttribute::Destination( 41 | match destination { 42 | IpAddr::V4(v4) => NeighbourAddress::Inet(v4), 43 | IpAddr::V6(v6) => NeighbourAddress::Inet6(v6), 44 | }, 45 | )); 46 | 47 | NeighbourAddRequest { 48 | handle, 49 | message, 50 | replace: false, 51 | } 52 | } 53 | 54 | #[cfg(not(target_os = "freebsd"))] 55 | pub(crate) fn new_bridge(handle: Handle, index: u32, lla: &[u8]) -> Self { 56 | let mut message = NeighbourMessage::default(); 57 | 58 | message.header.family = AddressFamily::Bridge; 59 | message.header.ifindex = index; 60 | message.header.state = NeighbourState::Permanent; 61 | message.header.kind = RouteType::Unspec; 62 | 63 | message 64 | .attributes 65 | .push(NeighbourAttribute::LinkLocalAddress(lla.to_vec())); 66 | 67 | NeighbourAddRequest { 68 | handle, 69 | message, 70 | replace: false, 71 | } 72 | } 73 | 74 | /// Set a bitmask of states for the neighbor cache entry. 75 | /// It should be a combination of `NUD_*` constants. 76 | pub fn state(mut self, state: NeighbourState) -> Self { 77 | self.message.header.state = state; 78 | self 79 | } 80 | 81 | /// Set flags for the neighbor cache entry. 82 | /// It should be a combination of `NTF_*` constants. 83 | pub fn flags(mut self, flags: NeighbourFlags) -> Self { 84 | self.message.header.flags = flags; 85 | self 86 | } 87 | 88 | /// Set attributes applicable to the the neighbor cache entry. 89 | /// It should be one of `NDA_*` constants. 90 | pub fn kind(mut self, kind: RouteType) -> Self { 91 | self.message.header.kind = kind; 92 | self 93 | } 94 | 95 | /// Set a neighbor cache link layer address (see `NDA_LLADDR` for details). 96 | pub fn link_local_address(mut self, addr: &[u8]) -> Self { 97 | let lla = 98 | self.message 99 | .attributes 100 | .iter_mut() 101 | .find_map(|nla| match nla { 102 | NeighbourAttribute::LinkLocalAddress(lla) => Some(lla), 103 | _ => None, 104 | }); 105 | 106 | if let Some(lla) = lla { 107 | *lla = addr.to_vec(); 108 | } else { 109 | self.message 110 | .attributes 111 | .push(NeighbourAttribute::LinkLocalAddress(addr.to_vec())); 112 | } 113 | 114 | self 115 | } 116 | 117 | /// Set the destination address for the neighbour (see `NDA_DST` for 118 | /// details). 119 | pub fn destination(mut self, addr: IpAddr) -> Self { 120 | let dst = 121 | self.message 122 | .attributes 123 | .iter_mut() 124 | .find_map(|nla| match nla { 125 | NeighbourAttribute::Destination(dst) => Some(dst), 126 | _ => None, 127 | }); 128 | 129 | let addr = match addr { 130 | IpAddr::V4(v4) => NeighbourAddress::Inet(v4), 131 | IpAddr::V6(v6) => NeighbourAddress::Inet6(v6), 132 | }; 133 | 134 | if let Some(dst) = dst { 135 | *dst = addr; 136 | } else { 137 | self.message 138 | .attributes 139 | .push(NeighbourAttribute::Destination(addr)); 140 | } 141 | 142 | self 143 | } 144 | 145 | /// Replace existing matching neighbor. 146 | pub fn replace(self) -> Self { 147 | Self { 148 | replace: true, 149 | ..self 150 | } 151 | } 152 | 153 | /// Execute the request. 154 | pub async fn execute(self) -> Result<(), Error> { 155 | let NeighbourAddRequest { 156 | mut handle, 157 | message, 158 | replace, 159 | } = self; 160 | 161 | let mut req = 162 | NetlinkMessage::from(RouteNetlinkMessage::NewNeighbour(message)); 163 | let replace = if replace { NLM_F_REPLACE } else { NLM_F_EXCL }; 164 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK | replace | NLM_F_CREATE; 165 | 166 | let mut response = handle.request(req)?; 167 | while let Some(message) = response.next().await { 168 | if let NetlinkPayload::Error(err) = message.payload { 169 | return Err(Error::NetlinkError(err)); 170 | } 171 | } 172 | 173 | Ok(()) 174 | } 175 | 176 | /// Return a mutable reference to the request message. 177 | pub fn message_mut(&mut self) -> &mut NeighbourMessage { 178 | &mut self.message 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/neighbour/del.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use netlink_packet_core::{ 5 | NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_REQUEST, 6 | }; 7 | use netlink_packet_route::{neighbour::NeighbourMessage, RouteNetlinkMessage}; 8 | 9 | use crate::{Error, Handle}; 10 | 11 | pub struct NeighbourDelRequest { 12 | handle: Handle, 13 | message: NeighbourMessage, 14 | } 15 | 16 | impl NeighbourDelRequest { 17 | pub(crate) fn new(handle: Handle, message: NeighbourMessage) -> Self { 18 | NeighbourDelRequest { handle, message } 19 | } 20 | 21 | /// Execute the request 22 | pub async fn execute(self) -> Result<(), Error> { 23 | let NeighbourDelRequest { 24 | mut handle, 25 | message, 26 | } = self; 27 | 28 | let mut req = 29 | NetlinkMessage::from(RouteNetlinkMessage::DelNeighbour(message)); 30 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK; 31 | let mut response = handle.request(req)?; 32 | while let Some(msg) = response.next().await { 33 | if let NetlinkPayload::Error(e) = msg.payload { 34 | return Err(Error::NetlinkError(e)); 35 | } 36 | } 37 | Ok(()) 38 | } 39 | 40 | pub fn message_mut(&mut self) -> &mut NeighbourMessage { 41 | &mut self.message 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/neighbour/get.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::{ 4 | future::{self, Either}, 5 | stream::{StreamExt, TryStream}, 6 | FutureExt, 7 | }; 8 | use netlink_packet_core::{ 9 | NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, 10 | }; 11 | use netlink_packet_route::{ 12 | neighbour::{NeighbourFlags, NeighbourMessage}, 13 | RouteNetlinkMessage, 14 | }; 15 | 16 | use crate::{Error, Handle, IpVersion}; 17 | 18 | pub struct NeighbourGetRequest { 19 | handle: Handle, 20 | message: NeighbourMessage, 21 | } 22 | 23 | impl NeighbourGetRequest { 24 | pub(crate) fn new(handle: Handle) -> Self { 25 | let message = NeighbourMessage::default(); 26 | NeighbourGetRequest { handle, message } 27 | } 28 | 29 | /// List neighbor proxies in the system (equivalent to: `ip neighbor show 30 | /// proxy`). 31 | pub fn proxies(mut self) -> Self { 32 | self.message.header.flags |= NeighbourFlags::Proxy; 33 | self 34 | } 35 | 36 | pub fn set_family(mut self, ip_version: IpVersion) -> Self { 37 | self.message.header.family = ip_version.family(); 38 | self 39 | } 40 | 41 | /// Execute the request 42 | pub fn execute( 43 | self, 44 | ) -> impl TryStream { 45 | let NeighbourGetRequest { 46 | mut handle, 47 | message, 48 | } = self; 49 | 50 | let mut req = 51 | NetlinkMessage::from(RouteNetlinkMessage::GetNeighbour(message)); 52 | req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; 53 | 54 | match handle.request(req) { 55 | Ok(response) => Either::Left(response.map(move |msg| { 56 | let (header, payload) = msg.into_parts(); 57 | match payload { 58 | NetlinkPayload::InnerMessage( 59 | RouteNetlinkMessage::NewNeighbour(msg), 60 | ) => Ok(msg), 61 | NetlinkPayload::Error(err) => Err(Error::NetlinkError(err)), 62 | _ => Err(Error::UnexpectedMessage(NetlinkMessage::new( 63 | header, payload, 64 | ))), 65 | } 66 | })), 67 | Err(e) => Either::Right( 68 | future::err::(e).into_stream(), 69 | ), 70 | } 71 | } 72 | 73 | /// Return a mutable reference to the request 74 | pub fn message_mut(&mut self) -> &mut NeighbourMessage { 75 | &mut self.message 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/neighbour/handle.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{ 4 | Handle, NeighbourAddRequest, NeighbourDelRequest, NeighbourGetRequest, 5 | }; 6 | use netlink_packet_route::neighbour::NeighbourMessage; 7 | use std::net::IpAddr; 8 | 9 | pub struct NeighbourHandle(Handle); 10 | 11 | impl NeighbourHandle { 12 | pub fn new(handle: Handle) -> Self { 13 | NeighbourHandle(handle) 14 | } 15 | 16 | /// List neighbour entries (equivalent to `ip neighbour show`) 17 | pub fn get(&self) -> NeighbourGetRequest { 18 | NeighbourGetRequest::new(self.0.clone()) 19 | } 20 | 21 | /// Add a new neighbour entry (equivalent to `ip neighbour add`) 22 | pub fn add(&self, index: u32, destination: IpAddr) -> NeighbourAddRequest { 23 | NeighbourAddRequest::new(self.0.clone(), index, destination) 24 | } 25 | 26 | #[cfg(not(target_os = "freebsd"))] 27 | /// Add a new fdb entry (equivalent to `bridge fdb add`) 28 | pub fn add_bridge(&self, index: u32, lla: &[u8]) -> NeighbourAddRequest { 29 | NeighbourAddRequest::new_bridge(self.0.clone(), index, lla) 30 | } 31 | 32 | /// Delete a neighbour entry (equivalent to `ip neighbour delete`) 33 | pub fn del(&self, message: NeighbourMessage) -> NeighbourDelRequest { 34 | NeighbourDelRequest::new(self.0.clone(), message) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/neighbour/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | mod add; 4 | mod del; 5 | mod get; 6 | mod handle; 7 | 8 | pub use self::add::NeighbourAddRequest; 9 | pub use self::del::NeighbourDelRequest; 10 | pub use self::get::NeighbourGetRequest; 11 | pub use self::handle::NeighbourHandle; 12 | -------------------------------------------------------------------------------- /src/route/add.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use std::{marker::PhantomData, net::IpAddr}; 5 | 6 | use netlink_packet_core::{ 7 | NetlinkMessage, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE, 8 | NLM_F_REQUEST, 9 | }; 10 | use netlink_packet_route::{route::RouteMessage, RouteNetlinkMessage}; 11 | 12 | use crate::{try_nl, Error, Handle}; 13 | 14 | /// A request to create a new route. This is equivalent to the `ip route add` 15 | /// commands. 16 | #[derive(Debug, Clone)] 17 | pub struct RouteAddRequest { 18 | handle: Handle, 19 | message: RouteMessage, 20 | replace: bool, 21 | _phantom: PhantomData, 22 | } 23 | 24 | impl RouteAddRequest { 25 | pub(crate) fn new(handle: Handle, message: RouteMessage) -> Self { 26 | RouteAddRequest { 27 | handle, 28 | message, 29 | replace: false, 30 | _phantom: Default::default(), 31 | } 32 | } 33 | 34 | pub fn message_mut(&mut self) -> &mut RouteMessage { 35 | &mut self.message 36 | } 37 | 38 | /// Replace existing matching route. 39 | pub fn replace(self) -> Self { 40 | Self { 41 | replace: true, 42 | ..self 43 | } 44 | } 45 | 46 | /// Execute the request. 47 | pub async fn execute(self) -> Result<(), Error> { 48 | let RouteAddRequest { 49 | mut handle, 50 | message, 51 | replace, 52 | .. 53 | } = self; 54 | let mut req = 55 | NetlinkMessage::from(RouteNetlinkMessage::NewRoute(message)); 56 | let replace = if replace { NLM_F_REPLACE } else { NLM_F_EXCL }; 57 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK | replace | NLM_F_CREATE; 58 | 59 | let mut response = handle.request(req)?; 60 | while let Some(message) = response.next().await { 61 | try_nl!(message); 62 | } 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/route/del.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use netlink_packet_core::{ 5 | NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_REQUEST, 6 | }; 7 | use netlink_packet_route::{route::RouteMessage, RouteNetlinkMessage}; 8 | 9 | use crate::{Error, Handle}; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct RouteDelRequest { 13 | handle: Handle, 14 | message: RouteMessage, 15 | } 16 | 17 | impl RouteDelRequest { 18 | pub(crate) fn new(handle: Handle, message: RouteMessage) -> Self { 19 | RouteDelRequest { handle, message } 20 | } 21 | 22 | /// Execute the request 23 | pub async fn execute(self) -> Result<(), Error> { 24 | let RouteDelRequest { 25 | mut handle, 26 | message, 27 | } = self; 28 | 29 | let mut req = 30 | NetlinkMessage::from(RouteNetlinkMessage::DelRoute(message)); 31 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK; 32 | let mut response = handle.request(req)?; 33 | while let Some(msg) = response.next().await { 34 | if let NetlinkPayload::Error(e) = msg.payload { 35 | return Err(Error::NetlinkError(e)); 36 | } 37 | } 38 | Ok(()) 39 | } 40 | 41 | pub fn message_mut(&mut self) -> &mut RouteMessage { 42 | &mut self.message 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/route/get.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::{ 4 | future::{self, Either}, 5 | stream::{StreamExt, TryStream}, 6 | FutureExt, 7 | }; 8 | 9 | use netlink_packet_core::{NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST}; 10 | use netlink_packet_route::{ 11 | route::RouteMessage, AddressFamily, RouteNetlinkMessage, 12 | }; 13 | 14 | use crate::{try_rtnl, Error, Handle}; 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct RouteGetRequest { 18 | handle: Handle, 19 | message: RouteMessage, 20 | } 21 | 22 | /// Internet Protocol (IP) version. 23 | #[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] 24 | pub enum IpVersion { 25 | /// IPv4 26 | V4, 27 | /// IPv6 28 | V6, 29 | } 30 | 31 | impl IpVersion { 32 | pub(crate) fn family(self) -> AddressFamily { 33 | match self { 34 | IpVersion::V4 => AddressFamily::Inet, 35 | IpVersion::V6 => AddressFamily::Inet6, 36 | } 37 | } 38 | } 39 | 40 | impl RouteGetRequest { 41 | pub(crate) fn new(handle: Handle, message: RouteMessage) -> Self { 42 | RouteGetRequest { handle, message } 43 | } 44 | 45 | pub fn message_mut(&mut self) -> &mut RouteMessage { 46 | &mut self.message 47 | } 48 | 49 | pub fn execute(self) -> impl TryStream { 50 | let RouteGetRequest { 51 | mut handle, 52 | message, 53 | } = self; 54 | 55 | let mut req = 56 | NetlinkMessage::from(RouteNetlinkMessage::GetRoute(message)); 57 | req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; 58 | 59 | match handle.request(req) { 60 | Ok(response) => Either::Left(response.map(move |msg| { 61 | Ok(try_rtnl!(msg, RouteNetlinkMessage::NewRoute)) 62 | })), 63 | Err(e) => Either::Right( 64 | future::err::(e).into_stream(), 65 | ), 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/route/handle.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{Handle, RouteAddRequest, RouteDelRequest, RouteGetRequest}; 4 | use netlink_packet_route::route::RouteMessage; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct RouteHandle(Handle); 8 | 9 | impl RouteHandle { 10 | pub fn new(handle: Handle) -> Self { 11 | RouteHandle(handle) 12 | } 13 | 14 | /// Retrieve the list of routing table entries (equivalent to `ip route 15 | /// show`) 16 | /// The `RouteMessage` could be built by [crate::RouteMessageBuilder]. 17 | /// In order to perform kernel side filter, please enable 18 | /// `NETLINK_GET_STRICT_CHK` via 19 | /// `rtnetlink::sys::Socket::set_netlink_get_strict_chk(true)`. 20 | pub fn get(&self, route: RouteMessage) -> RouteGetRequest { 21 | RouteGetRequest::new(self.0.clone(), route) 22 | } 23 | 24 | /// Add an routing table entry (equivalent to `ip route add`) 25 | /// The `RouteMessage` could be built by [crate::RouteMessageBuilder]. 26 | pub fn add(&self, route: RouteMessage) -> RouteAddRequest { 27 | RouteAddRequest::new(self.0.clone(), route) 28 | } 29 | 30 | /// Delete the given routing table entry (equivalent to `ip route del`) 31 | /// The `RouteMessage` could be built by [crate::RouteMessageBuilder]. 32 | pub fn del(&self, route: RouteMessage) -> RouteDelRequest { 33 | RouteDelRequest::new(self.0.clone(), route) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/route/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | mod add; 4 | mod builder; 5 | mod del; 6 | mod get; 7 | mod handle; 8 | 9 | pub use self::add::RouteAddRequest; 10 | pub use self::builder::RouteMessageBuilder; 11 | pub use self::builder::RouteNextHopBuilder; 12 | pub use self::del::RouteDelRequest; 13 | pub use self::get::{IpVersion, RouteGetRequest}; 14 | pub use self::handle::RouteHandle; 15 | -------------------------------------------------------------------------------- /src/rule/add.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use std::{ 5 | marker::PhantomData, 6 | net::{Ipv4Addr, Ipv6Addr}, 7 | }; 8 | 9 | use netlink_packet_core::{ 10 | NetlinkMessage, NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE, 11 | NLM_F_REQUEST, 12 | }; 13 | 14 | use netlink_packet_route::{ 15 | route::RouteHeader, 16 | rule::{RuleAction, RuleAttribute, RuleMessage}, 17 | AddressFamily, RouteNetlinkMessage, 18 | }; 19 | 20 | use crate::{try_nl, Error, Handle}; 21 | 22 | /// A request to create a new rule. This is equivalent to the `ip rule add` 23 | /// command. 24 | #[derive(Debug, Clone)] 25 | pub struct RuleAddRequest { 26 | handle: Handle, 27 | message: RuleMessage, 28 | replace: bool, 29 | _phantom: PhantomData, 30 | } 31 | 32 | impl RuleAddRequest { 33 | pub(crate) fn new(handle: Handle) -> Self { 34 | let mut message = RuleMessage::default(); 35 | 36 | message.header.table = RouteHeader::RT_TABLE_MAIN; 37 | message.header.action = RuleAction::Unspec; 38 | 39 | RuleAddRequest { 40 | handle, 41 | message, 42 | replace: false, 43 | _phantom: Default::default(), 44 | } 45 | } 46 | 47 | /// Sets the input interface name. 48 | pub fn input_interface(mut self, ifname: String) -> Self { 49 | self.message.attributes.push(RuleAttribute::Iifname(ifname)); 50 | self 51 | } 52 | 53 | /// Sets the output interface name. 54 | pub fn output_interface(mut self, ifname: String) -> Self { 55 | self.message.attributes.push(RuleAttribute::Oifname(ifname)); 56 | self 57 | } 58 | 59 | /// Sets the rule table. 60 | /// 61 | /// Default is main rule table. 62 | #[deprecated(note = "Please use `table_id` instead")] 63 | pub fn table(mut self, table: u8) -> Self { 64 | self.message.header.table = table; 65 | self 66 | } 67 | 68 | /// Sets the rule table ID. 69 | /// 70 | /// Default is main rule table. 71 | pub fn table_id(mut self, table: u32) -> Self { 72 | if table > 255 { 73 | self.message.attributes.push(RuleAttribute::Table(table)); 74 | } else { 75 | self.message.header.table = table as u8; 76 | } 77 | self 78 | } 79 | 80 | /// Set the tos. 81 | pub fn tos(mut self, tos: u8) -> Self { 82 | self.message.header.tos = tos; 83 | self 84 | } 85 | 86 | /// Set action. 87 | pub fn action(mut self, action: RuleAction) -> Self { 88 | self.message.header.action = action; 89 | self 90 | } 91 | 92 | /// Set the priority. 93 | pub fn priority(mut self, priority: u32) -> Self { 94 | self.message 95 | .attributes 96 | .push(RuleAttribute::Priority(priority)); 97 | self 98 | } 99 | 100 | /// Set the fwmark 101 | pub fn fw_mark(mut self, fw_mark: u32) -> Self { 102 | self.message.attributes.push(RuleAttribute::FwMark(fw_mark)); 103 | self 104 | } 105 | 106 | /// Build an IP v4 rule 107 | pub fn v4(mut self) -> RuleAddRequest { 108 | self.message.header.family = AddressFamily::Inet; 109 | RuleAddRequest { 110 | handle: self.handle, 111 | message: self.message, 112 | replace: false, 113 | _phantom: Default::default(), 114 | } 115 | } 116 | 117 | /// Build an IP v6 rule 118 | pub fn v6(mut self) -> RuleAddRequest { 119 | self.message.header.family = AddressFamily::Inet6; 120 | RuleAddRequest { 121 | handle: self.handle, 122 | message: self.message, 123 | replace: false, 124 | _phantom: Default::default(), 125 | } 126 | } 127 | 128 | /// Replace existing matching rule. 129 | pub fn replace(self) -> Self { 130 | Self { 131 | replace: true, 132 | ..self 133 | } 134 | } 135 | 136 | /// Execute the request. 137 | pub async fn execute(self) -> Result<(), Error> { 138 | let RuleAddRequest { 139 | mut handle, 140 | message, 141 | replace, 142 | .. 143 | } = self; 144 | let mut req = 145 | NetlinkMessage::from(RouteNetlinkMessage::NewRule(message)); 146 | let replace = if replace { NLM_F_REPLACE } else { NLM_F_EXCL }; 147 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK | replace | NLM_F_CREATE; 148 | 149 | let mut response = handle.request(req)?; 150 | while let Some(message) = response.next().await { 151 | try_nl!(message); 152 | } 153 | 154 | Ok(()) 155 | } 156 | 157 | pub fn message_mut(&mut self) -> &mut RuleMessage { 158 | &mut self.message 159 | } 160 | } 161 | 162 | impl RuleAddRequest { 163 | /// Sets the source address prefix. 164 | pub fn source_prefix(mut self, addr: Ipv4Addr, prefix_length: u8) -> Self { 165 | self.message.header.src_len = prefix_length; 166 | self.message 167 | .attributes 168 | .push(RuleAttribute::Source(addr.into())); 169 | self 170 | } 171 | 172 | /// Sets the destination address prefix. 173 | pub fn destination_prefix( 174 | mut self, 175 | addr: Ipv4Addr, 176 | prefix_length: u8, 177 | ) -> Self { 178 | self.message.header.dst_len = prefix_length; 179 | self.message 180 | .attributes 181 | .push(RuleAttribute::Destination(addr.into())); 182 | self 183 | } 184 | } 185 | 186 | impl RuleAddRequest { 187 | /// Sets the source address prefix. 188 | pub fn source_prefix(mut self, addr: Ipv6Addr, prefix_length: u8) -> Self { 189 | self.message.header.src_len = prefix_length; 190 | self.message 191 | .attributes 192 | .push(RuleAttribute::Source(addr.into())); 193 | self 194 | } 195 | 196 | /// Sets the destination address prefix. 197 | pub fn destination_prefix( 198 | mut self, 199 | addr: Ipv6Addr, 200 | prefix_length: u8, 201 | ) -> Self { 202 | self.message.header.dst_len = prefix_length; 203 | self.message 204 | .attributes 205 | .push(RuleAttribute::Destination(addr.into())); 206 | self 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/rule/del.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}; 5 | use netlink_packet_route::{rule::RuleMessage, RouteNetlinkMessage}; 6 | 7 | use crate::{try_nl, Error, Handle}; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct RuleDelRequest { 11 | handle: Handle, 12 | message: RuleMessage, 13 | } 14 | 15 | impl RuleDelRequest { 16 | pub(crate) fn new(handle: Handle, message: RuleMessage) -> Self { 17 | RuleDelRequest { handle, message } 18 | } 19 | 20 | /// Execute the request 21 | pub async fn execute(self) -> Result<(), Error> { 22 | let RuleDelRequest { 23 | mut handle, 24 | message, 25 | } = self; 26 | 27 | let mut req = 28 | NetlinkMessage::from(RouteNetlinkMessage::DelRule(message)); 29 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK; 30 | let mut response = handle.request(req)?; 31 | while let Some(msg) = response.next().await { 32 | try_nl!(msg); 33 | } 34 | Ok(()) 35 | } 36 | 37 | pub fn message_mut(&mut self) -> &mut RuleMessage { 38 | &mut self.message 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/rule/get.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::{ 4 | future::{self, Either}, 5 | stream::{StreamExt, TryStream}, 6 | FutureExt, 7 | }; 8 | use netlink_packet_core::{NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST}; 9 | use netlink_packet_route::{ 10 | route::RouteHeader, 11 | rule::{RuleAction, RuleMessage}, 12 | RouteNetlinkMessage, 13 | }; 14 | 15 | use crate::{try_rtnl, Error, Handle, IpVersion}; 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct RuleGetRequest { 19 | handle: Handle, 20 | message: RuleMessage, 21 | } 22 | 23 | impl RuleGetRequest { 24 | pub(crate) fn new(handle: Handle, ip_version: IpVersion) -> Self { 25 | let mut message = RuleMessage::default(); 26 | message.header.family = ip_version.family(); 27 | 28 | message.header.dst_len = 0; 29 | message.header.src_len = 0; 30 | message.header.tos = 0; 31 | message.header.action = RuleAction::Unspec; 32 | message.header.table = RouteHeader::RT_TABLE_UNSPEC; 33 | 34 | RuleGetRequest { handle, message } 35 | } 36 | 37 | pub fn message_mut(&mut self) -> &mut RuleMessage { 38 | &mut self.message 39 | } 40 | 41 | pub fn execute(self) -> impl TryStream { 42 | let RuleGetRequest { 43 | mut handle, 44 | message, 45 | } = self; 46 | 47 | let mut req = 48 | NetlinkMessage::from(RouteNetlinkMessage::GetRule(message)); 49 | req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; 50 | 51 | match handle.request(req) { 52 | Ok(response) => Either::Left(response.map(move |msg| { 53 | Ok(try_rtnl!(msg, RouteNetlinkMessage::NewRule)) 54 | })), 55 | Err(e) => Either::Right( 56 | future::err::(e).into_stream(), 57 | ), 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/rule/handle.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::{ 4 | Handle, IpVersion, RuleAddRequest, RuleDelRequest, RuleGetRequest, 5 | }; 6 | use netlink_packet_route::rule::RuleMessage; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct RuleHandle(Handle); 10 | 11 | impl RuleHandle { 12 | pub fn new(handle: Handle) -> Self { 13 | RuleHandle(handle) 14 | } 15 | 16 | /// Retrieve the list of route rule entries (equivalent to `ip rule show`) 17 | pub fn get(&self, ip_version: IpVersion) -> RuleGetRequest { 18 | RuleGetRequest::new(self.0.clone(), ip_version) 19 | } 20 | 21 | /// Add a route rule entry (equivalent to `ip rule add`) 22 | pub fn add(&self) -> RuleAddRequest { 23 | RuleAddRequest::new(self.0.clone()) 24 | } 25 | 26 | /// Delete the given route rule entry (equivalent to `ip rule del`) 27 | pub fn del(&self, rule: RuleMessage) -> RuleDelRequest { 28 | RuleDelRequest::new(self.0.clone(), rule) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/rule/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | mod add; 4 | mod del; 5 | mod get; 6 | mod handle; 7 | 8 | pub use self::add::RuleAddRequest; 9 | pub use self::del::RuleDelRequest; 10 | pub use self::get::RuleGetRequest; 11 | pub use self::handle::RuleHandle; 12 | -------------------------------------------------------------------------------- /src/traffic_control/add_qdisc.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | 5 | use crate::{ 6 | packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}, 7 | packet_route::{ 8 | tc::{TcAttribute, TcHandle, TcMessage}, 9 | RouteNetlinkMessage, 10 | }, 11 | try_nl, Error, Handle, 12 | }; 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct QDiscNewRequest { 16 | handle: Handle, 17 | message: TcMessage, 18 | flags: u16, 19 | } 20 | 21 | impl QDiscNewRequest { 22 | pub(crate) fn new(handle: Handle, message: TcMessage, flags: u16) -> Self { 23 | Self { 24 | handle, 25 | message, 26 | flags: NLM_F_REQUEST | flags, 27 | } 28 | } 29 | 30 | /// Execute the request 31 | pub async fn execute(self) -> Result<(), Error> { 32 | let Self { 33 | mut handle, 34 | message, 35 | flags, 36 | } = self; 37 | 38 | let mut req = NetlinkMessage::from( 39 | RouteNetlinkMessage::NewQueueDiscipline(message), 40 | ); 41 | req.header.flags = NLM_F_ACK | flags; 42 | 43 | let mut response = handle.request(req)?; 44 | while let Some(message) = response.next().await { 45 | try_nl!(message); 46 | } 47 | Ok(()) 48 | } 49 | 50 | /// Set handle, 51 | pub fn handle(mut self, major: u16, minor: u16) -> Self { 52 | self.message.header.handle = TcHandle { major, minor }; 53 | self 54 | } 55 | 56 | /// Set parent to root. 57 | pub fn root(mut self) -> Self { 58 | self.message.header.parent = TcHandle::ROOT; 59 | self 60 | } 61 | 62 | /// Set parent 63 | pub fn parent(mut self, parent: u32) -> Self { 64 | self.message.header.parent = parent.into(); 65 | self 66 | } 67 | 68 | /// New a ingress qdisc 69 | pub fn ingress(mut self) -> Self { 70 | self.message.header.parent = TcHandle::INGRESS; 71 | self.message.header.handle = TcHandle::from(0xffff0000); 72 | self.message 73 | .attributes 74 | .push(TcAttribute::Kind("ingress".to_string())); 75 | self 76 | } 77 | } 78 | 79 | #[cfg(test)] 80 | mod test { 81 | use std::{fs::File, os::fd::AsFd, path::Path}; 82 | 83 | use futures::stream::TryStreamExt; 84 | use nix::sched::{setns, CloneFlags}; 85 | use tokio::runtime::Runtime; 86 | 87 | use super::*; 88 | use crate::{ 89 | new_connection, 90 | packet_route::{link::LinkMessage, AddressFamily}, 91 | LinkDummy, NetworkNamespace, NETNS_PATH, SELF_NS_PATH, 92 | }; 93 | 94 | const TEST_NS: &str = "netlink_test_qdisc_ns"; 95 | const TEST_DUMMY: &str = "test_dummy"; 96 | 97 | struct Netns { 98 | path: String, 99 | _cur: File, 100 | last: File, 101 | } 102 | 103 | impl Netns { 104 | async fn new(path: &str) -> Self { 105 | // record current ns 106 | let last = File::open(Path::new(SELF_NS_PATH)).unwrap(); 107 | 108 | // create new ns 109 | NetworkNamespace::add(path.to_string()).await.unwrap(); 110 | 111 | // entry new ns 112 | let ns_path = Path::new(NETNS_PATH); 113 | let file = File::open(ns_path.join(path)).unwrap(); 114 | setns(file.as_fd(), CloneFlags::CLONE_NEWNET).unwrap(); 115 | 116 | Self { 117 | path: path.to_string(), 118 | _cur: file, 119 | last, 120 | } 121 | } 122 | } 123 | impl Drop for Netns { 124 | fn drop(&mut self) { 125 | println!("exit ns: {}", self.path); 126 | setns(self.last.as_fd(), CloneFlags::CLONE_NEWNET).unwrap(); 127 | 128 | let ns_path = Path::new(NETNS_PATH).join(&self.path); 129 | nix::mount::umount2(&ns_path, nix::mount::MntFlags::MNT_DETACH) 130 | .unwrap(); 131 | nix::unistd::unlink(&ns_path).unwrap(); 132 | // _cur File will be closed auto 133 | // Since there is no async drop, NetworkNamespace::del cannot be 134 | // called here. Dummy interface will be deleted 135 | // automatically after netns is deleted. 136 | } 137 | } 138 | 139 | async fn setup_env() -> (Handle, LinkMessage, Netns) { 140 | let netns = Netns::new(TEST_NS).await; 141 | 142 | // Notice: The Handle can only be created after the setns, so that the 143 | // Handle is the connection within the new ns. 144 | let (connection, handle, _) = new_connection().unwrap(); 145 | tokio::spawn(connection); 146 | handle 147 | .link() 148 | .add(LinkDummy::new(TEST_DUMMY).up().build()) 149 | .execute() 150 | .await 151 | .unwrap(); 152 | let mut links = handle 153 | .link() 154 | .get() 155 | .match_name(TEST_DUMMY.to_string()) 156 | .execute(); 157 | let link = links.try_next().await.unwrap(); 158 | (handle, link.unwrap(), netns) 159 | } 160 | 161 | async fn test_async_new_qdisc() { 162 | let (handle, test_link, _netns) = setup_env().await; 163 | handle 164 | .qdisc() 165 | .add(test_link.header.index as i32) 166 | .ingress() 167 | .execute() 168 | .await 169 | .unwrap(); 170 | let mut qdiscs_iter = handle 171 | .qdisc() 172 | .get() 173 | .index(test_link.header.index as i32) 174 | .ingress() 175 | .execute(); 176 | 177 | let mut found = false; 178 | while let Some(nl_msg) = qdiscs_iter.try_next().await.unwrap() { 179 | if nl_msg.header.index == test_link.header.index as i32 180 | && nl_msg.header.handle == 0xffff0000.into() 181 | { 182 | assert_eq!(nl_msg.header.family, AddressFamily::Unspec); 183 | assert_eq!(nl_msg.header.handle, 0xffff0000.into()); 184 | assert_eq!(nl_msg.header.parent, TcHandle::INGRESS); 185 | assert_eq!(nl_msg.header.info, 1); // refcount 186 | assert_eq!( 187 | nl_msg.attributes[0], 188 | TcAttribute::Kind("ingress".to_string()) 189 | ); 190 | assert_eq!(nl_msg.attributes[2], TcAttribute::HwOffload(0)); 191 | found = true; 192 | break; 193 | } 194 | } 195 | if !found { 196 | panic!("not found dev:{} qdisc.", test_link.header.index); 197 | } 198 | } 199 | 200 | #[test] 201 | fn test_new_qdisc() { 202 | Runtime::new().unwrap().block_on(test_async_new_qdisc()); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/traffic_control/del_filter.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::stream::StreamExt; 4 | use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}; 5 | use netlink_packet_route::{ 6 | tc::{TcHandle, TcMessage}, 7 | RouteNetlinkMessage, 8 | }; 9 | 10 | use crate::{try_nl, Error, Handle}; 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct TrafficFilterDelRequest { 14 | handle: Handle, 15 | message: TcMessage, 16 | flags: u16, 17 | } 18 | 19 | impl TrafficFilterDelRequest { 20 | pub(crate) fn new(handle: Handle, ifindex: i32) -> Self { 21 | Self { 22 | handle, 23 | message: TcMessage::with_index(ifindex), 24 | flags: NLM_F_REQUEST | NLM_F_ACK, 25 | } 26 | } 27 | 28 | /// Execute the request 29 | pub async fn execute(self) -> Result<(), Error> { 30 | let Self { 31 | mut handle, 32 | message, 33 | flags, 34 | } = self; 35 | 36 | let mut req = NetlinkMessage::from( 37 | RouteNetlinkMessage::DelTrafficFilter(message), 38 | ); 39 | req.header.flags = flags; 40 | 41 | let mut response = handle.request(req)?; 42 | if let Some(message) = response.next().await { 43 | try_nl!(message); 44 | } 45 | 46 | Ok(()) 47 | } 48 | 49 | /// Set parent. 50 | /// Equivalent to `[ root | ingress | egress | parent CLASSID ]` 51 | /// command args. They are mutually exclusive. 52 | pub fn parent(mut self, parent: u32) -> Self { 53 | self.message.header.parent = parent.into(); 54 | self 55 | } 56 | 57 | /// Set parent to root. 58 | pub fn root(mut self) -> Self { 59 | self.message.header.parent = TcHandle::ROOT; 60 | self 61 | } 62 | 63 | /// Set parent to ingress. 64 | pub fn ingress(mut self) -> Self { 65 | self.message.header.parent = TcHandle { 66 | major: 0xffff, 67 | minor: TcHandle::MIN_INGRESS, 68 | }; 69 | self 70 | } 71 | 72 | /// Set parent to egress. 73 | pub fn egress(mut self) -> Self { 74 | self.message.header.parent = TcHandle { 75 | major: 0xffff, 76 | minor: TcHandle::MIN_EGRESS, 77 | }; 78 | self 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/traffic_control/del_qdisc.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::StreamExt; 4 | use netlink_packet_core::{NetlinkMessage, NLM_F_ACK, NLM_F_REQUEST}; 5 | use netlink_packet_route::{tc::TcMessage, RouteNetlinkMessage}; 6 | 7 | use crate::{try_nl, Error, Handle}; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct QDiscDelRequest { 11 | handle: Handle, 12 | message: TcMessage, 13 | } 14 | 15 | impl QDiscDelRequest { 16 | pub(crate) fn new(handle: Handle, message: TcMessage) -> Self { 17 | QDiscDelRequest { handle, message } 18 | } 19 | 20 | // Execute the request 21 | pub async fn execute(self) -> Result<(), Error> { 22 | let QDiscDelRequest { 23 | mut handle, 24 | message, 25 | } = self; 26 | 27 | let mut req = NetlinkMessage::from( 28 | RouteNetlinkMessage::DelQueueDiscipline(message), 29 | ); 30 | req.header.flags = NLM_F_REQUEST | NLM_F_ACK; 31 | 32 | let mut response = handle.request(req)?; 33 | while let Some(message) = response.next().await { 34 | try_nl!(message) 35 | } 36 | Ok(()) 37 | } 38 | 39 | /// Return a mutable reference to the request 40 | pub fn message_mut(&mut self) -> &mut TcMessage { 41 | &mut self.message 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/traffic_control/get.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use futures::{ 4 | future::{self, Either}, 5 | stream::{StreamExt, TryStream}, 6 | FutureExt, 7 | }; 8 | use netlink_packet_core::{NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST}; 9 | use netlink_packet_route::{ 10 | tc::{TcHandle, TcMessage}, 11 | RouteNetlinkMessage, 12 | }; 13 | 14 | use crate::{try_rtnl, Error, Handle}; 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct QDiscGetRequest { 18 | handle: Handle, 19 | message: TcMessage, 20 | } 21 | 22 | impl QDiscGetRequest { 23 | pub(crate) fn new(handle: Handle) -> Self { 24 | QDiscGetRequest { 25 | handle, 26 | message: TcMessage::default(), 27 | } 28 | } 29 | 30 | /// Execute the request 31 | pub fn execute(self) -> impl TryStream { 32 | let QDiscGetRequest { 33 | mut handle, 34 | message, 35 | } = self; 36 | 37 | let mut req = NetlinkMessage::from( 38 | RouteNetlinkMessage::GetQueueDiscipline(message), 39 | ); 40 | req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; 41 | 42 | match handle.request(req) { 43 | Ok(response) => Either::Left(response.map(move |msg| { 44 | Ok(try_rtnl!(msg, RouteNetlinkMessage::NewQueueDiscipline)) 45 | })), 46 | Err(e) => { 47 | Either::Right(future::err::(e).into_stream()) 48 | } 49 | } 50 | } 51 | 52 | pub fn index(mut self, index: i32) -> Self { 53 | self.message.header.index = index; 54 | self 55 | } 56 | 57 | /// Get ingress qdisc 58 | pub fn ingress(mut self) -> Self { 59 | self.message.header.parent = TcHandle::INGRESS; 60 | self 61 | } 62 | } 63 | 64 | #[derive(Debug, Clone)] 65 | pub struct TrafficClassGetRequest { 66 | handle: Handle, 67 | message: TcMessage, 68 | } 69 | 70 | impl TrafficClassGetRequest { 71 | pub(crate) fn new(handle: Handle, ifindex: i32) -> Self { 72 | let mut message = TcMessage::default(); 73 | message.header.index = ifindex; 74 | TrafficClassGetRequest { handle, message } 75 | } 76 | 77 | /// Execute the request 78 | pub fn execute(self) -> impl TryStream { 79 | let TrafficClassGetRequest { 80 | mut handle, 81 | message, 82 | } = self; 83 | 84 | let mut req = 85 | NetlinkMessage::from(RouteNetlinkMessage::GetTrafficClass(message)); 86 | req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; 87 | 88 | match handle.request(req) { 89 | Ok(response) => Either::Left(response.map(move |msg| { 90 | Ok(try_rtnl!(msg, RouteNetlinkMessage::NewTrafficClass)) 91 | })), 92 | Err(e) => { 93 | Either::Right(future::err::(e).into_stream()) 94 | } 95 | } 96 | } 97 | } 98 | 99 | #[derive(Debug, Clone)] 100 | pub struct TrafficFilterGetRequest { 101 | handle: Handle, 102 | message: TcMessage, 103 | } 104 | 105 | impl TrafficFilterGetRequest { 106 | pub(crate) fn new(handle: Handle, ifindex: i32) -> Self { 107 | let mut message = TcMessage::default(); 108 | message.header.index = ifindex; 109 | TrafficFilterGetRequest { handle, message } 110 | } 111 | 112 | /// Execute the request 113 | pub fn execute(self) -> impl TryStream { 114 | let TrafficFilterGetRequest { 115 | mut handle, 116 | message, 117 | } = self; 118 | 119 | let mut req = NetlinkMessage::from( 120 | RouteNetlinkMessage::GetTrafficFilter(message), 121 | ); 122 | req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; 123 | 124 | match handle.request(req) { 125 | Ok(response) => Either::Left(response.map(move |msg| { 126 | Ok(try_rtnl!(msg, RouteNetlinkMessage::NewTrafficFilter)) 127 | })), 128 | Err(e) => { 129 | Either::Right(future::err::(e).into_stream()) 130 | } 131 | } 132 | } 133 | 134 | /// Set parent to root. 135 | pub fn root(mut self) -> Self { 136 | self.message.header.parent = TcHandle::ROOT; 137 | self 138 | } 139 | } 140 | 141 | #[derive(Debug, Clone)] 142 | pub struct TrafficChainGetRequest { 143 | handle: Handle, 144 | message: TcMessage, 145 | } 146 | 147 | impl TrafficChainGetRequest { 148 | pub(crate) fn new(handle: Handle, ifindex: i32) -> Self { 149 | let mut message = TcMessage::default(); 150 | message.header.index = ifindex; 151 | TrafficChainGetRequest { handle, message } 152 | } 153 | 154 | /// Execute the request 155 | pub fn execute(self) -> impl TryStream { 156 | let TrafficChainGetRequest { 157 | mut handle, 158 | message, 159 | } = self; 160 | 161 | let mut req = 162 | NetlinkMessage::from(RouteNetlinkMessage::GetTrafficChain(message)); 163 | req.header.flags = NLM_F_REQUEST | NLM_F_DUMP; 164 | 165 | match handle.request(req) { 166 | Ok(response) => Either::Left(response.map(move |msg| { 167 | Ok(try_rtnl!(msg, RouteNetlinkMessage::NewTrafficChain)) 168 | })), 169 | Err(e) => { 170 | Either::Right(future::err::(e).into_stream()) 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/traffic_control/handle.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use super::{ 4 | QDiscDelRequest, QDiscGetRequest, QDiscNewRequest, TrafficChainGetRequest, 5 | TrafficClassGetRequest, TrafficFilterDelRequest, TrafficFilterGetRequest, 6 | TrafficFilterNewRequest, 7 | }; 8 | 9 | use crate::Handle; 10 | use netlink_packet_core::{NLM_F_CREATE, NLM_F_EXCL, NLM_F_REPLACE}; 11 | use netlink_packet_route::tc::TcMessage; 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct QDiscHandle(Handle); 15 | 16 | impl QDiscHandle { 17 | pub fn new(handle: Handle) -> Self { 18 | QDiscHandle(handle) 19 | } 20 | 21 | /// Retrieve the list of qdisc (equivalent to `tc qdisc show`) 22 | pub fn get(&mut self) -> QDiscGetRequest { 23 | QDiscGetRequest::new(self.0.clone()) 24 | } 25 | 26 | /// Create a new qdisc, don't replace if the object already exists. 27 | /// ( equivalent to `tc qdisc add dev STRING`) 28 | pub fn add(&mut self, index: i32) -> QDiscNewRequest { 29 | let msg = TcMessage::with_index(index); 30 | QDiscNewRequest::new(self.0.clone(), msg, NLM_F_EXCL | NLM_F_CREATE) 31 | } 32 | 33 | /// Change the qdisc, the handle cannot be changed and neither can the 34 | /// parent. In other words, change cannot move a node. 35 | /// ( equivalent to `tc qdisc change dev STRING`) 36 | pub fn change(&mut self, index: i32) -> QDiscNewRequest { 37 | let msg = TcMessage::with_index(index); 38 | QDiscNewRequest::new(self.0.clone(), msg, 0) 39 | } 40 | 41 | /// Replace existing matching qdisc, create qdisc if it doesn't already 42 | /// exist. ( equivalent to `tc qdisc replace dev STRING`) 43 | pub fn replace(&mut self, index: i32) -> QDiscNewRequest { 44 | let msg = TcMessage::with_index(index); 45 | QDiscNewRequest::new(self.0.clone(), msg, NLM_F_CREATE | NLM_F_REPLACE) 46 | } 47 | 48 | /// Performs a replace where the node must exist already. 49 | /// ( equivalent to `tc qdisc link dev STRING`) 50 | pub fn link(&mut self, index: i32) -> QDiscNewRequest { 51 | let msg = TcMessage::with_index(index); 52 | QDiscNewRequest::new(self.0.clone(), msg, NLM_F_REPLACE) 53 | } 54 | 55 | /// Delete the qdisc ( equivalent to `tc qdisc del dev STRING`) 56 | pub fn del(&mut self, index: i32) -> QDiscDelRequest { 57 | let msg = TcMessage::with_index(index); 58 | QDiscDelRequest::new(self.0.clone(), msg) 59 | } 60 | } 61 | 62 | #[derive(Debug, Clone)] 63 | pub struct TrafficClassHandle { 64 | handle: Handle, 65 | ifindex: i32, 66 | } 67 | 68 | impl TrafficClassHandle { 69 | pub fn new(handle: Handle, ifindex: i32) -> Self { 70 | TrafficClassHandle { handle, ifindex } 71 | } 72 | 73 | /// Retrieve the list of traffic class (equivalent to 74 | /// `tc class show dev `) 75 | pub fn get(&mut self) -> TrafficClassGetRequest { 76 | TrafficClassGetRequest::new(self.handle.clone(), self.ifindex) 77 | } 78 | } 79 | 80 | #[derive(Debug, Clone)] 81 | pub struct TrafficFilterHandle { 82 | handle: Handle, 83 | ifindex: i32, 84 | } 85 | 86 | impl TrafficFilterHandle { 87 | pub fn new(handle: Handle, ifindex: i32) -> Self { 88 | TrafficFilterHandle { handle, ifindex } 89 | } 90 | 91 | /// Retrieve the list of filter (equivalent to 92 | /// `tc filter show dev `) 93 | pub fn get(&mut self) -> TrafficFilterGetRequest { 94 | TrafficFilterGetRequest::new(self.handle.clone(), self.ifindex) 95 | } 96 | 97 | /// Add a filter to a node, don't replace if the object already exists. 98 | /// ( equivalent to `tc filter add dev STRING`) 99 | pub fn add(&mut self) -> TrafficFilterNewRequest { 100 | TrafficFilterNewRequest::new( 101 | self.handle.clone(), 102 | self.ifindex, 103 | NLM_F_EXCL | NLM_F_CREATE, 104 | ) 105 | } 106 | 107 | /// Delete a filter from a node, don't replace if the object already exists. 108 | /// ( equivalent to `tc filter del dev STRING`) 109 | pub fn del(&mut self) -> TrafficFilterDelRequest { 110 | TrafficFilterDelRequest::new(self.handle.clone(), self.ifindex) 111 | } 112 | 113 | /// Change the filter, the handle cannot be changed and neither can the 114 | /// parent. In other words, change cannot move a node. 115 | /// ( equivalent to `tc filter change dev STRING`) 116 | pub fn change(&mut self) -> TrafficFilterNewRequest { 117 | TrafficFilterNewRequest::new(self.handle.clone(), self.ifindex, 0) 118 | } 119 | 120 | /// Replace existing matching filter, create filter if it doesn't already 121 | /// exist. ( equivalent to `tc filter replace dev STRING`) 122 | pub fn replace(&mut self) -> TrafficFilterNewRequest { 123 | TrafficFilterNewRequest::new( 124 | self.handle.clone(), 125 | self.ifindex, 126 | NLM_F_CREATE, 127 | ) 128 | } 129 | } 130 | 131 | #[derive(Debug, Clone)] 132 | pub struct TrafficChainHandle { 133 | handle: Handle, 134 | ifindex: i32, 135 | } 136 | 137 | impl TrafficChainHandle { 138 | pub fn new(handle: Handle, ifindex: i32) -> Self { 139 | TrafficChainHandle { handle, ifindex } 140 | } 141 | 142 | /// Retrieve the list of chain (equivalent to 143 | /// `tc chain show dev `) 144 | pub fn get(&mut self) -> TrafficChainGetRequest { 145 | TrafficChainGetRequest::new(self.handle.clone(), self.ifindex) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/traffic_control/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | mod add_filter; 4 | mod add_qdisc; 5 | mod del_filter; 6 | mod del_qdisc; 7 | mod get; 8 | mod handle; 9 | #[cfg(test)] 10 | mod test; 11 | 12 | pub use self::add_filter::TrafficFilterNewRequest; 13 | pub use self::add_qdisc::QDiscNewRequest; 14 | pub use self::del_filter::TrafficFilterDelRequest; 15 | pub use self::del_qdisc::QDiscDelRequest; 16 | pub use self::get::{ 17 | QDiscGetRequest, TrafficChainGetRequest, TrafficClassGetRequest, 18 | TrafficFilterGetRequest, 19 | }; 20 | pub use self::handle::{ 21 | QDiscHandle, TrafficChainHandle, TrafficClassHandle, TrafficFilterHandle, 22 | }; 23 | --------------------------------------------------------------------------------