Skip to main content

netlink_socket2/
multicast.rs

1use std::{io, os::fd::AsRawFd, sync::Arc};
2
3use netlink_bindings::utils;
4
5use crate::{NetlinkReplyInner, NetlinkSocket, ReplyError, Socket, RECV_BUF_SIZE};
6
7#[derive(Debug, Clone)]
8pub struct MulticastRecv {
9    pub multicast_group: u32,
10    pub message_type: u16,
11}
12
13pub struct MulticastSocketRaw {
14    buf: Arc<[u8; RECV_BUF_SIZE]>,
15    sock: Socket,
16    reply: NetlinkReplyInner,
17    last_group: Option<u32>,
18}
19
20impl MulticastSocketRaw {
21    pub fn new(protonum: u16) -> io::Result<Self> {
22        let sock = NetlinkSocket::get_socket_new(protonum)?;
23
24        // Enable multicast group number via recvmsg ancillary messages
25        let res = unsafe {
26            libc::setsockopt(
27                sock.as_raw_fd(),
28                libc::SOL_NETLINK,
29                libc::NETLINK_PKTINFO,
30                &1u32 as *const u32 as *const libc::c_void,
31                4,
32            )
33        };
34        if res < 0 {
35            return Err(io::Error::from_raw_os_error(-res));
36        }
37
38        let mut buf: libc::sockaddr_nl = unsafe { std::mem::zeroed() };
39        buf.nl_family = libc::AF_NETLINK as u16;
40        buf.nl_groups = 0;
41
42        let res = unsafe {
43            libc::bind(
44                sock.as_raw_fd(),
45                &buf as *const _ as *const libc::sockaddr,
46                std::mem::size_of_val(&buf) as libc::socklen_t,
47            )
48        };
49        if res < 0 {
50            return Err(io::Error::last_os_error());
51        }
52
53        Ok(Self {
54            buf: Arc::new([0u8; RECV_BUF_SIZE]),
55            sock,
56            reply: NetlinkReplyInner {
57                buf_offset: 0,
58                buf_read: 0,
59            },
60            last_group: None,
61        })
62    }
63
64    pub fn listen(&mut self, group_id: u32) -> io::Result<()> {
65        let res = unsafe {
66            libc::setsockopt(
67                self.sock.as_raw_fd(),
68                libc::SOL_NETLINK,
69                libc::NETLINK_ADD_MEMBERSHIP,
70                &group_id as *const u32 as *const libc::c_void,
71                4,
72            )
73        };
74        if res < 0 {
75            return Err(io::Error::last_os_error());
76        }
77
78        Ok(())
79    }
80
81    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
82    pub async fn recv(&mut self) -> Result<(MulticastRecv, &[u8]), ReplyError> {
83        let buf = Arc::make_mut(&mut self.buf);
84
85        loop {
86            if self.reply.buf_offset == self.reply.buf_read {
87                let read = Self::read_buf(&self.sock, buf, &mut self.last_group).await?;
88                self.reply.buf_read = read;
89                self.reply.buf_offset = 0;
90            }
91
92            match self.reply.parse_next(buf).await {
93                Err(io_err) => {
94                    return Err(io_err.into());
95                }
96                Ok((_seq, message_type, res)) => {
97                    // Seq number is mostly, but not always, 0. For example, removing a network
98                    // interface on linux 6.1 creates a multicast notification with the same seq
99                    // number as in the request. We don't allow sending requests on the multicast
100                    // socket, so the only incoming messages here are notifications.
101
102                    let Some(multicast_group) = self.last_group else {
103                        continue;
104                    };
105
106                    match res {
107                        Ok((l, r)) => {
108                            return Ok((
109                                MulticastRecv {
110                                    multicast_group,
111                                    message_type,
112                                },
113                                &self.buf[l..r],
114                            ));
115                        }
116                        Err(mut err) => {
117                            if err.code.raw_os_error().unwrap() == 0 {
118                                continue;
119                            }
120
121                            if err.has_context() {
122                                // err.lookup = Request::lookup;
123                                err.reply_buf = Some(self.buf.clone());
124                            }
125
126                            return Err(err);
127                        }
128                    };
129                }
130            };
131        }
132    }
133
134    #[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
135    async fn read_buf(
136        sock: &Socket,
137        buf: &mut [u8],
138        last_group: &mut Option<u32>,
139    ) -> Result<usize, ReplyError> {
140        loop {
141            let mut addr: libc::sockaddr_nl = unsafe { std::mem::zeroed() };
142            let mut iov = libc::iovec {
143                iov_base: buf.as_mut_ptr() as *mut libc::c_void,
144                iov_len: buf.len(),
145            };
146
147            let mut control_buf = [0u8; 128];
148
149            // Use zeroed init + field assignment to avoid private-field struct
150            // literal restrictions on musl targets (__pad1, __pad2 in msghdr).
151            let mut msghdr: libc::msghdr = unsafe { std::mem::zeroed() };
152            msghdr.msg_name = &mut addr as *mut libc::sockaddr_nl as *mut libc::c_void;
153            msghdr.msg_namelen = std::mem::size_of_val(&addr) as u32;
154            msghdr.msg_iov = &mut iov as *mut libc::iovec;
155            msghdr.msg_iovlen = 1;
156            msghdr.msg_control = control_buf.as_mut_ptr() as *mut libc::c_void;
157            // msg_controllen is usize on glibc but u32 on musl - let the compiler infer.
158            msghdr.msg_controllen = control_buf.len() as _;
159            msghdr.msg_flags = 0;
160
161            let do_recvmsg = || unsafe {
162                let res = libc::recvmsg(sock.as_raw_fd(), &mut msghdr, 0);
163                if res < 0 {
164                    return Err(io::Error::last_os_error());
165                }
166                Ok(res)
167            };
168
169            // Tokio attempts to be clever and doesn't poll the fd if it hasn't seen EAGAIN first hand
170            #[cfg(feature = "tokio")]
171            let read = sock
172                .async_io(tokio::io::Interest::READABLE, do_recvmsg)
173                .await?;
174
175            #[cfg(not(feature = "tokio"))]
176            let read = match { do_recvmsg }() {
177                #[cfg(feature = "async")]
178                Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
179                    sock.readable().await?;
180                    continue;
181                }
182                Err(err) => return Err(err.into()),
183                Ok(read) => read,
184            };
185
186            *last_group = None;
187            unsafe {
188                let msghdr_ptr = &msghdr as *const libc::msghdr;
189                let mut cmsg_ptr: *const libc::cmsghdr = libc::CMSG_FIRSTHDR(msghdr_ptr);
190                while !cmsg_ptr.is_null() {
191                    let libc::cmsghdr {
192                        cmsg_len,
193                        cmsg_level,
194                        cmsg_type,
195                        .. // ignore __pad1 on musl
196                    } = *cmsg_ptr;
197
198                    match (cmsg_level, cmsg_type) {
199                        (libc::SOL_NETLINK, libc::NETLINK_PKTINFO) => {
200                            let data = std::slice::from_raw_parts(
201                                libc::CMSG_DATA(cmsg_ptr),
202                                cmsg_len as usize - libc::CMSG_LEN(0) as usize,
203                            );
204                            *last_group = Some(utils::parse_u32(&data[..4]).unwrap());
205                        }
206                        _ => {}
207                    }
208
209                    cmsg_ptr = libc::CMSG_NXTHDR(msghdr_ptr, cmsg_ptr);
210                }
211            }
212
213            return Ok(read as usize);
214        }
215    }
216}