Skip to main content

dhcp_client/
main.rs

1//! dhcp-client – DHCP status monitor for strat9-os
2//!
3//! This is **not** a full DHCP client.  The actual DHCP exchange is performed
4//! by the `strate-net` silo (via smoltcp's DHCPv4 socket).  `dhcp-client` simply
5//! polls the `/net/ip`, `/net/gateway`, `/net/route` and `/net/dns` scheme files until a
6//! valid address is obtained, then prints the result to the console.
7//!
8//! All I/O is done through Plan 9–style schemes – no BSD sockets.
9
10#![no_std]
11#![no_main]
12#![feature(alloc_error_handler)]
13
14extern crate alloc;
15
16use core::{alloc::Layout, panic::PanicInfo};
17use strat9_syscall::{call, data::TimeSpec, number};
18
19// ---------------------------------------------------------------------------
20// Minimal bump allocator (same pattern as other strat9 silos)
21// ---------------------------------------------------------------------------
22
23alloc_freelist::define_freelist_allocator!(pub struct BumpAllocator; heap_size = 64 * 1024;);
24
25#[global_allocator]
26static GLOBAL_ALLOCATOR: BumpAllocator = BumpAllocator;
27
28#[alloc_error_handler]
29/// Implements alloc error.
30fn alloc_error(_layout: Layout) -> ! {
31    log("[dhcp-client] OOM\n");
32    call::exit(12)
33}
34
35#[panic_handler]
36/// Implements panic.
37fn panic(info: &PanicInfo) -> ! {
38    log("[dhcp-client] PANIC: ");
39    let msg = info.message();
40    let mut buf = [0u8; 256];
41    use core::fmt::Write;
42    let mut cursor = BufWriter {
43        buf: &mut buf,
44        pos: 0,
45    };
46    let _ = write!(cursor, "{}", msg);
47    let written = cursor.pos;
48    if written > 0 {
49        if let Ok(s) = core::str::from_utf8(&buf[..written]) {
50            log(s);
51        }
52    }
53    log("\n");
54    call::exit(255)
55}
56
57// ---------------------------------------------------------------------------
58// Helpers
59// ---------------------------------------------------------------------------
60
61/// Implements log.
62fn log(msg: &str) {
63    let _ = call::debug_log(msg.as_bytes());
64}
65
66struct BufWriter<'a> {
67    buf: &'a mut [u8],
68    pos: usize,
69}
70impl core::fmt::Write for BufWriter<'_> {
71    /// Writes str.
72    fn write_str(&mut self, s: &str) -> core::fmt::Result {
73        let bytes = s.as_bytes();
74        let avail = self.buf.len().saturating_sub(self.pos);
75        let n = bytes.len().min(avail);
76        self.buf[self.pos..self.pos + n].copy_from_slice(&bytes[..n]);
77        self.pos += n;
78        Ok(())
79    }
80}
81
82/// Read a scheme file and return how many bytes were read into `buf`.
83fn scheme_read(path: &str, buf: &mut [u8]) -> Result<usize, ()> {
84    let fd = call::openat(0, path, 0x1, 0).map_err(|_| ())?; // O_READ
85    let n = call::read(fd as usize, buf).map_err(|_| {
86        let _ = call::close(fd as usize);
87    })?;
88    let _ = call::close(fd as usize);
89    Ok(n)
90}
91
92/// Implements sleep ms.
93fn sleep_ms(ms: u64) {
94    let req = TimeSpec {
95        tv_sec: (ms / 1000) as i64,
96        tv_nsec: ((ms % 1000) * 1_000_000) as i64,
97    };
98    let _ = unsafe {
99        strat9_syscall::syscall2(number::SYS_NANOSLEEP, &req as *const TimeSpec as usize, 0)
100    };
101}
102
103/// Returns whether unconfigured.
104fn is_unconfigured(data: &[u8]) -> bool {
105    data.starts_with(b"0.0.0.0") || data.starts_with(b"169.254.")
106}
107
108/// Implements cidr to netmask.
109fn cidr_to_netmask(prefix_str: &str) -> Option<[u8; 20]> {
110    let mut out = [0u8; 20];
111    let s = prefix_str.trim();
112    if s.is_empty() {
113        return None;
114    }
115
116    let mut prefix: u16 = 0;
117    for &b in s.as_bytes() {
118        if b < b'0' || b > b'9' {
119            return None;
120        }
121        prefix = prefix.checked_mul(10)?.checked_add((b - b'0') as u16)?;
122    }
123    if prefix > 32 {
124        return None;
125    }
126
127    let prefix = prefix as u8;
128    let mask: u32 = if prefix == 32 {
129        0xFFFF_FFFF
130    } else if prefix == 0 {
131        0
132    } else {
133        !((1u32 << (32 - prefix)) - 1)
134    };
135    let o = mask.to_be_bytes();
136    use core::fmt::Write;
137    let mut w = BufWriter {
138        buf: &mut out,
139        pos: 0,
140    };
141    let _ = write!(w, "{}.{}.{}.{}", o[0], o[1], o[2], o[3]);
142    Some(out)
143}
144
145// ---------------------------------------------------------------------------
146// Main
147// ---------------------------------------------------------------------------
148
149const BOOT_RETRIES: usize = 10;
150const POLL_INTERVAL_MS: u64 = 500;
151const BACKGROUND_POLL_INTERVAL_MS: u64 = 1000;
152
153#[unsafe(no_mangle)]
154/// Implements start.
155pub extern "C" fn _start() -> ! {
156    log("[dhcp-client] Waiting for DHCP configuration via /net scheme...\n");
157
158    let mut ip_buf = [0u8; 64];
159    let mut gw_buf = [0u8; 64];
160    let mut route_buf = [0u8; 64];
161    let mut dns_buf = [0u8; 96];
162    let mut retries = 0;
163    let mut background_mode = false;
164
165    loop {
166        // Try to read the IP address from the network strate
167        let ip_n = match scheme_read("/net/ip", &mut ip_buf) {
168            Ok(n) => n,
169            Err(_) => {
170                if retries == 0 {
171                    log("[dhcp-client] /net not available yet, retrying...\n");
172                }
173                retries += 1;
174                if retries >= BOOT_RETRIES {
175                    if !background_mode {
176                        log("[dhcp-client] /net not ready yet; keeping background probe alive\n");
177                        background_mode = true;
178                    }
179                    sleep_ms(BACKGROUND_POLL_INTERVAL_MS);
180                    continue;
181                }
182                sleep_ms(POLL_INTERVAL_MS);
183                continue;
184            }
185        };
186
187        if ip_n == 0 || is_unconfigured(&ip_buf[..ip_n]) {
188            retries += 1;
189            if retries >= BOOT_RETRIES {
190                if !background_mode {
191                    log("[dhcp-client] DHCP not ready during boot window; keeping background probe alive\n");
192                    background_mode = true;
193                }
194                sleep_ms(BACKGROUND_POLL_INTERVAL_MS);
195                continue;
196            }
197            sleep_ms(POLL_INTERVAL_MS);
198            continue;
199        }
200
201        let gw_n = scheme_read("/net/gateway", &mut gw_buf).unwrap_or(0);
202        let route_n = scheme_read("/net/route", &mut route_buf).unwrap_or(0);
203        let dns_n = scheme_read("/net/dns", &mut dns_buf).unwrap_or(0);
204
205        let ip_str = core::str::from_utf8(&ip_buf[..ip_n]).unwrap_or("").trim();
206
207        // Split "a.b.c.d/prefix" into address and netmask
208        let (addr, netmask) = if let Some(slash) = ip_str.find('/') {
209            let prefix_str = &ip_str[slash + 1..];
210            let mask = cidr_to_netmask(prefix_str).unwrap_or([0u8; 20]);
211            (&ip_str[..slash], mask)
212        } else {
213            (ip_str, [0u8; 20])
214        };
215
216        log("\n");
217        log("============================================================\n");
218        log("  Network configuration (DHCP)\n");
219        log("------------------------------------------------------------\n");
220        log("  Address : ");
221        log(addr);
222        log("\n  Netmask : ");
223        if let Ok(s) = core::str::from_utf8(&netmask) {
224            let s = s.trim_end_matches('\0');
225            if !s.is_empty() {
226                log(s);
227            } else {
228                log("(none)");
229            }
230        }
231        log("\n  Gateway : ");
232        if gw_n > 0 {
233            if let Ok(s) = core::str::from_utf8(&gw_buf[..gw_n]) {
234                log(s.trim());
235            }
236        } else {
237            log("(none)");
238        }
239        log("\n  Route   : ");
240        if route_n > 0 {
241            if let Ok(s) = core::str::from_utf8(&route_buf[..route_n]) {
242                log(s.trim());
243            }
244        } else {
245            log("(none)");
246        }
247        log("\n  DNS     : ");
248        if dns_n > 0 {
249            if let Ok(s) = core::str::from_utf8(&dns_buf[..dns_n]) {
250                log(s.trim());
251            }
252        } else {
253            log("(none)");
254        }
255        log("\n");
256        log("============================================================\n");
257
258        break;
259    }
260
261    log("[dhcp-client] Done.\n");
262    call::exit(0)
263}