ndn_faces/l2/
ether.rs

1use std::os::unix::io::AsRawFd;
2
3use bytes::Bytes;
4use ndn_packet::Name;
5use ndn_transport::{Face, FaceError, FaceId, FaceKind};
6use tokio::io::unix::AsyncFd;
7
8use super::af_packet::{
9    MacAddr, PacketRing, get_ifindex, make_sockaddr_ll, open_packet_socket, setup_packet_ring,
10};
11use super::radio::RadioFaceMetadata;
12use crate::NDN_ETHERTYPE;
13
14// ─── NamedEtherFace ──────────────────────────────────────────────────────────
15
16/// NDN face over raw Ethernet (`AF_PACKET` / Ethertype 0x8624).
17///
18/// Uses `SOCK_DGRAM` with a TPACKET_V2 mmap'd ring buffer for zero-copy
19/// packet I/O.  The kernel strips/builds the Ethernet header automatically:
20/// `recv()` returns the NDN TLV payload directly; `send()` accepts the NDN
21/// TLV payload and the kernel prepends the Ethernet frame header.
22///
23/// The MAC address is an internal implementation detail — above the face layer
24/// everything is NDN names.  The node name is stable across channel switches
25/// and radio changes; only the internal MAC binding needs updating on mobility.
26///
27/// Requires `CAP_NET_RAW` or root.
28pub struct NamedEtherFace {
29    id: FaceId,
30    /// NDN node name of the remote peer.
31    pub node_name: Name,
32    /// Resolved MAC address of the remote peer.
33    peer_mac: MacAddr,
34    /// Local network interface name.
35    iface: String,
36    /// Interface index (cached from constructor).
37    ifindex: i32,
38    /// Radio metadata for multi-radio strategies.
39    pub radio: RadioFaceMetadata,
40    /// Non-blocking AF_PACKET socket registered with tokio.
41    socket: AsyncFd<std::os::unix::io::OwnedFd>,
42    /// Mmap'd TPACKET_V2 RX + TX ring buffers.
43    ring: PacketRing,
44}
45
46impl NamedEtherFace {
47    /// Create a new Ethernet face bound to `iface`.
48    ///
49    /// Opens an `AF_PACKET + SOCK_DGRAM` socket, configures a TPACKET_V2 mmap
50    /// ring buffer, binds to the given network interface, and registers the
51    /// socket with the tokio reactor.  Requires `CAP_NET_RAW`.
52    pub fn new(
53        id: FaceId,
54        node_name: Name,
55        peer_mac: MacAddr,
56        iface: impl Into<String>,
57        radio: RadioFaceMetadata,
58    ) -> std::io::Result<Self> {
59        let iface = iface.into();
60
61        // Temporary socket to resolve the interface index.
62        let probe_fd = unsafe {
63            libc::socket(
64                libc::AF_PACKET,
65                libc::SOCK_DGRAM | libc::SOCK_CLOEXEC,
66                NDN_ETHERTYPE.to_be() as i32,
67            )
68        };
69        if probe_fd == -1 {
70            return Err(std::io::Error::last_os_error());
71        }
72        let ifindex = {
73            let idx = get_ifindex(probe_fd, &iface);
74            unsafe {
75                libc::close(probe_fd);
76            }
77            idx?
78        };
79
80        let fd = open_packet_socket(ifindex, NDN_ETHERTYPE)?;
81
82        // Set up mmap ring buffers BEFORE registering with AsyncFd.
83        let ring = setup_packet_ring(fd.as_raw_fd())?;
84        let socket = AsyncFd::new(fd)?;
85
86        Ok(Self {
87            id,
88            node_name,
89            peer_mac,
90            iface,
91            ifindex,
92            radio,
93            socket,
94            ring,
95        })
96    }
97
98    /// Update the peer MAC address (e.g. after a mobility event).
99    pub fn set_peer_mac(&mut self, mac: MacAddr) {
100        self.peer_mac = mac;
101    }
102
103    /// Current peer MAC address.
104    pub fn peer_mac(&self) -> MacAddr {
105        self.peer_mac
106    }
107
108    /// Interface name this face is bound to.
109    pub fn iface(&self) -> &str {
110        &self.iface
111    }
112}
113
114impl Face for NamedEtherFace {
115    fn id(&self) -> FaceId {
116        self.id
117    }
118    fn kind(&self) -> FaceKind {
119        FaceKind::Ethernet
120    }
121
122    async fn recv(&self) -> Result<Bytes, FaceError> {
123        loop {
124            if let Some(pkt) = self.ring.try_pop_rx() {
125                return Ok(pkt);
126            }
127            let mut guard = self.socket.readable().await?;
128            guard.clear_ready();
129        }
130    }
131
132    async fn send(&self, pkt: Bytes) -> Result<(), FaceError> {
133        // Wait for an available TX frame.
134        loop {
135            if self.ring.try_push_tx(&pkt) {
136                break;
137            }
138            let mut guard = self.socket.writable().await?;
139            guard.clear_ready();
140        }
141
142        // Flush pending TX frames.
143        let dst = make_sockaddr_ll(self.ifindex, &self.peer_mac, NDN_ETHERTYPE);
144        let fd = self.socket.get_ref().as_raw_fd();
145        let ret = unsafe {
146            libc::sendto(
147                fd,
148                std::ptr::null(),
149                0,
150                0,
151                &dst as *const libc::sockaddr_ll as *const libc::sockaddr,
152                std::mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
153            )
154        };
155        if ret == -1 {
156            let err = std::io::Error::last_os_error();
157            if err.kind() != std::io::ErrorKind::WouldBlock {
158                return Err(FaceError::Io(err));
159            }
160        }
161        Ok(())
162    }
163}
164
165// ─── Tests ───────────────────────────────────────────────────────────────────
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use std::str::FromStr;
171
172    /// Opening an AF_PACKET socket without CAP_NET_RAW should fail with EPERM.
173    #[tokio::test]
174    async fn new_fails_without_cap_net_raw() {
175        let name = Name::from_str("/test/node").unwrap();
176        let result = NamedEtherFace::new(
177            FaceId(1),
178            name,
179            MacAddr::BROADCAST,
180            "lo",
181            RadioFaceMetadata::default(),
182        );
183        if let Err(e) = result {
184            let raw = e.raw_os_error().unwrap_or(0);
185            assert!(
186                raw == libc::EPERM || raw == libc::EACCES,
187                "expected EPERM or EACCES, got: {e}"
188            );
189        }
190    }
191
192    /// Full loopback roundtrip — requires root / CAP_NET_RAW.
193    #[tokio::test]
194    #[ignore = "requires CAP_NET_RAW"]
195    async fn loopback_roundtrip() {
196        let name = Name::from_str("/test/node").unwrap();
197        let lo_mac = MacAddr::new([0; 6]);
198        let face_a = NamedEtherFace::new(
199            FaceId(1),
200            name.clone(),
201            lo_mac,
202            "lo",
203            RadioFaceMetadata::default(),
204        )
205        .expect("need CAP_NET_RAW");
206        let face_b =
207            NamedEtherFace::new(FaceId(2), name, lo_mac, "lo", RadioFaceMetadata::default())
208                .expect("need CAP_NET_RAW");
209
210        let pkt = Bytes::from_static(b"\x05\x03\x01\x02\x03");
211        face_a.send(pkt.clone()).await.unwrap();
212
213        let received = tokio::time::timeout(std::time::Duration::from_secs(2), face_b.recv())
214            .await
215            .expect("timed out")
216            .unwrap();
217
218        assert_eq!(received, pkt);
219    }
220}