README: rewritten around what runs (Linux software, Xorg, apk, Linux's drivers), the honest gaps, measured numbers. plan/: 1.0.md is the finish line, where each clause of the acceptance test stands, and the register (one failing clause: a root that survives a reboot); built plans (foundations, hybrid, alpine, xorg) and the decided-against one (wayland) are gone, their content lives in architecture/; roadmap.md is only what is still open. guides: building/layout/testing/adding-a-driver (Linux code may be used). history: four entries (ls -lR, TLB shootdowns, MDIO 0x02460246, netlink MSG_TRUNC). Website: cards, stack, download page, docs index. Code comments that pointed at deleted plan docs point at the architecture docs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
270 lines
7.3 KiB
C
270 lines
7.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// 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/architecture/kernel.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 "core/types.h"
|
|
#include "uapi/user.h"
|
|
#include "net/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 <host> [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();
|
|
}
|