// SPDX-License-Identifier: Apache-2.0 // // ping -- ICMP echo, over a SchOS ping socket. // // This is a first-party program rather than toybox's ping, and the reason is // worth recording because it is not "ours is better". toybox's ping reads its // replies with recvmsg(2) and asks for the TTL through an IP_RECVTTL control // message, and SchOS has neither: no recvmsg, no ancillary data. Both are owed // -- recvmsg is item 1 of docs/plan/foundations.md, where it is wanted for // SCM_RIGHTS -- and neither should be rushed to make one applet run. // // So this uses sendto/recvfrom, which the kernel does have, and proves the ICMP // socket end to end in the meantime. When recvmsg lands, toybox's ping will // work and this can go. // // A "ping socket" is socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP): the kernel owns // the IP header and the caller reads and writes the ICMP message alone. See // inet_icmp_recv_cb in kernel/net/inet.c. #include "types.h" #include "user.h" #include "socket.h" #define ICMP_ECHO 8 #define ICMP_ECHOREPLY 0 #define DEFAULT_COUNT 4 #define PAYLOAD 56 // what every other ping sends struct icmphdr { uchar type; uchar code; ushort checksum; ushort id; ushort seq; }; // lib/ is raw syscall stubs -- no libc, so not even memset. Everything here is // hand-rolled for the same reason cmd/schinit is. static void zero(void *p, int n) { char *b = p; while (n-- > 0) *b++ = 0; } static void puts_(const char *s) { int n = 0; while (s[n]) n++; write(1, (void *)s, n); } static void putn_(int v) { char buf[12]; int i = 0, neg = 0; if (v < 0) { neg = 1; v = -v; } if (v == 0) buf[i++] = '0'; while (v) { buf[i++] = (char)('0' + v % 10); v /= 10; } if (neg) buf[i++] = '-'; while (i--) write(1, &buf[i], 1); } // The Internet checksum: one's complement of the one's complement sum of every // 16-bit word. Computed over the ICMP message with the checksum field zeroed, // which is why it is filled in last. static ushort cksum(const void *p, int n) { const uchar *b = p; uint sum = 0; int i; for (i = 0; i + 1 < n; i += 2) sum += (uint)((b[i] << 8) | b[i + 1]); if (i < n) sum += (uint)(b[i] << 8); while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); return (ushort)(~sum & 0xFFFF); } static ushort hton16(ushort v) { return (ushort)((v >> 8) | (v << 8)); } // Dotted quad to a network-byte-order address. Returns 0 on a malformed one, // which is indistinguishable from 0.0.0.0 and is fine: nobody pings that. static uint parse_ip(const char *s, int *ok) { uint out = 0; int part, i, digits; *ok = 0; for (i = 0; i < 4; i++) { part = 0; digits = 0; while (*s >= '0' && *s <= '9') { part = part * 10 + (*s - '0'); if (part > 255) return 0; s++; digits++; } if (digits == 0) return 0; out |= (uint)part << (8 * i); // little-endian host, so LSB first if (i < 3) { if (*s != '.') return 0; s++; } } if (*s != '\0') return 0; *ok = 1; return out; } static void print_ip(uint addr) { for (int i = 0; i < 4; i++) { putn_((int)((addr >> (8 * i)) & 0xFF)); if (i < 3) puts_("."); } } int main(int argc, char *argv[]) { char target[64]; int count = DEFAULT_COUNT; int ok, sent = 0, recvd = 0; if (argc < 2) { puts_("usage: ping [count]\n"); exit(); } // "localhost" without a resolver. The kernel has no DNS client reachable // from userland, and the one name worth special-casing is the one that // always means the same thing. const char *host = argv[1]; if (host[0] == 'l' && host[1] == 'o' && host[2] == 'c' && host[3] == 'a' && host[4] == 'l' && host[5] == 'h' && host[6] == 'o' && host[7] == 's' && host[8] == 't' && host[9] == 0) host = "127.0.0.1"; { int i = 0; while (host[i] && i < 63) { target[i] = host[i]; i++; } target[i] = 0; } uint addr = parse_ip(target, &ok); if (!ok) { puts_("ping: not a dotted-quad address: "); puts_(argv[1]); puts_("\nping: there is no resolver yet; use an address\n"); exit(); } if (argc > 2) { count = 0; for (const char *p = argv[2]; *p >= '0' && *p <= '9'; p++) count = count * 10 + (*p - '0'); if (count <= 0) count = DEFAULT_COUNT; } int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); if (fd < 0) { puts_("ping: socket: "); putn_(fd); puts_("\n"); exit(); } puts_("PING "); print_ip(addr); puts_(" "); putn_(PAYLOAD); puts_(" bytes of data.\n"); ushort id = (ushort)getpid(); for (int seq = 1; seq <= count; seq++) { char out[sizeof(struct icmphdr) + PAYLOAD]; struct icmphdr *ih = (struct icmphdr *)out; zero(out, (int)sizeof(out)); ih->type = ICMP_ECHO; ih->code = 0; ih->id = hton16(id); ih->seq = hton16((ushort)seq); for (int i = 0; i < PAYLOAD; i++) out[sizeof(*ih) + i] = (char)('a' + (i % 26)); ih->checksum = 0; // The checksum is stored as it is computed: cksum() already works in // network order, so byte-swapping it here would send a wrong one. ih->checksum = hton16(cksum(out, (int)sizeof(out))); struct sockaddr_in sa; zero(&sa, (int)sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = 0; sa.sin_addr = addr; int t0 = uptime(); int n = sendto(fd, out, (int)sizeof(out), 0, (struct sockaddr *)&sa, sizeof(sa)); if (n < 0) { puts_("ping: sendto: "); putn_(n); puts_("\n"); break; } sent++; // Wait for a reply, but not forever. uptime() counts 10 ms ticks, so // 100 of them is one second -- the same timeout every ping uses. char in[256]; int got = -1, waited = 0; struct sockaddr_in from; int fromlen = sizeof(from); while (waited < 100) { got = recvfrom(fd, in, (int)sizeof(in), 0, (struct sockaddr *)&from, &fromlen); if (got > 0) break; sleep(1); waited = uptime() - t0; } if (got <= 0) { puts_("Request timeout for icmp_seq "); putn_(seq); puts_("\n"); continue; } struct icmphdr *rh = (struct icmphdr *)in; if (rh->type != ICMP_ECHOREPLY || hton16(rh->id) != id) { puts_("ping: reply for somebody else, ignored\n"); continue; } recvd++; putn_(got); puts_(" bytes from "); print_ip(from.sin_addr); puts_(": icmp_seq="); putn_((int)hton16(rh->seq)); puts_(" time="); putn_((uptime() - t0) * 10); puts_(" ms\n"); if (seq < count) sleep(100); // one second between packets } close(fd); puts_("--- "); print_ip(addr); puts_(" ping statistics ---\n"); putn_(sent); puts_(" packets transmitted, "); putn_(recvd); puts_(" received, "); putn_(sent ? (sent - recvd) * 100 / sent : 0); puts_("% packet loss\n"); exit(); }