busybox wget hands TLS to ssl_client over a socketpair and then does shutdown(SHUT_WR). That cut the AF_UNIX link both ways, so ssl_client's reply was a SIGPIPE and every https:// download hung. SHUT_WR is now a half-close: the peer reads EOF and can still answer. TCP sockets get shutdown() at all (tcp_shutdown for the FIN); it used to do nothing. - exec keeps ignored signals and the signal mask, as execve(2) says. - /proc/<pid>/fd: a link per descriptor -- the path it was opened by, socket:[n], pipe:[n], anon_inode:[eventpoll]. /proc/self too. The global inode bit moves above pid << 8; at 0x10000 it was /proc/256. - fstat on a socket or pipe answers S_IFSOCK / S_IFIFO instead of EINVAL. - poll, select and epoll wake as soon as a pipe, socket or input device changes, not on the next tick. - Pipes are 64 KB of lazily allocated pages, copied in blocks, with PIPE_BUF atomicity: 300 MB through `yes | head | wc` in 7 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
360 lines
13 KiB
C
360 lines
13 KiB
C
#pragma once
|
|
|
|
#include "core/types.h"
|
|
|
|
// Unix domain sockets.
|
|
//
|
|
// These exist for Schcom. A compositor and its clients have to exchange
|
|
// messages, and a pipe cannot do it: a pipe is one-directional, has no
|
|
// notion of a connection, and gives no way for a server to be *found* by a
|
|
// client that starts later. A socket with a name in the filesystem is
|
|
// exactly the missing piece.
|
|
//
|
|
// AF_INET arrived later, and exactly as planned: underneath the same syscalls
|
|
// rather than reshaping them. A socket carries its domain and every entry point
|
|
// dispatches on it -- kernel/net/unix.c for AF_UNIX, kernel/net/inet.c (lwIP)
|
|
// for AF_INET.
|
|
|
|
#define AF_UNIX 1
|
|
#define AF_LOCAL 1
|
|
#define AF_INET 2
|
|
// Linux's number, because a musl program passes Linux's constant. getifaddrs()
|
|
// is implemented over this, which is why it exists here: enumerating interfaces
|
|
// is something every networked program does and nothing else offers.
|
|
#define AF_NETLINK 16
|
|
#define PF_NETLINK AF_NETLINK
|
|
#define NETLINK_ROUTE 0
|
|
|
|
#define SOCK_STREAM 1
|
|
#define SOCK_DGRAM 2
|
|
#define SOCK_RAW 3
|
|
|
|
// Flags ORed into socket()'s and accept4()'s `type`/`flags` argument. Linux's
|
|
// values, because a musl program passes Linux's constants.
|
|
#define SOCK_NONBLOCK 0x800 // O_NONBLOCK
|
|
#define SOCK_CLOEXEC 0x80000 // O_CLOEXEC
|
|
|
|
// setsockopt/getsockopt levels and names, Linux's numbering.
|
|
//
|
|
// Only what is listed here is accepted; anything else is answered with
|
|
// ENOPROTOOPT rather than a success it did not earn. See the comment above
|
|
// sys_setsockopt for why that is worth the risk of failing a caller that never
|
|
// checked before.
|
|
#define SOL_SOCKET 1
|
|
#define IPPROTO_ICMP 1
|
|
#define IPPROTO_TCP 6
|
|
#define IPPROTO_UDP 17
|
|
|
|
#define SO_REUSEADDR 2
|
|
#define SO_TYPE 3
|
|
#define SO_ERROR 4
|
|
#define SO_BROADCAST 6
|
|
#define SO_SNDBUF 7
|
|
#define SO_RCVBUF 8
|
|
#define SO_KEEPALIVE 9
|
|
#define SO_ACCEPTCONN 30
|
|
|
|
#define TCP_NODELAY 1
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Ancillary data -- sendmsg/recvmsg, and SCM_RIGHTS
|
|
//
|
|
// A descriptor is the only thing that can be passed between two processes that
|
|
// is not a number: it names an object the kernel already knows, and handing it
|
|
// over is the difference between "here is a buffer" and "here is a *copy* of a
|
|
// buffer". Wayland's wl_shm_create_pool passes one and has no integer-shaped
|
|
// alternative; docs/plan/hybrid.md's mechanism 1 is this and nothing else.
|
|
//
|
|
// Every structure below is Linux's, byte for byte, because a musl-linked
|
|
// program builds them with Linux's headers and we do not get a vote. The
|
|
// offsets that matter on x86-64 are, for msghdr:
|
|
//
|
|
// 0 msg_name 8 msg_namelen(+4 pad) 16 msg_iov 24 msg_iovlen
|
|
// 32 msg_control 40 msg_controllen 48 msg_flags
|
|
//
|
|
// -- 56 bytes total, which is what musl's int-typed msg_iovlen plus a pad word
|
|
// adds up to as well.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#define MSG_OOB 0x0001
|
|
#define MSG_PEEK 0x0002
|
|
#define MSG_DONTROUTE 0x0004
|
|
#define MSG_CTRUNC 0x0008 // some ancillary data was dropped
|
|
#define MSG_TRUNC 0x0020
|
|
#define MSG_DONTWAIT 0x0040
|
|
#define MSG_EOR 0x0080
|
|
#define MSG_WAITALL 0x0100
|
|
#define MSG_NOSIGNAL 0x4000
|
|
#define MSG_CMSG_CLOEXEC 0x40000000
|
|
|
|
// The only control message that exists here. SCM_CREDENTIALS is deliberately
|
|
// absent rather than stubbed: a caller told it succeeded would read a uid out
|
|
// of uninitialised memory and act on it.
|
|
#define SCM_RIGHTS 1
|
|
|
|
// The most descriptors one message may carry. A cap has to exist -- the kernel
|
|
// holds a reference to every one of them until it is received -- and saying so
|
|
// here means a sender can size its control buffer against the same number the
|
|
// kernel will enforce.
|
|
#define SCM_MAXFD 8
|
|
|
|
// struct iovec is declared in two headers on purpose, and guarded so that
|
|
// including both is not an error.
|
|
//
|
|
// posix.h has it because readv/writev need it; this file has it because a
|
|
// userspace program including <socket.h> for sendmsg must get a complete
|
|
// struct msghdr without also having to know about a kernel header. Both
|
|
// definitions are identical and both are Linux's layout.
|
|
//
|
|
// The guard is not cosmetic. C23 permits redeclaring a struct with identical
|
|
// members and GCC 15 defaults to C23, so the duplicate was invisible on the
|
|
// gcc path -- while clang defaults to gnu17 and rejected it, which broke the
|
|
// one toolchain shell.nix exists to provide and which the UEFI loader needs.
|
|
#ifndef SCH_IOVEC_DEFINED
|
|
#define SCH_IOVEC_DEFINED
|
|
struct iovec {
|
|
void *iov_base;
|
|
uint64 iov_len;
|
|
};
|
|
#endif
|
|
|
|
struct msghdr {
|
|
void *msg_name; // struct sockaddr *, or 0
|
|
uint32 msg_namelen;
|
|
uint32 __pad1;
|
|
struct iovec *msg_iov;
|
|
uint64 msg_iovlen;
|
|
void *msg_control; // struct cmsghdr *, or 0
|
|
uint64 msg_controllen;
|
|
int msg_flags; // out on recvmsg: MSG_CTRUNC
|
|
};
|
|
|
|
struct cmsghdr {
|
|
uint64 cmsg_len; // CMSG_LEN(payload), NOT including padding
|
|
int cmsg_level; // SOL_SOCKET
|
|
int cmsg_type; // SCM_RIGHTS
|
|
};
|
|
|
|
// cmsg_len counts the header plus the payload; CMSG_SPACE additionally counts
|
|
// the padding to the next header. Conflating the two is the classic way to
|
|
// build a control buffer the kernel then rejects, which is why both exist.
|
|
#define CMSG_ALIGN(n) (((uint64)(n) + sizeof(uint64) - 1) & ~(uint64)(sizeof(uint64) - 1))
|
|
#define CMSG_LEN(n) (CMSG_ALIGN(sizeof(struct cmsghdr)) + (uint64)(n))
|
|
#define CMSG_SPACE(n) (CMSG_ALIGN(sizeof(struct cmsghdr)) + CMSG_ALIGN(n))
|
|
#define CMSG_DATA(c) ((unsigned char *)((struct cmsghdr *)(c) + 1))
|
|
|
|
#define CMSG_FIRSTHDR(m) \
|
|
((m)->msg_control && (m)->msg_controllen >= sizeof(struct cmsghdr) \
|
|
? (struct cmsghdr *)(m)->msg_control \
|
|
: (struct cmsghdr *)0)
|
|
|
|
#define CMSG_NXTHDR(m, c) \
|
|
((c)->cmsg_len < sizeof(struct cmsghdr) || \
|
|
(uint64)((char *)(c) + CMSG_ALIGN((c)->cmsg_len) + sizeof(struct cmsghdr) \
|
|
- (char *)(m)->msg_control) > (m)->msg_controllen \
|
|
? (struct cmsghdr *)0 \
|
|
: (struct cmsghdr *)((char *)(c) + CMSG_ALIGN((c)->cmsg_len)))
|
|
|
|
#define UNIX_PATH_MAX 108
|
|
|
|
#define SHUT_RD 0
|
|
#define SHUT_WR 1
|
|
#define SHUT_RDWR 2
|
|
|
|
struct sockaddr_un {
|
|
uint16 sun_family;
|
|
char sun_path[UNIX_PATH_MAX];
|
|
};
|
|
|
|
// The generic form syscalls take. Same shape as Linux's, so a musl-linked
|
|
// program's struct lines up byte for byte.
|
|
struct sockaddr {
|
|
uint16 sa_family;
|
|
char sa_data[UNIX_PATH_MAX];
|
|
};
|
|
|
|
// The AF_INET address. Same layout as Linux's, so a musl-linked program's
|
|
// struct lines up byte for byte. sin_port and sin_addr are network byte order
|
|
// and stay that way: lwIP wants them so, and converting on the way in and out
|
|
// again would be two swaps that cancel.
|
|
struct sockaddr_in {
|
|
uint16 sin_family;
|
|
uint16 sin_port;
|
|
uint32 sin_addr;
|
|
char sin_zero[8];
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// rtnetlink, enough of it for getifaddrs(3)
|
|
//
|
|
// Every structure below is Linux's, byte for byte, because musl parses what it
|
|
// is handed with Linux's own headers. They are not ours to design.
|
|
//
|
|
// What musl actually does (src/network/netlink.c) is the whole specification
|
|
// this has to meet: open PF_NETLINK/SOCK_RAW/NETLINK_ROUTE, send an
|
|
// nlmsghdr+rtgenmsg asking RTM_GETLINK with NLM_F_DUMP, read replies until
|
|
// NLMSG_DONE, then do it again for RTM_GETADDR on the same socket.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct nlmsghdr {
|
|
uint32 nlmsg_len;
|
|
uint16 nlmsg_type;
|
|
uint16 nlmsg_flags;
|
|
uint32 nlmsg_seq;
|
|
uint32 nlmsg_pid;
|
|
};
|
|
|
|
struct rtgenmsg { uchar rtgen_family; };
|
|
|
|
struct ifinfomsg {
|
|
uchar ifi_family;
|
|
uchar __ifi_pad;
|
|
uint16 ifi_type; // ARPHRD_*
|
|
int32 ifi_index;
|
|
uint32 ifi_flags; // IFF_*
|
|
uint32 ifi_change;
|
|
};
|
|
|
|
struct ifaddrmsg {
|
|
uchar ifa_family;
|
|
uchar ifa_prefixlen;
|
|
uchar ifa_flags;
|
|
uchar ifa_scope;
|
|
uint32 ifa_index;
|
|
};
|
|
|
|
struct rtattr {
|
|
uint16 rta_len;
|
|
uint16 rta_type;
|
|
};
|
|
|
|
#define NLMSG_ERROR 2
|
|
#define NLMSG_DONE 3
|
|
#define NLM_F_REQUEST 0x001
|
|
#define NLM_F_MULTI 0x002
|
|
#define NLM_F_DUMP 0x300
|
|
|
|
#define RTM_NEWLINK 16
|
|
#define RTM_GETLINK 18
|
|
#define RTM_NEWADDR 20
|
|
#define RTM_GETADDR 22
|
|
|
|
#define IFLA_ADDRESS 1
|
|
#define IFLA_IFNAME 3
|
|
#define IFA_ADDRESS 1
|
|
#define IFA_LOCAL 2
|
|
#define IFA_LABEL 3
|
|
|
|
#define ARPHRD_ETHER 1
|
|
#define ARPHRD_LOOPBACK 772
|
|
|
|
#define IFF_UP 0x1
|
|
#define IFF_LOOPBACK 0x8
|
|
#define IFF_RUNNING 0x40
|
|
#define IFF_BROADCAST 0x2
|
|
#define IFF_MULTICAST 0x1000
|
|
|
|
// Both are four-byte aligned on every architecture Linux supports.
|
|
#define NLMSG_ALIGN(n) (((n) + 3) & ~3)
|
|
#define RTA_ALIGN(n) (((n) + 3) & ~3)
|
|
|
|
#ifdef _KERNEL_SOCKET_INTERNALS
|
|
|
|
#define NSOCK 128 // sockets in the system, 4.9 KB each
|
|
#define SOCKBUF 4096 // receive buffer per connected socket
|
|
#define NBACKLOG 8
|
|
|
|
// Descriptors in flight.
|
|
//
|
|
// A batch is pinned to the stream position of the first byte of the message
|
|
// that carried it, and that is the whole delivery rule: a reader that arrives
|
|
// at that position gets the descriptors, and a reader that has not reached it
|
|
// yet is stopped *short* of it rather than allowed to read past. Without the
|
|
// second half a program that read 4096 bytes in one call would swallow three
|
|
// messages and be handed one message's descriptors, with no way to tell which.
|
|
//
|
|
// Eight batches in flight is not a guess: a sender that has eight unreceived
|
|
// messages carrying descriptors and keeps going is not going to be rescued by
|
|
// a ninth slot, and ENOBUFS says so where a larger array would only move the
|
|
// same failure further away.
|
|
#define NSCM 8 // messages carrying descriptors, in flight
|
|
|
|
struct file;
|
|
|
|
struct scm_fds {
|
|
uint pos; // stream position of the message's first byte
|
|
int n;
|
|
struct file *f[SCM_MAXFD]; // references held by the kernel, not by anyone
|
|
};
|
|
|
|
enum sockstate {
|
|
SS_FREE = 0,
|
|
SS_UNBOUND,
|
|
SS_BOUND,
|
|
SS_LISTENING,
|
|
SS_CONNECTED,
|
|
};
|
|
|
|
// Every field here is protected by the socket table's single lock in
|
|
// kernel/net/unix.c -- there is deliberately no per-socket lock; see the
|
|
// comment at the top of that file for why.
|
|
struct socket {
|
|
int ref;
|
|
int type;
|
|
int state;
|
|
int domain; // AF_UNIX or AF_INET
|
|
|
|
// For AF_INET: the lwIP-backed socket in kernel/net/inet.c. Opaque here so
|
|
// that socket.h does not have to drag lwIP's headers into every file that
|
|
// wants a struct socket.
|
|
void *inet;
|
|
|
|
char name[UNIX_PATH_MAX]; // the bound path, for a listener and getsockname
|
|
// The filesystem node bind() created, by identity rather than by name.
|
|
//
|
|
// Matching on the *path string* was what made these sockets invisible to the
|
|
// filesystem: two spellings of one path missed each other, a relative path
|
|
// never matched an absolute one, and there was nothing to stat() or
|
|
// unlink(). An inode is the thing a path resolves *to*, so connect() can
|
|
// walk the path however it likes and still arrive at the same socket.
|
|
//
|
|
// bind_dev 0 means "no node": a socket bound before this existed, or one
|
|
// whose file has been unlinked. The socket stays bound and stays usable by
|
|
// anyone already connected; it is simply no longer findable, which is
|
|
// exactly what Linux does.
|
|
uint bind_dev;
|
|
uint64 bind_ino;
|
|
|
|
// A connected socket's peer. Writing to a socket appends to the *peer's*
|
|
// buffer, which is what makes one buffer per socket enough for a
|
|
// bidirectional connection.
|
|
struct socket *peer;
|
|
int peer_closed;
|
|
// shutdown(SHUT_WR): this end has said all it will. The peer reads EOF but
|
|
// can still answer, which is what a request/response helper depends on.
|
|
int wr_shut;
|
|
|
|
char data[SOCKBUF];
|
|
uint nread, nwrite;
|
|
|
|
// Connections waiting to be accepted, on a listener.
|
|
struct socket *queue[NBACKLOG];
|
|
int nqueue;
|
|
|
|
// Socket options that are genuinely honoured. Kept here rather than only in
|
|
// lwIP's pcb because getsockopt has to answer for an AF_UNIX socket too, and
|
|
// because a flag set before bind() has to survive until there is a pcb to
|
|
// apply it to.
|
|
int o_reuseaddr;
|
|
int o_keepalive;
|
|
int o_broadcast;
|
|
int o_nodelay;
|
|
|
|
// Descriptors sent to this socket and not yet received. They live on the
|
|
// *receiving* side, next to the bytes they were sent with, because that is
|
|
// the only side that can say when they have been delivered.
|
|
struct scm_fds scm[NSCM];
|
|
int nscm;
|
|
};
|
|
|
|
#endif
|