├── .cargo └── config.toml ├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── examples ├── add.rs ├── delete.rs ├── find_route.rs ├── list.rs ├── listen.rs └── listen_async.rs ├── src ├── common │ └── mod.rs ├── lib.rs ├── linux │ ├── async_route.rs │ └── mod.rs ├── unix │ ├── async_route │ │ ├── async_io.rs │ │ ├── mod.rs │ │ └── tokio.rs │ ├── mod.rs │ └── shutdown.rs ├── unix_bsd │ ├── async_route.rs │ ├── bind.rs │ └── mod.rs └── windows │ ├── async_route.rs │ ├── ffi.rs │ └── mod.rs └── wrapper.h /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [registries.crates-io] 2 | protocol = "sparse" 3 | 4 | [build] 5 | # target = ["x86_64-unknown-linux-musl"] 6 | # target = ["x86_64-pc-windows-msvc"] 7 | # target = ["x86_64-apple-darwin"] 8 | # target = ["x86_64-unknown-freebsd"] 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "cargo" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Push or PR 2 | 3 | on: 4 | [push, pull_request] 5 | 6 | env: 7 | CARGO_TERM_COLOR: always 8 | 9 | jobs: 10 | build_n_test: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu-latest, windows-latest, macos-latest] 15 | 16 | runs-on: ${{ matrix.os }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: rustfmt 21 | if: ${{ !cancelled() }} 22 | run: cargo fmt --all -- --check 23 | - name: check 24 | if: ${{ !cancelled() }} 25 | run: cargo check --verbose 26 | - name: clippy 27 | if: ${{ !cancelled() }} 28 | run: | 29 | cargo clippy --all-targets -- -D warnings 30 | cargo clippy --all-targets --all-features -- -D warnings 31 | - name: Build 32 | if: ${{ !cancelled() }} 33 | run: | 34 | cargo build --verbose --examples --tests 35 | - name: Abort on error 36 | if: ${{ failure() }} 37 | run: echo "Some of jobs failed" && false 38 | - name: Build x86_64-FreeBSD 39 | uses: cross-platform-actions/action@v0.27.0 40 | if: startsWith(matrix.os, 'ubuntu') 41 | env: 42 | TARGET: x86_64-unknown-freebsd 43 | with: 44 | operating_system: freebsd 45 | environment_variables: TARGET 46 | architecture: x86-64 47 | version: 13.2 48 | shell: bash 49 | memory: 5G 50 | cpu_count: 4 51 | run: | 52 | uname -a 53 | echo $SHELL 54 | pwd 55 | ls -lah 56 | whoami 57 | env | sort 58 | sudo pkg install -y git protobuf llvm15 59 | curl --proto 'https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 60 | source $HOME/.cargo/env 61 | export CC=clang 62 | export CXX=clang++ 63 | cargo fmt --all -- --check 64 | export CARGO_TERM_COLOR=always 65 | cargo clippy --all-targets -- -D warnings 66 | 67 | semver: 68 | name: Check semver 69 | strategy: 70 | matrix: 71 | os: [ubuntu-latest, macos-latest, windows-latest] 72 | runs-on: ${{ matrix.os }} 73 | steps: 74 | - uses: actions/checkout@v4 75 | - uses: actions-rs/toolchain@v1 76 | with: 77 | profile: minimal 78 | toolchain: stable 79 | override: true 80 | - uses: obi1kenobi/cargo-semver-checks-action@v2 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # RustRover 17 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 18 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 19 | # and can be added to the global gitignore or merged into this file. For a more nuclear 20 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 21 | .idea/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "route_manager" 3 | version = "0.1.4" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | readme = "README.md" 7 | description = "Cross-platform route management interface" 8 | repository = "https://github.com/rustp2p/route_manager" 9 | keywords = ["route", "network", "ip"] 10 | 11 | [lib] 12 | crate-type = ["staticlib", "cdylib", "lib"] 13 | 14 | [dependencies] 15 | libc = "0.2.170" 16 | tokio = { version = "1", features = [ 17 | "net", 18 | "macros", 19 | "io-util", 20 | "rt", 21 | ], optional = true } 22 | async-io = { version = "2.3", optional = true } 23 | 24 | [target.'cfg(target_os = "linux")'.dependencies] 25 | netlink-packet-route = "0.23.0" 26 | netlink-sys = "0.8.7" 27 | netlink-packet-core = "0.7.0" 28 | 29 | [target.'cfg(target_os = "windows")'.dependencies] 30 | flume = "0.11" 31 | windows-sys = { version = "0.59", features = [ 32 | "Win32_System_Diagnostics_Debug", 33 | "Win32_System_SystemServices", 34 | "Win32_Security_Cryptography", 35 | "Win32_NetworkManagement_IpHelper", 36 | "Win32_NetworkManagement_Ndis", 37 | "Win32_Networking_WinSock", 38 | "Win32_System_Threading", 39 | "Win32_System_Com", 40 | "Win32_System_Rpc", 41 | "Win32_Security", 42 | "Win32_Foundation", 43 | "Win32_System_Ioctl", 44 | "Win32_System_IO", 45 | "Win32_System_LibraryLoader", 46 | "Win32_Security_WinTrust", 47 | ] } 48 | 49 | [package.metadata.docs.rs] 50 | all-features = true 51 | targets = [ 52 | "x86_64-unknown-linux-gnu", 53 | "x86_64-pc-windows-msvc", 54 | "aarch64-apple-darwin", 55 | "x86_64-apple-darwin", 56 | "x86_64-unknown-freebsd" 57 | ] 58 | 59 | [features] 60 | default = [] 61 | shutdown = [] 62 | async = ["tokio"] 63 | async_io = ["async-io"] 64 | 65 | [build-dependencies] 66 | bindgen = "0.71" 67 | 68 | [dev-dependencies] 69 | tokio = { version = "1.43.0", features = ["full"] } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # route_manager 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/route_manager.svg)](https://crates.io/crates/route_manager) 4 | [![route_manager](https://docs.rs/route_manager/badge.svg)](https://docs.rs/route_manager) 5 | 6 | Used for adding, deleting, and querying routes, 7 | with support for asynchronous or synchronous monitoring of route changes. 8 | 9 | ## Supported Platforms 10 | 11 | | Platform | | 12 | |----------|---| 13 | | Windows | ✅ | 14 | | Linux | ✅ | 15 | | macOS | ✅ | 16 | | FreeBSD | ✅ | 17 | 18 | ## Features: 19 | 20 | 1. Supporting Synchronous and Asynchronous API 21 | 2. Supports choosing between Tokio and async-io for asynchronous I/O operations. 22 | 23 | ## Example: 24 | Asynchronous API 25 | ```rust 26 | use route_manager::{AsyncRouteManager, Route}; 27 | use std::time::Duration; 28 | #[tokio::main] 29 | pub async fn main() { 30 | let mut route_listener = AsyncRouteManager::listener().unwrap(); 31 | tokio::spawn(async move { 32 | while let Ok(route) = route_listener.listen().await { 33 | println!("listen {route}"); 34 | } 35 | }); 36 | // Need to set up the correct gateway 37 | let route = Route::new("192.168.2.0".parse().unwrap(), 24).with_if_index(1); 38 | let mut manager = AsyncRouteManager::new().unwrap(); 39 | let result = manager.add(&route).await; 40 | println!("route add {route} {result:?}"); 41 | tokio::time::sleep(Duration::from_secs(1)).await; 42 | let result = manager.delete(&route).await; 43 | println!("route delete {route} {result:?}"); 44 | tokio::time::sleep(Duration::from_secs(1)).await; 45 | } 46 | ``` 47 | Synchronous API 48 | ```rust 49 | use route_manager::{Route, RouteManager}; 50 | use std::thread; 51 | use std::time::Duration; 52 | 53 | pub fn main() { 54 | let mut route_listener = RouteManager::listener().unwrap(); 55 | #[cfg(feature = "shutdown")] 56 | let shutdown_handle = route_listener.shutdown_handle().unwrap(); 57 | thread::spawn(move || { 58 | while let Ok(route) = route_listener.listen() { 59 | println!("listen {route}"); 60 | } 61 | println!("========= end ========="); 62 | }); 63 | // Need to set up the correct gateway 64 | let route = Route::new("192.168.2.0".parse().unwrap(), 24).with_if_index(1); 65 | let mut manager = RouteManager::new().unwrap(); 66 | 67 | let result = manager.add(&route); 68 | println!("route add {route} {result:?}"); 69 | thread::sleep(Duration::from_secs(1)); 70 | let result = manager.delete(&route); 71 | println!("route delete {route} {result:?}"); 72 | thread::sleep(Duration::from_secs(1)); 73 | #[cfg(feature = "shutdown")] 74 | shutdown_handle.shutdown().unwrap(); 75 | thread::sleep(Duration::from_secs(100)); 76 | } 77 | ``` 78 | ## Reference project 79 | 80 | - [net-route](https://github.com/johnyburd/net-route) -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // detect docs rs builder so we don't try to link to macos/freebsd libs while cross compiling 3 | let docs_builder = std::env::var("DOCS_RS").is_ok(); 4 | let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); 5 | 6 | if (target_os == "macos" || target_os == "freebsd") && !docs_builder { 7 | build_wrapper(); 8 | } 9 | } 10 | 11 | fn build_wrapper() { 12 | use std::env; 13 | use std::path::PathBuf; 14 | // Tell cargo to look for shared libraries in the specified directory 15 | //println!("cargo:rustc-link-search=/path/to/lib"); 16 | 17 | // Tell cargo to tell rustc to link the system bzip2 18 | // shared library. 19 | //println!("cargo:rustc-link-lib=bz2"); 20 | 21 | // Tell cargo to invalidate the built crate whenever the wrapper changes 22 | println!("cargo:rerun-if-changed=wrapper.h"); 23 | 24 | // The bindgen::Builder is the main entry point 25 | // to bindgen, and lets you build up options for 26 | // the resulting bindings. 27 | let bindings = bindgen::Builder::default() 28 | // The input header we would like to generate 29 | // bindings for. 30 | .header("wrapper.h") 31 | // Tell cargo to invalidate the built crate whenever any of the 32 | // included header files changed. 33 | .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) 34 | // Finish the builder and generate the bindings. 35 | .generate() 36 | // Unwrap the Result and panic on failure. 37 | .expect("Unable to generate bindings"); 38 | 39 | // Write the bindings to the $OUT_DIR/bindings.rs file. 40 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 41 | bindings 42 | .write_to_file(out_path.join("bindings.rs")) 43 | .expect("Couldn't write bindings!"); 44 | } 45 | -------------------------------------------------------------------------------- /examples/add.rs: -------------------------------------------------------------------------------- 1 | use route_manager::{Route, RouteManager}; 2 | 3 | pub fn main() { 4 | // Need to set up the correct gateway 5 | let route = Route::new("192.168.2.0".parse().unwrap(), 24).with_if_index(1); 6 | println!("route add {:?}", route); 7 | let result = RouteManager::new().unwrap().add(&route); 8 | println!("{result:?}"); 9 | } 10 | -------------------------------------------------------------------------------- /examples/delete.rs: -------------------------------------------------------------------------------- 1 | use route_manager::{Route, RouteManager}; 2 | 3 | pub fn main() { 4 | // Need to set up the correct gateway 5 | let route = Route::new("192.168.2.0".parse().unwrap(), 24).with_if_index(1); 6 | println!("route delete {:?}", route); 7 | let result = RouteManager::new().unwrap().delete(&route); 8 | println!("{result:?}"); 9 | } 10 | -------------------------------------------------------------------------------- /examples/find_route.rs: -------------------------------------------------------------------------------- 1 | use route_manager::{Route, RouteManager}; 2 | use std::net::IpAddr; 3 | use std::thread; 4 | use std::time::Duration; 5 | 6 | pub fn main() { 7 | let net: IpAddr = "192.168.4.0".parse().unwrap(); 8 | let ip: IpAddr = "192.168.4.10".parse().unwrap(); 9 | let mut manager = RouteManager::new().unwrap(); 10 | let find_route = manager.find_route(&ip).unwrap(); 11 | println!("find route: {ip} -> {find_route:?}"); 12 | // Need to set up the correct gateway 13 | let route = Route::new(net, 24).with_if_index(1); 14 | println!("route add {:?}", route); 15 | manager.add(&route).unwrap(); 16 | thread::sleep(Duration::from_secs(1)); 17 | 18 | let find_route = manager.find_route(&ip).unwrap(); 19 | println!("find route: {ip} -> {find_route:?}"); 20 | manager.delete(&route).unwrap(); 21 | } 22 | -------------------------------------------------------------------------------- /examples/list.rs: -------------------------------------------------------------------------------- 1 | use route_manager::RouteManager; 2 | 3 | pub fn main() { 4 | let vec = RouteManager::new().unwrap().list().unwrap(); 5 | for x in vec { 6 | println!("{x}"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/listen.rs: -------------------------------------------------------------------------------- 1 | use route_manager::{Route, RouteManager}; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | pub fn main() { 6 | let mut route_listener = RouteManager::listener().unwrap(); 7 | #[cfg(feature = "shutdown")] 8 | let shutdown_handle = route_listener.shutdown_handle().unwrap(); 9 | thread::spawn(move || { 10 | loop { 11 | match route_listener.listen() { 12 | Ok(route) => { 13 | println!("========= listen {route} ========="); 14 | } 15 | Err(e) => { 16 | println!("========= listen {e:?} ========="); 17 | break; 18 | } 19 | } 20 | } 21 | println!("========= listen end ========="); 22 | }); 23 | // Need to set up the correct gateway 24 | let route = Route::new("192.168.2.0".parse().unwrap(), 24).with_if_index(1); 25 | let mut manager = RouteManager::new().unwrap(); 26 | 27 | let result = manager.add(&route); 28 | println!("route add {route} {result:?}"); 29 | thread::sleep(Duration::from_secs(1)); 30 | let result = manager.delete(&route); 31 | println!("route delete {route} {result:?}"); 32 | thread::sleep(Duration::from_secs(3)); 33 | #[cfg(feature = "shutdown")] 34 | { 35 | shutdown_handle.shutdown().unwrap(); 36 | } 37 | thread::sleep(Duration::from_secs(100)); 38 | } 39 | -------------------------------------------------------------------------------- /examples/listen_async.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "async")] 2 | use route_manager::{AsyncRouteManager, Route}; 3 | #[cfg(feature = "async")] 4 | use std::time::Duration; 5 | 6 | #[cfg(feature = "async")] 7 | #[tokio::main] 8 | pub async fn main() { 9 | let mut route_listener = AsyncRouteManager::listener().unwrap(); 10 | tokio::spawn(async move { 11 | while let Ok(route) = route_listener.listen().await { 12 | println!("listen {route}"); 13 | } 14 | println!("========= listen end ========="); 15 | }); 16 | // Need to set up the correct gateway 17 | let route = Route::new("192.168.2.0".parse().unwrap(), 24).with_if_index(1); 18 | let mut manager = AsyncRouteManager::new().unwrap(); 19 | let result = manager.add(&route).await; 20 | println!("route add {route} {result:?}"); 21 | tokio::time::sleep(Duration::from_secs(1)).await; 22 | let result = manager.delete(&route).await; 23 | println!("route delete {route} {result:?}"); 24 | tokio::time::sleep(Duration::from_secs(1)).await; 25 | } 26 | 27 | #[cfg(not(feature = "async"))] 28 | #[tokio::main] 29 | pub async fn main() { 30 | unimplemented!("This examples needs the 'async' feature."); 31 | } 32 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 3 | use std::{fmt, io}; 4 | 5 | #[derive(Debug, Clone, PartialEq, Eq)] 6 | pub enum RouteChange { 7 | Add(Route), 8 | Delete(Route), 9 | Change(Route), 10 | } 11 | 12 | #[derive(Debug, Clone, PartialEq, Eq)] 13 | pub struct Route { 14 | pub(crate) destination: IpAddr, 15 | pub(crate) prefix: u8, 16 | pub(crate) gateway: Option, 17 | pub(crate) if_name: Option, 18 | pub(crate) if_index: Option, 19 | #[cfg(target_os = "linux")] 20 | pub(crate) table: u8, 21 | #[cfg(target_os = "linux")] 22 | pub(crate) source: Option, 23 | #[cfg(target_os = "linux")] 24 | pub(crate) source_prefix: u8, 25 | #[cfg(target_os = "linux")] 26 | pub(crate) pref_source: Option, 27 | #[cfg(any(target_os = "windows", target_os = "linux"))] 28 | pub(crate) metric: Option, 29 | #[cfg(target_os = "windows")] 30 | pub(crate) luid: Option, 31 | } 32 | impl Route { 33 | pub fn destination(&self) -> IpAddr { 34 | self.destination 35 | } 36 | pub fn prefix(&self) -> u8 { 37 | self.prefix 38 | } 39 | pub fn gateway(&self) -> Option { 40 | self.gateway 41 | } 42 | pub fn if_name(&self) -> Option<&String> { 43 | self.if_name.as_ref() 44 | } 45 | pub fn if_index(&self) -> Option { 46 | self.if_index 47 | } 48 | #[cfg(target_os = "linux")] 49 | pub fn table(&self) -> u8 { 50 | self.table 51 | } 52 | #[cfg(target_os = "linux")] 53 | pub fn source(&self) -> Option { 54 | self.source 55 | } 56 | #[cfg(target_os = "linux")] 57 | pub fn source_prefix(&self) -> u8 { 58 | self.source_prefix 59 | } 60 | #[cfg(target_os = "linux")] 61 | pub fn pref_source(&self) -> Option { 62 | self.pref_source 63 | } 64 | #[cfg(any(target_os = "windows", target_os = "linux"))] 65 | pub fn metric(&self) -> Option { 66 | self.metric 67 | } 68 | #[cfg(target_os = "windows")] 69 | pub fn luid(&self) -> Option { 70 | self.luid 71 | } 72 | } 73 | impl Route { 74 | pub fn new(destination: IpAddr, prefix: u8) -> Self { 75 | Self { 76 | destination, 77 | prefix, 78 | gateway: None, 79 | if_name: None, 80 | if_index: None, 81 | #[cfg(target_os = "linux")] 82 | table: 0, 83 | #[cfg(target_os = "linux")] 84 | source: None, 85 | #[cfg(target_os = "linux")] 86 | source_prefix: 0, 87 | #[cfg(target_os = "linux")] 88 | pref_source: None, 89 | #[cfg(any(target_os = "windows", target_os = "linux"))] 90 | metric: None, 91 | #[cfg(target_os = "windows")] 92 | luid: None, 93 | } 94 | } 95 | /// Sets the gateway (next hop) for the route. 96 | pub fn with_gateway(mut self, gateway: IpAddr) -> Self { 97 | self.gateway = Some(gateway); 98 | self 99 | } 100 | /// Sets the network interface by name (e.g., "eth0"). 101 | pub fn with_if_name(mut self, if_name: String) -> Self { 102 | self.if_name = Some(if_name); 103 | self 104 | } 105 | /// Sets the network interface by index. 106 | pub fn with_if_index(mut self, if_index: u32) -> Self { 107 | self.if_index = Some(if_index); 108 | self 109 | } 110 | /// (Linux only) Sets the routing table ID. 111 | #[cfg(target_os = "linux")] 112 | pub fn with_table(mut self, table: u8) -> Self { 113 | self.table = table; 114 | self 115 | } 116 | /// (Linux only) Sets the source address and prefix for policy-based routing. 117 | #[cfg(target_os = "linux")] 118 | pub fn with_source(mut self, source: IpAddr, prefix: u8) -> Self { 119 | self.source = Some(source); 120 | self.source_prefix = prefix; 121 | self 122 | } 123 | /// (Linux only) Sets the preferred source address for the route. 124 | #[cfg(target_os = "linux")] 125 | pub fn with_pref_source(mut self, pref_source: IpAddr) -> Self { 126 | self.pref_source = Some(pref_source); 127 | self 128 | } 129 | /// (Windows/Linux) Sets the route metric (priority). 130 | #[cfg(any(target_os = "windows", target_os = "linux"))] 131 | pub fn with_metric(mut self, metric: u32) -> Self { 132 | self.metric = Some(metric); 133 | self 134 | } 135 | /// (Windows only) Sets the LUID (Local Unique Identifier) for the interface. 136 | #[cfg(target_os = "windows")] 137 | pub fn with_luid(mut self, luid: u64) -> Self { 138 | self.luid = Some(luid); 139 | self 140 | } 141 | } 142 | impl Route { 143 | pub fn check(&self) -> io::Result<()> { 144 | if self.destination.is_ipv4() { 145 | if self.prefix > 32 { 146 | return Err(io::Error::new(io::ErrorKind::Other, "prefix error")); 147 | } 148 | } else if self.prefix > 128 { 149 | return Err(io::Error::new(io::ErrorKind::Other, "prefix error")); 150 | } 151 | if let Some(index) = self.if_index { 152 | crate::if_index_to_name(index)?; 153 | } 154 | if let Some(gateway) = self.gateway { 155 | if gateway.is_ipv4() != self.destination.is_ipv4() { 156 | return Err(io::Error::new(io::ErrorKind::Other, "gateway error")); 157 | } 158 | } 159 | if let Some(name) = self.if_name.as_ref() { 160 | let index = crate::if_name_to_index(name)?; 161 | if let Some(if_index) = self.if_index { 162 | if index != if_index { 163 | return Err(io::Error::new(io::ErrorKind::Other, "if_index mismatch")); 164 | } 165 | } 166 | } 167 | Ok(()) 168 | } 169 | /// network address 170 | pub fn network(&self) -> IpAddr { 171 | Route::network_addr(self.destination, self.prefix) 172 | } 173 | fn network_addr(ip: IpAddr, prefix: u8) -> IpAddr { 174 | match ip { 175 | IpAddr::V4(ipv4) => { 176 | let ip = u32::from(ipv4); 177 | let mask = if prefix == 0 { 0 } else { !0 << (32 - prefix) }; 178 | IpAddr::V4(Ipv4Addr::from(ip & mask)) 179 | } 180 | IpAddr::V6(ipv6) => { 181 | let ip = u128::from(ipv6); 182 | let mask = if prefix == 0 { 183 | 0 184 | } else { 185 | !0_u128 << (128 - prefix) 186 | }; 187 | IpAddr::V6(Ipv6Addr::from(ip & mask)) 188 | } 189 | } 190 | } 191 | /// Determine whether the target address is included in the route 192 | pub fn contains(&self, dest: &IpAddr) -> bool { 193 | if dest.is_ipv4() != self.destination.is_ipv4() { 194 | return false; 195 | } 196 | let route_network = self.network(); 197 | let addr_network = Route::network_addr(*dest, self.prefix); 198 | route_network == addr_network 199 | } 200 | /// Subnet Mask 201 | pub fn mask(&self) -> IpAddr { 202 | match self.destination { 203 | IpAddr::V4(_) => IpAddr::V4(Ipv4Addr::from( 204 | u32::MAX.checked_shl(32 - self.prefix as u32).unwrap_or(0), 205 | )), 206 | IpAddr::V6(_) => IpAddr::V6(Ipv6Addr::from( 207 | u128::MAX.checked_shl(128 - self.prefix as u32).unwrap_or(0), 208 | )), 209 | } 210 | } 211 | #[allow(dead_code)] 212 | pub(crate) fn get_index(&self) -> Option { 213 | self.if_index.or_else(|| { 214 | if let Some(name) = &self.if_name { 215 | crate::if_name_to_index(name).ok() 216 | } else { 217 | None 218 | } 219 | }) 220 | } 221 | #[allow(dead_code)] 222 | pub(crate) fn get_name(&self) -> Option { 223 | self.if_name.clone().or_else(|| { 224 | if let Some(index) = &self.if_index { 225 | crate::if_index_to_name(*index).ok() 226 | } else { 227 | None 228 | } 229 | }) 230 | } 231 | } 232 | impl PartialOrd for Route { 233 | fn partial_cmp(&self, other: &Self) -> Option { 234 | Some(self.cmp(other)) 235 | } 236 | } 237 | 238 | impl Ord for Route { 239 | fn cmp(&self, other: &Self) -> Ordering { 240 | match self.prefix.cmp(&other.prefix) { 241 | #[cfg(any(target_os = "windows", target_os = "linux"))] 242 | Ordering::Equal => other.metric.cmp(&self.metric), 243 | v => v, 244 | } 245 | } 246 | } 247 | impl crate::RouteManager { 248 | /// Route Lookup by Destination Address 249 | #[cfg(not(target_os = "windows"))] 250 | pub fn find_route(&mut self, dest: &IpAddr) -> io::Result> { 251 | let mut list = self.list()?; 252 | list.sort_by(|v1, v2| v2.cmp(v1)); 253 | let rs = list 254 | .iter() 255 | .filter(|v| v.destination.is_ipv4() == dest.is_ipv4()) 256 | .find(|v| v.contains(dest)) 257 | .cloned(); 258 | Ok(rs) 259 | } 260 | } 261 | impl fmt::Display for RouteChange { 262 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 263 | match self { 264 | RouteChange::Add(route) => write!(f, "Add({})", route), 265 | RouteChange::Delete(route) => write!(f, "Delete({})", route), 266 | RouteChange::Change(route) => write!(f, "Change({})", route), 267 | } 268 | } 269 | } 270 | impl fmt::Display for Route { 271 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 272 | write!( 273 | f, 274 | "Route {{ destination: {}/{}, gateway: ", 275 | self.destination, self.prefix 276 | )?; 277 | 278 | match self.gateway { 279 | Some(addr) => write!(f, "{}", addr), 280 | None => write!(f, "None"), 281 | }?; 282 | 283 | write!(f, ", if_index: ")?; 284 | 285 | match self.if_index { 286 | Some(index) => write!(f, "{}", index), 287 | None => write!(f, "None"), 288 | }?; 289 | write!(f, ", if_name: ")?; 290 | 291 | match &self.if_name { 292 | Some(if_name) => write!(f, "{}", if_name), 293 | None => write!(f, "None"), 294 | }?; 295 | 296 | #[cfg(any(target_os = "windows", target_os = "linux"))] 297 | { 298 | write!(f, ", metric: ")?; 299 | match self.metric { 300 | Some(m) => write!(f, "{}", m), 301 | None => write!(f, "None"), 302 | }?; 303 | } 304 | 305 | #[cfg(target_os = "windows")] 306 | { 307 | write!(f, ", luid: ")?; 308 | match self.luid { 309 | Some(l) => write!(f, "{}", l), 310 | None => write!(f, "None"), 311 | }?; 312 | } 313 | 314 | write!(f, " }}") 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | ## Example: 3 | ### Asynchronous API 4 | ```rust,no_run 5 | use route_manager::{AsyncRouteManager, Route}; 6 | use std::time::Duration; 7 | #[cfg(feature = "async")] 8 | #[tokio::main] 9 | pub async fn main() { 10 | let mut route_listener = AsyncRouteManager::listener().unwrap(); 11 | tokio::spawn(async move { 12 | while let Ok(route) = route_listener.listen().await { 13 | println!("listen {route}"); 14 | } 15 | }); 16 | // Need to set up the correct gateway 17 | let route = Route::new("192.168.2.0".parse().unwrap(), 24).with_if_index(1); 18 | let mut manager = AsyncRouteManager::new().unwrap(); 19 | let result = manager.add(&route).await; 20 | println!("route add {route} {result:?}"); 21 | tokio::time::sleep(Duration::from_secs(1)).await; 22 | let result = manager.delete(&route).await; 23 | println!("route delete {route} {result:?}"); 24 | tokio::time::sleep(Duration::from_secs(1)).await; 25 | } 26 | ``` 27 | ### Synchronous API 28 | ```rust,no_run 29 | use route_manager::{Route, RouteManager}; 30 | use std::thread; 31 | use std::time::Duration; 32 | let mut route_listener = RouteManager::listener().unwrap(); 33 | #[cfg(feature = "shutdown")] 34 | let shutdown_handle = route_listener.shutdown_handle().unwrap(); 35 | thread::spawn(move || { 36 | while let Ok(route) = route_listener.listen() { 37 | println!("listen {route}"); 38 | } 39 | println!("========= end ========="); 40 | }); 41 | // Need to set up the correct gateway 42 | let route = Route::new("192.168.2.0".parse().unwrap(), 24).with_if_index(1); 43 | let mut manager = RouteManager::new().unwrap(); 44 | 45 | let result = manager.add(&route); 46 | println!("route add {route} {result:?}"); 47 | thread::sleep(Duration::from_secs(1)); 48 | let result = manager.delete(&route); 49 | println!("route delete {route} {result:?}"); 50 | thread::sleep(Duration::from_secs(1)); 51 | #[cfg(feature = "shutdown")] 52 | shutdown_handle.shutdown().unwrap(); 53 | thread::sleep(Duration::from_secs(100)); 54 | ``` 55 | */ 56 | mod common; 57 | #[cfg(windows)] 58 | mod windows; 59 | pub use common::*; 60 | #[cfg(windows)] 61 | pub use windows::*; 62 | 63 | #[cfg(target_os = "linux")] 64 | mod linux; 65 | #[cfg(target_os = "linux")] 66 | pub use linux::*; 67 | #[cfg(any(target_os = "freebsd", target_os = "macos"))] 68 | mod unix_bsd; 69 | #[cfg(any(target_os = "freebsd", target_os = "macos"))] 70 | pub use unix_bsd::*; 71 | #[cfg(unix)] 72 | mod unix; 73 | #[cfg(unix)] 74 | #[allow(unused_imports)] 75 | pub use crate::unix::*; 76 | -------------------------------------------------------------------------------- /src/linux/async_route.rs: -------------------------------------------------------------------------------- 1 | use crate::linux::{ 2 | add_route_req, convert_add_route, delete_route_req, deserialize_res, list_route_req, 3 | RouteSocket, 4 | }; 5 | use crate::AsyncRoute; 6 | use crate::{Route, RouteChange}; 7 | use std::collections::VecDeque; 8 | use std::io; 9 | /// AsyncRouteListener for asynchronously receiving route change events. 10 | pub struct AsyncRouteListener { 11 | list: VecDeque, 12 | socket: AsyncRoute, 13 | } 14 | impl AsyncRouteListener { 15 | /// Creates a new AsyncRouteListener. 16 | pub fn new() -> io::Result { 17 | let mut route_socket = RouteSocket::new()?; 18 | route_socket.add_membership()?; 19 | let socket = AsyncRoute::new(route_socket)?; 20 | Ok(Self { 21 | list: Default::default(), 22 | socket, 23 | }) 24 | } 25 | /// Asynchronously listens for a route change event and returns a RouteChange. 26 | pub async fn listen(&mut self) -> io::Result { 27 | if let Some(route) = self.list.pop_front() { 28 | return Ok(route); 29 | } 30 | let mut buf = vec![0; 4096]; 31 | loop { 32 | let len = self.socket.read_with(|s| s.recv(&mut buf[..])).await?; 33 | deserialize_res( 34 | |route| { 35 | self.list.push_back(route); 36 | }, 37 | &buf[..len], 38 | )?; 39 | if let Some(route) = self.list.pop_front() { 40 | return Ok(route); 41 | } 42 | } 43 | } 44 | } 45 | /// AsyncRouteManager for asynchronously managing routes (adding, deleting, and listing). 46 | pub struct AsyncRouteManager { 47 | _private: std::marker::PhantomData<()>, 48 | } 49 | impl AsyncRouteManager { 50 | /// Creates a new AsyncRouteManager. 51 | pub fn new() -> io::Result { 52 | Ok(AsyncRouteManager { 53 | _private: std::marker::PhantomData, 54 | }) 55 | } 56 | /// Retrieves a new instance of AsyncRouteListener. 57 | pub fn listener() -> io::Result { 58 | AsyncRouteListener::new() 59 | } 60 | /// Asynchronously lists all current routes. 61 | pub async fn list(&mut self) -> io::Result> { 62 | let req = list_route_req(); 63 | let mut socket = AsyncRoute::new(RouteSocket::new()?)?; 64 | socket.write_with(|s| s.send(&req)).await?; 65 | let mut buf = vec![0; 4096]; 66 | let mut list = Vec::new(); 67 | 68 | loop { 69 | let len = socket.read_with(|s| s.recv(&mut buf)).await?; 70 | let rs = deserialize_res( 71 | |route| { 72 | list.push(route); 73 | }, 74 | &buf[..len], 75 | )?; 76 | if !rs { 77 | break; 78 | } 79 | } 80 | Ok(convert_add_route(list)) 81 | } 82 | /// Asynchronously adds a new route. 83 | pub async fn add(&mut self, route: &Route) -> io::Result<()> { 84 | let req = add_route_req(route)?; 85 | let mut socket = AsyncRoute::new(RouteSocket::new()?)?; 86 | socket.write_with(|s| s.send(&req)).await?; 87 | let mut buf = vec![0; 4096]; 88 | let len = socket.read_with(|s| s.recv(&mut buf)).await?; 89 | deserialize_res(|_| {}, &buf[..len]).map(|_| ()) 90 | } 91 | /// Asynchronously deletes an existing route. 92 | pub async fn delete(&mut self, route: &Route) -> io::Result<()> { 93 | let req = delete_route_req(route)?; 94 | let mut socket = AsyncRoute::new(RouteSocket::new()?)?; 95 | socket.write_with(|s| s.send(&req)).await?; 96 | let mut buf = vec![0; 4096]; 97 | let len = socket.read_with(|s| s.recv(&mut buf)).await?; 98 | deserialize_res(|_| {}, &buf[..len]).map(|_| ()) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/linux/mod.rs: -------------------------------------------------------------------------------- 1 | use libc::RTM_DELROUTE; 2 | use netlink_packet_core::{ 3 | NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL, 4 | NLM_F_REQUEST, 5 | }; 6 | use netlink_packet_route::route::{ 7 | RouteAddress, RouteAttribute, RouteMessage, RouteProtocol, RouteScope, RouteType, 8 | }; 9 | use netlink_packet_route::{AddressFamily, RouteNetlinkMessage}; 10 | use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; 11 | use std::collections::VecDeque; 12 | use std::io; 13 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 14 | use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd}; 15 | 16 | use crate::{Route, RouteChange}; 17 | #[cfg(any(feature = "async", feature = "async_io"))] 18 | pub(crate) mod async_route; 19 | #[cfg(any(feature = "async", feature = "async_io"))] 20 | pub use async_route::*; 21 | 22 | /// RouteListener for receiving route change events. 23 | pub struct RouteListener { 24 | list: VecDeque, 25 | route_socket: RouteSocket, 26 | #[cfg(feature = "shutdown")] 27 | pub(crate) shutdown_handle: crate::RouteListenerShutdown, 28 | } 29 | impl AsRawFd for RouteListener { 30 | fn as_raw_fd(&self) -> RawFd { 31 | self.route_socket.as_raw_fd() 32 | } 33 | } 34 | 35 | impl RouteListener { 36 | /// Creates a new RouteListener. 37 | pub fn new() -> io::Result { 38 | let mut route_socket = RouteSocket::new()?; 39 | route_socket.add_membership()?; 40 | #[cfg(feature = "shutdown")] 41 | route_socket.0.set_non_blocking(true)?; 42 | Ok(Self { 43 | list: Default::default(), 44 | route_socket, 45 | #[cfg(feature = "shutdown")] 46 | shutdown_handle: crate::RouteListenerShutdown::new()?, 47 | }) 48 | } 49 | /// Listens for a route change event and returns a RouteChange. 50 | #[cfg(not(feature = "shutdown"))] 51 | pub fn listen(&mut self) -> io::Result { 52 | if let Some(route) = self.list.pop_front() { 53 | return Ok(route); 54 | } 55 | let mut buf = vec![0; 4096]; 56 | loop { 57 | let len = self.route_socket.recv(&mut buf)?; 58 | deserialize_res( 59 | |route| { 60 | self.list.push_back(route); 61 | }, 62 | &buf[..len], 63 | )?; 64 | if let Some(route) = self.list.pop_front() { 65 | return Ok(route); 66 | } 67 | } 68 | } 69 | } 70 | impl RouteListener { 71 | /// Listens for a route change event and returns a RouteChange. 72 | #[cfg(feature = "shutdown")] 73 | pub fn listen(&mut self) -> io::Result { 74 | if let Some(route) = self.list.pop_front() { 75 | return Ok(route); 76 | } 77 | let mut buf = vec![0; 4096]; 78 | loop { 79 | self.wait()?; 80 | let len = match self.route_socket.recv(&mut buf) { 81 | Ok(list) => list, 82 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, 83 | Err(e) => return Err(e), 84 | }; 85 | deserialize_res( 86 | |route| { 87 | self.list.push_back(route); 88 | }, 89 | &buf[..len], 90 | )?; 91 | if let Some(route) = self.list.pop_front() { 92 | return Ok(route); 93 | } 94 | } 95 | } 96 | } 97 | /// RouteManager is used for managing routes (adding, deleting, and listing). 98 | pub struct RouteManager { 99 | _private: std::marker::PhantomData<()>, 100 | } 101 | 102 | pub(crate) struct RouteSocket(Socket); 103 | impl AsRawFd for RouteSocket { 104 | fn as_raw_fd(&self) -> RawFd { 105 | self.0.as_raw_fd() 106 | } 107 | } 108 | impl AsFd for RouteSocket { 109 | fn as_fd(&self) -> BorrowedFd<'_> { 110 | self.0.as_fd() 111 | } 112 | } 113 | impl RouteSocket { 114 | pub(crate) fn new() -> io::Result { 115 | Ok(Self(route_socket()?)) 116 | } 117 | pub(crate) fn send(&self, buf: &[u8]) -> io::Result { 118 | self.0.send(buf, 0) 119 | } 120 | pub(crate) fn recv(&self, mut buf: &mut [u8]) -> io::Result { 121 | self.0.recv(&mut buf, 0) 122 | } 123 | pub(crate) fn add_membership(&mut self) -> io::Result<()> { 124 | self.0.add_membership(libc::RTNLGRP_IPV4_ROUTE)?; 125 | self.0.add_membership(libc::RTNLGRP_IPV6_ROUTE)?; 126 | Ok(()) 127 | } 128 | } 129 | 130 | impl RouteManager { 131 | /// Creates a new RouteManager. 132 | pub fn new() -> io::Result { 133 | Ok(Self { 134 | _private: std::marker::PhantomData, 135 | }) 136 | } 137 | /// Returns a new instance of RouteListener. 138 | pub fn listener() -> io::Result { 139 | RouteListener::new() 140 | } 141 | 142 | /// Lists all current routes. 143 | pub fn list(&mut self) -> io::Result> { 144 | let req = list_route_req(); 145 | let socket = RouteSocket::new()?; 146 | socket.send(&req)?; 147 | let mut buf = vec![0; 4096]; 148 | let mut list = Vec::new(); 149 | loop { 150 | let len = socket.recv(&mut buf)?; 151 | let rs = deserialize_res( 152 | |route| { 153 | list.push(route); 154 | }, 155 | &buf[..len], 156 | )?; 157 | if !rs { 158 | break; 159 | } 160 | } 161 | Ok(convert_add_route(list)) 162 | } 163 | /// Adds a new route. 164 | pub fn add(&mut self, route: &Route) -> io::Result<()> { 165 | let req = add_route_req(route)?; 166 | let socket = RouteSocket::new()?; 167 | socket.send(&req)?; 168 | let mut buf = vec![0; 4096]; 169 | let len = socket.recv(&mut buf)?; 170 | deserialize_res(|_| {}, &buf[..len]).map(|_| ()) 171 | } 172 | /// Deletes an existing route. 173 | pub fn delete(&mut self, route: &Route) -> io::Result<()> { 174 | let req = delete_route_req(route)?; 175 | let socket = RouteSocket::new()?; 176 | socket.send(&req)?; 177 | let mut buf = vec![0; 4096]; 178 | let len = socket.recv(&mut buf)?; 179 | deserialize_res(|_| {}, &buf[..len]).map(|_| ()) 180 | } 181 | } 182 | pub(crate) fn route_socket() -> io::Result { 183 | let mut socket = Socket::new(NETLINK_ROUTE)?; 184 | let _port_number = socket.bind_auto()?.port_number(); 185 | socket.connect(&SocketAddr::new(0, 0))?; 186 | Ok(socket) 187 | } 188 | pub(crate) fn convert_add_route(list: Vec) -> Vec { 189 | list.into_iter() 190 | .filter_map(|v| { 191 | if let RouteChange::Add(route) = v { 192 | Some(route) 193 | } else { 194 | None 195 | } 196 | }) 197 | .collect() 198 | } 199 | 200 | pub(crate) fn deserialize_res( 201 | mut add_fn: F, 202 | receive_buffer: &[u8], 203 | ) -> io::Result { 204 | let mut offset = 0; 205 | loop { 206 | let bytes = &receive_buffer[offset..]; 207 | if bytes.is_empty() { 208 | return Ok(false); 209 | } 210 | let rx_packet = >::deserialize(bytes) 211 | .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{e:?}")))?; 212 | match rx_packet.payload { 213 | NetlinkPayload::Done(_) => return Ok(true), 214 | NetlinkPayload::Error(e) => { 215 | if e.code.is_none() { 216 | return Ok(true); 217 | } 218 | return Err(e.to_io()); 219 | } 220 | NetlinkPayload::Noop => {} 221 | NetlinkPayload::Overrun(_) => {} 222 | NetlinkPayload::InnerMessage(msg) => match msg { 223 | RouteNetlinkMessage::NewRoute(msg) => add_fn(RouteChange::Add(msg.try_into()?)), 224 | RouteNetlinkMessage::DelRoute(msg) => add_fn(RouteChange::Delete(msg.try_into()?)), 225 | _ => {} 226 | }, 227 | _ => {} 228 | } 229 | 230 | offset += rx_packet.header.length as usize; 231 | if rx_packet.header.length == 0 { 232 | return Ok(false); 233 | } 234 | } 235 | } 236 | 237 | impl TryFrom for Route { 238 | type Error = io::Error; 239 | 240 | fn try_from(msg: RouteMessage) -> Result { 241 | let mut destination = None; 242 | let mut gateway = None; 243 | let prefix = msg.header.destination_prefix_length; 244 | let source_prefix = msg.header.source_prefix_length; 245 | let mut source = None; 246 | let table = msg.header.table; 247 | let mut if_index = None; 248 | let mut metric = None; 249 | let mut pref_source = None; 250 | for x in msg.attributes { 251 | match x { 252 | RouteAttribute::Metrics(_) => {} 253 | RouteAttribute::MfcStats(_) => {} 254 | RouteAttribute::MultiPath(_) => {} 255 | RouteAttribute::CacheInfo(_) => {} 256 | RouteAttribute::Destination(addr) => { 257 | destination = route_address_to_ip(addr); 258 | } 259 | RouteAttribute::Source(addr) => { 260 | source = route_address_to_ip(addr); 261 | } 262 | RouteAttribute::Gateway(addr) => { 263 | gateway = route_address_to_ip(addr); 264 | } 265 | RouteAttribute::PrefSource(addr) => { 266 | pref_source = route_address_to_ip(addr); 267 | } 268 | RouteAttribute::Via(_) => {} 269 | RouteAttribute::NewDestination(_) => {} 270 | RouteAttribute::Preference(_) => {} 271 | RouteAttribute::EncapType(_) => {} 272 | RouteAttribute::Encap(_) => {} 273 | RouteAttribute::Expires(_) => {} 274 | RouteAttribute::MulticastExpires(_) => {} 275 | RouteAttribute::Uid(_) => {} 276 | RouteAttribute::TtlPropagate(_) => {} 277 | RouteAttribute::Iif(_) => {} 278 | RouteAttribute::Oif(v) => { 279 | if_index = Some(v); 280 | } 281 | RouteAttribute::Priority(v) => metric = Some(v), 282 | RouteAttribute::Realm(_) => {} 283 | RouteAttribute::Table(_) => {} 284 | RouteAttribute::Mark(_) => {} 285 | RouteAttribute::Other(_) => {} 286 | _ => {} 287 | } 288 | } 289 | let destination = if let Some(destination) = destination { 290 | destination 291 | } else { 292 | match msg.header.address_family { 293 | AddressFamily::Inet => Ipv4Addr::UNSPECIFIED.into(), 294 | AddressFamily::Inet6 => Ipv6Addr::UNSPECIFIED.into(), 295 | _ => { 296 | return Err(io::Error::new( 297 | io::ErrorKind::InvalidData, 298 | "invalid destination family", 299 | )) 300 | } 301 | } 302 | }; 303 | let mut route = Route::new(destination, prefix).with_table(table); 304 | if let Some(source) = source { 305 | route = route.with_source(source, source_prefix); 306 | } 307 | if let Some(if_index) = if_index { 308 | route = route.with_if_index(if_index); 309 | route.if_name = crate::unix::if_index_to_name(if_index).ok(); 310 | } 311 | if let Some(gateway) = gateway { 312 | route = route.with_gateway(gateway); 313 | } 314 | if let Some(metric) = metric { 315 | route = route.with_metric(metric); 316 | } 317 | if let Some(pref_source) = pref_source { 318 | route = route.with_pref_source(pref_source); 319 | } 320 | Ok(route) 321 | } 322 | } 323 | impl TryFrom<&Route> for RouteMessage { 324 | type Error = io::Error; 325 | fn try_from(route: &Route) -> Result { 326 | route.check()?; 327 | let mut route_msg = RouteMessage::default(); 328 | route_msg.header.address_family = if route.destination.is_ipv4() { 329 | AddressFamily::Inet 330 | } else { 331 | AddressFamily::Inet6 332 | }; 333 | route_msg.header.destination_prefix_length = route.prefix; 334 | route_msg.header.protocol = RouteProtocol::Static; 335 | route_msg.header.scope = RouteScope::Universe; 336 | route_msg.header.kind = RouteType::Unicast; 337 | route_msg.header.table = route.table; 338 | route_msg 339 | .attributes 340 | .push(RouteAttribute::Destination(route.destination.into())); 341 | if let Some(gateway) = route.gateway { 342 | route_msg 343 | .attributes 344 | .push(RouteAttribute::Gateway(gateway.into())); 345 | } 346 | if let Some(if_index) = route.get_index() { 347 | route_msg.attributes.push(RouteAttribute::Oif(if_index)); 348 | } 349 | if let Some(metric) = route.metric { 350 | route_msg.attributes.push(RouteAttribute::Priority(metric)); 351 | } 352 | if let Some(source) = route.source { 353 | route_msg.header.source_prefix_length = route.source_prefix; 354 | route_msg 355 | .attributes 356 | .push(RouteAttribute::Source(source.into())); 357 | } 358 | if let Some(pref_source) = route.pref_source { 359 | route_msg 360 | .attributes 361 | .push(RouteAttribute::PrefSource(pref_source.into())); 362 | } 363 | 364 | Ok(route_msg) 365 | } 366 | } 367 | 368 | pub(crate) fn list_route_req() -> Vec { 369 | let mut nl_hdr = NetlinkHeader::default(); 370 | nl_hdr.flags = NLM_F_REQUEST | NLM_F_DUMP; 371 | let mut packet = NetlinkMessage::new( 372 | nl_hdr, 373 | NetlinkPayload::from(RouteNetlinkMessage::GetRoute(RouteMessage::default())), 374 | ); 375 | 376 | packet.finalize(); 377 | 378 | let mut buf = vec![0; packet.header.length as usize]; 379 | packet.serialize(&mut buf[..]); 380 | buf 381 | } 382 | 383 | pub(crate) fn add_route_req(route: &Route) -> io::Result> { 384 | let mut nl_hdr = NetlinkHeader::default(); 385 | nl_hdr.flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK; 386 | 387 | let mut packet = NetlinkMessage::new( 388 | nl_hdr, 389 | NetlinkPayload::from(RouteNetlinkMessage::NewRoute(route.try_into()?)), 390 | ); 391 | 392 | packet.finalize(); 393 | 394 | let mut buf = vec![0; packet.header.length as usize]; 395 | packet.serialize(&mut buf[..]); 396 | Ok(buf) 397 | } 398 | 399 | pub(crate) fn delete_route_req(route: &Route) -> io::Result> { 400 | let mut nl_hdr = NetlinkHeader::default(); 401 | nl_hdr.message_type = RTM_DELROUTE; 402 | nl_hdr.flags = NLM_F_REQUEST | NLM_F_ACK; 403 | 404 | let mut packet = NetlinkMessage::new( 405 | nl_hdr, 406 | NetlinkPayload::from(RouteNetlinkMessage::DelRoute(route.try_into()?)), 407 | ); 408 | 409 | packet.finalize(); 410 | 411 | let mut buf = vec![0; packet.header.length as usize]; 412 | packet.serialize(&mut buf[..]); 413 | Ok(buf) 414 | } 415 | 416 | fn route_address_to_ip(addr: RouteAddress) -> Option { 417 | match addr { 418 | RouteAddress::Inet(ip) => Some(IpAddr::V4(ip)), 419 | RouteAddress::Inet6(ip) => Some(IpAddr::V6(ip)), 420 | _ => None, 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /src/unix/async_route/async_io.rs: -------------------------------------------------------------------------------- 1 | use async_io::Async; 2 | use std::io; 3 | use std::os::fd::{AsFd, AsRawFd}; 4 | 5 | pub struct AsyncRoute { 6 | fd: Async, 7 | } 8 | impl AsyncRoute { 9 | pub fn new(fd: T) -> io::Result { 10 | Ok(Self { 11 | fd: Async::new(fd)?, 12 | }) 13 | } 14 | pub async fn read_with(&mut self, op: impl FnMut(&mut T) -> io::Result) -> io::Result { 15 | unsafe { self.fd.read_with_mut(op).await } 16 | } 17 | pub async fn write_with( 18 | &mut self, 19 | op: impl FnMut(&mut T) -> io::Result, 20 | ) -> io::Result { 21 | unsafe { self.fd.write_with_mut(op).await } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/unix/async_route/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "async")] 2 | mod tokio; 3 | #[cfg(feature = "async")] 4 | pub(crate) use tokio::*; 5 | #[cfg(all(feature = "async_io", not(feature = "async")))] 6 | mod async_io; 7 | #[cfg(all(feature = "async_io", not(feature = "async")))] 8 | pub(crate) use async_io::*; 9 | -------------------------------------------------------------------------------- /src/unix/async_route/tokio.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::os::fd::AsRawFd; 3 | use tokio::io::unix::AsyncFd; 4 | use tokio::io::Interest; 5 | 6 | pub struct AsyncRoute { 7 | fd: AsyncFd, 8 | } 9 | impl AsyncRoute { 10 | pub fn new(fd: T) -> io::Result { 11 | let mut nonblocking = true as libc::c_int; 12 | if unsafe { libc::ioctl(fd.as_raw_fd(), libc::FIONBIO, &mut nonblocking) } != 0 { 13 | return Err(io::Error::last_os_error()); 14 | } 15 | Ok(AsyncRoute { 16 | fd: AsyncFd::new(fd)?, 17 | }) 18 | } 19 | pub async fn read_with( 20 | &mut self, 21 | mut op: impl FnMut(&mut T) -> io::Result, 22 | ) -> io::Result { 23 | self.fd 24 | .async_io_mut(Interest::READABLE.add(Interest::ERROR), |fd| op(fd)) 25 | .await 26 | } 27 | pub async fn write_with( 28 | &mut self, 29 | mut op: impl FnMut(&mut T) -> io::Result, 30 | ) -> io::Result { 31 | self.fd.async_io_mut(Interest::WRITABLE, |fd| op(fd)).await 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/unix/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "shutdown")] 2 | mod shutdown; 3 | 4 | #[cfg(any(feature = "async", feature = "async_io"))] 5 | mod async_route; 6 | #[cfg(any(feature = "async", feature = "async_io"))] 7 | pub(crate) use async_route::*; 8 | use libc::c_char; 9 | #[cfg(feature = "shutdown")] 10 | pub use shutdown::*; 11 | use std::ffi::{CStr, CString}; 12 | use std::io; 13 | 14 | pub(crate) fn if_name_to_index(name: &str) -> io::Result { 15 | let name = CString::new(name)?; 16 | let idx = unsafe { libc::if_nametoindex(name.as_ptr()) }; 17 | if idx != 0 { 18 | Ok(idx) 19 | } else { 20 | Err(io::Error::last_os_error()) 21 | } 22 | } 23 | pub(crate) fn if_index_to_name(index: u32) -> io::Result { 24 | let mut ifname: [c_char; 256] = unsafe { std::mem::zeroed() }; 25 | 26 | unsafe { 27 | if libc::if_indextoname(index as libc::c_uint, ifname.as_mut_ptr()).is_null() { 28 | Err(io::Error::last_os_error()) 29 | } else { 30 | let ifname_str = CStr::from_ptr(ifname.as_ptr()) 31 | .to_str() 32 | .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8"))?; 33 | Ok(ifname_str.to_string()) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/unix/shutdown.rs: -------------------------------------------------------------------------------- 1 | use crate::RouteListener; 2 | use std::io; 3 | use std::os::fd::AsRawFd; 4 | use std::sync::atomic::{AtomicBool, Ordering}; 5 | use std::sync::Arc; 6 | 7 | #[cfg(any(target_os = "linux", target_os = "freebsd"))] 8 | pub(crate) struct EventFd(std::fs::File); 9 | #[cfg(any(target_os = "linux", target_os = "freebsd"))] 10 | impl EventFd { 11 | pub(crate) fn new() -> io::Result { 12 | #[cfg(not(target_os = "espidf"))] 13 | let flags = libc::EFD_CLOEXEC | libc::EFD_NONBLOCK; 14 | // ESP-IDF is EFD_NONBLOCK by default and errors if you try to pass this flag. 15 | #[cfg(target_os = "espidf")] 16 | let flags = 0; 17 | let event_fd = unsafe { libc::eventfd(0, flags) }; 18 | if event_fd < 0 { 19 | return Err(io::Error::last_os_error()); 20 | } 21 | use std::os::fd::FromRawFd; 22 | let file = unsafe { std::fs::File::from_raw_fd(event_fd) }; 23 | Ok(Self(file)) 24 | } 25 | fn wake(&self) -> io::Result<()> { 26 | use std::io::Write; 27 | let buf: [u8; 8] = 1u64.to_ne_bytes(); 28 | match (&self.0).write_all(&buf) { 29 | Ok(_) => Ok(()), 30 | Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => Ok(()), 31 | Err(err) => Err(err), 32 | } 33 | } 34 | fn as_event_fd(&self) -> libc::c_int { 35 | self.0.as_raw_fd() as _ 36 | } 37 | } 38 | #[cfg(target_os = "macos")] 39 | struct EventFd(libc::c_int, libc::c_int); 40 | #[cfg(target_os = "macos")] 41 | impl EventFd { 42 | fn new() -> io::Result { 43 | let mut fds: [libc::c_int; 2] = [0; 2]; 44 | if unsafe { libc::pipe(fds.as_mut_ptr()) } == -1 { 45 | return Err(io::Error::last_os_error()); 46 | } 47 | let read_fd = fds[0]; 48 | let write_fd = fds[1]; 49 | Ok(Self(read_fd, write_fd)) 50 | } 51 | fn wake(&self) -> io::Result<()> { 52 | let buf: [u8; 8] = 1u64.to_ne_bytes(); 53 | let res = unsafe { libc::write(self.1, buf.as_ptr() as *const libc::c_void, buf.len()) }; 54 | if res == -1 { 55 | Err(io::Error::last_os_error()) 56 | } else { 57 | Ok(()) 58 | } 59 | } 60 | fn as_event_fd(&self) -> libc::c_int { 61 | self.0 62 | } 63 | } 64 | #[cfg(target_os = "macos")] 65 | impl Drop for EventFd { 66 | fn drop(&mut self) { 67 | unsafe { 68 | let _ = libc::close(self.0); 69 | let _ = libc::close(self.1); 70 | } 71 | } 72 | } 73 | impl RouteListener { 74 | pub(crate) fn wait(&self) -> io::Result<()> { 75 | let fd = self.as_raw_fd() as libc::c_int; 76 | 77 | let event_fd = self.shutdown_handle.event_fd.as_event_fd(); 78 | let mut readfds: libc::fd_set = unsafe { std::mem::zeroed() }; 79 | unsafe { 80 | libc::FD_SET(fd, &mut readfds); 81 | libc::FD_SET(event_fd, &mut readfds); 82 | } 83 | let result = unsafe { 84 | libc::select( 85 | fd.max(event_fd) + 1, 86 | &mut readfds, 87 | std::ptr::null_mut(), 88 | std::ptr::null_mut(), 89 | std::ptr::null_mut(), 90 | ) 91 | }; 92 | if self.shutdown_handle.is_shutdown.load(Ordering::Relaxed) { 93 | return Err(io::Error::new(io::ErrorKind::Interrupted, "shutdown")); 94 | } 95 | if result == -1 { 96 | return Err(io::Error::last_os_error()); 97 | } 98 | if result == 0 { 99 | return Err(io::Error::from(io::ErrorKind::TimedOut)); 100 | } 101 | Ok(()) 102 | } 103 | /// Retrieves a shutdown handle for the RouteListener. 104 | pub fn shutdown_handle(&self) -> io::Result { 105 | Ok(self.shutdown_handle.clone()) 106 | } 107 | } 108 | 109 | /// Shutdown handle for the RouteListener, used to stop listening. 110 | #[derive(Clone)] 111 | pub struct RouteListenerShutdown { 112 | is_shutdown: Arc, 113 | event_fd: Arc, 114 | } 115 | impl RouteListenerShutdown { 116 | pub(crate) fn new() -> io::Result { 117 | Ok(Self { 118 | is_shutdown: Arc::new(Default::default()), 119 | event_fd: Arc::new(EventFd::new()?), 120 | }) 121 | } 122 | /// Shuts down the RouteListener. 123 | pub fn shutdown(&self) -> io::Result<()> { 124 | self.is_shutdown.store(true, Ordering::Relaxed); 125 | self.event_fd.wake() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/unix_bsd/async_route.rs: -------------------------------------------------------------------------------- 1 | use crate::unix_bsd::bind::*; 2 | use crate::unix_bsd::{ 3 | add_or_del_route_req, create_route_socket, deserialize_res, deserialize_res_change, 4 | list_routes, m_rtmsg, 5 | }; 6 | use crate::Route; 7 | use crate::{AsyncRoute, RouteChange}; 8 | use std::collections::VecDeque; 9 | use std::io; 10 | use std::io::{Read, Write}; 11 | use std::os::unix::net::UnixStream; 12 | 13 | /// AsyncRouteListener for asynchronously receiving route change events. 14 | pub struct AsyncRouteListener { 15 | list: VecDeque, 16 | route_socket: AsyncRoute, 17 | } 18 | impl AsyncRouteListener { 19 | /// Creates a new AsyncRouteListener. 20 | pub fn new() -> io::Result { 21 | let route_socket = create_route_socket()?; 22 | let route_socket = AsyncRoute::new(route_socket)?; 23 | Ok(AsyncRouteListener { 24 | list: Default::default(), 25 | route_socket, 26 | }) 27 | } 28 | /// Asynchronously listens for a route change event and returns a RouteChange. 29 | pub async fn listen(&mut self) -> io::Result { 30 | if let Some(route) = self.list.pop_front() { 31 | return Ok(route); 32 | } 33 | let mut buf = [0u8; 2048]; 34 | let route_socket = &mut self.route_socket; 35 | loop { 36 | let read = route_socket.read_with(|s| s.read(&mut buf)).await?; 37 | 38 | deserialize_res_change( 39 | |route| { 40 | self.list.push_back(route); 41 | }, 42 | &buf[..read], 43 | )?; 44 | if let Some(route) = self.list.pop_front() { 45 | return Ok(route); 46 | } 47 | } 48 | } 49 | } 50 | /// AsyncRouteManager for asynchronously managing routes (adding, deleting, and listing). 51 | pub struct AsyncRouteManager { 52 | _private: std::marker::PhantomData<()>, 53 | } 54 | 55 | impl AsyncRouteManager { 56 | /// Creates a new AsyncRouteManager. 57 | pub fn new() -> io::Result { 58 | Ok(AsyncRouteManager { 59 | _private: std::marker::PhantomData, 60 | }) 61 | } 62 | /// Retrieves a new instance of AsyncRouteListener. 63 | pub fn listener() -> io::Result { 64 | AsyncRouteListener::new() 65 | } 66 | 67 | /// Asynchronously lists all current routes. 68 | /// **Note: On macOS and FreeBSD, this is not truly asynchronous.** 69 | pub async fn list(&mut self) -> io::Result> { 70 | list_routes() 71 | } 72 | /// Asynchronously adds a new route. 73 | pub async fn add(&mut self, route: &Route) -> io::Result<()> { 74 | add_route(route).await 75 | } 76 | /// Asynchronously deletes an existing route. 77 | pub async fn delete(&mut self, route: &Route) -> io::Result<()> { 78 | delete_route(route).await 79 | } 80 | } 81 | 82 | async fn add_route(route: &Route) -> io::Result<()> { 83 | add_or_del_route(route, RTM_ADD as u8).await 84 | } 85 | async fn delete_route(route: &Route) -> io::Result<()> { 86 | add_or_del_route(route, RTM_DELETE as u8).await 87 | } 88 | 89 | async fn add_or_del_route(route: &Route, rtm_type: u8) -> io::Result<()> { 90 | let rtmsg = add_or_del_route_req(route, rtm_type)?; 91 | let route_socket = create_route_socket()?; 92 | 93 | let mut route_socket = AsyncRoute::new(route_socket)?; 94 | 95 | route_socket 96 | .write_with(|s| s.write_all(rtmsg.slice())) 97 | .await?; 98 | 99 | let mut buf = [0u8; std::mem::size_of::()]; 100 | let len = route_socket.read_with(|s| s.read(&mut buf)).await?; 101 | deserialize_res(|_, _| {}, &buf[..len])?; 102 | 103 | Ok(()) 104 | } 105 | -------------------------------------------------------------------------------- /src/unix_bsd/bind.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings)] 2 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 3 | -------------------------------------------------------------------------------- /src/unix_bsd/mod.rs: -------------------------------------------------------------------------------- 1 | // See https://github.com/johnyburd/net-route/blob/main/src/platform_impl/macos/macos.rs 2 | 3 | use crate::{Route, RouteChange}; 4 | use std::collections::VecDeque; 5 | use std::io::{Read, Write}; 6 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 7 | use std::os::fd::{AsRawFd, FromRawFd, RawFd}; 8 | use std::os::unix::net::UnixStream; 9 | use std::{io, mem}; 10 | #[cfg(any(feature = "async", feature = "async_io"))] 11 | mod async_route; 12 | #[cfg(any(feature = "async", feature = "async_io"))] 13 | pub use async_route::*; 14 | mod bind; 15 | use crate::if_index_to_name; 16 | use bind::*; 17 | /// RouteListener for receiving route change events. 18 | pub struct RouteListener { 19 | list: VecDeque, 20 | route_socket: UnixStream, 21 | #[cfg(feature = "shutdown")] 22 | pub(crate) shutdown_handle: crate::RouteListenerShutdown, 23 | } 24 | impl RouteListener { 25 | /// Creates a new RouteListener. 26 | pub fn new() -> io::Result { 27 | let route_socket = create_route_socket()?; 28 | #[cfg(feature = "shutdown")] 29 | route_socket.set_nonblocking(true)?; 30 | Ok(RouteListener { 31 | list: Default::default(), 32 | route_socket, 33 | #[cfg(feature = "shutdown")] 34 | shutdown_handle: crate::RouteListenerShutdown::new()?, 35 | }) 36 | } 37 | 38 | /// Listens for a route change event and returns a RouteChange. 39 | #[cfg(not(feature = "shutdown"))] 40 | pub fn listen(&mut self) -> io::Result { 41 | if let Some(route) = self.list.pop_front() { 42 | return Ok(route); 43 | } 44 | let mut buf = [0u8; 4096]; 45 | let route_socket = &mut self.route_socket; 46 | loop { 47 | let len = route_socket.read(&mut buf)?; 48 | 49 | deserialize_res_change( 50 | |route| { 51 | self.list.push_back(route); 52 | }, 53 | &buf[..len], 54 | )?; 55 | if let Some(route) = self.list.pop_front() { 56 | return Ok(route); 57 | } 58 | } 59 | } 60 | } 61 | impl AsRawFd for RouteListener { 62 | fn as_raw_fd(&self) -> RawFd { 63 | self.route_socket.as_raw_fd() 64 | } 65 | } 66 | impl RouteListener { 67 | /// Listens for a route change event and returns a RouteChange. 68 | #[cfg(feature = "shutdown")] 69 | pub fn listen(&mut self) -> io::Result { 70 | if let Some(route) = self.list.pop_front() { 71 | return Ok(route); 72 | } 73 | let mut buf = [0u8; 4096]; 74 | loop { 75 | self.wait()?; 76 | let len = match self.route_socket.read(&mut buf) { 77 | Ok(list) => list, 78 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, 79 | Err(e) => return Err(e), 80 | }; 81 | deserialize_res_change( 82 | |route| { 83 | self.list.push_back(route); 84 | }, 85 | &buf[..len], 86 | )?; 87 | if let Some(route) = self.list.pop_front() { 88 | return Ok(route); 89 | } 90 | } 91 | } 92 | } 93 | 94 | /// RouteManager is used for managing routes (adding, deleting, and listing). 95 | pub struct RouteManager { 96 | _private: std::marker::PhantomData<()>, 97 | } 98 | impl RouteManager { 99 | /// Creates a new RouteManager. 100 | pub fn new() -> io::Result { 101 | Ok(Self { 102 | _private: std::marker::PhantomData, 103 | }) 104 | } 105 | /// Returns a new instance of RouteListener. 106 | pub fn listener() -> io::Result { 107 | RouteListener::new() 108 | } 109 | /// Lists all current routes. 110 | pub fn list(&mut self) -> io::Result> { 111 | list_routes() 112 | } 113 | /// Adds a new route. 114 | pub fn add(&mut self, route: &Route) -> io::Result<()> { 115 | add_route(route) 116 | } 117 | /// Deletes an existing route. 118 | pub fn delete(&mut self, route: &Route) -> io::Result<()> { 119 | delete_route(route) 120 | } 121 | } 122 | 123 | fn try_get_msg_buf() -> io::Result> { 124 | const MAX_RETRYS: usize = 3; 125 | 126 | for _ in 0..MAX_RETRYS { 127 | let mut mib: [u32; 6] = [0; 6]; 128 | let mut len = 0; 129 | 130 | mib[0] = CTL_NET; 131 | mib[1] = AF_ROUTE; 132 | mib[2] = 0; 133 | mib[3] = 0; // family: ipv4 & ipv6 134 | mib[4] = NET_RT_DUMP; 135 | // mib[5] flags: 0 136 | 137 | // see: https://github.com/golang/net/blob/ec05fdcd71141c885f3fb84c41d1c692f094ccbe/route/route.go#L126 138 | if unsafe { 139 | sysctl( 140 | &mut mib as *mut _ as *mut _, 141 | 6, 142 | std::ptr::null_mut(), 143 | &mut len, 144 | std::ptr::null_mut(), 145 | 0, 146 | ) 147 | } < 0 148 | { 149 | return Err(io::Error::last_os_error()); 150 | } 151 | 152 | let mut msgs_buf: Vec = vec![0; len]; 153 | 154 | if unsafe { 155 | sysctl( 156 | &mut mib as *mut _ as *mut _, 157 | 6, 158 | msgs_buf.as_mut_ptr() as _, 159 | &mut len, 160 | std::ptr::null_mut(), 161 | 0, 162 | ) 163 | } < 0 164 | { 165 | // will retry return error if 166 | continue; 167 | } else { 168 | return Ok(msgs_buf); 169 | } 170 | } 171 | 172 | Err(io::Error::new( 173 | io::ErrorKind::Other, 174 | "Failed to get routing table", 175 | )) 176 | } 177 | 178 | fn list_routes() -> io::Result> { 179 | let msgs_buf = try_get_msg_buf()?; 180 | 181 | let mut routes = vec![]; 182 | deserialize_res( 183 | |rtm_type, route| { 184 | if rtm_type == RTM_GET { 185 | routes.push(route); 186 | } 187 | }, 188 | &msgs_buf, 189 | )?; 190 | Ok(routes) 191 | } 192 | 193 | fn add_route(route: &Route) -> io::Result<()> { 194 | add_or_del_route(route, RTM_ADD as u8) 195 | } 196 | fn delete_route(route: &Route) -> io::Result<()> { 197 | add_or_del_route(route, RTM_DELETE as u8) 198 | } 199 | 200 | fn add_or_del_route_req(route: &Route, rtm_type: u8) -> io::Result { 201 | let rtm_flags = RTF_STATIC | RTF_UP; 202 | 203 | let rtm_addrs = RTA_DST | RTA_NETMASK | RTA_GATEWAY; 204 | 205 | let mut rtmsg: m_rtmsg = route.try_into()?; 206 | 207 | rtmsg.hdr.rtm_addrs = rtm_addrs as i32; 208 | rtmsg.hdr.rtm_seq = 1; 209 | rtmsg.hdr.rtm_flags = rtm_flags as i32; 210 | rtmsg.hdr.rtm_type = rtm_type; 211 | rtmsg.hdr.rtm_version = RTM_VERSION as u8; 212 | Ok(rtmsg) 213 | } 214 | 215 | fn add_or_del_route(route: &Route, rtm_type: u8) -> io::Result<()> { 216 | let rtmsg = add_or_del_route_req(route, rtm_type)?; 217 | let fd = unsafe { socket(PF_ROUTE as i32, SOCK_RAW as i32, AF_UNSPEC as i32) }; 218 | if fd < 0 { 219 | return Err(io::Error::last_os_error()); 220 | } 221 | 222 | let mut route_fd = unsafe { UnixStream::from_raw_fd(fd) }; 223 | 224 | route_fd.write_all(rtmsg.slice())?; 225 | 226 | let mut buf = [0u8; std::mem::size_of::()]; 227 | 228 | let len = route_fd.read(&mut buf)?; 229 | deserialize_res(|_, _| {}, &buf[..len])?; 230 | 231 | Ok(()) 232 | } 233 | 234 | impl TryFrom<&Route> for m_rtmsg { 235 | type Error = io::Error; 236 | fn try_from(value: &Route) -> Result { 237 | value.check()?; 238 | let mut rtmsg = m_rtmsg { 239 | hdr: rt_msghdr::default(), 240 | attrs: [0u8; 512], 241 | }; 242 | 243 | let mut attr_offset = put_ip_addr(0, &mut rtmsg, value.destination)?; 244 | 245 | if let Some(gateway) = value.gateway { 246 | attr_offset = put_ip_addr(attr_offset, &mut rtmsg, gateway)?; 247 | } 248 | 249 | if let Some(if_index) = value.get_index() { 250 | attr_offset = put_ifa_addr(attr_offset, &mut rtmsg, if_index)?; 251 | } 252 | 253 | attr_offset = put_ip_addr(attr_offset, &mut rtmsg, value.mask())?; 254 | 255 | let msg_len = std::mem::size_of::() + attr_offset; 256 | rtmsg.hdr.rtm_msglen = msg_len as u16; 257 | Ok(rtmsg) 258 | } 259 | } 260 | fn put_ifa_addr(mut attr_offset: usize, rtmsg: &mut m_rtmsg, if_index: u32) -> io::Result { 261 | let sdl_len = std::mem::size_of::(); 262 | let sa_dl = sockaddr_dl { 263 | sdl_len: sdl_len as u8, 264 | sdl_family: AF_LINK as u8, 265 | sdl_index: if_index as u16, 266 | ..Default::default() 267 | }; 268 | 269 | let sa_ptr = &sa_dl as *const sockaddr_dl as *const u8; 270 | let sa_bytes = unsafe { std::slice::from_raw_parts(sa_ptr, sdl_len) }; 271 | rtmsg.attrs[attr_offset..attr_offset + sdl_len].copy_from_slice(sa_bytes); 272 | 273 | attr_offset += sa_size(sdl_len); 274 | Ok(attr_offset) 275 | } 276 | fn put_ip_addr(mut attr_offset: usize, rtmsg: &mut m_rtmsg, addr: IpAddr) -> io::Result { 277 | match addr { 278 | IpAddr::V4(addr) => { 279 | let sa_len = std::mem::size_of::(); 280 | let sa_in: sockaddr_in = addr.into(); 281 | 282 | let sa_ptr = &sa_in as *const sockaddr_in as *const u8; 283 | let sa_bytes = unsafe { std::slice::from_raw_parts(sa_ptr, sa_len) }; 284 | rtmsg.attrs[attr_offset..attr_offset + sa_len].copy_from_slice(sa_bytes); 285 | 286 | attr_offset += sa_size(sa_len); 287 | } 288 | IpAddr::V6(addr) => { 289 | let sa_len = std::mem::size_of::(); 290 | let sa_in: sockaddr_in6 = addr.into(); 291 | 292 | let sa_ptr = &sa_in as *const sockaddr_in6 as *const u8; 293 | let sa_bytes = unsafe { std::slice::from_raw_parts(sa_ptr, sa_len) }; 294 | rtmsg.attrs[attr_offset..attr_offset + sa_len].copy_from_slice(sa_bytes); 295 | 296 | attr_offset += sa_size(sa_len); 297 | } 298 | } 299 | Ok(attr_offset) 300 | } 301 | #[cfg(target_os = "macos")] 302 | fn sa_size(len: usize) -> usize { 303 | len 304 | } 305 | #[cfg(target_os = "freebsd")] 306 | fn sa_size(sa_len: usize) -> usize { 307 | // See https://github.com/freebsd/freebsd-src/blob/7e51bc6cdd5c317109e25b0b64230d00d68dceb3/contrib/bsnmp/lib/support.h#L89 308 | if sa_len == 0 { 309 | return std::mem::size_of::(); 310 | } 311 | 1 + ((sa_len - 1) | (std::mem::size_of::() - 1)) 312 | } 313 | fn deserialize_res_change(mut add_fn: F, msgs_buf: &[u8]) -> io::Result<()> { 314 | deserialize_res( 315 | |rtm_type, route| { 316 | let route = match rtm_type { 317 | RTM_ADD => RouteChange::Add(route), 318 | RTM_DELETE => RouteChange::Delete(route), 319 | RTM_CHANGE => RouteChange::Change(route), 320 | _ => return, 321 | }; 322 | add_fn(route); 323 | }, 324 | msgs_buf, 325 | ) 326 | } 327 | fn deserialize_res(mut add_fn: F, msgs_buf: &[u8]) -> io::Result<()> { 328 | let mut offset = 0; 329 | while offset + std::mem::size_of::() <= msgs_buf.len() { 330 | let buf = &msgs_buf[offset..]; 331 | 332 | let rt_hdr = unsafe { &*buf.as_ptr().cast::() }; 333 | assert_eq!(rt_hdr.rtm_version as u32, RTM_VERSION); 334 | if rt_hdr.rtm_errno != 0 { 335 | return Err(io::Error::from_raw_os_error(rt_hdr.rtm_errno)); 336 | } 337 | 338 | let msg_len = rt_hdr.rtm_msglen as usize; 339 | offset += msg_len; 340 | #[cfg(target_os = "macos")] 341 | if rt_hdr.rtm_flags as u32 & RTF_WASCLONED != 0 { 342 | continue; 343 | } 344 | let rt_msg = &buf[std::mem::size_of::()..msg_len]; 345 | 346 | if let Some(route) = message_to_route(rt_hdr, rt_msg) { 347 | add_fn(rt_hdr.rtm_type as u32, route); 348 | } 349 | } 350 | Ok(()) 351 | } 352 | 353 | fn message_to_route(hdr: &rt_msghdr, msg: &[u8]) -> Option { 354 | let mut gateway = None; 355 | 356 | // check if message has no destination 357 | if hdr.rtm_addrs & (1 << RTAX_DST) == 0 { 358 | return None; 359 | } 360 | 361 | // The body of the route message (msg) is a list of `struct sockaddr`. However, thanks to v6, 362 | // the size 363 | 364 | // See https://opensource.apple.com/source/network_cmds/network_cmds-606.40.2/netstat.tproj/route.c.auto.html, 365 | // function `get_rtaddrs()` 366 | let mut route_addresses = [None; RTAX_MAX as usize]; 367 | let mut cur_pos = 0; 368 | for (idx, item) in route_addresses 369 | .iter_mut() 370 | .enumerate() 371 | .take(RTAX_MAX as usize) 372 | { 373 | if hdr.rtm_addrs & (1 << idx) != 0 { 374 | let buf = &msg[cur_pos..]; 375 | if buf.len() < std::mem::size_of::() { 376 | continue; 377 | } 378 | assert!(buf.len() >= std::mem::size_of::()); 379 | let sa: &sockaddr = unsafe { &*(buf.as_ptr() as *const sockaddr) }; 380 | assert!(buf.len() >= sa.sa_len as usize); 381 | *item = Some(sa); 382 | #[cfg(target_os = "freebsd")] 383 | { 384 | cur_pos += sa_size(sa.sa_len as usize); 385 | } 386 | #[cfg(target_os = "macos")] 387 | { 388 | // see ROUNDUP() macro in the route.c file linked above. 389 | // The len needs to be a multiple of 4bytes 390 | let aligned_len = if sa.sa_len == 0 { 391 | 4 392 | } else { 393 | ((sa.sa_len - 1) | 0x3) + 1 394 | }; 395 | cur_pos += aligned_len as usize; 396 | } 397 | } 398 | } 399 | 400 | let destination = sa_to_ip(route_addresses[RTAX_DST as usize]?)?; 401 | let mut prefix = match destination { 402 | IpAddr::V4(_) => 32, 403 | IpAddr::V6(_) => 128, 404 | }; 405 | 406 | // check if message has a gateway 407 | if hdr.rtm_addrs & (1 << RTAX_GATEWAY) != 0 { 408 | let gw_sa = route_addresses[RTAX_GATEWAY as usize]?; 409 | gateway = sa_to_ip(gw_sa); 410 | if let Some(IpAddr::V6(v6gw)) = gateway { 411 | // unicast link local start with FE80:: 412 | let is_unicast_ll = v6gw.segments()[0] == 0xfe80; 413 | // v6 multicast starts with FF 414 | let is_multicast = v6gw.octets()[0] == 0xff; 415 | // lower 4 bit of byte1 encode the multicast scope 416 | let multicast_scope = v6gw.octets()[1] & 0x0f; 417 | // scope 1 is interface/node-local. scope 2 is link-local 418 | // RFC4291, Sec. 2.7 for the gory details 419 | if is_unicast_ll || (is_multicast && (multicast_scope == 1 || multicast_scope == 2)) { 420 | // how fun. So it looks like some kernels encode the scope_id of the v6 address in 421 | // byte 2 & 3 of the gateway IP, if it's unicast link_local, or multicast with interface-local 422 | // or link-local scope. So we need to set these two bytes to 0 to turn it into the 423 | // real gateway address 424 | // Logic again taken from route.c (see link above), function `p_sockaddr()` 425 | let segs = v6gw.segments(); 426 | gateway = Some(IpAddr::V6(Ipv6Addr::new( 427 | segs[0], 0, segs[2], segs[3], segs[4], segs[5], segs[6], segs[7], 428 | ))) 429 | } 430 | } 431 | } 432 | 433 | // check if message has netmask 434 | if hdr.rtm_addrs & (1 << RTAX_NETMASK) != 0 { 435 | match route_addresses[RTAX_NETMASK as usize] { 436 | None => prefix = 0, 437 | // Yes, apparently a 0 prefixlen is encoded as having an sa_len of 0 438 | // (at least in some cases). 439 | Some(sa) if sa.sa_len == 0 => prefix = 0, 440 | Some(sa) => match destination { 441 | IpAddr::V4(_) => { 442 | let mask_sa: &sockaddr_in = unsafe { mem::transmute(sa) }; 443 | prefix = u32::from_be(mask_sa.sin_addr.s_addr).leading_ones() as u8; 444 | } 445 | IpAddr::V6(_) => { 446 | let mask_sa: &sockaddr_in6 = unsafe { mem::transmute(sa) }; 447 | // sin6_addr.__u6_addr is a union that represents the 16 v6 bytes either as 448 | // 16 u8's or 16 u16's or 4 u32's. So we need the unsafe here because of the union 449 | prefix = u128::from_be_bytes(unsafe { mask_sa.sin6_addr.__u6_addr.__u6_addr8 }) 450 | .leading_ones() as u8; 451 | } 452 | }, 453 | } 454 | } 455 | 456 | Some(Route { 457 | destination, 458 | prefix, 459 | gateway, 460 | if_name: if_index_to_name(hdr.rtm_index as u32).ok(), 461 | if_index: Some(hdr.rtm_index as u32), 462 | }) 463 | } 464 | #[repr(C)] 465 | #[derive(Clone, Copy)] 466 | #[allow(non_camel_case_types)] 467 | struct m_rtmsg { 468 | hdr: rt_msghdr, 469 | attrs: [u8; 512], 470 | } 471 | impl m_rtmsg { 472 | pub(crate) fn slice(&self) -> &[u8] { 473 | let slice = { 474 | let ptr = self as *const m_rtmsg as *const u8; 475 | let len = self.hdr.rtm_msglen as usize; 476 | unsafe { std::slice::from_raw_parts(ptr, len) } 477 | }; 478 | slice 479 | } 480 | } 481 | impl Default for sockaddr_dl { 482 | fn default() -> Self { 483 | let mut sdl: sockaddr_dl = unsafe { mem::zeroed() }; 484 | sdl.sdl_len = std::mem::size_of::() as u8; 485 | sdl.sdl_family = AF_LINK as u8; 486 | sdl 487 | } 488 | } 489 | impl Default for rt_metrics { 490 | fn default() -> Self { 491 | unsafe { mem::zeroed() } 492 | } 493 | } 494 | impl Default for rt_msghdr { 495 | fn default() -> Self { 496 | unsafe { mem::zeroed() } 497 | } 498 | } 499 | 500 | fn sa_to_ip(sa: &sockaddr) -> Option { 501 | match sa.sa_family as u32 { 502 | AF_INET => { 503 | assert!(sa.sa_len as usize >= std::mem::size_of::()); 504 | let inet: &sockaddr_in = unsafe { std::mem::transmute(sa) }; 505 | let octets: [u8; 4] = inet.sin_addr.s_addr.to_ne_bytes(); 506 | Some(IpAddr::from(octets)) 507 | } 508 | AF_INET6 => { 509 | assert!(sa.sa_len as usize >= std::mem::size_of::()); 510 | let inet6: &sockaddr_in6 = unsafe { mem::transmute(sa) }; 511 | let octets: [u8; 16] = unsafe { inet6.sin6_addr.__u6_addr.__u6_addr8 }; 512 | Some(IpAddr::from(octets)) 513 | } 514 | AF_LINK => None, 515 | _ => None, 516 | } 517 | } 518 | impl From for sockaddr_in { 519 | fn from(ip: Ipv4Addr) -> Self { 520 | let sa_len = std::mem::size_of::(); 521 | sockaddr_in { 522 | sin_len: sa_len as u8, 523 | sin_family: AF_INET as u8, 524 | sin_port: 0, 525 | sin_addr: in_addr { 526 | s_addr: unsafe { mem::transmute::<[u8; 4], u32>(ip.octets()) }, 527 | }, 528 | sin_zero: [0i8; 8], 529 | } 530 | } 531 | } 532 | impl From for sockaddr_in6 { 533 | fn from(ip: Ipv6Addr) -> Self { 534 | let sa_len = std::mem::size_of::(); 535 | sockaddr_in6 { 536 | sin6_len: sa_len as u8, 537 | sin6_family: AF_INET6 as u8, 538 | sin6_port: 0, 539 | sin6_flowinfo: 0, 540 | sin6_addr: in6_addr { 541 | __u6_addr: unsafe { 542 | mem::transmute::<[u8; 16], in6_addr__bindgen_ty_1>(ip.octets()) 543 | }, 544 | }, 545 | sin6_scope_id: 0, 546 | } 547 | } 548 | } 549 | fn create_route_socket() -> io::Result { 550 | let fd = unsafe { socket(PF_ROUTE as i32, SOCK_RAW as i32, AF_UNSPEC as i32) }; 551 | if fd < 0 { 552 | return Err(io::Error::last_os_error()); 553 | } 554 | let route_fd = unsafe { UnixStream::from_raw_fd(fd) }; 555 | Ok(route_fd) 556 | } 557 | -------------------------------------------------------------------------------- /src/windows/async_route.rs: -------------------------------------------------------------------------------- 1 | use crate::{Route, RouteChange, RouteListener, RouteManager}; 2 | use std::io; 3 | 4 | /// AsyncRouteListener for asynchronously receiving route change events. 5 | pub struct AsyncRouteListener { 6 | route_listener: RouteListener, 7 | } 8 | impl AsyncRouteListener { 9 | /// Creates a new AsyncRouteListener. 10 | pub fn new() -> io::Result { 11 | Ok(Self { 12 | route_listener: RouteListener::new()?, 13 | }) 14 | } 15 | /// Asynchronously listens for a route change event and returns a RouteChange. 16 | pub async fn listen(&mut self) -> io::Result { 17 | self.route_listener 18 | .receiver 19 | .recv_async() 20 | .await 21 | .map_err(|_| io::Error::new(io::ErrorKind::Interrupted, "shutdown")) 22 | } 23 | } 24 | /// AsyncRouteManager for asynchronously managing routes (adding, deleting, and listing). 25 | pub struct AsyncRouteManager { 26 | _private: std::marker::PhantomData<()>, 27 | } 28 | impl AsyncRouteManager { 29 | /// Creates a new AsyncRouteManager. 30 | pub fn new() -> io::Result { 31 | Ok(Self { 32 | _private: std::marker::PhantomData, 33 | }) 34 | } 35 | /// Retrieves a new instance of AsyncRouteListener. 36 | pub fn listener() -> io::Result { 37 | AsyncRouteListener::new() 38 | } 39 | /// Asynchronously lists all current routes. 40 | /// **Note: On Windows, this is not truly asynchronous.** 41 | pub async fn list(&mut self) -> io::Result> { 42 | RouteManager::new()?.list() 43 | } 44 | /// Asynchronously adds a new route. 45 | /// **Note: On Windows, this is not truly asynchronous.** 46 | pub async fn add(&mut self, route: &Route) -> io::Result<()> { 47 | RouteManager::new()?.add(route) 48 | } 49 | 50 | /// Asynchronously deletes an existing route. 51 | /// **Note: On Windows, this is not truly asynchronous.** 52 | pub async fn delete(&mut self, route: &Route) -> io::Result<()> { 53 | RouteManager::new()?.delete(route) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/windows/ffi.rs: -------------------------------------------------------------------------------- 1 | use crate::Route; 2 | use std::net::IpAddr; 3 | use std::{io, mem}; 4 | use windows_sys::Win32::NetworkManagement::IpHelper::{ 5 | ConvertInterfaceAliasToLuid, ConvertInterfaceIndexToLuid, ConvertInterfaceLuidToAlias, 6 | ConvertInterfaceLuidToIndex, InitializeIpForwardEntry, MIB_IPFORWARD_ROW2, 7 | }; 8 | use windows_sys::Win32::NetworkManagement::Ndis::NET_LUID_LH; 9 | use windows_sys::Win32::Networking::WinSock::{AF_INET, AF_INET6, IN6_ADDR, IN_ADDR}; 10 | 11 | pub(crate) fn encode_utf16(string: &str) -> Vec { 12 | use std::iter::once; 13 | string.encode_utf16().chain(once(0)).collect() 14 | } 15 | 16 | pub(crate) fn decode_utf16(string: &[u16]) -> String { 17 | let end = string.iter().position(|b| *b == 0).unwrap_or(string.len()); 18 | String::from_utf16_lossy(&string[..end]) 19 | } 20 | pub(crate) fn if_name_to_index(name: &str) -> io::Result { 21 | let luid = alias_to_luid(name)?; 22 | luid_to_index(&luid) 23 | } 24 | pub(crate) fn if_index_to_name(index: u32) -> io::Result { 25 | let luid = index_to_luid(index)?; 26 | luid_to_alias(&luid) 27 | } 28 | pub(crate) fn alias_to_luid(alias: &str) -> io::Result { 29 | let alias = encode_utf16(alias); 30 | let mut luid = unsafe { mem::zeroed() }; 31 | match unsafe { ConvertInterfaceAliasToLuid(alias.as_ptr(), &mut luid) } { 32 | 0 => Ok(luid), 33 | _err => Err(io::Error::last_os_error()), 34 | } 35 | } 36 | 37 | pub(crate) fn index_to_luid(index: u32) -> io::Result { 38 | let mut luid = unsafe { mem::zeroed() }; 39 | match unsafe { ConvertInterfaceIndexToLuid(index, &mut luid) } { 40 | 0 => Ok(luid), 41 | _err => Err(io::Error::last_os_error()), 42 | } 43 | } 44 | 45 | pub(crate) fn luid_to_index(luid: &NET_LUID_LH) -> io::Result { 46 | let mut index = 0; 47 | match unsafe { ConvertInterfaceLuidToIndex(luid, &mut index) } { 48 | 0 => Ok(index), 49 | _err => Err(io::Error::last_os_error()), 50 | } 51 | } 52 | 53 | pub(crate) fn luid_to_alias(luid: &NET_LUID_LH) -> io::Result { 54 | // IF_MAX_STRING_SIZE + 1 55 | let mut alias = vec![0; 257]; 56 | match unsafe { ConvertInterfaceLuidToAlias(luid, alias.as_mut_ptr(), alias.len()) } { 57 | 0 => Ok(decode_utf16(&alias)), 58 | _err => Err(io::Error::last_os_error()), 59 | } 60 | } 61 | 62 | pub(crate) unsafe fn row_to_route(row: *const MIB_IPFORWARD_ROW2) -> Option { 63 | let dst_family = (*row).DestinationPrefix.Prefix.si_family; 64 | let dst = match dst_family { 65 | AF_INET => IpAddr::from(mem::transmute::( 66 | (*row).DestinationPrefix.Prefix.Ipv4.sin_addr, 67 | )), 68 | AF_INET6 => IpAddr::from(mem::transmute::( 69 | (*row).DestinationPrefix.Prefix.Ipv6.sin6_addr, 70 | )), 71 | _ => panic!("Unexpected family {}", dst_family), 72 | }; 73 | 74 | let dst_len = (*row).DestinationPrefix.PrefixLength; 75 | 76 | let nexthop_family = (*row).NextHop.si_family; 77 | 78 | let gateway = match nexthop_family { 79 | AF_INET => Some(IpAddr::from(std::mem::transmute::( 80 | (*row).NextHop.Ipv4.sin_addr, 81 | ))), 82 | AF_INET6 => Some(IpAddr::from(std::mem::transmute::( 83 | (*row).NextHop.Ipv6.sin6_addr, 84 | ))), 85 | _ => None, 86 | }; 87 | 88 | let mut route = Route::new(dst, dst_len) 89 | .with_if_index((*row).InterfaceIndex) 90 | .with_luid(std::mem::transmute::( 91 | (*row).InterfaceLuid, 92 | )) 93 | .with_metric((*row).Metric); 94 | route.if_name = if_index_to_name((*row).InterfaceIndex).ok(); 95 | route.gateway = gateway; 96 | Some(route) 97 | } 98 | 99 | impl TryFrom<&Route> for MIB_IPFORWARD_ROW2 { 100 | type Error = io::Error; 101 | fn try_from(route: &Route) -> Result { 102 | route.check()?; 103 | let mut row: MIB_IPFORWARD_ROW2 = unsafe { std::mem::zeroed() }; 104 | unsafe { InitializeIpForwardEntry(&mut row) }; 105 | 106 | if let Some(ifindex) = route.get_index() { 107 | row.InterfaceIndex = ifindex; 108 | } 109 | 110 | if let Some(luid) = route.luid { 111 | row.InterfaceLuid = unsafe { std::mem::transmute::(luid) }; 112 | } 113 | 114 | if let Some(gateway) = route.gateway { 115 | match gateway { 116 | IpAddr::V4(addr) => unsafe { 117 | row.NextHop.si_family = AF_INET; 118 | row.NextHop.Ipv4.sin_addr = mem::transmute::<[u8; 4], IN_ADDR>(addr.octets()); 119 | }, 120 | IpAddr::V6(addr) => unsafe { 121 | row.NextHop.si_family = AF_INET6; 122 | row.NextHop.Ipv6.sin6_addr = 123 | mem::transmute::<[u8; 16], IN6_ADDR>(addr.octets()); 124 | }, 125 | } 126 | } else { 127 | // if we're not setting the gateway we need to explicitly set the family. 128 | row.NextHop.si_family = match route.destination { 129 | IpAddr::V4(_) => AF_INET, 130 | IpAddr::V6(_) => AF_INET6, 131 | }; 132 | } 133 | 134 | row.DestinationPrefix.PrefixLength = route.prefix; 135 | match route.destination { 136 | IpAddr::V4(addr) => unsafe { 137 | row.DestinationPrefix.Prefix.si_family = AF_INET; 138 | row.DestinationPrefix.Prefix.Ipv4.sin_addr = 139 | mem::transmute::<[u8; 4], IN_ADDR>(addr.octets()); 140 | }, 141 | IpAddr::V6(addr) => unsafe { 142 | row.DestinationPrefix.Prefix.si_family = AF_INET6; 143 | row.DestinationPrefix.Prefix.Ipv6.sin6_addr = 144 | mem::transmute::<[u8; 16], IN6_ADDR>(addr.octets()); 145 | }, 146 | } 147 | 148 | if let Some(metric) = route.metric { 149 | row.Metric = metric; 150 | } 151 | 152 | Ok(row) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/windows/mod.rs: -------------------------------------------------------------------------------- 1 | // See https://github.com/johnyburd/net-route/blob/main/src/platform_impl/windows.rs 2 | 3 | use crate::common::Route; 4 | use crate::RouteChange; 5 | use flume::{Receiver, Sender}; 6 | use std::io; 7 | use std::net::IpAddr; 8 | use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; 9 | use std::os::windows::raw::HANDLE; 10 | use std::sync::{Arc, Mutex}; 11 | use windows_sys::Win32::Foundation::{BOOLEAN, ERROR_SUCCESS}; 12 | use windows_sys::Win32::NetworkManagement::IpHelper::{ 13 | CancelMibChangeNotify2, CreateIpForwardEntry2, DeleteIpForwardEntry2, FreeMibTable, 14 | GetBestRoute2, GetIpForwardTable2, MibAddInstance, MibDeleteInstance, MibParameterNotification, 15 | NotifyRouteChange2, MIB_IPFORWARD_ROW2, MIB_IPFORWARD_TABLE2, MIB_NOTIFICATION_TYPE, 16 | }; 17 | use windows_sys::Win32::Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR_INET}; 18 | #[cfg(any(feature = "async", feature = "async_io"))] 19 | pub(crate) mod async_route; 20 | pub(crate) mod ffi; 21 | #[cfg(any(feature = "async", feature = "async_io"))] 22 | pub use async_route::*; 23 | pub(crate) use ffi::*; 24 | 25 | /// RouteListener for receiving route change events. 26 | pub struct RouteListener { 27 | handle: Arc>>, 28 | receiver: Receiver, 29 | } 30 | impl RouteListener { 31 | /// Creates a new RouteListener. 32 | pub fn new() -> io::Result { 33 | let mut handle: HANDLE = std::ptr::null_mut(); 34 | let (sender, receiver) = flume::bounded::(128); 35 | let mut sender = Box::new(sender); 36 | let ret = unsafe { 37 | NotifyRouteChange2( 38 | AF_UNSPEC, 39 | Some(callback), 40 | (sender.as_mut() as *mut _) as *mut _, 41 | BOOLEAN::from(false), 42 | &mut handle, 43 | ) 44 | }; 45 | if ret != ERROR_SUCCESS { 46 | return Err(io::Error::from_raw_os_error(ret as i32)); 47 | } 48 | unsafe { 49 | Ok(RouteListener { 50 | handle: Arc::new(Mutex::new(Some(( 51 | OwnedHandle::from_raw_handle(handle), 52 | sender, 53 | )))), 54 | receiver, 55 | }) 56 | } 57 | } 58 | /// Listens for a route change event and returns a RouteChange. 59 | pub fn listen(&mut self) -> io::Result { 60 | self.receiver 61 | .recv() 62 | .map_err(|_| io::Error::new(io::ErrorKind::Other, "shutdown")) 63 | } 64 | 65 | /// Retrieves a shutdown handle for the RouteListener. 66 | #[cfg(feature = "shutdown")] 67 | pub fn shutdown_handle(&self) -> io::Result { 68 | Ok(RouteListenerShutdown { 69 | handle: self.handle.clone(), 70 | }) 71 | } 72 | } 73 | fn shutdown(handle: &Mutex>) { 74 | if let Some((handle, sender)) = handle.lock().unwrap().take() { 75 | unsafe { 76 | CancelMibChangeNotify2(handle.as_raw_handle()); 77 | } 78 | drop(sender) 79 | } 80 | } 81 | 82 | /// Shutdown handle for the RouteListener, used to stop listening. 83 | #[derive(Clone)] 84 | #[cfg(feature = "shutdown")] 85 | pub struct RouteListenerShutdown { 86 | handle: Arc>>, 87 | } 88 | type RouteHandle = (OwnedHandle, Box>); 89 | #[cfg(feature = "shutdown")] 90 | impl RouteListenerShutdown { 91 | /// Shuts down the RouteListener. 92 | pub fn shutdown(&self) -> io::Result<()> { 93 | shutdown(&self.handle); 94 | Ok(()) 95 | } 96 | } 97 | impl Drop for RouteListener { 98 | fn drop(&mut self) { 99 | shutdown(&self.handle); 100 | } 101 | } 102 | /// RouteManager is used for managing routes (adding, deleting, and listing). 103 | pub struct RouteManager { 104 | _private: std::marker::PhantomData<()>, 105 | } 106 | impl RouteManager { 107 | /// Creates a new RouteManager. 108 | pub fn new() -> io::Result { 109 | Ok(Self { 110 | _private: std::marker::PhantomData, 111 | }) 112 | } 113 | /// Returns a new instance of RouteListener. 114 | pub fn listener() -> io::Result { 115 | RouteListener::new() 116 | } 117 | /// Lists all current routes. 118 | pub fn list(&mut self) -> io::Result> { 119 | let mut ptable: *mut MIB_IPFORWARD_TABLE2 = std::ptr::null_mut(); 120 | 121 | let ret = unsafe { GetIpForwardTable2(AF_UNSPEC, &mut ptable as *mut _ as *mut _) }; 122 | if ret != ERROR_SUCCESS { 123 | return Err(io::Error::from_raw_os_error(ret as i32)); 124 | } 125 | 126 | let prows = unsafe { 127 | std::ptr::slice_from_raw_parts( 128 | &(*ptable).Table as *const _ as *const MIB_IPFORWARD_ROW2, 129 | (*ptable).NumEntries as usize, 130 | ) 131 | }; 132 | 133 | let entries = unsafe { (*ptable).NumEntries }; 134 | let res = (0..entries) 135 | .map(|idx| unsafe { (*prows)[idx as usize] }) 136 | .filter_map(|row| unsafe { row_to_route(&row) }) 137 | .collect::>(); 138 | unsafe { FreeMibTable(ptable as *mut _ as *mut _) }; 139 | Ok(res) 140 | } 141 | /// Route Lookup by Destination Address 142 | pub fn find_route(&mut self, dest_ip: &IpAddr) -> io::Result> { 143 | unsafe { 144 | let mut row: MIB_IPFORWARD_ROW2 = std::mem::zeroed(); 145 | let mut dest: SOCKADDR_INET = std::mem::zeroed(); 146 | let mut best_source_address: SOCKADDR_INET = std::mem::zeroed(); 147 | 148 | match dest_ip { 149 | IpAddr::V4(ipv4) => { 150 | dest.si_family = AF_INET; 151 | dest.Ipv4.sin_family = AF_INET; 152 | dest.Ipv4.sin_addr.S_un.S_addr = u32::from(*ipv4).to_be(); 153 | } 154 | IpAddr::V6(ipv6) => { 155 | dest.si_family = AF_INET6; 156 | dest.Ipv6.sin6_family = AF_INET6; 157 | dest.Ipv6.sin6_addr.u.Byte = ipv6.octets(); 158 | } 159 | } 160 | 161 | let err = GetBestRoute2( 162 | std::ptr::null_mut(), 163 | 0, 164 | std::ptr::null(), 165 | &dest, 166 | 0, 167 | &mut row, 168 | &mut best_source_address, 169 | ); 170 | if err != ERROR_SUCCESS { 171 | return Err(io::Error::from_raw_os_error(err as i32)); 172 | } 173 | Ok(row_to_route(&row)) 174 | } 175 | } 176 | /// Adds a new route. 177 | pub fn add(&mut self, route: &Route) -> io::Result<()> { 178 | let row: MIB_IPFORWARD_ROW2 = route.try_into()?; 179 | 180 | let err = unsafe { CreateIpForwardEntry2(&row) }; 181 | if err != ERROR_SUCCESS { 182 | return Err(io::Error::from_raw_os_error(err as i32)); 183 | } 184 | Ok(()) 185 | } 186 | /// Deletes an existing route. 187 | pub fn delete(&mut self, route: &Route) -> io::Result<()> { 188 | let row: MIB_IPFORWARD_ROW2 = route.try_into()?; 189 | let err = unsafe { DeleteIpForwardEntry2(&row) }; 190 | if err != ERROR_SUCCESS { 191 | return Err(io::Error::from_raw_os_error(err as i32)); 192 | } 193 | Ok(()) 194 | } 195 | } 196 | 197 | unsafe extern "system" fn callback( 198 | callercontext: *const core::ffi::c_void, 199 | row: *const MIB_IPFORWARD_ROW2, 200 | notificationtype: MIB_NOTIFICATION_TYPE, 201 | ) { 202 | let tx = &*(callercontext as *const Sender); 203 | 204 | if let Some(route) = ffi::row_to_route(row) { 205 | let event = match notificationtype { 206 | n if n == MibParameterNotification => RouteChange::Change(route), 207 | n if n == MibAddInstance => RouteChange::Add(route), 208 | n if n == MibDeleteInstance => RouteChange::Delete(route), 209 | _ => return, 210 | }; 211 | _ = tx.send(event) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include --------------------------------------------------------------------------------