1use crate::kernel::usb;
2use alloc::string::String;
3
4const DHCP_SERVER_PORT: u16 = 67;
6const DHCP_CLIENT_PORT: u16 = 68;
7const DHCP_MAGIC_COOKIE: [u8; 4] = [99, 130, 83, 99];
8const DHCP_MAX_ATTEMPTS: usize = 5;
9const DHCP_POLL_PER_ATTEMPT: usize = 2048;
10const DHCP_POLL_SPIN_LOOPS: usize = 20_000;
11const DHCP_SERVICE_TICKS_PER_SEC: u64 = 100;
12const DHCP_AUTO_ATTEMPT_INTERVAL_TICKS: u64 = 500;
13const DHCP_ASYNC_TIMEOUT_TICKS: u64 = 100;
14const DHCP_RENEW_MIN_RETRANSMIT_TICKS: u64 = 60 * DHCP_SERVICE_TICKS_PER_SEC;
15const DHCP_ASYNC_IDLE: u8 = 0;
16const DHCP_ASYNC_WAIT_OFFER: u8 = 1;
17const DHCP_ASYNC_WAIT_ACK: u8 = 2;
18const DHCP_RENEW_STAGE_BOUND: u8 = 0;
19const DHCP_RENEW_STAGE_RENEWING: u8 = 1;
20const DHCP_RENEW_STAGE_REBINDING: u8 = 2;
21
22pub fn set_dhcp_auto_pending(auto: bool) {
23 unsafe {
24 super::DHCP_AUTO_PENDING = auto;
25 }
26}
27
28pub fn dhcp_status_line() -> String {
29 let _lock = super::NET_LOCK.lock();
30 let st = super::status();
31 let async_state = unsafe { super::DHCP_ASYNC_STATE };
32 if async_state == DHCP_ASYNC_WAIT_OFFER {
33 return String::from(if st.dhcp_active {
34 "dhcp=renewing(wait-offer)"
35 } else {
36 "dhcp=discovering"
37 });
38 }
39 if async_state == DHCP_ASYNC_WAIT_ACK {
40 return String::from(if st.dhcp_active {
41 "dhcp=renewing(wait-ack)"
42 } else {
43 "dhcp=requesting"
44 });
45 }
46 if st.dhcp_active {
47 alloc::format!(
48 "dhcp=bound ip={} mask={} gw={} dns={} server={} lease={}s t1={}s t2={}s",
49 super::format_ipv4(st.ip),
50 super::format_ipv4(st.dhcp_subnet),
51 super::format_ipv4(st.gateway),
52 super::format_ipv4(st.dns),
53 super::format_ipv4(st.dhcp_server),
54 st.dhcp_lease_secs,
55 st.dhcp_t1_secs,
56 st.dhcp_t2_secs
57 )
58 } else {
59 String::from("dhcp=inactive")
60 }
61}
62
63pub fn dhcp_release() {
64 let _lock = super::NET_LOCK.lock();
65 unsafe {
66 super::NET_STATE.dhcp_active = false;
67 super::NET_STATE.dhcp_server = [0; 4];
68 super::NET_STATE.dhcp_lease_secs = 0;
69 super::NET_STATE.dhcp_subnet = [0; 4];
70 super::NET_STATE.dhcp_t1_secs = 0;
71 super::NET_STATE.dhcp_t2_secs = 0;
72 super::DHCP_LEASE_START_TICK = 0;
73 super::DHCP_RENEW_STAGE = 0;
74 super::DHCP_NEXT_RENEW_TICK = 0;
75 super::DHCP_ASYNC_STATE = DHCP_ASYNC_IDLE;
76 super::DHCP_ASYNC_XID = 0;
77 super::DHCP_ASYNC_ATTEMPT = 0;
78 super::DHCP_ASYNC_DEADLINE_TICK = 0;
79 super::DHCP_ASYNC_RENEWING = false;
80 super::DHCP_ASYNC_REBINDING = false;
81 }
82}
83
84pub fn dhcp_tick() {
85 unsafe {
86 super::DHCP_SERVICE_TICK = crate::kernel::timer::get_ticks() as u64;
87
88 let st = super::NET_STATE;
89 if !st.usb_eth_present || !st.link_up {
90 return;
91 }
92
93 if super::DHCP_AUTO_PENDING
94 && !st.dhcp_active
95 && super::DHCP_ASYNC_STATE == DHCP_ASYNC_IDLE
96 && (super::DHCP_LAST_AUTO_ATTEMPT_TICK == 0
97 || super::DHCP_SERVICE_TICK.wrapping_sub(super::DHCP_LAST_AUTO_ATTEMPT_TICK)
98 >= DHCP_AUTO_ATTEMPT_INTERVAL_TICKS)
99 {
100 super::DHCP_LAST_AUTO_ATTEMPT_TICK = super::DHCP_SERVICE_TICK;
101 if let Err(e) = dhcp_begin_async(false) {
104 crate::warn!("[NET] DHCP auto discover could not start: {}", e);
105 }
106 }
107
108 if super::DHCP_ASYNC_STATE != DHCP_ASYNC_IDLE && !super::DHCP_ASYNC_RENEWING {
109 dhcp_poll_async();
110 return;
111 }
112 if super::DHCP_ASYNC_RENEWING {
113 dhcp_poll_renew_ack();
114 }
115
116 if !st.dhcp_active {
117 return;
118 }
119
120 if super::DHCP_LEASE_START_TICK == 0 {
121 super::DHCP_LEASE_START_TICK = super::DHCP_SERVICE_TICK;
122 }
123
124 let elapsed = super::DHCP_SERVICE_TICK.wrapping_sub(super::DHCP_LEASE_START_TICK);
125 let t1_ticks = (st.dhcp_t1_secs as u64).saturating_mul(DHCP_SERVICE_TICKS_PER_SEC);
126 let t2_ticks = (st.dhcp_t2_secs as u64).saturating_mul(DHCP_SERVICE_TICKS_PER_SEC);
127 let expiry_ticks = (st.dhcp_lease_secs as u64).saturating_mul(DHCP_SERVICE_TICKS_PER_SEC);
128
129 if t1_ticks == 0 {
130 return;
131 }
132
133 if expiry_ticks > 0 && elapsed >= expiry_ticks {
134 crate::warn!("[NET] DHCP lease expired; returning to INIT (will re-DISCOVER)");
135 let _lock = super::NET_LOCK.lock();
136 dhcp_async_clear();
137 super::NET_STATE.dhcp_active = false;
138 super::NET_STATE.ip = [0; 4];
139 super::DHCP_RENEW_STAGE = DHCP_RENEW_STAGE_BOUND;
140 super::DHCP_LEASE_START_TICK = 0;
141 super::DHCP_AUTO_PENDING = true;
142 return;
143 }
144
145 let want_stage = if t2_ticks > 0 && elapsed >= t2_ticks {
146 DHCP_RENEW_STAGE_REBINDING
147 } else if elapsed >= t1_ticks {
148 DHCP_RENEW_STAGE_RENEWING
149 } else {
150 DHCP_RENEW_STAGE_BOUND
151 };
152
153 if want_stage == DHCP_RENEW_STAGE_BOUND {
154 return;
155 }
156
157 let stage_changed = want_stage != super::DHCP_RENEW_STAGE;
158 let retransmit_due = super::DHCP_SERVICE_TICK >= super::DHCP_NEXT_RENEW_TICK;
159 if stage_changed || retransmit_due {
160 super::DHCP_RENEW_STAGE = want_stage;
161 let rebinding = want_stage == DHCP_RENEW_STAGE_REBINDING;
162
163 let next_milestone = if rebinding { expiry_ticks } else { t2_ticks };
164 let remaining = next_milestone.saturating_sub(elapsed);
165 let interval = (remaining / 2).max(DHCP_RENEW_MIN_RETRANSMIT_TICKS);
166 super::DHCP_NEXT_RENEW_TICK = super::DHCP_SERVICE_TICK.wrapping_add(interval);
167
168 if let Err(e) = dhcp_begin_renew(rebinding) {
169 crate::warn!(
170 "[NET] DHCP {} could not start: {}",
171 if rebinding { "rebind" } else { "renew" },
172 e
173 );
174 }
175 }
176 }
177}
178
179pub fn dhcp_acquire() -> Result<super::DhcpLease, &'static str> {
180 unsafe {
189 let _lock = super::NET_LOCK.lock();
190 if super::DHCP_ASYNC_STATE != DHCP_ASYNC_IDLE {
191 return Err("dhcp already in progress");
192 }
193 }
194
195 let mut last_err: &'static str = "dhcp response timeout";
196
197 for attempt in 0..DHCP_MAX_ATTEMPTS {
198 let xid = unsafe {
199 let x = super::DHCP_XID;
200 super::DHCP_XID = super::DHCP_XID.wrapping_add(1);
201 x
202 };
203
204 match dhcp_acquire_once(xid) {
205 Ok(lease) => return Ok(lease),
206 Err("dhcp nak") => {
207 dhcp_release();
208 return Err("dhcp nak");
209 }
210 Err(e) => {
211 last_err = e;
212 dhcp_backoff(attempt);
213 }
214 }
215 }
216
217 dhcp_release();
218 Err(last_err)
219}
220
221pub fn dhcp_start_async(renewing: bool) -> Result<(), &'static str> {
222 let _lock = super::NET_LOCK.lock();
223 unsafe {
224 if renewing && super::NET_STATE.dhcp_active && super::NET_STATE.ip != [0; 4] {
225 super::DHCP_RENEW_STAGE = DHCP_RENEW_STAGE_RENEWING;
226 super::DHCP_NEXT_RENEW_TICK = super::DHCP_SERVICE_TICK;
227 return dhcp_begin_renew(false);
228 }
229 }
230 dhcp_begin_async(false)
231}
232
233fn dhcp_acquire_once(xid: u32) -> Result<super::DhcpLease, &'static str> {
234 let st = super::status();
235
236 let mut discover = [0u8; 320];
237 let discover_len = build_dhcp_discover_payload(
238 xid,
239 st.mac,
240 DHCP_CLIENT_PORT,
241 DHCP_SERVER_PORT,
242 &mut discover,
243 )?;
244 if discover_len == 0 {
245 return Err("dhcp discover build failed");
246 }
247
248 let _ = super::udp::send_udp_ipv4_from(
249 [0, 0, 0, 0],
250 [255, 255, 255, 255],
251 DHCP_CLIENT_PORT,
252 DHCP_SERVER_PORT,
253 &discover[..discover_len],
254 )?;
255
256 let offer_info = dhcp_recv_message(xid, 2, DHCP_POLL_PER_ATTEMPT, "dhcp offer timeout")?;
257
258 let mut request = [0u8; 320];
259 let request_len = build_dhcp_request_payload(
260 xid,
261 st.mac,
262 DHCP_CLIENT_PORT,
263 DHCP_SERVER_PORT,
264 offer_info.yiaddr,
265 offer_info.server,
266 &mut request,
267 )?;
268 if request_len == 0 {
269 return Err("dhcp request build failed");
270 }
271
272 let _ = super::udp::send_udp_ipv4_from(
273 [0, 0, 0, 0],
274 [255, 255, 255, 255],
275 DHCP_CLIENT_PORT,
276 DHCP_SERVER_PORT,
277 &request[..request_len],
278 )?;
279
280 let info = dhcp_recv_message(xid, 5, DHCP_POLL_PER_ATTEMPT, "dhcp ack timeout")?;
281
282 unsafe {
283 super::NET_STATE.ip = info.yiaddr;
284 super::NET_STATE.gateway = info.router;
285 super::NET_STATE.dns = info.dns;
286 super::NET_STATE.dns2 = info.dns2;
287 super::NET_STATE.dhcp_active = true;
288 super::NET_STATE.dhcp_server = info.server;
289 super::NET_STATE.dhcp_lease_secs = info.lease_secs;
290 super::NET_STATE.dhcp_subnet = info.subnet;
291 super::NET_STATE.dhcp_t1_secs = info.t1_secs;
292 super::NET_STATE.dhcp_t2_secs = info.t2_secs;
293 super::NET_STATE.link_up = true;
294 super::DHCP_LEASE_START_TICK = super::DHCP_SERVICE_TICK;
295 super::DHCP_RENEW_STAGE = 0;
296 }
297
298 Ok(super::DhcpLease {
299 ip: info.yiaddr,
300 gateway: info.router,
301 dns: info.dns,
302 dns2: info.dns2,
303 server: info.server,
304 subnet: info.subnet,
305 lease_secs: info.lease_secs,
306 t1_secs: info.t1_secs,
307 t2_secs: info.t2_secs,
308 })
309}
310
311fn dhcp_backoff(attempt: usize) {
312 let spins = (attempt + 1) * 50_000;
313 for _ in 0..spins {
314 core::hint::spin_loop();
315 }
316}
317
318fn dhcp_begin_async(_renewing: bool) -> Result<(), &'static str> {
319 unsafe {
320 if super::DHCP_ASYNC_STATE != DHCP_ASYNC_IDLE {
321 return Err("dhcp already in progress");
322 }
323
324 let xid = super::DHCP_XID;
325 super::DHCP_XID = super::DHCP_XID.wrapping_add(1);
326 crate::info!("[NET] Sending DHCP Discover (xid={:#010X})...", xid);
327 dhcp_send_discover(xid)?;
328 super::DHCP_ASYNC_STATE = DHCP_ASYNC_WAIT_OFFER;
329 super::DHCP_ASYNC_XID = xid;
330 super::DHCP_ASYNC_ATTEMPT = 1;
331 super::DHCP_ASYNC_DEADLINE_TICK =
332 super::DHCP_SERVICE_TICK.wrapping_add(DHCP_ASYNC_TIMEOUT_TICKS);
333 super::DHCP_ASYNC_RENEWING = false;
334 super::DHCP_ASYNC_REBINDING = false;
335 }
336 Ok(())
337}
338
339fn dhcp_begin_renew(rebinding: bool) -> Result<(), &'static str> {
340 unsafe {
341 let ciaddr = super::NET_STATE.ip;
342 let server = super::NET_STATE.dhcp_server;
343 if ciaddr == [0; 4] {
344 return Err("dhcp renew: no bound ip");
345 }
346 if !rebinding && server == [0; 4] {
347 return Err("dhcp renew: no server id");
348 }
349
350 let xid = super::DHCP_XID;
351 super::DHCP_XID = super::DHCP_XID.wrapping_add(1);
352 dhcp_send_renew(xid, ciaddr, server, rebinding)?;
353 crate::info!(
354 "NET: DHCP {} REQUEST (ciaddr={}, {} xid={:#010X})",
355 if rebinding { "REBINDING" } else { "RENEWING" },
356 super::format_ipv4(ciaddr),
357 if rebinding {
358 String::from("broadcast")
359 } else {
360 alloc::format!("unicast->{}", super::format_ipv4(server))
361 },
362 xid
363 );
364 super::DHCP_ASYNC_STATE = DHCP_ASYNC_WAIT_ACK;
365 super::DHCP_ASYNC_XID = xid;
366 super::DHCP_ASYNC_ATTEMPT = 1;
367 super::DHCP_ASYNC_RENEWING = true;
368 super::DHCP_ASYNC_REBINDING = rebinding;
369 super::DHCP_ASYNC_DEADLINE_TICK = 0;
370 }
371 Ok(())
372}
373
374fn dhcp_poll_renew_ack() {
375 unsafe {
376 usb::poll_ethernet_data_plane_only();
377
378 while let Some(dgram) = super::udp_recv_next_datagram(DHCP_CLIENT_PORT) {
379 if dgram.src_port != DHCP_SERVER_PORT {
380 continue;
381 }
382 let mut info = match parse_dhcp_ack_payload(&dgram.payload, super::DHCP_ASYNC_XID) {
383 Ok(v) => v,
384 Err(_) => continue,
385 };
386 if info.server == [0; 4] {
387 info.server = dgram.src_ip;
388 }
389
390 if info.msg_type == 6 {
391 crate::warn!("[NET] DHCP renew NAK; returning to INIT");
392 dhcp_async_clear();
393 super::NET_STATE.dhcp_active = false;
394 super::NET_STATE.ip = [0; 4];
395 super::DHCP_RENEW_STAGE = DHCP_RENEW_STAGE_BOUND;
396 super::DHCP_LEASE_START_TICK = 0;
397 super::DHCP_AUTO_PENDING = true;
398 return;
399 }
400
401 if info.msg_type == 5 {
402 let lease = dhcp_apply_lease(&info);
403 dhcp_async_clear();
404 crate::info!(
405 "NET: DHCP renewed ip={} lease={}s t1={}s t2={}s",
406 super::format_ipv4(lease.ip),
407 lease.lease_secs,
408 lease.t1_secs,
409 lease.t2_secs
410 );
411 return;
412 }
413 }
414 }
415}
416
417fn dhcp_poll_async() {
418 unsafe {
419 usb::poll_ethernet_data_plane_only();
420
421 while let Some(dgram) = super::udp_recv_next_datagram(DHCP_CLIENT_PORT) {
422 if dgram.src_port != DHCP_SERVER_PORT {
423 continue;
424 }
425
426 let mut info = match parse_dhcp_ack_payload(&dgram.payload, super::DHCP_ASYNC_XID) {
427 Ok(v) => v,
428 Err(_) => continue,
429 };
430
431 if info.server == [0; 4] {
432 info.server = dgram.src_ip;
433 }
434
435 if info.msg_type == 6 {
436 crate::debug!(
437 "NET: Received DHCP NAK from {}",
438 super::format_ipv4(info.server)
439 );
440 dhcp_async_fail("dhcp nak");
441 return;
442 }
443
444 if super::DHCP_ASYNC_STATE == DHCP_ASYNC_WAIT_OFFER && info.msg_type == 2 {
445 crate::debug!(
446 "NET: Received DHCP Offer (yiaddr={}) from {}, sending Request...",
447 super::format_ipv4(info.yiaddr),
448 super::format_ipv4(info.server)
449 );
450 if dhcp_send_request(super::DHCP_ASYNC_XID, info.yiaddr, info.server).is_err() {
451 dhcp_async_fail("dhcp request send failed");
452 return;
453 }
454 super::DHCP_ASYNC_STATE = DHCP_ASYNC_WAIT_ACK;
455 super::DHCP_ASYNC_DEADLINE_TICK =
456 super::DHCP_SERVICE_TICK.wrapping_add(DHCP_ASYNC_TIMEOUT_TICKS);
457 return;
458 }
459
460 if super::DHCP_ASYNC_STATE == DHCP_ASYNC_WAIT_ACK && info.msg_type == 5 {
461 let lease = dhcp_apply_lease(&info);
462 let was_renewing = super::DHCP_ASYNC_RENEWING;
463 dhcp_async_clear();
464 crate::info!(
465 "NET: DHCP {} ip={} gw={} dns={}",
466 if was_renewing { "renewed" } else { "bound" },
467 super::format_ipv4(lease.ip),
468 super::format_ipv4(lease.gateway),
469 super::format_ipv4(lease.dns)
470 );
471 return;
472 }
473
474 crate::info!(
475 "NET: Ignored DHCP message type {} (state={})",
476 info.msg_type,
477 super::DHCP_ASYNC_STATE
478 );
479 }
480
481 if super::DHCP_ASYNC_DEADLINE_TICK.wrapping_sub(super::DHCP_SERVICE_TICK) < u64::MAX / 2 {
482 return;
483 }
484
485 if super::DHCP_ASYNC_ATTEMPT >= DHCP_MAX_ATTEMPTS {
486 dhcp_async_fail(if super::DHCP_ASYNC_STATE == DHCP_ASYNC_WAIT_ACK {
487 "dhcp ack timeout"
488 } else {
489 "dhcp offer timeout"
490 });
491 return;
492 }
493
494 super::DHCP_ASYNC_ATTEMPT += 1;
495 let new_xid = super::DHCP_XID;
496 super::DHCP_XID = super::DHCP_XID.wrapping_add(1);
497 super::DHCP_ASYNC_XID = new_xid;
498
499 crate::debug!(
500 "NET: DHCP timeout. Retrying (attempt {}/{} with xid={:#010X})...",
501 super::DHCP_ASYNC_ATTEMPT,
502 DHCP_MAX_ATTEMPTS,
503 new_xid
504 );
505 if dhcp_send_discover(new_xid).is_err() {
506 dhcp_async_fail("dhcp discover send failed");
507 return;
508 }
509 super::DHCP_ASYNC_STATE = DHCP_ASYNC_WAIT_OFFER;
510 super::DHCP_ASYNC_DEADLINE_TICK =
511 super::DHCP_SERVICE_TICK.wrapping_add(DHCP_ASYNC_TIMEOUT_TICKS);
512 }
513}
514
515fn dhcp_async_fail(err: &'static str) {
516 unsafe {
517 let renewing = super::DHCP_ASYNC_RENEWING;
518 dhcp_async_clear();
519 if !renewing {
520 super::NET_STATE.dhcp_active = false;
521 super::NET_STATE.dhcp_server = [0; 4];
522 super::NET_STATE.dhcp_lease_secs = 0;
523 super::NET_STATE.dhcp_subnet = [0; 4];
524 super::NET_STATE.dhcp_t1_secs = 0;
525 super::NET_STATE.dhcp_t2_secs = 0;
526 super::DHCP_AUTO_PENDING = true;
527 }
528 crate::warn!(
529 "NET: DHCP {} failed: {}",
530 if renewing { "renew" } else { "auto-start" },
531 err
532 );
533 }
534}
535
536fn dhcp_async_clear() {
537 unsafe {
538 super::DHCP_ASYNC_STATE = DHCP_ASYNC_IDLE;
539 super::DHCP_ASYNC_XID = 0;
540 super::DHCP_ASYNC_ATTEMPT = 0;
541 super::DHCP_ASYNC_DEADLINE_TICK = 0;
542 super::DHCP_ASYNC_RENEWING = false;
543 super::DHCP_ASYNC_REBINDING = false;
544 }
545}
546
547fn dhcp_send_renew(
548 xid: u32,
549 ciaddr: [u8; 4],
550 server: [u8; 4],
551 rebinding: bool,
552) -> Result<(), &'static str> {
553 let st = super::status();
554 let mut request = [0u8; 320];
555 let request_len = build_dhcp_renew_payload(xid, st.mac, ciaddr, &mut request)?;
556 if request_len == 0 {
557 return Err("dhcp renew build failed");
558 }
559
560 let dst_ip = if rebinding {
561 [255, 255, 255, 255]
562 } else {
563 server
564 };
565 super::udp::send_udp_ipv4_from(
566 ciaddr,
567 dst_ip,
568 DHCP_CLIENT_PORT,
569 DHCP_SERVER_PORT,
570 &request[..request_len],
571 )?;
572 Ok(())
573}
574
575fn dhcp_send_discover(xid: u32) -> Result<(), &'static str> {
576 let st = super::status();
577 let mut discover = [0u8; 320];
578 let discover_len = build_dhcp_discover_payload(
579 xid,
580 st.mac,
581 DHCP_CLIENT_PORT,
582 DHCP_SERVER_PORT,
583 &mut discover,
584 )?;
585 if discover_len == 0 {
586 return Err("dhcp discover build failed");
587 }
588
589 super::udp::send_udp_ipv4_from(
590 [0, 0, 0, 0],
591 [255, 255, 255, 255],
592 DHCP_CLIENT_PORT,
593 DHCP_SERVER_PORT,
594 &discover[..discover_len],
595 )?;
596 Ok(())
597}
598
599fn dhcp_send_request(xid: u32, req_ip: [u8; 4], server: [u8; 4]) -> Result<(), &'static str> {
600 let st = super::status();
601 let mut request = [0u8; 320];
602 let request_len = build_dhcp_request_payload(
603 xid,
604 st.mac,
605 DHCP_CLIENT_PORT,
606 DHCP_SERVER_PORT,
607 req_ip,
608 server,
609 &mut request,
610 )?;
611 if request_len == 0 {
612 return Err("dhcp request build failed");
613 }
614
615 super::udp::send_udp_ipv4_from(
616 [0, 0, 0, 0],
617 [255, 255, 255, 255],
618 DHCP_CLIENT_PORT,
619 DHCP_SERVER_PORT,
620 &request[..request_len],
621 )?;
622 Ok(())
623}
624
625fn dhcp_apply_lease(info: &super::DhcpAckInfo) -> super::DhcpLease {
626 let _lock = super::NET_LOCK.lock();
627 unsafe {
628 super::NET_STATE.ip = info.yiaddr;
629 super::NET_STATE.gateway = info.router;
630 super::NET_STATE.dns = info.dns;
631 super::NET_STATE.dns2 = info.dns2;
632 super::NET_STATE.dhcp_active = true;
633 super::NET_STATE.dhcp_server = info.server;
634 super::NET_STATE.dhcp_lease_secs = info.lease_secs;
635 super::NET_STATE.dhcp_subnet = info.subnet;
636 super::NET_STATE.dhcp_t1_secs = info.t1_secs;
637 super::NET_STATE.dhcp_t2_secs = info.t2_secs;
638 super::NET_STATE.link_up = true;
639 super::DHCP_LEASE_START_TICK = super::DHCP_SERVICE_TICK;
640 super::DHCP_RENEW_STAGE = 0;
641 }
642
643 super::DhcpLease {
644 ip: info.yiaddr,
645 gateway: info.router,
646 dns: info.dns,
647 dns2: info.dns2,
648 server: info.server,
649 subnet: info.subnet,
650 lease_secs: info.lease_secs,
651 t1_secs: info.t1_secs,
652 t2_secs: info.t2_secs,
653 }
654}
655
656fn dhcp_recv_message(
657 xid: u32,
658 expect_type: u8,
659 max_poll: usize,
660 timeout_err: &'static str,
661) -> Result<super::DhcpAckInfo, &'static str> {
662 for _ in 0..max_poll {
663 usb::poll_ethernet_data_plane_only();
664
665 let dgram = match super::udp_recv_next_datagram(DHCP_CLIENT_PORT) {
666 Some(v) => v,
667 None => {
668 for _ in 0..DHCP_POLL_SPIN_LOOPS {
669 core::hint::spin_loop();
670 }
671 continue;
672 }
673 };
674
675 if dgram.src_port != DHCP_SERVER_PORT {
676 continue;
677 }
678
679 if let Ok(mut info) = parse_dhcp_ack_payload(&dgram.payload, xid) {
680 if info.server == [0; 4] {
681 info.server = dgram.src_ip;
682 }
683 if info.msg_type == 6 {
684 return Err("dhcp nak");
685 }
686 if info.msg_type == expect_type {
687 return Ok(info);
688 }
689 }
690 }
691
692 Err(timeout_err)
693}
694
695fn build_dhcp_discover_payload(
696 xid: u32,
697 mac: [u8; 6],
698 src_port: u16,
699 dst_port: u16,
700 out: &mut [u8],
701) -> Result<usize, &'static str> {
702 if src_port != DHCP_CLIENT_PORT || dst_port != DHCP_SERVER_PORT {
703 return Err("invalid dhcp discover ports");
704 }
705 build_dhcp_payload(1, xid, mac, [0; 4], [0; 4], [0; 4], 0, out)
706}
707
708fn build_dhcp_request_payload(
709 xid: u32,
710 mac: [u8; 6],
711 src_port: u16,
712 dst_port: u16,
713 req_ip: [u8; 4],
714 server: [u8; 4],
715 out: &mut [u8],
716) -> Result<usize, &'static str> {
717 if src_port != DHCP_CLIENT_PORT || dst_port != DHCP_SERVER_PORT {
718 return Err("invalid dhcp request ports");
719 }
720 build_dhcp_payload(3, xid, mac, [0; 4], req_ip, server, 0, out)
721}
722
723fn build_dhcp_renew_payload(
724 xid: u32,
725 mac: [u8; 6],
726 ciaddr: [u8; 4],
727 out: &mut [u8],
728) -> Result<usize, &'static str> {
729 if out.len() < 300 {
730 return Err("dhcp buffer too small");
731 }
732 for b in out.iter_mut() {
733 *b = 0;
734 }
735
736 out[0] = 1;
737 out[1] = 1;
738 out[2] = 6;
739 out[4] = (xid >> 24) as u8;
740 out[5] = (xid >> 16) as u8;
741 out[6] = (xid >> 8) as u8;
742 out[7] = xid as u8;
743 out[12..16].copy_from_slice(&ciaddr);
744 out[28..34].copy_from_slice(&mac);
745 out[236..240].copy_from_slice(&DHCP_MAGIC_COOKIE);
746
747 let mut idx = 240usize;
748 idx = dhcp_option_u8(out, idx, 53, 3)?;
749 idx = dhcp_option_list(out, idx, 55, &[1, 3, 6, 51, 58, 59])?;
750
751 if idx >= out.len() {
752 return Err("dhcp option overflow");
753 }
754 out[idx] = 255;
755 let payload_len = (idx + 1).max(300);
756 Ok(payload_len)
757}
758
759pub(super) fn build_dhcp_ack_payload_pub(
761 xid: u32,
762 mac: [u8; 6],
763 yiaddr: [u8; 4],
764 server: [u8; 4],
765 dns: [u8; 4],
766 lease_secs: u32,
767 out: &mut [u8],
768) -> Result<usize, &'static str> {
769 build_dhcp_ack_payload(xid, mac, yiaddr, server, dns, lease_secs, out)
770}
771
772pub(super) fn build_dhcp_discover_payload_pub(
773 xid: u32,
774 mac: [u8; 6],
775 src_port: u16,
776 dst_port: u16,
777 out: &mut [u8],
778) -> Result<usize, &'static str> {
779 build_dhcp_discover_payload(xid, mac, src_port, dst_port, out)
780}
781
782pub(super) fn parse_dhcp_ack_payload_pub(
783 payload: &[u8],
784 expect_xid: u32,
785) -> Result<super::DhcpAckInfo, &'static str> {
786 parse_dhcp_ack_payload(payload, expect_xid)
787}
788
789fn build_dhcp_offer_payload(
790 xid: u32,
791 mac: [u8; 6],
792 yiaddr: [u8; 4],
793 server: [u8; 4],
794 dns: [u8; 4],
795 lease_secs: u32,
796 out: &mut [u8],
797) -> Result<usize, &'static str> {
798 build_dhcp_payload(2, xid, mac, yiaddr, yiaddr, server, lease_secs.max(60), out)
799 .map(|len| write_dhcp_dns_option(out, len, dns).unwrap_or(len))
800}
801
802fn build_dhcp_ack_payload(
803 xid: u32,
804 mac: [u8; 6],
805 yiaddr: [u8; 4],
806 server: [u8; 4],
807 dns: [u8; 4],
808 lease_secs: u32,
809 out: &mut [u8],
810) -> Result<usize, &'static str> {
811 build_dhcp_payload(5, xid, mac, yiaddr, yiaddr, server, lease_secs.max(60), out)
812 .map(|len| write_dhcp_dns_option(out, len, dns).unwrap_or(len))
813}
814
815fn build_dhcp_payload(
816 msg_type: u8,
817 xid: u32,
818 mac: [u8; 6],
819 yiaddr: [u8; 4],
820 req_ip: [u8; 4],
821 server: [u8; 4],
822 lease_secs: u32,
823 out: &mut [u8],
824) -> Result<usize, &'static str> {
825 if out.len() < 300 {
826 return Err("dhcp buffer too small");
827 }
828
829 for b in out.iter_mut() {
830 *b = 0;
831 }
832
833 out[0] = if msg_type == 2 || msg_type == 5 { 2 } else { 1 };
834 out[1] = 1;
835 out[2] = 6;
836 out[3] = 0;
837 out[4] = (xid >> 24) as u8;
838 out[5] = (xid >> 16) as u8;
839 out[6] = (xid >> 8) as u8;
840 out[7] = xid as u8;
841 out[10] = 0x80;
842 out[11] = 0x00;
843 out[16..20].copy_from_slice(&yiaddr);
844 out[28..34].copy_from_slice(&mac);
845 out[236..240].copy_from_slice(&DHCP_MAGIC_COOKIE);
846
847 let mut idx = 240usize;
848 idx = dhcp_option_u8(out, idx, 53, msg_type)?;
849
850 if server != [0; 4] {
851 idx = dhcp_option_ipv4(out, idx, 54, server)?;
852 }
853
854 if req_ip != [0; 4] {
855 idx = dhcp_option_ipv4(out, idx, 50, req_ip)?;
856 }
857
858 if lease_secs > 0 {
859 idx = dhcp_option_u32(out, idx, 51, lease_secs)?;
860 idx = dhcp_option_ipv4(out, idx, 3, server)?;
861 }
862
863 if msg_type == 1 || msg_type == 3 {
864 idx = dhcp_option_list(out, idx, 55, &[1, 3, 6, 51, 54])?;
865 }
866
867 if idx >= out.len() {
868 return Err("dhcp option overflow");
869 }
870 out[idx] = 255;
871 let payload_len = (idx + 1).max(300);
872 Ok(payload_len)
873}
874
875fn write_dhcp_dns_option(out: &mut [u8], len: usize, dns: [u8; 4]) -> Option<usize> {
876 if len == 0 || len > out.len() || out[len - 1] != 255 {
877 return None;
878 }
879 if len + 6 > out.len() {
880 return None;
881 }
882
883 let idx = len - 1;
884 out[idx] = 6;
885 out[idx + 1] = 4;
886 out[idx + 2..idx + 6].copy_from_slice(&dns);
887 out[idx + 6] = 255;
888 Some(idx + 7)
889}
890
891fn parse_dhcp_ack_payload(
892 payload: &[u8],
893 expect_xid: u32,
894) -> Result<super::DhcpAckInfo, &'static str> {
895 if payload.len() < 241 {
896 crate::info!("[NET] DHCP Parse Error: Payload too short ({})", payload.len());
897 return Err("dhcp payload too short");
898 }
899 if payload[0] != 2 || payload[1] != 1 || payload[2] != 6 {
900 crate::info!(
901 "DHCP Parse Error: Invalid bootp header ({}, {}, {})",
902 payload[0],
903 payload[1],
904 payload[2]
905 );
906 return Err("invalid dhcp bootp header");
907 }
908
909 let xid = ((payload[4] as u32) << 24)
910 | ((payload[5] as u32) << 16)
911 | ((payload[6] as u32) << 8)
912 | payload[7] as u32;
913 if xid != expect_xid {
914 crate::info!(
915 "DHCP Parse Error: XID mismatch (expected {:X}, got {:X})",
916 expect_xid,
917 xid
918 );
919 return Err("dhcp xid mismatch");
920 }
921 if payload[236..240] != DHCP_MAGIC_COOKIE {
922 crate::info!("[NET] DHCP Parse Error: Magic cookie missing");
923 return Err("dhcp magic cookie missing");
924 }
925
926 let yiaddr = [payload[16], payload[17], payload[18], payload[19]];
927 let mut msg_type: u8 = 0;
928 let mut server = [0u8; 4];
929 let mut router = [0u8; 4];
930 let mut dns = [0u8; 4];
931 let mut dns2 = [0u8; 4];
932 let mut subnet = [0u8; 4];
933 let mut lease_secs = 3600u32;
934 let mut t1_secs = 0u32;
935 let mut t2_secs = 0u32;
936
937 let mut idx = 240usize;
938 while idx < payload.len() {
939 let code = payload[idx];
940 idx += 1;
941 if code == 255 {
942 break;
943 }
944 if code == 0 {
945 continue;
946 }
947 if idx >= payload.len() {
948 crate::info!(
949 "DHCP Parse Error: Option malformed (idx={} >= len={})",
950 idx,
951 payload.len()
952 );
953 return Err("dhcp option malformed");
954 }
955
956 let opt_len = payload[idx] as usize;
957 idx += 1;
958 if idx + opt_len > payload.len() {
959 crate::info!(
960 "DHCP Parse Error: Option {} truncated (len={}, buffer remaining={})",
961 code,
962 opt_len,
963 payload.len() - idx
964 );
965 return Err("dhcp option truncated");
966 }
967
968 match code {
969 53 if opt_len == 1 => {
970 msg_type = payload[idx];
971 }
972 1 if opt_len == 4 => {
973 subnet.copy_from_slice(&payload[idx..idx + 4]);
974 }
975 54 if opt_len == 4 => {
976 server.copy_from_slice(&payload[idx..idx + 4]);
977 }
978 3 if opt_len >= 4 => {
979 router.copy_from_slice(&payload[idx..idx + 4]);
980 }
981 6 if opt_len >= 4 => {
982 dns.copy_from_slice(&payload[idx..idx + 4]);
983 if opt_len >= 8 {
984 dns2.copy_from_slice(&payload[idx + 4..idx + 8]);
985 }
986 }
987 51 if opt_len == 4 => {
988 lease_secs = ((payload[idx] as u32) << 24)
989 | ((payload[idx + 1] as u32) << 16)
990 | ((payload[idx + 2] as u32) << 8)
991 | payload[idx + 3] as u32;
992 }
993 58 if opt_len == 4 => {
994 t1_secs = ((payload[idx] as u32) << 24)
995 | ((payload[idx + 1] as u32) << 16)
996 | ((payload[idx + 2] as u32) << 8)
997 | payload[idx + 3] as u32;
998 }
999 59 if opt_len == 4 => {
1000 t2_secs = ((payload[idx] as u32) << 24)
1001 | ((payload[idx + 1] as u32) << 16)
1002 | ((payload[idx + 2] as u32) << 8)
1003 | payload[idx + 3] as u32;
1004 }
1005 _ => {}
1006 }
1007
1008 idx += opt_len;
1009 }
1010
1011 if msg_type == 0 {
1012 crate::info!("[NET] DHCP Parse Error: Message type missing");
1013 return Err("dhcp message type missing");
1014 }
1015 if msg_type != 6 && yiaddr == [0; 4] {
1016 crate::info!("[NET] DHCP Parse Error: yiaddr missing");
1017 return Err("dhcp yiaddr missing");
1018 }
1019 if msg_type != 6 && server == [0; 4] {
1020 crate::info!("[NET] DHCP Parse Warning: server id missing, falling back to 0.0.0.0");
1021 }
1022 if router == [0; 4] {
1023 router = server;
1024 }
1025 if dns == [0; 4] {
1026 dns = server;
1027 }
1028
1029 if subnet == [0; 4] {
1030 subnet = [255, 255, 255, 0];
1031 }
1032
1033 if lease_secs > 0 {
1034 if t1_secs == 0 {
1035 t1_secs = lease_secs / 2;
1036 }
1037 if t2_secs == 0 {
1038 t2_secs = lease_secs.saturating_mul(7) / 8;
1039 }
1040 }
1041
1042 unsafe {
1043 super::NET_STATE.rx_packets = super::NET_STATE.rx_packets.saturating_add(1);
1044 }
1045
1046 Ok(super::DhcpAckInfo {
1047 msg_type,
1048 yiaddr,
1049 server,
1050 router,
1051 dns,
1052 dns2,
1053 subnet,
1054 lease_secs,
1055 t1_secs,
1056 t2_secs,
1057 })
1058}
1059
1060fn dhcp_option_u8(out: &mut [u8], idx: usize, code: u8, val: u8) -> Result<usize, &'static str> {
1061 if idx + 3 > out.len() {
1062 return Err("dhcp option overflow");
1063 }
1064 out[idx] = code;
1065 out[idx + 1] = 1;
1066 out[idx + 2] = val;
1067 Ok(idx + 3)
1068}
1069
1070fn dhcp_option_u32(out: &mut [u8], idx: usize, code: u8, val: u32) -> Result<usize, &'static str> {
1071 if idx + 6 > out.len() {
1072 return Err("dhcp option overflow");
1073 }
1074 out[idx] = code;
1075 out[idx + 1] = 4;
1076 out[idx + 2] = (val >> 24) as u8;
1077 out[idx + 3] = (val >> 16) as u8;
1078 out[idx + 4] = (val >> 8) as u8;
1079 out[idx + 5] = val as u8;
1080 Ok(idx + 6)
1081}
1082
1083fn dhcp_option_ipv4(
1084 out: &mut [u8],
1085 idx: usize,
1086 code: u8,
1087 ip: [u8; 4],
1088) -> Result<usize, &'static str> {
1089 if idx + 6 > out.len() {
1090 return Err("dhcp option overflow");
1091 }
1092 out[idx] = code;
1093 out[idx + 1] = 4;
1094 out[idx + 2..idx + 6].copy_from_slice(&ip);
1095 Ok(idx + 6)
1096}
1097
1098fn dhcp_option_list(
1099 out: &mut [u8],
1100 idx: usize,
1101 code: u8,
1102 vals: &[u8],
1103) -> Result<usize, &'static str> {
1104 if vals.is_empty() || vals.len() > 255 {
1105 return Err("invalid dhcp option list");
1106 }
1107 if idx + 2 + vals.len() > out.len() {
1108 return Err("dhcp option overflow");
1109 }
1110 out[idx] = code;
1111 out[idx + 1] = vals.len() as u8;
1112 out[idx + 2..idx + 2 + vals.len()].copy_from_slice(vals);
1113 Ok(idx + 2 + vals.len())
1114}