├── .github └── workflows │ ├── clippy-rustfmt.yml │ ├── license.yml │ └── main.yml ├── .gitignore ├── .licenserc.yaml ├── .rustfmt.toml ├── CHANGELOG ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── examples ├── audit_events.rs ├── audit_events_async_std.rs ├── audit_events_tokio.rs └── audit_events_tokio_manual_thread_builder.rs └── src ├── addr.rs ├── async_socket.rs ├── async_socket_ext.rs ├── constants.rs ├── lib.rs ├── mio.rs ├── smol.rs ├── socket.rs └── tokio.rs /.github/workflows/clippy-rustfmt.yml: -------------------------------------------------------------------------------- 1 | name: Rustfmt and clippy check 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | rustfmt_clippy: 12 | strategy: 13 | fail-fast: true 14 | 15 | name: Rustfmt and clippy check 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Install Rust Nightly 22 | run: | 23 | rustup override set nightly 24 | rustup update nightly 25 | rustup component add rustfmt clippy 26 | 27 | - name: rustfmt 28 | run: cargo fmt --all -- --check 29 | 30 | - name: clippy 31 | run: cargo clippy 32 | 33 | - name: clippy-tokio-socket 34 | run: cargo clippy --no-default-features --features tokio_socket 35 | 36 | - name: clippy-mio-socket 37 | run: cargo clippy --no-default-features --features mio_socket 38 | 39 | - name: clippy-smol-socket 40 | run: cargo clippy --no-default-features --features smol_socket 41 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | name: license 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | check-license: 12 | name: Check License 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 3 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Check License Header 19 | uses: apache/skywalking-eyes@v0.3.0 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | ci: 12 | name: CI (stable) 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Install Rust Stable 19 | run: | 20 | rustup override set stable 21 | rustup update stable 22 | 23 | - name: Test with default feature 24 | run: cargo test 25 | 26 | - name: Test with mio feature 27 | run: cargo test --features mio_socket 28 | 29 | - name: Test with tokio feature 30 | run: cargo test --features tokio_socket 31 | 32 | - name: Test with smol_socket feature 33 | run: cargo test --features smol_socket 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | vendor/ 4 | 5 | *.swp 6 | -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | header: 2 | license: 3 | content: | 4 | SPDX-License-Identifier: MIT 5 | paths-ignore: 6 | - 'target' 7 | - '**/*.toml' 8 | - '**/*.lock' 9 | - '**/*.yml' 10 | - '**/*.md' 11 | - 'CHANGELOG' 12 | - 'LICENSE-MIT' 13 | - '.gitignore' 14 | - '.packit.yaml' 15 | - 'fedora/*.spec' 16 | 17 | comment: on-failure 18 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | wrap_comments = true 3 | reorder_imports = true 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## [0.8.7] - 2024-12-10 3 | ### Breaking Changes 4 | * N/A 5 | 6 | ### New Features 7 | * N/A 8 | 9 | ### Bug Fixes 10 | * N/A 11 | 12 | ### Other changes 13 | * upgrade dependencies 14 | 15 | ## [0.8.6] - 2024-04-03 16 | ### Breaking Changes 17 | * N/A 18 | 19 | ### New Features 20 | * Support set `NETLINK_GET_STRICT_CHK`. (be6a7aa) 21 | * Support set and get socket receive buffer. (c4d7328) 22 | * Support set and get extended ACK. (008c65b) 23 | 24 | ### Bug Fixes 25 | * N/A 26 | 27 | ## [0.8.5] - 2023-03-05 28 | ### Breaking Changes 29 | * N/A 30 | 31 | ### New Features 32 | * N/A 33 | 34 | ### Bug Fixes 35 | * Just a try for packit auto downstream release. (a9640d2) 36 | 37 | ## [0.8.4] - 2023-01-23 38 | ### Breaking Changes 39 | * N/A 40 | 41 | ### New Features 42 | * N/A 43 | 44 | ### Bug Fixes 45 | * Move to standlone github repository. (8c1ee21) 46 | 47 | ## [0.8.3] - 2022-06-23 48 | ### Breaking Changes 49 | * N/A 50 | 51 | ### New Features 52 | * N/A 53 | 54 | ### Bug Fixes 55 | * N/A 56 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Corentin Henry "] 3 | name = "netlink-sys" 4 | version = "0.8.7" 5 | edition = "2018" 6 | homepage = "https://github.com/rust-netlink/netlink-sys" 7 | keywords = ["netlink", "ip", "linux"] 8 | license = "MIT" 9 | readme = "README.md" 10 | repository = "https://github.com/rust-netlink/netlink-sys" 11 | description = "netlink sockets, with optional integration with tokio" 12 | 13 | [dependencies] 14 | bytes = "1.8" 15 | libc = "0.2.164" 16 | log = "0.4.8" 17 | 18 | [dependencies.futures] 19 | optional = true 20 | version = "0.3.31" 21 | 22 | [dependencies.tokio] 23 | optional = true 24 | version = "1.41.1" 25 | default-features = false 26 | # We only depend on tokio for PollEvented 27 | features = ["net"] 28 | 29 | [dependencies.mio] 30 | optional = true 31 | version = "1.0" 32 | features = ["os-poll", "os-ext"] 33 | 34 | [dependencies.async-io] 35 | optional = true 36 | version = "2" 37 | 38 | [features] 39 | default = [] 40 | mio_socket = ["mio"] 41 | tokio_socket = ["tokio", "futures"] 42 | smol_socket = ["async-io", "futures"] 43 | 44 | [dev-dependencies] 45 | netlink-packet-audit = "0.4.1" 46 | 47 | [dev-dependencies.tokio] 48 | version = "1.0.1" 49 | default-features = false 50 | # We only depend on tokio for PollEvented 51 | features = ["net", "macros", "rt-multi-thread"] 52 | 53 | [dev-dependencies.async-std] 54 | version = "1.13" 55 | features = ["attributes"] 56 | 57 | 58 | [[example]] 59 | name = "audit_events" 60 | 61 | [[example]] 62 | name = "audit_events_tokio" 63 | required-features = ["tokio_socket"] 64 | 65 | [[example]] 66 | name = "audit_events_tokio_manual_thread_builder" 67 | required-features = ["tokio_socket"] 68 | 69 | [[example]] 70 | name = "audit_events_async_std" 71 | required-features = ["smol_socket"] 72 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of 2 | this software and associated documentation files (the "Software"), to deal in 3 | the Software without restriction, including without limitation the rights to 4 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 5 | of the Software, and to permit persons to whom the Software is furnished to do 6 | so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | Distributions of all or part of the Software intended to be used by the 12 | recipients as they would use the unmodified Software, containing modifications 13 | that substantially alter, remove, or disable functionality of the Software, 14 | outside of the documented configuration mechanisms provided by the Software, 15 | shall be modified such that the Original Author's bug reporting email addresses 16 | and urls are either replaced with the contact information of the parties 17 | responsible for the changes, or removed entirely. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust netlink-sys crate 2 | 3 | The netlink_sys crate provides netlink sockets. 4 | Integration with mio and tokio is optional. 5 | 6 | [Rust crate Document](https://docs.rs/netlink-sys/) 7 | -------------------------------------------------------------------------------- /examples/audit_events.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // Build: 4 | // 5 | // ``` 6 | // cd netlink-sys 7 | // cargo build --example audit_events 8 | // ``` 9 | // 10 | // Run *as root*: 11 | // 12 | // ``` 13 | // ../target/debug/examples/audit_events 14 | // ``` 15 | 16 | use std::process; 17 | 18 | use netlink_packet_audit::{ 19 | AuditMessage, NetlinkBuffer, NetlinkMessage, StatusMessage, NLM_F_ACK, 20 | NLM_F_REQUEST, 21 | }; 22 | 23 | use netlink_sys::{protocols::NETLINK_AUDIT, Socket, SocketAddr}; 24 | 25 | pub const AUDIT_STATUS_ENABLED: u32 = 1; 26 | pub const AUDIT_STATUS_PID: u32 = 4; 27 | 28 | fn main() { 29 | let kernel_unicast: SocketAddr = SocketAddr::new(0, 0); 30 | let socket = Socket::new(NETLINK_AUDIT).unwrap(); 31 | 32 | let mut status = StatusMessage::new(); 33 | status.enabled = 1; 34 | status.pid = process::id(); 35 | status.mask = AUDIT_STATUS_ENABLED | AUDIT_STATUS_PID; 36 | let payload = AuditMessage::SetStatus(status); 37 | let mut nl_msg = NetlinkMessage::from(payload); 38 | nl_msg.header.flags = NLM_F_REQUEST | NLM_F_ACK; 39 | nl_msg.finalize(); 40 | 41 | let mut buf = vec![0; 1024 * 8]; 42 | nl_msg.serialize(&mut buf[..nl_msg.buffer_len()]); 43 | 44 | socket 45 | .send_to(&buf[..nl_msg.buffer_len()], &kernel_unicast, 0) 46 | .unwrap(); 47 | let mut buf = vec![0; 1024 * 8]; 48 | loop { 49 | let (n, _addr) = socket.recv_from(&mut buf, 0).unwrap(); 50 | // This dance with the NetlinkBuffer should not be 51 | // necessary. It is here to work around a netlink bug. See: 52 | // https://github.com/mozilla/libaudit-go/issues/24 53 | // https://github.com/linux-audit/audit-userspace/issues/78 54 | { 55 | let mut nl_buf = NetlinkBuffer::new(&mut buf[0..n]); 56 | if n != nl_buf.length() as usize { 57 | nl_buf.set_length(n as u32); 58 | } 59 | } 60 | let parsed = 61 | NetlinkMessage::::deserialize(&buf[0..n]).unwrap(); 62 | println!("<<< {parsed:?}"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/audit_events_async_std.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // Build: 4 | // 5 | // ``` 6 | // cd netlink-sys 7 | // cargo build --example audit_events_async_std --features async_std_socket 8 | // ``` 9 | // 10 | // Run *as root*: 11 | // 12 | // ``` 13 | // ../target/debug/examples/audit_events_async_std 14 | // ``` 15 | 16 | use std::process; 17 | 18 | use netlink_packet_audit::{ 19 | AuditMessage, NetlinkBuffer, NetlinkMessage, StatusMessage, NLM_F_ACK, 20 | NLM_F_REQUEST, 21 | }; 22 | 23 | use netlink_sys::{ 24 | protocols::NETLINK_AUDIT, AsyncSocket, AsyncSocketExt, SmolSocket, 25 | SocketAddr, 26 | }; 27 | 28 | const AUDIT_STATUS_ENABLED: u32 = 1; 29 | const AUDIT_STATUS_PID: u32 = 4; 30 | 31 | #[async_std::main] 32 | async fn main() { 33 | let kernel_unicast: SocketAddr = SocketAddr::new(0, 0); 34 | let mut socket = SmolSocket::new(NETLINK_AUDIT).unwrap(); 35 | 36 | let mut status = StatusMessage::new(); 37 | status.enabled = 1; 38 | status.pid = process::id(); 39 | status.mask = AUDIT_STATUS_ENABLED | AUDIT_STATUS_PID; 40 | let payload = AuditMessage::SetStatus(status); 41 | let mut nl_msg = NetlinkMessage::from(payload); 42 | nl_msg.header.flags = NLM_F_REQUEST | NLM_F_ACK; 43 | nl_msg.finalize(); 44 | 45 | let mut buf = vec![0; 1024 * 8]; 46 | nl_msg.serialize(&mut buf[..nl_msg.buffer_len()]); 47 | 48 | println!(">>> {:?}", nl_msg); 49 | socket 50 | .send_to(&buf[..nl_msg.buffer_len()], &kernel_unicast) 51 | .await 52 | .unwrap(); 53 | 54 | let mut buf = bytes::BytesMut::with_capacity(1024 * 8); 55 | loop { 56 | buf.clear(); 57 | let _addr = socket.recv_from(&mut buf).await.unwrap(); 58 | // This dance with the NetlinkBuffer should not be 59 | // necessary. It is here to work around a netlink bug. See: 60 | // https://github.com/mozilla/libaudit-go/issues/24 61 | // https://github.com/linux-audit/audit-userspace/issues/78 62 | { 63 | let n = buf.len(); 64 | let mut nl_buf = NetlinkBuffer::new(&mut buf); 65 | if n != nl_buf.length() as usize { 66 | nl_buf.set_length(n as u32); 67 | } 68 | } 69 | let parsed = NetlinkMessage::::deserialize(&buf).unwrap(); 70 | println!("<<< {:?}", parsed); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /examples/audit_events_tokio.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // Build: 4 | // 5 | // ``` 6 | // cd netlink-sys 7 | // cargo build --example audit_events_async --features tokio_socket 8 | // ``` 9 | // 10 | // Run *as root*: 11 | // 12 | // ``` 13 | // ../target/debug/examples/audit_events_async 14 | // ``` 15 | 16 | use std::process; 17 | 18 | use netlink_packet_audit::{ 19 | AuditMessage, NetlinkBuffer, NetlinkMessage, StatusMessage, NLM_F_ACK, 20 | NLM_F_REQUEST, 21 | }; 22 | 23 | use netlink_sys::{ 24 | protocols::NETLINK_AUDIT, AsyncSocket, AsyncSocketExt, SocketAddr, 25 | TokioSocket, 26 | }; 27 | 28 | const AUDIT_STATUS_ENABLED: u32 = 1; 29 | const AUDIT_STATUS_PID: u32 = 4; 30 | 31 | #[tokio::main] 32 | async fn main() -> Result<(), Box> { 33 | let kernel_unicast: SocketAddr = SocketAddr::new(0, 0); 34 | let mut socket = TokioSocket::new(NETLINK_AUDIT).unwrap(); 35 | 36 | let mut status = StatusMessage::new(); 37 | status.enabled = 1; 38 | status.pid = process::id(); 39 | status.mask = AUDIT_STATUS_ENABLED | AUDIT_STATUS_PID; 40 | let payload = AuditMessage::SetStatus(status); 41 | let mut nl_msg = NetlinkMessage::from(payload); 42 | nl_msg.header.flags = NLM_F_REQUEST | NLM_F_ACK; 43 | nl_msg.finalize(); 44 | 45 | let mut buf = vec![0; 1024 * 8]; 46 | nl_msg.serialize(&mut buf[..nl_msg.buffer_len()]); 47 | 48 | println!(">>> {:?}", nl_msg); 49 | socket 50 | .send_to(&buf[..nl_msg.buffer_len()], &kernel_unicast) 51 | .await 52 | .unwrap(); 53 | 54 | let mut buf = bytes::BytesMut::with_capacity(1024 * 8); 55 | loop { 56 | buf.clear(); 57 | let _addr = socket.recv_from(&mut buf).await.unwrap(); 58 | // This dance with the NetlinkBuffer should not be 59 | // necessary. It is here to work around a netlink bug. See: 60 | // https://github.com/mozilla/libaudit-go/issues/24 61 | // https://github.com/linux-audit/audit-userspace/issues/78 62 | { 63 | let n = buf.len(); 64 | let mut nl_buf = NetlinkBuffer::new(&mut buf); 65 | if n != nl_buf.length() as usize { 66 | nl_buf.set_length(n as u32); 67 | } 68 | } 69 | let parsed = NetlinkMessage::::deserialize(&buf).unwrap(); 70 | println!("<<< {:?}", parsed); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /examples/audit_events_tokio_manual_thread_builder.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /* 4 | * This example shows the mimimal manual tokio initialization required to be 5 | * able to use netlink. 6 | */ 7 | 8 | use netlink_sys::{protocols::NETLINK_AUDIT, AsyncSocket, TokioSocket}; 9 | 10 | fn main() -> Result<(), String> { 11 | let rt = tokio::runtime::Builder::new_multi_thread() 12 | .enable_io() 13 | .build() 14 | .unwrap(); 15 | 16 | let future = async { 17 | TokioSocket::new(NETLINK_AUDIT).unwrap(); 18 | }; 19 | rt.handle().block_on(future); 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /src/addr.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::{ 4 | fmt, 5 | hash::{Hash, Hasher}, 6 | mem, 7 | }; 8 | 9 | /// The address of a netlink socket 10 | /// 11 | /// A netlink address is made of two parts: the unicast address of the socket, 12 | /// called _port number_ or _PID_, and the multicast address called _group ID_. 13 | /// In this library, we've chosen to stick to the "port number" terminology, 14 | /// since PID can be confused with process ID. However, the netlink man page 15 | /// mostly uses PID. 16 | /// 17 | /// ## Port number 18 | /// 19 | /// Sockets in kernel space have 0 as a port number. For sockets opened by a 20 | /// user-space process, the port number can either be assigned by the process 21 | /// itself, or by the kernel. The only constraint is that this port number must 22 | /// be unique: two netlink sockets created by a given process must have a 23 | /// different port number. However, netlinks sockets created by different 24 | /// processes can have the same port number. 25 | /// 26 | /// ### Port number assigned by the kernel 27 | /// 28 | /// One way to set the port number is to let the kernel assign it, by calling 29 | /// [`Socket::bind`][bind] with a port number set to 0. The kernel will usually 30 | /// use the process ID as port number for the first netlink socket created by 31 | /// the process, which is why the socket port number is also called PID. For 32 | /// example: 33 | /// 34 | /// ```rust 35 | /// use std::process; 36 | /// use netlink_sys::{ 37 | /// protocols::NETLINK_ROUTE, 38 | /// SocketAddr, Socket, 39 | /// }; 40 | /// 41 | /// let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); 42 | /// // The first parameter is the port number. By setting it to 0 we ask the kernel to pick a port for us 43 | /// let mut addr = SocketAddr::new(0, 0); 44 | /// socket.bind(&addr).unwrap(); 45 | /// // Retrieve the socket address 46 | /// socket.get_address(&mut addr).unwrap(); 47 | /// // the socket port number should be equal to the process ID, but there is no guarantee 48 | /// println!("socket port number = {}, process ID = {}", addr.port_number(), process::id()); 49 | /// 50 | /// let mut socket2 = Socket::new(NETLINK_ROUTE).unwrap(); 51 | /// let mut addr2 = SocketAddr::new(0, 0); 52 | /// socket2.bind(&addr2).unwrap(); 53 | /// socket2.get_address(&mut addr2).unwrap(); 54 | /// // the unicast address picked by the kernel for the second socket should be different 55 | /// assert!(addr.port_number() != addr2.port_number()); 56 | /// ``` 57 | /// 58 | /// Note that it's a little tedious to create a socket address, call `bind` and 59 | /// then retrive the address with [`Socket::get_address`][get_addr]. To avoid 60 | /// this boilerplate you can use [`Socket::bind_auto`][bind_auto]: 61 | /// 62 | /// ```rust 63 | /// use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; 64 | /// use std::process; 65 | /// 66 | /// let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); 67 | /// let addr = socket.bind_auto().unwrap(); 68 | /// println!("socket port number = {}", addr.port_number()); 69 | /// ``` 70 | /// 71 | /// ### Setting the port number manually 72 | /// 73 | /// The application can also pick the port number by calling Socket::bind with 74 | /// an address with a non-zero port number. However, it must ensure that this 75 | /// number is unique for each socket created. For instance: 76 | /// 77 | /// ```rust 78 | /// use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; 79 | /// use std::process; 80 | /// 81 | /// let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); 82 | /// // set the socket port number to 2 83 | /// let mut addr = SocketAddr::new(2, 0); 84 | /// socket.bind(&addr).unwrap(); 85 | /// // Retrieve the socket address 86 | /// socket.get_address(&mut addr).unwrap(); 87 | /// assert_eq!(2, addr.port_number()); 88 | /// 89 | /// // Creating a second socket with the same port number fails 90 | /// let mut socket2 = Socket::new(NETLINK_ROUTE).unwrap(); 91 | /// let mut addr2 = SocketAddr::new(2, 0); 92 | /// socket2.bind(&addr2).unwrap_err(); 93 | /// ``` 94 | /// 95 | /// [bind]: crate::Socket::bind 96 | /// [bind_auto]: crate::Socket::bind_auto 97 | /// [get_addr]: crate::Socket::get_address 98 | #[derive(Copy, Clone)] 99 | pub struct SocketAddr(pub(crate) libc::sockaddr_nl); 100 | 101 | impl Hash for SocketAddr { 102 | fn hash(&self, state: &mut H) { 103 | self.0.nl_family.hash(state); 104 | self.0.nl_pid.hash(state); 105 | self.0.nl_groups.hash(state); 106 | } 107 | } 108 | 109 | impl PartialEq for SocketAddr { 110 | fn eq(&self, other: &SocketAddr) -> bool { 111 | self.0.nl_family == other.0.nl_family 112 | && self.0.nl_pid == other.0.nl_pid 113 | && self.0.nl_groups == other.0.nl_groups 114 | } 115 | } 116 | 117 | impl fmt::Debug for SocketAddr { 118 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 119 | write!( 120 | f, 121 | "SocketAddr(nl_family={}, nl_pid={}, nl_groups={})", 122 | self.0.nl_family, self.0.nl_pid, self.0.nl_groups 123 | ) 124 | } 125 | } 126 | 127 | impl Eq for SocketAddr {} 128 | 129 | impl fmt::Display for SocketAddr { 130 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 131 | write!( 132 | f, 133 | "address family: {}, pid: {}, multicast groups: {})", 134 | self.0.nl_family, self.0.nl_pid, self.0.nl_groups 135 | ) 136 | } 137 | } 138 | 139 | impl SocketAddr { 140 | /// Create a new socket address for with th 141 | pub fn new(port_number: u32, multicast_groups: u32) -> Self { 142 | let mut addr: libc::sockaddr_nl = unsafe { mem::zeroed() }; 143 | addr.nl_family = libc::PF_NETLINK as libc::sa_family_t; 144 | addr.nl_pid = port_number; 145 | addr.nl_groups = multicast_groups; 146 | SocketAddr(addr) 147 | } 148 | 149 | /// Get the unicast address of this socket 150 | pub fn port_number(&self) -> u32 { 151 | self.0.nl_pid 152 | } 153 | 154 | /// Get the multicast groups of this socket 155 | pub fn multicast_groups(&self) -> u32 { 156 | self.0.nl_groups 157 | } 158 | 159 | pub(crate) fn as_raw(&self) -> (*const libc::sockaddr, libc::socklen_t) { 160 | let addr_ptr = 161 | &self.0 as *const libc::sockaddr_nl as *const libc::sockaddr; 162 | // \ / \ 163 | // / +---------------+---------------+ 164 | // +----------+---------+ | 165 | // | v 166 | // | create a raw pointer to the sockaddr_nl 167 | // | 168 | // v cast 169 | // *sockaddr_nl -> *sockaddr 170 | // 171 | // This kind of things seems to be pretty usual when using C APIs from 172 | // Rust. It could be written in a shorter way thank to type 173 | // inference: 174 | // 175 | // let addr_ptr: *const libc:sockaddr = &self.0 as *const _ as 176 | // *const _; 177 | // 178 | // But since this is my first time dealing with this kind of things I 179 | // chose the most explicit form. 180 | 181 | let addr_len = mem::size_of::() as libc::socklen_t; 182 | (addr_ptr, addr_len) 183 | } 184 | 185 | pub(crate) fn as_raw_mut( 186 | &mut self, 187 | ) -> (*mut libc::sockaddr, libc::socklen_t) { 188 | let addr_ptr = 189 | &mut self.0 as *mut libc::sockaddr_nl as *mut libc::sockaddr; 190 | let addr_len = mem::size_of::() as libc::socklen_t; 191 | (addr_ptr, addr_len) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/async_socket.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::{ 4 | io, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use crate::{Socket, SocketAddr}; 9 | 10 | /// Trait to support different async backends 11 | pub trait AsyncSocket: Sized + Unpin { 12 | /// Access underyling [`Socket`] 13 | fn socket_ref(&self) -> &Socket; 14 | 15 | /// Mutable access to underyling [`Socket`] 16 | fn socket_mut(&mut self) -> &mut Socket; 17 | 18 | /// Wrapper for [`Socket::new`] 19 | fn new(protocol: isize) -> io::Result; 20 | 21 | /// Polling wrapper for [`Socket::send`] 22 | fn poll_send( 23 | &mut self, 24 | cx: &mut Context<'_>, 25 | buf: &[u8], 26 | ) -> Poll>; 27 | 28 | /// Polling wrapper for [`Socket::send_to`] 29 | fn poll_send_to( 30 | &mut self, 31 | cx: &mut Context<'_>, 32 | buf: &[u8], 33 | addr: &SocketAddr, 34 | ) -> Poll>; 35 | 36 | /// Polling wrapper for [`Socket::recv`] 37 | /// 38 | /// Passes 0 for flags, and ignores the returned length (the buffer will 39 | /// have advanced by the amount read). 40 | fn poll_recv( 41 | &mut self, 42 | cx: &mut Context<'_>, 43 | buf: &mut B, 44 | ) -> Poll> 45 | where 46 | B: bytes::BufMut; 47 | 48 | /// Polling wrapper for [`Socket::recv_from`] 49 | /// 50 | /// Passes 0 for flags, and ignores the returned length - just returns the 51 | /// address (the buffer will have advanced by the amount read). 52 | fn poll_recv_from( 53 | &mut self, 54 | cx: &mut Context<'_>, 55 | buf: &mut B, 56 | ) -> Poll> 57 | where 58 | B: bytes::BufMut; 59 | 60 | /// Polling wrapper for [`Socket::recv_from_full`] 61 | /// 62 | /// Passes 0 for flags, and ignores the returned length - just returns the 63 | /// address (the buffer will have advanced by the amount read). 64 | fn poll_recv_from_full( 65 | &mut self, 66 | cx: &mut Context<'_>, 67 | ) -> Poll, SocketAddr)>>; 68 | } 69 | -------------------------------------------------------------------------------- /src/async_socket_ext.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::{ 4 | future::Future, 5 | io, 6 | pin::Pin, 7 | task::{Context, Poll}, 8 | }; 9 | 10 | use crate::{AsyncSocket, SocketAddr}; 11 | 12 | /// Support trait for [`AsyncSocket`] 13 | /// 14 | /// Provides awaitable variants of the poll functions from [`AsyncSocket`]. 15 | pub trait AsyncSocketExt: AsyncSocket { 16 | /// `async fn send(&mut self, buf: &[u8]) -> io::Result` 17 | fn send<'a, 'b>(&'a mut self, buf: &'b [u8]) -> PollSend<'a, 'b, Self> { 18 | PollSend { socket: self, buf } 19 | } 20 | 21 | /// `async fn send(&mut self, buf: &[u8]) -> io::Result` 22 | fn send_to<'a, 'b>( 23 | &'a mut self, 24 | buf: &'b [u8], 25 | addr: &'b SocketAddr, 26 | ) -> PollSendTo<'a, 'b, Self> { 27 | PollSendTo { 28 | socket: self, 29 | buf, 30 | addr, 31 | } 32 | } 33 | 34 | /// `async fn recv(&mut self, buf: &mut [u8]) -> io::Result<()>` 35 | fn recv<'a, 'b, B>( 36 | &'a mut self, 37 | buf: &'b mut B, 38 | ) -> PollRecv<'a, 'b, Self, B> 39 | where 40 | B: bytes::BufMut, 41 | { 42 | PollRecv { socket: self, buf } 43 | } 44 | 45 | /// `async fn recv(&mut self, buf: &mut [u8]) -> io::Result` 46 | fn recv_from<'a, 'b, B>( 47 | &'a mut self, 48 | buf: &'b mut B, 49 | ) -> PollRecvFrom<'a, 'b, Self, B> 50 | where 51 | B: bytes::BufMut, 52 | { 53 | PollRecvFrom { socket: self, buf } 54 | } 55 | 56 | /// `async fn recrecv_from_full(&mut self) -> io::Result<(Vec, 57 | /// SocketAddr)>` 58 | fn recv_from_full(&mut self) -> PollRecvFromFull<'_, Self> { 59 | PollRecvFromFull { socket: self } 60 | } 61 | } 62 | 63 | impl AsyncSocketExt for S {} 64 | 65 | pub struct PollSend<'a, 'b, S> { 66 | socket: &'a mut S, 67 | buf: &'b [u8], 68 | } 69 | 70 | impl Future for PollSend<'_, '_, S> 71 | where 72 | S: AsyncSocket, 73 | { 74 | type Output = io::Result; 75 | 76 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 77 | let this: &mut Self = Pin::into_inner(self); 78 | this.socket.poll_send(cx, this.buf) 79 | } 80 | } 81 | 82 | pub struct PollSendTo<'a, 'b, S> { 83 | socket: &'a mut S, 84 | buf: &'b [u8], 85 | addr: &'b SocketAddr, 86 | } 87 | 88 | impl Future for PollSendTo<'_, '_, S> 89 | where 90 | S: AsyncSocket, 91 | { 92 | type Output = io::Result; 93 | 94 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 95 | let this: &mut Self = Pin::into_inner(self); 96 | this.socket.poll_send_to(cx, this.buf, this.addr) 97 | } 98 | } 99 | 100 | pub struct PollRecv<'a, 'b, S, B> { 101 | socket: &'a mut S, 102 | buf: &'b mut B, 103 | } 104 | 105 | impl Future for PollRecv<'_, '_, S, B> 106 | where 107 | S: AsyncSocket, 108 | B: bytes::BufMut, 109 | { 110 | type Output = io::Result<()>; 111 | 112 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 113 | let this: &mut Self = Pin::into_inner(self); 114 | this.socket.poll_recv(cx, this.buf) 115 | } 116 | } 117 | 118 | pub struct PollRecvFrom<'a, 'b, S, B> { 119 | socket: &'a mut S, 120 | buf: &'b mut B, 121 | } 122 | 123 | impl Future for PollRecvFrom<'_, '_, S, B> 124 | where 125 | S: AsyncSocket, 126 | B: bytes::BufMut, 127 | { 128 | type Output = io::Result; 129 | 130 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 131 | let this: &mut Self = Pin::into_inner(self); 132 | this.socket.poll_recv_from(cx, this.buf) 133 | } 134 | } 135 | 136 | pub struct PollRecvFromFull<'a, S> { 137 | socket: &'a mut S, 138 | } 139 | 140 | impl Future for PollRecvFromFull<'_, S> 141 | where 142 | S: AsyncSocket, 143 | { 144 | type Output = io::Result<(Vec, SocketAddr)>; 145 | 146 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 147 | let this: &mut Self = Pin::into_inner(self); 148 | this.socket.poll_recv_from_full(cx) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | //! This module provides a lot of netlink constants for various protocol. As we 4 | //! add support for the various protocols, these constants will be moved to 5 | //! their own crate. 6 | 7 | use libc::c_int as int; 8 | 9 | /// Receives routing and link updates and may be used to modify the routing 10 | /// tables (both IPv4 and IPv6), IP addresses, link parameters, neighbor setups, 11 | /// queueing disciplines, traffic classes and packet classifiers (see 12 | /// rtnetlink(7)). 13 | pub const NETLINK_ROUTE: isize = 0; 14 | pub const NETLINK_UNUSED: isize = 1; 15 | /// Reserved for user-mode socket protocols. 16 | pub const NETLINK_USERSOCK: isize = 2; 17 | /// Transport IPv4 packets from netfilter to user space. Used by 18 | /// ip_queue kernel module. After a long period of being declared obsolete (in 19 | /// favor of the more advanced nfnetlink_queue feature), it was removed in 20 | /// Linux 3.5. 21 | pub const NETLINK_FIREWALL: isize = 3; 22 | /// Query information about sockets of various protocol families from the kernel 23 | /// (see sock_diag(7)). 24 | pub const NETLINK_SOCK_DIAG: isize = 4; 25 | /// Netfilter/iptables ULOG. 26 | pub const NETLINK_NFLOG: isize = 5; 27 | /// IPsec. 28 | pub const NETLINK_XFRM: isize = 6; 29 | /// SELinux event notifications. 30 | pub const NETLINK_SELINUX: isize = 7; 31 | /// Open-iSCSI. 32 | pub const NETLINK_ISCSI: isize = 8; 33 | /// Auditing. 34 | pub const NETLINK_AUDIT: isize = 9; 35 | /// Access to FIB lookup from user space. 36 | pub const NETLINK_FIB_LOOKUP: isize = 10; 37 | /// Kernel connector. See `Documentation/connector/*` in the Linux kernel source 38 | /// tree for further information. 39 | pub const NETLINK_CONNECTOR: isize = 11; 40 | /// Netfilter subsystem. 41 | pub const NETLINK_NETFILTER: isize = 12; 42 | /// Transport IPv6 packets from netfilter to user space. Used by ip6_queue 43 | /// kernel module. 44 | pub const NETLINK_IP6_FW: isize = 13; 45 | /// DECnet routing messages. 46 | pub const NETLINK_DNRTMSG: isize = 14; 47 | /// Kernel messages to user space. 48 | pub const NETLINK_KOBJECT_UEVENT: isize = 15; 49 | /// Generic netlink family for simplified netlink usage. 50 | pub const NETLINK_GENERIC: isize = 16; 51 | /// SCSI transpots 52 | pub const NETLINK_SCSITRANSPORT: isize = 18; 53 | pub const NETLINK_ECRYPTFS: isize = 19; 54 | /// Infiniband RDMA. 55 | pub const NETLINK_RDMA: isize = 20; 56 | /// Netlink interface to request information about ciphers registered with the 57 | /// kernel crypto API as well as allow configuration of the kernel crypto API. 58 | pub const NETLINK_CRYPTO: isize = 21; 59 | 60 | pub const TCA_ROOT_UNSPEC: int = 0; 61 | pub const TCA_ROOT_TAB: int = 1; 62 | pub const TCA_ROOT_FLAGS: int = 2; 63 | pub const TCA_ROOT_COUNT: int = 3; 64 | pub const TCA_ROOT_TIME_DELTA: int = 4; 65 | 66 | pub const EM_NONE: u32 = 0; 67 | pub const EM_M32: u32 = 1; 68 | pub const EM_SPARC: u32 = 2; 69 | pub const EM_386: u32 = 3; 70 | pub const EM_68K: u32 = 4; 71 | pub const EM_88K: u32 = 5; 72 | pub const EM_486: u32 = 6; 73 | pub const EM_860: u32 = 7; 74 | pub const EM_MIPS: u32 = 8; 75 | pub const EM_MIPS_RS3_LE: u32 = 10; 76 | pub const EM_MIPS_RS4_BE: u32 = 10; 77 | pub const EM_PARISC: u32 = 15; 78 | pub const EM_SPARC32PLUS: u32 = 18; 79 | pub const EM_PPC: u32 = 20; 80 | pub const EM_PPC64: u32 = 21; 81 | pub const EM_SPU: u32 = 23; 82 | pub const EM_ARM: u32 = 40; 83 | pub const EM_SH: u32 = 42; 84 | pub const EM_SPARCV9: u32 = 43; 85 | pub const EM_H8_300: u32 = 46; 86 | pub const EM_IA_64: u32 = 50; 87 | pub const EM_X86_64: u32 = 62; 88 | pub const EM_S390: u32 = 22; 89 | pub const EM_CRIS: u32 = 76; 90 | pub const EM_M32R: u32 = 88; 91 | pub const EM_MN10300: u32 = 89; 92 | pub const EM_OPENRISC: u32 = 92; 93 | pub const EM_BLACKFIN: u32 = 106; 94 | pub const EM_ALTERA_NIOS2: u32 = 113; 95 | pub const EM_TI_C6000: u32 = 140; 96 | pub const EM_AARCH64: u32 = 183; 97 | pub const EM_TILEPRO: u32 = 188; 98 | pub const EM_MICROBLAZE: u32 = 189; 99 | pub const EM_TILEGX: u32 = 191; 100 | pub const EM_BPF: u32 = 247; 101 | pub const EM_FRV: u32 = 21569; 102 | pub const EM_ALPHA: u32 = 36902; 103 | pub const EM_CYGNUS_M32R: u32 = 36929; 104 | pub const EM_S390_OLD: u32 = 41872; 105 | pub const EM_CYGNUS_MN10300: u32 = 48879; 106 | 107 | pub const NLMSGERR_ATTR_UNUSED: int = 0; 108 | pub const NLMSGERR_ATTR_MSG: int = 1; 109 | pub const NLMSGERR_ATTR_OFFS: int = 2; 110 | pub const NLMSGERR_ATTR_COOKIE: int = 3; 111 | pub const NLMSGERR_ATTR_MAX: int = 3; 112 | 113 | pub const NL_MMAP_STATUS_UNUSED: int = 0; 114 | pub const NL_MMAP_STATUS_RESERVED: int = 1; 115 | pub const NL_MMAP_STATUS_VALID: int = 2; 116 | pub const NL_MMAP_STATUS_COPY: int = 3; 117 | pub const NL_MMAP_STATUS_SKIP: int = 4; 118 | 119 | pub const NETLINK_UNCONNECTED: int = 0; 120 | pub const NETLINK_CONNECTED: int = 1; 121 | 122 | pub const __BITS_PER_LONG: int = 64; 123 | pub const __FD_SETSIZE: int = 1024; 124 | pub const SI_LOAD_SHIFT: int = 16; 125 | pub const _K_SS_MAXSIZE: int = 128; 126 | pub const NETLINK_SMC: int = 22; 127 | pub const NETLINK_INET_DIAG: int = 4; 128 | pub const MAX_LINKS: int = 32; 129 | 130 | pub const NLMSG_MIN_TYPE: int = 16; 131 | pub const NETLINK_ADD_MEMBERSHIP: int = 1; 132 | pub const NETLINK_DROP_MEMBERSHIP: int = 2; 133 | pub const NETLINK_PKTINFO: int = 3; 134 | pub const NETLINK_BROADCAST_ERROR: int = 4; 135 | pub const NETLINK_NO_ENOBUFS: int = 5; 136 | pub const NETLINK_RX_RING: int = 6; 137 | pub const NETLINK_TX_RING: int = 7; 138 | pub const NETLINK_LISTEN_ALL_NSID: int = 8; 139 | pub const NETLINK_LIST_MEMBERSHIPS: int = 9; 140 | pub const NETLINK_CAP_ACK: int = 10; 141 | pub const NETLINK_EXT_ACK: int = 11; 142 | pub const NL_MMAP_MSG_ALIGNMENT: int = 4; 143 | pub const NET_MAJOR: int = 36; 144 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pub mod constants; 4 | pub mod protocols { 5 | pub use super::constants::{ 6 | NETLINK_AUDIT, NETLINK_CONNECTOR, NETLINK_CRYPTO, NETLINK_DNRTMSG, 7 | NETLINK_ECRYPTFS, NETLINK_FIB_LOOKUP, NETLINK_FIREWALL, 8 | NETLINK_GENERIC, NETLINK_IP6_FW, NETLINK_ISCSI, NETLINK_KOBJECT_UEVENT, 9 | NETLINK_NETFILTER, NETLINK_NFLOG, NETLINK_RDMA, NETLINK_ROUTE, 10 | NETLINK_SCSITRANSPORT, NETLINK_SELINUX, NETLINK_SOCK_DIAG, 11 | NETLINK_UNUSED, NETLINK_USERSOCK, NETLINK_XFRM, 12 | }; 13 | } 14 | 15 | mod socket; 16 | pub use self::socket::Socket; 17 | 18 | mod addr; 19 | pub use self::addr::SocketAddr; 20 | 21 | mod async_socket; 22 | pub use self::async_socket::AsyncSocket; 23 | 24 | pub mod async_socket_ext; 25 | pub use self::async_socket_ext::AsyncSocketExt; 26 | 27 | #[cfg(feature = "tokio_socket")] 28 | mod tokio; 29 | #[cfg(feature = "tokio_socket")] 30 | pub use self::tokio::TokioSocket; 31 | 32 | #[cfg(feature = "smol_socket")] 33 | mod smol; 34 | #[cfg(feature = "smol_socket")] 35 | pub use self::smol::SmolSocket; 36 | 37 | #[cfg(feature = "mio_socket")] 38 | mod mio; 39 | -------------------------------------------------------------------------------- /src/mio.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use crate::Socket; 4 | use std::os::unix::io::AsRawFd; 5 | 6 | use mio::{event::Source, unix::SourceFd}; 7 | 8 | impl Source for Socket { 9 | fn register( 10 | &mut self, 11 | registry: &mio::Registry, 12 | token: mio::Token, 13 | interests: mio::Interest, 14 | ) -> std::io::Result<()> { 15 | let raw_fd = self.as_raw_fd(); 16 | 17 | SourceFd(&raw_fd).register(registry, token, interests) 18 | } 19 | 20 | fn reregister( 21 | &mut self, 22 | registry: &mio::Registry, 23 | token: mio::Token, 24 | interests: mio::Interest, 25 | ) -> std::io::Result<()> { 26 | let raw_fd = self.as_raw_fd(); 27 | 28 | SourceFd(&raw_fd).reregister(registry, token, interests) 29 | } 30 | 31 | fn deregister(&mut self, registry: &mio::Registry) -> std::io::Result<()> { 32 | let raw_fd = self.as_raw_fd(); 33 | 34 | SourceFd(&raw_fd).deregister(registry) 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::*; 41 | 42 | fn request_neighbour_dump(socket: &mut Socket) -> std::io::Result<()> { 43 | // Buffer generated from: 44 | // ``` 45 | // let mut neighbour_dump_request = NetlinkMessage { 46 | // header: NetlinkHeader { 47 | // flags: NLM_F_DUMP | NLM_F_REQUEST, 48 | // ..Default::default() 49 | // }, 50 | // payload: NetlinkPayload::from(RtnlMessage::GetNeighbour(NeighbourMessage::default())), 51 | // }; 52 | // ``` 53 | let buf = [ 54 | 28, 0, 0, 0, 30, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55 | 0, 0, 0, 0, 0, 0, 56 | ]; 57 | socket.send(&buf[..], 0)?; 58 | 59 | Ok(()) 60 | } 61 | 62 | #[test] 63 | fn test_event_loop() -> Result<(), Box> { 64 | use crate::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; 65 | use mio::{Events, Interest, Poll, Token}; 66 | use std::time::Duration; 67 | 68 | let mut poll = Poll::new()?; 69 | let mut events = Events::with_capacity(128); 70 | 71 | let mut socket = Socket::new(NETLINK_ROUTE)?; 72 | socket.bind_auto()?; 73 | socket.connect(&SocketAddr::new(0, 0))?; 74 | poll.registry() 75 | .register(&mut socket, Token(1), Interest::READABLE)?; 76 | 77 | // Send neighbour query 78 | request_neighbour_dump(&mut socket)?; 79 | 80 | // Make sure that we got anything 81 | poll.poll(&mut events, Some(Duration::from_secs(1)))?; 82 | assert!(!events.is_empty()); 83 | 84 | // Make sure the we didn't get a thing after removing socket from loop 85 | poll.registry().deregister(&mut socket)?; 86 | poll.poll(&mut events, Some(Duration::from_secs(1)))?; 87 | assert!(events.is_empty()); 88 | 89 | Ok(()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/smol.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::{ 4 | io, 5 | os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd}, 6 | task::{Context, Poll}, 7 | }; 8 | 9 | use async_io::Async; 10 | 11 | use futures::ready; 12 | 13 | use log::trace; 14 | 15 | use crate::{AsyncSocket, Socket, SocketAddr}; 16 | 17 | /// An I/O object representing a Netlink socket. 18 | pub struct SmolSocket(Async); 19 | 20 | impl FromRawFd for SmolSocket { 21 | unsafe fn from_raw_fd(fd: RawFd) -> Self { 22 | let socket = Socket::from_raw_fd(fd); 23 | socket.set_non_blocking(true).unwrap(); 24 | SmolSocket(Async::new(socket).unwrap()) 25 | } 26 | } 27 | 28 | impl AsRawFd for SmolSocket { 29 | fn as_raw_fd(&self) -> RawFd { 30 | self.0.get_ref().as_raw_fd() 31 | } 32 | } 33 | 34 | impl AsFd for SmolSocket { 35 | fn as_fd(&self) -> BorrowedFd<'_> { 36 | self.0.get_ref().as_fd() 37 | } 38 | } 39 | 40 | // async_io::Async<..>::{read,write}_with[_mut] functions try IO first, 41 | // and only register context if it would block. 42 | // replicate this in these poll functions: 43 | impl SmolSocket { 44 | fn poll_write_with( 45 | &mut self, 46 | cx: &mut Context<'_>, 47 | mut op: F, 48 | ) -> Poll> 49 | where 50 | F: FnMut(&mut Self) -> io::Result, 51 | { 52 | loop { 53 | match op(self) { 54 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} 55 | res => return Poll::Ready(res), 56 | } 57 | // try again if writable now, otherwise come back later: 58 | ready!(self.0.poll_writable(cx))?; 59 | } 60 | } 61 | 62 | fn poll_read_with( 63 | &mut self, 64 | cx: &mut Context<'_>, 65 | mut op: F, 66 | ) -> Poll> 67 | where 68 | F: FnMut(&mut Self) -> io::Result, 69 | { 70 | loop { 71 | match op(self) { 72 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} 73 | res => return Poll::Ready(res), 74 | } 75 | // try again if readable now, otherwise come back later: 76 | ready!(self.0.poll_readable(cx))?; 77 | } 78 | } 79 | } 80 | 81 | impl AsyncSocket for SmolSocket { 82 | fn socket_ref(&self) -> &Socket { 83 | self.0.get_ref() 84 | } 85 | 86 | /// Mutable access to underyling [`Socket`] 87 | fn socket_mut(&mut self) -> &mut Socket { 88 | unsafe { self.0.get_mut() } 89 | } 90 | 91 | fn new(protocol: isize) -> io::Result { 92 | let socket = Socket::new(protocol)?; 93 | Ok(Self(Async::new(socket)?)) 94 | } 95 | 96 | fn poll_send( 97 | &mut self, 98 | cx: &mut Context<'_>, 99 | buf: &[u8], 100 | ) -> Poll> { 101 | self.poll_write_with(cx, |this| this.socket_mut().send(buf, 0)) 102 | } 103 | 104 | fn poll_send_to( 105 | &mut self, 106 | cx: &mut Context<'_>, 107 | buf: &[u8], 108 | addr: &SocketAddr, 109 | ) -> Poll> { 110 | self.poll_write_with(cx, |this| this.socket_mut().send_to(buf, addr, 0)) 111 | } 112 | 113 | fn poll_recv( 114 | &mut self, 115 | cx: &mut Context<'_>, 116 | buf: &mut B, 117 | ) -> Poll> 118 | where 119 | B: bytes::BufMut, 120 | { 121 | self.poll_read_with(cx, |this| { 122 | this.socket_mut().recv(buf, 0).map(|_len| ()) 123 | }) 124 | } 125 | 126 | fn poll_recv_from( 127 | &mut self, 128 | cx: &mut Context<'_>, 129 | buf: &mut B, 130 | ) -> Poll> 131 | where 132 | B: bytes::BufMut, 133 | { 134 | self.poll_read_with(cx, |this| { 135 | let x = this.socket_mut().recv_from(buf, 0); 136 | trace!("poll_recv_from: {:?}", x); 137 | x.map(|(_len, addr)| addr) 138 | }) 139 | } 140 | 141 | fn poll_recv_from_full( 142 | &mut self, 143 | cx: &mut Context<'_>, 144 | ) -> Poll, SocketAddr)>> { 145 | self.poll_read_with(cx, |this| this.socket_mut().recv_from_full()) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/socket.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::{ 4 | io::{Error, Result}, 5 | mem, 6 | os::{ 7 | fd::{AsFd, BorrowedFd, FromRawFd}, 8 | unix::io::{AsRawFd, RawFd}, 9 | }, 10 | }; 11 | 12 | use crate::SocketAddr; 13 | 14 | /// A netlink socket. 15 | /// 16 | /// # Example 17 | /// 18 | /// In this example we: 19 | /// 20 | /// 1. open a new socket 21 | /// 2. send a message to the kernel 22 | /// 3. read the reponse 23 | /// 24 | /// ```rust 25 | /// use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; 26 | /// use std::process; 27 | /// 28 | /// // open a new socket for the NETLINK_ROUTE subsystem (see "man 7 rtnetlink") 29 | /// let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); 30 | /// // address of the remote peer we'll send a message to. This particular address is for the kernel 31 | /// let kernel_addr = SocketAddr::new(0, 0); 32 | /// // this is a valid message for listing the network links on the system 33 | /// let pkt = vec![ 34 | /// 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x03, 0xfd, 0xfe, 0x38, 0x5c, 0x00, 0x00, 0x00, 35 | /// 0x00, 0x00, 0x00, 0x00, 0x00, 36 | /// ]; 37 | /// // send the message to the kernel 38 | /// let n_sent = socket.send_to(&pkt[..], &kernel_addr, 0).unwrap(); 39 | /// assert_eq!(n_sent, pkt.len()); 40 | /// // buffer for receiving the response 41 | /// let mut buf = vec![0; 4096]; 42 | /// loop { 43 | /// // receive a datagram 44 | /// let (n_received, sender_addr) = socket.recv_from(&mut &mut buf[..], 0).unwrap(); 45 | /// assert_eq!(sender_addr, kernel_addr); 46 | /// println!("received datagram {:?}", &buf[..n_received]); 47 | /// if buf[4] == 2 && buf[5] == 0 { 48 | /// println!("the kernel responded with an error"); 49 | /// return; 50 | /// } 51 | /// if buf[4] == 3 && buf[5] == 0 { 52 | /// println!("end of dump"); 53 | /// return; 54 | /// } 55 | /// } 56 | /// ``` 57 | #[derive(Clone, Debug)] 58 | pub struct Socket(RawFd); 59 | 60 | impl AsRawFd for Socket { 61 | fn as_raw_fd(&self) -> RawFd { 62 | self.0 63 | } 64 | } 65 | 66 | impl AsFd for Socket { 67 | fn as_fd(&self) -> BorrowedFd<'_> { 68 | unsafe { BorrowedFd::borrow_raw(self.0) } 69 | } 70 | } 71 | 72 | impl FromRawFd for Socket { 73 | unsafe fn from_raw_fd(fd: RawFd) -> Self { 74 | Socket(fd) 75 | } 76 | } 77 | 78 | impl Drop for Socket { 79 | fn drop(&mut self) { 80 | unsafe { libc::close(self.as_raw_fd()) }; 81 | } 82 | } 83 | 84 | impl Socket { 85 | /// Open a new socket for the given netlink subsystem. `protocol` must be 86 | /// one of the [`netlink_sys::protocols`][protos] constants. 87 | /// 88 | /// [protos]: crate::protocols 89 | pub fn new(protocol: isize) -> Result { 90 | let res = unsafe { 91 | libc::socket( 92 | libc::PF_NETLINK, 93 | libc::SOCK_DGRAM | libc::SOCK_CLOEXEC, 94 | protocol as libc::c_int, 95 | ) 96 | }; 97 | if res < 0 { 98 | return Err(Error::last_os_error()); 99 | } 100 | Ok(Socket(res)) 101 | } 102 | 103 | /// Bind the socket to the given address 104 | pub fn bind(&mut self, addr: &SocketAddr) -> Result<()> { 105 | let (addr_ptr, addr_len) = addr.as_raw(); 106 | let res = unsafe { libc::bind(self.as_raw_fd(), addr_ptr, addr_len) }; 107 | if res < 0 { 108 | return Err(Error::last_os_error()); 109 | } 110 | Ok(()) 111 | } 112 | 113 | /// Bind the socket to an address assigned by the kernel, and return that 114 | /// address. 115 | pub fn bind_auto(&mut self) -> Result { 116 | let mut addr = SocketAddr::new(0, 0); 117 | self.bind(&addr)?; 118 | self.get_address(&mut addr)?; 119 | Ok(addr) 120 | } 121 | 122 | /// Get the socket address 123 | pub fn get_address(&self, addr: &mut SocketAddr) -> Result<()> { 124 | let (addr_ptr, mut addr_len) = addr.as_raw_mut(); 125 | let addr_len_copy = addr_len; 126 | let addr_len_ptr = &mut addr_len as *mut libc::socklen_t; 127 | let res = unsafe { 128 | libc::getsockname(self.as_raw_fd(), addr_ptr, addr_len_ptr) 129 | }; 130 | if res < 0 { 131 | return Err(Error::last_os_error()); 132 | } 133 | assert_eq!(addr_len, addr_len_copy); 134 | Ok(()) 135 | } 136 | 137 | // when building with --features smol we don't need this 138 | #[allow(dead_code)] 139 | /// Make this socket non-blocking 140 | pub fn set_non_blocking(&self, non_blocking: bool) -> Result<()> { 141 | let mut non_blocking = non_blocking as libc::c_int; 142 | let res = unsafe { 143 | libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &mut non_blocking) 144 | }; 145 | if res < 0 { 146 | return Err(Error::last_os_error()); 147 | } 148 | Ok(()) 149 | } 150 | 151 | /// Connect the socket to the given address. Netlink is a connection-less 152 | /// protocol, so a socket can communicate with multiple peers with the 153 | /// [`Socket::send_to`] and [`Socket::recv_from`] methods. However, if the 154 | /// socket only needs to communicate with one peer, it is convenient not 155 | /// to have to bother with the peer address. This is what `connect` is 156 | /// for. After calling `connect`, [`Socket::send`] and [`Socket::recv`] 157 | /// respectively send and receive datagrams to and from `remote_addr`. 158 | /// 159 | /// # Examples 160 | /// 161 | /// In this example we: 162 | /// 163 | /// 1. open a socket 164 | /// 2. connect it to the kernel with [`Socket::connect`] 165 | /// 3. send a request to the kernel with [`Socket::send`] 166 | /// 4. read the response (which can span over several messages) 167 | /// [`Socket::recv`] 168 | /// 169 | /// ```rust 170 | /// use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr}; 171 | /// use std::process; 172 | /// 173 | /// let mut socket = Socket::new(NETLINK_ROUTE).unwrap(); 174 | /// let _ = socket.bind_auto().unwrap(); 175 | /// let kernel_addr = SocketAddr::new(0, 0); 176 | /// socket.connect(&kernel_addr).unwrap(); 177 | /// // This is a valid message for listing the network links on the system 178 | /// let msg = vec![ 179 | /// 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x03, 0xfd, 0xfe, 0x38, 0x5c, 0x00, 0x00, 0x00, 180 | /// 0x00, 0x00, 0x00, 0x00, 0x00, 181 | /// ]; 182 | /// let n_sent = socket.send(&msg[..], 0).unwrap(); 183 | /// assert_eq!(n_sent, msg.len()); 184 | /// // buffer for receiving the response 185 | /// let mut buf = vec![0; 4096]; 186 | /// loop { 187 | /// let mut n_received = socket.recv(&mut &mut buf[..], 0).unwrap(); 188 | /// println!("received {:?}", &buf[..n_received]); 189 | /// if buf[4] == 2 && buf[5] == 0 { 190 | /// println!("the kernel responded with an error"); 191 | /// return; 192 | /// } 193 | /// if buf[4] == 3 && buf[5] == 0 { 194 | /// println!("end of dump"); 195 | /// return; 196 | /// } 197 | /// } 198 | /// ``` 199 | pub fn connect(&self, remote_addr: &SocketAddr) -> Result<()> { 200 | // FIXME: 201 | // 202 | // Event though for SOCK_DGRAM sockets there's no IO, if our socket is 203 | // non-blocking, connect() might return EINPROGRESS. In theory, 204 | // the right way to treat EINPROGRESS would be to ignore the 205 | // error, and let the user poll the socket to check when it becomes 206 | // writable, indicating that the connection succeeded. The code already 207 | // exists in mio for TcpStream: 208 | // 209 | // > pub fn connect(stream: net::TcpStream, addr: &SocketAddr) -> 210 | // > io::Result { 211 | // > set_non_block(stream.as_raw_fd())?; 212 | // > match stream.connect(addr) { 213 | // > Ok(..) => {} 214 | // > Err(ref e) if e.raw_os_error() == Some(libc::EINPROGRESS) => {} 215 | // > Err(e) => return Err(e), 216 | // > } 217 | // > Ok(TcpStream { inner: stream }) 218 | // > } 219 | // 220 | // In practice, since the connection does not require any IO for 221 | // SOCK_DGRAM sockets, it almost never returns EINPROGRESS and 222 | // so for now, we just return whatever libc::connect returns. If 223 | // it returns EINPROGRESS, the caller will have to handle the error 224 | // themself 225 | // 226 | // Refs: 227 | // 228 | // - https://stackoverflow.com/a/14046386/1836144 229 | // - https://lists.isc.org/pipermail/bind-users/2009-August/077527.html 230 | let (addr, addr_len) = remote_addr.as_raw(); 231 | let res = unsafe { libc::connect(self.as_raw_fd(), addr, addr_len) }; 232 | if res < 0 { 233 | return Err(Error::last_os_error()); 234 | } 235 | Ok(()) 236 | } 237 | 238 | // Most of the comments in this method come from a discussion on rust users 239 | // forum. [thread]: https://users.rust-lang.org/t/help-understanding-libc-call/17308/9 240 | // 241 | /// Read a datagram from the socket and return the number of bytes that have 242 | /// been read and the address of the sender. The data being read is 243 | /// copied into `buf`. If `buf` is too small, the datagram is truncated. The 244 | /// supported flags are the `MSG_*` described in `man 2 recvmsg` 245 | /// 246 | /// # Warning 247 | /// 248 | /// In datagram oriented protocols, `recv` and `recvfrom` receive normally 249 | /// only ONE datagram, but this seems not to be always true for netlink 250 | /// sockets: with some protocols like `NETLINK_AUDIT`, multiple netlink 251 | /// packets can be read with a single call. 252 | pub fn recv_from( 253 | &self, 254 | buf: &mut B, 255 | flags: libc::c_int, 256 | ) -> Result<(usize, SocketAddr)> 257 | where 258 | B: bytes::BufMut, 259 | { 260 | // Create an empty storage for the address. Note that Rust standard 261 | // library create a sockaddr_storage so that it works for any 262 | // address family, but here, we already know that we'll have a 263 | // Netlink address, so we can create the appropriate storage. 264 | let mut addr = unsafe { mem::zeroed::() }; 265 | 266 | // recvfrom takes a *sockaddr as parameter so that it can accept any 267 | // kind of address storage, so we need to create such a pointer 268 | // for the sockaddr_nl we just initialized. 269 | // 270 | // Create a raw pointer to Cast our raw 271 | // pointer to a our storage. We cannot 272 | // generic pointer to *sockaddr pass it to 273 | // recvfrom yet. that recvfrom can use 274 | // ^ ^ 275 | // | | 276 | // +--------------+---------------+ +---------+--------+ 277 | // / \ / 278 | // \ 279 | let addr_ptr = 280 | &mut addr as *mut libc::sockaddr_nl as *mut libc::sockaddr; 281 | 282 | // Why do we need to pass the address length? We're passing a generic 283 | // *sockaddr to recvfrom. Somehow recvfrom needs to make sure 284 | // that the address of the received packet would fit into the 285 | // actual type that is behind *sockaddr: it could be a sockaddr_nl but 286 | // also a sockaddr_in, a sockaddr_in6, or even the generic 287 | // sockaddr_storage that can store any address. 288 | let mut addrlen = mem::size_of_val(&addr); 289 | // recvfrom does not take the address length by value (see [thread]), so 290 | // we need to create a pointer to it. 291 | let addrlen_ptr = &mut addrlen as *mut usize as *mut libc::socklen_t; 292 | 293 | let chunk = buf.chunk_mut(); 294 | // Cast the *mut u8 into *mut void. 295 | // This is equivalent to casting a *char into *void 296 | // See [thread] 297 | // ^ 298 | // Create a *mut u8 | 299 | // ^ | 300 | // | | 301 | // +------+-------+ +--------+-------+ 302 | // / \ / \ 303 | let buf_ptr = chunk.as_mut_ptr() as *mut libc::c_void; 304 | let buf_len = chunk.len() as libc::size_t; 305 | 306 | let res = unsafe { 307 | libc::recvfrom( 308 | self.as_raw_fd(), 309 | buf_ptr, 310 | buf_len, 311 | flags, 312 | addr_ptr, 313 | addrlen_ptr, 314 | ) 315 | }; 316 | if res < 0 { 317 | return Err(Error::last_os_error()); 318 | } else { 319 | // with `MSG_TRUNC` `res` might exceed `buf_len` 320 | let written = std::cmp::min(buf_len, res as usize); 321 | unsafe { 322 | buf.advance_mut(written); 323 | } 324 | } 325 | Ok((res as usize, SocketAddr(addr))) 326 | } 327 | 328 | /// For a connected socket, `recv` reads a datagram from the socket. The 329 | /// sender is the remote peer the socket is connected to (see 330 | /// [`Socket::connect`]). See also [`Socket::recv_from`] 331 | pub fn recv(&self, buf: &mut B, flags: libc::c_int) -> Result 332 | where 333 | B: bytes::BufMut, 334 | { 335 | let chunk = buf.chunk_mut(); 336 | let buf_ptr = chunk.as_mut_ptr() as *mut libc::c_void; 337 | let buf_len = chunk.len() as libc::size_t; 338 | 339 | let res = 340 | unsafe { libc::recv(self.as_raw_fd(), buf_ptr, buf_len, flags) }; 341 | if res < 0 { 342 | return Err(Error::last_os_error()); 343 | } else { 344 | // with `MSG_TRUNC` `res` might exceed `buf_len` 345 | let written = std::cmp::min(buf_len, res as usize); 346 | unsafe { 347 | buf.advance_mut(written); 348 | } 349 | } 350 | Ok(res as usize) 351 | } 352 | 353 | /// Receive a full message. Unlike [`Socket::recv_from`], which truncates 354 | /// messages that exceed the length of the buffer passed as argument, 355 | /// this method always reads a whole message, no matter its size. 356 | pub fn recv_from_full(&self) -> Result<(Vec, SocketAddr)> { 357 | // Peek 358 | let mut buf: Vec = Vec::new(); 359 | let (peek_len, _) = 360 | self.recv_from(&mut buf, libc::MSG_PEEK | libc::MSG_TRUNC)?; 361 | 362 | // Receive 363 | buf.clear(); 364 | buf.reserve(peek_len); 365 | let (rlen, addr) = self.recv_from(&mut buf, 0)?; 366 | assert_eq!(rlen, peek_len); 367 | Ok((buf, addr)) 368 | } 369 | 370 | /// Send the given buffer `buf` to the remote peer with address `addr`. The 371 | /// supported flags are the `MSG_*` values documented in `man 2 send`. 372 | pub fn send_to( 373 | &self, 374 | buf: &[u8], 375 | addr: &SocketAddr, 376 | flags: libc::c_int, 377 | ) -> Result { 378 | let (addr_ptr, addr_len) = addr.as_raw(); 379 | let buf_ptr = buf.as_ptr() as *const libc::c_void; 380 | let buf_len = buf.len() as libc::size_t; 381 | 382 | let res = unsafe { 383 | libc::sendto( 384 | self.as_raw_fd(), 385 | buf_ptr, 386 | buf_len, 387 | flags, 388 | addr_ptr, 389 | addr_len, 390 | ) 391 | }; 392 | if res < 0 { 393 | return Err(Error::last_os_error()); 394 | } 395 | Ok(res as usize) 396 | } 397 | 398 | /// For a connected socket, `send` sends the given buffer `buf` to the 399 | /// remote peer the socket is connected to. See also [`Socket::connect`] 400 | /// and [`Socket::send_to`]. 401 | pub fn send(&self, buf: &[u8], flags: libc::c_int) -> Result { 402 | let buf_ptr = buf.as_ptr() as *const libc::c_void; 403 | let buf_len = buf.len() as libc::size_t; 404 | 405 | let res = 406 | unsafe { libc::send(self.as_raw_fd(), buf_ptr, buf_len, flags) }; 407 | if res < 0 { 408 | return Err(Error::last_os_error()); 409 | } 410 | Ok(res as usize) 411 | } 412 | 413 | pub fn set_pktinfo(&mut self, value: bool) -> Result<()> { 414 | let value: libc::c_int = value.into(); 415 | setsockopt( 416 | self.as_raw_fd(), 417 | libc::SOL_NETLINK, 418 | libc::NETLINK_PKTINFO, 419 | value, 420 | ) 421 | } 422 | 423 | pub fn get_pktinfo(&self) -> Result { 424 | let res = getsockopt::( 425 | self.as_raw_fd(), 426 | libc::SOL_NETLINK, 427 | libc::NETLINK_PKTINFO, 428 | )?; 429 | Ok(res == 1) 430 | } 431 | 432 | pub fn add_membership(&mut self, group: u32) -> Result<()> { 433 | setsockopt( 434 | self.as_raw_fd(), 435 | libc::SOL_NETLINK, 436 | libc::NETLINK_ADD_MEMBERSHIP, 437 | group, 438 | ) 439 | } 440 | 441 | pub fn drop_membership(&mut self, group: u32) -> Result<()> { 442 | setsockopt( 443 | self.as_raw_fd(), 444 | libc::SOL_NETLINK, 445 | libc::NETLINK_DROP_MEMBERSHIP, 446 | group, 447 | ) 448 | } 449 | 450 | // pub fn list_membership(&self) -> Vec { 451 | // unimplemented!(); 452 | // // getsockopt won't be enough here, because we may need to perform 2 453 | // calls, and because the // length of the list returned by 454 | // libc::getsockopt is returned by mutating the length // argument, 455 | // which our implementation of getsockopt forbids. } 456 | 457 | /// `NETLINK_BROADCAST_ERROR` (since Linux 2.6.30). When not set, 458 | /// `netlink_broadcast()` only reports `ESRCH` errors and silently 459 | /// ignore `NOBUFS` errors. 460 | pub fn set_broadcast_error(&mut self, value: bool) -> Result<()> { 461 | let value: libc::c_int = value.into(); 462 | setsockopt( 463 | self.as_raw_fd(), 464 | libc::SOL_NETLINK, 465 | libc::NETLINK_BROADCAST_ERROR, 466 | value, 467 | ) 468 | } 469 | 470 | pub fn get_broadcast_error(&self) -> Result { 471 | let res = getsockopt::( 472 | self.as_raw_fd(), 473 | libc::SOL_NETLINK, 474 | libc::NETLINK_BROADCAST_ERROR, 475 | )?; 476 | Ok(res == 1) 477 | } 478 | 479 | /// `NETLINK_NO_ENOBUFS` (since Linux 2.6.30). This flag can be used by 480 | /// unicast and broadcast listeners to avoid receiving `ENOBUFS` errors. 481 | pub fn set_no_enobufs(&mut self, value: bool) -> Result<()> { 482 | let value: libc::c_int = value.into(); 483 | setsockopt( 484 | self.as_raw_fd(), 485 | libc::SOL_NETLINK, 486 | libc::NETLINK_NO_ENOBUFS, 487 | value, 488 | ) 489 | } 490 | 491 | pub fn get_no_enobufs(&self) -> Result { 492 | let res = getsockopt::( 493 | self.as_raw_fd(), 494 | libc::SOL_NETLINK, 495 | libc::NETLINK_NO_ENOBUFS, 496 | )?; 497 | Ok(res == 1) 498 | } 499 | 500 | /// `NETLINK_LISTEN_ALL_NSID` (since Linux 4.2). When set, this socket will 501 | /// receive netlink notifications from all network namespaces that 502 | /// have an nsid assigned into the network namespace where the socket 503 | /// has been opened. The nsid is sent to user space via an ancillary 504 | /// data. 505 | pub fn set_listen_all_namespaces(&mut self, value: bool) -> Result<()> { 506 | let value: libc::c_int = value.into(); 507 | setsockopt( 508 | self.as_raw_fd(), 509 | libc::SOL_NETLINK, 510 | libc::NETLINK_LISTEN_ALL_NSID, 511 | value, 512 | ) 513 | } 514 | 515 | pub fn get_listen_all_namespaces(&self) -> Result { 516 | let res = getsockopt::( 517 | self.as_raw_fd(), 518 | libc::SOL_NETLINK, 519 | libc::NETLINK_LISTEN_ALL_NSID, 520 | )?; 521 | Ok(res == 1) 522 | } 523 | 524 | /// `NETLINK_CAP_ACK` (since Linux 4.2). The kernel may fail to allocate the 525 | /// necessary room for the acknowledgment message back to user space. 526 | /// This option trims off the payload of the original netlink message. 527 | /// The netlink message header is still included, so the user can 528 | /// guess from the sequence number which message triggered the 529 | /// acknowledgment. 530 | pub fn set_cap_ack(&mut self, value: bool) -> Result<()> { 531 | let value: libc::c_int = value.into(); 532 | setsockopt( 533 | self.as_raw_fd(), 534 | libc::SOL_NETLINK, 535 | libc::NETLINK_CAP_ACK, 536 | value, 537 | ) 538 | } 539 | 540 | pub fn get_cap_ack(&self) -> Result { 541 | let res = getsockopt::( 542 | self.as_raw_fd(), 543 | libc::SOL_NETLINK, 544 | libc::NETLINK_CAP_ACK, 545 | )?; 546 | Ok(res == 1) 547 | } 548 | 549 | /// `NETLINK_EXT_ACK` 550 | /// Extended ACK controls reporting of additional error/warning TLVs in 551 | /// NLMSG_ERROR and NLMSG_DONE messages. 552 | pub fn set_ext_ack(&mut self, value: bool) -> Result<()> { 553 | let value: libc::c_int = value.into(); 554 | setsockopt( 555 | self.as_raw_fd(), 556 | libc::SOL_NETLINK, 557 | libc::NETLINK_EXT_ACK, 558 | value, 559 | ) 560 | } 561 | 562 | pub fn get_ext_ack(&self) -> Result { 563 | let res = getsockopt::( 564 | self.as_raw_fd(), 565 | libc::SOL_NETLINK, 566 | libc::NETLINK_EXT_ACK, 567 | )?; 568 | Ok(res == 1) 569 | } 570 | 571 | /// Sets socket receive buffer in bytes. 572 | /// The kernel doubles this value (to allow space for bookkeeping overhead), 573 | /// and this doubled value is returned by [get_rx_buf_sz].(see socket(7) 574 | /// The default value is set by the proc/sys/net/core/rmem_default file, and 575 | /// the maximum allowed value is set by the /proc/sys/net/core/rmem_max 576 | /// file. The minimum (doubled) value for this option is 256. 577 | pub fn set_rx_buf_sz(&self, size: T) -> Result<()> { 578 | setsockopt(self.as_raw_fd(), libc::SOL_SOCKET, libc::SO_RCVBUF, size) 579 | } 580 | 581 | /// Gets socket receive buffer in bytes 582 | pub fn get_rx_buf_sz(&self) -> Result { 583 | let res = getsockopt::( 584 | self.as_raw_fd(), 585 | libc::SOL_SOCKET, 586 | libc::SO_RCVBUF, 587 | )?; 588 | Ok(res as usize) 589 | } 590 | 591 | /// Set strict input checking(`NETLINK_GET_STRICT_CHK`) in netlink route 592 | /// protocol. By default, `NETLINK_GET_STRICT_CHK` is not enabled. 593 | pub fn set_netlink_get_strict_chk(&self, value: bool) -> Result<()> { 594 | let value: u32 = value.into(); 595 | setsockopt( 596 | self.as_raw_fd(), 597 | libc::SOL_NETLINK, 598 | libc::NETLINK_GET_STRICT_CHK, 599 | value, 600 | ) 601 | } 602 | } 603 | 604 | /// Wrapper around `getsockopt`: 605 | /// 606 | /// ```no_rust 607 | /// int getsockopt(int socket, int level, int option_name, void *restrict option_value, socklen_t *restrict option_len); 608 | /// ``` 609 | pub(crate) fn getsockopt( 610 | fd: RawFd, 611 | level: libc::c_int, 612 | option: libc::c_int, 613 | ) -> Result { 614 | // Create storage for the options we're fetching 615 | let mut slot: T = unsafe { mem::zeroed() }; 616 | 617 | // Create a mutable raw pointer to the storage so that getsockopt can fill 618 | // the value 619 | let slot_ptr = &mut slot as *mut T as *mut libc::c_void; 620 | 621 | // Let getsockopt know how big our storage is 622 | let mut slot_len = mem::size_of::() as libc::socklen_t; 623 | 624 | // getsockopt takes a mutable pointer to the length, because for some 625 | // options like NETLINK_LIST_MEMBERSHIP where the option value is a list 626 | // with arbitrary length, getsockopt uses this parameter to signal how 627 | // big the storage needs to be. 628 | let slot_len_ptr = &mut slot_len as *mut libc::socklen_t; 629 | 630 | let res = 631 | unsafe { libc::getsockopt(fd, level, option, slot_ptr, slot_len_ptr) }; 632 | if res < 0 { 633 | return Err(Error::last_os_error()); 634 | } 635 | 636 | // Ignore the options that require the legnth to be set by getsockopt. 637 | // We'll deal with them individually. 638 | assert_eq!(slot_len as usize, mem::size_of::()); 639 | 640 | Ok(slot) 641 | } 642 | 643 | // adapted from rust standard library 644 | fn setsockopt( 645 | fd: RawFd, 646 | level: libc::c_int, 647 | option: libc::c_int, 648 | payload: T, 649 | ) -> Result<()> { 650 | let payload = &payload as *const T as *const libc::c_void; 651 | let payload_len = mem::size_of::() as libc::socklen_t; 652 | 653 | let res = 654 | unsafe { libc::setsockopt(fd, level, option, payload, payload_len) }; 655 | if res < 0 { 656 | return Err(Error::last_os_error()); 657 | } 658 | Ok(()) 659 | } 660 | 661 | #[cfg(test)] 662 | mod test { 663 | use super::*; 664 | use crate::protocols::NETLINK_ROUTE; 665 | 666 | #[test] 667 | fn new() { 668 | Socket::new(NETLINK_ROUTE).unwrap(); 669 | } 670 | 671 | #[test] 672 | fn connect() { 673 | let sock = Socket::new(NETLINK_ROUTE).unwrap(); 674 | sock.connect(&SocketAddr::new(0, 0)).unwrap(); 675 | } 676 | 677 | #[test] 678 | fn bind() { 679 | let mut sock = Socket::new(NETLINK_ROUTE).unwrap(); 680 | sock.bind(&SocketAddr::new(4321, 0)).unwrap(); 681 | } 682 | 683 | #[test] 684 | fn bind_auto() { 685 | let mut sock = Socket::new(NETLINK_ROUTE).unwrap(); 686 | let addr = sock.bind_auto().unwrap(); 687 | // make sure that the address we got from the kernel is there 688 | assert!(addr.port_number() != 0); 689 | } 690 | 691 | #[test] 692 | fn set_non_blocking() { 693 | let sock = Socket::new(NETLINK_ROUTE).unwrap(); 694 | sock.set_non_blocking(true).unwrap(); 695 | sock.set_non_blocking(false).unwrap(); 696 | } 697 | 698 | #[test] 699 | fn options() { 700 | let mut sock = Socket::new(NETLINK_ROUTE).unwrap(); 701 | 702 | sock.set_cap_ack(true).unwrap(); 703 | assert!(sock.get_cap_ack().unwrap()); 704 | sock.set_cap_ack(false).unwrap(); 705 | assert!(!sock.get_cap_ack().unwrap()); 706 | 707 | sock.set_no_enobufs(true).unwrap(); 708 | assert!(sock.get_no_enobufs().unwrap()); 709 | sock.set_no_enobufs(false).unwrap(); 710 | assert!(!sock.get_no_enobufs().unwrap()); 711 | 712 | sock.set_broadcast_error(true).unwrap(); 713 | assert!(sock.get_broadcast_error().unwrap()); 714 | sock.set_broadcast_error(false).unwrap(); 715 | assert!(!sock.get_broadcast_error().unwrap()); 716 | 717 | // FIXME: these require root permissions 718 | // sock.set_listen_all_namespaces(true).unwrap(); 719 | // assert!(sock.get_listen_all_namespaces().unwrap()); 720 | // sock.set_listen_all_namespaces(false).unwrap(); 721 | // assert!(!sock.get_listen_all_namespaces().unwrap()); 722 | } 723 | } 724 | -------------------------------------------------------------------------------- /src/tokio.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::{ 4 | io, 5 | os::unix::io::{AsRawFd, FromRawFd, RawFd}, 6 | task::{Context, Poll}, 7 | }; 8 | 9 | use futures::ready; 10 | use log::trace; 11 | use tokio::io::unix::AsyncFd; 12 | 13 | use crate::{AsyncSocket, Socket, SocketAddr}; 14 | 15 | /// An I/O object representing a Netlink socket. 16 | pub struct TokioSocket(AsyncFd); 17 | 18 | impl FromRawFd for TokioSocket { 19 | unsafe fn from_raw_fd(fd: RawFd) -> Self { 20 | let socket = Socket::from_raw_fd(fd); 21 | socket.set_non_blocking(true).unwrap(); 22 | TokioSocket(AsyncFd::new(socket).unwrap()) 23 | } 24 | } 25 | 26 | impl AsRawFd for TokioSocket { 27 | fn as_raw_fd(&self) -> RawFd { 28 | self.0.get_ref().as_raw_fd() 29 | } 30 | } 31 | 32 | impl AsyncSocket for TokioSocket { 33 | fn socket_ref(&self) -> &Socket { 34 | self.0.get_ref() 35 | } 36 | 37 | /// Mutable access to underyling [`Socket`] 38 | fn socket_mut(&mut self) -> &mut Socket { 39 | self.0.get_mut() 40 | } 41 | 42 | fn new(protocol: isize) -> io::Result { 43 | let socket = Socket::new(protocol)?; 44 | socket.set_non_blocking(true)?; 45 | Ok(Self(AsyncFd::new(socket)?)) 46 | } 47 | 48 | fn poll_send( 49 | &mut self, 50 | cx: &mut Context<'_>, 51 | buf: &[u8], 52 | ) -> Poll> { 53 | loop { 54 | // Check if the socket it writable. If 55 | // AsyncFd::poll_write_ready returns NotReady, it will 56 | // already have arranged for the current task to be 57 | // notified when the socket becomes writable, so we can 58 | // just return Pending 59 | let mut guard = ready!(self.0.poll_write_ready(cx))?; 60 | 61 | match guard.try_io(|inner| inner.get_ref().send(buf, 0)) { 62 | Ok(x) => return Poll::Ready(x), 63 | Err(_would_block) => continue, 64 | } 65 | } 66 | } 67 | 68 | fn poll_send_to( 69 | &mut self, 70 | cx: &mut Context<'_>, 71 | buf: &[u8], 72 | addr: &SocketAddr, 73 | ) -> Poll> { 74 | loop { 75 | let mut guard = ready!(self.0.poll_write_ready(cx))?; 76 | 77 | match guard.try_io(|inner| inner.get_ref().send_to(buf, addr, 0)) { 78 | Ok(x) => return Poll::Ready(x), 79 | Err(_would_block) => continue, 80 | } 81 | } 82 | } 83 | 84 | fn poll_recv( 85 | &mut self, 86 | cx: &mut Context<'_>, 87 | buf: &mut B, 88 | ) -> Poll> 89 | where 90 | B: bytes::BufMut, 91 | { 92 | loop { 93 | // Check if the socket is readable. If not, 94 | // AsyncFd::poll_read_ready would have arranged for the 95 | // current task to be polled again when the socket becomes 96 | // readable, so we can just return Pending 97 | let mut guard = ready!(self.0.poll_read_ready(cx))?; 98 | 99 | match guard.try_io(|inner| inner.get_ref().recv(buf, 0)) { 100 | Ok(x) => return Poll::Ready(x.map(|_len| ())), 101 | Err(_would_block) => continue, 102 | } 103 | } 104 | } 105 | 106 | fn poll_recv_from( 107 | &mut self, 108 | cx: &mut Context<'_>, 109 | buf: &mut B, 110 | ) -> Poll> 111 | where 112 | B: bytes::BufMut, 113 | { 114 | loop { 115 | trace!("poll_recv_from called"); 116 | let mut guard = ready!(self.0.poll_read_ready(cx))?; 117 | trace!("poll_recv_from socket is ready for reading"); 118 | 119 | match guard.try_io(|inner| inner.get_ref().recv_from(buf, 0)) { 120 | Ok(x) => { 121 | trace!("poll_recv_from {:?} bytes read", x); 122 | return Poll::Ready(x.map(|(_len, addr)| addr)); 123 | } 124 | Err(_would_block) => { 125 | trace!("poll_recv_from socket would block"); 126 | continue; 127 | } 128 | } 129 | } 130 | } 131 | 132 | fn poll_recv_from_full( 133 | &mut self, 134 | cx: &mut Context<'_>, 135 | ) -> Poll, SocketAddr)>> { 136 | loop { 137 | trace!("poll_recv_from_full called"); 138 | let mut guard = ready!(self.0.poll_read_ready(cx))?; 139 | trace!("poll_recv_from_full socket is ready for reading"); 140 | 141 | match guard.try_io(|inner| inner.get_ref().recv_from_full()) { 142 | Ok(x) => { 143 | trace!("poll_recv_from_full {:?} bytes read", x); 144 | return Poll::Ready(x); 145 | } 146 | Err(_would_block) => { 147 | trace!("poll_recv_from_full socket would block"); 148 | continue; 149 | } 150 | } 151 | } 152 | } 153 | } 154 | --------------------------------------------------------------------------------