xdp_util/
netlink.rs

1//! # Low-Level Netlink Interface
2//!
3//! ## Purpose
4//!
5//! This module provides functions to query the Linux kernel's networking subsystems via
6//! netlink sockets. It is used to fetch information such as network interface details,
7//! IP addresses, routes, and neighbor (ARP) entries.
8//!
9//! ## How it works
10//!
11//! It communicates with the kernel using a raw `NETLINK_ROUTE` socket. The `netlink-packet`
12//! crates are used to construct, serialize, and deserialize netlink messages. A generic
13//! `netlink` function handles the common pattern of sending a request and processing a
14//! potentially multi-part response. Specific functions like `get_ipv4_routes` and
15//! `get_neighbors` use this generic handler to fetch and parse different types of data.
16//!
17//! ## Main components
18//!
19//! - `netlink()`: A generic function for the netlink request/response message loop.
20//! - `get_links()`, `get_ipv4_routes()`, `get_neighbors()`, `get_ipv4_address()`: Public
21//!   functions for querying specific kernel data.
22//! - `Link`, `Ipv4Route`, `Neighbor`: Structs that represent the networking objects
23//!   retrieved from the kernel.
24
25use netlink_packet_core::{
26    NLM_F_DUMP, NLM_F_REQUEST, NetlinkDeserializable, NetlinkMessage, NetlinkPayload,
27    NetlinkSerializable,
28};
29use netlink_packet_route::{
30    AddressFamily, RouteNetlinkMessage,
31    address::{AddressAttribute, AddressMessage},
32    link::{LinkAttribute, LinkMessage},
33    neighbour::{NeighbourAddress, NeighbourAttribute, NeighbourMessage},
34    route::{RouteAddress, RouteAttribute, RouteMessage},
35};
36use netlink_sys::{Socket, SocketAddr};
37use std::io;
38use std::net::{IpAddr, Ipv4Addr};
39
40/// Represents a network interface link.
41#[derive(Clone, Debug, Default)]
42pub struct Link {
43    /// The interface index.
44    pub if_index: u32,
45    /// The interface name (e.g., "eth0").
46    pub name: String,
47    /// The Maximum Transmission Unit (MTU) of the interface.
48    pub mtu: u32,
49    /// The MAC address of the interface.
50    pub mac: [u8; 6],
51}
52
53/// Represents an IPv4 route.
54#[derive(Clone, Copy, Debug)]
55pub struct Ipv4Route {
56    /// The destination prefix length (CIDR).
57    pub dest_prefix: u8,
58    /// The destination IPv4 address.
59    pub destination: Ipv4Addr,
60    /// The gateway IP address, if any.
61    pub gateway: Option<Ipv4Addr>,
62    /// The index of the output interface.
63    pub out_if_index: Option<u32>,
64    /// The priority of the route. Lower values are preferred.
65    pub priority: u32,
66}
67
68/// Represents a neighbor (ARP) entry.
69#[derive(Clone, Copy, Debug)]
70pub struct Neighbor {
71    /// The neighbor's IPv4 address.
72    pub ip: Ipv4Addr,
73    /// The neighbor's MAC address.
74    pub mac: [u8; 6],
75    /// The index of the interface this neighbor is associated with.
76    pub if_index: u32,
77}
78
79/// Represents a default gateway.
80#[derive(Clone, Copy, Debug)]
81pub struct Gateway {
82    /// The gateway's IPv4 address.
83    pub ip: Ipv4Addr,
84    /// The priority of the route to this gateway.
85    pub priority: u32,
86    /// The index of the output interface for this gateway.
87    pub if_index: u32,
88}
89
90/// A generic function to send a netlink request and parse the response.
91///
92/// This function handles the low-level details of creating a netlink socket,
93/// sending a request message, and iterating through the potentially multi-part
94/// response from the kernel.
95///
96/// # How it works
97///
98/// It opens a `NETLINK_ROUTE` socket and binds it. The provided request message
99/// is serialized and sent to the kernel. It then enters a loop, receiving
100/// response messages from the socket. Each message is deserialized and passed to
101/// the provided closure `f` for processing. The loop continues until all parts
102/// of the kernel's response have been read. The results from the closure are
103/// collected into a `Vec` and returned.
104pub fn netlink<T, F, R>(mut req: NetlinkMessage<T>, f: F) -> Result<Vec<R>, io::Error>
105where
106    T: NetlinkSerializable + NetlinkDeserializable,
107    F: Fn(NetlinkMessage<T>) -> Result<Option<R>, io::Error>,
108{
109    let mut socket = Socket::new(netlink_sys::constants::NETLINK_ROUTE)?;
110    let kernel_addr = SocketAddr::new(0, 0);
111    socket.bind(&kernel_addr)?;
112    req.header.flags = NLM_F_REQUEST | NLM_F_DUMP;
113    let mut send_buf = vec![0u8; req.buffer_len()];
114    req.finalize();
115    req.serialize(&mut send_buf);
116    if socket.send(send_buf.as_slice(), 0)? != send_buf.len() {
117        return Err(io::Error::other("Failed to send request"));
118    };
119
120    let (recv_buf, _) = socket.recv_from_full()?;
121    let mut buffer_view = &recv_buf[..];
122    let mut result = Vec::new();
123    while !buffer_view.is_empty() {
124        let msg = NetlinkMessage::<T>::deserialize(buffer_view).map_err(io::Error::other)?;
125        let len = msg.header.length as usize;
126        if let Some(r) = f(msg)? {
127            result.push(r);
128        }
129        if len == 0 || len > buffer_view.len() {
130            return Err(io::Error::other(
131                "Received a malformed netlink message (invalid length)".to_string(),
132            ));
133        }
134        buffer_view = &buffer_view[len..];
135    }
136    Ok(result)
137}
138
139/// Retrieves a list of neighbor (ARP) entries from the kernel.
140///
141/// Optionally filters neighbors by a specific interface index.
142///
143/// # How it works
144///
145/// It constructs a `GetNeighbour` netlink request and sends it using the generic
146/// `netlink` function. The response parsing closure extracts neighbor details
147/// like IP address, MAC address, and interface index from each `NewNeighbour`
148/// message. If an `if_index` is provided, it filters the results to include
149/// only neighbors on that interface.
150pub fn get_neighbors(if_index: Option<u32>) -> Result<Vec<Neighbor>, io::Error> {
151    let mut req_msg = NeighbourMessage::default();
152    req_msg.header.family = AddressFamily::Inet;
153    let req = NetlinkMessage::from(RouteNetlinkMessage::GetNeighbour(req_msg));
154    netlink(req, |msg| match msg.payload {
155        NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewNeighbour(neigh_msg)) => {
156            if if_index.is_some_and(|x| x != neigh_msg.header.ifindex) {
157                return Ok(None); // Skip neighbors not matching the interface index
158            }
159            let mut neighbor = Neighbor {
160                ip: Ipv4Addr::UNSPECIFIED,
161                mac: [0; 6],
162                if_index: neigh_msg.header.ifindex,
163            };
164            for attr in neigh_msg.attributes.iter() {
165                match attr {
166                    NeighbourAttribute::Destination(NeighbourAddress::Inet(ip)) => {
167                        neighbor.ip = *ip;
168                    }
169                    NeighbourAttribute::LinkLocalAddress(mac) => {
170                        if mac.len() == 6 {
171                            neighbor.mac = mac[0..6].try_into().unwrap();
172                        } else {
173                            return Ok(None);
174                        }
175                    }
176                    //NeighbourAttribute::CacheInfo(_nfo) => {
177                    //
178                    //}
179                    _ => {}
180                }
181            }
182            Ok(Some(neighbor))
183        }
184        _ => Ok(None),
185    })
186}
187
188/// Retrieves a list of IPv4 routes from the kernel.
189///
190/// Optionally filters routes by a specific output interface index.
191///
192/// # How it works
193///
194/// It constructs a `GetRoute` netlink request for the IPv4 address family and
195/// sends it using the generic `netlink` function. The response parsing closure
196/// processes each `NewRoute` message, extracting attributes like destination,
197/// gateway, and priority. If an `if_index` is provided, it filters the results
198/// to include only routes that use that output interface.
199pub fn get_ipv4_routes(if_index: Option<u32>) -> Result<Vec<Ipv4Route>, io::Error> {
200    let mut req_msg = RouteMessage::default();
201    req_msg.header.address_family = AddressFamily::Inet;
202    //req_msg.header.destination_prefix_length = 32;
203    let req = NetlinkMessage::from(RouteNetlinkMessage::GetRoute(req_msg));
204    netlink(req, |msg| {
205        match msg.payload {
206            NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewRoute(ref route_msg)) => {
207                let mut route = Ipv4Route {
208                    dest_prefix: route_msg.header.destination_prefix_length,
209                    destination: Ipv4Addr::UNSPECIFIED,
210                    gateway: None,
211                    out_if_index: None,
212                    priority: 0,
213                };
214                for a in route_msg.attributes.iter() {
215                    match a {
216                        RouteAttribute::Destination(RouteAddress::Inet(dest)) => {
217                            route.destination = *dest;
218                        }
219                        RouteAttribute::Gateway(RouteAddress::Inet(gateway)) => {
220                            route.gateway = Some(*gateway);
221                        }
222                        RouteAttribute::Oif(ifd) => {
223                            if if_index.is_some_and(|x| x != *ifd) {
224                                break; // Skip routes not matching the interface IP
225                            }
226                            route.out_if_index = Some(*ifd);
227                        }
228                        RouteAttribute::Priority(priority) => {
229                            route.priority = *priority;
230                        }
231                        _ => {}
232                    }
233                }
234                Ok(route.out_if_index.and(Some(route)))
235            }
236            _ => Ok(None),
237        }
238    })
239}
240
241/// Retrieves IPv4 addresses associated with network interfaces.
242///
243/// Optionally filters addresses by a specific interface index.
244pub fn get_ipv4_address(if_index: Option<u32>) -> Result<Vec<(Ipv4Addr, u32)>, io::Error> {
245    let mut req_msg = AddressMessage::default();
246    req_msg.header.family = AddressFamily::Inet;
247    let req = NetlinkMessage::from(RouteNetlinkMessage::GetAddress(req_msg));
248    netlink(req, |msg| {
249        match msg.payload {
250            NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewAddress(ref addr_msg)) => {
251                if if_index.is_some_and(|x| x != addr_msg.header.index) {
252                    return Ok(None); // Skip addresses not matching the interface index
253                }
254                for attr in addr_msg.attributes.iter() {
255                    if let AddressAttribute::Address(IpAddr::V4(ip)) = attr {
256                        return Ok(Some((*ip, addr_msg.header.index)));
257                    }
258                }
259                Ok(None)
260            }
261            _ => Ok(None),
262        }
263    })
264}
265
266/// Retrieves a list of all network interfaces (links) from the kernel.
267///
268/// # How it works
269///
270/// It constructs a `GetLink` netlink request and sends it using the generic
271/// `netlink` function. The response parsing closure processes each `NewLink`
272/// message, extracting attributes like interface index, name, MTU, and MAC
273/// address to build a `Link` struct for each interface.
274pub fn get_links() -> Result<Vec<Link>, io::Error> {
275    let req_msg = LinkMessage::default();
276    let req = NetlinkMessage::from(RouteNetlinkMessage::GetLink(req_msg));
277    netlink(req, |msg| match msg.payload {
278        NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewLink(ref link_msg)) => {
279            let mut link = Link {
280                if_index: link_msg.header.index,
281                ..Default::default()
282            };
283            for attr in link_msg.attributes.iter() {
284                match attr {
285                    LinkAttribute::IfName(name) => {
286                        link.name = name.to_string();
287                    }
288                    LinkAttribute::Mtu(mtu) => {
289                        link.mtu = *mtu;
290                    }
291                    LinkAttribute::Address(mac) => {
292                        if mac.len() == 6 {
293                            link.mac = mac[0..6]
294                                .try_into()
295                                .map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?;
296                        } else {
297                            return Ok(None);
298                        }
299                    }
300                    _ => {}
301                }
302            }
303            Ok(Some(link))
304        }
305        _ => Ok(None),
306    })
307}
308
309/// Finds the default gateway from a list of IPv4 routes.
310///
311/// The default gateway is identified as the route with a destination prefix of 0
312/// and the lowest priority value (metric).
313///
314/// # How it works
315///
316/// It iterates through the provided slice of `Ipv4Route` structs. It looks for
317/// routes with a `dest_prefix` of 0, which signifies a default route. Among
318/// these, it selects the one with the lowest `priority` value, as is standard
319/// for route metrics. It returns a `Gateway` struct containing the gateway's IP,
320/// priority, and output interface index.
321pub fn find_default_gateway(routes: &[Ipv4Route]) -> Option<Gateway> {
322    routes
323        .iter()
324        .fold(None, |acc, x| {
325            if let Ipv4Route {
326                gateway: Some(gw),
327                priority,
328                out_if_index: Some(oif),
329                dest_prefix: 0,
330                ..
331            } = x
332            {
333                match acc {
334                    None => Some((*gw, *priority, *oif)),
335                    Some((_, acc_priority, _)) if *priority < acc_priority => {
336                        Some((*gw, *priority, *oif))
337                    }
338                    _ => acc,
339                }
340            } else {
341                acc
342            }
343        })
344        .map(|(ip, priority, if_index)| Gateway {
345            ip,
346            if_index,
347            priority,
348        })
349}