SchOS/kernel/net/inet.c
Nexoniarz 26bca020b6 licence: SchOS is GPL-2.0-or-later, no longer Apache-2.0
Nexoniarz is the sole author and SchOS was never published under
Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text,
every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE,
README.md, docs/licensing.md and the site say so. projects/schfs-linux
follows the rest of the tree.

The GPL gives copyleft: a fork has to ship its source. It also lifts the
old "Linux drivers, never" rule, since GPL-2.0-only code combines with
GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still
has to pick one version, so docs/licensing.md now keeps Apache-2.0 and
GPLv3 code out of the kernel to keep Linux code possible there. BSD
4-clause code goes from "avoid" to "no", because the advertising clause
is GPL-incompatible.

LDLS still ships no .ko. That boundary is now project policy rather than
a legal requirement, and ldls.md, devices.md and licensing.md section 6
say so. refmod declares license=GPL, and ldls-check passes (27 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:45 +02:00

1006 lines
30 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// AF_INET sockets, on top of lwIP's raw API.
//
// The raw API rather than lwIP's own sockets layer, because lwIP's needs
// NO_SYS=0 -- threads, mailboxes and semaphores of its own -- and this kernel
// already has a socket layer with a shape of its own that AF_INET has to fit
// into rather than replace. See kernel/net/lwip/lwipopts.h.
//
// ---------------------------------------------------------------------------
// Locking, and why there is only one lock here
//
// Everything in an inet socket is protected by the lwIP lock and nothing else.
// That is not a shortcut: lwIP's callbacks (data arriving, a connection
// completing, a peer closing) run in the network thread with that lock held,
// and they are the only things that write to a socket's receive buffer. A
// syscall reading that buffer takes the same lock. There is no second lock
// because there is no second writer.
//
// ---------------------------------------------------------------------------
// Blocking, and how a waiter is woken
//
// A blocking call sleeps until an lwIP callback says something changed. It used
// to wake on every timer tick and look, which cost a wakeup per socket per
// 10 ms and answered up to 10 ms late even when the data was already there.
//
// The obstacle was a lock mismatch, and it is worth writing down because it is
// the reason this is not simply sleep(&socket, &lwip_lock): sleep() wants a
// spinlock, and the lwIP lock is a sleeplock -- it must be, because a call into
// lwIP can reach the driver and the driver can sleep. A waiter cannot hold the
// lwIP lock while it sleeps either, or the network thread could never deliver
// the very thing it is waiting for.
//
// So the wait is on a counter with a spinlock of its own:
//
// reader: lwip_lock; check condition; s = net_seq(); lwip_unlock;
// net_wait(s)
// waker: (in a callback, lwIP lock held) net_wake()
//
// No wakeup can be lost. `s` is read while the reader still holds the lwIP
// lock, so anything that changes the condition must do so after that -- and
// therefore bumps the counter after the read. net_wait re-checks the counter
// under its spinlock before sleeping, sees it has moved, and does not sleep.
//
// One counter and one channel for every inet socket, rather than one each. At
// sixteen sockets the cost of waking all of them is a handful of instructions
// in the ones with nothing to do, and a per-socket channel would have to be
// torn down carefully in close() against callbacks that can still arrive.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "proc.h"
#include "fs.h" /* NDIRECT, which file.h's struct inode needs */
#include "file.h"
#define _KERNEL_SOCKET_INTERNALS
#include "socket.h"
#include "errno.h"
#include "lwip/opt.h"
#include "lwip/tcp.h"
#include "lwip/udp.h"
#include "lwip/raw.h"
#include "lwip/icmp.h"
#include "lwip/prot/ip4.h"
#include "lwip/ip_addr.h"
#include "lwip/pbuf.h"
#define NINET 16 // AF_INET sockets in the system
#define INET_RXBUF 8192 // per socket; one lwIP window plus slack
#define NDGRAM 16 // datagrams queued on a UDP socket
// Where one datagram ends and the next begins, and who sent it.
//
// The bytes themselves stay in the same ring TCP uses -- a datagram queue of
// whole buffers would be 16 sockets x 16 datagrams x an MTU of static memory
// for something that is nearly always empty. Only the boundaries and the
// source need to be kept alongside, and those are eight bytes each.
struct dgram {
uint addr; // source, network byte order
ushort port; // source, host byte order
ushort len;
};
struct inet_sock {
int inuse;
int type; // SOCK_STREAM or SOCK_DGRAM
int proto; // 0, or IPPROTO_ICMP for a ping socket
struct tcp_pcb *tpcb;
struct udp_pcb *upcb;
struct raw_pcb *rpcb; // SOCK_DGRAM + IPPROTO_ICMP
// Receive ring. lwIP hands over pbufs from the network thread; a reader
// drains bytes. Full means the sender is told to back off, which for TCP is
// the window closing rather than data being dropped.
char rx[INET_RXBUF];
uint rhead, rtail; // rhead == rtail is empty
// UDP only: the boundaries within that ring. A stream socket has none --
// that is the whole difference between the two -- so these stay at zero.
struct dgram dq[NDGRAM];
uint dhead, dtail; // dhead == dtail is empty
int connecting; // connect() issued, no answer yet
int connected;
int conn_failed;
int peer_closed; // FIN seen, or the connection died
int listening;
// Connections lwIP has completed but nobody has accept()ed yet.
struct inet_sock *acceptq[NBACKLOG];
int naccept;
};
static struct inet_sock itab[NINET];
/* ------------------------------------------------------------------ */
/* Waiting. See the note on blocking at the top of this file. */
/* ------------------------------------------------------------------ */
static struct spinlock netwait_lock;
static uint netwait_seq;
void
inet_init(void)
{
initlock(&netwait_lock, "netwait");
}
// The current wakeup count. Read by a caller that has just checked its
// condition and is still holding the lwIP lock.
static uint
net_seq(void)
{
uint s;
acquire(&netwait_lock);
s = netwait_seq;
release(&netwait_lock);
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.
static void
net_wait(uint seen)
{
acquire(&netwait_lock);
while (netwait_seq == seen && !myproc()->killed)
sleep(&netwait_seq, &netwait_lock);
release(&netwait_lock);
}
// Something a waiter might care about has happened. Called from the lwIP
// callbacks, which run in the network thread with the lwIP lock held -- taking
// a spinlock there is fine, since nothing between acquire and release sleeps.
static void
net_wake(void)
{
acquire(&netwait_lock);
netwait_seq++;
release(&netwait_lock);
wakeup(&netwait_seq);
}
/* ------------------------------------------------------------------ */
/* The receive ring. Callers hold the lwIP lock. */
/* ------------------------------------------------------------------ */
static uint
rx_used(struct inet_sock *is)
{
return is->rhead - is->rtail;
}
static uint
rx_space(struct inet_sock *is)
{
return INET_RXBUF - rx_used(is);
}
static void
rx_put(struct inet_sock *is, const char *p, uint n)
{
for (uint i = 0; i < n; i++)
is->rx[(is->rhead + i) % INET_RXBUF] = p[i];
is->rhead += n;
}
static uint
rx_take(struct inet_sock *is, char *p, uint n)
{
uint have = rx_used(is);
if (n > have)
n = have;
for (uint i = 0; i < n; i++)
p[i] = is->rx[(is->rtail + i) % INET_RXBUF];
is->rtail += n;
return n;
}
// Throw bytes away without copying them. A datagram larger than the buffer it
// was read into is truncated, and the remainder is discarded rather than left
// to be delivered as if it were the next datagram.
static void
rx_drop(struct inet_sock *is, uint n)
{
uint have = rx_used(is);
is->rtail += n > have ? have : n;
}
/* ------------------------------------------------------------------ */
/* Datagram boundaries. UDP only; callers hold the lwIP lock. */
/* ------------------------------------------------------------------ */
static uint
dq_used(struct inet_sock *is)
{
return is->dhead - is->dtail;
}
// Take one datagram, whole. Returns the bytes copied, or -1 if none is queued.
//
// Whole is the point: read() on a UDP socket returns one datagram or nothing,
// never the tail of one followed by the head of the next. Before this queue
// existed the bytes went into the ring unmarked, so two datagrams that arrived
// together came back as one -- which is a bug that only appears when the
// timing is right, and is then blamed on the sender.
static int
dq_take(struct inet_sock *is, char *p, uint n, uint *addr, ushort *port)
{
if (dq_used(is) == 0)
return -1;
struct dgram *d = &is->dq[is->dtail % NDGRAM];
uint want = d->len;
uint got = want < n ? want : n;
rx_take(is, p, got);
rx_drop(is, want - got);
if (addr) *addr = d->addr;
if (port) *port = d->port;
is->dtail++;
return (int)got;
}
/* ------------------------------------------------------------------ */
/* lwIP callbacks. All of these run in the network thread, holding the */
/* lwIP lock. */
/* ------------------------------------------------------------------ */
static void
tcp_fail(struct inet_sock *is)
{
is->peer_closed = 1;
is->conn_failed = 1;
is->connecting = 0;
// The pcb is already freed by lwIP when err_cb fires; keeping the pointer
// would leave a dangling handle that close() would then free a second time.
is->tpcb = 0;
// A failure is as much of a wakeup as data: whoever is blocked in connect,
// read or write is now waiting for something that will never arrive.
net_wake();
}
static void
inet_err_cb(void *arg, err_t err)
{
(void)err;
if (arg)
tcp_fail((struct inet_sock *)arg);
}
static err_t
inet_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
struct inet_sock *is = arg;
if (is == 0) {
if (p) pbuf_free(p);
return ERR_OK;
}
if (err != ERR_OK) {
if (p) pbuf_free(p);
return err;
}
// A null pbuf is the peer's FIN, not an error: the connection stays readable
// until the buffer drains, and only then does read() report end of file.
if (p == NULL) {
is->peer_closed = 1;
net_wake();
return ERR_OK;
}
// Refusing what will not fit leaves it queued in lwIP, which closes the
// window and makes the sender wait -- which is the whole point of a window.
// Accepting and dropping would look like a working receive that loses data.
if (p->tot_len > rx_space(is))
return ERR_MEM;
for (struct pbuf *q = p; q != NULL; q = q->next)
rx_put(is, (const char *)q->payload, q->len);
// Only now is the data ours, so only now may the window reopen.
tcp_recved(pcb, p->tot_len);
pbuf_free(p);
net_wake();
return ERR_OK;
}
// An acknowledgement freed space in the send buffer. Nothing reads `len`: the
// only waiter is a write that could not fit, and it re-reads tcp_sndbuf itself.
static err_t
inet_sent_cb(void *arg, struct tcp_pcb *pcb, u16_t len)
{
(void)arg; (void)pcb; (void)len;
net_wake();
return ERR_OK;
}
static err_t
inet_connected_cb(void *arg, struct tcp_pcb *pcb, err_t err)
{
struct inet_sock *is = arg;
(void)pcb;
if (is == 0)
return ERR_OK;
if (err != ERR_OK) {
tcp_fail(is);
return ERR_OK;
}
is->connecting = 0;
is->connected = 1;
net_wake();
return ERR_OK;
}
static struct inet_sock *inet_alloc(int type);
static err_t
inet_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err)
{
struct inet_sock *ls = arg;
if (ls == 0 || err != ERR_OK)
return ERR_VAL;
if (ls->naccept >= NBACKLOG)
return ERR_MEM; // backlog full: lwIP refuses the connection
struct inet_sock *is = inet_alloc(SOCK_STREAM);
if (is == 0)
return ERR_MEM;
is->tpcb = newpcb;
is->connected = 1;
tcp_arg(newpcb, is);
tcp_recv(newpcb, inet_recv_cb);
tcp_err(newpcb, inet_err_cb);
tcp_sent(newpcb, inet_sent_cb);
ls->acceptq[ls->naccept++] = is;
net_wake();
return ERR_OK;
}
// UDP delivers datagrams with no connection, so each one carries its own
// sender. Both the sender and the length are recorded next to the bytes, which
// is what lets recvfrom answer "who sent this" and read() return exactly one
// datagram.
static void
inet_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,
const ip_addr_t *addr, u16_t port)
{
struct inet_sock *is = arg;
(void)pcb;
if (is == 0 || p == NULL) {
if (p) pbuf_free(p);
return;
}
// Two ways to be full, and both mean the same thing for UDP: the datagram is
// dropped. That is the protocol's own contract -- there is no window to
// close and nothing to retransmit -- but it must be dropped *whole*, because
// half a datagram in the ring would corrupt every one after it.
if (p->tot_len <= rx_space(is) && dq_used(is) < NDGRAM) {
struct dgram *d = &is->dq[is->dhead % NDGRAM];
d->addr = addr ? ip4_addr_get_u32(ip_2_ip4(addr)) : 0;
d->port = port;
d->len = p->tot_len;
is->dhead++;
for (struct pbuf *q = p; q != NULL; q = q->next)
rx_put(is, (const char *)q->payload, q->len);
}
pbuf_free(p);
net_wake();
}
// ICMP, for ping sockets.
//
// Linux calls these "ping sockets": SOCK_DGRAM with IPPROTO_ICMP, giving an
// unprivileged process the one thing raw sockets were used for. The contract is
// that the caller reads and writes the ICMP message alone, and the kernel deals
// with the IP header -- which is why this strips it on the way in, where
// lwIP's raw layer hands over a pbuf that starts at the IP header.
//
// **Only echo replies are eaten.** raw_input runs before lwIP's own
// icmp_input, so a callback that returned 1 for everything would swallow the
// echo *requests* too and this machine would silently stop answering pings
// from outside. Returning 0 leaves the packet for lwIP, which is exactly what
// should happen to every ICMP message we did not ask for.
//
// No matching against the echo id: a reply goes to every open ICMP socket.
// Linux rewrites the id per socket and filters on it, which matters when two
// 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.
static u8_t
inet_icmp_recv_cb(void *arg, struct raw_pcb *pcb, struct pbuf *p,
const ip_addr_t *addr)
{
struct inet_sock *is = arg;
(void)pcb;
if (is == 0 || p == NULL)
return 0;
// The IP header is variable length: IHL counts 32-bit words.
if (p->tot_len < sizeof(struct ip_hdr))
return 0;
struct ip_hdr *ih = (struct ip_hdr *)p->payload;
u16_t hlen = (u16_t)(IPH_HL(ih) * 4);
if (hlen < sizeof(struct ip_hdr) || p->tot_len < hlen + 8)
return 0;
// Type 0 is ICMP Echo Reply. Everything else -- including the requests this
// host must keep answering -- is left for lwIP.
u8_t type;
if (pbuf_copy_partial(p, &type, 1, hlen) != 1 || type != 0)
return 0;
u16_t icmplen = (u16_t)(p->tot_len - hlen);
if (icmplen <= rx_space(is) && dq_used(is) < NDGRAM) {
struct dgram *d = &is->dq[is->dhead % NDGRAM];
d->addr = addr ? ip4_addr_get_u32(ip_2_ip4(addr)) : 0;
d->port = 0; // ICMP has no ports
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;
while (off < p->tot_len) {
char tmp[64];
u16_t want = (u16_t)(p->tot_len - off);
if (want > sizeof(tmp)) want = sizeof(tmp);
if (pbuf_copy_partial(p, tmp, want, off) != want)
break;
rx_put(is, tmp, want);
off = (u16_t)(off + want);
}
}
pbuf_free(p);
net_wake();
return 1; // eaten
}
/* ------------------------------------------------------------------ */
/* Allocation */
/* ------------------------------------------------------------------ */
static struct inet_sock *
inet_alloc(int type)
{
for (int i = 0; i < NINET; i++)
if (!itab[i].inuse) {
struct inet_sock *is = &itab[i];
memset(is, 0, sizeof(*is));
is->inuse = 1;
is->type = type;
return is;
}
return 0;
}
/* ------------------------------------------------------------------ */
/* The interface kernel/net/unix.c dispatches to */
/* ------------------------------------------------------------------ */
void *
inet_new(int type, int proto)
{
if (type != SOCK_STREAM && type != SOCK_DGRAM)
return 0;
// 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())
return 0;
lwip_sys_lock();
struct inet_sock *is = inet_alloc(type);
if (is == 0) {
lwip_sys_unlock();
return 0;
}
is->proto = proto;
if (proto == IPPROTO_ICMP) {
// A ping socket. raw_new registers for one IP protocol number; the
// callback below filters to echo replies so lwIP keeps answering the
// requests this host receives. See inet_icmp_recv_cb.
is->rpcb = raw_new(IP_PROTO_ICMP);
if (is->rpcb == 0) {
is->inuse = 0;
lwip_sys_unlock();
return 0;
}
raw_recv(is->rpcb, inet_icmp_recv_cb, is);
raw_bind(is->rpcb, IP_ANY_TYPE);
} else if (type == SOCK_STREAM) {
is->tpcb = tcp_new();
if (is->tpcb == 0) {
is->inuse = 0;
lwip_sys_unlock();
return 0;
}
tcp_arg(is->tpcb, is);
tcp_err(is->tpcb, inet_err_cb);
tcp_recv(is->tpcb, inet_recv_cb);
tcp_sent(is->tpcb, inet_sent_cb);
} else {
is->upcb = udp_new();
if (is->upcb == 0) {
is->inuse = 0;
lwip_sys_unlock();
return 0;
}
udp_recv(is->upcb, inet_udp_recv_cb, is);
}
lwip_sys_unlock();
return is;
}
int
inet_bind(void *h, uint32 addr, uint16 port)
{
struct inet_sock *is = h;
ip_addr_t ip;
err_t e;
ip_addr_set_ip4_u32(&ip, addr); // already network byte order
lwip_sys_lock();
e = (is->type == SOCK_STREAM) ? tcp_bind(is->tpcb, &ip, port)
: udp_bind(is->upcb, &ip, port);
lwip_sys_unlock();
if (e == ERR_USE)
return -EADDRINUSE;
return e == ERR_OK ? 0 : -EINVAL;
}
// Apply a socket option to the lwIP pcb behind this handle.
//
// Only the four that lwIP can genuinely honour. Everything else is refused by
// the caller (kernel/sys/sys_net.c) before it reaches here, so this never has
// to pretend.
//
// Called with the option already recorded on the struct socket, so a flag set
// before bind() is not lost: lwIP's pcb exists from inet_new() onwards, which
// is early enough for all four, but recording it there as well means
// getsockopt has one answer and not two.
int
inet_setopt(void *h, int level, int name, int val)
{
struct inet_sock *is = h;
lwip_sys_lock();
if (level == SOL_SOCKET) {
struct ip_pcb *p = (is->type == SOCK_STREAM) ? (struct ip_pcb *)is->tpcb
: (struct ip_pcb *)is->upcb;
if (p == 0) { lwip_sys_unlock(); return -EINVAL; }
switch (name) {
case SO_REUSEADDR:
if (val) ip_set_option(p, SOF_REUSEADDR);
else ip_reset_option(p, SOF_REUSEADDR);
break;
case SO_KEEPALIVE:
if (val) ip_set_option(p, SOF_KEEPALIVE);
else ip_reset_option(p, SOF_KEEPALIVE);
break;
case SO_BROADCAST:
if (val) ip_set_option(p, SOF_BROADCAST);
else ip_reset_option(p, SOF_BROADCAST);
break;
default:
lwip_sys_unlock();
return -ENOPROTOOPT;
}
} else if (level == IPPROTO_TCP && name == TCP_NODELAY) {
if (is->type != SOCK_STREAM || is->tpcb == 0) {
lwip_sys_unlock();
return -EINVAL;
}
if (val) tcp_nagle_disable(is->tpcb);
else tcp_nagle_enable(is->tpcb);
} else {
lwip_sys_unlock();
return -ENOPROTOOPT;
}
lwip_sys_unlock();
return 0;
}
int
inet_listen(void *h, int backlog)
{
struct inet_sock *is = h;
if (is->type != SOCK_STREAM)
return -EOPNOTSUPP;
lwip_sys_lock();
// tcp_listen frees the old pcb and returns a smaller listening one, so the
// handle has to be replaced rather than kept. Losing this is a use-after-free
// that only shows up on the second connection.
struct tcp_pcb *lp = tcp_listen_with_backlog(is->tpcb,
backlog > 0 && backlog < NBACKLOG ? (u8_t)backlog
: NBACKLOG);
if (lp == 0) {
lwip_sys_unlock();
return -ENOMEM;
}
is->tpcb = lp;
is->listening = 1;
tcp_arg(lp, is);
tcp_accept(lp, inet_accept_cb);
lwip_sys_unlock();
return 0;
}
void *
inet_accept(void *h)
{
struct inet_sock *ls = h;
if (!ls->listening)
return 0;
for (;;) {
lwip_sys_lock();
if (ls->naccept > 0) {
struct inet_sock *is = ls->acceptq[0];
for (int i = 1; i < ls->naccept; i++)
ls->acceptq[i - 1] = ls->acceptq[i];
ls->naccept--;
lwip_sys_unlock();
return is;
}
// Taken while the lwIP lock is still held, so no connection can arrive
// between the check above and the sleep below without moving it.
uint seen = net_seq();
lwip_sys_unlock();
if (myproc()->killed)
return 0;
net_wait(seen);
}
}
int
inet_connect(void *h, uint32 addr, uint16 port)
{
struct inet_sock *is = h;
ip_addr_t ip;
ip_addr_set_ip4_u32(&ip, addr);
if (is->type == SOCK_DGRAM) {
// A connected UDP socket just remembers where to send; there is no
// handshake to wait for.
lwip_sys_lock();
err_t e = udp_connect(is->upcb, &ip, port);
lwip_sys_unlock();
return e == ERR_OK ? 0 : -EINVAL;
}
lwip_sys_lock();
is->connecting = 1;
err_t e = tcp_connect(is->tpcb, &ip, port, inet_connected_cb);
lwip_sys_unlock();
if (e != ERR_OK) {
is->connecting = 0;
return -ECONNREFUSED;
}
for (;;) {
lwip_sys_lock();
int done = is->connected, failed = is->conn_failed;
uint seen = net_seq();
lwip_sys_unlock();
if (done)
return 0;
if (failed)
return -ECONNREFUSED;
if (myproc()->killed)
return -EINTR;
net_wait(seen);
}
}
// Read, and say where it came from.
//
// `addr` and `port` may be null, which is what read() and recv() pass: the
// source is then simply not reported. For a stream socket the answer is the
// 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.
int
inet_recvfrom(void *h, char *buf, int n, uint *addr, ushort *port)
{
struct inet_sock *is = h;
if (n <= 0)
return 0;
for (;;) {
int got = 0;
int have = 0; // something was delivered, even if it was zero bytes
int eof;
uint seen;
lwip_sys_lock();
if (is->type == SOCK_DGRAM) {
// A zero-length datagram is a real thing a sender can send, and the
// right answer to it is 0 rather than "nothing arrived yet" -- which is
// why dq_take reports an empty queue as -1 and not as zero bytes.
int r = dq_take(is, buf, (uint)n, addr, port);
have = r >= 0;
got = r > 0 ? r : 0;
} else {
got = (int)rx_take(is, buf, (uint)n);
have = got > 0;
// A stream's source is the peer it is connected to: the same answer for
// every byte, which is why it is read from the pcb rather than stored.
if (have && addr && is->tpcb) {
*addr = ip4_addr_get_u32(ip_2_ip4(&is->tpcb->remote_ip));
if (port)
*port = is->tpcb->remote_port;
}
}
eof = is->peer_closed;
seen = net_seq();
lwip_sys_unlock();
if (have)
return got;
if (eof)
return 0; // end of file, not an error
if (myproc()->killed)
return -EINTR;
net_wait(seen);
}
}
int
inet_read(void *h, char *buf, int n)
{
return inet_recvfrom(h, buf, n, 0, 0);
}
// Write, optionally to a named destination.
//
// `addr` of zero means "wherever this socket is already pointed", which is what
// write() and send() pass. For TCP that is the only possibility and a
// destination is ignored rather than refused: the connection decides where the
// bytes go, and a program that passes one anyway -- many do, out of habit -- is
// better served by having it work.
int
inet_sendto(void *h, char *buf, int n, uint addr, ushort port)
{
struct inet_sock *is = h;
int done = 0;
if (n <= 0)
return 0;
// ICMP. The caller hands over the ICMP message alone -- header, checksum and
// payload -- and lwIP puts the IP header on the front, which is what makes
// this a ping socket rather than a raw one.
//
// PBUF_IP rather than PBUF_TRANSPORT for the headroom: the IP header is what
// goes in front here, and asking for transport headroom would have lwIP
// reallocate to make room for it.
if (is->proto == IPPROTO_ICMP) {
if (is->rpcb == 0)
return -ENOTCONN;
if (addr == 0)
return -EDESTADDRREQ;
lwip_sys_lock();
struct pbuf *p = pbuf_alloc(PBUF_IP, (u16_t)n, PBUF_RAM);
if (p == 0) {
lwip_sys_unlock();
return -ENOBUFS;
}
pbuf_take(p, buf, (u16_t)n);
ip_addr_t ip;
ip_addr_set_ip4_u32(&ip, addr);
err_t e = raw_sendto(is->rpcb, p, &ip);
pbuf_free(p);
lwip_sys_unlock();
if (e == ERR_OK)
return n;
return e == ERR_RTE ? -ENETUNREACH : -EIO;
}
if (is->type == SOCK_DGRAM) {
lwip_sys_lock();
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, (u16_t)n, PBUF_RAM);
if (p == 0) {
lwip_sys_unlock();
return -ENOBUFS;
}
pbuf_take(p, buf, (u16_t)n);
err_t e;
if (addr) {
ip_addr_t ip;
ip_addr_set_ip4_u32(&ip, addr);
e = udp_sendto(is->upcb, p, &ip, port);
} else {
// No destination and none remembered is a program error, not a network
// one: lwIP would send to 0.0.0.0 and the datagram would vanish.
e = udp_send(is->upcb, p);
}
pbuf_free(p);
lwip_sys_unlock();
if (e == ERR_OK)
return n;
return e == ERR_RTE ? -EDESTADDRREQ : -EIO;
}
// TCP: the send buffer is finite, so a large write becomes several passes.
while (done < n) {
uint seen;
lwip_sys_lock();
if (is->peer_closed || is->tpcb == 0) {
lwip_sys_unlock();
// Same contract as a pipe with no reader, which is what the AF_UNIX side
// does: the signal first, then the error.
return done > 0 ? done : -EPIPE;
}
int room = tcp_sndbuf(is->tpcb);
int want = n - done;
if (want > room)
want = room;
if (want > 0) {
// TCP_WRITE_FLAG_COPY because `buf` is a kernel bounce buffer the caller
// reuses the moment this returns; lwIP would otherwise hold a pointer
// into memory that has changed by the time the segment goes out.
err_t e = tcp_write(is->tpcb, buf + done, (u16_t)want,
TCP_WRITE_FLAG_COPY);
if (e == ERR_OK) {
tcp_output(is->tpcb);
done += want;
} else if (e != ERR_MEM) {
lwip_sys_unlock();
return done > 0 ? done : -EIO;
}
}
seen = net_seq();
lwip_sys_unlock();
if (done < n) {
if (myproc()->killed)
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
// nobody left to end it, which is why that callback exists.
net_wait(seen);
}
}
return done;
}
int
inet_write(void *h, char *buf, int n)
{
return inet_sendto(h, buf, n, 0, 0);
}
int
inet_readable(void *h)
{
struct inet_sock *is = h;
int r;
lwip_sys_lock();
r = rx_used(is) > 0 || is->peer_closed || is->naccept > 0;
lwip_sys_unlock();
return r;
}
int
inet_hup(void *h)
{
struct inet_sock *is = h;
int r;
lwip_sys_lock();
r = is->peer_closed && rx_used(is) == 0;
lwip_sys_unlock();
return r;
}
void
inet_close(void *h)
{
struct inet_sock *is = h;
if (is == 0)
return;
lwip_sys_lock();
// Anything queued but never accepted is closed too, or those connections
// would sit half-open until the peer gave up on them.
for (int i = 0; i < is->naccept; i++) {
struct inet_sock *q = is->acceptq[i];
if (q->tpcb) {
tcp_arg(q->tpcb, 0);
tcp_close(q->tpcb);
}
q->inuse = 0;
}
is->naccept = 0;
if (is->tpcb) {
// Clearing the argument first: lwIP may still deliver a callback for this
// pcb after tcp_close, and it would otherwise arrive with a pointer to a
// socket that has just been freed. `callback_arg` is in the part of the
// structure both kinds of pcb share, so this one is safe either way.
tcp_arg(is->tpcb, 0);
// **A listening pcb is a different, smaller structure.** tcp_listen()
// frees the connection pcb and returns a `struct tcp_pcb_listen`, which
// has no recv, sent, err or poll field at all -- so tcp_recv() on one does
// not merely set something useless, it writes past the end of the
// allocation. lwIP asserts rather than allowing that, and the assert is a
// kernel panic here:
//
// lwip assert: invalid socket state for recv callback tcp.c:2024
// #1 tcp_recv #2 inet_close #3 sockclose #4 fileclose #5 exit
//
// which is a user process taking the machine down by pressing Ctrl-C.
// Nothing had ever closed a listening TCP socket before -- the first
// program to listen and then exit was cmd/httpd, and it found this on its
// first run.
//
// The callback a listening pcb *does* have is `accept`, and clearing that
// is the equivalent gesture.
if (is->listening) {
tcp_accept(is->tpcb, 0);
// Always ERR_OK for a LISTEN pcb, and tcp_abort() would be wrong on one:
// it reaches for connection state that a listening pcb does not have.
tcp_close(is->tpcb);
} else {
tcp_recv(is->tpcb, 0);
tcp_err(is->tpcb, 0);
tcp_sent(is->tpcb, 0);
if (tcp_close(is->tpcb) != ERR_OK)
tcp_abort(is->tpcb);
}
is->tpcb = 0;
is->listening = 0;
}
if (is->upcb) {
udp_recv(is->upcb, 0, 0);
udp_remove(is->upcb);
is->upcb = 0;
}
if (is->rpcb) {
raw_recv(is->rpcb, 0, 0);
raw_remove(is->rpcb);
is->rpcb = 0;
}
is->inuse = 0;
lwip_sys_unlock();
}