1#![no_std]
12#![no_main]
13#![feature(alloc_error_handler)]
14
15extern crate alloc;
16
17use core::{alloc::Layout, fmt::Write, panic::PanicInfo};
18use strat9_syscall::{call, data::TimeSpec, number};
19
20alloc_freelist::define_freelist_allocator!(pub struct BumpAllocator; heap_size = 64 * 1024;);
25
26#[global_allocator]
27static GLOBAL_ALLOCATOR: BumpAllocator = BumpAllocator;
28
29#[alloc_error_handler]
30fn alloc_error(_layout: Layout) -> ! {
32 log("[ping] OOM\n");
33 call::exit(12)
34}
35
36#[panic_handler]
37fn panic(info: &PanicInfo) -> ! {
39 log("[ping] PANIC: ");
40 let msg = info.message();
41 let mut buf = [0u8; 256];
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::write(1, msg.as_bytes());
64}
65
66fn log_u32(val: u32) {
68 let mut buf = [0u8; 12];
69 let s = u32_to_str(val, &mut buf);
70 log(s);
71}
72
73fn u32_to_str(mut val: u32, buf: &mut [u8; 12]) -> &str {
75 if val == 0 {
76 return "0";
77 }
78 let mut i = buf.len();
79 while val > 0 {
80 i -= 1;
81 buf[i] = b'0' + (val % 10) as u8;
82 val /= 10;
83 }
84 unsafe { core::str::from_utf8_unchecked(&buf[i..]) }
85}
86
87struct BufWriter<'a> {
88 buf: &'a mut [u8],
89 pos: usize,
90}
91impl core::fmt::Write for BufWriter<'_> {
92 fn write_str(&mut self, s: &str) -> core::fmt::Result {
94 let bytes = s.as_bytes();
95 let avail = self.buf.len().saturating_sub(self.pos);
96 let n = bytes.len().min(avail);
97 self.buf[self.pos..self.pos + n].copy_from_slice(&bytes[..n]);
98 self.pos += n;
99 Ok(())
100 }
101}
102
103fn clock_ns() -> u64 {
105 unsafe { strat9_syscall::syscall0(number::SYS_CLOCK_GETTIME) }
106 .map(|v| v as u64)
107 .unwrap_or(0)
108}
109
110fn sleep_ms(ms: u64) {
112 let req = TimeSpec {
113 tv_sec: (ms / 1000) as i64,
114 tv_nsec: ((ms % 1000) * 1_000_000) as i64,
115 };
116 let _ = unsafe {
117 strat9_syscall::syscall2(number::SYS_NANOSLEEP, &req as *const TimeSpec as usize, 0)
118 };
119}
120
121fn scheme_read(path: &str, buf: &mut [u8]) -> Result<usize, ()> {
123 let fd = call::openat(0, path, 0x1, 0).map_err(|_| ())?; let n = call::read(fd as usize, buf).map_err(|_| {
125 let _ = call::close(fd as usize);
126 })?;
127 let _ = call::close(fd as usize);
128 Ok(n)
129}
130
131fn scheme_write(path: &str, data: &[u8]) -> Result<usize, ()> {
133 let fd = call::openat(0, path, 0x2, 0).map_err(|_| ())?; let n = call::write(fd as usize, data).map_err(|_| {
135 let _ = call::close(fd as usize);
136 })?;
137 let _ = call::close(fd as usize);
138 Ok(n)
139}
140
141fn parse_ipv4_literal(s: &str) -> bool {
143 let bytes = s.as_bytes();
144 if bytes.is_empty() {
145 return false;
146 }
147 let mut dots = 0usize;
148 let mut val: u16 = 0;
149 let mut has_digit = false;
150 for &b in bytes {
151 if b == b'.' {
152 if !has_digit || val > 255 || dots >= 3 {
153 return false;
154 }
155 dots += 1;
156 val = 0;
157 has_digit = false;
158 continue;
159 }
160 if !b.is_ascii_digit() {
161 return false;
162 }
163 val = val * 10 + (b - b'0') as u16;
164 has_digit = true;
165 }
166 has_digit && val <= 255 && dots == 3
167}
168
169fn resolve_target<'a>(target: &'a str, resolved_buf: &'a mut [u8; 64]) -> Option<&'a str> {
170 if parse_ipv4_literal(target) {
171 return Some(target);
172 }
173 let mut path_buf = [0u8; 128];
174 let path_len = {
175 let mut pw = BufWriter {
176 buf: &mut path_buf,
177 pos: 0,
178 };
179 let _ = write!(pw, "/net/resolve/{}", target);
180 pw.pos
181 };
182 let path = core::str::from_utf8(&path_buf[..path_len]).ok()?;
183 let n = scheme_read(path, resolved_buf).ok()?;
184 if n == 0 {
185 return None;
186 }
187 let end = resolved_buf[..n]
188 .iter()
189 .position(|&b| b == b'\n')
190 .unwrap_or(n);
191 if end == 0 {
192 return None;
193 }
194 let resolved = core::str::from_utf8(&resolved_buf[..end]).ok()?;
195 if parse_ipv4_literal(resolved) {
196 Some(resolved)
197 } else {
198 None
199 }
200}
201
202struct PingArgs {
207 target: [u8; 64],
208 target_len: usize,
209 count: u32,
210}
211
212fn parse_args() -> PingArgs {
214 let mut target = [0u8; 64];
220 let target_len;
221
222 let mut gw_buf = [0u8; 64];
223 if let Ok(n) = scheme_read("/net/gateway", &mut gw_buf) {
224 if n > 0 && !gw_buf.starts_with(b"0.0.0.0") {
225 let end = gw_buf[..n].iter().position(|&b| b == b'\n').unwrap_or(n);
227 target[..end].copy_from_slice(&gw_buf[..end]);
228 target_len = end;
229 } else {
230 let default = b"10.0.2.2";
231 target[..default.len()].copy_from_slice(default);
232 target_len = default.len();
233 }
234 } else {
235 let default = b"10.0.2.2";
236 target[..default.len()].copy_from_slice(default);
237 target_len = default.len();
238 }
239
240 PingArgs {
241 target,
242 target_len,
243 count: 4,
244 }
245}
246
247#[repr(C)]
256struct PingRequest {
257 seq: u16,
258 timestamp_ns: u64,
259 payload: [u8; 40],
260}
261
262#[repr(C)]
263struct PingReply {
264 seq: u16,
265 rtt_us: u64,
266}
267
268#[unsafe(no_mangle)]
269pub extern "C" fn _start() -> ! {
271 let args = parse_args();
272 let raw_target = unsafe { core::str::from_utf8_unchecked(&args.target[..args.target_len]) };
273 let mut resolved_buf = [0u8; 64];
274 let target = match resolve_target(raw_target, &mut resolved_buf) {
275 Some(ip) => ip,
276 None => {
277 log("ping: cannot resolve target ");
278 log(raw_target);
279 log("\n");
280 call::exit(2);
281 }
282 };
283
284 log("PING ");
285 log(target);
286 log(" - ");
287 log_u32(args.count);
288 log(" packets\n");
289
290 let mut path_buf = [0u8; 128];
292 let mut pw = BufWriter {
293 buf: &mut path_buf,
294 pos: 0,
295 };
296 let _ = write!(pw, "/net/ping/{}", target);
297 let path_len = pw.pos;
298 let path = unsafe { core::str::from_utf8_unchecked(&path_buf[..path_len]) };
299
300 let mut sent: u32 = 0;
301 let mut received: u32 = 0;
302 let mut min_rtt_us: u64 = u64::MAX;
303 let mut max_rtt_us: u64 = 0;
304 let mut total_rtt_us: u64 = 0;
305
306 for seq in 0..args.count {
307 let ts = clock_ns();
309 let req = PingRequest {
310 seq: seq as u16,
311 timestamp_ns: ts,
312 payload: [0xAA; 40],
313 };
314 let req_bytes = unsafe {
315 core::slice::from_raw_parts(
316 &req as *const PingRequest as *const u8,
317 core::mem::size_of::<PingRequest>(),
318 )
319 };
320
321 if scheme_write(path, req_bytes).is_err() {
323 log(" Request timeout (write failed)\n");
324 sent += 1;
325 sleep_ms(1000);
326 continue;
327 }
328 sent += 1;
329
330 sleep_ms(100);
332 let mut reply_buf = [0u8; 64];
333 match scheme_read(path, &mut reply_buf) {
334 Ok(n) if n >= 10 => {
335 let rtt_us = u64::from_le_bytes([
336 reply_buf[2],
337 reply_buf[3],
338 reply_buf[4],
339 reply_buf[5],
340 reply_buf[6],
341 reply_buf[7],
342 reply_buf[8],
343 reply_buf[9],
344 ]);
345 let rtt_ms = rtt_us / 1000;
346 let rtt_frac = (rtt_us % 1000) / 100;
347
348 log(" Reply from ");
349 log(target);
350 log(": seq=");
351 log_u32(seq);
352 log(" time=");
353 log_u32(rtt_ms as u32);
354 log(".");
355 log_u32(rtt_frac as u32);
356 log("ms\n");
357
358 received += 1;
359 total_rtt_us += rtt_us;
360 if rtt_us < min_rtt_us {
361 min_rtt_us = rtt_us;
362 }
363 if rtt_us > max_rtt_us {
364 max_rtt_us = rtt_us;
365 }
366 }
367 _ => {
368 log(" Request timeout: seq=");
369 log_u32(seq);
370 log("\n");
371 }
372 }
373
374 if seq + 1 < args.count {
375 sleep_ms(900); }
377 }
378
379 log("\n--- ");
381 log(target);
382 log(" ping statistics ---\n");
383 log_u32(sent);
384 log(" packets transmitted, ");
385 log_u32(received);
386 log(" received");
387 if sent > 0 {
388 let loss = ((sent - received) * 100) / sent;
389 log(", ");
390 log_u32(loss);
391 log("% packet loss");
392 }
393 log("\n");
394
395 if received > 0 {
396 let avg = total_rtt_us / received as u64;
397 log("rtt min/avg/max = ");
398 log_u32((min_rtt_us / 1000) as u32);
399 log("/");
400 log_u32((avg / 1000) as u32);
401 log("/");
402 log_u32((max_rtt_us / 1000) as u32);
403 log(" ms\n");
404 }
405
406 call::exit(0)
407}