ndn_faces/l2/
multicast_ether.rs

1//! Multicast Ethernet face for L2 neighbor discovery.
2//!
3//! Like `MulticastUdpFace` but at the link layer — joins an Ethernet multicast
4//! group and sends/receives NDN packets via `AF_PACKET + TPACKET_V2`.
5
6use std::os::unix::io::AsRawFd;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9use bytes::Bytes;
10use ndn_transport::{Face, FaceAddr, FaceError, FaceId, FaceKind, LinkType};
11use tokio::io::unix::AsyncFd;
12
13use super::af_packet::{
14    MacAddr, PacketRing, get_ifindex, make_sockaddr_ll, open_packet_socket, setsockopt_val,
15    setup_packet_ring,
16};
17use crate::NDN_ETHERTYPE;
18
19/// NDN Ethernet multicast MAC address.
20///
21/// This is the IANA-assigned multicast address for NDN over Ethernet:
22/// `01:00:5e:00:17:aa` (same group used by NFD's EthernetFactory).
23pub const NDN_ETHER_MCAST_MAC: MacAddr = MacAddr([0x01, 0x00, 0x5E, 0x00, 0x17, 0xAA]);
24
25/// NDN face over multicast Ethernet (`AF_PACKET` / Ethertype 0x8624).
26///
27/// Joins the NDN multicast group on the specified interface so that frames
28/// sent to `NDN_ETHER_MCAST_MAC` are received.  Outgoing packets are always
29/// sent to the multicast address.
30///
31/// Suitable for L2 neighbor discovery and local-subnet NDN communication
32/// without IP.  Mirrors `MulticastUdpFace` but at the link layer.
33///
34/// Requires `CAP_NET_RAW` or root.  Linux only.
35pub struct MulticastEtherFace {
36    id: FaceId,
37    iface: String,
38    ifindex: i32,
39    socket: AsyncFd<std::os::unix::io::OwnedFd>,
40    ring: PacketRing,
41    /// Monotonic sequence counter for NDNLPv2 fragmentation.
42    seq: AtomicU64,
43}
44
45impl MulticastEtherFace {
46    /// Create a new multicast Ethernet face on `iface`.
47    ///
48    /// Opens an `AF_PACKET + SOCK_DGRAM` socket, joins the NDN multicast
49    /// group (`01:00:5e:00:17:aa`), and sets up TPACKET_V2 ring buffers.
50    pub fn new(id: FaceId, iface: impl Into<String>) -> std::io::Result<Self> {
51        let iface = iface.into();
52
53        // Resolve interface index.
54        let probe_fd = unsafe {
55            libc::socket(
56                libc::AF_PACKET,
57                libc::SOCK_DGRAM | libc::SOCK_CLOEXEC,
58                NDN_ETHERTYPE.to_be() as i32,
59            )
60        };
61        if probe_fd == -1 {
62            return Err(std::io::Error::last_os_error());
63        }
64        let ifindex = {
65            let idx = get_ifindex(probe_fd, &iface);
66            unsafe {
67                libc::close(probe_fd);
68            }
69            idx?
70        };
71
72        let fd = open_packet_socket(ifindex, NDN_ETHERTYPE)?;
73
74        // Join the NDN Ethernet multicast group on this interface.
75        let mreq = libc::packet_mreq {
76            mr_ifindex: ifindex,
77            mr_type: libc::PACKET_MR_MULTICAST as u16,
78            mr_alen: 6,
79            mr_address: {
80                let mut addr = [0u8; 8];
81                addr[..6].copy_from_slice(NDN_ETHER_MCAST_MAC.as_bytes());
82                addr
83            },
84        };
85        setsockopt_val(
86            fd.as_raw_fd(),
87            libc::SOL_PACKET,
88            libc::PACKET_ADD_MEMBERSHIP,
89            &mreq,
90        )?;
91
92        // Set up mmap ring buffers BEFORE registering with AsyncFd.
93        let ring = setup_packet_ring(fd.as_raw_fd())?;
94        let socket = AsyncFd::new(fd)?;
95
96        Ok(Self {
97            id,
98            iface,
99            ifindex,
100            socket,
101            ring,
102            seq: AtomicU64::new(0),
103        })
104    }
105
106    /// Interface name this face is bound to.
107    pub fn iface(&self) -> &str {
108        &self.iface
109    }
110
111    /// Receive the next NDN packet along with the sender's MAC address.
112    ///
113    /// This is the discovery-layer variant of [`Face::recv`].  The source MAC
114    /// is extracted from the TPACKET_V2 `sockaddr_ll` embedded in each ring
115    /// frame — no extra syscall is needed.  Discovery protocols use this to
116    /// identify which peer sent a Hello packet and create a unicast face for it.
117    pub async fn recv_with_source(&self) -> Result<(Bytes, MacAddr), ndn_transport::FaceError> {
118        loop {
119            if let Some(result) = self.ring.try_pop_rx_with_source() {
120                return Ok(result);
121            }
122            let mut guard = self.socket.readable().await?;
123            guard.clear_ready();
124        }
125    }
126}
127
128impl Face for MulticastEtherFace {
129    fn id(&self) -> FaceId {
130        self.id
131    }
132    fn kind(&self) -> FaceKind {
133        FaceKind::EtherMulticast
134    }
135
136    fn link_type(&self) -> LinkType {
137        LinkType::MultiAccess
138    }
139
140    fn remote_uri(&self) -> Option<String> {
141        Some(format!("ether://[{}]/{}", NDN_ETHER_MCAST_MAC, self.iface))
142    }
143
144    fn local_uri(&self) -> Option<String> {
145        Some(format!("dev://{}", self.iface))
146    }
147
148    async fn recv(&self) -> Result<Bytes, FaceError> {
149        loop {
150            if let Some(pkt) = self.ring.try_pop_rx() {
151                return Ok(pkt);
152            }
153            let mut guard = self.socket.readable().await?;
154            guard.clear_ready();
155        }
156    }
157
158    async fn recv_with_addr(&self) -> Result<(Bytes, Option<FaceAddr>), FaceError> {
159        let (pkt, src_mac) = self.recv_with_source().await?;
160        Ok((pkt, Some(FaceAddr::Ether(src_mac.0))))
161    }
162
163    async fn send(&self, pkt: Bytes) -> Result<(), FaceError> {
164        let wire = ndn_packet::lp::encode_lp_packet(&pkt);
165
166        // Fragment if larger than Ethernet MTU (1500).
167        let frames = if wire.len() > 1500 {
168            let seq = self.seq.fetch_add(1, Ordering::Relaxed);
169            ndn_packet::fragment::fragment_packet(&wire, 1500, seq)
170        } else {
171            vec![wire]
172        };
173
174        for frame in &frames {
175            // Wait for an available TX frame.
176            loop {
177                if self.ring.try_push_tx(frame) {
178                    break;
179                }
180                let mut guard = self.socket.writable().await?;
181                guard.clear_ready();
182            }
183
184            // Flush pending TX frames — destination is always the multicast MAC.
185            let dst = make_sockaddr_ll(self.ifindex, &NDN_ETHER_MCAST_MAC, NDN_ETHERTYPE);
186            let fd = self.socket.get_ref().as_raw_fd();
187            let ret = unsafe {
188                libc::sendto(
189                    fd,
190                    std::ptr::null(),
191                    0,
192                    0,
193                    &dst as *const libc::sockaddr_ll as *const libc::sockaddr,
194                    std::mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
195                )
196            };
197            if ret == -1 {
198                let err = std::io::Error::last_os_error();
199                if err.kind() != std::io::ErrorKind::WouldBlock {
200                    return Err(FaceError::Io(err));
201                }
202            }
203        }
204        Ok(())
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn mcast_mac_is_multicast() {
214        // Bit 0 of byte 0 must be set for multicast.
215        assert_eq!(NDN_ETHER_MCAST_MAC.as_bytes()[0] & 0x01, 0x01);
216    }
217
218    /// Opening without CAP_NET_RAW should fail with EPERM.
219    #[tokio::test]
220    async fn new_fails_without_cap_net_raw() {
221        let result = MulticastEtherFace::new(FaceId(1), "lo");
222        if let Err(e) = result {
223            let raw = e.raw_os_error().unwrap_or(0);
224            assert!(
225                raw == libc::EPERM || raw == libc::EACCES,
226                "expected EPERM or EACCES, got: {e}"
227            );
228        }
229    }
230}