Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
943 lines
28 KiB
C
943 lines
28 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 "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "arch/mmu.h"
|
|
#include "core/proc.h"
|
|
#include "fs/fs.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/file.h"
|
|
#include "uapi/posix.h"
|
|
#include "uapi/errno.h"
|
|
|
|
#define _KERNEL_SOCKET_INTERNALS
|
|
#include "net/socket.h"
|
|
#include "core/sbuf.h"
|
|
#include "core/dbg.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);
|
|
|
|
// The socket table.
|
|
//
|
|
// Two columns here are the ones worth having: `buf`, because a peer that has
|
|
// stopped reading is invisible until its buffer is full and the writer blocks;
|
|
// and `scm`, because a descriptor sent over a socket and never received holds
|
|
// a kernel reference open, and there is no garbage collector for it -- see
|
|
// "What it does not do" in docs/architecture/networking.md.
|
|
static void
|
|
sock_dump(struct sbuf *out, const char *arg)
|
|
{
|
|
(void)arg;
|
|
struct socket *s;
|
|
int used = 0;
|
|
|
|
sb_puts(out, " slot dom state ref buf peer scm name\n");
|
|
|
|
acquire(&stab.lock);
|
|
for (s = stab.sock; s < stab.sock + NSOCK; s++) {
|
|
if (s->state == SS_FREE && s->ref == 0)
|
|
continue;
|
|
used++;
|
|
sb_putu_pad(out, (int)(s - stab.sock), 6);
|
|
sb_puts(out, " ");
|
|
sb_pad(out, s->domain == AF_UNIX ? "unix" :
|
|
s->domain == AF_INET ? "inet" :
|
|
s->domain == AF_NETLINK ? "nl" : "?", 7);
|
|
sb_pad(out, s->state == SS_FREE ? "free" :
|
|
s->state == SS_UNBOUND ? "unbound" :
|
|
s->state == SS_BOUND ? "bound" :
|
|
s->state == SS_LISTENING ? "listening" :
|
|
s->state == SS_CONNECTED ? "connected" : "?", 11);
|
|
sb_putu_pad(out, s->ref, 3);
|
|
sb_putu_pad(out, s->nwrite - s->nread, 7);
|
|
sb_puts(out, " ");
|
|
if (s->peer)
|
|
sb_putu_pad(out, (int)(s->peer - stab.sock), 4);
|
|
else
|
|
sb_puts(out, s->peer_closed ? "gone" : " -");
|
|
sb_putu_pad(out, s->nscm, 5);
|
|
sb_puts(out, " ");
|
|
if (s->name[0])
|
|
sb_puts(out, s->name);
|
|
else if (s->nqueue)
|
|
sb_putu(out, s->nqueue);
|
|
sb_putc(out, '\n');
|
|
}
|
|
release(&stab.lock);
|
|
|
|
if (!used)
|
|
sb_puts(out, " (no sockets)\n");
|
|
sb_puts(out, "\n buf is bytes written and not yet read; scm is descriptors\n"
|
|
" sent and not yet received, which nothing reclaims.\n");
|
|
}
|
|
|
|
void
|
|
sockinit(void)
|
|
{
|
|
initlock(&stab.lock, "socktab");
|
|
dbg_register("sock", sock_dump,
|
|
"the socket table, with buffered bytes and descriptors in flight");
|
|
}
|
|
|
|
// 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_ino(uint dev, uint64 ino)
|
|
{
|
|
struct socket *s;
|
|
|
|
for(s = stab.sock; s < stab.sock + NSOCK; s++)
|
|
if(s->state == SS_LISTENING && s->bind_dev == dev && s->bind_ino == ino)
|
|
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;
|
|
}
|
|
|
|
// bind(2) for AF_UNIX. The caller has already created the filesystem node and
|
|
// hands over its identity; see sys_bind() in kernel/sys/sys_net.c for why the
|
|
// creation lives there and not here.
|
|
//
|
|
// The name used to live only in this table, so `bind()` created nothing --
|
|
// there was no file to stat(), no `s` in `ls -l`, and nothing to unlink. A
|
|
// Wayland client finds its compositor by bind()ing a real path, and
|
|
// libwayland locks the file beside it; a name table can express neither.
|
|
//
|
|
// **The node is a marker, not the binding.** It holds no data. What makes a
|
|
// socket findable is that connect() resolves the path to an inode and looks
|
|
// for the socket bound to *that inode*, so any spelling of the path arrives
|
|
// at the same place -- which a string comparison could never manage.
|
|
int
|
|
sockbind(struct socket *s, const char *path, uint dev, uint64 ino)
|
|
{
|
|
if(path[0] == '\0')
|
|
return -EINVAL;
|
|
|
|
acquire(&stab.lock);
|
|
if(s->state != SS_UNBOUND){
|
|
release(&stab.lock);
|
|
return -EINVAL;
|
|
}
|
|
safestrcpy(s->name, path, UNIX_PATH_MAX);
|
|
s->bind_dev = dev;
|
|
s->bind_ino = ino;
|
|
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;
|
|
}
|
|
|
|
// connect(2) for AF_UNIX. The listener is the socket bound to the inode the
|
|
// path resolved to -- the caller did the resolving, for the same reason
|
|
// sockbind() does not create the node.
|
|
int
|
|
sockconnect(struct socket *s, uint dev, uint64 ino)
|
|
{
|
|
struct socket *srv, *peer;
|
|
|
|
acquire(&stab.lock);
|
|
if(s->state != SS_UNBOUND && s->state != SS_BOUND){
|
|
release(&stab.lock);
|
|
return -EINVAL;
|
|
}
|
|
srv = find_listener_ino(dev, ino);
|
|
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);
|
|
}
|