Skip to main content

nlink/netlink/
link.rs

1//! Link creation and management builders.
2//!
3//! This module provides typed builders for creating virtual network interfaces.
4//!
5//! # Supported Link Types
6//!
7//! - [`DummyLink`] - Dummy interface (loopback-like, no actual network)
8//! - [`VethLink`] - Virtual ethernet pair
9//! - [`BridgeLink`] - Bridge interface
10//! - [`BondLink`] - Bonding (link aggregation) interface
11//! - [`VlanLink`] - VLAN interface
12//! - [`VxlanLink`] - VXLAN overlay interface
13//! - [`MacvlanLink`] - MAC-based VLAN interface
14//! - [`MacvtapLink`] - MAC-based tap interface (for VMs)
15//! - [`IpvlanLink`] - IP-based VLAN interface
16//! - [`VrfLink`] - Virtual Routing and Forwarding interface
17//! - [`IfbLink`] - Intermediate Functional Block (for ingress shaping)
18//! - [`GeneveLink`] - Generic Network Virtualization Encapsulation
19//! - [`BareudpLink`] - Bare UDP tunneling
20//! - [`NetkitLink`] - BPF-optimized virtual ethernet
21//! - [`NlmonLink`] - Netlink monitor for debugging
22//! - [`VirtWifiLink`] - Virtual WiFi for testing
23//! - [`GreLink`] - GRE tunnel (IPv4)
24//! - [`GretapLink`] - GRE TAP tunnel (Layer 2 over IPv4)
25//! - [`IpipLink`] - IP-in-IP tunnel
26//! - [`SitLink`] - SIT tunnel (IPv6-in-IPv4)
27//! - [`VtiLink`] - Virtual Tunnel Interface (IPv4 IPsec)
28//! - [`Vti6Link`] - Virtual Tunnel Interface (IPv6 IPsec)
29//! - [`Ip6GreLink`] - IPv6 GRE tunnel
30//! - [`Ip6GretapLink`] - IPv6 GRE TAP tunnel (Layer 2)
31//! - [`WireguardLink`] - WireGuard interface
32//!
33//! # Tunnel Modification Limitations
34//!
35//! Tunnel parameters (local/remote IP, keys, TTL, encapsulation options) are
36//! **immutable after creation**. This is a Linux kernel limitation, not a library bug:
37//!
38//! - `RTM_NEWLINK` with `NLM_F_CREATE` sets tunnel parameters at creation time
39//! - `RTM_SETLINK` can only modify link-level attributes (MTU, name, up/down state)
40//! - No kernel API exists to modify `IFLA_LINKINFO_DATA` after creation
41//!
42//! ## What Can Be Changed After Creation
43//!
44//! These operations work on all link types, including tunnels:
45//!
46//! - Interface up/down state ([`set_link_up()`](Connection::set_link_up), [`set_link_down()`](Connection::set_link_down))
47//! - MTU ([`set_link_mtu()`](Connection::set_link_mtu))
48//! - Interface name ([`set_link_name()`](Connection::set_link_name))
49//! - MAC address ([`set_link_address()`](Connection::set_link_address))
50//! - Master device ([`set_link_master()`](Connection::set_link_master))
51//! - Network namespace ([`set_link_netns_pid()`](Connection::set_link_netns_pid))
52//!
53//! ## What Cannot Be Changed (requires delete + recreate)
54//!
55//! These parameters are set at creation and cannot be modified:
56//!
57//! - Tunnel endpoints (local/remote IP addresses)
58//! - Tunnel keys (GRE, VTI)
59//! - TTL, TOS, encapsulation flags
60//! - VXLAN VNI, port, learning settings
61//! - Geneve VNI and options
62//! - Any parameter stored in `IFLA_LINKINFO_DATA`
63//!
64//! ## Safe Replacement Pattern
65//!
66//! To change tunnel parameters, delete and recreate the tunnel:
67//!
68//! ```ignore
69//! // To change tunnel parameters:
70//! conn.del_link("gre1").await?;
71//! conn.add_link(GreLink::new("gre1")
72//!     .remote(new_remote_ip)
73//!     .local(new_local_ip)
74//!     .ttl(64)
75//! ).await?;
76//! ```
77//!
78//! Note: This causes a brief network interruption. For zero-downtime changes,
79//! consider creating the new tunnel with a temporary name, migrating traffic,
80//! then renaming.
81//!
82//! # Example
83//!
84//! ```ignore
85//! use nlink::netlink::{Connection, Route};
86//! use nlink::netlink::link::{DummyLink, VethLink, BridgeLink, VlanLink};
87//!
88//! let conn = Connection::<Route>::new()?;
89//!
90//! // Create a dummy interface
91//! conn.add_link(DummyLink::new("dummy0")).await?;
92//!
93//! // Create a veth pair
94//! conn.add_link(VethLink::new("veth0", "veth1")).await?;
95//!
96//! // Create a bridge
97//! conn.add_link(BridgeLink::new("br0")).await?;
98//!
99//! // Create a VLAN on eth0
100//! conn.add_link(VlanLink::new("eth0.100", "eth0", 100)).await?;
101//! ```
102
103use std::net::Ipv4Addr;
104
105use super::builder::MessageBuilder;
106use super::connection::Connection;
107use super::error::Result;
108use super::interface_ref::InterfaceRef;
109use super::message::NlMsgType;
110use super::protocol::Route;
111use super::types::link::{IfInfoMsg, IflaAttr, IflaInfo};
112
113/// NLM_F_CREATE flag
114const NLM_F_CREATE: u16 = 0x400;
115/// NLM_F_EXCL flag (fail if exists)
116const NLM_F_EXCL: u16 = 0x200;
117
118/// Trait for link configurations that can be added to the system.
119pub trait LinkConfig: Send + Sync {
120    /// Get the name of this interface.
121    fn name(&self) -> &str;
122
123    /// Get the kind string for this link type (e.g., "dummy", "veth", "bridge").
124    fn kind(&self) -> &str;
125
126    /// Get the peer interface name, if this is a paired link type (veth, netkit).
127    fn peer_name(&self) -> Option<&str> {
128        None
129    }
130
131    /// Get the parent/link interface reference, if any.
132    ///
133    /// Returns `Some(&InterfaceRef)` for link types that require a parent interface
134    /// (VLAN, MACVLAN, VXLAN, etc.), `None` for standalone types (dummy, bridge, etc.).
135    fn parent_ref(&self) -> Option<&InterfaceRef> {
136        None
137    }
138
139    /// Write the link configuration to the message builder.
140    ///
141    /// The `parent_index` parameter contains the resolved interface index
142    /// for link types that have a parent reference.
143    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>);
144}
145
146// ============================================================================
147// Dummy Link
148// ============================================================================
149
150/// Configuration for a dummy interface.
151///
152/// Dummy interfaces are virtual interfaces that simply drop all traffic.
153/// They're useful for testing or as anchors for IP addresses.
154///
155/// # Example
156///
157/// ```ignore
158/// use nlink::netlink::link::DummyLink;
159///
160/// let dummy = DummyLink::new("dummy0")
161///     .mtu(9000);
162///
163/// conn.add_link(dummy).await?;
164/// ```
165#[derive(Debug, Clone)]
166#[must_use = "builders do nothing unless used"]
167pub struct DummyLink {
168    name: String,
169    mtu: Option<u32>,
170    address: Option<[u8; 6]>,
171}
172
173impl DummyLink {
174    /// Create a new dummy interface configuration.
175    pub fn new(name: impl Into<String>) -> Self {
176        Self {
177            name: name.into(),
178            mtu: None,
179            address: None,
180        }
181    }
182
183    /// Set the MTU for this interface.
184    pub fn mtu(mut self, mtu: u32) -> Self {
185        self.mtu = Some(mtu);
186        self
187    }
188
189    /// Set the MAC address for this interface.
190    pub fn address(mut self, addr: [u8; 6]) -> Self {
191        self.address = Some(addr);
192        self
193    }
194}
195
196impl LinkConfig for DummyLink {
197    fn name(&self) -> &str {
198        &self.name
199    }
200
201    fn kind(&self) -> &str {
202        "dummy"
203    }
204
205    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
206        write_simple_link(
207            builder,
208            &self.name,
209            "dummy",
210            self.mtu,
211            self.address.as_ref(),
212        );
213    }
214}
215
216// ============================================================================
217// Veth Link
218// ============================================================================
219
220/// Configuration for a veth (virtual ethernet) pair.
221///
222/// Veth devices are created in pairs. Whatever enters one end comes out the other.
223/// They're commonly used to connect network namespaces.
224///
225/// # Example
226///
227/// ```ignore
228/// use nlink::netlink::link::VethLink;
229///
230/// // Create a veth pair
231/// let veth = VethLink::new("veth0", "veth1");
232/// conn.add_link(veth).await?;
233///
234/// // Now veth0 and veth1 are connected
235/// ```
236#[derive(Debug, Clone)]
237#[must_use = "builders do nothing unless used"]
238pub struct VethLink {
239    name: String,
240    peer_name: String,
241    mtu: Option<u32>,
242    address: Option<[u8; 6]>,
243    peer_address: Option<[u8; 6]>,
244    peer_netns_fd: Option<i32>,
245    peer_netns_pid: Option<u32>,
246}
247
248impl VethLink {
249    /// Create a new veth pair configuration.
250    ///
251    /// # Arguments
252    ///
253    /// * `name` - Name for the first interface
254    /// * `peer_name` - Name for the peer interface
255    pub fn new(name: impl Into<String>, peer_name: impl Into<String>) -> Self {
256        Self {
257            name: name.into(),
258            peer_name: peer_name.into(),
259            mtu: None,
260            address: None,
261            peer_address: None,
262            peer_netns_fd: None,
263            peer_netns_pid: None,
264        }
265    }
266
267    /// Set the MTU for both interfaces.
268    pub fn mtu(mut self, mtu: u32) -> Self {
269        self.mtu = Some(mtu);
270        self
271    }
272
273    /// Set the MAC address for the first interface.
274    pub fn address(mut self, addr: [u8; 6]) -> Self {
275        self.address = Some(addr);
276        self
277    }
278
279    /// Set the MAC address for the peer interface.
280    pub fn peer_address(mut self, addr: [u8; 6]) -> Self {
281        self.peer_address = Some(addr);
282        self
283    }
284
285    /// Move the peer interface to a different network namespace by fd.
286    pub fn peer_netns_fd(mut self, fd: i32) -> Self {
287        self.peer_netns_fd = Some(fd);
288        self.peer_netns_pid = None;
289        self
290    }
291
292    /// Move the peer interface to a different network namespace by PID.
293    pub fn peer_netns_pid(mut self, pid: u32) -> Self {
294        self.peer_netns_pid = Some(pid);
295        self.peer_netns_fd = None;
296        self
297    }
298}
299
300/// VETH-specific nested attributes
301mod veth {
302    pub const VETH_INFO_PEER: u16 = 1;
303}
304
305impl LinkConfig for VethLink {
306    fn name(&self) -> &str {
307        &self.name
308    }
309
310    fn kind(&self) -> &str {
311        "veth"
312    }
313
314    fn peer_name(&self) -> Option<&str> {
315        Some(&self.peer_name)
316    }
317
318    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
319        // Add interface name
320        write_ifname(builder, &self.name);
321
322        // Add optional attributes
323        if let Some(mtu) = self.mtu {
324            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
325        }
326        if let Some(ref addr) = self.address {
327            builder.append_attr(IflaAttr::Address as u16, addr);
328        }
329
330        // IFLA_LINKINFO
331        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
332        builder.append_attr_str(IflaInfo::Kind as u16, "veth");
333
334        // IFLA_INFO_DATA -> VETH_INFO_PEER -> nested ifinfomsg + attrs
335        let data = builder.nest_start(IflaInfo::Data as u16);
336        let peer = builder.nest_start(veth::VETH_INFO_PEER);
337
338        // Peer ifinfomsg
339        let peer_ifinfo = IfInfoMsg::new();
340        builder.append(&peer_ifinfo);
341
342        // Peer name
343        builder.append_attr_str(IflaAttr::Ifname as u16, &self.peer_name);
344
345        // Peer MTU
346        if let Some(mtu) = self.mtu {
347            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
348        }
349
350        // Peer address
351        if let Some(ref addr) = self.peer_address {
352            builder.append_attr(IflaAttr::Address as u16, addr);
353        }
354
355        // Peer namespace
356        if let Some(fd) = self.peer_netns_fd {
357            builder.append_attr_u32(IflaAttr::NetNsFd as u16, fd as u32);
358        } else if let Some(pid) = self.peer_netns_pid {
359            builder.append_attr_u32(IflaAttr::NetNsPid as u16, pid);
360        }
361
362        builder.nest_end(peer);
363        builder.nest_end(data);
364        builder.nest_end(linkinfo);
365    }
366}
367
368// ============================================================================
369// Bridge Link
370// ============================================================================
371
372/// Configuration for a bridge interface.
373///
374/// A bridge is a virtual switch that forwards packets between attached interfaces.
375///
376/// # Example
377///
378/// ```ignore
379/// use nlink::netlink::link::BridgeLink;
380///
381/// let bridge = BridgeLink::new("br0")
382///     .stp(true)
383///     .vlan_filtering(true);
384///
385/// conn.add_link(bridge).await?;
386/// ```
387#[derive(Debug, Clone)]
388#[must_use = "builders do nothing unless used"]
389pub struct BridgeLink {
390    name: String,
391    mtu: Option<u32>,
392    address: Option<[u8; 6]>,
393    /// Forward delay in centiseconds
394    forward_delay: Option<u32>,
395    /// Hello time in centiseconds
396    hello_time: Option<u32>,
397    /// Max age in centiseconds
398    max_age: Option<u32>,
399    /// Ageing time in centiseconds
400    ageing_time: Option<u32>,
401    /// STP state (0 = off, 1 = on)
402    stp_state: Option<u32>,
403    /// Priority (0-65535)
404    priority: Option<u16>,
405    /// VLAN filtering enabled
406    vlan_filtering: Option<bool>,
407    /// Default PVID
408    vlan_default_pvid: Option<u16>,
409}
410
411/// Bridge-specific attributes (IFLA_BR_*)
412mod bridge {
413    pub const IFLA_BR_FORWARD_DELAY: u16 = 1;
414    pub const IFLA_BR_HELLO_TIME: u16 = 2;
415    pub const IFLA_BR_MAX_AGE: u16 = 3;
416    pub const IFLA_BR_AGEING_TIME: u16 = 4;
417    pub const IFLA_BR_STP_STATE: u16 = 5;
418    pub const IFLA_BR_PRIORITY: u16 = 6;
419    pub const IFLA_BR_VLAN_FILTERING: u16 = 7;
420    pub const IFLA_BR_VLAN_DEFAULT_PVID: u16 = 39;
421}
422
423impl BridgeLink {
424    /// Create a new bridge interface configuration.
425    pub fn new(name: impl Into<String>) -> Self {
426        Self {
427            name: name.into(),
428            mtu: None,
429            address: None,
430            forward_delay: None,
431            hello_time: None,
432            max_age: None,
433            ageing_time: None,
434            stp_state: None,
435            priority: None,
436            vlan_filtering: None,
437            vlan_default_pvid: None,
438        }
439    }
440
441    /// Set the MTU.
442    pub fn mtu(mut self, mtu: u32) -> Self {
443        self.mtu = Some(mtu);
444        self
445    }
446
447    /// Set the MAC address.
448    pub fn address(mut self, addr: [u8; 6]) -> Self {
449        self.address = Some(addr);
450        self
451    }
452
453    /// Enable or disable STP.
454    pub fn stp(mut self, enabled: bool) -> Self {
455        self.stp_state = Some(if enabled { 1 } else { 0 });
456        self
457    }
458
459    /// Set the forward delay in milliseconds.
460    pub fn forward_delay_ms(mut self, ms: u32) -> Self {
461        // Kernel expects centiseconds (USER_HZ typically 100)
462        self.forward_delay = Some(ms / 10);
463        self
464    }
465
466    /// Set the hello time in milliseconds.
467    pub fn hello_time_ms(mut self, ms: u32) -> Self {
468        self.hello_time = Some(ms / 10);
469        self
470    }
471
472    /// Set the max age in milliseconds.
473    pub fn max_age_ms(mut self, ms: u32) -> Self {
474        self.max_age = Some(ms / 10);
475        self
476    }
477
478    /// Set the ageing time in seconds.
479    pub fn ageing_time(mut self, seconds: u32) -> Self {
480        // Kernel expects centiseconds
481        self.ageing_time = Some(seconds * 100);
482        self
483    }
484
485    /// Set the bridge priority.
486    pub fn priority(mut self, priority: u16) -> Self {
487        self.priority = Some(priority);
488        self
489    }
490
491    /// Enable or disable VLAN filtering.
492    pub fn vlan_filtering(mut self, enabled: bool) -> Self {
493        self.vlan_filtering = Some(enabled);
494        self
495    }
496
497    /// Set the default PVID (port VLAN ID).
498    pub fn vlan_default_pvid(mut self, pvid: u16) -> Self {
499        self.vlan_default_pvid = Some(pvid);
500        self
501    }
502}
503
504impl LinkConfig for BridgeLink {
505    fn name(&self) -> &str {
506        &self.name
507    }
508
509    fn kind(&self) -> &str {
510        "bridge"
511    }
512
513    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
514        // Add interface name
515        write_ifname(builder, &self.name);
516
517        // Add optional attributes
518        if let Some(mtu) = self.mtu {
519            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
520        }
521        if let Some(ref addr) = self.address {
522            builder.append_attr(IflaAttr::Address as u16, addr);
523        }
524
525        // IFLA_LINKINFO
526        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
527        builder.append_attr_str(IflaInfo::Kind as u16, "bridge");
528
529        // Check if we have any bridge-specific options
530        let has_options = self.forward_delay.is_some()
531            || self.hello_time.is_some()
532            || self.max_age.is_some()
533            || self.ageing_time.is_some()
534            || self.stp_state.is_some()
535            || self.priority.is_some()
536            || self.vlan_filtering.is_some()
537            || self.vlan_default_pvid.is_some();
538
539        if has_options {
540            let data = builder.nest_start(IflaInfo::Data as u16);
541
542            if let Some(val) = self.forward_delay {
543                builder.append_attr_u32(bridge::IFLA_BR_FORWARD_DELAY, val);
544            }
545            if let Some(val) = self.hello_time {
546                builder.append_attr_u32(bridge::IFLA_BR_HELLO_TIME, val);
547            }
548            if let Some(val) = self.max_age {
549                builder.append_attr_u32(bridge::IFLA_BR_MAX_AGE, val);
550            }
551            if let Some(val) = self.ageing_time {
552                builder.append_attr_u32(bridge::IFLA_BR_AGEING_TIME, val);
553            }
554            if let Some(val) = self.stp_state {
555                builder.append_attr_u32(bridge::IFLA_BR_STP_STATE, val);
556            }
557            if let Some(val) = self.priority {
558                builder.append_attr_u16(bridge::IFLA_BR_PRIORITY, val);
559            }
560            if let Some(enabled) = self.vlan_filtering {
561                builder.append_attr_u8(bridge::IFLA_BR_VLAN_FILTERING, if enabled { 1 } else { 0 });
562            }
563            if let Some(pvid) = self.vlan_default_pvid {
564                builder.append_attr_u16(bridge::IFLA_BR_VLAN_DEFAULT_PVID, pvid);
565            }
566
567            builder.nest_end(data);
568        }
569
570        builder.nest_end(linkinfo);
571    }
572}
573
574// ============================================================================
575// VLAN Link
576// ============================================================================
577
578/// Configuration for a VLAN interface.
579///
580/// A VLAN interface tags/untags packets with an 802.1Q VLAN ID.
581///
582/// # Example
583///
584/// ```ignore
585/// use nlink::netlink::link::VlanLink;
586///
587/// // Create VLAN 100 on eth0
588/// let vlan = VlanLink::new("eth0.100", "eth0", 100);
589/// conn.add_link(vlan).await?;
590/// ```
591#[derive(Debug, Clone)]
592#[must_use = "builders do nothing unless used"]
593pub struct VlanLink {
594    name: String,
595    parent: InterfaceRef,
596    vlan_id: u16,
597    mtu: Option<u32>,
598    address: Option<[u8; 6]>,
599    /// Protocol: 0x8100 for 802.1Q, 0x88a8 for 802.1ad
600    protocol: Option<u16>,
601    flags: VlanFlags,
602}
603
604/// VLAN-specific attributes (IFLA_VLAN_*)
605mod vlan {
606    pub const IFLA_VLAN_ID: u16 = 1;
607    pub const IFLA_VLAN_FLAGS: u16 = 2;
608    pub const IFLA_VLAN_PROTOCOL: u16 = 5;
609
610    /// VLAN flags
611    pub const VLAN_FLAG_REORDER_HDR: u32 = 0x1;
612    pub const VLAN_FLAG_GVRP: u32 = 0x2;
613    pub const VLAN_FLAG_LOOSE_BINDING: u32 = 0x4;
614    pub const VLAN_FLAG_MVRP: u32 = 0x8;
615}
616
617/// VLAN flags structure
618#[repr(C)]
619#[derive(Debug, Clone, Copy, Default)]
620pub struct VlanFlags {
621    pub flags: u32,
622    pub mask: u32,
623}
624
625impl VlanLink {
626    /// Create a new VLAN interface configuration.
627    ///
628    /// # Arguments
629    ///
630    /// * `name` - Name for the VLAN interface (e.g., "eth0.100")
631    /// * `parent` - Parent interface name (e.g., "eth0")
632    /// * `vlan_id` - VLAN ID (1-4094)
633    pub fn new(name: impl Into<String>, parent: impl Into<String>, vlan_id: u16) -> Self {
634        Self {
635            name: name.into(),
636            parent: InterfaceRef::Name(parent.into()),
637            vlan_id,
638            mtu: None,
639            address: None,
640            protocol: None,
641            flags: VlanFlags::default(),
642        }
643    }
644
645    /// Create a new VLAN interface with parent specified by index.
646    ///
647    /// This is the namespace-safe variant that avoids reading from /sys/class/net/.
648    ///
649    /// # Arguments
650    ///
651    /// * `name` - Name for the VLAN interface
652    /// * `parent_index` - Parent interface index
653    /// * `vlan_id` - VLAN ID (1-4094)
654    pub fn with_parent_index(name: impl Into<String>, parent_index: u32, vlan_id: u16) -> Self {
655        Self {
656            name: name.into(),
657            parent: InterfaceRef::Index(parent_index),
658            vlan_id,
659            mtu: None,
660            address: None,
661            protocol: None,
662            flags: VlanFlags::default(),
663        }
664    }
665
666    /// Set the MTU.
667    pub fn mtu(mut self, mtu: u32) -> Self {
668        self.mtu = Some(mtu);
669        self
670    }
671
672    /// Set the MAC address.
673    pub fn address(mut self, addr: [u8; 6]) -> Self {
674        self.address = Some(addr);
675        self
676    }
677
678    /// Set to 802.1ad (QinQ) protocol instead of 802.1Q.
679    pub fn qinq(mut self) -> Self {
680        self.protocol = Some(0x88a8);
681        self
682    }
683
684    /// Enable GVRP (GARP VLAN Registration Protocol).
685    pub fn gvrp(mut self, enabled: bool) -> Self {
686        self.flags.mask |= vlan::VLAN_FLAG_GVRP;
687        if enabled {
688            self.flags.flags |= vlan::VLAN_FLAG_GVRP;
689        } else {
690            self.flags.flags &= !vlan::VLAN_FLAG_GVRP;
691        }
692        self
693    }
694
695    /// Enable MVRP (Multiple VLAN Registration Protocol).
696    pub fn mvrp(mut self, enabled: bool) -> Self {
697        self.flags.mask |= vlan::VLAN_FLAG_MVRP;
698        if enabled {
699            self.flags.flags |= vlan::VLAN_FLAG_MVRP;
700        } else {
701            self.flags.flags &= !vlan::VLAN_FLAG_MVRP;
702        }
703        self
704    }
705
706    /// Enable loose binding (don't follow parent state).
707    pub fn loose_binding(mut self, enabled: bool) -> Self {
708        self.flags.mask |= vlan::VLAN_FLAG_LOOSE_BINDING;
709        if enabled {
710            self.flags.flags |= vlan::VLAN_FLAG_LOOSE_BINDING;
711        } else {
712            self.flags.flags &= !vlan::VLAN_FLAG_LOOSE_BINDING;
713        }
714        self
715    }
716
717    /// Enable reorder header.
718    pub fn reorder_hdr(mut self, enabled: bool) -> Self {
719        self.flags.mask |= vlan::VLAN_FLAG_REORDER_HDR;
720        if enabled {
721            self.flags.flags |= vlan::VLAN_FLAG_REORDER_HDR;
722        } else {
723            self.flags.flags &= !vlan::VLAN_FLAG_REORDER_HDR;
724        }
725        self
726    }
727}
728
729impl LinkConfig for VlanLink {
730    fn name(&self) -> &str {
731        &self.name
732    }
733
734    fn kind(&self) -> &str {
735        "vlan"
736    }
737
738    fn parent_ref(&self) -> Option<&InterfaceRef> {
739        Some(&self.parent)
740    }
741
742    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
743        // Add interface name
744        write_ifname(builder, &self.name);
745
746        // Link to parent (parent_index is guaranteed to be Some for types with parent_ref)
747        let idx = parent_index.expect("VlanLink requires parent_index");
748        builder.append_attr_u32(IflaAttr::Link as u16, idx);
749
750        // Add optional attributes
751        if let Some(mtu) = self.mtu {
752            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
753        }
754        if let Some(ref addr) = self.address {
755            builder.append_attr(IflaAttr::Address as u16, addr);
756        }
757
758        // IFLA_LINKINFO
759        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
760        builder.append_attr_str(IflaInfo::Kind as u16, "vlan");
761
762        // IFLA_INFO_DATA
763        let data = builder.nest_start(IflaInfo::Data as u16);
764
765        // VLAN ID
766        builder.append_attr_u16(vlan::IFLA_VLAN_ID, self.vlan_id);
767
768        // Protocol (if set)
769        if let Some(proto) = self.protocol {
770            builder.append_attr_u16_be(vlan::IFLA_VLAN_PROTOCOL, proto);
771        }
772
773        // Flags (if any set)
774        if self.flags.mask != 0 {
775            // SAFETY: VlanFlags is a #[repr(C)] struct of two u32 fields with no padding.
776            // The pointer and size are valid for the lifetime of `self.flags`.
777            let flags_bytes = unsafe {
778                std::slice::from_raw_parts(
779                    &self.flags as *const VlanFlags as *const u8,
780                    std::mem::size_of::<VlanFlags>(),
781                )
782            };
783            builder.append_attr(vlan::IFLA_VLAN_FLAGS, flags_bytes);
784        }
785
786        builder.nest_end(data);
787        builder.nest_end(linkinfo);
788    }
789}
790
791// ============================================================================
792// VXLAN Link
793// ============================================================================
794
795/// Configuration for a VXLAN interface.
796///
797/// VXLAN provides Layer 2 overlay networks over Layer 3 infrastructure.
798///
799/// # Example
800///
801/// ```ignore
802/// use nlink::netlink::link::VxlanLink;
803/// use std::net::Ipv4Addr;
804///
805/// let vxlan = VxlanLink::new("vxlan0", 100)
806///     .local(Ipv4Addr::new(192, 168, 1, 1))
807///     .group(Ipv4Addr::new(239, 1, 1, 1))
808///     .dev("eth0")
809///     .port(4789);
810///
811/// conn.add_link(vxlan).await?;
812/// ```
813#[derive(Debug, Clone)]
814#[must_use = "builders do nothing unless used"]
815pub struct VxlanLink {
816    name: String,
817    vni: u32,
818    mtu: Option<u32>,
819    address: Option<[u8; 6]>,
820    /// Local IP address
821    local: Option<Ipv4Addr>,
822    /// Remote IP address (for point-to-point)
823    remote: Option<Ipv4Addr>,
824    /// Multicast group
825    group: Option<Ipv4Addr>,
826    /// Underlying device
827    dev: Option<InterfaceRef>,
828    /// UDP port (default 4789)
829    port: Option<u16>,
830    /// Port range
831    port_range: Option<(u16, u16)>,
832    /// TTL
833    ttl: Option<u8>,
834    /// TOS
835    tos: Option<u8>,
836    /// Learning enabled
837    learning: Option<bool>,
838    /// Proxy ARP enabled
839    proxy: Option<bool>,
840    /// RSC (route short circuit)
841    rsc: Option<bool>,
842    /// L2miss notifications
843    l2miss: Option<bool>,
844    /// L3miss notifications
845    l3miss: Option<bool>,
846    /// UDP checksum
847    udp_csum: Option<bool>,
848}
849
850/// VXLAN-specific attributes (IFLA_VXLAN_*)
851mod vxlan {
852    pub const IFLA_VXLAN_ID: u16 = 1;
853    pub const IFLA_VXLAN_GROUP: u16 = 2;
854    pub const IFLA_VXLAN_LINK: u16 = 3;
855    pub const IFLA_VXLAN_LOCAL: u16 = 4;
856    pub const IFLA_VXLAN_TTL: u16 = 5;
857    pub const IFLA_VXLAN_TOS: u16 = 6;
858    pub const IFLA_VXLAN_LEARNING: u16 = 7;
859    pub const IFLA_VXLAN_PORT_RANGE: u16 = 10;
860    pub const IFLA_VXLAN_PROXY: u16 = 11;
861    pub const IFLA_VXLAN_RSC: u16 = 12;
862    pub const IFLA_VXLAN_L2MISS: u16 = 13;
863    pub const IFLA_VXLAN_L3MISS: u16 = 14;
864    pub const IFLA_VXLAN_PORT: u16 = 15;
865    pub const IFLA_VXLAN_UDP_CSUM: u16 = 18;
866}
867
868impl VxlanLink {
869    /// Create a new VXLAN interface configuration.
870    ///
871    /// # Arguments
872    ///
873    /// * `name` - Name for the VXLAN interface
874    /// * `vni` - VXLAN Network Identifier (1-16777215)
875    pub fn new(name: impl Into<String>, vni: u32) -> Self {
876        Self {
877            name: name.into(),
878            vni,
879            mtu: None,
880            address: None,
881            local: None,
882            remote: None,
883            group: None,
884            dev: None,
885            port: None,
886            port_range: None,
887            ttl: None,
888            tos: None,
889            learning: None,
890            proxy: None,
891            rsc: None,
892            l2miss: None,
893            l3miss: None,
894            udp_csum: None,
895        }
896    }
897
898    /// Set the MTU.
899    pub fn mtu(mut self, mtu: u32) -> Self {
900        self.mtu = Some(mtu);
901        self
902    }
903
904    /// Set the MAC address.
905    pub fn address(mut self, addr: [u8; 6]) -> Self {
906        self.address = Some(addr);
907        self
908    }
909
910    /// Set the local IP address.
911    pub fn local(mut self, addr: Ipv4Addr) -> Self {
912        self.local = Some(addr);
913        self
914    }
915
916    /// Set the remote IP address (for point-to-point).
917    pub fn remote(mut self, addr: Ipv4Addr) -> Self {
918        self.remote = Some(addr);
919        self
920    }
921
922    /// Set the multicast group.
923    pub fn group(mut self, addr: Ipv4Addr) -> Self {
924        self.group = Some(addr);
925        self
926    }
927
928    /// Set the underlying device by name.
929    pub fn dev(mut self, dev: impl Into<String>) -> Self {
930        self.dev = Some(InterfaceRef::Name(dev.into()));
931        self
932    }
933
934    /// Set the underlying device by interface index.
935    ///
936    /// This is the namespace-safe variant that avoids reading from /sys/class/net/.
937    pub fn dev_index(mut self, index: u32) -> Self {
938        self.dev = Some(InterfaceRef::Index(index));
939        self
940    }
941
942    /// Set the UDP port (default 4789).
943    pub fn port(mut self, port: u16) -> Self {
944        self.port = Some(port);
945        self
946    }
947
948    /// Set the UDP port range.
949    pub fn port_range(mut self, low: u16, high: u16) -> Self {
950        self.port_range = Some((low, high));
951        self
952    }
953
954    /// Set the TTL.
955    pub fn ttl(mut self, ttl: u8) -> Self {
956        self.ttl = Some(ttl);
957        self
958    }
959
960    /// Set the TOS.
961    pub fn tos(mut self, tos: u8) -> Self {
962        self.tos = Some(tos);
963        self
964    }
965
966    /// Enable or disable learning.
967    pub fn learning(mut self, enabled: bool) -> Self {
968        self.learning = Some(enabled);
969        self
970    }
971
972    /// Enable or disable proxy ARP.
973    pub fn proxy(mut self, enabled: bool) -> Self {
974        self.proxy = Some(enabled);
975        self
976    }
977
978    /// Enable or disable RSC (route short circuit).
979    pub fn rsc(mut self, enabled: bool) -> Self {
980        self.rsc = Some(enabled);
981        self
982    }
983
984    /// Enable or disable L2miss notifications.
985    pub fn l2miss(mut self, enabled: bool) -> Self {
986        self.l2miss = Some(enabled);
987        self
988    }
989
990    /// Enable or disable L3miss notifications.
991    pub fn l3miss(mut self, enabled: bool) -> Self {
992        self.l3miss = Some(enabled);
993        self
994    }
995
996    /// Enable or disable UDP checksum.
997    pub fn udp_csum(mut self, enabled: bool) -> Self {
998        self.udp_csum = Some(enabled);
999        self
1000    }
1001}
1002
1003impl LinkConfig for VxlanLink {
1004    fn name(&self) -> &str {
1005        &self.name
1006    }
1007
1008    fn kind(&self) -> &str {
1009        "vxlan"
1010    }
1011
1012    fn parent_ref(&self) -> Option<&InterfaceRef> {
1013        self.dev.as_ref()
1014    }
1015
1016    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
1017        // Add interface name
1018        write_ifname(builder, &self.name);
1019
1020        // Add optional attributes
1021        if let Some(mtu) = self.mtu {
1022            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
1023        }
1024        if let Some(ref addr) = self.address {
1025            builder.append_attr(IflaAttr::Address as u16, addr);
1026        }
1027
1028        // IFLA_LINKINFO
1029        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
1030        builder.append_attr_str(IflaInfo::Kind as u16, "vxlan");
1031
1032        // IFLA_INFO_DATA
1033        let data = builder.nest_start(IflaInfo::Data as u16);
1034
1035        // VNI (required)
1036        builder.append_attr_u32(vxlan::IFLA_VXLAN_ID, self.vni);
1037
1038        // Local address
1039        if let Some(addr) = self.local {
1040            builder.append_attr(vxlan::IFLA_VXLAN_LOCAL, &addr.octets());
1041        }
1042
1043        // Remote/Group
1044        if let Some(addr) = self.remote {
1045            builder.append_attr(vxlan::IFLA_VXLAN_GROUP, &addr.octets());
1046        } else if let Some(addr) = self.group {
1047            builder.append_attr(vxlan::IFLA_VXLAN_GROUP, &addr.octets());
1048        }
1049
1050        // Underlying device (use resolved parent_index if dev was set)
1051        if let Some(idx) = parent_index {
1052            builder.append_attr_u32(vxlan::IFLA_VXLAN_LINK, idx);
1053        }
1054
1055        // Port
1056        if let Some(port) = self.port {
1057            builder.append_attr_u16_be(vxlan::IFLA_VXLAN_PORT, port);
1058        }
1059
1060        // Port range
1061        if let Some((low, high)) = self.port_range {
1062            let range = [low.to_be(), high.to_be()];
1063            // SAFETY: [u16; 2] is 4 bytes with no padding; pointer is valid for the array lifetime.
1064            let range_bytes = unsafe { std::slice::from_raw_parts(range.as_ptr() as *const u8, 4) };
1065            builder.append_attr(vxlan::IFLA_VXLAN_PORT_RANGE, range_bytes);
1066        }
1067
1068        // TTL
1069        if let Some(ttl) = self.ttl {
1070            builder.append_attr_u8(vxlan::IFLA_VXLAN_TTL, ttl);
1071        }
1072
1073        // TOS
1074        if let Some(tos) = self.tos {
1075            builder.append_attr_u8(vxlan::IFLA_VXLAN_TOS, tos);
1076        }
1077
1078        // Boolean options
1079        if let Some(enabled) = self.learning {
1080            builder.append_attr_u8(vxlan::IFLA_VXLAN_LEARNING, if enabled { 1 } else { 0 });
1081        }
1082        if let Some(enabled) = self.proxy {
1083            builder.append_attr_u8(vxlan::IFLA_VXLAN_PROXY, if enabled { 1 } else { 0 });
1084        }
1085        if let Some(enabled) = self.rsc {
1086            builder.append_attr_u8(vxlan::IFLA_VXLAN_RSC, if enabled { 1 } else { 0 });
1087        }
1088        if let Some(enabled) = self.l2miss {
1089            builder.append_attr_u8(vxlan::IFLA_VXLAN_L2MISS, if enabled { 1 } else { 0 });
1090        }
1091        if let Some(enabled) = self.l3miss {
1092            builder.append_attr_u8(vxlan::IFLA_VXLAN_L3MISS, if enabled { 1 } else { 0 });
1093        }
1094        if let Some(enabled) = self.udp_csum {
1095            builder.append_attr_u8(vxlan::IFLA_VXLAN_UDP_CSUM, if enabled { 1 } else { 0 });
1096        }
1097
1098        builder.nest_end(data);
1099        builder.nest_end(linkinfo);
1100    }
1101}
1102
1103// ============================================================================
1104// Macvlan Link
1105// ============================================================================
1106
1107/// Macvlan mode.
1108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1109#[non_exhaustive]
1110pub enum MacvlanMode {
1111    /// Private mode - no communication with other macvlans
1112    Private = 1,
1113    /// VEPA mode - traffic goes through external switch
1114    Vepa = 2,
1115    /// Bridge mode - communication between macvlans
1116    Bridge = 4,
1117    /// Passthrough mode - single macvlan on parent
1118    Passthru = 8,
1119    /// Source mode - filter by source MAC
1120    Source = 16,
1121}
1122
1123/// Configuration for a macvlan interface.
1124///
1125/// Macvlan creates virtual interfaces with their own MAC addresses on a parent device.
1126///
1127/// # Example
1128///
1129/// ```ignore
1130/// use nlink::netlink::link::{MacvlanLink, MacvlanMode};
1131///
1132/// let macvlan = MacvlanLink::new("macvlan0", "eth0")
1133///     .mode(MacvlanMode::Bridge);
1134///
1135/// conn.add_link(macvlan).await?;
1136/// ```
1137#[derive(Debug, Clone)]
1138#[must_use = "builders do nothing unless used"]
1139pub struct MacvlanLink {
1140    name: String,
1141    parent: InterfaceRef,
1142    mode: MacvlanMode,
1143    mtu: Option<u32>,
1144    address: Option<[u8; 6]>,
1145}
1146
1147/// Macvlan-specific attributes
1148mod macvlan {
1149    pub const IFLA_MACVLAN_MODE: u16 = 1;
1150}
1151
1152impl MacvlanLink {
1153    /// Create a new macvlan interface configuration.
1154    pub fn new(name: impl Into<String>, parent: impl Into<String>) -> Self {
1155        Self {
1156            name: name.into(),
1157            parent: InterfaceRef::Name(parent.into()),
1158            mode: MacvlanMode::Bridge,
1159            mtu: None,
1160            address: None,
1161        }
1162    }
1163
1164    /// Create a new macvlan interface with parent specified by index.
1165    ///
1166    /// This is the namespace-safe variant that avoids reading from /sys/class/net/.
1167    pub fn with_parent_index(name: impl Into<String>, parent_index: u32) -> Self {
1168        Self {
1169            name: name.into(),
1170            parent: InterfaceRef::Index(parent_index),
1171            mode: MacvlanMode::Bridge,
1172            mtu: None,
1173            address: None,
1174        }
1175    }
1176
1177    /// Set the macvlan mode.
1178    pub fn mode(mut self, mode: MacvlanMode) -> Self {
1179        self.mode = mode;
1180        self
1181    }
1182
1183    /// Set the MTU.
1184    pub fn mtu(mut self, mtu: u32) -> Self {
1185        self.mtu = Some(mtu);
1186        self
1187    }
1188
1189    /// Set the MAC address.
1190    pub fn address(mut self, addr: [u8; 6]) -> Self {
1191        self.address = Some(addr);
1192        self
1193    }
1194}
1195
1196impl LinkConfig for MacvlanLink {
1197    fn name(&self) -> &str {
1198        &self.name
1199    }
1200
1201    fn kind(&self) -> &str {
1202        "macvlan"
1203    }
1204
1205    fn parent_ref(&self) -> Option<&InterfaceRef> {
1206        Some(&self.parent)
1207    }
1208
1209    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
1210        // Add interface name
1211        write_ifname(builder, &self.name);
1212
1213        // Link to parent
1214        let idx = parent_index.expect("MacvlanLink requires parent_index");
1215        builder.append_attr_u32(IflaAttr::Link as u16, idx);
1216
1217        // Add optional attributes
1218        if let Some(mtu) = self.mtu {
1219            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
1220        }
1221        if let Some(ref addr) = self.address {
1222            builder.append_attr(IflaAttr::Address as u16, addr);
1223        }
1224
1225        // IFLA_LINKINFO
1226        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
1227        builder.append_attr_str(IflaInfo::Kind as u16, "macvlan");
1228
1229        // IFLA_INFO_DATA
1230        let data = builder.nest_start(IflaInfo::Data as u16);
1231        builder.append_attr_u32(macvlan::IFLA_MACVLAN_MODE, self.mode as u32);
1232        builder.nest_end(data);
1233
1234        builder.nest_end(linkinfo);
1235    }
1236}
1237
1238// ============================================================================
1239// Ipvlan Link
1240// ============================================================================
1241
1242/// Ipvlan mode.
1243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1244#[non_exhaustive]
1245pub enum IpvlanMode {
1246    /// L2 mode
1247    L2 = 0,
1248    /// L3 mode
1249    L3 = 1,
1250    /// L3S mode (L3 with source check)
1251    L3S = 2,
1252}
1253
1254/// Ipvlan flags.
1255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1256#[non_exhaustive]
1257pub enum IpvlanFlags {
1258    /// Bridge mode
1259    Bridge = 0,
1260    /// Private mode
1261    Private = 1,
1262    /// VEPA mode
1263    Vepa = 2,
1264}
1265
1266/// Configuration for an ipvlan interface.
1267///
1268/// Ipvlan is similar to macvlan but shares the parent's MAC address.
1269///
1270/// # Example
1271///
1272/// ```ignore
1273/// use nlink::netlink::link::{IpvlanLink, IpvlanMode};
1274///
1275/// let ipvlan = IpvlanLink::new("ipvlan0", "eth0")
1276///     .mode(IpvlanMode::L3);
1277///
1278/// conn.add_link(ipvlan).await?;
1279/// ```
1280#[derive(Debug, Clone)]
1281#[must_use = "builders do nothing unless used"]
1282pub struct IpvlanLink {
1283    name: String,
1284    parent: InterfaceRef,
1285    mode: IpvlanMode,
1286    flags: IpvlanFlags,
1287    mtu: Option<u32>,
1288}
1289
1290/// Ipvlan-specific attributes
1291mod ipvlan {
1292    pub const IFLA_IPVLAN_MODE: u16 = 1;
1293    pub const IFLA_IPVLAN_FLAGS: u16 = 2;
1294}
1295
1296impl IpvlanLink {
1297    /// Create a new ipvlan interface configuration.
1298    pub fn new(name: impl Into<String>, parent: impl Into<String>) -> Self {
1299        Self {
1300            name: name.into(),
1301            parent: InterfaceRef::Name(parent.into()),
1302            mode: IpvlanMode::L3,
1303            flags: IpvlanFlags::Bridge,
1304            mtu: None,
1305        }
1306    }
1307
1308    /// Create a new ipvlan interface with parent specified by index.
1309    ///
1310    /// This is the namespace-safe variant that avoids reading from /sys/class/net/.
1311    pub fn with_parent_index(name: impl Into<String>, parent_index: u32) -> Self {
1312        Self {
1313            name: name.into(),
1314            parent: InterfaceRef::Index(parent_index),
1315            mode: IpvlanMode::L3,
1316            flags: IpvlanFlags::Bridge,
1317            mtu: None,
1318        }
1319    }
1320
1321    /// Set the ipvlan mode.
1322    pub fn mode(mut self, mode: IpvlanMode) -> Self {
1323        self.mode = mode;
1324        self
1325    }
1326
1327    /// Set the ipvlan flags.
1328    pub fn flags(mut self, flags: IpvlanFlags) -> Self {
1329        self.flags = flags;
1330        self
1331    }
1332
1333    /// Set the MTU.
1334    pub fn mtu(mut self, mtu: u32) -> Self {
1335        self.mtu = Some(mtu);
1336        self
1337    }
1338}
1339
1340impl LinkConfig for IpvlanLink {
1341    fn name(&self) -> &str {
1342        &self.name
1343    }
1344
1345    fn kind(&self) -> &str {
1346        "ipvlan"
1347    }
1348
1349    fn parent_ref(&self) -> Option<&InterfaceRef> {
1350        Some(&self.parent)
1351    }
1352
1353    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
1354        // Add interface name
1355        write_ifname(builder, &self.name);
1356
1357        // Link to parent
1358        let idx = parent_index.expect("IpvlanLink requires parent_index");
1359        builder.append_attr_u32(IflaAttr::Link as u16, idx);
1360
1361        // Add optional attributes
1362        if let Some(mtu) = self.mtu {
1363            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
1364        }
1365
1366        // IFLA_LINKINFO
1367        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
1368        builder.append_attr_str(IflaInfo::Kind as u16, "ipvlan");
1369
1370        // IFLA_INFO_DATA
1371        let data = builder.nest_start(IflaInfo::Data as u16);
1372        builder.append_attr_u16(ipvlan::IFLA_IPVLAN_MODE, self.mode as u16);
1373        builder.append_attr_u16(ipvlan::IFLA_IPVLAN_FLAGS, self.flags as u16);
1374        builder.nest_end(data);
1375
1376        builder.nest_end(linkinfo);
1377    }
1378}
1379
1380// ============================================================================
1381// IFB Link
1382// ============================================================================
1383
1384/// Configuration for an IFB (Intermediate Functional Block) interface.
1385///
1386/// IFB devices are used in conjunction with TC to redirect traffic for
1387/// ingress shaping. They act as a pseudo-interface where you can attach
1388/// qdiscs to shape incoming traffic.
1389///
1390/// # Example
1391///
1392/// ```ignore
1393/// use nlink::netlink::link::IfbLink;
1394///
1395/// let ifb = IfbLink::new("ifb0");
1396/// conn.add_link(ifb).await?;
1397///
1398/// // Then redirect ingress traffic to ifb0 for shaping
1399/// ```
1400#[derive(Debug, Clone)]
1401#[must_use = "builders do nothing unless used"]
1402pub struct IfbLink {
1403    name: String,
1404    mtu: Option<u32>,
1405}
1406
1407impl IfbLink {
1408    /// Create a new IFB interface configuration.
1409    pub fn new(name: impl Into<String>) -> Self {
1410        Self {
1411            name: name.into(),
1412            mtu: None,
1413        }
1414    }
1415
1416    /// Set the MTU for this interface.
1417    pub fn mtu(mut self, mtu: u32) -> Self {
1418        self.mtu = Some(mtu);
1419        self
1420    }
1421}
1422
1423impl LinkConfig for IfbLink {
1424    fn name(&self) -> &str {
1425        &self.name
1426    }
1427
1428    fn kind(&self) -> &str {
1429        "ifb"
1430    }
1431
1432    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
1433        write_simple_link(builder, &self.name, "ifb", self.mtu, None);
1434    }
1435}
1436
1437// ============================================================================
1438// Macvtap Link
1439// ============================================================================
1440
1441/// Configuration for a macvtap interface.
1442///
1443/// Macvtap is similar to macvlan but provides a tap-like interface
1444/// that can be used by userspace programs (like QEMU/KVM for VM networking).
1445///
1446/// # Example
1447///
1448/// ```ignore
1449/// use nlink::netlink::link::{MacvtapLink, MacvlanMode};
1450///
1451/// let macvtap = MacvtapLink::new("macvtap0", "eth0")
1452///     .mode(MacvlanMode::Bridge);
1453///
1454/// conn.add_link(macvtap).await?;
1455/// ```
1456#[derive(Debug, Clone)]
1457#[must_use = "builders do nothing unless used"]
1458pub struct MacvtapLink {
1459    name: String,
1460    parent: InterfaceRef,
1461    mode: MacvlanMode,
1462    mtu: Option<u32>,
1463    address: Option<[u8; 6]>,
1464}
1465
1466impl MacvtapLink {
1467    /// Create a new macvtap interface configuration.
1468    pub fn new(name: impl Into<String>, parent: impl Into<String>) -> Self {
1469        Self {
1470            name: name.into(),
1471            parent: InterfaceRef::Name(parent.into()),
1472            mode: MacvlanMode::Bridge,
1473            mtu: None,
1474            address: None,
1475        }
1476    }
1477
1478    /// Create a new macvtap interface with parent specified by index.
1479    ///
1480    /// This is the namespace-safe variant that avoids reading from /sys/class/net/.
1481    pub fn with_parent_index(name: impl Into<String>, parent_index: u32) -> Self {
1482        Self {
1483            name: name.into(),
1484            parent: InterfaceRef::Index(parent_index),
1485            mode: MacvlanMode::Bridge,
1486            mtu: None,
1487            address: None,
1488        }
1489    }
1490
1491    /// Set the macvtap mode.
1492    pub fn mode(mut self, mode: MacvlanMode) -> Self {
1493        self.mode = mode;
1494        self
1495    }
1496
1497    /// Set the MTU.
1498    pub fn mtu(mut self, mtu: u32) -> Self {
1499        self.mtu = Some(mtu);
1500        self
1501    }
1502
1503    /// Set the MAC address.
1504    pub fn address(mut self, addr: [u8; 6]) -> Self {
1505        self.address = Some(addr);
1506        self
1507    }
1508}
1509
1510impl LinkConfig for MacvtapLink {
1511    fn name(&self) -> &str {
1512        &self.name
1513    }
1514
1515    fn kind(&self) -> &str {
1516        "macvtap"
1517    }
1518
1519    fn parent_ref(&self) -> Option<&InterfaceRef> {
1520        Some(&self.parent)
1521    }
1522
1523    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
1524        // Add interface name
1525        write_ifname(builder, &self.name);
1526
1527        // Link to parent
1528        let idx = parent_index.expect("MacvtapLink requires parent_index");
1529        builder.append_attr_u32(IflaAttr::Link as u16, idx);
1530
1531        // Add optional attributes
1532        if let Some(mtu) = self.mtu {
1533            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
1534        }
1535        if let Some(ref addr) = self.address {
1536            builder.append_attr(IflaAttr::Address as u16, addr);
1537        }
1538
1539        // IFLA_LINKINFO
1540        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
1541        builder.append_attr_str(IflaInfo::Kind as u16, "macvtap");
1542
1543        // IFLA_INFO_DATA - uses same attributes as macvlan
1544        let data = builder.nest_start(IflaInfo::Data as u16);
1545        builder.append_attr_u32(macvlan::IFLA_MACVLAN_MODE, self.mode as u32);
1546        builder.nest_end(data);
1547
1548        builder.nest_end(linkinfo);
1549    }
1550}
1551
1552// ============================================================================
1553// Geneve Link
1554// ============================================================================
1555
1556/// Configuration for a Geneve (Generic Network Virtualization Encapsulation) interface.
1557///
1558/// Geneve is an overlay network encapsulation protocol similar to VXLAN but
1559/// with a more flexible TLV-based option mechanism.
1560///
1561/// # Example
1562///
1563/// ```ignore
1564/// use nlink::netlink::link::GeneveLink;
1565/// use std::net::Ipv4Addr;
1566///
1567/// let geneve = GeneveLink::new("geneve0", 100)
1568///     .remote(Ipv4Addr::new(192, 168, 1, 100))
1569///     .port(6081);
1570///
1571/// conn.add_link(geneve).await?;
1572/// ```
1573#[derive(Debug, Clone)]
1574#[must_use = "builders do nothing unless used"]
1575pub struct GeneveLink {
1576    name: String,
1577    vni: u32,
1578    mtu: Option<u32>,
1579    /// Remote IPv4 address
1580    remote: Option<Ipv4Addr>,
1581    /// Remote IPv6 address
1582    remote6: Option<std::net::Ipv6Addr>,
1583    /// TTL
1584    ttl: Option<u8>,
1585    /// TTL inherit from inner packet
1586    ttl_inherit: bool,
1587    /// TOS
1588    tos: Option<u8>,
1589    /// Don't Fragment setting
1590    df: Option<GeneveDf>,
1591    /// Flow label for IPv6
1592    label: Option<u32>,
1593    /// UDP destination port (default 6081)
1594    port: Option<u16>,
1595    /// Collect metadata mode (for BPF)
1596    collect_metadata: bool,
1597    /// UDP checksum
1598    udp_csum: Option<bool>,
1599    /// Zero UDP checksum for IPv6 TX
1600    udp6_zero_csum_tx: Option<bool>,
1601    /// Zero UDP checksum for IPv6 RX
1602    udp6_zero_csum_rx: Option<bool>,
1603    /// Inherit inner protocol
1604    inner_proto_inherit: bool,
1605}
1606
1607/// Geneve DF (Don't Fragment) setting.
1608#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1609#[non_exhaustive]
1610pub enum GeneveDf {
1611    /// Don't set DF
1612    Unset = 0,
1613    /// Set DF
1614    Set = 1,
1615    /// Inherit from inner packet
1616    Inherit = 2,
1617}
1618
1619/// Geneve-specific attributes (IFLA_GENEVE_*)
1620mod geneve {
1621    pub const IFLA_GENEVE_ID: u16 = 1;
1622    pub const IFLA_GENEVE_REMOTE: u16 = 2;
1623    pub const IFLA_GENEVE_TTL: u16 = 3;
1624    pub const IFLA_GENEVE_TOS: u16 = 4;
1625    pub const IFLA_GENEVE_PORT: u16 = 5;
1626    pub const IFLA_GENEVE_COLLECT_METADATA: u16 = 6;
1627    pub const IFLA_GENEVE_REMOTE6: u16 = 7;
1628    pub const IFLA_GENEVE_UDP_CSUM: u16 = 8;
1629    pub const IFLA_GENEVE_UDP_ZERO_CSUM6_TX: u16 = 9;
1630    pub const IFLA_GENEVE_UDP_ZERO_CSUM6_RX: u16 = 10;
1631    pub const IFLA_GENEVE_LABEL: u16 = 11;
1632    pub const IFLA_GENEVE_TTL_INHERIT: u16 = 12;
1633    pub const IFLA_GENEVE_DF: u16 = 13;
1634    pub const IFLA_GENEVE_INNER_PROTO_INHERIT: u16 = 14;
1635}
1636
1637impl GeneveLink {
1638    /// Create a new Geneve interface configuration.
1639    ///
1640    /// # Arguments
1641    ///
1642    /// * `name` - Name for the Geneve interface
1643    /// * `vni` - Virtual Network Identifier (0-16777215)
1644    pub fn new(name: impl Into<String>, vni: u32) -> Self {
1645        Self {
1646            name: name.into(),
1647            vni,
1648            mtu: None,
1649            remote: None,
1650            remote6: None,
1651            ttl: None,
1652            ttl_inherit: false,
1653            tos: None,
1654            df: None,
1655            label: None,
1656            port: None,
1657            collect_metadata: false,
1658            udp_csum: None,
1659            udp6_zero_csum_tx: None,
1660            udp6_zero_csum_rx: None,
1661            inner_proto_inherit: false,
1662        }
1663    }
1664
1665    /// Set the MTU.
1666    pub fn mtu(mut self, mtu: u32) -> Self {
1667        self.mtu = Some(mtu);
1668        self
1669    }
1670
1671    /// Set the remote IPv4 address.
1672    pub fn remote(mut self, addr: Ipv4Addr) -> Self {
1673        self.remote = Some(addr);
1674        self.remote6 = None;
1675        self
1676    }
1677
1678    /// Set the remote IPv6 address.
1679    pub fn remote6(mut self, addr: std::net::Ipv6Addr) -> Self {
1680        self.remote6 = Some(addr);
1681        self.remote = None;
1682        self
1683    }
1684
1685    /// Set the TTL.
1686    pub fn ttl(mut self, ttl: u8) -> Self {
1687        self.ttl = Some(ttl);
1688        self.ttl_inherit = false;
1689        self
1690    }
1691
1692    /// Inherit TTL from inner packet.
1693    pub fn ttl_inherit(mut self) -> Self {
1694        self.ttl_inherit = true;
1695        self.ttl = None;
1696        self
1697    }
1698
1699    /// Set the TOS.
1700    pub fn tos(mut self, tos: u8) -> Self {
1701        self.tos = Some(tos);
1702        self
1703    }
1704
1705    /// Set the Don't Fragment behavior.
1706    pub fn df(mut self, df: GeneveDf) -> Self {
1707        self.df = Some(df);
1708        self
1709    }
1710
1711    /// Set the flow label for IPv6.
1712    pub fn label(mut self, label: u32) -> Self {
1713        self.label = Some(label);
1714        self
1715    }
1716
1717    /// Set the UDP destination port.
1718    pub fn port(mut self, port: u16) -> Self {
1719        self.port = Some(port);
1720        self
1721    }
1722
1723    /// Enable collect metadata mode (for BPF programs).
1724    pub fn collect_metadata(mut self) -> Self {
1725        self.collect_metadata = true;
1726        self
1727    }
1728
1729    /// Set UDP checksum.
1730    pub fn udp_csum(mut self, enabled: bool) -> Self {
1731        self.udp_csum = Some(enabled);
1732        self
1733    }
1734
1735    /// Set zero UDP checksum for IPv6 TX.
1736    pub fn udp6_zero_csum_tx(mut self, enabled: bool) -> Self {
1737        self.udp6_zero_csum_tx = Some(enabled);
1738        self
1739    }
1740
1741    /// Set zero UDP checksum for IPv6 RX.
1742    pub fn udp6_zero_csum_rx(mut self, enabled: bool) -> Self {
1743        self.udp6_zero_csum_rx = Some(enabled);
1744        self
1745    }
1746
1747    /// Enable inner protocol inheritance.
1748    pub fn inner_proto_inherit(mut self) -> Self {
1749        self.inner_proto_inherit = true;
1750        self
1751    }
1752}
1753
1754impl LinkConfig for GeneveLink {
1755    fn name(&self) -> &str {
1756        &self.name
1757    }
1758
1759    fn kind(&self) -> &str {
1760        "geneve"
1761    }
1762
1763    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
1764        // Add interface name
1765        write_ifname(builder, &self.name);
1766
1767        if let Some(mtu) = self.mtu {
1768            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
1769        }
1770
1771        // IFLA_LINKINFO
1772        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
1773        builder.append_attr_str(IflaInfo::Kind as u16, "geneve");
1774
1775        // IFLA_INFO_DATA
1776        let data = builder.nest_start(IflaInfo::Data as u16);
1777
1778        // VNI (required)
1779        builder.append_attr_u32(geneve::IFLA_GENEVE_ID, self.vni);
1780
1781        // Remote address
1782        if let Some(addr) = self.remote {
1783            builder.append_attr(geneve::IFLA_GENEVE_REMOTE, &addr.octets());
1784        } else if let Some(addr) = self.remote6 {
1785            builder.append_attr(geneve::IFLA_GENEVE_REMOTE6, &addr.octets());
1786        }
1787
1788        // TTL
1789        if self.ttl_inherit {
1790            builder.append_attr_u8(geneve::IFLA_GENEVE_TTL_INHERIT, 1);
1791        } else if let Some(ttl) = self.ttl {
1792            builder.append_attr_u8(geneve::IFLA_GENEVE_TTL, ttl);
1793        }
1794
1795        // TOS
1796        if let Some(tos) = self.tos {
1797            builder.append_attr_u8(geneve::IFLA_GENEVE_TOS, tos);
1798        }
1799
1800        // DF
1801        if let Some(df) = self.df {
1802            builder.append_attr_u8(geneve::IFLA_GENEVE_DF, df as u8);
1803        }
1804
1805        // Flow label
1806        if let Some(label) = self.label {
1807            builder.append_attr_u32(geneve::IFLA_GENEVE_LABEL, label);
1808        }
1809
1810        // Port
1811        if let Some(port) = self.port {
1812            builder.append_attr_u16_be(geneve::IFLA_GENEVE_PORT, port);
1813        }
1814
1815        // Collect metadata
1816        if self.collect_metadata {
1817            builder.append_attr_empty(geneve::IFLA_GENEVE_COLLECT_METADATA);
1818        }
1819
1820        // UDP checksum options
1821        if let Some(enabled) = self.udp_csum {
1822            builder.append_attr_u8(geneve::IFLA_GENEVE_UDP_CSUM, if enabled { 1 } else { 0 });
1823        }
1824        if let Some(enabled) = self.udp6_zero_csum_tx {
1825            builder.append_attr_u8(
1826                geneve::IFLA_GENEVE_UDP_ZERO_CSUM6_TX,
1827                if enabled { 1 } else { 0 },
1828            );
1829        }
1830        if let Some(enabled) = self.udp6_zero_csum_rx {
1831            builder.append_attr_u8(
1832                geneve::IFLA_GENEVE_UDP_ZERO_CSUM6_RX,
1833                if enabled { 1 } else { 0 },
1834            );
1835        }
1836
1837        // Inner protocol inherit
1838        if self.inner_proto_inherit {
1839            builder.append_attr_empty(geneve::IFLA_GENEVE_INNER_PROTO_INHERIT);
1840        }
1841
1842        builder.nest_end(data);
1843        builder.nest_end(linkinfo);
1844    }
1845}
1846
1847// ============================================================================
1848// Bareudp Link
1849// ============================================================================
1850
1851/// Configuration for a Bareudp interface.
1852///
1853/// Bareudp is a minimal UDP tunneling driver that provides UDP encapsulation
1854/// for various L3 protocols like MPLS and IP.
1855///
1856/// # Example
1857///
1858/// ```ignore
1859/// use nlink::netlink::link::BareudpLink;
1860///
1861/// // Create a bareudp tunnel for MPLS
1862/// let bareudp = BareudpLink::new("bareudp0", 6635, 0x8847);
1863/// conn.add_link(bareudp).await?;
1864/// ```
1865#[derive(Debug, Clone)]
1866#[must_use = "builders do nothing unless used"]
1867pub struct BareudpLink {
1868    name: String,
1869    /// UDP destination port
1870    port: u16,
1871    /// EtherType of the tunneled protocol
1872    ethertype: u16,
1873    /// Minimum source port
1874    srcport_min: Option<u16>,
1875    /// Multiprotocol mode
1876    multiproto_mode: bool,
1877    mtu: Option<u32>,
1878}
1879
1880/// Bareudp-specific attributes (IFLA_BAREUDP_*)
1881mod bareudp {
1882    pub const IFLA_BAREUDP_PORT: u16 = 1;
1883    pub const IFLA_BAREUDP_ETHERTYPE: u16 = 2;
1884    pub const IFLA_BAREUDP_SRCPORT_MIN: u16 = 3;
1885    pub const IFLA_BAREUDP_MULTIPROTO_MODE: u16 = 4;
1886}
1887
1888impl BareudpLink {
1889    /// Create a new Bareudp interface configuration.
1890    ///
1891    /// # Arguments
1892    ///
1893    /// * `name` - Name for the interface
1894    /// * `port` - UDP destination port
1895    /// * `ethertype` - EtherType of tunneled protocol (e.g., 0x8847 for MPLS unicast)
1896    pub fn new(name: impl Into<String>, port: u16, ethertype: u16) -> Self {
1897        Self {
1898            name: name.into(),
1899            port,
1900            ethertype,
1901            srcport_min: None,
1902            multiproto_mode: false,
1903            mtu: None,
1904        }
1905    }
1906
1907    /// Set the minimum source port.
1908    pub fn srcport_min(mut self, port: u16) -> Self {
1909        self.srcport_min = Some(port);
1910        self
1911    }
1912
1913    /// Enable multiprotocol mode.
1914    pub fn multiproto_mode(mut self) -> Self {
1915        self.multiproto_mode = true;
1916        self
1917    }
1918
1919    /// Set the MTU.
1920    pub fn mtu(mut self, mtu: u32) -> Self {
1921        self.mtu = Some(mtu);
1922        self
1923    }
1924}
1925
1926impl LinkConfig for BareudpLink {
1927    fn name(&self) -> &str {
1928        &self.name
1929    }
1930
1931    fn kind(&self) -> &str {
1932        "bareudp"
1933    }
1934
1935    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
1936        // Add interface name
1937        write_ifname(builder, &self.name);
1938
1939        if let Some(mtu) = self.mtu {
1940            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
1941        }
1942
1943        // IFLA_LINKINFO
1944        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
1945        builder.append_attr_str(IflaInfo::Kind as u16, "bareudp");
1946
1947        // IFLA_INFO_DATA
1948        let data = builder.nest_start(IflaInfo::Data as u16);
1949
1950        // Port (required) - network byte order
1951        builder.append_attr_u16_be(bareudp::IFLA_BAREUDP_PORT, self.port);
1952
1953        // Ethertype (required) - network byte order
1954        builder.append_attr_u16_be(bareudp::IFLA_BAREUDP_ETHERTYPE, self.ethertype);
1955
1956        // Source port min
1957        if let Some(srcport) = self.srcport_min {
1958            builder.append_attr_u16(bareudp::IFLA_BAREUDP_SRCPORT_MIN, srcport);
1959        }
1960
1961        // Multiproto mode
1962        if self.multiproto_mode {
1963            builder.append_attr_empty(bareudp::IFLA_BAREUDP_MULTIPROTO_MODE);
1964        }
1965
1966        builder.nest_end(data);
1967        builder.nest_end(linkinfo);
1968    }
1969}
1970
1971// ============================================================================
1972// Netkit Link
1973// ============================================================================
1974
1975/// Configuration for a Netkit interface.
1976///
1977/// Netkit devices are similar to veth but designed for BPF program attachment.
1978/// They provide a more efficient path for BPF-based packet processing.
1979///
1980/// # Example
1981///
1982/// ```ignore
1983/// use nlink::netlink::link::{NetkitLink, NetkitMode, NetkitPolicy};
1984///
1985/// let netkit = NetkitLink::new("nk0", "nk1")
1986///     .mode(NetkitMode::L3)
1987///     .policy(NetkitPolicy::Forward);
1988///
1989/// conn.add_link(netkit).await?;
1990/// ```
1991#[derive(Debug, Clone)]
1992#[must_use = "builders do nothing unless used"]
1993pub struct NetkitLink {
1994    name: String,
1995    peer_name: String,
1996    mode: Option<NetkitMode>,
1997    policy: Option<NetkitPolicy>,
1998    peer_policy: Option<NetkitPolicy>,
1999    scrub: Option<NetkitScrub>,
2000    peer_scrub: Option<NetkitScrub>,
2001    mtu: Option<u32>,
2002}
2003
2004/// Netkit operating mode.
2005#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2006#[non_exhaustive]
2007pub enum NetkitMode {
2008    /// L2 mode (Ethernet frames)
2009    L2 = 0,
2010    /// L3 mode (IP packets)
2011    L3 = 1,
2012}
2013
2014/// Netkit default policy.
2015#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2016#[non_exhaustive]
2017pub enum NetkitPolicy {
2018    /// Forward packets (default)
2019    Forward = 0,
2020    /// Blackhole (drop)
2021    Blackhole = 2,
2022}
2023
2024/// Netkit scrub mode.
2025#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2026pub enum NetkitScrub {
2027    /// No scrubbing
2028    None = 0,
2029    /// Default scrubbing
2030    Default = 1,
2031}
2032
2033/// Netkit-specific attributes (IFLA_NETKIT_*)
2034mod netkit {
2035    pub const IFLA_NETKIT_PEER_INFO: u16 = 1;
2036    pub const IFLA_NETKIT_MODE: u16 = 4;
2037    pub const IFLA_NETKIT_POLICY: u16 = 2;
2038    pub const IFLA_NETKIT_PEER_POLICY: u16 = 3;
2039    pub const IFLA_NETKIT_SCRUB: u16 = 5;
2040    pub const IFLA_NETKIT_PEER_SCRUB: u16 = 6;
2041}
2042
2043impl NetkitLink {
2044    /// Create a new Netkit pair configuration.
2045    ///
2046    /// # Arguments
2047    ///
2048    /// * `name` - Name for the primary interface
2049    /// * `peer_name` - Name for the peer interface
2050    pub fn new(name: impl Into<String>, peer_name: impl Into<String>) -> Self {
2051        Self {
2052            name: name.into(),
2053            peer_name: peer_name.into(),
2054            mode: None,
2055            policy: None,
2056            peer_policy: None,
2057            scrub: None,
2058            peer_scrub: None,
2059            mtu: None,
2060        }
2061    }
2062
2063    /// Set the operating mode.
2064    pub fn mode(mut self, mode: NetkitMode) -> Self {
2065        self.mode = Some(mode);
2066        self
2067    }
2068
2069    /// Set the default policy for the primary interface.
2070    pub fn policy(mut self, policy: NetkitPolicy) -> Self {
2071        self.policy = Some(policy);
2072        self
2073    }
2074
2075    /// Set the default policy for the peer interface.
2076    pub fn peer_policy(mut self, policy: NetkitPolicy) -> Self {
2077        self.peer_policy = Some(policy);
2078        self
2079    }
2080
2081    /// Set the scrub mode for the primary interface.
2082    pub fn scrub(mut self, scrub: NetkitScrub) -> Self {
2083        self.scrub = Some(scrub);
2084        self
2085    }
2086
2087    /// Set the scrub mode for the peer interface.
2088    pub fn peer_scrub(mut self, scrub: NetkitScrub) -> Self {
2089        self.peer_scrub = Some(scrub);
2090        self
2091    }
2092
2093    /// Set the MTU.
2094    pub fn mtu(mut self, mtu: u32) -> Self {
2095        self.mtu = Some(mtu);
2096        self
2097    }
2098}
2099
2100impl LinkConfig for NetkitLink {
2101    fn name(&self) -> &str {
2102        &self.name
2103    }
2104
2105    fn kind(&self) -> &str {
2106        "netkit"
2107    }
2108
2109    fn peer_name(&self) -> Option<&str> {
2110        Some(&self.peer_name)
2111    }
2112
2113    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
2114        // Add interface name
2115        write_ifname(builder, &self.name);
2116
2117        if let Some(mtu) = self.mtu {
2118            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
2119        }
2120
2121        // IFLA_LINKINFO
2122        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
2123        builder.append_attr_str(IflaInfo::Kind as u16, "netkit");
2124
2125        // IFLA_INFO_DATA
2126        let data = builder.nest_start(IflaInfo::Data as u16);
2127
2128        // Mode
2129        if let Some(mode) = self.mode {
2130            builder.append_attr_u32(netkit::IFLA_NETKIT_MODE, mode as u32);
2131        }
2132
2133        // Policy
2134        if let Some(policy) = self.policy {
2135            builder.append_attr_u32(netkit::IFLA_NETKIT_POLICY, policy as u32);
2136        }
2137
2138        // Peer policy
2139        if let Some(policy) = self.peer_policy {
2140            builder.append_attr_u32(netkit::IFLA_NETKIT_PEER_POLICY, policy as u32);
2141        }
2142
2143        // Scrub
2144        if let Some(scrub) = self.scrub {
2145            builder.append_attr_u32(netkit::IFLA_NETKIT_SCRUB, scrub as u32);
2146        }
2147
2148        // Peer scrub
2149        if let Some(scrub) = self.peer_scrub {
2150            builder.append_attr_u32(netkit::IFLA_NETKIT_PEER_SCRUB, scrub as u32);
2151        }
2152
2153        // Peer info (nested ifinfomsg for peer)
2154        let peer_info = builder.nest_start(netkit::IFLA_NETKIT_PEER_INFO);
2155        let peer_ifinfo = IfInfoMsg::new();
2156        builder.append(&peer_ifinfo);
2157        builder.append_attr_str(IflaAttr::Ifname as u16, &self.peer_name);
2158        builder.nest_end(peer_info);
2159
2160        builder.nest_end(data);
2161        builder.nest_end(linkinfo);
2162    }
2163}
2164
2165// ============================================================================
2166// Nlmon Link (Netlink Monitor)
2167// ============================================================================
2168
2169/// Configuration for a netlink monitor interface.
2170///
2171/// Nlmon interfaces capture netlink traffic for debugging and analysis.
2172/// All netlink messages passing through the system can be captured.
2173///
2174/// # Example
2175///
2176/// ```ignore
2177/// use nlink::netlink::link::NlmonLink;
2178///
2179/// let nlmon = NlmonLink::new("nlmon0");
2180/// conn.add_link(nlmon).await?;
2181///
2182/// // Now use tcpdump or similar to capture netlink traffic:
2183/// // tcpdump -i nlmon0 -w netlink.pcap
2184/// ```
2185#[derive(Debug, Clone)]
2186#[must_use = "builders do nothing unless used"]
2187pub struct NlmonLink {
2188    name: String,
2189}
2190
2191impl NlmonLink {
2192    /// Create a new netlink monitor interface configuration.
2193    pub fn new(name: impl Into<String>) -> Self {
2194        Self { name: name.into() }
2195    }
2196}
2197
2198impl LinkConfig for NlmonLink {
2199    fn name(&self) -> &str {
2200        &self.name
2201    }
2202
2203    fn kind(&self) -> &str {
2204        "nlmon"
2205    }
2206
2207    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
2208        write_simple_link(builder, &self.name, "nlmon", None, None);
2209    }
2210}
2211
2212// ============================================================================
2213// VirtWifi Link
2214// ============================================================================
2215
2216/// Configuration for a virtual WiFi interface.
2217///
2218/// VirtWifi creates a virtual wireless interface on top of an existing
2219/// Ethernet interface. This is useful for testing WiFi-dependent applications
2220/// without actual WiFi hardware.
2221///
2222/// # Example
2223///
2224/// ```ignore
2225/// use nlink::netlink::link::VirtWifiLink;
2226///
2227/// let vwifi = VirtWifiLink::new("vwifi0", "eth0");
2228/// conn.add_link(vwifi).await?;
2229/// ```
2230#[derive(Debug, Clone)]
2231#[must_use = "builders do nothing unless used"]
2232pub struct VirtWifiLink {
2233    name: String,
2234    link: InterfaceRef,
2235}
2236
2237impl VirtWifiLink {
2238    /// Create a new virtual WiFi interface configuration.
2239    ///
2240    /// # Arguments
2241    ///
2242    /// * `name` - Name for the virtual WiFi interface
2243    /// * `link` - Name of the underlying Ethernet interface
2244    pub fn new(name: impl Into<String>, link: impl Into<String>) -> Self {
2245        Self {
2246            name: name.into(),
2247            link: InterfaceRef::Name(link.into()),
2248        }
2249    }
2250
2251    /// Create a new virtual WiFi interface with underlying link specified by index.
2252    ///
2253    /// This is the namespace-safe variant that avoids reading from /sys/class/net/.
2254    pub fn with_link_index(name: impl Into<String>, link_index: u32) -> Self {
2255        Self {
2256            name: name.into(),
2257            link: InterfaceRef::Index(link_index),
2258        }
2259    }
2260}
2261
2262impl LinkConfig for VirtWifiLink {
2263    fn name(&self) -> &str {
2264        &self.name
2265    }
2266
2267    fn kind(&self) -> &str {
2268        "virt_wifi"
2269    }
2270
2271    fn parent_ref(&self) -> Option<&InterfaceRef> {
2272        Some(&self.link)
2273    }
2274
2275    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
2276        // Add interface name
2277        write_ifname(builder, &self.name);
2278
2279        // Set the underlying link
2280        let idx = parent_index.expect("VirtWifiLink requires link index");
2281        builder.append_attr_u32(IflaAttr::Link as u16, idx);
2282
2283        // IFLA_LINKINFO
2284        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
2285        builder.append_attr_str(IflaInfo::Kind as u16, "virt_wifi");
2286        builder.nest_end(linkinfo);
2287    }
2288}
2289
2290// ============================================================================
2291// VTI Link (Virtual Tunnel Interface)
2292// ============================================================================
2293
2294/// Configuration for a VTI (Virtual Tunnel Interface) for IPv4.
2295///
2296/// VTI interfaces are used with IPsec to create route-based VPNs.
2297/// Traffic routed through the VTI is automatically encrypted/decrypted.
2298///
2299/// # Example
2300///
2301/// ```ignore
2302/// use nlink::netlink::link::VtiLink;
2303/// use std::net::Ipv4Addr;
2304///
2305/// let vti = VtiLink::new("vti0")
2306///     .local(Ipv4Addr::new(10, 0, 0, 1))
2307///     .remote(Ipv4Addr::new(10, 0, 0, 2))
2308///     .ikey(100)
2309///     .okey(100);
2310///
2311/// conn.add_link(vti).await?;
2312/// ```
2313#[derive(Debug, Clone)]
2314#[must_use = "builders do nothing unless used"]
2315pub struct VtiLink {
2316    name: String,
2317    local: Option<Ipv4Addr>,
2318    remote: Option<Ipv4Addr>,
2319    ikey: Option<u32>,
2320    okey: Option<u32>,
2321    link: Option<InterfaceRef>,
2322}
2323
2324/// VTI-specific attributes (IFLA_VTI_*)
2325#[allow(dead_code)]
2326mod vti {
2327    pub const IFLA_VTI_LINK: u16 = 1;
2328    pub const IFLA_VTI_IKEY: u16 = 2;
2329    pub const IFLA_VTI_OKEY: u16 = 3;
2330    pub const IFLA_VTI_LOCAL: u16 = 4;
2331    pub const IFLA_VTI_REMOTE: u16 = 5;
2332    pub const IFLA_VTI_FWMARK: u16 = 6;
2333}
2334
2335impl VtiLink {
2336    /// Create a new VTI interface configuration.
2337    pub fn new(name: impl Into<String>) -> Self {
2338        Self {
2339            name: name.into(),
2340            local: None,
2341            remote: None,
2342            ikey: None,
2343            okey: None,
2344            link: None,
2345        }
2346    }
2347
2348    /// Set the local (source) address.
2349    pub fn local(mut self, addr: Ipv4Addr) -> Self {
2350        self.local = Some(addr);
2351        self
2352    }
2353
2354    /// Set the remote (destination) address.
2355    pub fn remote(mut self, addr: Ipv4Addr) -> Self {
2356        self.remote = Some(addr);
2357        self
2358    }
2359
2360    /// Set the input key (for identifying incoming traffic).
2361    pub fn ikey(mut self, key: u32) -> Self {
2362        self.ikey = Some(key);
2363        self
2364    }
2365
2366    /// Set the output key (for marking outgoing traffic).
2367    pub fn okey(mut self, key: u32) -> Self {
2368        self.okey = Some(key);
2369        self
2370    }
2371
2372    /// Set the underlying link device by name.
2373    pub fn link(mut self, link: impl Into<String>) -> Self {
2374        self.link = Some(InterfaceRef::Name(link.into()));
2375        self
2376    }
2377
2378    /// Set the underlying link device by index.
2379    ///
2380    /// This is the namespace-safe variant that avoids reading from /sys/class/net/.
2381    pub fn link_index(mut self, index: u32) -> Self {
2382        self.link = Some(InterfaceRef::Index(index));
2383        self
2384    }
2385}
2386
2387impl LinkConfig for VtiLink {
2388    fn name(&self) -> &str {
2389        &self.name
2390    }
2391
2392    fn kind(&self) -> &str {
2393        "vti"
2394    }
2395
2396    fn parent_ref(&self) -> Option<&InterfaceRef> {
2397        self.link.as_ref()
2398    }
2399
2400    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
2401        // Add interface name
2402        write_ifname(builder, &self.name);
2403
2404        // Set underlying link if specified
2405        if let Some(idx) = parent_index {
2406            builder.append_attr_u32(IflaAttr::Link as u16, idx);
2407        }
2408
2409        // IFLA_LINKINFO
2410        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
2411        builder.append_attr_str(IflaInfo::Kind as u16, "vti");
2412
2413        // IFLA_INFO_DATA
2414        let data = builder.nest_start(IflaInfo::Data as u16);
2415
2416        if let Some(local) = self.local {
2417            builder.append_attr(vti::IFLA_VTI_LOCAL, &local.octets());
2418        }
2419        if let Some(remote) = self.remote {
2420            builder.append_attr(vti::IFLA_VTI_REMOTE, &remote.octets());
2421        }
2422        if let Some(ikey) = self.ikey {
2423            builder.append_attr_u32_be(vti::IFLA_VTI_IKEY, ikey);
2424        }
2425        if let Some(okey) = self.okey {
2426            builder.append_attr_u32_be(vti::IFLA_VTI_OKEY, okey);
2427        }
2428
2429        builder.nest_end(data);
2430        builder.nest_end(linkinfo);
2431    }
2432}
2433
2434// ============================================================================
2435// VTI6 Link (Virtual Tunnel Interface for IPv6)
2436// ============================================================================
2437
2438/// Configuration for a VTI6 (Virtual Tunnel Interface) for IPv6.
2439///
2440/// Similar to VTI but for IPv6 tunnels.
2441///
2442/// # Example
2443///
2444/// ```ignore
2445/// use nlink::netlink::link::Vti6Link;
2446/// use std::net::Ipv6Addr;
2447///
2448/// let vti6 = Vti6Link::new("vti6_0")
2449///     .local(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1))
2450///     .remote(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2));
2451///
2452/// conn.add_link(vti6).await?;
2453/// ```
2454#[derive(Debug, Clone)]
2455#[must_use = "builders do nothing unless used"]
2456pub struct Vti6Link {
2457    name: String,
2458    local: Option<std::net::Ipv6Addr>,
2459    remote: Option<std::net::Ipv6Addr>,
2460    ikey: Option<u32>,
2461    okey: Option<u32>,
2462    link: Option<InterfaceRef>,
2463}
2464
2465impl Vti6Link {
2466    /// Create a new VTI6 interface configuration.
2467    pub fn new(name: impl Into<String>) -> Self {
2468        Self {
2469            name: name.into(),
2470            local: None,
2471            remote: None,
2472            ikey: None,
2473            okey: None,
2474            link: None,
2475        }
2476    }
2477
2478    /// Set the local (source) address.
2479    pub fn local(mut self, addr: std::net::Ipv6Addr) -> Self {
2480        self.local = Some(addr);
2481        self
2482    }
2483
2484    /// Set the remote (destination) address.
2485    pub fn remote(mut self, addr: std::net::Ipv6Addr) -> Self {
2486        self.remote = Some(addr);
2487        self
2488    }
2489
2490    /// Set the input key (for identifying incoming traffic).
2491    pub fn ikey(mut self, key: u32) -> Self {
2492        self.ikey = Some(key);
2493        self
2494    }
2495
2496    /// Set the output key (for marking outgoing traffic).
2497    pub fn okey(mut self, key: u32) -> Self {
2498        self.okey = Some(key);
2499        self
2500    }
2501
2502    /// Set the underlying link device by name.
2503    pub fn link(mut self, link: impl Into<String>) -> Self {
2504        self.link = Some(InterfaceRef::Name(link.into()));
2505        self
2506    }
2507
2508    /// Set the underlying link device by index.
2509    ///
2510    /// This is the namespace-safe variant that avoids reading from /sys/class/net/.
2511    pub fn link_index(mut self, index: u32) -> Self {
2512        self.link = Some(InterfaceRef::Index(index));
2513        self
2514    }
2515}
2516
2517impl LinkConfig for Vti6Link {
2518    fn name(&self) -> &str {
2519        &self.name
2520    }
2521
2522    fn kind(&self) -> &str {
2523        "vti6"
2524    }
2525
2526    fn parent_ref(&self) -> Option<&InterfaceRef> {
2527        self.link.as_ref()
2528    }
2529
2530    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
2531        // Add interface name
2532        write_ifname(builder, &self.name);
2533
2534        // Set underlying link if specified
2535        if let Some(idx) = parent_index {
2536            builder.append_attr_u32(IflaAttr::Link as u16, idx);
2537        }
2538
2539        // IFLA_LINKINFO
2540        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
2541        builder.append_attr_str(IflaInfo::Kind as u16, "vti6");
2542
2543        // IFLA_INFO_DATA - VTI6 uses same attributes as VTI
2544        let data = builder.nest_start(IflaInfo::Data as u16);
2545
2546        if let Some(local) = self.local {
2547            builder.append_attr(vti::IFLA_VTI_LOCAL, &local.octets());
2548        }
2549        if let Some(remote) = self.remote {
2550            builder.append_attr(vti::IFLA_VTI_REMOTE, &remote.octets());
2551        }
2552        if let Some(ikey) = self.ikey {
2553            builder.append_attr_u32_be(vti::IFLA_VTI_IKEY, ikey);
2554        }
2555        if let Some(okey) = self.okey {
2556            builder.append_attr_u32_be(vti::IFLA_VTI_OKEY, okey);
2557        }
2558
2559        builder.nest_end(data);
2560        builder.nest_end(linkinfo);
2561    }
2562}
2563
2564// ============================================================================
2565// IP6GRE Link
2566// ============================================================================
2567
2568/// Configuration for an IP6GRE (IPv6 GRE tunnel) interface.
2569///
2570/// IP6GRE creates a GRE tunnel over IPv6. This is useful for
2571/// encapsulating IPv4 or IPv6 traffic over an IPv6 network.
2572///
2573/// # Example
2574///
2575/// ```ignore
2576/// use nlink::netlink::link::Ip6GreLink;
2577/// use std::net::Ipv6Addr;
2578///
2579/// let gre = Ip6GreLink::new("ip6gre0")
2580///     .local(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1))
2581///     .remote(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2))
2582///     .ttl(64);
2583///
2584/// conn.add_link(gre).await?;
2585/// ```
2586#[derive(Debug, Clone)]
2587#[must_use = "builders do nothing unless used"]
2588pub struct Ip6GreLink {
2589    name: String,
2590    local: Option<std::net::Ipv6Addr>,
2591    remote: Option<std::net::Ipv6Addr>,
2592    ttl: Option<u8>,
2593    encap_limit: Option<u8>,
2594    flowinfo: Option<u32>,
2595    flags: Option<u32>,
2596    link: Option<InterfaceRef>,
2597}
2598
2599/// IFLA_GRE_* attributes (shared by gre, gretap, ip6gre, ip6gretap, erspan).
2600/// Verified against linux/if_tunnel.h (kernel 6.19.6).
2601#[allow(dead_code)]
2602mod gre_attr {
2603    pub const IFLA_GRE_LINK: u16 = 1;
2604    pub const IFLA_GRE_IFLAGS: u16 = 2;
2605    pub const IFLA_GRE_OFLAGS: u16 = 3;
2606    pub const IFLA_GRE_IKEY: u16 = 4;
2607    pub const IFLA_GRE_OKEY: u16 = 5;
2608    pub const IFLA_GRE_LOCAL: u16 = 6;
2609    pub const IFLA_GRE_REMOTE: u16 = 7;
2610    pub const IFLA_GRE_TTL: u16 = 8;
2611    pub const IFLA_GRE_TOS: u16 = 9;
2612    pub const IFLA_GRE_PMTUDISC: u16 = 10;
2613    pub const IFLA_GRE_ENCAP_LIMIT: u16 = 11;
2614    pub const IFLA_GRE_FLOWINFO: u16 = 12;
2615    pub const IFLA_GRE_FLAGS: u16 = 13;
2616    pub const IFLA_GRE_ENCAP_TYPE: u16 = 14;
2617    pub const IFLA_GRE_ENCAP_FLAGS: u16 = 15;
2618    pub const IFLA_GRE_ENCAP_SPORT: u16 = 16;
2619    pub const IFLA_GRE_ENCAP_DPORT: u16 = 17;
2620    pub const IFLA_GRE_COLLECT_METADATA: u16 = 18;
2621    pub const IFLA_GRE_IGNORE_DF: u16 = 19;
2622    pub const IFLA_GRE_FWMARK: u16 = 20;
2623
2624    /// GRE_KEY flag for IFLA_GRE_IFLAGS/OFLAGS.
2625    pub const GRE_KEY: u16 = 0x2000;
2626}
2627
2628/// IFLA_IPTUN_* attributes (for ipip and sit tunnels).
2629/// Separate enum from IFLA_GRE_* — different numeric values.
2630/// Verified against linux/if_tunnel.h (kernel 6.19.6).
2631#[allow(dead_code)]
2632mod iptun_attr {
2633    pub const IFLA_IPTUN_LINK: u16 = 1;
2634    pub const IFLA_IPTUN_LOCAL: u16 = 2;
2635    pub const IFLA_IPTUN_REMOTE: u16 = 3;
2636    pub const IFLA_IPTUN_TTL: u16 = 4;
2637    pub const IFLA_IPTUN_TOS: u16 = 5;
2638    pub const IFLA_IPTUN_ENCAP_LIMIT: u16 = 6;
2639    pub const IFLA_IPTUN_FLOWINFO: u16 = 7;
2640    pub const IFLA_IPTUN_FLAGS: u16 = 8;
2641    pub const IFLA_IPTUN_PROTO: u16 = 9;
2642    pub const IFLA_IPTUN_PMTUDISC: u16 = 10;
2643    pub const IFLA_IPTUN_ENCAP_TYPE: u16 = 15;
2644    pub const IFLA_IPTUN_ENCAP_FLAGS: u16 = 16;
2645    pub const IFLA_IPTUN_ENCAP_SPORT: u16 = 17;
2646    pub const IFLA_IPTUN_ENCAP_DPORT: u16 = 18;
2647    pub const IFLA_IPTUN_COLLECT_METADATA: u16 = 19;
2648    pub const IFLA_IPTUN_FWMARK: u16 = 20;
2649
2650    /// ISATAP flag for SIT tunnels (IFLA_IPTUN_FLAGS).
2651    pub const SIT_ISATAP: u16 = 0x0001;
2652}
2653
2654impl Ip6GreLink {
2655    /// Create a new IP6GRE interface configuration.
2656    pub fn new(name: impl Into<String>) -> Self {
2657        Self {
2658            name: name.into(),
2659            local: None,
2660            remote: None,
2661            ttl: None,
2662            encap_limit: None,
2663            flowinfo: None,
2664            flags: None,
2665            link: None,
2666        }
2667    }
2668
2669    /// Set the local (source) address.
2670    pub fn local(mut self, addr: std::net::Ipv6Addr) -> Self {
2671        self.local = Some(addr);
2672        self
2673    }
2674
2675    /// Set the remote (destination) address.
2676    pub fn remote(mut self, addr: std::net::Ipv6Addr) -> Self {
2677        self.remote = Some(addr);
2678        self
2679    }
2680
2681    /// Set the TTL (hop limit).
2682    pub fn ttl(mut self, ttl: u8) -> Self {
2683        self.ttl = Some(ttl);
2684        self
2685    }
2686
2687    /// Set the encapsulation limit.
2688    pub fn encap_limit(mut self, limit: u8) -> Self {
2689        self.encap_limit = Some(limit);
2690        self
2691    }
2692
2693    /// Set the flow label.
2694    pub fn flowinfo(mut self, flowinfo: u32) -> Self {
2695        self.flowinfo = Some(flowinfo);
2696        self
2697    }
2698
2699    /// Set the underlying link device by name.
2700    pub fn link(mut self, link: impl Into<String>) -> Self {
2701        self.link = Some(InterfaceRef::Name(link.into()));
2702        self
2703    }
2704
2705    /// Set the underlying link device by index.
2706    ///
2707    /// This is the namespace-safe variant that avoids reading from /sys/class/net/.
2708    pub fn link_index(mut self, index: u32) -> Self {
2709        self.link = Some(InterfaceRef::Index(index));
2710        self
2711    }
2712}
2713
2714impl LinkConfig for Ip6GreLink {
2715    fn name(&self) -> &str {
2716        &self.name
2717    }
2718
2719    fn kind(&self) -> &str {
2720        "ip6gre"
2721    }
2722
2723    fn parent_ref(&self) -> Option<&InterfaceRef> {
2724        self.link.as_ref()
2725    }
2726
2727    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
2728        // Add interface name
2729        write_ifname(builder, &self.name);
2730
2731        // IFLA_LINKINFO
2732        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
2733        builder.append_attr_str(IflaInfo::Kind as u16, "ip6gre");
2734
2735        // IFLA_INFO_DATA
2736        let data = builder.nest_start(IflaInfo::Data as u16);
2737
2738        if let Some(idx) = parent_index {
2739            builder.append_attr_u32(gre_attr::IFLA_GRE_LINK, idx);
2740        }
2741        if let Some(local) = self.local {
2742            builder.append_attr(gre_attr::IFLA_GRE_LOCAL, &local.octets());
2743        }
2744        if let Some(remote) = self.remote {
2745            builder.append_attr(gre_attr::IFLA_GRE_REMOTE, &remote.octets());
2746        }
2747        if let Some(ttl) = self.ttl {
2748            builder.append_attr_u8(gre_attr::IFLA_GRE_TTL, ttl);
2749        }
2750        if let Some(limit) = self.encap_limit {
2751            builder.append_attr_u8(gre_attr::IFLA_GRE_ENCAP_LIMIT, limit);
2752        }
2753        if let Some(flowinfo) = self.flowinfo {
2754            builder.append_attr_u32_be(gre_attr::IFLA_GRE_FLOWINFO, flowinfo);
2755        }
2756        if let Some(flags) = self.flags {
2757            builder.append_attr_u32(gre_attr::IFLA_GRE_FLAGS, flags);
2758        }
2759
2760        builder.nest_end(data);
2761        builder.nest_end(linkinfo);
2762    }
2763}
2764
2765// ============================================================================
2766// IP6GRETAP Link
2767// ============================================================================
2768
2769/// Configuration for an IP6GRETAP (IPv6 GRE TAP tunnel) interface.
2770///
2771/// Similar to IP6GRE but operates at Layer 2, encapsulating Ethernet frames.
2772/// This is useful for bridging networks over an IPv6 tunnel.
2773///
2774/// # Example
2775///
2776/// ```ignore
2777/// use nlink::netlink::link::Ip6GretapLink;
2778/// use std::net::Ipv6Addr;
2779///
2780/// let gretap = Ip6GretapLink::new("ip6gretap0")
2781///     .local(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1))
2782///     .remote(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2))
2783///     .ttl(64);
2784///
2785/// conn.add_link(gretap).await?;
2786/// ```
2787#[derive(Debug, Clone)]
2788#[must_use = "builders do nothing unless used"]
2789pub struct Ip6GretapLink {
2790    name: String,
2791    local: Option<std::net::Ipv6Addr>,
2792    remote: Option<std::net::Ipv6Addr>,
2793    ttl: Option<u8>,
2794    encap_limit: Option<u8>,
2795    flowinfo: Option<u32>,
2796    link: Option<InterfaceRef>,
2797}
2798
2799impl Ip6GretapLink {
2800    /// Create a new IP6GRETAP interface configuration.
2801    pub fn new(name: impl Into<String>) -> Self {
2802        Self {
2803            name: name.into(),
2804            local: None,
2805            remote: None,
2806            ttl: None,
2807            encap_limit: None,
2808            flowinfo: None,
2809            link: None,
2810        }
2811    }
2812
2813    /// Set the local (source) address.
2814    pub fn local(mut self, addr: std::net::Ipv6Addr) -> Self {
2815        self.local = Some(addr);
2816        self
2817    }
2818
2819    /// Set the remote (destination) address.
2820    pub fn remote(mut self, addr: std::net::Ipv6Addr) -> Self {
2821        self.remote = Some(addr);
2822        self
2823    }
2824
2825    /// Set the TTL (hop limit).
2826    pub fn ttl(mut self, ttl: u8) -> Self {
2827        self.ttl = Some(ttl);
2828        self
2829    }
2830
2831    /// Set the encapsulation limit.
2832    pub fn encap_limit(mut self, limit: u8) -> Self {
2833        self.encap_limit = Some(limit);
2834        self
2835    }
2836
2837    /// Set the flow label.
2838    pub fn flowinfo(mut self, flowinfo: u32) -> Self {
2839        self.flowinfo = Some(flowinfo);
2840        self
2841    }
2842
2843    /// Set the underlying link device by name.
2844    pub fn link(mut self, link: impl Into<String>) -> Self {
2845        self.link = Some(InterfaceRef::Name(link.into()));
2846        self
2847    }
2848
2849    /// Set the underlying link device by index.
2850    ///
2851    /// This is the namespace-safe variant that avoids reading from /sys/class/net/.
2852    pub fn link_index(mut self, index: u32) -> Self {
2853        self.link = Some(InterfaceRef::Index(index));
2854        self
2855    }
2856}
2857
2858impl LinkConfig for Ip6GretapLink {
2859    fn name(&self) -> &str {
2860        &self.name
2861    }
2862
2863    fn kind(&self) -> &str {
2864        "ip6gretap"
2865    }
2866
2867    fn parent_ref(&self) -> Option<&InterfaceRef> {
2868        self.link.as_ref()
2869    }
2870
2871    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
2872        // Add interface name
2873        write_ifname(builder, &self.name);
2874
2875        // IFLA_LINKINFO
2876        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
2877        builder.append_attr_str(IflaInfo::Kind as u16, "ip6gretap");
2878
2879        // IFLA_INFO_DATA - uses same attributes as ip6gre
2880        let data = builder.nest_start(IflaInfo::Data as u16);
2881
2882        if let Some(idx) = parent_index {
2883            builder.append_attr_u32(gre_attr::IFLA_GRE_LINK, idx);
2884        }
2885        if let Some(local) = self.local {
2886            builder.append_attr(gre_attr::IFLA_GRE_LOCAL, &local.octets());
2887        }
2888        if let Some(remote) = self.remote {
2889            builder.append_attr(gre_attr::IFLA_GRE_REMOTE, &remote.octets());
2890        }
2891        if let Some(ttl) = self.ttl {
2892            builder.append_attr_u8(gre_attr::IFLA_GRE_TTL, ttl);
2893        }
2894        if let Some(limit) = self.encap_limit {
2895            builder.append_attr_u8(gre_attr::IFLA_GRE_ENCAP_LIMIT, limit);
2896        }
2897        if let Some(flowinfo) = self.flowinfo {
2898            builder.append_attr_u32_be(gre_attr::IFLA_GRE_FLOWINFO, flowinfo);
2899        }
2900
2901        builder.nest_end(data);
2902        builder.nest_end(linkinfo);
2903    }
2904}
2905
2906// ============================================================================
2907// Bond Link
2908// ============================================================================
2909
2910/// Bond mode constants (deprecated, use `BondMode` enum instead).
2911#[deprecated(note = "use `BondMode` enum instead")]
2912pub mod bond_mode {
2913    pub const BALANCE_RR: u8 = 0;
2914    pub const ACTIVE_BACKUP: u8 = 1;
2915    pub const BALANCE_XOR: u8 = 2;
2916    pub const BROADCAST: u8 = 3;
2917    pub const LACP: u8 = 4; // 802.3ad
2918    pub const BALANCE_TLB: u8 = 5;
2919    pub const BALANCE_ALB: u8 = 6;
2920}
2921
2922/// Bonding mode.
2923///
2924/// Determines how traffic is distributed across slave interfaces.
2925#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2926#[repr(u8)]
2927#[non_exhaustive]
2928pub enum BondMode {
2929    /// Round-robin: packets transmitted in sequential order.
2930    BalanceRr = 0,
2931    /// Active-backup: only one slave active, failover on link failure.
2932    ActiveBackup = 1,
2933    /// XOR: transmit based on hash of source/destination.
2934    BalanceXor = 2,
2935    /// Broadcast: all packets on all slaves.
2936    Broadcast = 3,
2937    /// IEEE 802.3ad (LACP): dynamic link aggregation.
2938    Lacp = 4,
2939    /// Adaptive transmit load balancing.
2940    BalanceTlb = 5,
2941    /// Adaptive load balancing (RX + TX).
2942    BalanceAlb = 6,
2943}
2944
2945impl TryFrom<u8> for BondMode {
2946    type Error = super::Error;
2947    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
2948        match value {
2949            0 => Ok(Self::BalanceRr),
2950            1 => Ok(Self::ActiveBackup),
2951            2 => Ok(Self::BalanceXor),
2952            3 => Ok(Self::Broadcast),
2953            4 => Ok(Self::Lacp),
2954            5 => Ok(Self::BalanceTlb),
2955            6 => Ok(Self::BalanceAlb),
2956            _ => Err(super::Error::InvalidAttribute(format!(
2957                "unknown bond mode: {value}"
2958            ))),
2959        }
2960    }
2961}
2962
2963/// Transmit hash policy for XOR/LACP modes.
2964#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2965#[repr(u8)]
2966#[non_exhaustive]
2967pub enum XmitHashPolicy {
2968    /// Hash by L2 (MAC) addresses.
2969    Layer2 = 0,
2970    /// Hash by L3+L4 (IP + port).
2971    Layer34 = 1,
2972    /// Hash by L2+L3 (MAC + IP).
2973    Layer23 = 2,
2974    /// Hash by encapsulated L2+L3.
2975    Encap23 = 3,
2976    /// Hash by encapsulated L3+L4.
2977    Encap34 = 4,
2978    /// Hash by VLAN + source MAC.
2979    VlanSrcMac = 5,
2980}
2981
2982impl TryFrom<u8> for XmitHashPolicy {
2983    type Error = super::Error;
2984    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
2985        match value {
2986            0 => Ok(Self::Layer2),
2987            1 => Ok(Self::Layer34),
2988            2 => Ok(Self::Layer23),
2989            3 => Ok(Self::Encap23),
2990            4 => Ok(Self::Encap34),
2991            5 => Ok(Self::VlanSrcMac),
2992            _ => Err(super::Error::InvalidAttribute(format!(
2993                "unknown xmit hash policy: {value}"
2994            ))),
2995        }
2996    }
2997}
2998
2999/// LACP rate for 802.3ad mode.
3000#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3001#[repr(u8)]
3002#[non_exhaustive]
3003pub enum LacpRate {
3004    /// Send LACPDUs every 30 seconds.
3005    Slow = 0,
3006    /// Send LACPDUs every 1 second.
3007    Fast = 1,
3008}
3009
3010/// Primary slave reselection policy.
3011#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3012#[repr(u8)]
3013pub enum PrimaryReselect {
3014    /// Always reselect when a better slave comes up.
3015    Always = 0,
3016    /// Reselect only if the new slave is better.
3017    Better = 1,
3018    /// Reselect only on active slave failure.
3019    Failure = 2,
3020}
3021
3022/// Fail-over MAC address policy.
3023#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3024#[repr(u8)]
3025pub enum FailOverMac {
3026    /// Don't change MAC on failover.
3027    None = 0,
3028    /// Set bond MAC to active slave's MAC.
3029    Active = 1,
3030    /// Follow the current active slave's MAC.
3031    Follow = 2,
3032}
3033
3034/// ARP validation mode.
3035#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3036#[repr(u8)]
3037pub enum ArpValidate {
3038    /// No ARP validation.
3039    None = 0,
3040    /// Validate only on the active slave.
3041    Active = 1,
3042    /// Validate only on backup slaves.
3043    Backup = 2,
3044    /// Validate on all slaves.
3045    All = 3,
3046    /// Filter and validate on active.
3047    FilterActive = 4,
3048    /// Filter and validate on backup.
3049    FilterBackup = 5,
3050}
3051
3052/// Ad (802.3ad) selection logic.
3053#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3054#[repr(u8)]
3055pub enum AdSelect {
3056    /// Select by highest aggregator bandwidth.
3057    Stable = 0,
3058    /// Select by aggregator bandwidth.
3059    Bandwidth = 1,
3060    /// Select by number of ports.
3061    Count = 2,
3062}
3063
3064/// IFLA_BOND_* attribute constants (verified against linux/if_link.h, kernel 6.19.6).
3065#[allow(dead_code)]
3066mod bond_attr {
3067    pub const IFLA_BOND_MODE: u16 = 1;
3068    pub const IFLA_BOND_ACTIVE_SLAVE: u16 = 2;
3069    pub const IFLA_BOND_MIIMON: u16 = 3;
3070    pub const IFLA_BOND_UPDELAY: u16 = 4;
3071    pub const IFLA_BOND_DOWNDELAY: u16 = 5;
3072    pub const IFLA_BOND_USE_CARRIER: u16 = 6;
3073    pub const IFLA_BOND_ARP_INTERVAL: u16 = 7;
3074    pub const IFLA_BOND_ARP_IP_TARGET: u16 = 8;
3075    pub const IFLA_BOND_ARP_VALIDATE: u16 = 9;
3076    pub const IFLA_BOND_ARP_ALL_TARGETS: u16 = 10;
3077    pub const IFLA_BOND_PRIMARY: u16 = 11;
3078    pub const IFLA_BOND_PRIMARY_RESELECT: u16 = 12;
3079    pub const IFLA_BOND_FAIL_OVER_MAC: u16 = 13;
3080    pub const IFLA_BOND_XMIT_HASH_POLICY: u16 = 14;
3081    pub const IFLA_BOND_RESEND_IGMP: u16 = 15;
3082    pub const IFLA_BOND_NUM_PEER_NOTIF: u16 = 16;
3083    pub const IFLA_BOND_ALL_SLAVES_ACTIVE: u16 = 17;
3084    pub const IFLA_BOND_MIN_LINKS: u16 = 18;
3085    pub const IFLA_BOND_LP_INTERVAL: u16 = 19;
3086    pub const IFLA_BOND_PACKETS_PER_SLAVE: u16 = 20;
3087    pub const IFLA_BOND_AD_LACP_RATE: u16 = 21;
3088    pub const IFLA_BOND_AD_SELECT: u16 = 22;
3089    pub const IFLA_BOND_AD_INFO: u16 = 23;
3090    pub const IFLA_BOND_AD_ACTOR_SYS_PRIO: u16 = 24;
3091    pub const IFLA_BOND_AD_USER_PORT_KEY: u16 = 25;
3092    pub const IFLA_BOND_AD_ACTOR_SYSTEM: u16 = 26;
3093    pub const IFLA_BOND_TLB_DYNAMIC_LB: u16 = 27;
3094    pub const IFLA_BOND_PEER_NOTIF_DELAY: u16 = 28;
3095    pub const IFLA_BOND_AD_LACP_ACTIVE: u16 = 29;
3096    pub const IFLA_BOND_MISSED_MAX: u16 = 30;
3097    pub const IFLA_BOND_NS_IP6_TARGET: u16 = 31;
3098    pub const IFLA_BOND_COUPLED_CONTROL: u16 = 32;
3099}
3100
3101/// Configuration for a bonding (link aggregation) interface.
3102///
3103/// Supports all 33 kernel IFLA_BOND_* attributes with typed enums.
3104///
3105/// # Example
3106///
3107/// ```ignore
3108/// use nlink::netlink::link::{BondLink, BondMode, XmitHashPolicy, LacpRate};
3109///
3110/// // LACP bond with fast rate and layer3+4 hashing
3111/// let bond = BondLink::new("bond0")
3112///     .mode(BondMode::Lacp)
3113///     .miimon(100)
3114///     .lacp_rate(LacpRate::Fast)
3115///     .xmit_hash_policy(XmitHashPolicy::Layer34)
3116///     .min_links(1);
3117/// conn.add_link(bond).await?;
3118///
3119/// // Active-backup with ARP monitoring
3120/// let bond = BondLink::new("bond1")
3121///     .mode(BondMode::ActiveBackup)
3122///     .arp_interval(200)
3123///     .arp_ip_target(Ipv4Addr::new(192, 168, 1, 1))
3124///     .arp_validate(ArpValidate::All);
3125/// conn.add_link(bond).await?;
3126/// ```
3127#[derive(Debug, Clone)]
3128#[must_use = "builders do nothing unless used"]
3129pub struct BondLink {
3130    name: String,
3131    mode: BondMode,
3132    mtu: Option<u32>,
3133    address: Option<[u8; 6]>,
3134
3135    // MII monitoring
3136    miimon: Option<u32>,
3137    updelay: Option<u32>,
3138    downdelay: Option<u32>,
3139    use_carrier: Option<bool>,
3140
3141    // ARP monitoring
3142    arp_interval: Option<u32>,
3143    arp_ip_targets: Vec<Ipv4Addr>,
3144    arp_validate: Option<ArpValidate>,
3145    arp_all_targets: Option<u32>,
3146
3147    // Slave selection
3148    primary_reselect: Option<PrimaryReselect>,
3149    fail_over_mac: Option<FailOverMac>,
3150
3151    // Hashing / distribution
3152    xmit_hash_policy: Option<XmitHashPolicy>,
3153    min_links: Option<u32>,
3154    packets_per_slave: Option<u32>,
3155
3156    // 802.3ad (LACP) specific
3157    lacp_rate: Option<LacpRate>,
3158    ad_select: Option<AdSelect>,
3159    ad_actor_sys_prio: Option<u16>,
3160    ad_user_port_key: Option<u16>,
3161    ad_actor_system: Option<[u8; 6]>,
3162    lacp_active: Option<bool>,
3163
3164    // Misc
3165    all_slaves_active: Option<bool>,
3166    resend_igmp: Option<u32>,
3167    num_peer_notif: Option<u8>,
3168    lp_interval: Option<u32>,
3169    tlb_dynamic_lb: Option<bool>,
3170    peer_notif_delay: Option<u32>,
3171    missed_max: Option<u8>,
3172    coupled_control: Option<bool>,
3173}
3174
3175impl BondLink {
3176    /// Create a new bond interface with default mode (balance-rr).
3177    pub fn new(name: impl Into<String>) -> Self {
3178        Self {
3179            name: name.into(),
3180            mode: BondMode::BalanceRr,
3181            mtu: None,
3182            address: None,
3183            miimon: None,
3184            updelay: None,
3185            downdelay: None,
3186            use_carrier: None,
3187            arp_interval: None,
3188            arp_ip_targets: Vec::new(),
3189            arp_validate: None,
3190            arp_all_targets: None,
3191            primary_reselect: None,
3192            fail_over_mac: None,
3193            xmit_hash_policy: None,
3194            min_links: None,
3195            packets_per_slave: None,
3196            lacp_rate: None,
3197            ad_select: None,
3198            ad_actor_sys_prio: None,
3199            ad_user_port_key: None,
3200            ad_actor_system: None,
3201            lacp_active: None,
3202            all_slaves_active: None,
3203            resend_igmp: None,
3204            num_peer_notif: None,
3205            lp_interval: None,
3206            tlb_dynamic_lb: None,
3207            peer_notif_delay: None,
3208            missed_max: None,
3209            coupled_control: None,
3210        }
3211    }
3212
3213    /// Set the bonding mode.
3214    pub fn mode(mut self, mode: BondMode) -> Self {
3215        self.mode = mode;
3216        self
3217    }
3218
3219    /// Set the MII link monitoring interval in milliseconds.
3220    pub fn miimon(mut self, ms: u32) -> Self {
3221        self.miimon = Some(ms);
3222        self
3223    }
3224
3225    /// Set the delay before enabling a slave after link up (ms).
3226    pub fn updelay(mut self, ms: u32) -> Self {
3227        self.updelay = Some(ms);
3228        self
3229    }
3230
3231    /// Set the delay before disabling a slave after link down (ms).
3232    pub fn downdelay(mut self, ms: u32) -> Self {
3233        self.downdelay = Some(ms);
3234        self
3235    }
3236
3237    /// Use carrier state for link monitoring instead of MII/ethtool.
3238    pub fn use_carrier(mut self, enabled: bool) -> Self {
3239        self.use_carrier = Some(enabled);
3240        self
3241    }
3242
3243    /// Set the minimum number of links for the bond to be up.
3244    pub fn min_links(mut self, n: u32) -> Self {
3245        self.min_links = Some(n);
3246        self
3247    }
3248
3249    /// Set the transmit hash policy.
3250    pub fn xmit_hash_policy(mut self, policy: XmitHashPolicy) -> Self {
3251        self.xmit_hash_policy = Some(policy);
3252        self
3253    }
3254
3255    /// Set the LACP rate (for 802.3ad mode).
3256    pub fn lacp_rate(mut self, rate: LacpRate) -> Self {
3257        self.lacp_rate = Some(rate);
3258        self
3259    }
3260
3261    /// Set the ad selection logic (for 802.3ad mode).
3262    pub fn ad_select(mut self, select: AdSelect) -> Self {
3263        self.ad_select = Some(select);
3264        self
3265    }
3266
3267    /// Set the ARP monitoring interval in milliseconds.
3268    pub fn arp_interval(mut self, ms: u32) -> Self {
3269        self.arp_interval = Some(ms);
3270        self
3271    }
3272
3273    /// Add an ARP monitoring target IP (up to 16).
3274    pub fn arp_ip_target(mut self, addr: Ipv4Addr) -> Self {
3275        self.arp_ip_targets.push(addr);
3276        self
3277    }
3278
3279    /// Set the ARP validation mode.
3280    pub fn arp_validate(mut self, validate: ArpValidate) -> Self {
3281        self.arp_validate = Some(validate);
3282        self
3283    }
3284
3285    /// Set the primary slave reselection policy.
3286    pub fn primary_reselect(mut self, policy: PrimaryReselect) -> Self {
3287        self.primary_reselect = Some(policy);
3288        self
3289    }
3290
3291    /// Set the fail-over MAC address policy.
3292    pub fn fail_over_mac(mut self, policy: FailOverMac) -> Self {
3293        self.fail_over_mac = Some(policy);
3294        self
3295    }
3296
3297    /// Enable/disable all slaves active (for multicast/broadcast).
3298    pub fn all_slaves_active(mut self, enabled: bool) -> Self {
3299        self.all_slaves_active = Some(enabled);
3300        self
3301    }
3302
3303    /// Enable/disable TLB dynamic load balancing.
3304    pub fn tlb_dynamic_lb(mut self, enabled: bool) -> Self {
3305        self.tlb_dynamic_lb = Some(enabled);
3306        self
3307    }
3308
3309    /// Set the MTU.
3310    pub fn mtu(mut self, mtu: u32) -> Self {
3311        self.mtu = Some(mtu);
3312        self
3313    }
3314
3315    /// Set the MAC address.
3316    pub fn address(mut self, address: [u8; 6]) -> Self {
3317        self.address = Some(address);
3318        self
3319    }
3320
3321    /// Set 802.3ad actor system priority.
3322    pub fn ad_actor_sys_prio(mut self, prio: u16) -> Self {
3323        self.ad_actor_sys_prio = Some(prio);
3324        self
3325    }
3326
3327    /// Set 802.3ad user port key.
3328    pub fn ad_user_port_key(mut self, key: u16) -> Self {
3329        self.ad_user_port_key = Some(key);
3330        self
3331    }
3332
3333    /// Set 802.3ad actor system MAC address.
3334    pub fn ad_actor_system(mut self, mac: [u8; 6]) -> Self {
3335        self.ad_actor_system = Some(mac);
3336        self
3337    }
3338
3339    /// Enable/disable LACP active mode (for 802.3ad).
3340    pub fn lacp_active(mut self, enabled: bool) -> Self {
3341        self.lacp_active = Some(enabled);
3342        self
3343    }
3344
3345    /// Set the number of peer notifications after failover.
3346    pub fn num_peer_notif(mut self, n: u8) -> Self {
3347        self.num_peer_notif = Some(n);
3348        self
3349    }
3350
3351    /// Set the IGMP resend count after failover.
3352    pub fn resend_igmp(mut self, count: u32) -> Self {
3353        self.resend_igmp = Some(count);
3354        self
3355    }
3356
3357    /// Set the learning packets interval (ms).
3358    pub fn lp_interval(mut self, ms: u32) -> Self {
3359        self.lp_interval = Some(ms);
3360        self
3361    }
3362
3363    /// Set packets per slave for balance-rr mode.
3364    pub fn packets_per_slave(mut self, n: u32) -> Self {
3365        self.packets_per_slave = Some(n);
3366        self
3367    }
3368
3369    /// Set the peer notification delay (ms).
3370    pub fn peer_notif_delay(mut self, ms: u32) -> Self {
3371        self.peer_notif_delay = Some(ms);
3372        self
3373    }
3374
3375    /// Set the maximum number of missed MII monitoring intervals.
3376    pub fn missed_max(mut self, n: u8) -> Self {
3377        self.missed_max = Some(n);
3378        self
3379    }
3380
3381    /// Enable/disable coupled control (kernel 6.0+).
3382    pub fn coupled_control(mut self, enabled: bool) -> Self {
3383        self.coupled_control = Some(enabled);
3384        self
3385    }
3386}
3387
3388impl LinkConfig for BondLink {
3389    fn name(&self) -> &str {
3390        &self.name
3391    }
3392
3393    fn kind(&self) -> &str {
3394        "bond"
3395    }
3396
3397    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
3398        write_ifname(builder, &self.name);
3399
3400        if let Some(mtu) = self.mtu {
3401            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
3402        }
3403        if let Some(ref addr) = self.address {
3404            builder.append_attr(IflaAttr::Address as u16, addr);
3405        }
3406
3407        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
3408        builder.append_attr_str(IflaInfo::Kind as u16, "bond");
3409
3410        let data = builder.nest_start(IflaInfo::Data as u16);
3411        builder.append_attr_u8(bond_attr::IFLA_BOND_MODE, self.mode as u8);
3412
3413        if let Some(v) = self.miimon {
3414            builder.append_attr_u32(bond_attr::IFLA_BOND_MIIMON, v);
3415        }
3416        if let Some(v) = self.updelay {
3417            builder.append_attr_u32(bond_attr::IFLA_BOND_UPDELAY, v);
3418        }
3419        if let Some(v) = self.downdelay {
3420            builder.append_attr_u32(bond_attr::IFLA_BOND_DOWNDELAY, v);
3421        }
3422        if let Some(v) = self.use_carrier {
3423            builder.append_attr_u8(bond_attr::IFLA_BOND_USE_CARRIER, v as u8);
3424        }
3425        if let Some(v) = self.arp_interval {
3426            builder.append_attr_u32(bond_attr::IFLA_BOND_ARP_INTERVAL, v);
3427        }
3428        if let Some(v) = self.arp_validate {
3429            builder.append_attr_u32(bond_attr::IFLA_BOND_ARP_VALIDATE, v as u32);
3430        }
3431        if let Some(v) = self.arp_all_targets {
3432            builder.append_attr_u32(bond_attr::IFLA_BOND_ARP_ALL_TARGETS, v);
3433        }
3434        if let Some(v) = self.primary_reselect {
3435            builder.append_attr_u8(bond_attr::IFLA_BOND_PRIMARY_RESELECT, v as u8);
3436        }
3437        if let Some(v) = self.fail_over_mac {
3438            builder.append_attr_u8(bond_attr::IFLA_BOND_FAIL_OVER_MAC, v as u8);
3439        }
3440        if let Some(v) = self.xmit_hash_policy {
3441            builder.append_attr_u8(bond_attr::IFLA_BOND_XMIT_HASH_POLICY, v as u8);
3442        }
3443        if let Some(v) = self.resend_igmp {
3444            builder.append_attr_u32(bond_attr::IFLA_BOND_RESEND_IGMP, v);
3445        }
3446        if let Some(v) = self.num_peer_notif {
3447            builder.append_attr_u8(bond_attr::IFLA_BOND_NUM_PEER_NOTIF, v);
3448        }
3449        if let Some(v) = self.all_slaves_active {
3450            builder.append_attr_u8(bond_attr::IFLA_BOND_ALL_SLAVES_ACTIVE, v as u8);
3451        }
3452        if let Some(v) = self.min_links {
3453            builder.append_attr_u32(bond_attr::IFLA_BOND_MIN_LINKS, v);
3454        }
3455        if let Some(v) = self.lp_interval {
3456            builder.append_attr_u32(bond_attr::IFLA_BOND_LP_INTERVAL, v);
3457        }
3458        if let Some(v) = self.packets_per_slave {
3459            builder.append_attr_u32(bond_attr::IFLA_BOND_PACKETS_PER_SLAVE, v);
3460        }
3461        if let Some(v) = self.lacp_rate {
3462            builder.append_attr_u8(bond_attr::IFLA_BOND_AD_LACP_RATE, v as u8);
3463        }
3464        if let Some(v) = self.ad_select {
3465            builder.append_attr_u8(bond_attr::IFLA_BOND_AD_SELECT, v as u8);
3466        }
3467        if let Some(v) = self.ad_actor_sys_prio {
3468            builder.append_attr_u16(bond_attr::IFLA_BOND_AD_ACTOR_SYS_PRIO, v);
3469        }
3470        if let Some(v) = self.ad_user_port_key {
3471            builder.append_attr_u16(bond_attr::IFLA_BOND_AD_USER_PORT_KEY, v);
3472        }
3473        if let Some(ref mac) = self.ad_actor_system {
3474            builder.append_attr(bond_attr::IFLA_BOND_AD_ACTOR_SYSTEM, mac);
3475        }
3476        if let Some(v) = self.tlb_dynamic_lb {
3477            builder.append_attr_u8(bond_attr::IFLA_BOND_TLB_DYNAMIC_LB, v as u8);
3478        }
3479        if let Some(v) = self.peer_notif_delay {
3480            builder.append_attr_u32(bond_attr::IFLA_BOND_PEER_NOTIF_DELAY, v);
3481        }
3482        if let Some(v) = self.lacp_active {
3483            builder.append_attr_u8(bond_attr::IFLA_BOND_AD_LACP_ACTIVE, v as u8);
3484        }
3485        if let Some(v) = self.missed_max {
3486            builder.append_attr_u8(bond_attr::IFLA_BOND_MISSED_MAX, v);
3487        }
3488        if let Some(v) = self.coupled_control {
3489            builder.append_attr_u8(bond_attr::IFLA_BOND_COUPLED_CONTROL, v as u8);
3490        }
3491
3492        // ARP IP targets (nested)
3493        if !self.arp_ip_targets.is_empty() {
3494            let targets = builder.nest_start(bond_attr::IFLA_BOND_ARP_IP_TARGET);
3495            for (i, addr) in self.arp_ip_targets.iter().enumerate() {
3496                builder.append_attr(i as u16, &addr.octets());
3497            }
3498            builder.nest_end(targets);
3499        }
3500
3501        builder.nest_end(data);
3502        builder.nest_end(linkinfo);
3503    }
3504}
3505
3506// ============================================================================
3507// VRF Link
3508// ============================================================================
3509
3510/// VRF attribute constants.
3511mod vrf_attr {
3512    pub const IFLA_VRF_TABLE: u16 = 1;
3513}
3514
3515/// Configuration for a VRF (Virtual Routing and Forwarding) interface.
3516///
3517/// # Example
3518///
3519/// ```ignore
3520/// use nlink::netlink::link::VrfLink;
3521///
3522/// let vrf = VrfLink::new("vrf-red", 100);
3523///
3524/// conn.add_link(vrf).await?;
3525/// ```
3526#[derive(Debug, Clone)]
3527#[must_use = "builders do nothing unless used"]
3528pub struct VrfLink {
3529    name: String,
3530    table: u32,
3531    mtu: Option<u32>,
3532}
3533
3534impl VrfLink {
3535    /// Create a new VRF interface configuration.
3536    pub fn new(name: &str, table: u32) -> Self {
3537        Self {
3538            name: name.to_string(),
3539            table,
3540            mtu: None,
3541        }
3542    }
3543
3544    /// Set the MTU.
3545    pub fn mtu(mut self, mtu: u32) -> Self {
3546        self.mtu = Some(mtu);
3547        self
3548    }
3549}
3550
3551impl LinkConfig for VrfLink {
3552    fn name(&self) -> &str {
3553        &self.name
3554    }
3555
3556    fn kind(&self) -> &str {
3557        "vrf"
3558    }
3559
3560    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
3561        write_ifname(builder, &self.name);
3562
3563        if let Some(mtu) = self.mtu {
3564            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
3565        }
3566
3567        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
3568        builder.append_attr_str(IflaInfo::Kind as u16, "vrf");
3569
3570        let data = builder.nest_start(IflaInfo::Data as u16);
3571        builder.append_attr_u32(vrf_attr::IFLA_VRF_TABLE, self.table);
3572        builder.nest_end(data);
3573
3574        builder.nest_end(linkinfo);
3575    }
3576}
3577
3578// ============================================================================
3579// GRE Link (IPv4)
3580// ============================================================================
3581
3582/// Configuration for a GRE tunnel interface.
3583///
3584/// # Example
3585///
3586/// ```ignore
3587/// use nlink::netlink::link::GreLink;
3588/// use std::net::Ipv4Addr;
3589///
3590/// let gre = GreLink::new("gre1")
3591///     .remote(Ipv4Addr::new(192, 168, 1, 1))
3592///     .local(Ipv4Addr::new(192, 168, 1, 2))
3593///     .ttl(64);
3594///
3595/// conn.add_link(gre).await?;
3596/// ```
3597#[derive(Debug, Clone)]
3598#[must_use = "builders do nothing unless used"]
3599pub struct GreLink {
3600    name: String,
3601    local: Option<Ipv4Addr>,
3602    remote: Option<Ipv4Addr>,
3603    ttl: Option<u8>,
3604    tos: Option<u8>,
3605    ikey: Option<u32>,
3606    okey: Option<u32>,
3607    pmtudisc: Option<bool>,
3608    ignore_df: Option<bool>,
3609    fwmark: Option<u32>,
3610    mtu: Option<u32>,
3611    link: Option<InterfaceRef>,
3612}
3613
3614impl GreLink {
3615    /// Create a new GRE tunnel configuration.
3616    pub fn new(name: impl Into<String>) -> Self {
3617        Self {
3618            name: name.into(),
3619            local: None,
3620            remote: None,
3621            ttl: None,
3622            tos: None,
3623            ikey: None,
3624            okey: None,
3625            pmtudisc: None,
3626            ignore_df: None,
3627            fwmark: None,
3628            mtu: None,
3629            link: None,
3630        }
3631    }
3632
3633    /// Set the local endpoint address.
3634    pub fn local(mut self, addr: Ipv4Addr) -> Self {
3635        self.local = Some(addr);
3636        self
3637    }
3638
3639    /// Set the remote endpoint address.
3640    pub fn remote(mut self, addr: Ipv4Addr) -> Self {
3641        self.remote = Some(addr);
3642        self
3643    }
3644
3645    /// Set the TTL.
3646    pub fn ttl(mut self, ttl: u8) -> Self {
3647        self.ttl = Some(ttl);
3648        self
3649    }
3650
3651    /// Set the TOS.
3652    pub fn tos(mut self, tos: u8) -> Self {
3653        self.tos = Some(tos);
3654        self
3655    }
3656
3657    /// Set the input GRE key. Automatically enables GRE_KEY flag.
3658    pub fn ikey(mut self, key: u32) -> Self {
3659        self.ikey = Some(key);
3660        self
3661    }
3662
3663    /// Set the output GRE key. Automatically enables GRE_KEY flag.
3664    pub fn okey(mut self, key: u32) -> Self {
3665        self.okey = Some(key);
3666        self
3667    }
3668
3669    /// Set both input and output GRE key.
3670    pub fn key(self, key: u32) -> Self {
3671        self.ikey(key).okey(key)
3672    }
3673
3674    /// Enable/disable Path MTU Discovery.
3675    pub fn pmtudisc(mut self, enabled: bool) -> Self {
3676        self.pmtudisc = Some(enabled);
3677        self
3678    }
3679
3680    /// Ignore the Don't Fragment flag on inner packets.
3681    pub fn ignore_df(mut self, enabled: bool) -> Self {
3682        self.ignore_df = Some(enabled);
3683        self
3684    }
3685
3686    /// Set firewall mark.
3687    pub fn fwmark(mut self, mark: u32) -> Self {
3688        self.fwmark = Some(mark);
3689        self
3690    }
3691
3692    /// Set the MTU.
3693    pub fn mtu(mut self, mtu: u32) -> Self {
3694        self.mtu = Some(mtu);
3695        self
3696    }
3697
3698    /// Set the underlay interface by name.
3699    pub fn link(mut self, iface: impl Into<String>) -> Self {
3700        self.link = Some(InterfaceRef::Name(iface.into()));
3701        self
3702    }
3703
3704    /// Set the underlay interface by index.
3705    pub fn link_index(mut self, index: u32) -> Self {
3706        self.link = Some(InterfaceRef::Index(index));
3707        self
3708    }
3709}
3710
3711impl LinkConfig for GreLink {
3712    fn name(&self) -> &str {
3713        &self.name
3714    }
3715
3716    fn kind(&self) -> &str {
3717        "gre"
3718    }
3719
3720    fn parent_ref(&self) -> Option<&InterfaceRef> {
3721        self.link.as_ref()
3722    }
3723
3724    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
3725        write_ifname(builder, &self.name);
3726
3727        if let Some(mtu) = self.mtu {
3728            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
3729        }
3730        if let Some(idx) = parent_index {
3731            builder.append_attr_u32(IflaAttr::Link as u16, idx);
3732        }
3733
3734        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
3735        builder.append_attr_str(IflaInfo::Kind as u16, "gre");
3736
3737        let data = builder.nest_start(IflaInfo::Data as u16);
3738        if let Some(addr) = self.local {
3739            builder.append_attr(gre_attr::IFLA_GRE_LOCAL, &addr.octets());
3740        }
3741        if let Some(addr) = self.remote {
3742            builder.append_attr(gre_attr::IFLA_GRE_REMOTE, &addr.octets());
3743        }
3744        if let Some(ttl) = self.ttl {
3745            builder.append_attr_u8(gre_attr::IFLA_GRE_TTL, ttl);
3746        }
3747        if let Some(tos) = self.tos {
3748            builder.append_attr_u8(gre_attr::IFLA_GRE_TOS, tos);
3749        }
3750        if let Some(key) = self.ikey {
3751            builder.append_attr_u16(gre_attr::IFLA_GRE_IFLAGS, gre_attr::GRE_KEY);
3752            builder.append_attr_u32(gre_attr::IFLA_GRE_IKEY, key);
3753        }
3754        if let Some(key) = self.okey {
3755            builder.append_attr_u16(gre_attr::IFLA_GRE_OFLAGS, gre_attr::GRE_KEY);
3756            builder.append_attr_u32(gre_attr::IFLA_GRE_OKEY, key);
3757        }
3758        if let Some(pmtu) = self.pmtudisc {
3759            builder.append_attr_u8(gre_attr::IFLA_GRE_PMTUDISC, pmtu as u8);
3760        }
3761        if let Some(ignore) = self.ignore_df {
3762            builder.append_attr_u8(gre_attr::IFLA_GRE_IGNORE_DF, ignore as u8);
3763        }
3764        if let Some(mark) = self.fwmark {
3765            builder.append_attr_u32(gre_attr::IFLA_GRE_FWMARK, mark);
3766        }
3767        builder.nest_end(data);
3768
3769        builder.nest_end(linkinfo);
3770    }
3771}
3772
3773// ============================================================================
3774// GRETAP Link (Layer 2 GRE)
3775// ============================================================================
3776
3777/// Configuration for a GRETAP (Ethernet over GRE) tunnel interface.
3778///
3779/// # Example
3780///
3781/// ```ignore
3782/// use nlink::netlink::link::GretapLink;
3783/// use std::net::Ipv4Addr;
3784///
3785/// let gretap = GretapLink::new("gretap1")
3786///     .remote(Ipv4Addr::new(192, 168, 1, 1))
3787///     .local(Ipv4Addr::new(192, 168, 1, 2));
3788///
3789/// conn.add_link(gretap).await?;
3790/// ```
3791#[derive(Debug, Clone)]
3792#[must_use = "builders do nothing unless used"]
3793pub struct GretapLink {
3794    name: String,
3795    local: Option<Ipv4Addr>,
3796    remote: Option<Ipv4Addr>,
3797    ttl: Option<u8>,
3798    tos: Option<u8>,
3799    ikey: Option<u32>,
3800    okey: Option<u32>,
3801    pmtudisc: Option<bool>,
3802    fwmark: Option<u32>,
3803    mtu: Option<u32>,
3804    link: Option<InterfaceRef>,
3805}
3806
3807impl GretapLink {
3808    /// Create a new GRETAP tunnel configuration.
3809    pub fn new(name: impl Into<String>) -> Self {
3810        Self {
3811            name: name.into(),
3812            local: None,
3813            remote: None,
3814            ttl: None,
3815            tos: None,
3816            ikey: None,
3817            okey: None,
3818            pmtudisc: None,
3819            fwmark: None,
3820            mtu: None,
3821            link: None,
3822        }
3823    }
3824
3825    /// Set the local endpoint address.
3826    pub fn local(mut self, addr: Ipv4Addr) -> Self {
3827        self.local = Some(addr);
3828        self
3829    }
3830
3831    /// Set the remote endpoint address.
3832    pub fn remote(mut self, addr: Ipv4Addr) -> Self {
3833        self.remote = Some(addr);
3834        self
3835    }
3836
3837    /// Set the TTL.
3838    pub fn ttl(mut self, ttl: u8) -> Self {
3839        self.ttl = Some(ttl);
3840        self
3841    }
3842
3843    /// Set the TOS.
3844    pub fn tos(mut self, tos: u8) -> Self {
3845        self.tos = Some(tos);
3846        self
3847    }
3848
3849    /// Set the input GRE key.
3850    pub fn ikey(mut self, key: u32) -> Self {
3851        self.ikey = Some(key);
3852        self
3853    }
3854
3855    /// Set the output GRE key.
3856    pub fn okey(mut self, key: u32) -> Self {
3857        self.okey = Some(key);
3858        self
3859    }
3860
3861    /// Set both input and output GRE key.
3862    pub fn key(self, key: u32) -> Self {
3863        self.ikey(key).okey(key)
3864    }
3865
3866    /// Enable/disable Path MTU Discovery.
3867    pub fn pmtudisc(mut self, enabled: bool) -> Self {
3868        self.pmtudisc = Some(enabled);
3869        self
3870    }
3871
3872    /// Set firewall mark.
3873    pub fn fwmark(mut self, mark: u32) -> Self {
3874        self.fwmark = Some(mark);
3875        self
3876    }
3877
3878    /// Set the MTU.
3879    pub fn mtu(mut self, mtu: u32) -> Self {
3880        self.mtu = Some(mtu);
3881        self
3882    }
3883
3884    /// Set the underlay interface by name.
3885    pub fn link(mut self, iface: impl Into<String>) -> Self {
3886        self.link = Some(InterfaceRef::Name(iface.into()));
3887        self
3888    }
3889
3890    /// Set the underlay interface by index.
3891    pub fn link_index(mut self, index: u32) -> Self {
3892        self.link = Some(InterfaceRef::Index(index));
3893        self
3894    }
3895}
3896
3897impl LinkConfig for GretapLink {
3898    fn name(&self) -> &str {
3899        &self.name
3900    }
3901
3902    fn kind(&self) -> &str {
3903        "gretap"
3904    }
3905
3906    fn parent_ref(&self) -> Option<&InterfaceRef> {
3907        self.link.as_ref()
3908    }
3909
3910    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
3911        write_ifname(builder, &self.name);
3912
3913        if let Some(mtu) = self.mtu {
3914            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
3915        }
3916        if let Some(idx) = parent_index {
3917            builder.append_attr_u32(IflaAttr::Link as u16, idx);
3918        }
3919
3920        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
3921        builder.append_attr_str(IflaInfo::Kind as u16, "gretap");
3922
3923        let data = builder.nest_start(IflaInfo::Data as u16);
3924        if let Some(addr) = self.local {
3925            builder.append_attr(gre_attr::IFLA_GRE_LOCAL, &addr.octets());
3926        }
3927        if let Some(addr) = self.remote {
3928            builder.append_attr(gre_attr::IFLA_GRE_REMOTE, &addr.octets());
3929        }
3930        if let Some(ttl) = self.ttl {
3931            builder.append_attr_u8(gre_attr::IFLA_GRE_TTL, ttl);
3932        }
3933        if let Some(tos) = self.tos {
3934            builder.append_attr_u8(gre_attr::IFLA_GRE_TOS, tos);
3935        }
3936        if let Some(key) = self.ikey {
3937            builder.append_attr_u16(gre_attr::IFLA_GRE_IFLAGS, gre_attr::GRE_KEY);
3938            builder.append_attr_u32(gre_attr::IFLA_GRE_IKEY, key);
3939        }
3940        if let Some(key) = self.okey {
3941            builder.append_attr_u16(gre_attr::IFLA_GRE_OFLAGS, gre_attr::GRE_KEY);
3942            builder.append_attr_u32(gre_attr::IFLA_GRE_OKEY, key);
3943        }
3944        if let Some(pmtu) = self.pmtudisc {
3945            builder.append_attr_u8(gre_attr::IFLA_GRE_PMTUDISC, pmtu as u8);
3946        }
3947        if let Some(mark) = self.fwmark {
3948            builder.append_attr_u32(gre_attr::IFLA_GRE_FWMARK, mark);
3949        }
3950        builder.nest_end(data);
3951
3952        builder.nest_end(linkinfo);
3953    }
3954}
3955
3956// ============================================================================
3957// IPIP Link
3958// ============================================================================
3959
3960/// Configuration for an IPIP (IP-in-IP) tunnel interface.
3961///
3962/// # Example
3963///
3964/// ```ignore
3965/// use nlink::netlink::link::IpipLink;
3966/// use std::net::Ipv4Addr;
3967///
3968/// let ipip = IpipLink::new("ipip1")
3969///     .remote(Ipv4Addr::new(192, 168, 1, 1))
3970///     .local(Ipv4Addr::new(192, 168, 1, 2));
3971///
3972/// conn.add_link(ipip).await?;
3973/// ```
3974#[derive(Debug, Clone)]
3975#[must_use = "builders do nothing unless used"]
3976pub struct IpipLink {
3977    name: String,
3978    local: Option<Ipv4Addr>,
3979    remote: Option<Ipv4Addr>,
3980    ttl: Option<u8>,
3981    tos: Option<u8>,
3982    pmtudisc: Option<bool>,
3983    fwmark: Option<u32>,
3984    mtu: Option<u32>,
3985    link: Option<InterfaceRef>,
3986}
3987
3988impl IpipLink {
3989    /// Create a new IPIP tunnel configuration.
3990    pub fn new(name: impl Into<String>) -> Self {
3991        Self {
3992            name: name.into(),
3993            local: None,
3994            remote: None,
3995            ttl: None,
3996            tos: None,
3997            pmtudisc: None,
3998            fwmark: None,
3999            mtu: None,
4000            link: None,
4001        }
4002    }
4003
4004    /// Set the local endpoint address.
4005    pub fn local(mut self, addr: Ipv4Addr) -> Self {
4006        self.local = Some(addr);
4007        self
4008    }
4009
4010    /// Set the remote endpoint address.
4011    pub fn remote(mut self, addr: Ipv4Addr) -> Self {
4012        self.remote = Some(addr);
4013        self
4014    }
4015
4016    /// Set the TTL.
4017    pub fn ttl(mut self, ttl: u8) -> Self {
4018        self.ttl = Some(ttl);
4019        self
4020    }
4021
4022    /// Set the TOS.
4023    pub fn tos(mut self, tos: u8) -> Self {
4024        self.tos = Some(tos);
4025        self
4026    }
4027
4028    /// Enable/disable Path MTU Discovery.
4029    pub fn pmtudisc(mut self, enabled: bool) -> Self {
4030        self.pmtudisc = Some(enabled);
4031        self
4032    }
4033
4034    /// Set firewall mark.
4035    pub fn fwmark(mut self, mark: u32) -> Self {
4036        self.fwmark = Some(mark);
4037        self
4038    }
4039
4040    /// Set the MTU.
4041    pub fn mtu(mut self, mtu: u32) -> Self {
4042        self.mtu = Some(mtu);
4043        self
4044    }
4045
4046    /// Set the underlay interface by name.
4047    pub fn link(mut self, iface: impl Into<String>) -> Self {
4048        self.link = Some(InterfaceRef::Name(iface.into()));
4049        self
4050    }
4051
4052    /// Set the underlay interface by index.
4053    pub fn link_index(mut self, index: u32) -> Self {
4054        self.link = Some(InterfaceRef::Index(index));
4055        self
4056    }
4057}
4058
4059impl LinkConfig for IpipLink {
4060    fn name(&self) -> &str {
4061        &self.name
4062    }
4063
4064    fn kind(&self) -> &str {
4065        "ipip"
4066    }
4067
4068    fn parent_ref(&self) -> Option<&InterfaceRef> {
4069        self.link.as_ref()
4070    }
4071
4072    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
4073        write_ifname(builder, &self.name);
4074
4075        if let Some(mtu) = self.mtu {
4076            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
4077        }
4078        if let Some(idx) = parent_index {
4079            builder.append_attr_u32(IflaAttr::Link as u16, idx);
4080        }
4081
4082        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
4083        builder.append_attr_str(IflaInfo::Kind as u16, "ipip");
4084
4085        let data = builder.nest_start(IflaInfo::Data as u16);
4086        if let Some(addr) = self.local {
4087            builder.append_attr(iptun_attr::IFLA_IPTUN_LOCAL, &addr.octets());
4088        }
4089        if let Some(addr) = self.remote {
4090            builder.append_attr(iptun_attr::IFLA_IPTUN_REMOTE, &addr.octets());
4091        }
4092        if let Some(ttl) = self.ttl {
4093            builder.append_attr_u8(iptun_attr::IFLA_IPTUN_TTL, ttl);
4094        }
4095        if let Some(tos) = self.tos {
4096            builder.append_attr_u8(iptun_attr::IFLA_IPTUN_TOS, tos);
4097        }
4098        if let Some(pmtu) = self.pmtudisc {
4099            builder.append_attr_u8(iptun_attr::IFLA_IPTUN_PMTUDISC, pmtu as u8);
4100        }
4101        if let Some(mark) = self.fwmark {
4102            builder.append_attr_u32(iptun_attr::IFLA_IPTUN_FWMARK, mark);
4103        }
4104        builder.nest_end(data);
4105
4106        builder.nest_end(linkinfo);
4107    }
4108}
4109
4110// ============================================================================
4111// SIT Link (IPv6-in-IPv4)
4112// ============================================================================
4113
4114/// Configuration for a SIT (Simple Internet Transition) tunnel interface.
4115///
4116/// SIT tunnels encapsulate IPv6 packets in IPv4 for transition mechanisms.
4117///
4118/// # Example
4119///
4120/// ```ignore
4121/// use nlink::netlink::link::SitLink;
4122/// use std::net::Ipv4Addr;
4123///
4124/// let sit = SitLink::new("sit1")
4125///     .remote(Ipv4Addr::new(192, 168, 1, 1))
4126///     .local(Ipv4Addr::new(192, 168, 1, 2));
4127///
4128/// conn.add_link(sit).await?;
4129/// ```
4130#[derive(Debug, Clone)]
4131#[must_use = "builders do nothing unless used"]
4132pub struct SitLink {
4133    name: String,
4134    local: Option<Ipv4Addr>,
4135    remote: Option<Ipv4Addr>,
4136    ttl: Option<u8>,
4137    tos: Option<u8>,
4138    pmtudisc: Option<bool>,
4139    fwmark: Option<u32>,
4140    isatap: bool,
4141    mtu: Option<u32>,
4142    link: Option<InterfaceRef>,
4143}
4144
4145impl SitLink {
4146    /// Create a new SIT tunnel configuration.
4147    pub fn new(name: impl Into<String>) -> Self {
4148        Self {
4149            name: name.into(),
4150            local: None,
4151            remote: None,
4152            ttl: None,
4153            tos: None,
4154            pmtudisc: None,
4155            fwmark: None,
4156            isatap: false,
4157            mtu: None,
4158            link: None,
4159        }
4160    }
4161
4162    /// Set the local endpoint address.
4163    pub fn local(mut self, addr: Ipv4Addr) -> Self {
4164        self.local = Some(addr);
4165        self
4166    }
4167
4168    /// Set the remote endpoint address.
4169    pub fn remote(mut self, addr: Ipv4Addr) -> Self {
4170        self.remote = Some(addr);
4171        self
4172    }
4173
4174    /// Set the TTL.
4175    pub fn ttl(mut self, ttl: u8) -> Self {
4176        self.ttl = Some(ttl);
4177        self
4178    }
4179
4180    /// Set the TOS.
4181    pub fn tos(mut self, tos: u8) -> Self {
4182        self.tos = Some(tos);
4183        self
4184    }
4185
4186    /// Enable/disable Path MTU Discovery.
4187    pub fn pmtudisc(mut self, enabled: bool) -> Self {
4188        self.pmtudisc = Some(enabled);
4189        self
4190    }
4191
4192    /// Set firewall mark.
4193    pub fn fwmark(mut self, mark: u32) -> Self {
4194        self.fwmark = Some(mark);
4195        self
4196    }
4197
4198    /// Enable ISATAP (Intra-Site Automatic Tunnel Addressing Protocol) mode.
4199    pub fn isatap(mut self) -> Self {
4200        self.isatap = true;
4201        self
4202    }
4203
4204    /// Set the MTU.
4205    pub fn mtu(mut self, mtu: u32) -> Self {
4206        self.mtu = Some(mtu);
4207        self
4208    }
4209
4210    /// Set the underlay interface by name.
4211    pub fn link(mut self, iface: impl Into<String>) -> Self {
4212        self.link = Some(InterfaceRef::Name(iface.into()));
4213        self
4214    }
4215
4216    /// Set the underlay interface by index.
4217    pub fn link_index(mut self, index: u32) -> Self {
4218        self.link = Some(InterfaceRef::Index(index));
4219        self
4220    }
4221}
4222
4223impl LinkConfig for SitLink {
4224    fn name(&self) -> &str {
4225        &self.name
4226    }
4227
4228    fn kind(&self) -> &str {
4229        "sit"
4230    }
4231
4232    fn parent_ref(&self) -> Option<&InterfaceRef> {
4233        self.link.as_ref()
4234    }
4235
4236    fn write_to(&self, builder: &mut MessageBuilder, parent_index: Option<u32>) {
4237        write_ifname(builder, &self.name);
4238
4239        if let Some(mtu) = self.mtu {
4240            builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
4241        }
4242        if let Some(idx) = parent_index {
4243            builder.append_attr_u32(IflaAttr::Link as u16, idx);
4244        }
4245
4246        let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
4247        builder.append_attr_str(IflaInfo::Kind as u16, "sit");
4248
4249        let data = builder.nest_start(IflaInfo::Data as u16);
4250        if let Some(addr) = self.local {
4251            builder.append_attr(iptun_attr::IFLA_IPTUN_LOCAL, &addr.octets());
4252        }
4253        if let Some(addr) = self.remote {
4254            builder.append_attr(iptun_attr::IFLA_IPTUN_REMOTE, &addr.octets());
4255        }
4256        if let Some(ttl) = self.ttl {
4257            builder.append_attr_u8(iptun_attr::IFLA_IPTUN_TTL, ttl);
4258        }
4259        if let Some(tos) = self.tos {
4260            builder.append_attr_u8(iptun_attr::IFLA_IPTUN_TOS, tos);
4261        }
4262        if let Some(pmtu) = self.pmtudisc {
4263            builder.append_attr_u8(iptun_attr::IFLA_IPTUN_PMTUDISC, pmtu as u8);
4264        }
4265        if let Some(mark) = self.fwmark {
4266            builder.append_attr_u32(iptun_attr::IFLA_IPTUN_FWMARK, mark);
4267        }
4268        if self.isatap {
4269            builder.append_attr_u16(iptun_attr::IFLA_IPTUN_FLAGS, iptun_attr::SIT_ISATAP);
4270        }
4271        builder.nest_end(data);
4272
4273        builder.nest_end(linkinfo);
4274    }
4275}
4276
4277// ============================================================================
4278// WireGuard Link
4279// ============================================================================
4280
4281/// Configuration for a WireGuard interface.
4282///
4283/// Note: WireGuard interfaces are created with just the interface name.
4284/// Configuration (keys, peers, etc.) is done via the WireGuard netlink API.
4285///
4286/// # Example
4287///
4288/// ```ignore
4289/// use nlink::netlink::{Connection, Wireguard};
4290/// use nlink::netlink::link::WireguardLink;
4291///
4292/// let wg = WireguardLink::new("wg0");
4293/// conn.add_link(wg).await?;
4294///
4295/// // Then configure via Connection<Wireguard>
4296/// let wg_conn = Connection::<Wireguard>::new_async().await?;
4297/// wg_conn.set_device("wg0", |dev| dev.private_key(key)).await?;
4298/// ```
4299#[derive(Debug, Clone)]
4300#[must_use = "builders do nothing unless used"]
4301pub struct WireguardLink {
4302    name: String,
4303    mtu: Option<u32>,
4304}
4305
4306impl WireguardLink {
4307    /// Create a new WireGuard interface configuration.
4308    pub fn new(name: &str) -> Self {
4309        Self {
4310            name: name.to_string(),
4311            mtu: None,
4312        }
4313    }
4314
4315    /// Set the MTU.
4316    pub fn mtu(mut self, mtu: u32) -> Self {
4317        self.mtu = Some(mtu);
4318        self
4319    }
4320}
4321
4322impl LinkConfig for WireguardLink {
4323    fn name(&self) -> &str {
4324        &self.name
4325    }
4326
4327    fn kind(&self) -> &str {
4328        "wireguard"
4329    }
4330
4331    fn write_to(&self, builder: &mut MessageBuilder, _parent_index: Option<u32>) {
4332        write_simple_link(builder, &self.name, "wireguard", self.mtu, None);
4333    }
4334}
4335
4336// ============================================================================
4337// Helper Functions
4338// ============================================================================
4339
4340/// Write the interface name attribute.
4341fn write_ifname(builder: &mut MessageBuilder, name: &str) {
4342    builder.append_attr_str(IflaAttr::Ifname as u16, name);
4343}
4344
4345/// Write a simple link (like dummy) with just name and optional MTU/address.
4346fn write_simple_link(
4347    builder: &mut MessageBuilder,
4348    name: &str,
4349    kind: &str,
4350    mtu: Option<u32>,
4351    address: Option<&[u8; 6]>,
4352) {
4353    // Add interface name
4354    write_ifname(builder, name);
4355
4356    // Add optional attributes
4357    if let Some(mtu) = mtu {
4358        builder.append_attr_u32(IflaAttr::Mtu as u16, mtu);
4359    }
4360    if let Some(addr) = address {
4361        builder.append_attr(IflaAttr::Address as u16, addr);
4362    }
4363
4364    // IFLA_LINKINFO -> IFLA_INFO_KIND
4365    let linkinfo = builder.nest_start(IflaAttr::Linkinfo as u16);
4366    builder.append_attr_str(IflaInfo::Kind as u16, kind);
4367    builder.nest_end(linkinfo);
4368}
4369
4370// ============================================================================
4371// Connection Methods
4372// ============================================================================
4373
4374impl Connection<Route> {
4375    /// Add a new network interface.
4376    ///
4377    /// # Example
4378    ///
4379    /// ```ignore
4380    /// use nlink::netlink::link::{DummyLink, VethLink, BridgeLink};
4381    ///
4382    /// // Create a dummy interface
4383    /// conn.add_link(DummyLink::new("dummy0")).await?;
4384    ///
4385    /// // Create a veth pair
4386    /// conn.add_link(VethLink::new("veth0", "veth1")).await?;
4387    ///
4388    /// // Create a bridge
4389    /// conn.add_link(BridgeLink::new("br0").stp(true)).await?;
4390    ///
4391    /// // Create a VLAN with parent by index (namespace-safe)
4392    /// conn.add_link(VlanLink::with_parent_index("vlan100", 5, 100)).await?;
4393    /// ```
4394    pub async fn add_link<L: LinkConfig>(&self, config: L) -> Result<()> {
4395        use super::message::{NLM_F_ACK, NLM_F_REQUEST};
4396
4397        // Validate interface name(s) before sending to kernel
4398        crate::util::ifname::validate(config.name()).map_err(super::error::Error::Interface)?;
4399        if let Some(peer) = config.peer_name() {
4400            crate::util::ifname::validate(peer).map_err(super::error::Error::Interface)?;
4401        }
4402
4403        // Resolve parent interface if needed
4404        let parent_index = match config.parent_ref() {
4405            Some(iface) => Some(self.resolve_interface(iface).await?),
4406            None => None,
4407        };
4408
4409        // Build the message
4410        let mut builder = MessageBuilder::new(
4411            NlMsgType::RTM_NEWLINK,
4412            NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
4413        );
4414
4415        // Append ifinfomsg header
4416        let ifinfo = IfInfoMsg::new();
4417        builder.append(&ifinfo);
4418
4419        // Write the link configuration
4420        let link_name = config.name().to_string();
4421        let link_kind = config.kind().to_string();
4422        config.write_to(&mut builder, parent_index);
4423
4424        self.send_ack(builder)
4425            .await
4426            .map_err(|e| e.with_context(format!("add_link({link_name}, kind={link_kind})")))
4427    }
4428
4429    /// Set the master (controller) device for an interface.
4430    ///
4431    /// This is used to add an interface to a bridge or bond.
4432    ///
4433    /// # Example
4434    ///
4435    /// ```ignore
4436    /// // Add eth0 to bridge br0
4437    /// conn.set_link_master("eth0", "br0").await?;
4438    ///
4439    /// // Or by index
4440    /// conn.set_link_master(InterfaceRef::Index(5), InterfaceRef::Index(10)).await?;
4441    /// ```
4442    pub async fn set_link_master(
4443        &self,
4444        iface: impl Into<InterfaceRef>,
4445        master: impl Into<InterfaceRef>,
4446    ) -> Result<()> {
4447        let ifindex = self.resolve_interface(&iface.into()).await?;
4448        let master_index = self.resolve_interface(&master.into()).await?;
4449        self.set_link_master_by_index(ifindex, master_index).await
4450    }
4451
4452    /// Set the master device by interface indices.
4453    pub async fn set_link_master_by_index(&self, ifindex: u32, master_index: u32) -> Result<()> {
4454        use super::connection::ack_request;
4455
4456        let ifinfo = IfInfoMsg::new().with_index(ifindex as i32);
4457
4458        let mut builder = ack_request(NlMsgType::RTM_SETLINK);
4459        builder.append(&ifinfo);
4460        builder.append_attr_u32(IflaAttr::Master as u16, master_index);
4461
4462        self.send_ack(builder)
4463            .await
4464            .map_err(|e| e.with_context("set_link_master"))
4465    }
4466
4467    /// Enslave an interface to a bond or bridge.
4468    ///
4469    /// This convenience method handles the required down/master/up sequence:
4470    /// the member interface must be brought down before enslaving, then brought
4471    /// back up afterward.
4472    ///
4473    /// # Example
4474    ///
4475    /// ```ignore
4476    /// // Enslave eth0 to bond0 (handles down/master/up automatically)
4477    /// conn.enslave("eth0", "bond0").await?;
4478    /// ```
4479    pub async fn enslave(
4480        &self,
4481        member: impl Into<InterfaceRef>,
4482        master: impl Into<InterfaceRef>,
4483    ) -> Result<()> {
4484        let member = member.into();
4485        let master = master.into();
4486        let member_idx = self.resolve_interface(&member).await?;
4487        let master_idx = self.resolve_interface(&master).await?;
4488        self.set_link_down_by_index(member_idx).await?;
4489        self.set_link_master_by_index(member_idx, master_idx)
4490            .await?;
4491        self.set_link_up_by_index(member_idx).await
4492    }
4493
4494    /// Enslave an interface to a bond or bridge by index.
4495    pub async fn enslave_by_index(&self, member_index: u32, master_index: u32) -> Result<()> {
4496        self.set_link_down_by_index(member_index).await?;
4497        self.set_link_master_by_index(member_index, master_index)
4498            .await?;
4499        self.set_link_up_by_index(member_index).await
4500    }
4501
4502    /// Remove an interface from its master device.
4503    ///
4504    /// Accepts either an interface name or index via [`InterfaceRef`].
4505    ///
4506    /// # Example
4507    ///
4508    /// ```ignore
4509    /// // Remove eth0 from its bridge/bond
4510    /// conn.set_link_nomaster("eth0").await?;
4511    /// ```
4512    pub async fn set_link_nomaster(&self, iface: impl Into<InterfaceRef>) -> Result<()> {
4513        let ifindex = self.resolve_interface(&iface.into()).await?;
4514        self.set_link_nomaster_by_index(ifindex).await
4515    }
4516
4517    /// Remove an interface from its master by index.
4518    pub async fn set_link_nomaster_by_index(&self, ifindex: u32) -> Result<()> {
4519        use super::connection::ack_request;
4520
4521        let ifinfo = IfInfoMsg::new().with_index(ifindex as i32);
4522
4523        let mut builder = ack_request(NlMsgType::RTM_SETLINK);
4524        builder.append(&ifinfo);
4525        builder.append_attr_u32(IflaAttr::Master as u16, 0);
4526
4527        self.send_ack(builder)
4528            .await
4529            .map_err(|e| e.with_context("set_link_nomaster"))
4530    }
4531
4532    /// Rename a network interface.
4533    ///
4534    /// Accepts either an interface name or index via [`InterfaceRef`].
4535    /// Note: The interface must be down to be renamed.
4536    ///
4537    /// # Example
4538    ///
4539    /// ```ignore
4540    /// conn.set_link_name("eth0", "lan0").await?;
4541    /// ```
4542    pub async fn set_link_name(
4543        &self,
4544        iface: impl Into<InterfaceRef>,
4545        new_name: &str,
4546    ) -> Result<()> {
4547        let ifindex = self.resolve_interface(&iface.into()).await?;
4548        self.set_link_name_by_index(ifindex, new_name).await
4549    }
4550
4551    /// Rename a network interface by index.
4552    pub async fn set_link_name_by_index(&self, ifindex: u32, new_name: &str) -> Result<()> {
4553        use super::connection::ack_request;
4554
4555        // Validate the new name before sending to kernel
4556        crate::util::ifname::validate(new_name).map_err(super::error::Error::Interface)?;
4557
4558        let ifinfo = IfInfoMsg::new().with_index(ifindex as i32);
4559
4560        let mut builder = ack_request(NlMsgType::RTM_SETLINK);
4561        builder.append(&ifinfo);
4562        builder.append_attr_str(IflaAttr::Ifname as u16, new_name);
4563
4564        self.send_ack(builder)
4565            .await
4566            .map_err(|e| e.with_context("set_link_name"))
4567    }
4568
4569    /// Set the MAC address of a network interface.
4570    ///
4571    /// Accepts either an interface name or index via [`InterfaceRef`].
4572    ///
4573    /// # Example
4574    ///
4575    /// ```ignore
4576    /// conn.set_link_address("eth0", [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]).await?;
4577    /// ```
4578    pub async fn set_link_address(
4579        &self,
4580        iface: impl Into<InterfaceRef>,
4581        address: [u8; 6],
4582    ) -> Result<()> {
4583        let ifindex = self.resolve_interface(&iface.into()).await?;
4584        self.set_link_address_by_index(ifindex, address).await
4585    }
4586
4587    /// Set the MAC address by interface index.
4588    pub async fn set_link_address_by_index(&self, ifindex: u32, address: [u8; 6]) -> Result<()> {
4589        use super::connection::ack_request;
4590
4591        let ifinfo = IfInfoMsg::new().with_index(ifindex as i32);
4592
4593        let mut builder = ack_request(NlMsgType::RTM_SETLINK);
4594        builder.append(&ifinfo);
4595        builder.append_attr(IflaAttr::Address as u16, &address);
4596
4597        self.send_ack(builder)
4598            .await
4599            .map_err(|e| e.with_context("set_link_address"))
4600    }
4601
4602    /// Move a network interface to a different network namespace.
4603    ///
4604    /// Accepts either an interface name or index via [`InterfaceRef`].
4605    ///
4606    /// # Example
4607    ///
4608    /// ```ignore
4609    /// // Move veth1 to namespace by PID
4610    /// conn.set_link_netns_pid("veth1", container_pid).await?;
4611    /// ```
4612    pub async fn set_link_netns_pid(&self, iface: impl Into<InterfaceRef>, pid: u32) -> Result<()> {
4613        let ifindex = self.resolve_interface(&iface.into()).await?;
4614        self.set_link_netns_pid_by_index(ifindex, pid).await
4615    }
4616
4617    /// Move a network interface to a namespace by PID (by index).
4618    pub async fn set_link_netns_pid_by_index(&self, ifindex: u32, pid: u32) -> Result<()> {
4619        use super::connection::ack_request;
4620
4621        let ifinfo = IfInfoMsg::new().with_index(ifindex as i32);
4622
4623        let mut builder = ack_request(NlMsgType::RTM_SETLINK);
4624        builder.append(&ifinfo);
4625        builder.append_attr_u32(IflaAttr::NetNsPid as u16, pid);
4626
4627        self.send_ack(builder)
4628            .await
4629            .map_err(|e| e.with_context("set_link_netns"))
4630    }
4631
4632    /// Move a network interface to a namespace by file descriptor.
4633    ///
4634    /// Accepts either an interface name or index via [`InterfaceRef`].
4635    pub async fn set_link_netns_fd(&self, iface: impl Into<InterfaceRef>, fd: i32) -> Result<()> {
4636        let ifindex = self.resolve_interface(&iface.into()).await?;
4637        self.set_link_netns_fd_by_index(ifindex, fd).await
4638    }
4639
4640    /// Move a network interface to a namespace by fd (by index).
4641    pub async fn set_link_netns_fd_by_index(&self, ifindex: u32, fd: i32) -> Result<()> {
4642        use super::connection::ack_request;
4643
4644        let ifinfo = IfInfoMsg::new().with_index(ifindex as i32);
4645
4646        let mut builder = ack_request(NlMsgType::RTM_SETLINK);
4647        builder.append(&ifinfo);
4648        builder.append_attr_u32(IflaAttr::NetNsFd as u16, fd as u32);
4649
4650        self.send_ack(builder)
4651            .await
4652            .map_err(|e| e.with_context("set_link_netns"))
4653    }
4654}
4655
4656#[cfg(test)]
4657mod tests {
4658    use super::*;
4659
4660    #[test]
4661    fn test_bond_mode_try_from() {
4662        assert!(matches!(BondMode::try_from(0u8), Ok(BondMode::BalanceRr)));
4663        assert!(matches!(
4664            BondMode::try_from(1u8),
4665            Ok(BondMode::ActiveBackup)
4666        ));
4667        assert!(matches!(BondMode::try_from(2u8), Ok(BondMode::BalanceXor)));
4668        assert!(matches!(BondMode::try_from(3u8), Ok(BondMode::Broadcast)));
4669        assert!(matches!(BondMode::try_from(4u8), Ok(BondMode::Lacp)));
4670        assert!(matches!(BondMode::try_from(5u8), Ok(BondMode::BalanceTlb)));
4671        assert!(matches!(BondMode::try_from(6u8), Ok(BondMode::BalanceAlb)));
4672        assert!(BondMode::try_from(7u8).is_err());
4673        assert!(BondMode::try_from(255u8).is_err());
4674    }
4675
4676    #[test]
4677    fn test_xmit_hash_policy_try_from() {
4678        assert!(matches!(
4679            XmitHashPolicy::try_from(0u8),
4680            Ok(XmitHashPolicy::Layer2)
4681        ));
4682        assert!(matches!(
4683            XmitHashPolicy::try_from(1u8),
4684            Ok(XmitHashPolicy::Layer34)
4685        ));
4686        assert!(matches!(
4687            XmitHashPolicy::try_from(2u8),
4688            Ok(XmitHashPolicy::Layer23)
4689        ));
4690        assert!(matches!(
4691            XmitHashPolicy::try_from(3u8),
4692            Ok(XmitHashPolicy::Encap23)
4693        ));
4694        assert!(matches!(
4695            XmitHashPolicy::try_from(4u8),
4696            Ok(XmitHashPolicy::Encap34)
4697        ));
4698        assert!(matches!(
4699            XmitHashPolicy::try_from(5u8),
4700            Ok(XmitHashPolicy::VlanSrcMac)
4701        ));
4702        assert!(XmitHashPolicy::try_from(6u8).is_err());
4703        assert!(XmitHashPolicy::try_from(255u8).is_err());
4704    }
4705
4706    #[test]
4707    fn test_bond_mode_debug_format() {
4708        assert_eq!(format!("{:?}", BondMode::BalanceRr), "BalanceRr");
4709        assert_eq!(format!("{:?}", BondMode::Lacp), "Lacp");
4710        assert_eq!(format!("{:?}", BondMode::BalanceAlb), "BalanceAlb");
4711    }
4712
4713    #[test]
4714    fn test_xmit_hash_policy_debug_format() {
4715        assert_eq!(format!("{:?}", XmitHashPolicy::Layer2), "Layer2");
4716        assert_eq!(format!("{:?}", XmitHashPolicy::Layer34), "Layer34");
4717        assert_eq!(format!("{:?}", XmitHashPolicy::VlanSrcMac), "VlanSrcMac");
4718    }
4719}