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:
parent
d0941780f5
commit
a4ed556543
9 changed files with 428 additions and 28 deletions
|
|
@ -94,6 +94,7 @@ struct inet_sock {
|
|||
int inuse;
|
||||
int type; // SOCK_STREAM or SOCK_DGRAM
|
||||
int proto; // 0, or IPPROTO_ICMP for a ping socket
|
||||
int iphdr; // SOCK_RAW: reads start at the IP header
|
||||
|
||||
struct tcp_pcb *tpcb;
|
||||
struct udp_pcb *upcb;
|
||||
|
|
@ -149,13 +150,36 @@ net_seq(void)
|
|||
return s;
|
||||
}
|
||||
|
||||
// Sleep until the count moves past `seen`, or this process is killed. kill()
|
||||
// makes a sleeping process runnable, so the loop re-checks and leaves.
|
||||
// A blocked socket call gives up for a kill and for any signal it can take.
|
||||
// The second used to be missing, so a signal woke the sleeper and the loop
|
||||
// below put it straight back: busybox's ping sends each request from a
|
||||
// SIGALRM handler while it sits in recvfrom, and got one reply and then
|
||||
// silence. There is no SA_RESTART, so the call returns EINTR and the caller
|
||||
// asks again, which is what every one of them is written to do.
|
||||
//
|
||||
// Only signals with a handler count, as on Linux. A SIGCHLD left at its
|
||||
// default is posted to a parent all the same, and ending a server's accept()
|
||||
// for it would be an EINTR that program had never been handed before.
|
||||
static int
|
||||
net_interrupted(void)
|
||||
{
|
||||
struct proc *p = myproc();
|
||||
if (p->killed)
|
||||
return 1;
|
||||
uint64 live = p->pending_signals & ~p->signal_mask;
|
||||
for (int sig = 1; sig < 32; sig++)
|
||||
if ((live & (1ULL << sig)) && p->sig_handler[sig] > 1) // not DFL/IGN
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Sleep until the count moves past `seen`, or a signal arrives. kill() makes
|
||||
// a sleeping process runnable, so the loop re-checks and leaves.
|
||||
static void
|
||||
net_wait(uint seen)
|
||||
{
|
||||
acquire(&netwait_lock);
|
||||
while (netwait_seq == seen && !myproc()->killed)
|
||||
while (netwait_seq == seen && !net_interrupted())
|
||||
sleep(&netwait_seq, &netwait_lock);
|
||||
release(&netwait_lock);
|
||||
}
|
||||
|
|
@ -438,6 +462,11 @@ inet_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,
|
|||
// processes ping at once; with one ping at a time the difference is invisible,
|
||||
// and a socket that sees a reply to somebody else's request will read a
|
||||
// sequence number it never sent and ignore it.
|
||||
//
|
||||
// A SOCK_RAW ICMP socket is the same socket, except that a read starts at the
|
||||
// IP header: that is the raw-socket contract, and busybox's ping reads the TTL
|
||||
// out of it and finds the ICMP message by IHL. Writing is the same for both --
|
||||
// without IP_HDRINCL a raw socket's caller sends the ICMP message alone too.
|
||||
static u8_t
|
||||
inet_icmp_recv_cb(void *arg, struct raw_pcb *pcb, struct pbuf *p,
|
||||
const ip_addr_t *addr)
|
||||
|
|
@ -462,7 +491,8 @@ inet_icmp_recv_cb(void *arg, struct raw_pcb *pcb, struct pbuf *p,
|
|||
if (pbuf_copy_partial(p, &type, 1, hlen) != 1 || type != 0)
|
||||
return 0;
|
||||
|
||||
u16_t icmplen = (u16_t)(p->tot_len - hlen);
|
||||
u16_t skip = is->iphdr ? 0 : hlen;
|
||||
u16_t icmplen = (u16_t)(p->tot_len - skip);
|
||||
|
||||
if (icmplen <= rx_space(is) && dq_used(is) < NDGRAM) {
|
||||
struct dgram *d = &is->dq[is->dhead % NDGRAM];
|
||||
|
|
@ -471,9 +501,9 @@ inet_icmp_recv_cb(void *arg, struct raw_pcb *pcb, struct pbuf *p,
|
|||
d->len = icmplen;
|
||||
is->dhead++;
|
||||
|
||||
// Copy the ICMP message out, skipping the IP header, across whatever pbuf
|
||||
// chain it arrived in.
|
||||
u16_t off = hlen;
|
||||
// Copy the message out, skipping the IP header unless this is a raw
|
||||
// socket, across whatever pbuf chain it arrived in.
|
||||
u16_t off = skip;
|
||||
while (off < p->tot_len) {
|
||||
char tmp[64];
|
||||
u16_t want = (u16_t)(p->tot_len - off);
|
||||
|
|
@ -515,8 +545,15 @@ inet_alloc(int type)
|
|||
void *
|
||||
inet_new(int type, int proto)
|
||||
{
|
||||
if (type != SOCK_STREAM && type != SOCK_DGRAM)
|
||||
if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_RAW)
|
||||
return 0;
|
||||
if (type == SOCK_RAW && proto != IPPROTO_ICMP)
|
||||
return 0;
|
||||
// Past this point a raw ICMP socket is a ping socket with one flag: every
|
||||
// datagram path below already does the right thing for it.
|
||||
int iphdr = (type == SOCK_RAW);
|
||||
if (iphdr)
|
||||
type = SOCK_DGRAM;
|
||||
// Ready, not *up*: see net_is_ready() in kernel/net/net.c. Requiring an
|
||||
// address here made every socket() fail until DHCP answered.
|
||||
if (!net_is_ready())
|
||||
|
|
@ -529,6 +566,7 @@ inet_new(int type, int proto)
|
|||
return 0;
|
||||
}
|
||||
is->proto = proto;
|
||||
is->iphdr = iphdr;
|
||||
|
||||
if (proto == IPPROTO_ICMP) {
|
||||
// A ping socket. raw_new registers for one IP protocol number; the
|
||||
|
|
@ -688,7 +726,7 @@ inet_accept(void *h)
|
|||
uint seen = net_seq();
|
||||
lwip_sys_unlock();
|
||||
|
||||
if (myproc()->killed)
|
||||
if (net_interrupted())
|
||||
return 0;
|
||||
net_wait(seen);
|
||||
}
|
||||
|
|
@ -729,7 +767,7 @@ inet_connect(void *h, uint32 addr, uint16 port)
|
|||
return 0;
|
||||
if (failed)
|
||||
return -ECONNREFUSED;
|
||||
if (myproc()->killed)
|
||||
if (net_interrupted())
|
||||
return -EINTR;
|
||||
net_wait(seen);
|
||||
}
|
||||
|
|
@ -742,6 +780,47 @@ inet_connect(void *h, uint32 addr, uint16 port)
|
|||
// peer it is connected to and is the same for every byte; for a datagram
|
||||
// socket it belongs to the individual datagram, which is why UDP needs the
|
||||
// queue above and TCP does not.
|
||||
// getsockname / getpeername: this end's address or the other's, the address
|
||||
// in network order and the port in host order, as inet_recvfrom gives them. A
|
||||
// socket not yet bound reads as 0.0.0.0:0 locally, which is what Linux says;
|
||||
// one with no peer is ENOTCONN. A listening pcb has no remote fields at all
|
||||
// -- lwIP's listen pcb is the smaller struct -- so it is never asked for them.
|
||||
int
|
||||
inet_name(void *h, int peer, uint *addr, ushort *port)
|
||||
{
|
||||
struct inet_sock *is = h;
|
||||
int r = 0;
|
||||
|
||||
*addr = 0;
|
||||
*port = 0;
|
||||
lwip_sys_lock();
|
||||
if (is->tpcb) {
|
||||
if (peer && (is->listening || is->tpcb->state < SYN_SENT)) {
|
||||
r = -ENOTCONN;
|
||||
} else if (peer) {
|
||||
*addr = ip4_addr_get_u32(ip_2_ip4(&is->tpcb->remote_ip));
|
||||
*port = is->tpcb->remote_port;
|
||||
} else {
|
||||
*addr = ip4_addr_get_u32(ip_2_ip4(&is->tpcb->local_ip));
|
||||
*port = is->tpcb->local_port;
|
||||
}
|
||||
} else if (is->upcb) {
|
||||
if (peer && !(is->upcb->flags & UDP_FLAGS_CONNECTED)) {
|
||||
r = -ENOTCONN;
|
||||
} else if (peer) {
|
||||
*addr = ip4_addr_get_u32(ip_2_ip4(&is->upcb->remote_ip));
|
||||
*port = is->upcb->remote_port;
|
||||
} else {
|
||||
*addr = ip4_addr_get_u32(ip_2_ip4(&is->upcb->local_ip));
|
||||
*port = is->upcb->local_port;
|
||||
}
|
||||
} else if (peer) {
|
||||
r = -ENOTCONN; // a ping socket has no peer
|
||||
}
|
||||
lwip_sys_unlock();
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
inet_recvfrom(void *h, char *buf, int n, uint *addr, ushort *port)
|
||||
{
|
||||
|
|
@ -787,7 +866,7 @@ inet_recvfrom(void *h, char *buf, int n, uint *addr, ushort *port)
|
|||
return got;
|
||||
if (eof)
|
||||
return 0; // end of file, not an error
|
||||
if (myproc()->killed)
|
||||
if (net_interrupted())
|
||||
return -EINTR;
|
||||
net_wait(seen);
|
||||
}
|
||||
|
|
@ -909,7 +988,7 @@ inet_sendto(void *h, char *buf, int n, uint addr, ushort port)
|
|||
lwip_sys_unlock();
|
||||
|
||||
if (done < n) {
|
||||
if (myproc()->killed)
|
||||
if (net_interrupted())
|
||||
return done > 0 ? done : -EINTR;
|
||||
// Woken by inet_sent_cb when an acknowledgement frees send buffer, or by
|
||||
// a failure. Without tcp_sent registered this would be a sleep with
|
||||
|
|
|
|||
|
|
@ -688,6 +688,61 @@ sockbind(struct socket *s, const char *path, uint dev, uint64 ino)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// The abstract namespace: a sun_path that starts with a NUL names no file.
|
||||
// Linux keeps these names in the socket layer, and Xorg binds one before its
|
||||
// /tmp/.X11-unix path -- which this used to read as the empty path, resolve
|
||||
// to the directory it was called from and refuse with EADDRINUSE, so Xorg
|
||||
// reported "server already running" and exited.
|
||||
//
|
||||
// The name becomes a key on a device number no filesystem has, and the rest
|
||||
// is the inode machinery unchanged: connect() finds the listener bound to
|
||||
// (SOCK_ABSTRACT_DEV, key). The name goes when the socket does, because
|
||||
// nothing else holds it.
|
||||
int
|
||||
sockbind_abstract(struct socket *s, uint64 key)
|
||||
{
|
||||
acquire(&stab.lock);
|
||||
if(s->state != SS_UNBOUND){
|
||||
release(&stab.lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
for(struct socket *o = stab.sock; o < stab.sock + NSOCK; o++)
|
||||
if(o != s && o->state != SS_FREE && o->state != SS_UNBOUND &&
|
||||
o->bind_dev == SOCK_ABSTRACT_DEV && o->bind_ino == key){
|
||||
release(&stab.lock);
|
||||
return -EADDRINUSE;
|
||||
}
|
||||
s->name[0] = '\0'; // getsockname reports it unnamed, not as a path
|
||||
s->bind_dev = SOCK_ABSTRACT_DEV;
|
||||
s->bind_ino = key;
|
||||
s->state = SS_BOUND;
|
||||
release(&stab.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The path for getsockname (peer 0) or getpeername (peer 1) of an AF_UNIX
|
||||
// socket. Empty for an unnamed one -- a client that never bound, or an
|
||||
// abstract name, which the caller reports as the family alone.
|
||||
int
|
||||
sockname_inet(struct socket *s, int peer, uint *addr, ushort *port)
|
||||
{
|
||||
return inet_name(s->inet, peer, addr, port);
|
||||
}
|
||||
|
||||
int
|
||||
sockname_unix(struct socket *s, int peer, char *path)
|
||||
{
|
||||
acquire(&stab.lock);
|
||||
struct socket *o = peer ? s->peer : s;
|
||||
if(o == 0){
|
||||
release(&stab.lock);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
safestrcpy(path, o->name, UNIX_PATH_MAX);
|
||||
release(&stab.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
socklisten(struct socket *s, int backlog)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue