nlink/netlink/
neigh.rs

1//! Neighbor (ARP/NDP) management.
2//!
3//! This module provides typed builders for adding and managing neighbor entries.
4//!
5//! # Example
6//!
7//! ```ignore
8//! use nlink::netlink::{Connection, Route};
9//! use nlink::netlink::neigh::{Neighbor, NeighborState};
10//! use std::net::Ipv4Addr;
11//!
12//! let conn = Connection::<Route>::new()?;
13//!
14//! // Add a permanent ARP entry
15//! conn.add_neighbor(
16//!     Neighbor::new_v4("eth0", Ipv4Addr::new(192, 168, 1, 100))
17//!         .lladdr([0x00, 0x11, 0x22, 0x33, 0x44, 0x55])
18//!         .state(NeighborState::Permanent)
19//! ).await?;
20//!
21//! // Add a proxy ARP entry
22//! conn.add_neighbor(
23//!     Neighbor::new_v4("eth0", Ipv4Addr::new(192, 168, 1, 200))
24//!         .proxy()
25//! ).await?;
26//!
27//! // Delete a neighbor entry
28//! conn.del_neighbor_v4("eth0", Ipv4Addr::new(192, 168, 1, 100)).await?;
29//! ```
30
31use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
32
33use super::builder::MessageBuilder;
34use super::connection::Connection;
35use super::error::{Error, Result};
36use super::message::{NLM_F_ACK, NLM_F_REQUEST, NlMsgType};
37use super::protocol::Route;
38use super::types::neigh::{NdMsg, NdaAttr, NeighborState, ntf, nud};
39
40/// NLM_F_CREATE flag
41const NLM_F_CREATE: u16 = 0x400;
42/// NLM_F_EXCL flag
43const NLM_F_EXCL: u16 = 0x200;
44
45/// Address families
46const AF_INET: u8 = 2;
47const AF_INET6: u8 = 10;
48
49// Re-export NeighborState for convenience
50pub use super::types::neigh::NeighborState as State;
51
52/// Trait for neighbor configurations that can be added.
53pub trait NeighborConfig {
54    /// Get the interface name.
55    fn interface(&self) -> &str;
56
57    /// Build the netlink message for adding this neighbor.
58    fn build(&self) -> Result<MessageBuilder>;
59
60    /// Build a message for deleting this neighbor.
61    fn build_delete(&self) -> Result<MessageBuilder>;
62}
63
64// ============================================================================
65// Neighbor Entry
66// ============================================================================
67
68/// Configuration for a neighbor (ARP/NDP) entry.
69///
70/// # Example
71///
72/// ```ignore
73/// use nlink::netlink::neigh::{Neighbor, NeighborState};
74/// use std::net::Ipv4Addr;
75///
76/// // Add a permanent ARP entry
77/// let neigh = Neighbor::new_v4("eth0", Ipv4Addr::new(192, 168, 1, 100))
78///     .lladdr([0x00, 0x11, 0x22, 0x33, 0x44, 0x55])
79///     .state(NeighborState::Permanent);
80///
81/// conn.add_neighbor(neigh).await?;
82/// ```
83#[derive(Debug, Clone)]
84pub struct Neighbor {
85    interface: String,
86    /// Destination IP address
87    destination: IpAddr,
88    /// Link-layer address (MAC address for Ethernet)
89    lladdr: Option<Vec<u8>>,
90    /// Neighbor state
91    state: u16,
92    /// Neighbor flags
93    flags: u8,
94    /// VLAN ID (for bridge FDB)
95    vlan: Option<u16>,
96    /// VNI (for VXLAN)
97    vni: Option<u32>,
98    /// Master device index
99    master: Option<u32>,
100}
101
102impl Neighbor {
103    /// Create a new IPv4 neighbor entry.
104    ///
105    /// # Arguments
106    ///
107    /// * `interface` - Interface name (e.g., "eth0")
108    /// * `destination` - IPv4 address of the neighbor
109    pub fn new_v4(interface: impl Into<String>, destination: Ipv4Addr) -> Self {
110        Self {
111            interface: interface.into(),
112            destination: IpAddr::V4(destination),
113            lladdr: None,
114            state: nud::PERMANENT,
115            flags: 0,
116            vlan: None,
117            vni: None,
118            master: None,
119        }
120    }
121
122    /// Create a new IPv6 neighbor entry.
123    ///
124    /// # Arguments
125    ///
126    /// * `interface` - Interface name (e.g., "eth0")
127    /// * `destination` - IPv6 address of the neighbor
128    pub fn new_v6(interface: impl Into<String>, destination: Ipv6Addr) -> Self {
129        Self {
130            interface: interface.into(),
131            destination: IpAddr::V6(destination),
132            lladdr: None,
133            state: nud::PERMANENT,
134            flags: 0,
135            vlan: None,
136            vni: None,
137            master: None,
138        }
139    }
140
141    /// Create a new neighbor entry from an IpAddr.
142    pub fn new(interface: impl Into<String>, destination: IpAddr) -> Self {
143        Self {
144            interface: interface.into(),
145            destination,
146            lladdr: None,
147            state: nud::PERMANENT,
148            flags: 0,
149            vlan: None,
150            vni: None,
151            master: None,
152        }
153    }
154
155    /// Set the link-layer (MAC) address as a 6-byte array.
156    pub fn lladdr(mut self, addr: [u8; 6]) -> Self {
157        self.lladdr = Some(addr.to_vec());
158        self
159    }
160
161    /// Set the link-layer address from bytes.
162    pub fn lladdr_bytes(mut self, addr: impl Into<Vec<u8>>) -> Self {
163        self.lladdr = Some(addr.into());
164        self
165    }
166
167    /// Set the neighbor state.
168    pub fn state(mut self, state: NeighborState) -> Self {
169        self.state = state as u16;
170        self
171    }
172
173    /// Set as permanent entry.
174    pub fn permanent(mut self) -> Self {
175        self.state = nud::PERMANENT;
176        self
177    }
178
179    /// Set as reachable entry.
180    pub fn reachable(mut self) -> Self {
181        self.state = nud::REACHABLE;
182        self
183    }
184
185    /// Set as stale entry.
186    pub fn stale(mut self) -> Self {
187        self.state = nud::STALE;
188        self
189    }
190
191    /// Set as noarp entry (no ARP requests will be sent).
192    pub fn noarp(mut self) -> Self {
193        self.state = nud::NOARP;
194        self
195    }
196
197    /// Mark as proxy ARP entry.
198    pub fn proxy(mut self) -> Self {
199        self.flags |= ntf::PROXY;
200        self
201    }
202
203    /// Mark as router (for NDP).
204    pub fn router(mut self) -> Self {
205        self.flags |= ntf::ROUTER;
206        self
207    }
208
209    /// Mark as externally learned.
210    pub fn extern_learn(mut self) -> Self {
211        self.flags |= ntf::EXT_LEARNED;
212        self
213    }
214
215    /// Set VLAN ID (for bridge FDB entries).
216    pub fn vlan(mut self, vlan_id: u16) -> Self {
217        self.vlan = Some(vlan_id);
218        self
219    }
220
221    /// Set VNI (for VXLAN FDB entries).
222    pub fn vni(mut self, vni: u32) -> Self {
223        self.vni = Some(vni);
224        self
225    }
226
227    /// Set master device (e.g., for bridge FDB).
228    pub fn master(mut self, master: impl Into<String>) -> Self {
229        if let Ok(idx) = ifname_to_index(&master.into()) {
230            self.master = Some(idx);
231        }
232        self
233    }
234}
235
236impl NeighborConfig for Neighbor {
237    fn interface(&self) -> &str {
238        &self.interface
239    }
240
241    fn build(&self) -> Result<MessageBuilder> {
242        let ifindex = ifname_to_index(&self.interface)?;
243
244        let family = match self.destination {
245            IpAddr::V4(_) => AF_INET,
246            IpAddr::V6(_) => AF_INET6,
247        };
248
249        let mut builder = MessageBuilder::new(
250            NlMsgType::RTM_NEWNEIGH,
251            NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
252        );
253
254        let mut ndmsg = NdMsg::new()
255            .with_family(family)
256            .with_ifindex(ifindex as i32);
257        ndmsg.ndm_state = self.state;
258        ndmsg.ndm_flags = self.flags;
259
260        builder.append(&ndmsg);
261
262        // NDA_DST - destination IP address
263        match self.destination {
264            IpAddr::V4(addr) => {
265                builder.append_attr(NdaAttr::Dst as u16, &addr.octets());
266            }
267            IpAddr::V6(addr) => {
268                builder.append_attr(NdaAttr::Dst as u16, &addr.octets());
269            }
270        }
271
272        // NDA_LLADDR - link-layer address
273        if let Some(ref lladdr) = self.lladdr {
274            builder.append_attr(NdaAttr::Lladdr as u16, lladdr);
275        }
276
277        // NDA_VLAN
278        if let Some(vlan) = self.vlan {
279            builder.append_attr_u16(NdaAttr::Vlan as u16, vlan);
280        }
281
282        // NDA_VNI
283        if let Some(vni) = self.vni {
284            builder.append_attr_u32(NdaAttr::Vni as u16, vni);
285        }
286
287        // NDA_MASTER
288        if let Some(master) = self.master {
289            builder.append_attr_u32(NdaAttr::Master as u16, master);
290        }
291
292        Ok(builder)
293    }
294
295    fn build_delete(&self) -> Result<MessageBuilder> {
296        let ifindex = ifname_to_index(&self.interface)?;
297
298        let family = match self.destination {
299            IpAddr::V4(_) => AF_INET,
300            IpAddr::V6(_) => AF_INET6,
301        };
302
303        let mut builder = MessageBuilder::new(NlMsgType::RTM_DELNEIGH, NLM_F_REQUEST | NLM_F_ACK);
304
305        let ndmsg = NdMsg::new()
306            .with_family(family)
307            .with_ifindex(ifindex as i32);
308
309        builder.append(&ndmsg);
310
311        // NDA_DST
312        match self.destination {
313            IpAddr::V4(addr) => {
314                builder.append_attr(NdaAttr::Dst as u16, &addr.octets());
315            }
316            IpAddr::V6(addr) => {
317                builder.append_attr(NdaAttr::Dst as u16, &addr.octets());
318            }
319        }
320
321        Ok(builder)
322    }
323}
324
325// ============================================================================
326// Helper Functions
327// ============================================================================
328
329/// Helper function to convert interface name to index.
330fn ifname_to_index(name: &str) -> Result<u32> {
331    let path = format!("/sys/class/net/{}/ifindex", name);
332    let content = std::fs::read_to_string(&path)
333        .map_err(|_| Error::InvalidMessage(format!("interface not found: {}", name)))?;
334    content
335        .trim()
336        .parse()
337        .map_err(|_| Error::InvalidMessage(format!("invalid ifindex for: {}", name)))
338}
339
340// ============================================================================
341// Connection Methods
342// ============================================================================
343
344impl Connection<Route> {
345    /// Add a neighbor entry.
346    ///
347    /// # Example
348    ///
349    /// ```ignore
350    /// use nlink::netlink::neigh::{Neighbor, NeighborState};
351    /// use std::net::Ipv4Addr;
352    ///
353    /// // Add a permanent ARP entry
354    /// conn.add_neighbor(
355    ///     Neighbor::new_v4("eth0", Ipv4Addr::new(192, 168, 1, 100))
356    ///         .lladdr([0x00, 0x11, 0x22, 0x33, 0x44, 0x55])
357    ///         .state(NeighborState::Permanent)
358    /// ).await?;
359    /// ```
360    pub async fn add_neighbor<N: NeighborConfig>(&self, config: N) -> Result<()> {
361        let builder = config.build()?;
362        self.send_ack(builder).await
363    }
364
365    /// Delete a neighbor entry using a config.
366    pub async fn del_neighbor<N: NeighborConfig>(&self, config: N) -> Result<()> {
367        let builder = config.build_delete()?;
368        self.send_ack(builder).await
369    }
370
371    /// Delete an IPv4 neighbor entry.
372    ///
373    /// # Example
374    ///
375    /// ```ignore
376    /// conn.del_neighbor_v4("eth0", Ipv4Addr::new(192, 168, 1, 100)).await?;
377    /// ```
378    pub async fn del_neighbor_v4(&self, ifname: &str, destination: Ipv4Addr) -> Result<()> {
379        let neigh = Neighbor::new_v4(ifname, destination);
380        self.del_neighbor(neigh).await
381    }
382
383    /// Delete an IPv6 neighbor entry.
384    pub async fn del_neighbor_v6(&self, ifname: &str, destination: Ipv6Addr) -> Result<()> {
385        let neigh = Neighbor::new_v6(ifname, destination);
386        self.del_neighbor(neigh).await
387    }
388
389    /// Replace a neighbor entry (add or update).
390    ///
391    /// If the entry exists, it will be updated. Otherwise, it will be created.
392    pub async fn replace_neighbor<N: NeighborConfig>(&self, config: N) -> Result<()> {
393        // Similar to add but with REPLACE flag
394        let builder = config.build()?;
395        self.send_ack(builder).await
396    }
397
398    /// Flush all neighbor entries for an interface.
399    ///
400    /// # Example
401    ///
402    /// ```ignore
403    /// conn.flush_neighbors("eth0").await?;
404    /// ```
405    pub async fn flush_neighbors(&self, ifname: &str) -> Result<()> {
406        let neighbors = self.get_neighbors_for(ifname).await?;
407
408        for neigh in neighbors {
409            if let Some(dest) = neigh.destination {
410                // Skip permanent entries unless explicitly requested
411                // (matching iproute2 behavior)
412                if neigh.state() == NeighborState::Permanent {
413                    continue;
414                }
415
416                if let Err(e) = self.del_neighbor(Neighbor::new(ifname, dest)).await {
417                    // Ignore "not found" errors (race condition)
418                    if !e.is_not_found() {
419                        return Err(e);
420                    }
421                }
422            }
423        }
424
425        Ok(())
426    }
427
428    /// Add a proxy ARP entry.
429    ///
430    /// # Example
431    ///
432    /// ```ignore
433    /// conn.add_proxy_arp("eth0", Ipv4Addr::new(192, 168, 1, 100)).await?;
434    /// ```
435    pub async fn add_proxy_arp(&self, ifname: &str, destination: Ipv4Addr) -> Result<()> {
436        let neigh = Neighbor::new_v4(ifname, destination).proxy();
437        self.add_neighbor(neigh).await
438    }
439
440    /// Delete a proxy ARP entry.
441    pub async fn del_proxy_arp(&self, ifname: &str, destination: Ipv4Addr) -> Result<()> {
442        let neigh = Neighbor::new_v4(ifname, destination).proxy();
443        self.del_neighbor(neigh).await
444    }
445}