1#![allow(clippy::arithmetic_side_effects)]
2
3use {
4 libc::{
5 AF_INET, AF_INET6, AF_NETLINK, IFLA_INFO_DATA, IFLA_INFO_KIND, IFLA_LINKINFO, MSG_DONTWAIT,
6 MSG_TRUNC, NDA_DST, NDA_LLADDR, NETLINK_EXT_ACK, NETLINK_GET_STRICT_CHK, NETLINK_ROUTE,
7 NLA_ALIGNTO, NLA_TYPE_MASK, NLM_F_DUMP, NLM_F_DUMP_INTR, NLM_F_MULTI, NLM_F_REQUEST,
8 NLMSG_DONE, NLMSG_ERROR, RTA_DST, RTA_GATEWAY, RTA_IIF, RTA_OIF, RTA_PREFSRC, RTA_PRIORITY,
9 RTA_TABLE, RTM_GETLINK, RTM_GETNEIGH, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWNEIGH,
10 RTM_NEWROUTE, SO_RCVBUF, SOCK_RAW, SOL_NETLINK, SOL_SOCKET, nlattr, nlmsgerr, nlmsghdr,
11 recv, send, setsockopt, sockaddr_nl, socket,
12 },
13 std::{
14 collections::HashMap,
15 ffi::CStr,
16 io, mem,
17 net::{IpAddr, Ipv4Addr, Ipv6Addr},
18 os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
19 ptr, slice,
20 },
21 thiserror::Error,
22};
23
24const NETLINK_RCVBUF_SIZE: i32 = 1 << 16;
25const NLA_HDR_LEN: usize = align_to(mem::size_of::<nlattr>(), NLA_ALIGNTO as usize);
26
27const IFLA_MTU: u16 = 4;
29
30const IFLA_GRE_LOCAL: u16 = 6;
32const IFLA_GRE_REMOTE: u16 = 7;
33const IFLA_GRE_TTL: u16 = 8;
34const IFLA_GRE_TOS: u16 = 9;
35const IFLA_GRE_PMTUDISC: u16 = 10;
36
37const IFLA_VLAN_ID: u16 = 1;
39const IFLA_VLAN_PROTOCOL: u16 = 5;
40
41#[repr(C)]
42#[allow(non_camel_case_types)]
43struct ifinfomsg {
44 ifi_family: u8,
45 __ifi_pad: u8,
46 ifi_type: u16,
47 ifi_index: u32,
48 ifi_flags: u32,
49 ifi_change: u32,
50}
51
52pub struct NetlinkSocket {
53 sock: OwnedFd,
54 _nl_pid: u32,
55}
56
57impl NetlinkSocket {
58 fn open() -> Result<Self, io::Error> {
59 let sock = unsafe { socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) };
61 if sock < 0 {
62 return Err(io::Error::last_os_error());
63 }
64 let sock = unsafe { OwnedFd::from_raw_fd(sock) };
66
67 let enable = 1i32;
68 for opt in [NETLINK_EXT_ACK, NETLINK_GET_STRICT_CHK] {
69 if unsafe {
71 setsockopt(
72 sock.as_raw_fd(),
73 SOL_NETLINK,
74 opt,
75 &enable as *const _ as *const _,
76 mem::size_of::<i32>() as u32,
77 )
78 } < 0
79 {
80 return Err(io::Error::last_os_error());
81 }
82 }
83 Ok(Self { sock, _nl_pid: 0 })
84 }
85
86 fn send(&self, msg: &[u8]) -> Result<(), io::Error> {
87 if unsafe {
88 send(
89 self.sock.as_raw_fd(),
90 msg.as_ptr() as *const _,
91 msg.len(),
92 0,
93 )
94 } < 0
95 {
96 return Err(io::Error::last_os_error());
97 }
98 Ok(())
99 }
100
101 pub(crate) fn recv(&self) -> Result<Vec<NetlinkMessage>, io::Error> {
102 self.recv_with_flags(0)
103 }
104
105 pub(crate) fn recv_nonblocking(&self) -> Result<Option<Vec<NetlinkMessage>>, io::Error> {
106 match self.recv_with_flags(MSG_DONTWAIT) {
107 Ok(messages) => Ok(Some(messages)),
108 Err(e)
109 if e.raw_os_error()
110 .is_some_and(|errno| errno == libc::EAGAIN || errno == libc::EWOULDBLOCK) =>
111 {
112 Ok(None)
113 }
114 Err(e) => Err(e),
115 }
116 }
117
118 fn recv_with_flags(&self, flags: i32) -> Result<Vec<NetlinkMessage>, io::Error> {
119 let mut buf = [0u8; 8 * 1024]; let mut messages = Vec::new();
125 let mut multipart = true;
126 'out: while multipart {
127 multipart = false;
128 let len = unsafe {
130 recv(
131 self.sock.as_raw_fd(),
132 buf.as_mut_ptr() as *mut _,
133 buf.len(),
134 flags | MSG_TRUNC,
135 )
136 };
137 if len < 0 {
138 return Err(io::Error::last_os_error());
139 }
140 if len == 0 {
141 break;
142 }
143
144 let len = len as usize;
145 if len > buf.len() {
146 return Err(io::Error::other("netlink datagram truncated"));
147 }
148 let mut offset = 0;
149 while offset < len {
150 let message = NetlinkMessage::read(&buf[offset..])?;
151 offset += align_to(message.header.nlmsg_len as usize, NLMSG_ALIGNTO as usize);
152 multipart = message.header.nlmsg_flags & NLM_F_MULTI as u16 != 0;
153 if message.header.nlmsg_flags & NLM_F_DUMP_INTR as u16 != 0 {
154 return Err(io::Error::new(
155 io::ErrorKind::Interrupted,
156 "netlink dump interrupted",
157 ));
158 }
159 match message.header.nlmsg_type as i32 {
160 NLMSG_ERROR => {
161 let err = message.error.unwrap();
162 if err.error == 0 {
163 continue;
165 }
166 return Err(io::Error::from_raw_os_error(-err.error));
167 }
168 NLMSG_DONE => break 'out,
169 _ => messages.push(message),
170 }
171 }
172 }
173
174 Ok(messages)
175 }
176
177 pub fn bind(groups: u32) -> Result<Self, io::Error> {
180 let sock = Self::open()?;
181
182 let mut addr: sockaddr_nl = unsafe { mem::zeroed() };
184 addr.nl_family = AF_NETLINK as u16;
185 addr.nl_groups = groups;
186 if unsafe {
187 libc::bind(
188 sock.as_raw_fd(),
189 &addr as *const _ as *const _,
190 mem::size_of::<sockaddr_nl>() as u32,
191 )
192 } < 0
193 {
194 return Err(io::Error::last_os_error());
195 }
196
197 unsafe {
198 setsockopt(
199 sock.as_raw_fd(),
200 SOL_SOCKET,
201 SO_RCVBUF,
202 &NETLINK_RCVBUF_SIZE as *const _ as *const _,
203 mem::size_of::<i32>() as u32,
204 );
205 }
206
207 Ok(sock)
208 }
209
210 #[inline]
211 pub fn as_raw_fd(&self) -> RawFd {
212 self.sock.as_raw_fd()
213 }
214}
215
216#[derive(Debug, Clone)]
217pub struct NetlinkMessage {
218 pub(crate) header: nlmsghdr,
219 data: Vec<u8>,
220 error: Option<nlmsgerr>,
221}
222
223impl NetlinkMessage {
224 fn read(buf: &[u8]) -> Result<Self, io::Error> {
225 if mem::size_of::<nlmsghdr>() > buf.len() {
226 return Err(io::Error::other("buffer smaller than nlmsghdr"));
227 }
228
229 let header = unsafe { ptr::read_unaligned(buf.as_ptr() as *const nlmsghdr) };
231 let msg_len = header.nlmsg_len as usize;
232 if msg_len < mem::size_of::<nlmsghdr>() || msg_len > buf.len() {
233 return Err(io::Error::other("invalid nlmsg_len"));
234 }
235
236 let data_offset = align_to(mem::size_of::<nlmsghdr>(), NLMSG_ALIGNTO as usize);
237 if data_offset >= buf.len() {
238 return Err(io::Error::other("need more data"));
239 }
240
241 let (data, error) = if header.nlmsg_type == NLMSG_ERROR as u16 {
242 if data_offset + mem::size_of::<nlmsgerr>() > buf.len() {
243 return Err(io::Error::other(
244 "NLMSG_ERROR but not enough space for nlmsgerr",
245 ));
246 }
247 (
248 Vec::new(),
249 Some(unsafe {
251 ptr::read_unaligned(buf[data_offset..].as_ptr() as *const nlmsgerr)
252 }),
253 )
254 } else {
255 (buf[data_offset..msg_len].to_vec(), None)
256 };
257
258 Ok(Self {
259 header,
260 data,
261 error,
262 })
263 }
264}
265
266const fn align_to(v: usize, align: usize) -> usize {
267 (v + (align - 1)) & !(align - 1)
268}
269
270struct NlAttrsIterator<'a> {
271 attrs: &'a [u8],
272 offset: usize,
273}
274
275impl<'a> NlAttrsIterator<'a> {
276 fn new(attrs: &'a [u8]) -> Self {
277 Self { attrs, offset: 0 }
278 }
279}
280
281impl<'a> Iterator for NlAttrsIterator<'a> {
282 type Item = Result<NlAttr<'a>, NlAttrError>;
283
284 fn next(&mut self) -> Option<Self::Item> {
285 let buf = &self.attrs[self.offset..];
286 if buf.is_empty() {
287 return None;
288 }
289
290 if NLA_HDR_LEN > buf.len() {
291 self.offset = buf.len();
292 return Some(Err(NlAttrError::InvalidBufferLength {
293 size: buf.len(),
294 expected: NLA_HDR_LEN,
295 }));
296 }
297
298 let attr = unsafe { ptr::read_unaligned(buf.as_ptr() as *const nlattr) };
299 let len = attr.nla_len as usize;
300 let align_len = align_to(len, NLA_ALIGNTO as usize);
301 if len < NLA_HDR_LEN {
302 return Some(Err(NlAttrError::InvalidHeaderLength(len)));
303 }
304 if align_len > buf.len() {
305 return Some(Err(NlAttrError::InvalidBufferLength {
306 size: buf.len(),
307 expected: align_len,
308 }));
309 }
310
311 let data = &buf[NLA_HDR_LEN..len];
312
313 self.offset += align_len;
314 Some(Ok(NlAttr { header: attr, data }))
315 }
316}
317
318fn parse_attrs(buf: &[u8]) -> Result<HashMap<u16, NlAttr<'_>>, NlAttrError> {
319 let mut attrs = HashMap::new();
320 for attr in NlAttrsIterator::new(buf) {
321 let attr = attr?;
322 attrs.insert(attr.header.nla_type & NLA_TYPE_MASK as u16, attr);
323 }
324 Ok(attrs)
325}
326
327#[derive(Clone)]
328struct NlAttr<'a> {
329 header: nlattr,
330 data: &'a [u8],
331}
332
333#[derive(Debug, Error, PartialEq, Eq)]
334enum NlAttrError {
335 #[error("invalid buffer size `{size}`, expected `{expected}`")]
336 InvalidBufferLength { size: usize, expected: usize },
337
338 #[error("invalid nlattr header length `{0}`")]
339 InvalidHeaderLength(usize),
340}
341
342impl From<NlAttrError> for io::Error {
343 fn from(e: NlAttrError) -> Self {
344 Self::other(e)
345 }
346}
347
348fn bytes_of<T>(val: &T) -> &[u8] {
349 let size = mem::size_of::<T>();
350 unsafe { slice::from_raw_parts(slice::from_ref(val).as_ptr().cast(), size) }
351}
352
353const NLMSG_ALIGNTO: u32 = 4;
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq)]
356pub struct MacAddress(pub [u8; 6]);
357
358impl MacAddress {
359 pub fn new(bytes: [u8; 6]) -> Self {
360 MacAddress(bytes)
361 }
362
363 pub fn as_bytes(&self) -> &[u8; 6] {
364 &self.0
365 }
366}
367
368impl std::fmt::Display for MacAddress {
369 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
370 write!(
371 f,
372 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
373 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
374 )
375 }
376}
377
378#[derive(Debug, Clone, PartialEq, Eq)]
382pub struct GreTunnelInfo {
383 pub local: IpAddr,
385 pub remote: IpAddr,
387 pub ttl: u8,
388 pub tos: u8,
389 pub pmtudisc: u8,
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
395pub struct VlanLinkInfo {
396 pub vid: u16,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq)]
401pub struct InterfaceInfo {
402 pub if_index: u32,
403 pub mtu: u32,
404 pub gre_tunnel: Option<GreTunnelInfo>,
405 pub vlan_link: Option<VlanLinkInfo>,
406}
407
408impl InterfaceInfo {
409 pub fn is_gre(&self) -> bool {
410 self.gre_tunnel.is_some()
411 }
412}
413
414#[repr(C)]
415struct InterfaceRequest {
416 header: nlmsghdr,
417 ifi: ifinfomsg,
418}
419
420pub fn netlink_get_interfaces(family: u8) -> Result<Vec<InterfaceInfo>, io::Error> {
421 let sock = NetlinkSocket::open()?;
422
423 let mut req = unsafe { mem::zeroed::<InterfaceRequest>() };
425
426 let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<ifinfomsg>();
427 req.header = nlmsghdr {
428 nlmsg_len: nlmsg_len as u32,
429 nlmsg_flags: (NLM_F_REQUEST | NLM_F_DUMP) as u16,
430 nlmsg_type: RTM_GETLINK,
431 nlmsg_pid: 0,
432 nlmsg_seq: 1,
433 };
434
435 req.ifi.ifi_family = family;
436 sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?;
437
438 let mut interfaces = Vec::new();
439 for msg in sock.recv()? {
440 if msg.header.nlmsg_type != RTM_NEWLINK {
441 continue;
442 }
443
444 if let Some(if_info) = parse_rtm_ifinfomsg(&msg) {
445 interfaces.push(if_info);
446 }
447 }
448
449 Ok(interfaces)
450}
451
452pub(crate) fn parse_rtm_ifinfomsg(msg: &NetlinkMessage) -> Option<InterfaceInfo> {
453 if msg.data.len() < mem::size_of::<ifinfomsg>() {
454 return None;
455 }
456
457 let ifi = unsafe { ptr::read_unaligned(msg.data.as_ptr() as *const ifinfomsg) };
458 let Ok(attrs) = parse_attrs(&msg.data[mem::size_of::<ifinfomsg>()..]) else {
459 return None;
460 };
461
462 let mtu = attrs
463 .get(&IFLA_MTU)
464 .and_then(|a| u32_from_ne_bytes(a.data))?;
465
466 let gre_tunnel = parse_gre_tunnel_info_from_linkinfo(&attrs);
468 let vlan_link = parse_vlan_link_info_from_linkinfo(&attrs);
470 Some(InterfaceInfo {
471 if_index: ifi.ifi_index,
472 mtu,
473 gre_tunnel,
474 vlan_link,
475 })
476}
477
478fn parse_vlan_link_info_from_linkinfo(attrs: &HashMap<u16, NlAttr>) -> Option<VlanLinkInfo> {
480 let vlan = parse_linkinfo_data_for_kind(attrs, b"vlan")?;
481
482 if let Some(proto) = vlan.get(&IFLA_VLAN_PROTOCOL) {
485 let proto = u16::from_be_bytes(proto.data.get(..2)?.try_into().ok()?);
486 if proto != libc::ETH_P_8021Q as u16 {
487 return None;
488 }
489 }
490
491 let vid = vlan
492 .get(&IFLA_VLAN_ID)
493 .and_then(|a| u16_from_ne_bytes(a.data))?;
494 Some(VlanLinkInfo { vid })
495}
496
497fn parse_gre_tunnel_info_from_linkinfo(attrs: &HashMap<u16, NlAttr>) -> Option<GreTunnelInfo> {
499 let gre = parse_linkinfo_data_for_kind(attrs, b"gre")?;
500
501 let u8_from_bytes = |data: &[u8]| -> Option<u8> { data.first().copied() };
502
503 let local = gre
504 .get(&IFLA_GRE_LOCAL)
505 .and_then(|a| parse_ip_address(a.data, AF_INET as u8))?;
506 let remote = gre
507 .get(&IFLA_GRE_REMOTE)
508 .and_then(|a| parse_ip_address(a.data, AF_INET as u8))?;
509 let ttl = gre.get(&IFLA_GRE_TTL).and_then(|a| u8_from_bytes(a.data))?;
510 let tos = gre.get(&IFLA_GRE_TOS).and_then(|a| u8_from_bytes(a.data))?;
511 let pmtudisc = gre
512 .get(&IFLA_GRE_PMTUDISC)
513 .and_then(|a| u8_from_bytes(a.data))?;
514
515 Some(GreTunnelInfo {
516 local,
517 remote,
518 ttl,
519 tos,
520 pmtudisc,
521 })
522}
523
524fn parse_linkinfo_data_for_kind<'a>(
525 attrs: &HashMap<u16, NlAttr<'a>>,
526 expected_kind: &[u8],
527) -> Option<HashMap<u16, NlAttr<'a>>> {
528 let li = attrs.get(&IFLA_LINKINFO)?;
529 let info = parse_attrs(li.data).ok()?;
531 let kind_attr = info.get(&IFLA_INFO_KIND)?;
532 if kind_attr.data.is_empty() {
533 return None;
534 }
535 let kind = CStr::from_bytes_until_nul(kind_attr.data).ok()?;
536 if kind.to_bytes() != expected_kind {
537 return None;
538 }
539 let data_attr = info.get(&IFLA_INFO_DATA)?;
541 parse_attrs(data_attr.data).ok()
542}
543
544#[derive(Debug, Clone, Eq, PartialEq)]
546pub struct NeighborEntry {
547 pub destination: Option<IpAddr>,
549 pub lladdr: Option<MacAddress>,
551 pub ifindex: i32,
553 pub state: u16,
555}
556
557impl NeighborEntry {
558 #[inline]
559 pub fn key(&self) -> Option<(i32, Ipv4Addr)> {
560 match self.destination {
561 Some(IpAddr::V4(ip)) => Some((self.ifindex, ip)),
562 _ => None,
563 }
564 }
565}
566
567#[repr(C)]
568#[allow(non_camel_case_types)]
569struct ndmsg {
570 ndm_family: u8,
571 _ndm_pad1: u8,
572 _ndm_pad2: u16,
573 ndm_ifindex: i32,
574 ndm_state: u16,
575 _ndm_flags: u8,
576 _ndm_type: u8,
577}
578
579#[repr(C)]
580struct NeighRequest {
581 header: nlmsghdr,
582 ndm: ndmsg,
583}
584
585pub fn netlink_get_neighbors(
587 if_index: Option<u32>,
588 family: u8,
589) -> Result<Vec<NeighborEntry>, io::Error> {
590 let sock = NetlinkSocket::open()?;
591
592 let mut req = unsafe { mem::zeroed::<NeighRequest>() };
594
595 let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<ndmsg>();
596 req.header = nlmsghdr {
597 nlmsg_len: nlmsg_len as u32,
598 nlmsg_flags: (NLM_F_REQUEST | NLM_F_DUMP) as u16,
599 nlmsg_type: RTM_GETNEIGH,
600 nlmsg_pid: 0,
601 nlmsg_seq: 1,
602 };
603
604 req.ndm.ndm_family = family;
605 if let Some(idx) = if_index {
606 req.ndm.ndm_ifindex = idx as i32;
607 }
608
609 sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?;
610
611 let mut neighbors = Vec::new();
612
613 for msg in sock.recv()? {
614 if msg.header.nlmsg_type != RTM_NEWNEIGH {
615 continue;
616 }
617
618 if msg.data.len() < mem::size_of::<ndmsg>() {
619 continue;
620 }
621
622 if let Some(neighbor) = parse_rtm_newneigh(&msg, if_index) {
623 neighbors.push(neighbor);
624 }
625 }
626
627 Ok(neighbors)
628}
629
630pub fn parse_rtm_newneigh(msg: &NetlinkMessage, if_index: Option<u32>) -> Option<NeighborEntry> {
631 if msg.data.len() < mem::size_of::<ndmsg>() {
632 return None;
633 }
634 let nd_msg = unsafe { ptr::read_unaligned(msg.data.as_ptr() as *const ndmsg) };
635 if let Some(idx) = if_index
636 && nd_msg.ndm_ifindex != idx as i32
637 {
638 return None;
639 }
640 let Ok(attrs) = parse_attrs(&msg.data[mem::size_of::<ndmsg>()..]) else {
641 return None;
642 };
643 let mut neighbor = NeighborEntry {
644 destination: None,
645 lladdr: None,
646 ifindex: nd_msg.ndm_ifindex,
647 state: nd_msg.ndm_state,
648 };
649 if let Some(dst_attr) = attrs.get(&NDA_DST) {
650 neighbor.destination = parse_ip_address(dst_attr.data, nd_msg.ndm_family);
651 }
652 if let Some(lladdr_attr) = attrs.get(&NDA_LLADDR)
653 && lladdr_attr.data.len() >= 6
654 {
655 let mut mac = [0u8; 6];
656 mac.copy_from_slice(&lladdr_attr.data[0..6]);
657 neighbor.lladdr = Some(MacAddress(mac));
658 }
659 Some(neighbor)
660}
661
662#[derive(Clone)]
663pub struct RouteEntry {
664 pub destination: Option<IpAddr>,
665 pub gateway: Option<IpAddr>,
666 pub pref_src: Option<IpAddr>,
667 pub out_if_index: Option<i32>,
668 pub in_if_index: Option<i32>,
669 pub priority: Option<u32>,
670 pub table: Option<u32>,
671 pub protocol: u8,
672 pub scope: u8,
673 pub type_: u8,
674 pub family: u8,
675 pub dst_len: u8,
676 pub flags: u32,
677}
678
679#[repr(C)]
680#[allow(non_camel_case_types)]
681struct rtmsg {
682 rtm_family: u8,
683 rtm_dst_len: u8,
684 rtm_src_len: u8,
685 rtm_tos: u8,
686 rtm_table: u8,
687 rtm_protocol: u8,
688 rtm_scope: u8,
689 rtm_type: u8,
690 rtm_flags: u32,
691}
692
693#[repr(C)]
694struct RouteRequest {
695 header: nlmsghdr,
696 rtm: rtmsg,
697}
698
699fn parse_ip_address(data: &[u8], family: u8) -> Option<IpAddr> {
700 match family as i32 {
701 AF_INET if data.len() == 4 => Some(IpAddr::V4(Ipv4Addr::new(
702 data[0], data[1], data[2], data[3],
703 ))),
704 AF_INET6 if data.len() == 16 => {
705 let mut segments = [0u16; 8];
706 for i in 0..8 {
707 segments[i] = ((data[i * 2] as u16) << 8) | (data[i * 2 + 1] as u16);
708 }
709 Some(IpAddr::V6(Ipv6Addr::from(segments)))
710 }
711 _ => None,
712 }
713}
714
715pub fn netlink_get_routes(family: u8, table: u32) -> Result<Vec<RouteEntry>, io::Error> {
716 let sock = NetlinkSocket::open()?;
717
718 let mut req = unsafe { mem::zeroed::<RouteRequest>() };
720
721 let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<rtmsg>();
722 let table_attr_len = align_to(NLA_HDR_LEN + mem::size_of::<u32>(), NLA_ALIGNTO as usize);
723 req.header = nlmsghdr {
724 nlmsg_len: (nlmsg_len + table_attr_len) as u32,
725 nlmsg_flags: (NLM_F_REQUEST | NLM_F_DUMP) as u16,
726 nlmsg_type: RTM_GETROUTE,
727 nlmsg_pid: 0,
728 nlmsg_seq: 1,
729 };
730
731 req.rtm.rtm_family = family;
732
733 let mut req_buf = bytes_of(&req)[..nlmsg_len].to_vec();
734 push_nlattr(&mut req_buf, RTA_TABLE, &table);
735 sock.send(&req_buf)?;
736
737 let mut routes = Vec::new();
738
739 for msg in sock.recv()? {
740 if msg.header.nlmsg_type != RTM_NEWROUTE {
741 continue;
742 }
743 if let Some(route) = parse_rtm_newroute(&msg) {
744 debug_assert!(route.table == Some(table));
746 if route.table == Some(table) {
747 routes.push(route);
748 }
749 }
750 }
751
752 Ok(routes)
753}
754
755pub fn parse_rtm_newroute(msg: &NetlinkMessage) -> Option<RouteEntry> {
756 if msg.data.len() < mem::size_of::<rtmsg>() {
757 return None;
758 }
759
760 let rt_msg = unsafe { ptr::read_unaligned(msg.data.as_ptr() as *const rtmsg) };
761 let Ok(attrs) = parse_attrs(&msg.data[mem::size_of::<rtmsg>()..]) else {
762 return None;
763 };
764 let mut route = RouteEntry {
765 destination: None,
766 gateway: None,
767 pref_src: None,
768 out_if_index: None,
769 in_if_index: None,
770 priority: None,
771 table: Some(u32::from(rt_msg.rtm_table)),
772 protocol: rt_msg.rtm_protocol,
773 scope: rt_msg.rtm_scope,
774 type_: rt_msg.rtm_type,
775 family: rt_msg.rtm_family,
776 dst_len: rt_msg.rtm_dst_len,
777 flags: rt_msg.rtm_flags,
778 };
779 if let Some(dst_attr) = attrs.get(&RTA_DST) {
780 route.destination = parse_ip_address(dst_attr.data, rt_msg.rtm_family);
781 }
782 if let Some(gateway_attr) = attrs.get(&RTA_GATEWAY) {
783 route.gateway = parse_ip_address(gateway_attr.data, rt_msg.rtm_family);
784 }
785
786 if let Some(oif_attr) = attrs.get(&RTA_OIF) {
787 route.out_if_index = u32_from_ne_bytes(oif_attr.data).map(|i| i as i32);
788 }
789 if let Some(iif_attr) = attrs.get(&RTA_IIF) {
790 route.in_if_index = u32_from_ne_bytes(iif_attr.data).map(|i| i as i32);
791 }
792 if let Some(priority_attr) = attrs.get(&RTA_PRIORITY) {
793 route.priority = u32_from_ne_bytes(priority_attr.data);
794 }
795 if let Some(table_attr) = attrs.get(&RTA_TABLE) {
796 route.table = u32_from_ne_bytes(table_attr.data);
797 }
798 if let Some(prefsrc_attr) = attrs.get(&RTA_PREFSRC) {
799 route.pref_src = parse_ip_address(prefsrc_attr.data, rt_msg.rtm_family);
800 }
801 Some(route)
802}
803
804fn push_nlattr<T>(buf: &mut Vec<u8>, attr_type: u16, value: &T) {
805 let attr_len = NLA_HDR_LEN + mem::size_of::<T>();
806 let aligned_len = align_to(attr_len, NLA_ALIGNTO as usize);
807 let attr = nlattr {
808 nla_len: attr_len as u16,
809 nla_type: attr_type,
810 };
811 buf.extend_from_slice(bytes_of(&attr));
812 buf.extend_from_slice(bytes_of(value));
813 buf.resize(buf.len() + (aligned_len - attr_len), 0);
814}
815
816fn u32_from_ne_bytes(data: &[u8]) -> Option<u32> {
817 let bytes: [u8; 4] = data.get(..4)?.try_into().ok()?;
818 Some(u32::from_ne_bytes(bytes))
819}
820
821fn u16_from_ne_bytes(data: &[u8]) -> Option<u16> {
822 let bytes: [u8; 2] = data.get(..2)?.try_into().ok()?;
823 Some(u16::from_ne_bytes(bytes))
824}