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]
36fn panic(info: &PanicInfo) -> ! {
37    call::handle_panic("dhcp-client", info)
38}
39
40// ---------------------------------------------------------------------------
41// Helpers
42// ---------------------------------------------------------------------------
43
44/// Implements log.
45fn log(msg: &str) {
46    let _ = call::debug_log(msg.as_bytes());
47}
48
49struct BufWriter<'a> {
50    buf: &'a mut [u8],
51    pos: usize,
52}
53impl core::fmt::Write for BufWriter<'_> {
54    /// Writes str.
55    fn write_str(&mut self, s: &str) -> core::fmt::Result {
56        let bytes = s.as_bytes();
57        let avail = self.buf.len().saturating_sub(self.pos);
58        let n = bytes.len().min(avail);
59        self.buf[self.pos..self.pos + n].copy_from_slice(&bytes[..n]);
60        self.pos += n;
61        Ok(())
62    }
63}
64
65/// Read a scheme file and return how many bytes were read into `buf`.
66fn scheme_read(path: &str, buf: &mut [u8]) -> Result<usize, ()> {
67    let fd = call::openat(-100_i64 as usize, path, 0x1, 0).map_err(|_| ())?; // O_READ
68    let n = call::read(fd as usize, buf).map_err(|_| {
69        let _ = call::close(fd as usize);
70    })?;
71    let _ = call::close(fd as usize);
72    Ok(n)
73}
74
75/// Implements sleep ms.
76fn sleep_ms(ms: u64) {
77    let req = TimeSpec {
78        tv_sec: (ms / 1000) as i64,
79        tv_nsec: ((ms % 1000) * 1_000_000) as i64,
80    };
81    let _ = unsafe {
82        strat9_syscall::syscall2(number::SYS_NANOSLEEP, &req as *const TimeSpec as usize, 0)
83    };
84}
85
86/// Returns whether unconfigured.
87fn is_unconfigured(data: &[u8]) -> bool {
88    data.starts_with(b"0.0.0.0") || data.starts_with(b"169.254.")
89}
90
91/// Implements cidr to netmask.
92fn cidr_to_netmask(prefix_str: &str) -> Option<[u8; 20]> {
93    let mut out = [0u8; 20];
94    let s = prefix_str.trim();
95    if s.is_empty() {
96        return None;
97    }
98
99    let mut prefix: u16 = 0;
100    for &b in s.as_bytes() {
101        if !(b'0'..=b'9').contains(&b) {
102            return None;
103        }
104        prefix = prefix.checked_mul(10)?.checked_add((b - b'0') as u16)?;
105    }
106    if prefix > 32 {
107        return None;
108    }
109
110    let prefix = prefix as u8;
111    let mask: u32 = if prefix == 32 {
112        0xFFFF_FFFF
113    } else if prefix == 0 {
114        0
115    } else {
116        !((1u32 << (32 - prefix)) - 1)
117    };
118    let o = mask.to_be_bytes();
119    use core::fmt::Write;
120    let mut w = BufWriter {
121        buf: &mut out,
122        pos: 0,
123    };
124    let _ = write!(w, "{}.{}.{}.{}", o[0], o[1], o[2], o[3]);
125    Some(out)
126}
127
128// ---------------------------------------------------------------------------
129// Main
130// ---------------------------------------------------------------------------
131
132const BOOT_RETRIES: usize = 10;
133const POLL_INTERVAL_MS: u64 = 500;
134const BACKGROUND_POLL_INTERVAL_MS: u64 = 1000;
135
136#[unsafe(no_mangle)]
137/// Implements start.
138pub extern "C" fn _start() -> ! {
139    log("[dhcp-client] Waiting for DHCP configuration via /net scheme...\n");
140
141    let mut ip_buf = [0u8; 64];
142    let mut gw_buf = [0u8; 64];
143    let mut route_buf = [0u8; 64];
144    let mut dns_buf = [0u8; 96];
145    let mut retries = 0;
146    let mut background_mode = false;
147
148    loop {
149        // Try to read the IP address from the network strate
150        let ip_n = match scheme_read("/net/ip", &mut ip_buf) {
151            Ok(n) => n,
152            Err(_) => {
153                if retries == 0 {
154                    log("[dhcp-client] /net not available yet, retrying...\n");
155                }
156                retries += 1;
157                if retries >= BOOT_RETRIES {
158                    if !background_mode {
159                        log("[dhcp-client] /net not ready yet; keeping background probe alive\n");
160                        background_mode = true;
161                    }
162                    sleep_ms(BACKGROUND_POLL_INTERVAL_MS);
163                    continue;
164                }
165                sleep_ms(POLL_INTERVAL_MS);
166                continue;
167            }
168        };
169
170        if ip_n == 0 || is_unconfigured(&ip_buf[..ip_n]) {
171            retries += 1;
172            if retries >= BOOT_RETRIES {
173                if !background_mode {
174                    log("[dhcp-client] DHCP not ready during boot window; keeping background probe alive\n");
175                    background_mode = true;
176                }
177                sleep_ms(BACKGROUND_POLL_INTERVAL_MS);
178                continue;
179            }
180            sleep_ms(POLL_INTERVAL_MS);
181            continue;
182        }
183
184        let gw_n = scheme_read("/net/gateway", &mut gw_buf).unwrap_or(0);
185        let route_n = scheme_read("/net/route", &mut route_buf).unwrap_or(0);
186        let dns_n = scheme_read("/net/dns", &mut dns_buf).unwrap_or(0);
187
188        let ip_str = core::str::from_utf8(&ip_buf[..ip_n]).unwrap_or("").trim();
189
190        // Split "a.b.c.d/prefix" into address and netmask
191        let (addr, netmask) = if let Some(slash) = ip_str.find('/') {
192            let prefix_str = &ip_str[slash + 1..];
193            let mask = cidr_to_netmask(prefix_str).unwrap_or([0u8; 20]);
194            (&ip_str[..slash], mask)
195        } else {
196            (ip_str, [0u8; 20])
197        };
198
199        log("\n");
200        log("============================================================\n");
201        log("  Network configuration (DHCP)\n");
202        log("------------------------------------------------------------\n");
203        log("  Address : ");
204        log(addr);
205        log("\n  Netmask : ");
206        if let Ok(s) = core::str::from_utf8(&netmask) {
207            let s = s.trim_end_matches('\0');
208            if !s.is_empty() {
209                log(s);
210            } else {
211                log("(none)");
212            }
213        }
214        log("\n  Gateway : ");
215        if gw_n > 0 {
216            if let Ok(s) = core::str::from_utf8(&gw_buf[..gw_n]) {
217                log(s.trim());
218            }
219        } else {
220            log("(none)");
221        }
222        log("\n  Route   : ");
223        if route_n > 0 {
224            if let Ok(s) = core::str::from_utf8(&route_buf[..route_n]) {
225                log(s.trim());
226            }
227        } else {
228            log("(none)");
229        }
230        log("\n  DNS     : ");
231        if dns_n > 0 {
232            if let Ok(s) = core::str::from_utf8(&dns_buf[..dns_n]) {
233                log(s.trim());
234            }
235        } else {
236            log("(none)");
237        }
238        log("\n");
239        log("============================================================\n");
240
241        break;
242    }
243
244    log("[dhcp-client] Done.\n");
245    call::exit(0)
246}