1#![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
19alloc_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]
29fn alloc_error(_layout: Layout) -> ! {
31 log("[dhcp-client] OOM\n");
32 call::exit(12)
33}
34
35#[panic_handler]
36fn 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
57fn 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 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
82fn scheme_read(path: &str, buf: &mut [u8]) -> Result<usize, ()> {
84 let fd = call::openat(0, path, 0x1, 0).map_err(|_| ())?; 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
92fn 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
103fn is_unconfigured(data: &[u8]) -> bool {
105 data.starts_with(b"0.0.0.0") || data.starts_with(b"169.254.")
106}
107
108fn 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
145const BOOT_RETRIES: usize = 10;
150const POLL_INTERVAL_MS: u64 = 500;
151const BACKGROUND_POLL_INTERVAL_MS: u64 = 1000;
152
153#[unsafe(no_mangle)]
154pub 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 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 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}