Skip to main content

strat9_kernel/syscall/
net.rs

1//! Network syscall handlers.
2//!
3//! Implements the userspace networking interface: send, recv, and info.
4
5use super::error::SyscallError;
6use crate::memory::{UserSliceRead, UserSliceWrite};
7use core::sync::atomic::{AtomicU32, Ordering};
8use smallvec::SmallVec;
9
10/// Most Ethernet frames, including ICMP echo, fit comfortably under 2 KiB.
11/// Keep those on the stack to avoid hot-path heap churn in net send/recv syscalls.
12const NET_INLINE_BUF_CAPACITY: usize = 2048;
13
14/// Budget for logging network send errors to prevent log spam.
15static NET_SEND_ERR_LOG_BUDGET: AtomicU32 = AtomicU32::new(32);
16/// Budget for logging network receive errors to prevent log spam.
17static NET_RECV_ERR_LOG_BUDGET: AtomicU32 = AtomicU32::new(32);
18/// Budget for logging DHCP trace frames to prevent log spam.
19static DHCP_TRACE_LOG_BUDGET: AtomicU32 = AtomicU32::new(64);
20
21/// Parse and log a raw Ethernet frame if it looks like DHCP.
22///
23/// Only fires while the budget allows (64 frames), then goes silent.
24fn trace_dhcp_frame(tag: &str, frame: &[u8]) {
25    if DHCP_TRACE_LOG_BUDGET.fetch_sub(1, Ordering::Relaxed) == 0 {
26        return;
27    }
28    if frame.len() < 14 {
29        return;
30    }
31    let ethertype = u16::from_be_bytes([frame[12], frame[13]]);
32    if ethertype != 0x0800 {
33        return;
34    }
35    if frame.len() < 34 {
36        return;
37    }
38    let ip_hlen = ((frame[14] & 0x0f) as usize) * 4;
39    if ip_hlen < 20 || frame.len() < 14 + ip_hlen + 8 {
40        return;
41    }
42    if frame[23] != 17 {
43        return;
44    }
45    let udp = 14 + ip_hlen;
46    let src_port = u16::from_be_bytes([frame[udp], frame[udp + 1]]);
47    let dst_port = u16::from_be_bytes([frame[udp + 2], frame[udp + 3]]);
48    let is_dhcp = (src_port == 68 && dst_port == 67) || (src_port == 67 && dst_port == 68);
49    if !is_dhcp {
50        return;
51    }
52    let mut xid: u32 = 0;
53    let bootp = udp + 8;
54    if frame.len() >= bootp + 8 {
55        xid = u32::from_be_bytes([
56            frame[bootp + 4],
57            frame[bootp + 5],
58            frame[bootp + 6],
59            frame[bootp + 7],
60        ]);
61    }
62    crate::serial_println!(
63        "[dhcp-trace] {} src_port={} dst_port={} len={} xid=0x{:08x}",
64        tag,
65        src_port,
66        dst_port,
67        frame.len(),
68        xid
69    );
70}
71
72/// SYS_NET_RECV : Receive a raw Ethernet frame.
73pub fn sys_net_recv(buf_ptr: u64, buf_len: u64) -> Result<u64, SyscallError> {
74    let device = crate::hardware::nic::get_default_device().ok_or(SyscallError::Again)?;
75    let buf_len = buf_len as usize;
76    let mut kbuf = SmallVec::<[u8; NET_INLINE_BUF_CAPACITY]>::new();
77    kbuf.resize(buf_len, 0u8);
78
79    let n = match device.receive(&mut kbuf) {
80        Ok(n) => n,
81        Err(e) => {
82            let se = SyscallError::from(e);
83            if se != SyscallError::Again
84                && NET_RECV_ERR_LOG_BUDGET.fetch_sub(1, Ordering::Relaxed) > 0
85            {
86                crate::serial_println!("[net-sys] recv error: {:?} -> {}", e, se.name());
87            }
88            return Err(se);
89        }
90    };
91    crate::serial_println!("[net] recv {} bytes", n);
92    if n > 50 {
93        crate::serial_print!("[net] rx icmp");
94        for i in 34..50 {
95            crate::serial_print!(" {:02x}", kbuf[i]);
96        }
97        crate::serial_println!("");
98    }
99    trace_dhcp_frame("rx", &kbuf[..n]);
100
101    let user = UserSliceWrite::new(buf_ptr, n)?;
102    user.copy_from(&kbuf[..n]);
103    Ok(n as u64)
104}
105
106/// SYS_NET_SEND : Transmit a raw Ethernet frame.
107pub fn sys_net_send(buf_ptr: u64, buf_len: u64) -> Result<u64, SyscallError> {
108    let device = crate::hardware::nic::get_default_device().ok_or(SyscallError::Again)?;
109    let buf_len = buf_len as usize;
110    let user = UserSliceRead::new(buf_ptr, buf_len)?;
111    let mut kbuf = SmallVec::<[u8; NET_INLINE_BUF_CAPACITY]>::new();
112    kbuf.resize(buf_len, 0u8);
113    let copied = user.copy_to(&mut kbuf);
114    if copied != buf_len {
115        return Err(SyscallError::Fault);
116    }
117    trace_dhcp_frame("tx", &kbuf);
118
119    if let Err(e) = device.transmit(&kbuf) {
120        let se = SyscallError::from(e);
121        if NET_SEND_ERR_LOG_BUDGET.fetch_sub(1, Ordering::Relaxed) > 0 {
122            crate::serial_println!("[net-sys] send error: {:?} -> {}", e, se.name());
123        }
124        return Err(se);
125    }
126
127    crate::serial_println!("[net] tx ok {} bytes", buf_len);
128    Ok(buf_len as u64)
129}
130
131/// SYS_NET_INFO : Query network interface information.
132///
133/// `info_type` 0 returns the MAC address (6 bytes) into `buf_ptr`.
134pub fn sys_net_info(info_type: u64, buf_ptr: u64) -> Result<u64, SyscallError> {
135    let device = crate::hardware::nic::get_default_device().ok_or(SyscallError::Again)?;
136
137    match info_type {
138        0 => {
139            let mac = device.mac_address();
140            let user = UserSliceWrite::new(buf_ptr, 6)?;
141            user.copy_from(&mac);
142            Ok(6)
143        }
144        _ => Err(SyscallError::InvalidArgument),
145    }
146}