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_abi::ip::{
19 is_ipv4_literal_candidate, is_ipv6_literal_candidate, parse_ipv4_literal, parse_ipv6_literal,
20};
21use strat9_syscall::{call, data::TimeSpec, number};
22
23alloc_freelist::define_freelist_allocator!(pub struct BumpAllocator; heap_size = 64 * 1024;);
28
29#[global_allocator]
30static GLOBAL_ALLOCATOR: BumpAllocator = BumpAllocator;
31
32#[alloc_error_handler]
33fn alloc_error(_layout: Layout) -> ! {
35 log("[ping] OOM\n");
36 call::exit(12)
37}
38
39#[panic_handler]
40fn panic(info: &PanicInfo) -> ! {
41 call::handle_panic("ping", info)
42}
43
44fn log(msg: &str) {
50 let _ = call::write(1, msg.as_bytes());
51}
52
53fn log_u32(val: u32) {
55 let mut buf = [0u8; 12];
56 let s = u32_to_str(val, &mut buf);
57 log(s);
58}
59
60fn u32_to_str(mut val: u32, buf: &mut [u8; 12]) -> &str {
62 if val == 0 {
63 return "0";
64 }
65 let mut i = buf.len();
66 while val > 0 {
67 i -= 1;
68 buf[i] = b'0' + (val % 10) as u8;
69 val /= 10;
70 }
71 unsafe { core::str::from_utf8_unchecked(&buf[i..]) }
72}
73
74struct BufWriter<'a> {
75 buf: &'a mut [u8],
76 pos: usize,
77}
78impl core::fmt::Write for BufWriter<'_> {
79 fn write_str(&mut self, s: &str) -> core::fmt::Result {
81 let bytes = s.as_bytes();
82 let avail = self.buf.len().saturating_sub(self.pos);
83 let n = bytes.len().min(avail);
84 self.buf[self.pos..self.pos + n].copy_from_slice(&bytes[..n]);
85 self.pos += n;
86 Ok(())
87 }
88}
89
90fn clock_ns() -> u64 {
92 unsafe { strat9_syscall::syscall0(number::SYS_CLOCK_GETTIME) }
93 .map(|v| v as u64)
94 .unwrap_or(0)
95}
96
97fn sleep_ms(ms: u64) {
99 let req = TimeSpec {
100 tv_sec: (ms / 1000) as i64,
101 tv_nsec: ((ms % 1000) * 1_000_000) as i64,
102 };
103 let _ = unsafe {
104 strat9_syscall::syscall2(number::SYS_NANOSLEEP, &req as *const TimeSpec as usize, 0)
105 };
106}
107
108fn scheme_open(path: &str) -> Result<usize, ()> {
110 call::openat(0, path, 0x3, 0)
111 .map(|fd| fd)
112 .map_err(|_| ())
113}
114
115fn scheme_write_fd(fd: usize, data: &[u8]) -> Result<usize, ()> {
117 call::write(fd, data).map_err(|_| ())
118}
119
120fn scheme_read_fd(fd: usize, buf: &mut [u8]) -> Result<usize, ()> {
122 call::read(fd, buf).map_err(|_| ())
123}
124
125fn scheme_close(fd: usize) {
127 let _ = call::close(fd);
128}
129
130#[derive(Clone, Copy, PartialEq, Eq)]
131enum AddressFamily {
132 Ipv4,
133 Ipv6,
134}
135
136struct ResolvedTarget<'a> {
137 addr: &'a str,
138 family: AddressFamily,
139}
140
141enum ResolveError {
142 InvalidLiteral(AddressFamily),
143 ResolveFailed,
144 FamilyMismatch {
145 requested: AddressFamily,
146 actual: AddressFamily,
147 },
148}
149
150fn family_flag(family: AddressFamily) -> &'static str {
151 match family {
152 AddressFamily::Ipv4 => "-4",
153 AddressFamily::Ipv6 => "-6",
154 }
155}
156
157fn family_name(family: AddressFamily) -> &'static str {
158 match family {
159 AddressFamily::Ipv4 => "IPv4",
160 AddressFamily::Ipv6 => "IPv6",
161 }
162}
163
164fn apply_family_constraint(
165 requested: Option<AddressFamily>,
166 actual: AddressFamily,
167) -> Result<(), ResolveError> {
168 match requested {
169 Some(expected) if expected != actual => Err(ResolveError::FamilyMismatch {
170 requested: expected,
171 actual,
172 }),
173 _ => Ok(()),
174 }
175}
176
177fn resolve_target<'a>(
178 target: &'a str,
179 requested_family: Option<AddressFamily>,
180 resolved_buf: &'a mut [u8; 64],
181) -> Result<ResolvedTarget<'a>, ResolveError> {
182 if parse_ipv4_literal(target).is_some() {
183 apply_family_constraint(requested_family, AddressFamily::Ipv4)?;
184 return Ok(ResolvedTarget {
185 addr: target,
186 family: AddressFamily::Ipv4,
187 });
188 }
189 if parse_ipv6_literal(target).is_some() {
190 apply_family_constraint(requested_family, AddressFamily::Ipv6)?;
191 return Ok(ResolvedTarget {
192 addr: target,
193 family: AddressFamily::Ipv6,
194 });
195 }
196 if is_ipv4_literal_candidate(target) {
197 return Err(ResolveError::InvalidLiteral(AddressFamily::Ipv4));
198 }
199 if is_ipv6_literal_candidate(target) {
200 return Err(ResolveError::InvalidLiteral(AddressFamily::Ipv6));
201 }
202
203 let mut path_buf = [0u8; 128];
204 let path_len = {
205 let mut pw = BufWriter {
206 buf: &mut path_buf,
207 pos: 0,
208 };
209 let _ = write!(pw, "/net/resolve/{}", target);
210 pw.pos
211 };
212 let path =
213 core::str::from_utf8(&path_buf[..path_len]).map_err(|_| ResolveError::ResolveFailed)?;
214 let n = scheme_open(path)
215 .and_then(|fd| {
216 let r = scheme_read_fd(fd, resolved_buf);
217 scheme_close(fd);
218 r
219 })
220 .map_err(|_| ResolveError::ResolveFailed)?;
221 if n == 0 {
222 return Err(ResolveError::ResolveFailed);
223 }
224 let end = resolved_buf[..n]
225 .iter()
226 .position(|&b| b == b'\n')
227 .unwrap_or(n);
228 if end == 0 {
229 return Err(ResolveError::ResolveFailed);
230 }
231 let resolved =
232 core::str::from_utf8(&resolved_buf[..end]).map_err(|_| ResolveError::ResolveFailed)?;
233 if parse_ipv4_literal(resolved).is_some() {
234 apply_family_constraint(requested_family, AddressFamily::Ipv4)?;
235 Ok(ResolvedTarget {
236 addr: resolved,
237 family: AddressFamily::Ipv4,
238 })
239 } else if parse_ipv6_literal(resolved).is_some() {
240 apply_family_constraint(requested_family, AddressFamily::Ipv6)?;
241 Ok(ResolvedTarget {
242 addr: resolved,
243 family: AddressFamily::Ipv6,
244 })
245 } else {
246 Err(ResolveError::ResolveFailed)
247 }
248}
249
250fn print_usage() {
251 log("Usage: ping [-6] [-c count] [target] [count]\n");
252 log(" ping -h | --help\n");
253 log("Options:\n");
254 log(" -6 Require IPv6\n");
255 log(" -c <count> Send count requests (default: 4)\n");
256 log(" -h, --help Show this help\n");
257}
258
259fn copy_target(dst: &mut [u8; 64], value: &str) -> usize {
260 let len = value.len().min(dst.len() - 1);
261 dst[..len].copy_from_slice(&value.as_bytes()[..len]);
262 len
263}
264
265fn read_default_target(family: Option<AddressFamily>, target: &mut [u8; 64]) -> Option<usize> {
266 let mut gateway_buf = [0u8; 64];
267 let path = if family == Some(AddressFamily::Ipv6) {
268 "/net/ip6/gateway"
269 } else {
270 "/net/gateway"
271 };
272 let n = scheme_open(path)
273 .and_then(|fd| {
274 let r = scheme_read_fd(fd, &mut gateway_buf);
275 scheme_close(fd);
276 r
277 })
278 .ok()?;
279 if n == 0 {
280 return None;
281 }
282
283 let end = gateway_buf[..n]
284 .iter()
285 .position(|&b| b == b'\n')
286 .unwrap_or(n);
287 if end == 0 {
288 return None;
289 }
290
291 let gateway = &gateway_buf[..end];
292 if gateway == b"0.0.0.0" || gateway == b"::" {
293 return None;
294 }
295
296 let gateway_str = core::str::from_utf8(gateway).ok()?;
297 Some(copy_target(target, gateway_str))
298}
299
300struct PingArgs {
305 target: [u8; 64],
306 target_len: usize,
307 count: u32,
308 family: Option<AddressFamily>,
309}
310
311unsafe fn cstr_to_str(ptr: *const u8) -> &'static str {
318 let mut len = 0usize;
319 while *ptr.add(len) != 0 {
320 len += 1;
321 }
322 core::str::from_utf8_unchecked(core::slice::from_raw_parts(ptr, len))
323}
324
325fn str_to_u32(s: &str) -> Option<u32> {
327 if s.is_empty() {
328 return None;
329 }
330 let mut val: u32 = 0;
331 for &b in s.as_bytes() {
332 if !b.is_ascii_digit() {
333 return None;
334 }
335 val = val.checked_mul(10)?.checked_add((b - b'0') as u32)?;
336 }
337 Some(val)
338}
339
340fn parse_args_from_stack(argc: usize, argv: *const *const u8) -> PingArgs {
348 let mut target = [0u8; 64];
349 let mut target_len = 0usize;
350 let mut count = 4u32;
351 let mut family: Option<AddressFamily> = None;
352 let mut positional_count_set = false;
353
354 let mut i = 1usize;
355 while i < argc {
356 let arg = unsafe { cstr_to_str(*argv.add(i)) };
357 match arg {
358 "-h" | "--help" => {
359 print_usage();
360 call::exit(0);
361 }
362 "-6" => {
363 family = Some(AddressFamily::Ipv6);
364 }
365 "-c" => {
366 i += 1;
367 if i >= argc {
368 log("ping: missing value for -c\n");
369 print_usage();
370 call::exit(1);
371 }
372 let count_arg = unsafe { cstr_to_str(*argv.add(i)) };
373 let Some(parsed) = str_to_u32(count_arg) else {
374 log("ping: invalid count: ");
375 log(count_arg);
376 log("\n");
377 call::exit(1);
378 };
379 count = parsed;
380 }
381 _ if arg.starts_with('-') => {
382 log("ping: unknown option: ");
383 log(arg);
384 log("\n");
385 print_usage();
386 call::exit(1);
387 }
388 _ if target_len == 0 => {
389 target_len = copy_target(&mut target, arg);
390 }
391 _ if !positional_count_set => {
392 let Some(parsed) = str_to_u32(arg) else {
393 log("ping: unexpected argument: ");
394 log(arg);
395 log("\n");
396 print_usage();
397 call::exit(1);
398 };
399 count = parsed;
400 positional_count_set = true;
401 }
402 _ => {
403 log("ping: unexpected argument: ");
404 log(arg);
405 log("\n");
406 print_usage();
407 call::exit(1);
408 }
409 }
410
411 i += 1;
412 }
413
414 if target_len == 0 {
415 target_len = match read_default_target(family, &mut target) {
416 Some(len) => len,
417 None => {
418 log("ping: no target specified and no default gateway configured\n");
419 print_usage();
420 call::exit(1);
421 }
422 };
423 }
424
425 PingArgs {
426 target,
427 target_len,
428 count,
429 family,
430 }
431}
432
433#[repr(C)]
442struct PingRequest {
443 seq: u16,
444 timestamp_ns: u64,
445 payload: [u8; 40],
446}
447
448#[unsafe(naked)]
452#[unsafe(no_mangle)]
453pub unsafe extern "C" fn _start() -> ! {
454 core::arch::naked_asm!(
457 "mov rdi, rsp", "call {f}",
459 "ud2",
460 f = sym start_impl,
461 );
462}
463
464extern "C" fn start_impl(initial_sp: *const u64) -> ! {
469 let argc = unsafe { *initial_sp } as usize;
470 let argv = unsafe { initial_sp.add(1) as *const *const u8 };
471 let args = parse_args_from_stack(argc, argv);
472 let raw_target = unsafe { core::str::from_utf8_unchecked(&args.target[..args.target_len]) };
473 let mut resolved_buf = [0u8; 64];
474 let target = match resolve_target(raw_target, args.family, &mut resolved_buf) {
475 Ok(target) => target,
476 Err(ResolveError::InvalidLiteral(family)) => {
477 log("ping: invalid ");
478 log(family_name(family));
479 log(" address: ");
480 log(raw_target);
481 log("\n");
482 call::exit(1);
483 }
484 Err(ResolveError::ResolveFailed) => {
485 log("ping: cannot resolve host: ");
486 log(raw_target);
487 log("\n");
488 call::exit(1);
489 }
490 Err(ResolveError::FamilyMismatch { requested, actual }) => {
491 log("ping: ");
492 log(family_flag(requested));
493 log(" requires an ");
494 log(family_name(requested));
495 log(" target, but got ");
496 log(family_name(actual));
497 log("\n");
498 call::exit(1);
499 }
500 };
501
502 log("PING ");
503 log(target.addr);
504 log(", sending ");
505 log_u32(args.count);
506 log(" ICMP now\n");
507
508 let mut path_buf = [0u8; 128];
510 let mut pw = BufWriter {
511 buf: &mut path_buf,
512 pos: 0,
513 };
514 let scheme_name = match target.family {
515 AddressFamily::Ipv4 => "ping",
516 AddressFamily::Ipv6 => "ping6",
517 };
518 let _ = write!(pw, "/net/{}/{}", scheme_name, target.addr);
519 let path_len = pw.pos;
520 let path = unsafe { core::str::from_utf8_unchecked(&path_buf[..path_len]) };
521
522 let fd = match scheme_open(path) {
524 Ok(fd) => fd,
525 Err(()) => {
526 log("ping: cannot open ");
527 log(path);
528 log("\n");
529 call::exit(1);
530 }
531 };
532
533 const PING_TIMEOUT_MS: u64 = 5_000;
534 const POLL_INTERVAL_MS: u64 = 100;
535
536 let mut sent: u32 = 0;
537 let mut received: u32 = 0;
538 let mut min_rtt_us: u64 = u64::MAX;
539 let mut max_rtt_us: u64 = 0;
540 let mut total_rtt_us: u64 = 0;
541
542 for seq in 0..args.count {
543 let ts = clock_ns();
545 let req = PingRequest {
546 seq: seq as u16,
547 timestamp_ns: ts,
548 payload: [0xAA; 40],
549 };
550 let req_bytes = unsafe {
551 core::slice::from_raw_parts(
552 &req as *const PingRequest as *const u8,
553 core::mem::size_of::<PingRequest>(),
554 )
555 };
556
557 let mut wrote = false;
558 let write_deadline_ns = clock_ns().saturating_add(PING_TIMEOUT_MS * 1_000_000);
559 while clock_ns() < write_deadline_ns {
560 log("[pt] wr ");
561 if scheme_write_fd(fd, req_bytes).is_ok() {
562 wrote = true;
563 log("ok\n");
564 break;
565 }
566 log(".");
567 sleep_ms(POLL_INTERVAL_MS);
570 }
571
572 if !wrote {
573 log(" Request timeout (write failed): seq=");
574 log_u32(seq);
575 log("\n");
576 sent += 1;
577 continue;
578 }
579 sent += 1;
580
581 log("[pt] rd ");
582 let mut got_reply = false;
583 let read_deadline_ns = clock_ns().saturating_add(PING_TIMEOUT_MS * 1_000_000);
584 while clock_ns() < read_deadline_ns {
585 let mut reply_buf = [0u8; 64];
586 match scheme_read_fd(fd, &mut reply_buf) {
587 Ok(n) if n >= 10 => {
588 log("rep\n");
589 let reply_seq = u16::from_le_bytes([reply_buf[0], reply_buf[1]]);
592 let rtt_us =
593 u64::from_le_bytes(reply_buf[2..10].try_into().unwrap_or([0u8; 8]));
594 let rtt_ms = rtt_us / 1000;
595 let rtt_frac = (rtt_us % 1000) / 100;
596
597 log(" Reply from ");
598 log(target.addr);
599 log(": seq=");
600 log_u32(reply_seq as u32);
601 log(" time=");
602 log_u32(rtt_ms as u32);
603 log(".");
604 log_u32(rtt_frac as u32);
605 log("ms\n");
606
607 received += 1;
608 total_rtt_us += rtt_us;
609 if rtt_us < min_rtt_us {
610 min_rtt_us = rtt_us;
611 }
612 if rtt_us > max_rtt_us {
613 max_rtt_us = rtt_us;
614 }
615 got_reply = true;
616 break;
617 }
618 Ok(0) => {
619 log(".");
620 sleep_ms(POLL_INTERVAL_MS);
621 }
622 _ => {
623 log("x");
624 sleep_ms(POLL_INTERVAL_MS);
625 }
626 }
627 }
628
629 if !got_reply {
630 log("[pt] rto\n");
631 log(" Request timeout: seq=");
632 log_u32(seq);
633 log("\n");
634 }
635
636 if seq + 1 < args.count {
637 sleep_ms(900); }
639 }
640
641 log("\n--- ");
643 log(target.addr);
644 log(" ping statistics ---\n");
645 log_u32(sent);
646 log(" packets transmitted, ");
647 log_u32(received);
648 log(" received");
649 if sent > 0 {
650 let loss = ((sent - received) * 100) / sent;
651 log(", ");
652 log_u32(loss);
653 log("% packet loss");
654 }
655 log("\n");
656
657 if received > 0 {
658 let avg = total_rtt_us / received as u64;
659 log("rtt min/avg/max = ");
660 log_u32((min_rtt_us / 1000) as u32);
661 log("/");
662 log_u32((avg / 1000) as u32);
663 log("/");
664 log_u32((max_rtt_us / 1000) as u32);
665 log(" ms\n");
666 }
667
668 scheme_close(fd);
669 call::exit(0)
670}