Skip to main content

netlink_socket2/
sock.rs

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