Skip to main content

netlink_socket2/
lib.rs

1#![allow(clippy::doc_lazy_continuation)]
2#![doc = include_str!("../README.md")]
3
4use std::{
5    collections::{hash_map::Entry, HashMap},
6    io::{self, ErrorKind, IoSlice},
7    marker::PhantomData,
8    os::fd::{AsRawFd, FromRawFd, OwnedFd},
9    sync::Arc,
10};
11
12#[cfg(not(feature = "async"))]
13use std::{
14    io::{Read, Write},
15    net::TcpStream as Socket,
16};
17
18#[cfg(feature = "tokio")]
19use tokio::net::TcpStream as Socket;
20
21#[cfg(feature = "smol")]
22use smol::io::{AsyncReadExt, AsyncWriteExt};
23#[cfg(feature = "smol")]
24type Socket = smol::Async<std::net::TcpStream>;
25
26use netlink_bindings::{
27    builtin::Nlmsghdr,
28    nlctrl,
29    traits::{NetlinkRequest, Protocol},
30    utils,
31};
32
33mod chained;
34mod error;
35mod multicast;
36
37pub use chained::NetlinkReplyChained;
38pub use error::ReplyError;
39pub use multicast::{MulticastRecv, MulticastSocketRaw};
40
41/// Netlink documentation recommends max(8192, page_size)
42pub const RECV_BUF_SIZE: usize = 8192;
43
44pub struct NetlinkSocket {
45    buf: Arc<[u8; RECV_BUF_SIZE]>,
46    cache: HashMap<&'static [u8], u16>,
47    sock: HashMap<u16, Socket>,
48    seq: u32,
49}
50
51impl NetlinkSocket {
52    #[allow(clippy::new_without_default)]
53    pub fn new() -> Self {
54        Self {
55            buf: Arc::new([0u8; RECV_BUF_SIZE]),
56            cache: HashMap::default(),
57            sock: HashMap::new(),
58            seq: 1,
59        }
60    }
61
62    fn get_socket_cached(
63        cache: &mut HashMap<u16, Socket>,
64        protonum: u16,
65    ) -> io::Result<&mut Socket> {
66        match cache.entry(protonum) {
67            Entry::Occupied(sock) => Ok(sock.into_mut()),
68            Entry::Vacant(ent) => {
69                let sock = Self::get_socket_new(protonum)?;
70                Ok(ent.insert(sock))
71            }
72        }
73    }
74
75    fn get_socket_new(family: u16) -> io::Result<Socket> {
76        let fd = unsafe {
77            libc::socket(
78                libc::AF_NETLINK,
79                libc::SOCK_RAW | libc::SOCK_CLOEXEC,
80                family as i32,
81            )
82        };
83        if fd < 0 {
84            return Err(io::Error::from_raw_os_error(-fd));
85        }
86        let fd = unsafe { OwnedFd::from_raw_fd(fd) };
87
88        // Enable extended attributes in libc::NLMSG_ERROR and libc::NLMSG_DONE
89        let res = unsafe {
90            libc::setsockopt(
91                fd.as_raw_fd(),
92                libc::SOL_NETLINK,
93                libc::NETLINK_EXT_ACK,
94                &1u32 as *const u32 as *const libc::c_void,
95                4,
96            )
97        };
98        if res < 0 {
99            return Err(io::Error::from_raw_os_error(-res));
100        }
101
102        let sock: std::net::TcpStream = fd.into();
103
104        #[cfg(feature = "async")]
105        {
106            sock.set_nonblocking(true)?;
107            Socket::try_from(sock)
108        }
109
110        #[cfg(not(feature = "async"))]
111        Ok(sock)
112    }
113
114    /// Reserve a sequential chunk of `seq` values, so chained messages don't
115    /// get confused. A random `seq` number might be used just as well.
116    pub fn reserve_seq(&mut self, len: u32) -> u32 {
117        let seq = self.seq;
118        self.seq = self.seq.wrapping_add(len);
119        seq
120    }
121
122    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
123    pub async fn request<'sock, Request>(
124        &'sock mut self,
125        request: &Request,
126    ) -> io::Result<NetlinkReply<'sock, Request>>
127    where
128        Request: NetlinkRequest,
129    {
130        let (protonum, request_type) = match request.protocol() {
131            Protocol::Raw {
132                protonum,
133                request_type,
134            } => (protonum, request_type),
135            Protocol::Generic(name) => (libc::GENL_ID_CTRL as u16, self.resolve(name).await?),
136        };
137
138        self.request_raw(request, protonum, request_type).await
139    }
140
141    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
142    async fn resolve(&mut self, family_name: &'static [u8]) -> io::Result<u16> {
143        if let Some(id) = self.cache.get(family_name) {
144            return Ok(*id);
145        }
146
147        let mut request = nlctrl::Request::new().op_getfamily_do();
148        request.encode().push_family_name_bytes(family_name);
149
150        let Protocol::Raw {
151            protonum,
152            request_type,
153        } = request.protocol()
154        else {
155            unreachable!()
156        };
157        assert_eq!(protonum, libc::NETLINK_GENERIC as u16);
158        assert_eq!(request_type, libc::GENL_ID_CTRL as u16);
159
160        let mut iter = self.request_raw(&request, protonum, request_type).await?;
161        if let Some(reply) = iter.recv().await {
162            let Ok(id) = reply?.get_family_id() else {
163                return Err(ErrorKind::Unsupported.into());
164            };
165            self.cache.insert(family_name, id);
166            return Ok(id);
167        }
168
169        Err(ErrorKind::UnexpectedEof.into())
170    }
171
172    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
173    async fn request_raw<'sock, Request>(
174        &'sock mut self,
175        request: &Request,
176        protonum: u16,
177        request_type: u16,
178    ) -> io::Result<NetlinkReply<'sock, Request>>
179    where
180        Request: NetlinkRequest,
181    {
182        let seq = self.reserve_seq(1);
183        let sock = Self::get_socket_cached(&mut self.sock, protonum)?;
184
185        let header = Nlmsghdr {
186            len: Nlmsghdr::len() as u32 + request.payload().len() as u32,
187            r#type: request_type,
188            flags: request.flags() | libc::NLM_F_REQUEST as u16 | libc::NLM_F_ACK as u16,
189            seq,
190            pid: 0,
191        };
192
193        Self::write_buf(
194            sock,
195            &[
196                IoSlice::new(header.as_slice()),
197                IoSlice::new(request.payload()),
198            ],
199        )
200        .await?;
201
202        Ok(NetlinkReply {
203            sock,
204            buf: &mut self.buf,
205            inner: NetlinkReplyInner {
206                buf_offset: 0,
207                buf_read: 0,
208            },
209            seq: header.seq,
210            done: false,
211            phantom: PhantomData,
212        })
213    }
214
215    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
216    async fn write_buf(sock: &mut Socket, payload: &[IoSlice<'_>]) -> io::Result<()> {
217        loop {
218            #[cfg(not(feature = "tokio"))]
219            let res = sock.write_vectored(payload).await;
220
221            #[cfg(feature = "tokio")]
222            let res = loop {
223                // Some subsystems don't correctly implement io notifications, which tokio runtime
224                // expects to receive before doing any actual io, hence we instead always attempt an io
225                // operation first.
226                let res = sock.try_write_vectored(payload);
227                if matches!(&res, Err(err) if err.kind() == ErrorKind::WouldBlock) {
228                    sock.writable().await?;
229                    continue;
230                }
231                break res;
232            };
233
234            match res {
235                Ok(sent) if sent != payload.iter().map(|s| s.len()).sum() => {
236                    return Err(io::Error::other("Couldn't send the whole message"));
237                }
238                Ok(_) => return Ok(()),
239                Err(err) if err.kind() == ErrorKind::Interrupted => continue,
240                Err(err) => return Err(err),
241            }
242        }
243    }
244}
245
246struct NetlinkReplyInner {
247    buf_offset: usize,
248    buf_read: usize,
249}
250
251impl NetlinkReplyInner {
252    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
253    async fn read_buf(sock: &mut Socket, buf: &mut [u8]) -> io::Result<usize> {
254        loop {
255            #[cfg(not(feature = "tokio"))]
256            let res = sock.read(&mut buf[..]).await;
257
258            #[cfg(feature = "tokio")]
259            let res = {
260                // Some subsystems don't correctly implement io notifications, which tokio
261                // runtime expects to receive before doing any actual io, hence we instead
262                // always attempt an io operation first.
263                let res = sock.try_read(&mut buf[..]);
264                if matches!(&res, Err(err) if err.kind() == ErrorKind::WouldBlock) {
265                    sock.readable().await?;
266                    continue;
267                }
268                res
269            };
270
271            match res {
272                Ok(read) => return Ok(read),
273                Err(err) if err.kind() == ErrorKind::Interrupted => continue,
274                Err(err) => return Err(err),
275            }
276        }
277    }
278
279    #[allow(clippy::type_complexity)]
280    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
281    pub async fn recv(
282        &mut self,
283        sock: &mut Socket,
284        buf: &mut [u8; RECV_BUF_SIZE],
285    ) -> io::Result<(u32, u16, Result<(usize, usize), ReplyError>)> {
286        if self.buf_offset == self.buf_read {
287            self.buf_read = Self::read_buf(sock, &mut buf[..]).await?;
288            self.buf_offset = 0;
289        }
290        self.parse_next(buf).await
291    }
292
293    #[allow(clippy::type_complexity)]
294    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
295    async fn parse_next(
296        &mut self,
297        buf: &[u8; RECV_BUF_SIZE],
298    ) -> io::Result<(u32, u16, Result<(usize, usize), ReplyError>)> {
299        let packet = &buf[self.buf_offset..self.buf_read];
300
301        let too_short_err = || io::Error::other("Received packet is too short");
302
303        let Some(header) = packet.get(..Nlmsghdr::len()) else {
304            return Err(too_short_err());
305        };
306        let header = Nlmsghdr::from_slice(header);
307
308        let payload_start = self.buf_offset + Nlmsghdr::len();
309        self.buf_offset += header.len as usize;
310
311        match header.r#type as i32 {
312            libc::NLMSG_DONE | libc::NLMSG_ERROR => {
313                let Some(code) = packet.get(16..20) else {
314                    return Err(too_short_err());
315                };
316                let code = utils::parse_i32(code).unwrap();
317
318                let (echo_start, echo_end) =
319                    if code == 0 || header.r#type == libc::NLMSG_DONE as u16 {
320                        (20, 20)
321                    } else {
322                        let Some(echo_header) = packet.get(20..(20 + Nlmsghdr::len())) else {
323                            return Err(too_short_err());
324                        };
325                        let echo_header = Nlmsghdr::from_slice(echo_header);
326
327                        if echo_header.flags & libc::NLM_F_CAPPED as u16 == 0 {
328                            let start = echo_header.len;
329                            if packet.len() < start as usize + 20 {
330                                return Err(too_short_err());
331                            }
332
333                            (20 + 16, 20 + start as usize)
334                        } else {
335                            let ext_ack_start = 20 + Nlmsghdr::len();
336                            (ext_ack_start, ext_ack_start)
337                        }
338                    };
339
340                Ok((
341                    header.seq,
342                    header.r#type,
343                    Err(ReplyError {
344                        code: io::Error::from_raw_os_error(-code),
345                        request_bounds: (echo_start as u32, echo_end as u32),
346                        ext_ack_bounds: (echo_end as u32, self.buf_offset as u32),
347                        reply_buf: None,
348                        chained_name: None,
349                        lookup: |_, _, _| Default::default(),
350                    }),
351                ))
352            }
353            libc::NLMSG_NOOP => Ok((
354                header.seq,
355                header.r#type,
356                Err(io::Error::other("Received NLMSG_NOOP").into()),
357            )),
358            libc::NLMSG_OVERRUN => Ok((
359                header.seq,
360                header.r#type,
361                Err(io::Error::other("Received NLMSG_OVERRUN").into()),
362            )),
363            _ => Ok((
364                header.seq,
365                header.r#type,
366                Ok((payload_start, self.buf_offset)),
367            )),
368        }
369    }
370}
371
372pub struct NetlinkReply<'sock, Request: NetlinkRequest> {
373    inner: NetlinkReplyInner,
374    sock: &'sock mut Socket,
375    buf: &'sock mut Arc<[u8; RECV_BUF_SIZE]>,
376    seq: u32,
377    done: bool,
378    phantom: PhantomData<Request>,
379}
380
381impl<Request: NetlinkRequest> NetlinkReply<'_, Request> {
382    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
383    pub async fn recv_one(&mut self) -> Result<Request::ReplyType<'_>, ReplyError> {
384        if let Some(res) = self.recv().await {
385            return res;
386        }
387        Err(io::Error::other("Reply didn't contain data").into())
388    }
389
390    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
391    pub async fn recv_ack(&mut self) -> Result<(), ReplyError> {
392        if let Some(res) = self.recv().await {
393            res?;
394            return Err(io::Error::other("Reply isn't just an ack").into());
395        }
396        Ok(())
397    }
398
399    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
400    pub async fn recv(&mut self) -> Option<Result<Request::ReplyType<'_>, ReplyError>> {
401        if self.done {
402            return None;
403        }
404
405        let buf = Arc::make_mut(self.buf);
406
407        loop {
408            match self.inner.recv(self.sock, buf).await {
409                Err(io_err) => {
410                    self.done = true;
411                    return Some(Err(io_err.into()));
412                }
413                Ok((seq, _type, res)) => {
414                    if seq != self.seq {
415                        continue;
416                    }
417                    return match res {
418                        Ok((l, r)) => Some(Ok(Request::decode_reply(&self.buf[l..r]))),
419                        Err(mut err) => {
420                            self.done = true;
421                            if err.code.raw_os_error().unwrap() == 0 {
422                                None
423                            } else {
424                                if err.has_context() {
425                                    err.lookup = Request::lookup;
426                                    err.reply_buf = Some(self.buf.clone());
427                                }
428                                Some(Err(err))
429                            }
430                        }
431                    };
432                }
433            };
434        }
435    }
436}
437
438#[cfg(test)]
439mod test {
440    use super::*;
441
442    #[allow(unused)]
443    trait SpawnCompatible: Send {}
444    impl<'a> SpawnCompatible for NetlinkSocket {}
445}