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
41pub 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 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 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 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 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}