kernel: timers that fire, raw ping, and the sockets X needs

alarm() and setitimer() stored a countdown nothing counted down, so no
timer has ever fired; setitimer and getitimer were also registered with
no Linux number. alarmd now posts SIGALRM on the tick, while any process
has a timer armed. busybox's ping, /bin/ping on the Alpine root, sends
every request after the first from its SIGALRM handler.

It also opens SOCK_RAW, which was EPROTOTYPE. A raw ICMP socket is now a
ping socket whose reads start at the IP header. And a blocked socket
call gives up for a signal with a handler, not only for a kill: the
alarm woke recvfrom, and the wait put it straight back to sleep.

For Xorg: AF_UNIX abstract names, which read as the empty path and so as
EADDRINUSE ("server already running"); and getsockname and getpeername,
which did not exist, so libxcb sent no cookie and the server refused
every client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-24 19:44:10 +02:00
commit a4ed556543
9 changed files with 428 additions and 28 deletions

View file

@ -66,6 +66,28 @@ fetch_sun_path(addr_t uaddr, int ulen, char *out)
return 0;
}
// For an abstract address -- sun_path[0] == 0 -- the name as a key, and 1.
// The name is every byte after the NUL up to the length given, NULs included,
// as Linux has it; FNV-1a, which is plenty for a table of NSOCK.
static int
sun_abstract(addr_t uaddr, int ulen, uint64 *key)
{
struct sockaddr_un sa;
int plen = ulen - (int)sizeof(sa.sun_family);
if (plen < 2 || ulen > (int)sizeof(sa))
return 0;
if (copy_from_user(&sa, uaddr, (uint64)ulen) < 0 || sa.sun_path[0] != 0)
return 0;
uint64 h = 1469598103934665603ULL;
for (int i = 1; i < plen; i++) {
h ^= (uchar)sa.sun_path[i];
h *= 1099511628211ULL;
}
*key = h ? h : 1;
return 1;
}
// Wrap an established socket in a file and a descriptor. Consumes the
// socket's reference on success; on failure the caller still owns it.
//
@ -144,7 +166,8 @@ SYSCALL_DEFINE(socket)
type &= 0xFF;
if (domain == AF_UNIX && type != SOCK_STREAM)
return -EPROTOTYPE;
if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM)
if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM &&
type != SOCK_RAW)
return -EPROTOTYPE;
// AF_NETLINK takes SOCK_RAW or SOCK_DGRAM and its own protocol numbering;
// socknew checks both, because which protocols exist is the socket layer's
@ -168,10 +191,17 @@ SYSCALL_DEFINE(socket)
(domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP);
if (redundant)
protocol = 0;
else if (!(domain == AF_INET && type == SOCK_DGRAM &&
else if (!(domain == AF_INET &&
(type == SOCK_DGRAM || type == SOCK_RAW) &&
protocol == IPPROTO_ICMP))
return -EPROTONOSUPPORT;
}
// A raw socket is ICMP or nothing: it exists because busybox's ping, which
// is /bin/ping on the Alpine root, opens SOCK_RAW and stops at
// EPROTOTYPE. Raw TCP or UDP would be the stack handing over packets it is
// also processing, and nothing has asked for that.
if (domain == AF_INET && type == SOCK_RAW && protocol != IPPROTO_ICMP)
return -EPROTONOSUPPORT;
// ENETDOWN rather than ENFILE when the stack itself is not running: the
// old blanket ENFILE said "file table overflow" for a machine with no
@ -240,6 +270,10 @@ SYSCALL_DEFINE(bind)
return sockbind_in(s, ip, port_from_net(port));
}
uint64 key;
if (sun_abstract(uaddr, ulen, &key))
return sockbind_abstract(s, key);
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
return r;
@ -315,6 +349,10 @@ SYSCALL_DEFINE(connect)
return sockconnect_in(s, ip, port_from_net(port));
}
uint64 key;
if (sun_abstract(uaddr, ulen, &key))
return sockconnect(s, SOCK_ABSTRACT_DEV, key);
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
return r;
@ -550,6 +588,74 @@ SYSCALL_DEFINE(recvfrom)
return r;
}
// getsockname(2) and getpeername(2).
//
// Neither existed, and the first program to mind was every X client: libxcb
// picks the cookie to send by the family of the connection's address, asked
// with getpeername and then getsockname, and with both failing it sent none.
// Xorg answered "Authorization required, but no authorization protocol
// specified" and startx gave up.
static int
sock_name(int peer)
{
struct socket *s;
addr_t uaddr, ulenp;
int r, cap;
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &uaddr);
argaddr(2, &ulenp);
if (get_user(cap, ulenp) < 0)
return -EFAULT;
if (cap < 0)
return -EINVAL;
union {
struct sockaddr_in in;
struct sockaddr_un un;
} sa;
int alen;
memset(&sa, 0, sizeof(sa));
if (sock_domain(s) == AF_INET) {
uint ip;
ushort port;
if ((r = sockname_inet(s, peer, &ip, &port)) < 0)
return r;
sa.in.sin_family = AF_INET;
sa.in.sin_addr = ip;
sa.in.sin_port = port_to_net(port);
alen = (int)sizeof(sa.in);
} else if (sock_domain(s) == AF_NETLINK) {
// A sockaddr_nl of zeros: family, padding, port id 0 (the kernel),
// no groups.
sa.un.sun_family = AF_NETLINK;
alen = 12;
} else {
if ((r = sockname_unix(s, peer, sa.un.sun_path)) < 0)
return r;
sa.un.sun_family = (uint16)sock_domain(s);
// The family, the path and its NUL; an unnamed socket is the family
// alone, as on Linux.
alen = (int)sizeof(sa.un.sun_family);
if (sa.un.sun_path[0])
alen += (int)strlen(sa.un.sun_path) + 1;
}
// Truncated to the caller's buffer, with the full length written back.
if (cap > alen)
cap = alen;
if (cap > 0 && copy_to_user(uaddr, &sa, (uint64)cap) < 0)
return -EFAULT;
if (copy_to_user(ulenp, &alen, sizeof(alen)) < 0)
return -EFAULT;
return 0;
}
SYSCALL_DEFINE(getsockname) { return sock_name(0); }
SYSCALL_DEFINE(getpeername) { return sock_name(1); }
// shutdown(2). Half-close is not modelled -- there is one buffer per
// direction and no per-direction flag -- so SHUT_WR and SHUT_RDWR both mean
// "the peer sees end-of-file", which is the behaviour programs depend on.