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.

View file

@ -10,14 +10,36 @@
#include "core/uaccess.h"
#include "uapi/errno.h"
// Timer values in ticks, rounding a nonzero time up: POSIX says a timer set
// to 1 us goes off, not that it is disarmed.
static uint
tv_ticks(struct timeval *tv)
{
if (tv->tv_sec < 0 || tv->tv_usec < 0)
return 0;
uint64 t = (uint64)tv->tv_sec * HZ +
((uint64)tv->tv_usec + USEC_PER_TICK - 1) / USEC_PER_TICK;
return t > 0x7fffffff ? 0x7fffffff : (uint)t;
}
static void
ticks_tv(uint t, struct timeval *tv)
{
tv->tv_sec = t / HZ;
tv->tv_usec = (t % HZ) * USEC_PER_TICK;
}
// The seconds left on the previous alarm, rounded up, as POSIX asks.
addr_t
sys_alarm(void)
{
int seconds;
if (argint(0, &seconds) < 0)
return -1;
myproc()->alarm_ticks = seconds * HZ;
return 0;
struct proc *p = myproc();
uint left = alarm_left(p);
alarm_arm(p, seconds > 0 ? (uint)seconds * HZ : 0, 0);
return (left + HZ - 1) / HZ;
}
addr_t
@ -77,21 +99,52 @@ sys_clock_gettime(void)
return 0;
}
addr_t sys_getitimer(void) { return 0; } // stub
// ITIMER_REAL only. ITIMER_VIRTUAL and ITIMER_PROF count CPU time, which
// nothing here measures per tick, and are refused rather than accepted and
// never fired -- which is what all three used to be.
static int
itimer_get(struct proc *p, struct itimerval *iv)
{
ticks_tv(p->alarm_every, &iv->it_interval);
ticks_tv(alarm_left(p), &iv->it_value);
return 0;
}
addr_t
sys_getitimer(void)
{
int which;
addr_t curp;
argint(0, &which); argaddr(1, &curp);
if (which != 0)
return -EINVAL;
struct itimerval iv;
itimer_get(myproc(), &iv);
if (copy_to_user(curp, &iv, sizeof(iv)) != 0)
return -EFAULT;
return 0;
}
addr_t
sys_setitimer(void)
{
int which;
addr_t newp;
argint(0, &which); argaddr(1, &newp);
// We don't implement interval timers; just track the alarm if it's ITIMER_REAL.
if (which == 0 && newp) {
struct itimerval iv;
if (copy_from_user(&iv, newp, sizeof(iv)) == 0)
myproc()->alarm_ticks = (int)(iv.it_value.tv_sec * HZ +
iv.it_value.tv_usec / USEC_PER_TICK);
addr_t newp, oldp;
argint(0, &which); argaddr(1, &newp); argaddr(2, &oldp);
if (which != 0)
return -EINVAL;
struct proc *p = myproc();
struct itimerval iv;
if (oldp) {
itimer_get(p, &iv);
if (copy_to_user(oldp, &iv, sizeof(iv)) != 0)
return -EFAULT;
}
if (newp == 0)
return 0;
if (copy_from_user(&iv, newp, sizeof(iv)) != 0)
return -EFAULT;
alarm_arm(p, tv_ticks(&iv.it_value), tv_ticks(&iv.it_interval));
return 0;
}

View file

@ -184,8 +184,8 @@ SYSCALL(102, 157, prctl)
SYSCALL(103, 234, tgkill)
SYSCALL(104, 200, tkill)
SYSCALL(105, 34, pause)
SYSCALL(106, NO_LINUX, getitimer)
SYSCALL(107, NO_LINUX, setitimer)
SYSCALL(106, 36, getitimer)
SYSCALL(107, 38, setitimer)
SYSCALL(108, 98, getrusage)
SYSCALL(109, 100, times)
SYSCALL(110, 99, sysinfo)
@ -296,6 +296,8 @@ SYSCALL(203, 281, epoll_pwait)
SYSCALL(204, 282, signalfd)
SYSCALL(205, 284, eventfd)
SYSCALL(206, 73, flock)
SYSCALL(207, 51, getsockname)
SYSCALL(208, 52, getpeername)
// --- mounts ---
SYSCALL(185, 165, mount)