SchOS/cmd/ping/main.c
Nexoniarz cb3d28e3d6 docs: README, plan, guides and the site describe what is built now
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
2026-10-26 08:08:05 +01:00

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();
}