stalkermap_tcp/sys/
linux.rs

1// High‑level summary for Linux raw networking:
2//
3// 1. AF_PACKET is used to create a raw socket bound to an interface.
4//    - The same AF_PACKET socket fd is used for sendto() of raw frames.
5//    - It is protocol‑agnostic: you serialize your own TCP/IP headers.
6//
7// 2. TPACKET_V3 is enabled on this AF_PACKET socket to receive packets efficiently.
8//    - The kernel writes packets into this shared ring area (zero copy).
9//    - User space polls/reads frames directly from the mmap'd ring.
10//
11// 3. All TCP logic (flags, seq/ack, retransmission, state machine, matching responses)
12//    is implemented entirely in user space; the kernel does not manage TCP state here.
13//
14// In short: AF_PACKET for raw TX, TPACKET_V3 for zero‑copy RX, TCP state is all ours. :D
15
16use libc::{
17    AF_PACKET, ETH_P_ALL, SOCK_RAW, htons, if_nametoindex, setsockopt, sockaddr_ll, socket,
18};
19use std::ffi::c_void;
20use std::fs;
21use std::io::{self, BufRead};
22use std::mem;
23use std::net::Ipv4Addr;
24use std::os::fd::RawFd;
25
26const TPACKET_V3: libc::c_int = 2;
27
28// open packet socket to send raw packets at the device driver (OSI Layer 2) level.
29fn open_af_packet() -> RawFd {
30    unsafe {
31        let fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL as u16) as i32);
32
33        if fd < 0 {
34            let err = *libc::__errno_location();
35            if err == libc::EPERM || err == libc::EACCES {
36                panic!(
37                    "AF_PACKET sys call failed;\n Error number: {}; No permissions \n \n Error: CAP_NET_RAW or root privileges required to use raw AF_PACKET sockets.\n - Run with: sudo <cmd>\n - Or give the binary capability: sudo setcap cap_net_raw+ep <binary>\n
38                    ",
39                    err
40                );
41            } else {
42                panic!("AF_PACKET sys call failed; Error number: {}", err);
43            }
44        }
45
46        fd
47    }
48}
49
50fn lookup_arp_cache(ip: Ipv4Addr) -> io::Result<Option<[u8; 6]>> {
51    let file = fs::File::open("proc/net/arp")?;
52    let reader = io::BufReader::new(file);
53
54    for line in reader.lines().skip(1) {
55        let line = line?;
56        let cols: Vec<&str> = line.split_whitespace().collect();
57
58        if cols.len() < 6 {
59            continue;
60        }
61
62        let ip_addr = cols[0];
63        if ip_addr != ip.to_string() {
64            continue;
65        }
66
67        let hw_addr = cols[3];
68        if hw_addr == "00:00:00:00:00:00" {
69            return Ok(None);
70        }
71
72        let bytes: Vec<u8> = hw_addr
73            .split(':')
74            .filter_map(|s| u8::from_str_radix(s, 16).ok())
75            .collect();
76
77        if bytes.len() != 6 {
78            continue;
79        }
80
81        let mut mac = [0u8; 6];
82        mac.copy_from_slice(&bytes);
83        return Ok(Some(mac));
84    }
85    Ok(None)
86}
87
88pub fn resolve_mac(socket: &LinuxSocket, dst_ip: Ipv4Addr) -> io::Result<[u8; 6]> {
89    if let Some(mac) = lookup_arp_cache(dst_ip)? {
90        return Ok(mac);
91    }
92
93    if let Some(mac) = lookup_arp_cache(socket.default_gateway)? {
94        return Ok(mac);
95    }
96
97    // Gateway MAC missing... Needs ARP request (not implemented yet)
98    Err(io::Error::new(
99        io::ErrorKind::NotFound,
100        "MAC address not found in ARP for dst or gateway",
101    ))
102}
103
104fn get_default_ifindex_and_default_gateway() -> io::Result<(i32, Ipv4Addr)> {
105    let file = fs::File::open("/proc/net/route")?;
106    let reader = io::BufReader::new(file);
107
108    for line in reader.lines().skip(1) {
109        let line = line?;
110        let cols: Vec<&str> = line.split_whitespace().collect();
111
112        if cols.len() < 2 {
113            continue;
114        }
115
116        let iface = cols[0];
117        let destination = cols[1];
118        let gateway_hex = cols[2];
119
120        if destination == "00000000" {
121            let c_name = std::ffi::CString::new(iface).unwrap();
122            let index = unsafe { if_nametoindex(c_name.as_ptr()) };
123
124            if index == 0 {
125                return Err(io::Error::new(
126                    io::ErrorKind::Other,
127                    format!("if_nametoindex failed for {}", iface),
128                ));
129            }
130
131            let g = u32::from_str_radix(gateway_hex, 16).unwrap();
132
133            return Ok((index as i32, Ipv4Addr::from(g.swap_bytes())));
134        }
135    }
136
137    let fallback = unsafe { if_nametoindex(std::ffi::CString::new("lo").unwrap().as_ptr()) };
138
139    if fallback != 0 {
140        return Ok((fallback as i32, Ipv4Addr::new(127, 0, 0, 1)));
141    }
142
143    Err(io::Error::new(
144        io::ErrorKind::NotFound,
145        "No usable route found",
146    ))
147}
148
149fn bind_interface(fd: RawFd, if_index: i32) {
150    unsafe {
151        let mut addr: sockaddr_ll = mem::zeroed();
152        addr.sll_family = AF_PACKET as u16;
153        addr.sll_protocol = htons(ETH_P_ALL as u16);
154        addr.sll_ifindex = if_index;
155
156        let ret = libc::bind(
157            fd,
158            &addr as *const sockaddr_ll as *const libc::sockaddr,
159            mem::size_of::<sockaddr_ll>() as u32,
160        );
161
162        if ret < 0 {
163            panic!("AF_PACKET sys call bind failed");
164        }
165    }
166}
167
168fn set_sock_opt(fd: RawFd) -> Result<(*mut c_void, usize), SetSockOpsErrors> {
169    let version = TPACKET_V3;
170
171    let r = unsafe {
172        setsockopt(
173            fd,
174            libc::SOL_PACKET,
175            libc::PACKET_VERSION,
176            &version as *const _ as *const libc::c_void,
177            std::mem::size_of::<libc::c_int>() as libc::socklen_t,
178        )
179    };
180
181    if r != 0 {
182        return Err(SetSockOpsErrors::PacketVersion(
183            "Failed to set PACKET_VERSION TPACKET_V3".to_string(),
184        ));
185    }
186
187    let req = libc::tpacket_req3 {
188        tp_block_size: 1 << 20, // 1MB blocks
189        tp_block_nr: 64,
190        tp_frame_size: 2048,
191        tp_frame_nr: (64 * (1 << 20)) / 2048,
192        tp_retire_blk_tov: 60, // timeout
193        tp_sizeof_priv: 0,
194        tp_feature_req_word: 0,
195    };
196
197    let ret = unsafe {
198        setsockopt(
199            fd,
200            libc::SOL_PACKET,
201            libc::PACKET_RX_RING,
202            &req as *const _ as *const libc::c_void,
203            std::mem::size_of::<libc::tpacket_req3>() as libc::socklen_t,
204        )
205    };
206
207    if ret != 0 {
208        return Err(SetSockOpsErrors::RingBuffer(
209            "Failed to set PACKET_RX_RING".to_string(),
210        ));
211    }
212
213    let mmap_len = (req.tp_block_size * req.tp_block_nr) as usize;
214
215    let mmap = unsafe {
216        libc::mmap(
217            std::ptr::null_mut(),
218            mmap_len,
219            libc::PROT_READ | libc::PROT_WRITE,
220            libc::MAP_SHARED,
221            fd,
222            0,
223        )
224    };
225
226    if mmap.is_null() {
227        return Err(SetSockOpsErrors::Mmap(
228            "Failed set mmap on ring buffer".to_string(),
229        ));
230    }
231
232    Ok((mmap, mmap_len))
233}
234
235#[derive(Debug)]
236pub enum SetSockOpsErrors {
237    PacketVersion(String),
238    RingBuffer(String),
239    Mmap(String),
240}
241
242#[derive(Debug)]
243pub struct LinuxSocket {
244    pub fd: RawFd,
245    pub ifindex: i32,
246    pub default_gateway: Ipv4Addr,
247    pub mmap: Option<(*mut c_void, usize)>,
248}
249
250impl LinuxSocket {
251    pub fn new() -> Result<Self, LinuxSocketErrors> {
252        let (ifindex, default_gateway) = get_default_ifindex_and_default_gateway()
253            .map_err(|e| LinuxSocketErrors::DefaultIfIndex(e))?;
254
255        let fd = open_af_packet();
256        bind_interface(fd, ifindex);
257
258        Ok(LinuxSocket {
259            fd,
260            default_gateway,
261            ifindex,
262            mmap: None,
263        })
264    }
265
266    pub fn set_ops(&mut self) -> Result<(), LinuxSocketErrors> {
267        let (mmap, ptr_len) =
268            set_sock_opt(self.fd).map_err(|e| LinuxSocketErrors::SetSockOps(e))?;
269
270        self.mmap.replace((mmap, ptr_len));
271        Ok(())
272    }
273
274    pub fn send_raw_packet(
275        &self,
276        dst_mac: [u8; 6],
277        ether_type: u16,
278        packet: &[u8],
279    ) -> Result<(), LinuxSocketErrors> {
280        unsafe {
281            let mut addr: sockaddr_ll = mem::zeroed();
282            addr.sll_family = AF_PACKET as u16;
283            addr.sll_ifindex = self.ifindex;
284            addr.sll_protocol = htons(ether_type);
285            addr.sll_halen = 6;
286            addr.sll_addr[..6].copy_from_slice(&dst_mac);
287
288            let sent = libc::sendto(
289                self.fd,
290                packet.as_ptr() as *const _ as *const c_void,
291                packet.len(),
292                0,
293                &addr as *const sockaddr_ll as *const libc::sockaddr,
294                mem::size_of::<sockaddr_ll>() as u32,
295            );
296
297            if sent == -1 {
298                return Err(LinuxSocketErrors::SendingPacket(io::Error::last_os_error()));
299            }
300            Ok(())
301        }
302    }
303}
304
305impl Drop for LinuxSocket {
306    fn drop(&mut self) {
307        if let Some((mmap, ptr_len)) = self.mmap.take() {
308            let res = unsafe { libc::munmap(mmap, ptr_len) };
309            if res != 0 {
310                eprintln!("Failed to remove any mappings from the adress space");
311            }
312        }
313
314        unsafe { libc::close(self.fd) };
315    }
316}
317
318#[derive(Debug)]
319pub enum LinuxSocketErrors {
320    DefaultIfIndex(std::io::Error),
321    SetSockOps(SetSockOpsErrors),
322    SendingPacket(std::io::Error),
323}
324
325#[cfg(test)]
326mod tests {
327
328    use super::*;
329
330    #[test]
331    fn linux_af_packet_test() {
332        let (if_index, _default_gateway) = get_default_ifindex_and_default_gateway().unwrap();
333
334        let fd = open_af_packet();
335
336        bind_interface(fd, if_index);
337
338        let mut buf = [0u8; u16::MAX as usize];
339        unsafe {
340            let n = libc::recv(fd, buf.as_mut_ptr() as *mut _, buf.len(), 0);
341            assert!(n > 0);
342        }
343    }
344
345    #[test]
346    fn linux_af_packet_test_with_ring_buffer() {
347        let (if_index, _default_gateway) = get_default_ifindex_and_default_gateway().unwrap();
348
349        let fd = open_af_packet();
350
351        bind_interface(fd, if_index);
352
353        let mmap_ptr = set_sock_opt(fd);
354
355        assert!(mmap_ptr.is_ok());
356
357        let (mmap, len) = mmap_ptr.unwrap();
358
359        let res = unsafe { libc::munmap(mmap, len) };
360
361        assert!(res == 0);
362    }
363
364    #[test]
365    fn linux_socket_test() {
366        let socket = LinuxSocket::new();
367
368        assert!(socket.is_ok());
369
370        let mut s = socket.unwrap();
371
372        let res = s.set_ops();
373
374        assert!(res.is_ok());
375    }
376}