Skip to main content

ruvix_net/
stack.rs

1//! Network stack integration.
2//!
3//! This module provides the `NetworkStack` type that combines all network
4//! layers (Ethernet, ARP, IPv4, UDP, ICMP) into a cohesive stack.
5
6use crate::arp::{ArpCache, ArpPacket};
7use crate::device::NetworkDevice;
8use crate::error::{NetError, NetResult};
9use crate::ethernet::{EtherType, EthernetFrame, MacAddress};
10use crate::icmp::{IcmpEcho, IcmpHeader, IcmpType};
11use crate::ipv4::{Ipv4Addr, Ipv4Header, Protocol};
12use crate::udp::UdpHeader;
13use crate::{ETHERNET_HEADER_SIZE, IPV4_HEADER_MIN_SIZE, MTU, UDP_HEADER_SIZE};
14
15/// Network stack configuration.
16#[derive(Debug, Clone, Copy)]
17pub struct StackConfig {
18    /// Local IP address.
19    pub ip_addr: Ipv4Addr,
20    /// Subnet mask.
21    pub subnet_mask: Ipv4Addr,
22    /// Default gateway.
23    pub gateway: Ipv4Addr,
24    /// ARP cache timeout (in abstract time units).
25    pub arp_timeout: u64,
26    /// Default TTL for outgoing packets.
27    pub default_ttl: u8,
28    /// Enable ICMP echo reply (ping).
29    pub enable_ping: bool,
30}
31
32impl Default for StackConfig {
33    fn default() -> Self {
34        Self {
35            ip_addr: Ipv4Addr::UNSPECIFIED,
36            subnet_mask: Ipv4Addr::new(255, 255, 255, 0),
37            gateway: Ipv4Addr::UNSPECIFIED,
38            arp_timeout: 300,
39            default_ttl: 64,
40            enable_ping: true,
41        }
42    }
43}
44
45/// Network stack state.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum StackState {
48    /// Stack is not initialized.
49    Uninitialized,
50    /// Stack is initializing.
51    Initializing,
52    /// Stack is ready to send/receive.
53    Ready,
54    /// Stack has encountered an error.
55    Error,
56}
57
58/// Received packet information.
59#[derive(Debug, Clone, Copy)]
60pub struct ReceivedPacket {
61    /// Source MAC address.
62    pub src_mac: MacAddress,
63    /// Source IP address.
64    pub src_ip: Ipv4Addr,
65    /// Source port (for UDP).
66    pub src_port: u16,
67    /// Destination port (for UDP).
68    pub dst_port: u16,
69    /// Protocol.
70    pub protocol: Protocol,
71    /// Payload offset in buffer.
72    pub payload_offset: usize,
73    /// Payload length.
74    pub payload_len: usize,
75}
76
77/// Minimal network stack for `no_std` environments.
78///
79/// This stack supports:
80/// - Ethernet II frames
81/// - ARP resolution
82/// - IPv4 routing
83/// - UDP datagrams
84/// - ICMP echo (ping)
85pub struct NetworkStack<D: NetworkDevice> {
86    /// Underlying network device.
87    device: D,
88    /// Stack configuration.
89    config: StackConfig,
90    /// ARP cache.
91    arp_cache: ArpCache,
92    /// Stack state.
93    state: StackState,
94    /// Packet identification counter.
95    ip_id: u16,
96    /// Current time (abstract units, must be updated externally).
97    current_time: u64,
98}
99
100impl<D: NetworkDevice> NetworkStack<D> {
101    /// Creates a new network stack.
102    #[inline]
103    #[must_use]
104    pub fn new(device: D, config: StackConfig) -> Self {
105        Self {
106            device,
107            config,
108            arp_cache: ArpCache::with_timeout(config.arp_timeout),
109            state: StackState::Ready,
110            ip_id: 0,
111            current_time: 0,
112        }
113    }
114
115    /// Returns the MAC address of the device.
116    #[inline]
117    #[must_use]
118    pub fn mac_address(&self) -> MacAddress {
119        self.device.mac_address()
120    }
121
122    /// Returns the IP address.
123    #[inline]
124    #[must_use]
125    pub const fn ip_address(&self) -> Ipv4Addr {
126        self.config.ip_addr
127    }
128
129    /// Returns the stack state.
130    #[inline]
131    #[must_use]
132    pub const fn state(&self) -> StackState {
133        self.state
134    }
135
136    /// Returns a reference to the ARP cache.
137    #[inline]
138    #[must_use]
139    pub const fn arp_cache(&self) -> &ArpCache {
140        &self.arp_cache
141    }
142
143    /// Updates the current time (must be called periodically).
144    #[inline]
145    pub fn tick(&mut self, current_time: u64) {
146        self.current_time = current_time;
147        self.arp_cache.expire_stale(current_time);
148    }
149
150    /// Gets the next IP identification number.
151    #[inline]
152    fn next_ip_id(&mut self) -> u16 {
153        let id = self.ip_id;
154        self.ip_id = self.ip_id.wrapping_add(1);
155        id
156    }
157
158    /// Determines if a destination is on the local network.
159    #[inline]
160    #[must_use]
161    pub fn is_local(&self, dest: Ipv4Addr) -> bool {
162        self.config
163            .ip_addr
164            .is_same_subnet(&dest, &self.config.subnet_mask)
165    }
166
167    /// Gets the next-hop MAC address for a destination.
168    ///
169    /// If the destination is local, resolves via ARP.
170    /// If remote, returns the gateway's MAC.
171    pub fn resolve_next_hop(&mut self, dest: Ipv4Addr) -> NetResult<Option<MacAddress>> {
172        let next_hop = if self.is_local(dest) {
173            dest
174        } else {
175            self.config.gateway
176        };
177
178        // Check ARP cache
179        if let Some(mac) = self.arp_cache.resolve(next_hop, self.current_time) {
180            return Ok(Some(mac));
181        }
182
183        // Send ARP request
184        self.send_arp_request(next_hop)?;
185
186        Ok(None)
187    }
188
189    /// Sends an ARP request for the given IP.
190    pub fn send_arp_request(&mut self, target_ip: Ipv4Addr) -> NetResult<()> {
191        let arp = ArpPacket::request(self.device.mac_address(), self.config.ip_addr, target_ip);
192
193        let mut buf = [0u8; 64];
194
195        // Build Ethernet frame
196        let frame = EthernetFrame::new(
197            MacAddress::BROADCAST,
198            self.device.mac_address(),
199            EtherType::Arp,
200            &[], // Payload will be added below
201        );
202
203        let eth_len = frame.serialize(&mut buf)?;
204
205        // Serialize ARP packet
206        let arp_len = arp.serialize(&mut buf[ETHERNET_HEADER_SIZE..])?;
207
208        // Send frame
209        self.device.send(&buf[..ETHERNET_HEADER_SIZE + arp_len])?;
210
211        // Mark as pending in cache
212        self.arp_cache.mark_pending(target_ip, self.current_time)?;
213
214        // Fix unused variable warning
215        let _ = eth_len;
216
217        Ok(())
218    }
219
220    /// Sends a UDP datagram.
221    ///
222    /// Returns `NetError::ArpNotFound` if the destination MAC is not cached.
223    /// In this case, an ARP request has been sent and the caller should retry.
224    pub fn send_udp(
225        &mut self,
226        src_port: u16,
227        dst_ip: Ipv4Addr,
228        dst_port: u16,
229        payload: &[u8],
230    ) -> NetResult<()> {
231        // Check payload size
232        let max_payload = MTU - IPV4_HEADER_MIN_SIZE - UDP_HEADER_SIZE;
233        if payload.len() > max_payload {
234            return Err(NetError::PacketTooLarge);
235        }
236
237        // Resolve destination MAC
238        let dst_mac = match self.resolve_next_hop(dst_ip)? {
239            Some(mac) => mac,
240            None => return Err(NetError::ArpNotFound),
241        };
242
243        let mut buf = [0u8; 1536];
244        let mut offset = 0;
245
246        // Leave space for Ethernet header
247        offset += ETHERNET_HEADER_SIZE;
248
249        // Build IPv4 header
250        let ip_payload_len = UDP_HEADER_SIZE + payload.len();
251        let mut ip_header =
252            Ipv4Header::new(self.config.ip_addr, dst_ip, Protocol::Udp, ip_payload_len as u16);
253        ip_header.identification = self.next_ip_id();
254        ip_header.ttl = self.config.default_ttl;
255
256        let ip_len = ip_header.serialize(&mut buf[offset..])?;
257        offset += ip_len;
258
259        // Build UDP header
260        let udp_header = UdpHeader::new(src_port, dst_port, payload.len() as u16);
261        let udp_len = udp_header.serialize(&mut buf[offset..])?;
262
263        // Copy payload
264        buf[offset + udp_len..offset + udp_len + payload.len()].copy_from_slice(payload);
265
266        // Compute and fill UDP checksum
267        let checksum = UdpHeader::compute_checksum(
268            self.config.ip_addr,
269            dst_ip,
270            &udp_header,
271            payload,
272        );
273        buf[offset + 6..offset + 8].copy_from_slice(&checksum.to_be_bytes());
274
275        offset += udp_len + payload.len();
276
277        // Build Ethernet header directly
278        let src_mac = self.device.mac_address();
279        buf[0..6].copy_from_slice(&dst_mac.0);
280        buf[6..12].copy_from_slice(&src_mac.0);
281        buf[12..14].copy_from_slice(&EtherType::Ipv4.to_u16().to_be_bytes());
282
283        // Send
284        self.device.send(&buf[..offset])
285    }
286
287    /// Sends an ICMP echo request (ping).
288    pub fn send_ping(
289        &mut self,
290        dst_ip: Ipv4Addr,
291        identifier: u16,
292        sequence: u16,
293        data: &[u8],
294    ) -> NetResult<()> {
295        // Resolve destination MAC
296        let dst_mac = match self.resolve_next_hop(dst_ip)? {
297            Some(mac) => mac,
298            None => return Err(NetError::ArpNotFound),
299        };
300
301        let mut buf = [0u8; 1536];
302        let mut offset = 0;
303
304        // Leave space for Ethernet header
305        offset += ETHERNET_HEADER_SIZE;
306
307        // Build ICMP echo request
308        let echo = IcmpEcho {
309            identifier,
310            sequence,
311            data,
312        };
313        let icmp_start = offset + IPV4_HEADER_MIN_SIZE;
314        let icmp_len = echo.serialize(true, &mut buf[icmp_start..])?;
315
316        // Build IPv4 header
317        let mut ip_header =
318            Ipv4Header::new(self.config.ip_addr, dst_ip, Protocol::Icmp, icmp_len as u16);
319        ip_header.identification = self.next_ip_id();
320        ip_header.ttl = self.config.default_ttl;
321
322        ip_header.serialize(&mut buf[offset..])?;
323        offset = icmp_start + icmp_len;
324
325        // Build Ethernet header directly
326        let src_mac = self.device.mac_address();
327        buf[0..6].copy_from_slice(&dst_mac.0);
328        buf[6..12].copy_from_slice(&src_mac.0);
329        buf[12..14].copy_from_slice(&EtherType::Ipv4.to_u16().to_be_bytes());
330
331        // Send
332        self.device.send(&buf[..offset])
333    }
334
335    /// Processes a received frame.
336    ///
337    /// Returns information about the received packet if it contains
338    /// application-layer data (e.g., UDP payload).
339    pub fn receive(&mut self, buf: &mut [u8]) -> NetResult<Option<ReceivedPacket>> {
340        // Try to receive a frame
341        let frame_len = match self.device.receive(buf)? {
342            Some(len) => len,
343            None => return Ok(None),
344        };
345
346        if frame_len < ETHERNET_HEADER_SIZE {
347            return Ok(None);
348        }
349
350        // Parse Ethernet frame header manually to avoid borrow issues
351        let dest_mac = MacAddress::parse(&buf[0..6])?;
352        let src_mac = MacAddress::parse(&buf[6..12])?;
353        let ether_type = EtherType::from_u16(u16::from_be_bytes([buf[12], buf[13]]));
354
355        // Check if it's for us
356        let our_mac = self.device.mac_address();
357        if !dest_mac.is_broadcast()
358            && dest_mac != our_mac
359            && !dest_mac.is_multicast()
360        {
361            return Ok(None);
362        }
363
364        match ether_type {
365            EtherType::Arp => {
366                self.handle_arp(&buf[ETHERNET_HEADER_SIZE..frame_len])?;
367                Ok(None)
368            }
369            EtherType::Ipv4 => self.handle_ipv4_owned(src_mac, buf, frame_len),
370            _ => Ok(None),
371        }
372    }
373
374    /// Handles an ARP packet.
375    fn handle_arp(&mut self, payload: &[u8]) -> NetResult<()> {
376        let arp = ArpPacket::parse(payload)?;
377
378        // Update cache with sender's info
379        self.arp_cache
380            .insert(arp.sender_ip, arp.sender_mac, self.current_time)?;
381
382        // If this is a request for our IP, send a reply
383        if arp.is_request() && arp.target_ip == self.config.ip_addr {
384            let reply =
385                ArpPacket::reply(self.device.mac_address(), self.config.ip_addr, arp.sender_mac, arp.sender_ip);
386
387            let mut buf = [0u8; 64];
388
389            // Ethernet header (overwritten)
390            let frame = EthernetFrame::new(
391                arp.sender_mac,
392                self.device.mac_address(),
393                EtherType::Arp,
394                &[],
395            );
396            frame.serialize(&mut buf)?;
397
398            // ARP reply
399            let arp_len = reply.serialize(&mut buf[ETHERNET_HEADER_SIZE..])?;
400
401            self.device.send(&buf[..ETHERNET_HEADER_SIZE + arp_len])?;
402        }
403
404        Ok(())
405    }
406
407    /// Handles an IPv4 packet (owns buffer to avoid borrow issues).
408    fn handle_ipv4_owned(
409        &mut self,
410        src_mac: MacAddress,
411        buf: &mut [u8],
412        frame_len: usize,
413    ) -> NetResult<Option<ReceivedPacket>> {
414        let (ip_header, ip_payload) = Ipv4Header::parse(&buf[ETHERNET_HEADER_SIZE..frame_len])?;
415
416        // Check destination
417        if ip_header.dst_addr != self.config.ip_addr
418            && !ip_header.dst_addr.is_broadcast()
419            && !ip_header.dst_addr.is_multicast()
420        {
421            return Ok(None);
422        }
423
424        // Check for fragmentation
425        if ip_header.flags.more_fragments || ip_header.fragment_offset != 0 {
426            return Err(NetError::FragmentationNotSupported);
427        }
428
429        match ip_header.protocol {
430            Protocol::Icmp => {
431                self.handle_icmp(src_mac, &ip_header, ip_payload)?;
432                Ok(None)
433            }
434            Protocol::Udp => self.handle_udp(src_mac, &ip_header, ip_payload),
435            _ => Ok(None),
436        }
437    }
438
439    /// Handles an ICMP packet.
440    fn handle_icmp(
441        &mut self,
442        _src_mac: MacAddress,
443        ip_header: &Ipv4Header,
444        payload: &[u8],
445    ) -> NetResult<()> {
446        let (icmp_header, icmp_payload) = IcmpHeader::parse(payload)?;
447
448        // Verify checksum
449        if !IcmpHeader::verify_checksum(payload) {
450            return Ok(()); // Silently drop
451        }
452
453        // Handle echo request
454        if self.config.enable_ping && icmp_header.icmp_type == IcmpType::EchoRequest {
455            let echo = IcmpEcho::parse(&icmp_header, icmp_payload)?;
456
457            // Send echo reply
458            self.send_icmp_echo_reply(ip_header.src_addr, echo.identifier, echo.sequence, echo.data)?;
459        }
460
461        Ok(())
462    }
463
464    /// Sends an ICMP echo reply.
465    fn send_icmp_echo_reply(
466        &mut self,
467        dst_ip: Ipv4Addr,
468        identifier: u16,
469        sequence: u16,
470        data: &[u8],
471    ) -> NetResult<()> {
472        // Resolve destination MAC
473        let dst_mac = match self.arp_cache.resolve(dst_ip, self.current_time) {
474            Some(mac) => mac,
475            None => return Ok(()), // Don't have MAC, silently drop
476        };
477
478        let mut buf = [0u8; 1536];
479        let mut offset = 0;
480
481        // Leave space for Ethernet header
482        offset += ETHERNET_HEADER_SIZE;
483
484        // Build ICMP echo reply
485        let echo = IcmpEcho {
486            identifier,
487            sequence,
488            data,
489        };
490        let icmp_start = offset + IPV4_HEADER_MIN_SIZE;
491        let icmp_len = echo.serialize(false, &mut buf[icmp_start..])?; // false = reply
492
493        // Build IPv4 header
494        let mut ip_header =
495            Ipv4Header::new(self.config.ip_addr, dst_ip, Protocol::Icmp, icmp_len as u16);
496        ip_header.identification = self.next_ip_id();
497        ip_header.ttl = self.config.default_ttl;
498
499        ip_header.serialize(&mut buf[offset..])?;
500        offset = icmp_start + icmp_len;
501
502        // Build Ethernet header directly
503        let src_mac = self.device.mac_address();
504        buf[0..6].copy_from_slice(&dst_mac.0);
505        buf[6..12].copy_from_slice(&src_mac.0);
506        buf[12..14].copy_from_slice(&EtherType::Ipv4.to_u16().to_be_bytes());
507
508        // Send
509        self.device.send(&buf[..offset])
510    }
511
512    /// Handles a UDP packet.
513    fn handle_udp(
514        &mut self,
515        src_mac: MacAddress,
516        ip_header: &Ipv4Header,
517        payload: &[u8],
518    ) -> NetResult<Option<ReceivedPacket>> {
519        let (udp_header, udp_payload) = UdpHeader::parse(payload)?;
520
521        // Verify checksum if present
522        if udp_header.checksum != 0 {
523            if !UdpHeader::verify_checksum(
524                ip_header.src_addr,
525                ip_header.dst_addr,
526                &udp_header,
527                udp_payload,
528            ) {
529                return Err(NetError::UdpChecksumError);
530            }
531        }
532
533        Ok(Some(ReceivedPacket {
534            src_mac,
535            src_ip: ip_header.src_addr,
536            src_port: udp_header.src_port,
537            dst_port: udp_header.dst_port,
538            protocol: Protocol::Udp,
539            payload_offset: ETHERNET_HEADER_SIZE
540                + ip_header.header_len()
541                + UDP_HEADER_SIZE,
542            payload_len: udp_payload.len(),
543        }))
544    }
545
546    /// Returns a reference to the underlying device.
547    #[inline]
548    #[must_use]
549    pub const fn device(&self) -> &D {
550        &self.device
551    }
552
553    /// Returns a mutable reference to the underlying device.
554    #[inline]
555    pub fn device_mut(&mut self) -> &mut D {
556        &mut self.device
557    }
558}
559
560#[cfg(test)]
561mod tests {
562    use super::*;
563    use crate::device::LoopbackDevice;
564
565    fn create_test_stack() -> NetworkStack<LoopbackDevice> {
566        let device = LoopbackDevice::new();
567        let config = StackConfig {
568            ip_addr: Ipv4Addr::new(192, 168, 1, 1),
569            subnet_mask: Ipv4Addr::new(255, 255, 255, 0),
570            gateway: Ipv4Addr::new(192, 168, 1, 254),
571            ..Default::default()
572        };
573        NetworkStack::new(device, config)
574    }
575
576    #[test]
577    fn test_stack_creation() {
578        let stack = create_test_stack();
579        assert_eq!(stack.ip_address(), Ipv4Addr::new(192, 168, 1, 1));
580        assert_eq!(stack.state(), StackState::Ready);
581    }
582
583    #[test]
584    fn test_is_local() {
585        let stack = create_test_stack();
586
587        // Same subnet
588        assert!(stack.is_local(Ipv4Addr::new(192, 168, 1, 100)));
589
590        // Different subnet
591        assert!(!stack.is_local(Ipv4Addr::new(10, 0, 0, 1)));
592    }
593
594    #[test]
595    fn test_ip_id_counter() {
596        let mut stack = create_test_stack();
597
598        let id1 = stack.next_ip_id();
599        let id2 = stack.next_ip_id();
600        let id3 = stack.next_ip_id();
601
602        assert_eq!(id1, 0);
603        assert_eq!(id2, 1);
604        assert_eq!(id3, 2);
605    }
606
607    #[test]
608    fn test_tick() {
609        let mut stack = create_test_stack();
610
611        // Add an ARP entry
612        stack
613            .arp_cache
614            .insert(
615                Ipv4Addr::new(192, 168, 1, 2),
616                MacAddress::new([0, 1, 2, 3, 4, 5]),
617                0,
618            )
619            .unwrap();
620
621        // Should resolve
622        assert!(stack
623            .arp_cache
624            .resolve(Ipv4Addr::new(192, 168, 1, 2), 0)
625            .is_some());
626
627        // After timeout, should not resolve
628        stack.tick(1000);
629        assert!(stack
630            .arp_cache
631            .resolve(Ipv4Addr::new(192, 168, 1, 2), 1000)
632            .is_none());
633    }
634
635    #[test]
636    fn test_arp_request_send() {
637        let mut stack = create_test_stack();
638
639        // This should send an ARP request
640        let result = stack.send_arp_request(Ipv4Addr::new(192, 168, 1, 2));
641        assert!(result.is_ok());
642
643        // Entry should be pending
644        let entry = stack.arp_cache.lookup(Ipv4Addr::new(192, 168, 1, 2));
645        assert!(entry.is_some());
646    }
647
648    #[test]
649    fn test_receive_empty() {
650        let mut stack = create_test_stack();
651        let mut buf = [0u8; 1536];
652
653        // Should return None when no packets
654        let result = stack.receive(&mut buf).unwrap();
655        assert!(result.is_none());
656    }
657
658    #[test]
659    fn test_stack_config_default() {
660        let config = StackConfig::default();
661        assert_eq!(config.default_ttl, 64);
662        assert!(config.enable_ping);
663        assert_eq!(config.arp_timeout, 300);
664    }
665}