Skip to main content

wireforge_io/linux/
l2.rs

1//! L2 (AF_PACKET) socket I/O — Ethernet frame capture and injection.
2
3use std::os::fd::AsRawFd;
4use std::time::Duration;
5
6use socket2::{Domain, Protocol, Socket, Type};
7use wireforge_core::ether::EthernetPacket;
8
9use crate::common;
10use crate::error::IoError;
11use crate::traits::{L2Reader, L2Writer};
12
13/// Receiver for raw L2 Ethernet frames via AF_PACKET socket.
14pub struct L2Receiver {
15    sock: Socket,
16    buf: Vec<u8>,
17    ifname: String,
18}
19
20impl L2Receiver {
21    /// Open an AF_PACKET socket and bind to the given interface.
22    pub fn new(ifname: &str) -> Result<Self, IoError> {
23        let sock = Socket::new(
24            Domain::PACKET,
25            Type::RAW,
26            Some(Protocol::from(libc::ETH_P_ALL)),
27        )?;
28
29        // Bind to interface using SO_BINDTODEVICE
30        sock.bind_device(Some(ifname.as_bytes()))?;
31
32        Ok(Self {
33            sock,
34            buf: vec![0u8; 65536],
35            ifname: ifname.to_string(),
36        })
37    }
38
39    /// Receive a single Ethernet frame and parse it.
40    pub fn recv(&mut self) -> Result<EthernetPacket<'_>, IoError> {
41        let n = unsafe {
42            libc::recv(
43                self.sock.as_raw_fd(),
44                self.buf.as_mut_ptr() as *mut libc::c_void,
45                self.buf.len(),
46                0,
47            )
48        };
49        if n < 0 {
50            return Err(IoError::Socket(std::io::Error::last_os_error()));
51        }
52        let n = n as usize;
53        EthernetPacket::new(&self.buf[..n]).ok_or(IoError::Parse("invalid Ethernet frame"))
54    }
55
56    /// Enable or disable promiscuous mode.
57    pub fn set_promiscuous(&mut self, on: bool) -> Result<(), IoError> {
58        let fd = self.sock.as_raw_fd();
59        unsafe {
60            let mut ifreq: libc::ifreq = std::mem::zeroed();
61            copy_ifname(&mut ifreq, &self.ifname);
62            // Read current flags
63            libc::ioctl(fd, libc::SIOCGIFFLAGS, &mut ifreq);
64            let mut flags = ifreq.ifr_ifru.ifru_flags;
65            if on {
66                flags |= libc::IFF_PROMISC as i16;
67            } else {
68                flags &= !(libc::IFF_PROMISC as i16);
69            }
70            ifreq.ifr_ifru.ifru_flags = flags;
71            if libc::ioctl(fd, libc::SIOCSIFFLAGS, &ifreq) < 0 {
72                return Err(IoError::Socket(std::io::Error::last_os_error()));
73            }
74        }
75        Ok(())
76    }
77
78    /// Set receive timeout.
79    pub fn set_timeout(&mut self, dur: Option<Duration>) -> Result<(), IoError> {
80        common::set_socket_timeout(self.sock.as_raw_fd(), dur)
81    }
82}
83
84/// Sender for raw L2 Ethernet frames via AF_PACKET socket.
85pub struct L2Sender {
86    sock: Socket,
87    sockaddr: libc::sockaddr_ll,
88}
89
90impl L2Sender {
91    /// Open an AF_PACKET socket for sending on the given interface.
92    pub fn new(ifname: &str) -> Result<Self, IoError> {
93        let sock = Socket::new(
94            Domain::PACKET,
95            Type::RAW,
96            Some(Protocol::from(libc::ETH_P_ALL)),
97        )?;
98
99        let fd = sock.as_raw_fd();
100        let ifindex = unsafe {
101            let mut ifreq: libc::ifreq = std::mem::zeroed();
102            let name_bytes = ifname.as_bytes();
103            let copy_len = name_bytes.len().min(ifreq.ifr_name.len() - 1);
104            // SAFETY: ifr_name is [i8]; we copy raw bytes from the interface name
105            let name_i8 = std::slice::from_raw_parts(name_bytes.as_ptr() as *const i8, copy_len);
106            ifreq.ifr_name[..copy_len].copy_from_slice(name_i8);
107            libc::ioctl(fd, libc::SIOCGIFINDEX, &mut ifreq);
108            ifreq.ifr_ifru.ifru_ifindex
109        };
110
111        let sockaddr = libc::sockaddr_ll {
112            sll_family: libc::AF_PACKET as u16,
113            sll_protocol: (libc::ETH_P_ALL as u16).to_be(),
114            sll_ifindex: ifindex,
115            sll_hatype: 0,
116            sll_pkttype: 0,
117            sll_halen: 0,
118            sll_addr: [0u8; 8],
119        };
120
121        Ok(Self { sock, sockaddr })
122    }
123
124    /// Send raw L2 bytes on the interface.
125    pub fn send(&self, packet: &[u8]) -> Result<usize, IoError> {
126        let n = unsafe {
127            // SAFETY: transmute sockaddr_ll to sockaddr — same size, packed correctly
128            let addr: &libc::sockaddr = &*(&self.sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr);
129            libc::sendto(
130                self.sock.as_raw_fd(),
131                packet.as_ptr() as *const libc::c_void,
132                packet.len(),
133                0,
134                addr,
135                std::mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
136            )
137        };
138        if n < 0 {
139            return Err(IoError::Socket(std::io::Error::last_os_error()));
140        }
141        Ok(n as usize)
142    }
143}
144
145// Trait implementations
146impl L2Reader for L2Receiver {
147    fn recv(&mut self) -> Result<EthernetPacket<'_>, IoError> {
148        self.recv()
149    }
150
151    fn set_timeout(&mut self, dur: Option<Duration>) -> Result<(), IoError> {
152        self.set_timeout(dur)
153    }
154
155    fn set_promiscuous(&mut self, on: bool) -> Result<(), IoError> {
156        self.set_promiscuous(on)
157    }
158}
159
160impl L2Writer for L2Sender {
161    fn send(&self, packet: &[u8]) -> Result<usize, IoError> {
162        self.send(packet)
163    }
164}
165
166/// Copy interface name into an ifreq's ifr_name field.
167#[cfg(target_os = "linux")]
168fn copy_ifname(ifr: &mut libc::ifreq, name: &str) {
169    let bytes = name.as_bytes();
170    let len = bytes.len().min(ifr.ifr_name.len() - 1);
171    // SAFETY: ifr_name is [i8]; the interface name is ASCII, so reinterpret as i8 is safe.
172    let name_i8 = unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const i8, len) };
173    ifr.ifr_name[..len].copy_from_slice(name_i8);
174}