Skip to main content

atmos/kernel/net/
dhcp.rs

1use crate::kernel::usb;
2use alloc::string::String;
3
4// ─── DHCP constants ──────────────────────────────────────────────────────────
5const 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            // 自動 DISCOVER の失敗は次の周期で再試行されるが、黙って捨てると
102            // 「IP が取れないのに何も起きていないように見える」ため記録する。
103            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    // 【2026-07-26 バグ修正】以前はここで取得した`NET_LOCK`が関数全体(下の
181    // 再試行ループ全体)にわたって保持され続けていた。ループ内の
182    // `dhcp_acquire_once` → `dhcp_recv_message` → `udp_recv_next_datagram`
183    // (`udp.rs`の`udp_recv_matching`)が同じ`NET_LOCK`を再度取得しようとするため、
184    // `spin::Mutex`は再入不可であることから**自分自身のロックを永久に待ち続ける
185    // 確定的なデッドロック**だった(現状この関数はどこからも呼ばれていない
186    // デッドコードのため実害は無かったが、将来呼び出し箇所が追加された際の
187    // 地雷となるため修正する)。状態チェックだけをロックのスコープに収める。
188    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
759// pub(super) wrappers so self_test() in mod.rs can call these
760pub(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}