sendmsg/recvmsg with SCM_RIGHTS. Item 1 of docs/plan/foundations.md and
mechanism 1 of docs/plan/hybrid.md, which are the same primitive seen from two
directions: Wayland's wl_shm_create_pool passes a file descriptor with no
integer-shaped alternative, and handing a driver process the device it is
allowed to touch is the same act. Both plans stopped here.
Linux's structures byte for byte -- msghdr, cmsghdr, the CMSG_ macros -- because
the programs that need this are not ours. libwayland and toybox both call
recvmsg with Linux's headers and would not notice a friendlier door.
The delivery rule is the whole design. A batch of descriptors is pinned to the
stream position of the first byte of the message that carried it, and that has
two halves: a reader arriving at that position is handed them, and a reader that
has not reached it is stopped SHORT of it. Leave out the second half and a
program asking for 4096 bytes swallows three messages and is handed one
message's descriptors, with nothing in the result saying which. socktest checks
exactly that: two one-byte messages, an eight-byte read, one byte back.
The kernel holds a reference to every descriptor in flight, so a sender may
close its own copy the moment sendmsg returns. Giving that reference back is the
part that leaks if it is not written down, and there are three ways a message
ends:
- received with room installed in the receiver's table
- received without room, or by
plain read(2) closed; MSG_CTRUNC for the first case
- never received, socket closed closed by sockclose
The middle row looks like a choice and is not: holding kernel references for a
receiver that has proved it is not looking is a leak with extra steps.
No garbage collector. A socket descriptor sent over that same socket and never
received holds a reference to the socket that holds the reference to it, and
neither close reaches zero. Linux carries several hundred lines for this.
Nothing on either plan passes a socket over a socket -- Wayland passes memory,
the driver work passes devices -- so it is a bounded leak that is documented
rather than a silent one.
Fourteen checks in cmd/socktest, and every one moves data through the descriptor
rather than inspecting it. The descriptor passed is always a pipe end, because a
pipe announces its own lifetime: a read of a pipe whose writers are all gone
returns 0, which turns "was the kernel's reference released" -- otherwise
invisible from userspace -- into a value a test can compare.
Two bugs fell out, neither in the new code:
- sockpair() never set the socket's domain, so every socketpair() socket
claimed domain 0. Invisible while the data paths asked only whether a socket
was AF_INET or AF_NETLINK, since zero answers "neither" correctly.
SCM_RIGHTS asks "is this AF_UNIX", and zero is wrong for that one.
- pipe_poll() reports POLLHUP but not POLLIN at end of file, so a non-blocking
read of a pipe whose writers had all gone returned EAGAIN forever instead of
0 -- a program that polls and reads spins waiting for an end that already
happened. sockreadable() always had the equivalent condition; the pipe path
did not. Found because the test helper for "is this descriptor closed" is a
non-blocking read of a pipe.
projects/schcom/README.md said SCM_RIGHTS was done for a long time while nothing
in the kernel implemented it, and an entire GUI plan was drawn on that line.
It is true now, which is not the same as it having been right, and the README
says so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
876 lines
26 KiB
C
876 lines
26 KiB
C
// Unix domain sockets.
|
|
//
|
|
// A connected pair is two buffers, not one. Writing to a socket appends to
|
|
// its *peer's* buffer and reading drains its own, which is what makes a
|
|
// socket bidirectional where a pipe is not, without needing two of anything.
|
|
//
|
|
// **One lock covers the whole table**, rather than a lock per socket. A pipe
|
|
// gets away with a lock per pipe because every operation touches exactly one
|
|
// object; a socket operation touches two, and two-object locking needs an
|
|
// ordering rule that every path obeys or it deadlocks. Worse, the wakeup
|
|
// races are subtle: a reader that tests `peer_closed` under its own lock while
|
|
// the closer sets it under the peer's has no interlock at all, and sleeps
|
|
// through the wakeup that was meant for it. With one lock the condition and
|
|
// the sleep are atomic with respect to every writer of that condition, which
|
|
// is the property sleep/wakeup actually requires. Thirty-two sockets carrying
|
|
// GUI messages will not notice the contention.
|
|
//
|
|
// **Names are held in a table here, not in the filesystem.** A real Unix
|
|
// socket appears in a directory listing as a special file, and binding one
|
|
// creates that node. Doing that properly means a new inode type threaded
|
|
// through both filesystems, iget, dirlookup and stat -- a VFS change, not a
|
|
// socket change. The table gives every behaviour that matters: a client
|
|
// finds a server by a name it agreed in advance, which is the whole job.
|
|
// What is lost is `ls -l /tmp/schcom` showing an `s`, and `unlink()` of a
|
|
// socket path failing with ENOENT. Both are worth fixing when the VFS next
|
|
// gets opened up; neither blocks anything.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "mmu.h"
|
|
#include "proc.h"
|
|
#include "fs.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "file.h"
|
|
#include "posix.h"
|
|
#include "errno.h"
|
|
|
|
#define _KERNEL_SOCKET_INTERNALS
|
|
#include "socket.h"
|
|
|
|
static struct {
|
|
struct spinlock lock;
|
|
struct socket sock[NSOCK];
|
|
} stab;
|
|
|
|
// Defined with the rest of the descriptor-passing code below; declared here
|
|
// because sockclose is the one caller that cannot be moved after it.
|
|
static void scm_drain(struct socket *s);
|
|
|
|
void
|
|
sockinit(void)
|
|
{
|
|
initlock(&stab.lock, "socktab");
|
|
}
|
|
|
|
// Caller holds stab.lock.
|
|
static struct socket *
|
|
alloc_locked(int type)
|
|
{
|
|
struct socket *s;
|
|
|
|
for(s = stab.sock; s < stab.sock + NSOCK; s++){
|
|
if(s->state == SS_FREE && s->ref == 0){
|
|
memset(s, 0, sizeof(*s));
|
|
s->ref = 1;
|
|
s->type = type;
|
|
s->state = SS_UNBOUND;
|
|
return s;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Find a socket listening on this name. Caller holds stab.lock.
|
|
static struct socket *
|
|
find_listener(const char *name)
|
|
{
|
|
struct socket *s;
|
|
|
|
for(s = stab.sock; s < stab.sock + NSOCK; s++)
|
|
if(s->state == SS_LISTENING && strncmp(s->name, name, UNIX_PATH_MAX) == 0)
|
|
return s;
|
|
return 0;
|
|
}
|
|
|
|
// Detach s from its peer and tell the peer about it. Caller holds stab.lock.
|
|
static void
|
|
orphan_peer(struct socket *s)
|
|
{
|
|
struct socket *peer = s->peer;
|
|
|
|
if(peer == 0)
|
|
return;
|
|
peer->peer_closed = 1;
|
|
peer->peer = 0;
|
|
s->peer = 0;
|
|
|
|
// Both channels, because the peer may be blocked reading (waiting for data
|
|
// that will now never come) or writing (waiting for room in a buffer nobody
|
|
// will ever drain).
|
|
wakeup(&peer->nread);
|
|
wakeup(&peer->nwrite);
|
|
}
|
|
|
|
void
|
|
sockclose(struct socket *s)
|
|
{
|
|
int i;
|
|
|
|
if(s == 0)
|
|
return;
|
|
|
|
// An AF_INET socket's state lives in inet.c behind the lwIP lock, so its
|
|
// teardown happens there. The table entry below is freed either way.
|
|
if(s->domain == AF_INET){
|
|
acquire(&stab.lock);
|
|
int last = (--s->ref <= 0);
|
|
if(last)
|
|
s->state = SS_FREE;
|
|
release(&stab.lock);
|
|
if(last && s->inet){
|
|
inet_close(s->inet);
|
|
s->inet = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
struct socket *q[NBACKLOG];
|
|
int nq;
|
|
|
|
acquire(&stab.lock);
|
|
if(--s->ref > 0){
|
|
release(&stab.lock);
|
|
return;
|
|
}
|
|
|
|
// A listener that goes away takes its unaccepted connections with it. Each
|
|
// queued entry is the *server* side of a connection whose client is already
|
|
// live and may be writing into it; that client has to be told, or it hangs
|
|
// on a socket that will never be read.
|
|
//
|
|
// Taking the backlog out here rather than walking it below is what lets the
|
|
// drain happen with the lock dropped. At this point the reference count is
|
|
// zero and the state is not yet SS_FREE, so nothing can find these sockets
|
|
// and alloc_locked will not reuse them either.
|
|
nq = s->nqueue;
|
|
for(i = 0; i < nq; i++)
|
|
q[i] = s->queue[i];
|
|
s->nqueue = 0;
|
|
release(&stab.lock);
|
|
|
|
// Descriptors that were sent to these sockets and never received have to be
|
|
// closed rather than leaked -- the kernel is holding the only reference.
|
|
scm_drain(s);
|
|
for(i = 0; i < nq; i++)
|
|
if(q[i])
|
|
scm_drain(q[i]);
|
|
|
|
acquire(&stab.lock);
|
|
orphan_peer(s);
|
|
for(i = 0; i < nq; i++){
|
|
if(q[i] == 0)
|
|
continue;
|
|
orphan_peer(q[i]);
|
|
q[i]->state = SS_FREE;
|
|
q[i]->ref = 0;
|
|
}
|
|
|
|
s->state = SS_FREE;
|
|
s->ref = 0;
|
|
wakeup(&s->nqueue); // an accept() blocked on this listener
|
|
release(&stab.lock);
|
|
}
|
|
|
|
void
|
|
sockdup(struct socket *s)
|
|
{
|
|
acquire(&stab.lock);
|
|
s->ref++;
|
|
release(&stab.lock);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Socket options
|
|
//
|
|
// Only the options that are genuinely honoured appear here; everything else is
|
|
// answered ENOPROTOOPT by the switch's default. That is deliberate and it is a
|
|
// change of policy: both calls used to succeed unconditionally, which is the
|
|
// worst of the three possible answers. A caller told "no" adapts; a caller told
|
|
// "yes" falsely proceeds on the strength of it -- setting SO_RCVTIMEO and then
|
|
// blocking forever in a read it believes will time out.
|
|
//
|
|
// The flags are recorded here as well as pushed into lwIP so that getsockopt
|
|
// has one answer rather than two, and so that an AF_UNIX socket -- which has no
|
|
// pcb and for which none of these mean anything -- can still give back what was
|
|
// set, as POSIX requires.
|
|
int
|
|
sockopt_set(struct socket *s, int level, int name, int val)
|
|
{
|
|
int r = 0;
|
|
|
|
acquire(&stab.lock);
|
|
if (level == SOL_SOCKET) {
|
|
switch (name) {
|
|
case SO_REUSEADDR: s->o_reuseaddr = !!val; break;
|
|
case SO_KEEPALIVE: s->o_keepalive = !!val; break;
|
|
case SO_BROADCAST: s->o_broadcast = !!val; break;
|
|
// Accepted and left at the size we have. Not the same lie as before:
|
|
// Linux is explicitly free to clamp these, and sockopt_get below reports
|
|
// the real figure rather than the requested one.
|
|
case SO_SNDBUF:
|
|
case SO_RCVBUF: release(&stab.lock); return 0;
|
|
default: release(&stab.lock); return -ENOPROTOOPT;
|
|
}
|
|
} else if (level == IPPROTO_TCP && name == TCP_NODELAY) {
|
|
if (s->type != SOCK_STREAM) { release(&stab.lock); return -EINVAL; }
|
|
s->o_nodelay = !!val;
|
|
} else {
|
|
release(&stab.lock);
|
|
return -ENOPROTOOPT;
|
|
}
|
|
int inet = (s->domain == AF_INET);
|
|
void *h = s->inet;
|
|
release(&stab.lock);
|
|
|
|
// Outside the table lock: inet_setopt takes the lwIP lock, which is a
|
|
// sleeplock, and the same ordering rule the rest of this file follows.
|
|
if (inet && h)
|
|
r = inet_setopt(h, level, name, val);
|
|
return r;
|
|
}
|
|
|
|
int
|
|
sockopt_get(struct socket *s, int level, int name, int *out)
|
|
{
|
|
int r = 0;
|
|
|
|
acquire(&stab.lock);
|
|
if (level == IPPROTO_TCP && name == TCP_NODELAY) {
|
|
*out = s->o_nodelay;
|
|
} else if (level != SOL_SOCKET) {
|
|
r = -ENOPROTOOPT;
|
|
} else {
|
|
switch (name) {
|
|
case SO_TYPE: *out = s->type; break;
|
|
case SO_ACCEPTCONN: *out = (s->state == SS_LISTENING); break;
|
|
case SO_REUSEADDR: *out = s->o_reuseaddr; break;
|
|
case SO_KEEPALIVE: *out = s->o_keepalive; break;
|
|
case SO_BROADCAST: *out = s->o_broadcast; break;
|
|
// The real buffer, not a number somebody asked for.
|
|
case SO_SNDBUF:
|
|
case SO_RCVBUF: *out = SOCKBUF; break;
|
|
// An honest zero rather than the blanket one it replaces: connect() here
|
|
// is blocking and reports failure as its own return value, so there is no
|
|
// deferred error for SO_ERROR to carry. It becomes a real field the day a
|
|
// non-blocking connect exists.
|
|
case SO_ERROR: *out = 0; break;
|
|
default: r = -ENOPROTOOPT; break;
|
|
}
|
|
}
|
|
release(&stab.lock);
|
|
return r;
|
|
}
|
|
|
|
const char *
|
|
sockname(struct socket *s)
|
|
{
|
|
return s->name;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Descriptor passing
|
|
//
|
|
// docs/plan/hybrid.md mechanism 1, and item 1 of foundations.md. The kernel
|
|
// holds a reference on every descriptor in flight, which is what makes the
|
|
// sender free to close its own copy the instant sendmsg returns -- and what
|
|
// makes dropping a socket with descriptors still queued a thing that has to be
|
|
// handled rather than a thing that happens to work.
|
|
//
|
|
// **What this does not have is a garbage collector.** A descriptor for an
|
|
// AF_UNIX socket, sent over that same socket and never received, holds a
|
|
// reference to the socket that holds the reference to it; neither side's close
|
|
// can reach zero and the pair leaks until reboot. Linux carries several hundred
|
|
// lines to break exactly these cycles. Nothing on either plan passes a socket
|
|
// over a socket -- Wayland passes memory and the driver work passes devices --
|
|
// so this is a known, bounded leak (thirty-two sockets) and not a silent one.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Close every descriptor still queued on s.
|
|
//
|
|
// Called with the socket already unreachable but *not yet marked free*, and
|
|
// without stab.lock: fileclose can sleep. Popping one at a time keeps the
|
|
// kernel stack out of it -- a snapshot of every batch would be half a page.
|
|
static void
|
|
scm_drain(struct socket *s)
|
|
{
|
|
struct file *f;
|
|
int i, j;
|
|
|
|
for(;;){
|
|
f = 0;
|
|
acquire(&stab.lock);
|
|
for(i = 0; i < s->nscm && f == 0; i++)
|
|
for(j = 0; j < s->scm[i].n; j++)
|
|
if(s->scm[i].f[j]){
|
|
f = s->scm[i].f[j];
|
|
s->scm[i].f[j] = 0;
|
|
break;
|
|
}
|
|
if(f == 0){
|
|
s->nscm = 0;
|
|
release(&stab.lock);
|
|
return;
|
|
}
|
|
release(&stab.lock);
|
|
fileclose(f);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Data
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Read bytes, and the descriptors that came with them.
|
|
//
|
|
// `fout` is an array of SCM_MAXFD slots, or 0. **Zero is not "ignore them"**:
|
|
// a plain read(2) of a message carrying descriptors *closes* them, because the
|
|
// alternative is holding kernel references for a receiver that has already
|
|
// proved it is not looking. That is what Linux does and it is the only choice
|
|
// that cannot leak.
|
|
int
|
|
sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout)
|
|
{
|
|
struct file *taken[SCM_MAXFD];
|
|
int got = 0;
|
|
int i;
|
|
|
|
if(nfout)
|
|
*nfout = 0;
|
|
|
|
if(n < 0)
|
|
return -EINVAL;
|
|
if(s->domain == AF_INET)
|
|
return inet_read(s->inet, addr, n);
|
|
|
|
// Netlink never blocks: the answer was generated when the request was sent,
|
|
// so an empty buffer means the caller has already drained this dump rather
|
|
// than that more is coming. Blocking here would hang a getifaddrs() that
|
|
// asked one question too many.
|
|
if(s->domain == AF_NETLINK){
|
|
acquire(&stab.lock);
|
|
uint have = s->nwrite - s->nread;
|
|
if(have == 0){
|
|
release(&stab.lock);
|
|
return -EAGAIN;
|
|
}
|
|
if((uint)n > have)
|
|
n = (int)have;
|
|
memmove(addr, s->data + s->nread, (uint)n);
|
|
s->nread += (uint)n;
|
|
release(&stab.lock);
|
|
return n;
|
|
}
|
|
|
|
acquire(&stab.lock);
|
|
while(s->nread == s->nwrite && !s->peer_closed){
|
|
if(myproc()->killed || s->state == SS_FREE){
|
|
release(&stab.lock);
|
|
return -EINTR;
|
|
}
|
|
sleep(&s->nread, &stab.lock);
|
|
}
|
|
|
|
// A message carrying descriptors is a boundary. This read either starts
|
|
// exactly on one -- and takes them -- or is cut short so that it stops
|
|
// before it, leaving the next read to start there.
|
|
if(s->nscm > 0){
|
|
if(s->scm[0].pos == s->nread){
|
|
got = s->scm[0].n;
|
|
for(i = 0; i < got; i++)
|
|
taken[i] = s->scm[0].f[i];
|
|
for(i = 1; i < s->nscm; i++)
|
|
s->scm[i - 1] = s->scm[i];
|
|
s->nscm--;
|
|
}
|
|
// Positions strictly increase -- a message with descriptors carries at
|
|
// least one byte -- so after taking one the next is genuinely ahead.
|
|
if(s->nscm > 0 && (uint)n > s->scm[0].pos - s->nread)
|
|
n = (int)(s->scm[0].pos - s->nread);
|
|
}
|
|
|
|
for(i = 0; i < n; i++){
|
|
if(s->nread == s->nwrite)
|
|
break;
|
|
addr[i] = s->data[s->nread++ % SOCKBUF];
|
|
}
|
|
|
|
// Waking the writer matters even when nothing was read: it may be blocked
|
|
// on a full buffer that this call has just made room in.
|
|
wakeup(&s->nwrite);
|
|
release(&stab.lock);
|
|
|
|
// Outside the lock, because fileclose can sleep.
|
|
if(fout){
|
|
for(int j = 0; j < got; j++)
|
|
fout[j] = taken[j];
|
|
if(nfout)
|
|
*nfout = got;
|
|
} else {
|
|
for(int j = 0; j < got; j++)
|
|
fileclose(taken[j]);
|
|
}
|
|
return i;
|
|
}
|
|
|
|
int
|
|
sockread(struct socket *s, char *addr, int n)
|
|
{
|
|
return sockread_fds(s, addr, n, 0, 0);
|
|
}
|
|
|
|
// Write bytes, optionally handing `nf` descriptors over with them.
|
|
//
|
|
// The kernel takes its own reference on each before it gets here, so the
|
|
// sender may close its copies as soon as this returns. A return of *any*
|
|
// positive count means the descriptors were queued; a negative return means
|
|
// they were not, and are still the caller's to close. There is no third case,
|
|
// which is deliberate -- the attach happens under the same lock hold as the
|
|
// first byte, so no reader can slip between them.
|
|
int
|
|
sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
|
|
{
|
|
struct socket *dst;
|
|
int i;
|
|
|
|
if(n < 0)
|
|
return -EINVAL;
|
|
if(nf > 0 && (s->domain != AF_UNIX || n == 0))
|
|
return -EINVAL;
|
|
if(s->domain == AF_INET)
|
|
return inet_write(s->inet, addr, n);
|
|
|
|
// A netlink request is answered immediately, into this socket's own buffer
|
|
// rather than a peer's -- there is no peer. Doing the work here is what lets
|
|
// the read that follows never block, which matters because musl reads with
|
|
// MSG_DONTWAIT and treats a short read as the end of the dump.
|
|
if(s->domain == AF_NETLINK){
|
|
acquire(&stab.lock);
|
|
s->nread = s->nwrite = 0;
|
|
int got = netlink_request(addr, (uint)n, s->data, SOCKBUF);
|
|
if(got > 0)
|
|
s->nwrite = (uint)got;
|
|
release(&stab.lock);
|
|
return n; /* the request was consumed whole */
|
|
}
|
|
|
|
acquire(&stab.lock);
|
|
|
|
// Data goes into the peer's buffer, because that is where the peer will
|
|
// look for it. Same contract as a pipe with no reader: a signal, then an
|
|
// error. A process that writes to a dead connection and is not told about
|
|
// it spins forever believing it is making progress.
|
|
if(s->peer == 0){
|
|
release(&stab.lock);
|
|
sigsend(myproc()->pid, SIGPIPE);
|
|
return -EPIPE;
|
|
}
|
|
|
|
// Refuse early and cleanly rather than part-way through the bytes. A sender
|
|
// that is this far ahead of its receiver is not helped by a partial message.
|
|
if(nf > 0 && s->peer->nscm >= NSCM){
|
|
release(&stab.lock);
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
for(i = 0; i < n; i++){
|
|
// Re-read every iteration: the peer can go away while we sleep below.
|
|
dst = s->peer;
|
|
while(dst && dst->nwrite == dst->nread + SOCKBUF){
|
|
if(myproc()->killed){
|
|
release(&stab.lock);
|
|
return i > 0 ? i : -EINTR;
|
|
}
|
|
wakeup(&dst->nread);
|
|
sleep(&dst->nwrite, &stab.lock);
|
|
dst = s->peer;
|
|
}
|
|
if(dst == 0){
|
|
release(&stab.lock);
|
|
sigsend(myproc()->pid, SIGPIPE);
|
|
return i > 0 ? i : -EPIPE; // a short write is still a write
|
|
}
|
|
// The descriptors ride at the position of the first byte, pinned here --
|
|
// between the room check above and the store below, with the lock held
|
|
// throughout, so the position cannot be read past before it exists.
|
|
if(i == 0 && nf > 0){
|
|
if(dst->nscm >= NSCM){
|
|
release(&stab.lock);
|
|
return -ENOBUFS;
|
|
}
|
|
struct scm_fds *b = &dst->scm[dst->nscm++];
|
|
b->pos = dst->nwrite;
|
|
b->n = nf;
|
|
for(int j = 0; j < nf; j++)
|
|
b->f[j] = f[j];
|
|
}
|
|
dst->data[dst->nwrite++ % SOCKBUF] = addr[i];
|
|
}
|
|
|
|
wakeup(&s->peer->nread);
|
|
release(&stab.lock);
|
|
return i;
|
|
}
|
|
|
|
int
|
|
sockwrite(struct socket *s, char *addr, int n)
|
|
{
|
|
return sockwrite_fds(s, addr, n, 0, 0);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Connection setup
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct socket *
|
|
socknew(int domain, int type, int proto)
|
|
{
|
|
struct socket *s;
|
|
|
|
if(domain != AF_UNIX && domain != AF_INET && domain != AF_NETLINK)
|
|
return 0;
|
|
// AF_UNIX is stream-only here; AF_INET has datagrams too, and inet_new is
|
|
// what decides whether it can serve the type asked for.
|
|
if(domain == AF_UNIX && type != SOCK_STREAM)
|
|
return 0;
|
|
// AF_NETLINK carries whole messages and musl asks for SOCK_RAW; SOCK_DGRAM is
|
|
// accepted too because Linux does. NETLINK_ROUTE is the only protocol served
|
|
// -- see kernel/net/netlink.c, which answers RTM_GETLINK and RTM_GETADDR and
|
|
// nothing else.
|
|
if(domain == AF_NETLINK){
|
|
if(type != SOCK_RAW && type != SOCK_DGRAM)
|
|
return 0;
|
|
if(proto != NETLINK_ROUTE)
|
|
return 0;
|
|
}
|
|
|
|
acquire(&stab.lock);
|
|
s = alloc_locked(type);
|
|
if(s)
|
|
s->domain = domain;
|
|
release(&stab.lock);
|
|
if(s == 0)
|
|
return 0;
|
|
|
|
if(domain == AF_INET){
|
|
// Outside the table lock: inet_new takes the lwIP lock, which is a
|
|
// sleeplock, and sleeping while holding a spinlock is a panic.
|
|
s->inet = inet_new(type, proto);
|
|
if(s->inet == 0){
|
|
sockclose(s);
|
|
return 0;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// A connected pair with no name and no listener, for socketpair(2).
|
|
int
|
|
sockpair(struct socket **pa, struct socket **pb)
|
|
{
|
|
struct socket *a, *b;
|
|
|
|
acquire(&stab.lock);
|
|
if((a = alloc_locked(SOCK_STREAM)) == 0){
|
|
release(&stab.lock);
|
|
return -ENFILE;
|
|
}
|
|
if((b = alloc_locked(SOCK_STREAM)) == 0){
|
|
a->state = SS_FREE;
|
|
a->ref = 0;
|
|
release(&stab.lock);
|
|
return -ENFILE;
|
|
}
|
|
// The domain was never set here, and nothing noticed for as long as every
|
|
// data path asked only whether it was AF_INET or AF_NETLINK -- a socketpair
|
|
// socket answered "neither" by being zero. SCM_RIGHTS asks the other
|
|
// question, "is this AF_UNIX", and zero is the wrong answer to that one.
|
|
a->domain = AF_UNIX;
|
|
b->domain = AF_UNIX;
|
|
a->peer = b; a->state = SS_CONNECTED;
|
|
b->peer = a; b->state = SS_CONNECTED;
|
|
release(&stab.lock);
|
|
|
|
*pa = a;
|
|
*pb = b;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
sockbind(struct socket *s, const char *path)
|
|
{
|
|
struct socket *other;
|
|
|
|
if(path[0] == '\0')
|
|
return -EINVAL;
|
|
|
|
acquire(&stab.lock);
|
|
if(s->state != SS_UNBOUND){
|
|
release(&stab.lock);
|
|
return -EINVAL;
|
|
}
|
|
for(other = stab.sock; other < stab.sock + NSOCK; other++){
|
|
if(other != s && other->state >= SS_BOUND &&
|
|
strncmp(other->name, path, UNIX_PATH_MAX) == 0){
|
|
release(&stab.lock);
|
|
return -EADDRINUSE;
|
|
}
|
|
}
|
|
safestrcpy(s->name, path, UNIX_PATH_MAX);
|
|
s->state = SS_BOUND;
|
|
release(&stab.lock);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
socklisten(struct socket *s, int backlog)
|
|
{
|
|
if(s->domain == AF_INET)
|
|
return inet_listen(s->inet, backlog);
|
|
|
|
(void)backlog; // the queue is a fixed NBACKLOG either way
|
|
|
|
acquire(&stab.lock);
|
|
if(s->state != SS_BOUND && s->state != SS_LISTENING){
|
|
release(&stab.lock);
|
|
return -EINVAL;
|
|
}
|
|
s->state = SS_LISTENING;
|
|
release(&stab.lock);
|
|
return 0;
|
|
}
|
|
|
|
// Returns the server-side socket of a new connection with a reference already
|
|
// held, or 0 on error.
|
|
struct socket *
|
|
sockaccept(struct socket *s)
|
|
{
|
|
struct socket *c;
|
|
int i;
|
|
|
|
if(s->domain == AF_INET){
|
|
// The connection lwIP hands back needs a table entry of its own, because
|
|
// everything above this layer works in `struct socket` and knows nothing
|
|
// about lwIP.
|
|
void *h = inet_accept(s->inet);
|
|
if(h == 0)
|
|
return 0;
|
|
acquire(&stab.lock);
|
|
c = alloc_locked(s->type);
|
|
if(c){
|
|
c->domain = AF_INET;
|
|
c->state = SS_CONNECTED;
|
|
}
|
|
release(&stab.lock);
|
|
if(c == 0){
|
|
inet_close(h);
|
|
return 0;
|
|
}
|
|
c->inet = h;
|
|
return c;
|
|
}
|
|
|
|
acquire(&stab.lock);
|
|
if(s->state != SS_LISTENING){
|
|
release(&stab.lock);
|
|
return 0;
|
|
}
|
|
|
|
while(s->nqueue == 0){
|
|
if(myproc()->killed || s->state != SS_LISTENING){
|
|
release(&stab.lock);
|
|
return 0;
|
|
}
|
|
sleep(&s->nqueue, &stab.lock);
|
|
}
|
|
|
|
c = s->queue[0];
|
|
for(i = 0; i + 1 < s->nqueue; i++)
|
|
s->queue[i] = s->queue[i+1];
|
|
s->nqueue--;
|
|
release(&stab.lock);
|
|
|
|
return c;
|
|
}
|
|
|
|
int
|
|
sockconnect(struct socket *s, const char *path)
|
|
{
|
|
struct socket *srv, *peer;
|
|
|
|
acquire(&stab.lock);
|
|
if(s->state != SS_UNBOUND && s->state != SS_BOUND){
|
|
release(&stab.lock);
|
|
return -EINVAL;
|
|
}
|
|
srv = find_listener(path);
|
|
if(srv == 0){
|
|
release(&stab.lock);
|
|
return -ECONNREFUSED;
|
|
}
|
|
if(srv->nqueue >= NBACKLOG){
|
|
release(&stab.lock);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
// Every connection needs a socket at each end. The listener is not one of
|
|
// them -- it stays listening, and accept() hands back this new one.
|
|
peer = alloc_locked(s->type);
|
|
if(peer == 0){
|
|
release(&stab.lock);
|
|
return -ENFILE;
|
|
}
|
|
|
|
peer->peer = s;
|
|
peer->state = SS_CONNECTED;
|
|
s->peer = peer;
|
|
s->state = SS_CONNECTED;
|
|
|
|
srv->queue[srv->nqueue++] = peer;
|
|
wakeup(&srv->nqueue); // an accept() may be blocked on this
|
|
|
|
release(&stab.lock);
|
|
|
|
// Deliberately does *not* wait for accept(). On Unix a queued connection is
|
|
// already established: the client may write, and the bytes sit in the
|
|
// server-side buffer until somebody accepts and reads them. Blocking here
|
|
// instead would deadlock any server that connects to itself, and would make
|
|
// a busy server's backlog useless.
|
|
return 0;
|
|
}
|
|
|
|
// shutdown(2). There is one buffer per direction and no per-direction flag,
|
|
// so a half-close is approximated: SHUT_RD stops this end reading anything
|
|
// further, and SHUT_WR/SHUT_RDWR give the peer end-of-file. What programs
|
|
// actually use shutdown for -- "I have finished sending, read until EOF and
|
|
// then reply" -- works; what is missing is the ability to keep reading after
|
|
// SHUT_WR, since the connection is torn down rather than half-closed.
|
|
int
|
|
sock_shutdown(struct socket *s, int how)
|
|
{
|
|
acquire(&stab.lock);
|
|
if(s->state != SS_CONNECTED){
|
|
release(&stab.lock);
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
if(how == SHUT_RD || how == SHUT_RDWR){
|
|
s->peer_closed = 1;
|
|
wakeup(&s->nread);
|
|
}
|
|
if(how == SHUT_WR || how == SHUT_RDWR)
|
|
orphan_peer(s);
|
|
release(&stab.lock);
|
|
return 0;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Readiness, for poll/select
|
|
// ---------------------------------------------------------------------------
|
|
|
|
int
|
|
sockreadable(struct socket *s)
|
|
{
|
|
if(s->domain == AF_INET)
|
|
return inet_readable(s->inet);
|
|
if(s->state == SS_LISTENING)
|
|
return s->nqueue > 0;
|
|
return s->nread != s->nwrite || s->peer_closed;
|
|
}
|
|
|
|
// The peer is gone and everything it sent has been read.
|
|
int
|
|
sock_hup(struct socket *s)
|
|
{
|
|
if(s->domain == AF_INET)
|
|
return inet_hup(s->inet);
|
|
|
|
if(s->state == SS_LISTENING)
|
|
return 0;
|
|
return s->peer == 0 && s->nread == s->nwrite;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* AF_INET addressing */
|
|
/* */
|
|
/* Separate entry points rather than an overloaded sockbind, because */
|
|
/* the two families do not name the same kind of thing: one takes a */
|
|
/* path, the other an address and a port. The syscall layer picks by */
|
|
/* the family in the sockaddr the caller passed. */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
int
|
|
sockbind_in(struct socket *s, uint addr, ushort port)
|
|
{
|
|
if(s->domain != AF_INET)
|
|
return -EAFNOSUPPORT;
|
|
|
|
int r = inet_bind(s->inet, addr, port);
|
|
if(r == 0){
|
|
acquire(&stab.lock);
|
|
s->state = SS_BOUND;
|
|
release(&stab.lock);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int
|
|
sockconnect_in(struct socket *s, uint addr, ushort port)
|
|
{
|
|
if(s->domain != AF_INET)
|
|
return -EAFNOSUPPORT;
|
|
|
|
int r = inet_connect(s->inet, addr, port);
|
|
if(r == 0){
|
|
acquire(&stab.lock);
|
|
s->state = SS_CONNECTED;
|
|
release(&stab.lock);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// Which family a socket belongs to. An accessor because kernel/sys/sys_net.c is
|
|
// the translation layer and deliberately does not see inside struct socket --
|
|
// it works in descriptors and user addresses, not in the socket's guts.
|
|
int
|
|
sock_domain(struct socket *s)
|
|
{
|
|
return s ? s->domain : 0;
|
|
}
|
|
|
|
// recvfrom and sendto, which differ from read and write only in carrying an
|
|
// address. Both live here rather than in sys_net.c for the same reason as
|
|
// sock_domain: the syscall layer must not know which side of the socket union
|
|
// is live.
|
|
//
|
|
// `port` comes back in host byte order, matching what sockbind_in and
|
|
// sockconnect_in take. The syscall layer converts on the way out, in the one
|
|
// place that also builds the sockaddr.
|
|
int
|
|
sockrecvfrom(struct socket *s, char *buf, int n, uint *addr, ushort *port)
|
|
{
|
|
if(n < 0)
|
|
return -EINVAL;
|
|
if(s->domain == AF_INET)
|
|
return inet_recvfrom(s->inet, buf, n, addr, port);
|
|
|
|
// A Unix socket has no address of this shape. Saying so by leaving the
|
|
// caller's address untouched, rather than inventing one, is what lets
|
|
// recvfrom report an address length of zero -- which is exactly what a
|
|
// program reading from an unnamed peer expects.
|
|
return sockread(s, buf, n);
|
|
}
|
|
|
|
int
|
|
socksendto(struct socket *s, char *buf, int n, uint addr, ushort port)
|
|
{
|
|
if(n < 0)
|
|
return -EINVAL;
|
|
if(s->domain == AF_INET)
|
|
return inet_sendto(s->inet, buf, n, addr, port);
|
|
if(addr)
|
|
return -EAFNOSUPPORT; // an AF_INET destination on an AF_UNIX socket
|
|
return sockwrite(s, buf, n);
|
|
}
|