SchOS/include/net/socket.h
Nexoniarz 54b25708c3 Demand paging, the RTL8168H, links that come up late, and Ctrl+Alt+F1..F12
- Anonymous memory can be reserved without being backed: large mappings,
  MAP_NORESERVE and PROT_NONE are recorded and each page appears, zeroed,
  on first touch -- from trap() for the program, from uva2ka() for the
  kernel on its behalf. WebKit reserves 4 GB at start and touches a sliver;
  backing it all failed with ENOMEM and Epiphany aborted.
- r8169: the 8168G generation and later (the laptop's 8168H, XID 541) are
  taken from the firmware's out-of-band mode, get their PLL powered, the
  receive gate opened, and their PHY reached through the OCP window. The
  PHY's power-down bit is cleared; the old code kept it while restarting
  autonegotiation. Untested on the hardware so far.
- The network thread follows each device's link into lwIP. The link was
  read once at attach, 0.02 s into boot; real autonegotiation takes
  seconds, so on hardware the interface stayed down for good.
- Twelve terminals, switched with Ctrl+Alt+F1..F12 by default: the keys X
  uses, so the console and the desktop agree. AltGr+F-key is gone from
  the defaults (chvt -c altgr+F brings it back).
- SO_PASSCRED on Unix sockets, with SCM_CREDENTIALS delivered by recvmsg.
  WebKit sets it on its IPC sockets and aborted when refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 11:27:48 +01:00

367 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;
int o_passcred; // SO_PASSCRED: SCM_CREDENTIALS on every recvmsg
// 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;
// Who is at this end, for SO_PEERCRED on the other: the process that made
// the socket, or for the server's end of a connection, the one that made
// the listener -- which is what Linux records too.
int cred_pid;
uint cred_uid, cred_gid;
};
#endif