⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/net/
arp.rs

1//! ARP (Address Resolution Protocol) implementation
2//!
3//! Provides ARP cache management and request/reply processing for
4//! resolving IPv4 addresses to MAC addresses on the local network.
5
6#![allow(dead_code)] // Phase 6 network stack -- functions called as stack matures
7
8use alloc::{collections::BTreeMap, vec::Vec};
9
10use spin::Mutex;
11
12use crate::{
13    error::KernelError,
14    net::{Ipv4Address, MacAddress},
15};
16
17/// ARP hardware type: Ethernet
18const ARP_HTYPE_ETHERNET: u16 = 1;
19/// ARP protocol type: IPv4
20const ARP_PTYPE_IPV4: u16 = 0x0800;
21/// ARP operation: Request
22const ARP_OP_REQUEST: u16 = 1;
23/// ARP operation: Reply
24const ARP_OP_REPLY: u16 = 2;
25/// ARP header size for Ethernet/IPv4: 28 bytes
26const ARP_PACKET_SIZE: usize = 28;
27
28/// Maximum ARP cache entries
29const ARP_CACHE_MAX: usize = 128;
30
31/// ARP cache entry
32#[derive(Debug, Clone)]
33struct ArpEntry {
34    mac: MacAddress,
35    /// Tick count when this entry was created/refreshed
36    timestamp: u64,
37}
38
39/// Global ARP cache: Ipv4Address -> MacAddress
40static ARP_CACHE: Mutex<BTreeMap<Ipv4Address, ArpEntry>> = Mutex::new(BTreeMap::new());
41
42/// Monotonic tick counter for cache aging
43static ARP_TICK: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
44
45/// Get current ARP tick (incremented by timer or manually)
46fn current_tick() -> u64 {
47    ARP_TICK.load(core::sync::atomic::Ordering::Relaxed)
48}
49
50/// Advance the ARP tick counter (called periodically)
51pub fn tick() {
52    ARP_TICK.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
53}
54
55/// Maximum age of an ARP entry in ticks before it is considered stale.
56/// At ~1 tick/second this is about 5 minutes.
57const ARP_ENTRY_MAX_AGE: u64 = 300;
58
59/// Resolve an IPv4 address to a MAC address from the cache.
60///
61/// Returns `Some(MacAddress)` if the entry exists and is not stale,
62/// otherwise `None` (caller should send an ARP request).
63pub fn resolve(ip: Ipv4Address) -> Option<MacAddress> {
64    let cache = ARP_CACHE.lock();
65    if let Some(entry) = cache.get(&ip) {
66        let age = current_tick().wrapping_sub(entry.timestamp);
67        if age < ARP_ENTRY_MAX_AGE {
68            return Some(entry.mac);
69        }
70    }
71    None
72}
73
74/// Insert or update an ARP cache entry.
75pub fn update_cache(ip: Ipv4Address, mac: MacAddress) {
76    let mut cache = ARP_CACHE.lock();
77
78    // Evict oldest entry if at capacity and this is a new key
79    if cache.len() >= ARP_CACHE_MAX && !cache.contains_key(&ip) {
80        // Remove the entry with the smallest (oldest) timestamp
81        let oldest_key = cache
82            .iter()
83            .min_by_key(|(_, e)| e.timestamp)
84            .map(|(k, _)| *k);
85        if let Some(key) = oldest_key {
86            cache.remove(&key);
87        }
88    }
89
90    cache.insert(
91        ip,
92        ArpEntry {
93            mac,
94            timestamp: current_tick(),
95        },
96    );
97}
98
99/// Process an incoming ARP packet.
100///
101/// Handles both ARP requests (sends reply if the target is us) and
102/// ARP replies (updates cache).
103pub fn process_arp_packet(data: &[u8], our_mac: &MacAddress) -> Result<(), KernelError> {
104    if data.len() < ARP_PACKET_SIZE {
105        return Err(KernelError::InvalidArgument {
106            name: "arp_packet",
107            value: "too_short",
108        });
109    }
110
111    let htype = u16::from_be_bytes([data[0], data[1]]);
112    let ptype = u16::from_be_bytes([data[2], data[3]]);
113    let hlen = data[4];
114    let plen = data[5];
115    let operation = u16::from_be_bytes([data[6], data[7]]);
116
117    // Validate Ethernet/IPv4 ARP
118    if htype != ARP_HTYPE_ETHERNET || ptype != ARP_PTYPE_IPV4 || hlen != 6 || plen != 4 {
119        return Err(KernelError::InvalidArgument {
120            name: "arp_format",
121            value: "unsupported",
122        });
123    }
124
125    // Parse sender and target addresses
126    let mut sender_mac_bytes = [0u8; 6];
127    sender_mac_bytes.copy_from_slice(&data[8..14]);
128    let sender_mac = MacAddress(sender_mac_bytes);
129    let sender_ip = Ipv4Address([data[14], data[15], data[16], data[17]]);
130
131    let target_ip = Ipv4Address([data[24], data[25], data[26], data[27]]);
132
133    // Always learn the sender's mapping
134    update_cache(sender_ip, sender_mac);
135
136    match operation {
137        ARP_OP_REQUEST => {
138            // Check if the request is for our IP
139            let our_ip = get_interface_ip();
140            if target_ip == our_ip {
141                // Queue an ARP reply
142                let reply = build_arp_reply(*our_mac, our_ip, sender_mac, sender_ip);
143                send_arp_frame(&reply, *our_mac, sender_mac);
144            }
145        }
146        ARP_OP_REPLY => {
147            // Already updated cache above
148            println!(
149                "[ARP] Learned {}.{}.{}.{} -> {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
150                sender_ip.0[0],
151                sender_ip.0[1],
152                sender_ip.0[2],
153                sender_ip.0[3],
154                sender_mac.0[0],
155                sender_mac.0[1],
156                sender_mac.0[2],
157                sender_mac.0[3],
158                sender_mac.0[4],
159                sender_mac.0[5],
160            );
161        }
162        _ => {
163            // Unknown operation, ignore
164        }
165    }
166
167    Ok(())
168}
169
170/// Send an ARP request for the given target IP.
171///
172/// Broadcasts an ARP request on the local network.
173pub fn send_arp_request(target_ip: Ipv4Address) {
174    let our_mac = get_interface_mac();
175    let our_ip = get_interface_ip();
176
177    let packet = build_arp_request(our_mac, our_ip, target_ip);
178    send_arp_frame(&packet, our_mac, MacAddress::BROADCAST);
179}
180
181/// Build a raw ARP request packet (28 bytes).
182fn build_arp_request(
183    sender_mac: MacAddress,
184    sender_ip: Ipv4Address,
185    target_ip: Ipv4Address,
186) -> Vec<u8> {
187    let mut pkt = Vec::with_capacity(ARP_PACKET_SIZE);
188
189    // Hardware type: Ethernet
190    pkt.extend_from_slice(&ARP_HTYPE_ETHERNET.to_be_bytes());
191    // Protocol type: IPv4
192    pkt.extend_from_slice(&ARP_PTYPE_IPV4.to_be_bytes());
193    // Hardware address length
194    pkt.push(6);
195    // Protocol address length
196    pkt.push(4);
197    // Operation: Request
198    pkt.extend_from_slice(&ARP_OP_REQUEST.to_be_bytes());
199    // Sender hardware address
200    pkt.extend_from_slice(&sender_mac.0);
201    // Sender protocol address
202    pkt.extend_from_slice(&sender_ip.0);
203    // Target hardware address (zero for request)
204    pkt.extend_from_slice(&[0u8; 6]);
205    // Target protocol address
206    pkt.extend_from_slice(&target_ip.0);
207
208    pkt
209}
210
211/// Build a raw ARP reply packet (28 bytes).
212fn build_arp_reply(
213    sender_mac: MacAddress,
214    sender_ip: Ipv4Address,
215    target_mac: MacAddress,
216    target_ip: Ipv4Address,
217) -> Vec<u8> {
218    let mut pkt = Vec::with_capacity(ARP_PACKET_SIZE);
219
220    pkt.extend_from_slice(&ARP_HTYPE_ETHERNET.to_be_bytes());
221    pkt.extend_from_slice(&ARP_PTYPE_IPV4.to_be_bytes());
222    pkt.push(6);
223    pkt.push(4);
224    pkt.extend_from_slice(&ARP_OP_REPLY.to_be_bytes());
225    pkt.extend_from_slice(&sender_mac.0);
226    pkt.extend_from_slice(&sender_ip.0);
227    pkt.extend_from_slice(&target_mac.0);
228    pkt.extend_from_slice(&target_ip.0);
229
230    pkt
231}
232
233/// Wrap an ARP packet in an Ethernet frame and transmit it.
234fn send_arp_frame(arp_data: &[u8], src_mac: MacAddress, dst_mac: MacAddress) {
235    let frame = super::ethernet::construct_frame(
236        dst_mac,
237        src_mac,
238        super::ethernet::ETHERTYPE_ARP,
239        arp_data,
240    );
241
242    // Transmit via the first available network device
243    let _pkt = super::Packet::from_bytes(&frame);
244
245    // Try to send through eth0-style device; fall back silently if unavailable
246    super::device::with_device_mut("eth0", |dev| {
247        let _ = dev.transmit(&_pkt);
248    });
249}
250
251/// Get the currently configured interface IP address.
252///
253/// Reads from the IP interface configuration. Returns 0.0.0.0 if not
254/// configured (pre-DHCP).
255fn get_interface_ip() -> Ipv4Address {
256    super::ip::get_interface_ip()
257}
258
259/// Get the MAC address of the primary network interface.
260fn get_interface_mac() -> MacAddress {
261    super::device::with_device("eth0", |dev| dev.mac_address()).unwrap_or(MacAddress::ZERO)
262}
263
264/// Get a snapshot of the ARP cache for display purposes.
265pub fn get_cache_entries() -> Vec<(Ipv4Address, MacAddress)> {
266    let cache = ARP_CACHE.lock();
267    let now = current_tick();
268    cache
269        .iter()
270        .filter(|(_, entry)| now.wrapping_sub(entry.timestamp) < ARP_ENTRY_MAX_AGE)
271        .map(|(ip, entry)| (*ip, entry.mac))
272        .collect()
273}
274
275/// Clear all ARP cache entries.
276pub fn flush_cache() {
277    let mut cache = ARP_CACHE.lock();
278    cache.clear();
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    #[test]
286    fn test_arp_cache_insert_and_resolve() {
287        let ip = Ipv4Address::new(10, 0, 0, 1);
288        let mac = MacAddress([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
289
290        update_cache(ip, mac);
291        assert_eq!(resolve(ip), Some(mac));
292    }
293
294    #[test]
295    fn test_arp_request_build() {
296        let sender_mac = MacAddress([0x52, 0x54, 0x00, 0x12, 0x34, 0x56]);
297        let sender_ip = Ipv4Address::new(10, 0, 2, 15);
298        let target_ip = Ipv4Address::new(10, 0, 2, 1);
299
300        let pkt = build_arp_request(sender_mac, sender_ip, target_ip);
301        assert_eq!(pkt.len(), ARP_PACKET_SIZE);
302
303        // Check operation = Request (1)
304        assert_eq!(u16::from_be_bytes([pkt[6], pkt[7]]), ARP_OP_REQUEST);
305    }
306}