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}