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) -> ! {
37 call::handle_panic("dhcp-client", info)
38}
39
40fn 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 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
65fn scheme_read(path: &str, buf: &mut [u8]) -> Result<usize, ()> {
67 let fd = call::openat(-100_i64 as usize, path, 0x1, 0).map_err(|_| ())?; 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
75fn 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
86fn is_unconfigured(data: &[u8]) -> bool {
88 data.starts_with(b"0.0.0.0") || data.starts_with(b"169.254.")
89}
90
91fn 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
128const BOOT_RETRIES: usize = 10;
133const POLL_INTERVAL_MS: u64 = 500;
134const BACKGROUND_POLL_INTERVAL_MS: u64 = 1000;
135
136#[unsafe(no_mangle)]
137pub 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 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 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}