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

veridian_kernel/net/
dhcp.rs

1//! DHCP Client for Automatic Network Configuration
2//!
3//! Implements DHCPv4 protocol for obtaining IP addresses and network
4//! configuration.
5
6// DHCP client
7
8use alloc::vec::Vec;
9use core::convert::TryInto;
10
11use crate::{
12    error::KernelError,
13    net::{Ipv4Address, MacAddress},
14};
15
16/// DHCP message types
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[repr(u8)]
19pub enum DhcpMessageType {
20    Discover = 1,
21    Offer = 2,
22    Request = 3,
23    Decline = 4,
24    Ack = 5,
25    Nak = 6,
26    Release = 7,
27    Inform = 8,
28}
29
30/// DHCP operation codes
31const DHCP_OP_BOOTREQUEST: u8 = 1;
32#[allow(dead_code)] // DHCP protocol constant per RFC 2131
33const DHCP_OP_BOOTREPLY: u8 = 2;
34
35/// DHCP hardware types
36const DHCP_HTYPE_ETHERNET: u8 = 1;
37
38/// DHCP magic cookie
39const DHCP_MAGIC_COOKIE: u32 = 0x63825363;
40
41/// DHCP option codes
42const OPT_SUBNET_MASK: u8 = 1;
43const OPT_ROUTER: u8 = 3;
44const OPT_DNS_SERVER: u8 = 6;
45const OPT_REQUESTED_IP: u8 = 50;
46#[allow(dead_code)] // DHCP option per RFC 2132
47const OPT_LEASE_TIME: u8 = 51;
48const OPT_MESSAGE_TYPE: u8 = 53;
49const OPT_SERVER_ID: u8 = 54;
50const OPT_PARAMETER_LIST: u8 = 55;
51const OPT_END: u8 = 255;
52
53/// DHCP packet structure
54#[repr(C)]
55#[derive(Debug, Clone)]
56pub struct DhcpPacket {
57    /// Operation code (1 = request, 2 = reply)
58    pub op: u8,
59
60    /// Hardware type (1 = Ethernet)
61    pub htype: u8,
62
63    /// Hardware address length
64    pub hlen: u8,
65
66    /// Hops
67    pub hops: u8,
68
69    /// Transaction ID
70    pub xid: u32,
71
72    /// Seconds elapsed
73    pub secs: u16,
74
75    /// Flags
76    pub flags: u16,
77
78    /// Client IP address
79    pub ciaddr: Ipv4Address,
80
81    /// Your (client) IP address
82    pub yiaddr: Ipv4Address,
83
84    /// Server IP address
85    pub siaddr: Ipv4Address,
86
87    /// Gateway IP address
88    pub giaddr: Ipv4Address,
89
90    /// Client hardware address (16 bytes)
91    pub chaddr: [u8; 16],
92
93    /// Server host name (64 bytes)
94    pub sname: [u8; 64],
95
96    /// Boot file name (128 bytes)
97    pub file: [u8; 128],
98
99    /// Options (variable length)
100    pub options: Vec<u8>,
101}
102
103impl DhcpPacket {
104    /// Create a new DHCP packet
105    pub fn new(message_type: DhcpMessageType, mac_address: MacAddress, xid: u32) -> Self {
106        let mut packet = Self {
107            op: DHCP_OP_BOOTREQUEST,
108            htype: DHCP_HTYPE_ETHERNET,
109            hlen: 6,
110            hops: 0,
111            xid,
112            secs: 0,
113            flags: 0x8000, // Broadcast flag
114            ciaddr: Ipv4Address::UNSPECIFIED,
115            yiaddr: Ipv4Address::UNSPECIFIED,
116            siaddr: Ipv4Address::UNSPECIFIED,
117            giaddr: Ipv4Address::UNSPECIFIED,
118            chaddr: [0; 16],
119            sname: [0; 64],
120            file: [0; 128],
121            options: Vec::new(),
122        };
123
124        // Set client hardware address
125        packet.chaddr[0..6].copy_from_slice(&mac_address.0);
126
127        // Add magic cookie
128        packet.add_option_u32(DHCP_MAGIC_COOKIE);
129
130        // Add message type option
131        packet.add_option_u8(OPT_MESSAGE_TYPE, message_type as u8);
132
133        packet
134    }
135
136    /// Add a u8 option
137    fn add_option_u8(&mut self, code: u8, value: u8) {
138        self.options.push(code);
139        self.options.push(1); // Length
140        self.options.push(value);
141    }
142
143    /// Add a u32 option
144    fn add_option_u32(&mut self, value: u32) {
145        self.options.extend_from_slice(&value.to_be_bytes());
146    }
147
148    /// Add an IPv4 address option
149    fn add_option_ipv4(&mut self, code: u8, addr: Ipv4Address) {
150        self.options.push(code);
151        self.options.push(4); // Length
152        self.options.extend_from_slice(&addr.0);
153    }
154
155    /// Add parameter request list
156    pub fn add_parameter_request_list(&mut self) {
157        self.options.push(OPT_PARAMETER_LIST);
158        self.options.push(3); // Length
159        self.options.push(OPT_SUBNET_MASK);
160        self.options.push(OPT_ROUTER);
161        self.options.push(OPT_DNS_SERVER);
162    }
163
164    /// Finalize options
165    pub fn finalize(&mut self) {
166        self.options.push(OPT_END);
167    }
168
169    /// Serialize to bytes
170    pub fn to_bytes(&self) -> Vec<u8> {
171        let mut bytes = Vec::with_capacity(236 + self.options.len());
172
173        bytes.push(self.op);
174        bytes.push(self.htype);
175        bytes.push(self.hlen);
176        bytes.push(self.hops);
177        bytes.extend_from_slice(&self.xid.to_be_bytes());
178        bytes.extend_from_slice(&self.secs.to_be_bytes());
179        bytes.extend_from_slice(&self.flags.to_be_bytes());
180        bytes.extend_from_slice(&self.ciaddr.0);
181        bytes.extend_from_slice(&self.yiaddr.0);
182        bytes.extend_from_slice(&self.siaddr.0);
183        bytes.extend_from_slice(&self.giaddr.0);
184        bytes.extend_from_slice(&self.chaddr);
185        bytes.extend_from_slice(&self.sname);
186        bytes.extend_from_slice(&self.file);
187        bytes.extend_from_slice(&self.options);
188
189        bytes
190    }
191
192    /// Parse from bytes
193    pub fn from_bytes(bytes: &[u8]) -> Result<Self, KernelError> {
194        if bytes.len() < 236 {
195            return Err(KernelError::InvalidArgument {
196                name: "dhcp_packet_length",
197                value: "too_short",
198            });
199        }
200
201        let mut packet = Self {
202            op: bytes[0],
203            htype: bytes[1],
204            hlen: bytes[2],
205            hops: bytes[3],
206            // All try_into() calls below convert fixed-size slices to arrays.
207            // The length check above (bytes.len() >= 236) guarantees all slices
208            // are the correct size, so these conversions cannot fail.
209            xid: u32::from_be_bytes(bytes[4..8].try_into().expect("DHCP xid slice")),
210            secs: u16::from_be_bytes(bytes[8..10].try_into().expect("DHCP secs slice")),
211            flags: u16::from_be_bytes(bytes[10..12].try_into().expect("DHCP flags slice")),
212            ciaddr: Ipv4Address(bytes[12..16].try_into().expect("DHCP ciaddr slice")),
213            yiaddr: Ipv4Address(bytes[16..20].try_into().expect("DHCP yiaddr slice")),
214            siaddr: Ipv4Address(bytes[20..24].try_into().expect("DHCP siaddr slice")),
215            giaddr: Ipv4Address(bytes[24..28].try_into().expect("DHCP giaddr slice")),
216            chaddr: bytes[28..44].try_into().expect("DHCP chaddr slice"),
217            sname: bytes[44..108].try_into().expect("DHCP sname slice"),
218            file: bytes[108..236].try_into().expect("DHCP file slice"),
219            options: Vec::new(),
220        };
221
222        // Parse options
223        if bytes.len() > 236 {
224            packet.options = bytes[236..].to_vec();
225        }
226
227        Ok(packet)
228    }
229
230    /// Get message type from options
231    pub fn get_message_type(&self) -> Option<DhcpMessageType> {
232        let mut i = 4; // Skip magic cookie
233
234        while i < self.options.len() {
235            let code = self.options[i];
236            if code == OPT_END {
237                break;
238            }
239
240            if i + 1 >= self.options.len() {
241                break;
242            }
243
244            let len = self.options[i + 1] as usize;
245            if code == OPT_MESSAGE_TYPE && len == 1 && i + 2 < self.options.len() {
246                let msg_type = self.options[i + 2];
247                return match msg_type {
248                    1 => Some(DhcpMessageType::Discover),
249                    2 => Some(DhcpMessageType::Offer),
250                    3 => Some(DhcpMessageType::Request),
251                    4 => Some(DhcpMessageType::Decline),
252                    5 => Some(DhcpMessageType::Ack),
253                    6 => Some(DhcpMessageType::Nak),
254                    7 => Some(DhcpMessageType::Release),
255                    8 => Some(DhcpMessageType::Inform),
256                    _ => None,
257                };
258            }
259
260            i += 2 + len;
261        }
262
263        None
264    }
265}
266
267/// DHCP client state
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum DhcpState {
270    Init,
271    Selecting,
272    Requesting,
273    Bound,
274    Renewing,
275    Rebinding,
276}
277
278/// DHCP client configuration
279#[derive(Debug, Clone)]
280pub struct DhcpConfig {
281    pub ip_address: Ipv4Address,
282    pub subnet_mask: Ipv4Address,
283    pub router: Option<Ipv4Address>,
284    pub dns_servers: Vec<Ipv4Address>,
285    pub lease_time: u32,
286    pub server_id: Ipv4Address,
287}
288
289/// DHCP client
290pub struct DhcpClient {
291    /// MAC address
292    mac_address: MacAddress,
293
294    /// Current state
295    state: DhcpState,
296
297    /// Transaction ID
298    xid: u32,
299
300    /// Current configuration
301    #[allow(dead_code)] // Read during DHCP lease renewal (Phase 6)
302    config: Option<DhcpConfig>,
303}
304
305impl DhcpClient {
306    /// Create a new DHCP client
307    pub fn new(mac_address: MacAddress) -> Self {
308        Self {
309            mac_address,
310            state: DhcpState::Init,
311            xid: 0x12345678, // Would use random
312            config: None,
313        }
314    }
315
316    /// Create DHCP DISCOVER packet
317    pub fn create_discover(&self) -> DhcpPacket {
318        let mut packet = DhcpPacket::new(DhcpMessageType::Discover, self.mac_address, self.xid);
319        packet.add_parameter_request_list();
320        packet.finalize();
321        packet
322    }
323
324    /// Create DHCP REQUEST packet
325    pub fn create_request(&self, offered_ip: Ipv4Address, server_id: Ipv4Address) -> DhcpPacket {
326        let mut packet = DhcpPacket::new(DhcpMessageType::Request, self.mac_address, self.xid);
327        packet.add_option_ipv4(OPT_REQUESTED_IP, offered_ip);
328        packet.add_option_ipv4(OPT_SERVER_ID, server_id);
329        packet.add_parameter_request_list();
330        packet.finalize();
331        packet
332    }
333
334    /// Process DHCP OFFER -- parse options and transition to Requesting.
335    pub fn process_offer(&mut self, packet: &DhcpPacket) -> Result<(), KernelError> {
336        if self.state != DhcpState::Selecting {
337            return Err(KernelError::InvalidState {
338                expected: "Selecting",
339                actual: "Other",
340            });
341        }
342
343        let options = parse_dhcp_options(&packet.options);
344        let offered_ip = packet.yiaddr;
345        let server_id = options.server_id.unwrap_or(packet.siaddr);
346
347        println!(
348            "[DHCP] Received OFFER: {}.{}.{}.{} from server {}.{}.{}.{}",
349            offered_ip.0[0],
350            offered_ip.0[1],
351            offered_ip.0[2],
352            offered_ip.0[3],
353            server_id.0[0],
354            server_id.0[1],
355            server_id.0[2],
356            server_id.0[3],
357        );
358
359        // Send REQUEST for the offered IP
360        let request = self.create_request(offered_ip, server_id);
361        let request_bytes = request.to_bytes();
362        send_dhcp_packet(&request_bytes);
363
364        self.state = DhcpState::Requesting;
365        Ok(())
366    }
367
368    /// Process DHCP ACK -- configure network interface with obtained
369    /// parameters.
370    pub fn process_ack(&mut self, packet: &DhcpPacket) -> Result<(), KernelError> {
371        if self.state != DhcpState::Requesting {
372            return Err(KernelError::InvalidState {
373                expected: "Requesting",
374                actual: "Other",
375            });
376        }
377
378        let options = parse_dhcp_options(&packet.options);
379
380        let ip = packet.yiaddr;
381        let subnet = options
382            .subnet_mask
383            .unwrap_or(Ipv4Address::new(255, 255, 255, 0));
384        let gateway = options.router;
385        let lease = options.lease_time.unwrap_or(3600);
386
387        let config = DhcpConfig {
388            ip_address: ip,
389            subnet_mask: subnet,
390            router: gateway,
391            dns_servers: options.dns_servers,
392            lease_time: lease,
393            server_id: options.server_id.unwrap_or(packet.siaddr),
394        };
395
396        println!(
397            "[DHCP] ACK: IP {}.{}.{}.{} mask {}.{}.{}.{} lease {}s",
398            ip.0[0],
399            ip.0[1],
400            ip.0[2],
401            ip.0[3],
402            subnet.0[0],
403            subnet.0[1],
404            subnet.0[2],
405            subnet.0[3],
406            lease,
407        );
408        if let Some(gw) = gateway {
409            println!(
410                "[DHCP] Gateway: {}.{}.{}.{}",
411                gw.0[0], gw.0[1], gw.0[2], gw.0[3]
412            );
413        }
414
415        // Configure the IP layer with the obtained address
416        super::ip::set_interface_config(ip, subnet, gateway);
417
418        // Add default route via gateway
419        if let Some(gw) = gateway {
420            super::ip::add_route(super::ip::RouteEntry {
421                destination: Ipv4Address::new(0, 0, 0, 0),
422                netmask: Ipv4Address::new(0, 0, 0, 0),
423                gateway: Some(gw),
424                interface: 0,
425            });
426        }
427
428        self.config = Some(config);
429        self.state = DhcpState::Bound;
430
431        Ok(())
432    }
433
434    /// Process an incoming DHCP response packet.
435    ///
436    /// Dispatches to `process_offer` or `process_ack` based on the
437    /// message type option.
438    pub fn process_response(&mut self, data: &[u8]) -> Result<(), KernelError> {
439        let packet = DhcpPacket::from_bytes(data)?;
440
441        // Verify transaction ID matches
442        if packet.xid != self.xid {
443            return Ok(()); // Not for us
444        }
445
446        match packet.get_message_type() {
447            Some(DhcpMessageType::Offer) => self.process_offer(&packet),
448            Some(DhcpMessageType::Ack) => self.process_ack(&packet),
449            Some(DhcpMessageType::Nak) => {
450                println!("[DHCP] Received NAK, restarting negotiation");
451                self.state = DhcpState::Init;
452                Ok(())
453            }
454            _ => Ok(()),
455        }
456    }
457
458    /// Get current DHCP state
459    pub fn state(&self) -> DhcpState {
460        self.state
461    }
462
463    /// Get current configuration (if bound)
464    pub fn config(&self) -> Option<&DhcpConfig> {
465        self.config.as_ref()
466    }
467
468    /// Start DHCP negotiation -- sends DISCOVER via UDP broadcast.
469    pub fn start(&mut self) -> Result<(), KernelError> {
470        println!("[DHCP] Starting DHCP negotiation");
471
472        let discover = self.create_discover();
473        let discover_bytes = discover.to_bytes();
474
475        println!("[DHCP] Sending DISCOVER ({} bytes)", discover_bytes.len());
476        send_dhcp_packet(&discover_bytes);
477
478        self.state = DhcpState::Selecting;
479        Ok(())
480    }
481}
482
483/// Parsed DHCP options
484#[derive(Debug, Default)]
485struct ParsedDhcpOptions {
486    subnet_mask: Option<Ipv4Address>,
487    router: Option<Ipv4Address>,
488    dns_servers: Vec<Ipv4Address>,
489    lease_time: Option<u32>,
490    server_id: Option<Ipv4Address>,
491}
492
493/// Parse DHCP options from the options byte array (after magic cookie).
494fn parse_dhcp_options(options: &[u8]) -> ParsedDhcpOptions {
495    let mut result = ParsedDhcpOptions::default();
496    let mut i = 4; // Skip magic cookie (first 4 bytes)
497
498    while i < options.len() {
499        let code = options[i];
500        if code == OPT_END {
501            break;
502        }
503        if code == 0 {
504            // Padding
505            i += 1;
506            continue;
507        }
508        if i + 1 >= options.len() {
509            break;
510        }
511        let len = options[i + 1] as usize;
512        if i + 2 + len > options.len() {
513            break;
514        }
515        let data = &options[i + 2..i + 2 + len];
516
517        match code {
518            OPT_SUBNET_MASK if len == 4 => {
519                result.subnet_mask = Some(Ipv4Address([data[0], data[1], data[2], data[3]]));
520            }
521            OPT_ROUTER if len >= 4 => {
522                result.router = Some(Ipv4Address([data[0], data[1], data[2], data[3]]));
523            }
524            OPT_DNS_SERVER if len >= 4 => {
525                for chunk in data.chunks_exact(4) {
526                    result
527                        .dns_servers
528                        .push(Ipv4Address([chunk[0], chunk[1], chunk[2], chunk[3]]));
529                }
530            }
531            OPT_LEASE_TIME if len == 4 => {
532                result.lease_time = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]));
533            }
534            OPT_SERVER_ID if len == 4 => {
535                result.server_id = Some(Ipv4Address([data[0], data[1], data[2], data[3]]));
536            }
537            _ => {} // Unknown option, skip
538        }
539
540        i += 2 + len;
541    }
542
543    result
544}
545
546/// Send a DHCP packet via UDP broadcast (0.0.0.0:68 -> 255.255.255.255:67).
547fn send_dhcp_packet(data: &[u8]) {
548    let src = super::SocketAddr::v4(Ipv4Address::ANY, 68);
549    let dst = super::SocketAddr::v4(Ipv4Address::BROADCAST, 67);
550    let _ = super::udp::send_packet(src, dst, data);
551}
552
553/// Global DHCP client instance
554static DHCP_CLIENT: spin::Mutex<Option<DhcpClient>> = spin::Mutex::new(None);
555
556/// Start DHCP on the primary interface.
557pub fn start_dhcp() -> Result<(), KernelError> {
558    let mac =
559        super::device::with_device("eth0", |dev| dev.mac_address()).unwrap_or(MacAddress::ZERO);
560
561    let mut lock = DHCP_CLIENT.lock();
562    let client = lock.get_or_insert_with(|| DhcpClient::new(mac));
563    client.start()
564}
565
566/// Get current DHCP state for display.
567pub fn get_dhcp_state() -> Option<DhcpState> {
568    let lock = DHCP_CLIENT.lock();
569    lock.as_ref().map(|c| c.state())
570}
571
572/// Initialize DHCP client
573pub fn init() -> Result<(), KernelError> {
574    println!("[DHCP] DHCP client initialized");
575    Ok(())
576}
577
578#[cfg(test)]
579mod tests {
580    use super::*;
581
582    #[test]
583    fn test_dhcp_packet_creation() {
584        let mac = MacAddress([0x52, 0x54, 0x00, 0x12, 0x34, 0x56]);
585        let packet = DhcpPacket::new(DhcpMessageType::Discover, mac, 0x12345678);
586
587        assert_eq!(packet.op, DHCP_OP_BOOTREQUEST);
588        assert_eq!(packet.htype, DHCP_HTYPE_ETHERNET);
589        assert_eq!(packet.hlen, 6);
590    }
591
592    #[test]
593    fn test_dhcp_serialization() {
594        let mac = MacAddress([0x52, 0x54, 0x00, 0x12, 0x34, 0x56]);
595        let mut packet = DhcpPacket::new(DhcpMessageType::Discover, mac, 0x12345678);
596        packet.finalize();
597
598        let bytes = packet.to_bytes();
599        assert!(bytes.len() >= 236);
600    }
601}