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 buf_len = buf_len as usize;
75    let mut kbuf = SmallVec::<[u8; NET_INLINE_BUF_CAPACITY]>::new();
76    kbuf.resize(buf_len, 0u8);
77
78    // Prefer the N2 ring (IRQ-filled). If it is empty — no IRQ wired yet,
79    // or a dropped interrupt — fall back to a direct HW receive so DHCP
80    // and link-local IPv6 still make progress.
81    let n = {
82        let from_ring = {
83            let dp_guard = crate::hardware::nic::data_plane().lock();
84            match dp_guard.as_ref() {
85                Some(dp) => match dp.pop_rx(0, &mut kbuf) {
86                    Ok(Some(n)) => Some(n),
87                    _ => None,
88                },
89                None => None,
90            }
91        };
92        if let Some(n) = from_ring {
93            n
94        } else {
95            let device = crate::hardware::nic::get_default_device().ok_or(SyscallError::Again)?;
96            match device.receive(&mut kbuf) {
97                Ok(n) => n,
98                Err(e) => {
99                    let se = SyscallError::from(e);
100                    if se != SyscallError::Again
101                        && NET_RECV_ERR_LOG_BUDGET.fetch_sub(1, Ordering::Relaxed) > 0
102                    {
103                        crate::serial_println!("[net-sys] recv error: {:?} -> {}", e, se.name());
104                    }
105                    return Err(se);
106                }
107            }
108        }
109    };
110    crate::serial_println!("[net] recv {} bytes", n);
111    if n > 50 {
112        crate::serial_print!("[net] rx icmp");
113        for i in 34..50 {
114            crate::serial_print!(" {:02x}", kbuf[i]);
115        }
116        crate::serial_println!("");
117    }
118    trace_dhcp_frame("rx", &kbuf[..n]);
119
120    let user = UserSliceWrite::new(buf_ptr, n)?;
121    user.copy_from(&kbuf[..n]);
122    Ok(n as u64)
123}
124
125/// SYS_NET_SEND : Transmit a raw Ethernet frame.
126pub fn sys_net_send(buf_ptr: u64, buf_len: u64) -> Result<u64, SyscallError> {
127    let buf_len = buf_len as usize;
128    let user = UserSliceRead::new(buf_ptr, buf_len)?;
129    let mut kbuf = SmallVec::<[u8; NET_INLINE_BUF_CAPACITY]>::new();
130    kbuf.resize(buf_len, 0u8);
131    let copied = user.copy_to(&mut kbuf);
132    if copied != buf_len {
133        return Err(SyscallError::Fault);
134    }
135    trace_dhcp_frame("tx", &kbuf);
136
137    // Use the N2 TX ring only when an IRQ handler will drain it.
138    // Without a registered NIC device the ring would stall forever.
139    if crate::hardware::nic::nic_irq_ready() {
140        let pushed = {
141            let dp_guard = crate::hardware::nic::data_plane().lock();
142            match dp_guard.as_ref() {
143                Some(dp) => match dp.push_tx(0, &kbuf) {
144                    Ok(()) => {
145                        dp.notify_tx_producer(0);
146                        true
147                    }
148                    Err(_) => false,
149                },
150                None => false,
151            }
152        };
153        if pushed {
154            crate::serial_println!("[net] tx ok {} bytes (N2 ring)", buf_len);
155            return Ok(buf_len as u64);
156        }
157    }
158
159    // Direct HW transmit (no N2, no IRQ, or ring full).
160    let device = crate::hardware::nic::get_default_device().ok_or(SyscallError::Again)?;
161    if let Err(e) = device.transmit(&kbuf) {
162        let se = SyscallError::from(e);
163        if NET_SEND_ERR_LOG_BUDGET.fetch_sub(1, Ordering::Relaxed) > 0 {
164            crate::serial_println!("[net-sys] send error: {:?} -> {}", e, se.name());
165        }
166        return Err(se);
167    }
168
169    crate::serial_println!("[net] tx ok {} bytes (direct)", buf_len);
170    Ok(buf_len as u64)
171}
172
173/// SYS_NET_REGISTER : Register the calling task as the strate-net silo.
174///
175/// Called once by strate-net during startup.  Stores the task ID for
176/// IRQ-driven wakeup in the NIC handler.
177pub fn sys_net_register() -> Result<u64, SyscallError> {
178    let task = crate::process::current_task_clone().ok_or(SyscallError::PermissionDenied)?;
179    crate::hardware::nic::register_strate_net_tid(task.id.as_u64());
180    log::info!("[net] strate-net registered (tid={})", task.id.as_u64());
181    Ok(0)
182}
183
184/// SYS_NET_INFO : Query network interface information.
185///
186/// `info_type` 0 returns the MAC address (6 bytes) into `buf_ptr`.
187pub fn sys_net_info(info_type: u64, buf_ptr: u64) -> Result<u64, SyscallError> {
188    let device = crate::hardware::nic::get_default_device().ok_or(SyscallError::Again)?;
189
190    match info_type {
191        0 => {
192            let mac = device.mac_address();
193            let user = UserSliceWrite::new(buf_ptr, 6)?;
194            user.copy_from(&mac);
195            Ok(6)
196        }
197        _ => Err(SyscallError::InvalidArgument),
198    }
199}