RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

foundry / rust / net / src / bin / ping.rs
3912 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! `ping [-c count] [-i ms] host`: send ICMP echo requests, time the replies.
//!
//! ICMP is IP's "control message" protocol. An *echo request* (type 8) asks
//! a host to send the same bytes straight back as an *echo reply* (type 0).
//! The packet: type, code, checksum (2 bytes), identifier (2), sequence
//! number (2), then any payload. We use a Linux-style "ping socket"
//! (SOCK_DGRAM + IPPROTO_ICMP): the kernel fills in the identifier and
//! checksum and only gives us replies to *our* requests.
#![no_std]
#![no_main]

#[macro_use]
extern crate ulib;
ulib::entry!(main);

const PAYLOAD: usize = 56; // like Linux's ping: 56 + 8 header = 64 bytes

fn main() -> i64 {
    let mut count = 4u16;
    let mut interval_ms = 1000;
    let mut host = None;
    let mut i = 1;
    while let Some(a) = ulib::arg(i) {
        match a {
            "-c" => { i += 1; count = ulib::arg(i).and_then(|v| v.parse().ok()).unwrap_or(4); }
            "-i" => { i += 1; interval_ms = ulib::arg(i).and_then(|v| v.parse().ok()).unwrap_or(1000); }
            _ => host = Some(a),
        }
        i += 1;
    }
    let Some(host) = host else {
        eprintln!("usage: ping [-c count] [-i interval_ms] host");
        return 2;
    };
    let ip = match ulib::resolve(host) {
        Ok(ip) => ip,
        Err(e) => {
            eprintln!("ping: {}: {}", host, ulib::strerror(e));
            return 2;
        }
    };
    let fd = ulib::socket(ulib::SOCK_DGRAM, ulib::IPPROTO_ICMP);
    if fd < 0 {
        eprintln!("ping: socket: {}", ulib::strerror(fd));
        return 2;
    }
    ulib::set_recv_timeout(fd, 1000); // a reply later than 1 s counts as lost
    println!("PING {} ({}): {} data bytes", host, ip, PAYLOAD);

    let (mut received, mut min, mut max, mut total) = (0u64, u64::MAX, 0u64, 0u64);
    for seq in 1..=count {
        // Echo request: type 8, code 0, checksum and id (kernel fills in), seq.
        let mut packet = [0u8; 8 + PAYLOAD];
        packet[0] = 8;
        packet[6..8].copy_from_slice(&seq.to_be_bytes());
        for (j, b) in packet[8..].iter_mut().enumerate() {
            *b = j as u8; // a recognizable payload
        }
        let sent_at = ulib::now_us();
        let n = ulib::sendto(fd, &packet, ip, 0);
        if n < 0 {
            eprintln!("ping: sendto: {}", ulib::strerror(n));
            return 2;
        }
        // Wait for *this* reply (a late one for an older seq is skipped).
        let mut reply = [0u8; 8 + PAYLOAD + 64];
        loop {
            let (n, from) = ulib::recvfrom(fd, &mut reply);
            if n < 0 {
                println!("Request timeout for icmp_seq={}", seq);
                break;
            }
            let n = n as usize;
            if n >= 8 && reply[0] == 0 && reply[6..8] == seq.to_be_bytes() {
                let us = ulib::now_us() - sent_at;
                let ok = reply[8..n] == packet[8..];
                println!(
                    "{} bytes from {}: icmp_seq={} time={}.{:02} ms{}",
                    n, from.ip(), seq, us / 1000, us % 1000 / 10,
                    if ok { "" } else { " (payload differs!)" }
                );
                received += 1;
                (min, max, total) = (min.min(us), max.max(us), total + us);
                break;
            }
        }
        if seq < count {
            ulib::sleep_ms(interval_ms);
        }
    }
    ulib::close(fd);
    let loss = (count as u64 - received) * 100 / count.max(1) as u64;
    println!("--- {} ping statistics ---", host);
    println!("{} packets transmitted, {} received, {}% packet loss", count, received, loss);
    if received > 0 {
        let avg = total / received;
        println!(
            "rtt min/avg/max = {}.{:02}/{}.{:02}/{}.{:02} ms",
            min / 1000, min % 1000 / 10, avg / 1000, avg % 1000 / 10, max / 1000, max % 1000 / 10
        );
    }
    if received > 0 { 0 } else { 1 }
}