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>
572 lines
19 KiB
C
572 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// The network thread, and the one place the TCP/IP stack is started.
|
|
//
|
|
// lwIP with NO_SYS=1 has no threads of its own, so something has to feed it
|
|
// received frames and run its timers. That something is a kernel thread rather
|
|
// than a driver's interrupt handler, and the reason is the lock: a call into
|
|
// lwIP can reach the driver, the driver can sleep, and an interrupt handler
|
|
// cannot. So the interrupt keeps doing what it always did -- clear the cause
|
|
// register -- and this thread polls.
|
|
//
|
|
// Nothing here names a driver. Interfaces come from the netdev class
|
|
// (include/netdev.h): every bound network device is attached, and a machine
|
|
// with two NICs gets two interfaces without a line changing in this file.
|
|
//
|
|
// Polling costs a wakeup every few milliseconds when the link is idle. The
|
|
// alternative, waking this thread from the interrupt, is a worthwhile change
|
|
// later; it is deliberately not the first version, because a receive path that
|
|
// is woken from an interrupt is much harder to debug than one that is asked.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "proc.h"
|
|
#include "device.h"
|
|
#include "netdev.h"
|
|
|
|
#include "lwip/opt.h"
|
|
#include "lwip/init.h"
|
|
#include "lwip/timeouts.h"
|
|
#include "lwip/netif.h"
|
|
#include "lwip/dhcp.h"
|
|
#include "lwip/ip4_addr.h"
|
|
#include "lwip/dns.h"
|
|
|
|
// Wait until the next timer tick. `ticks` advances at 100 Hz, so this is a
|
|
// sleep of up to 10 ms -- and, more importantly, it is the kernel's only timed
|
|
// sleep: there is no higher-resolution timer to wait on. The lwIP lock must not
|
|
// be held across it, or nothing else could touch the stack while the link is
|
|
// idle, which is most of the time.
|
|
static void
|
|
wait_tick(void)
|
|
{
|
|
acquire(&tickslock);
|
|
uint start = ticks;
|
|
while (ticks == start)
|
|
sleep(&ticks, &tickslock);
|
|
release(&tickslock);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Waking on a received frame
|
|
//
|
|
// The thread used to wait for the clock and nothing else, so a frame arriving
|
|
// just after a poll sat in the ring for up to 10 ms. That was deliberate at the
|
|
// time -- a receive path woken by an interrupt is much harder to debug than one
|
|
// that is asked, and the stack had to be proven first -- and it is now the
|
|
// largest fixed latency in the system.
|
|
//
|
|
// The wait stays on `ticks`, rather than moving to a channel of its own, and
|
|
// that is the whole design. lwIP's timers must still run when the link is
|
|
// silent, so the clock has to remain a floor under the wait; a second channel
|
|
// would mean sleeping on two things at once, which sleep() cannot do. Joining
|
|
// the tick's existing wakeup costs nothing and keeps one wait, one predicate.
|
|
//
|
|
// `rx_signal` lives under tickslock for the same reason it is not its own lock:
|
|
// sleep() releases tickslock atomically, so a driver that sets the flag and
|
|
// wakes under that lock cannot lose the wakeup by arriving in the window
|
|
// between the predicate test and the sleep. A private lock would not be held
|
|
// across the sleep, and that window would be back.
|
|
//
|
|
// Called from interrupt context. acquire() disables interrupts, so a CPU
|
|
// holding tickslock cannot be interrupted into this; another CPU spins briefly.
|
|
// That is the same arrangement the timer handler itself uses.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static int rx_signal;
|
|
|
|
// Counted so the mechanism is observable rather than assumed: if `rx` stays
|
|
// zero while traffic flows, the interrupt is not arriving and the thread is
|
|
// running on the clock exactly as it did before.
|
|
static uint64 wake_rx, wake_tick;
|
|
|
|
void
|
|
netdev_rx_signal(void)
|
|
{
|
|
acquire(&tickslock);
|
|
rx_signal = 1;
|
|
wakeup(&ticks);
|
|
release(&tickslock);
|
|
}
|
|
|
|
void
|
|
net_wake_counts(uint64 *rx, uint64 *tick)
|
|
{
|
|
if (rx) *rx = wake_rx;
|
|
if (tick) *tick = wake_tick;
|
|
}
|
|
|
|
// Sleep until there is something to do: a frame has been signalled, or the
|
|
// clock has moved and lwIP's timers are due.
|
|
static void
|
|
wait_work(void)
|
|
{
|
|
acquire(&tickslock);
|
|
uint start = ticks;
|
|
while (ticks == start && !rx_signal)
|
|
sleep(&ticks, &tickslock);
|
|
if (rx_signal) {
|
|
wake_rx++;
|
|
rx_signal = 0;
|
|
} else {
|
|
wake_tick++;
|
|
}
|
|
release(&tickslock);
|
|
}
|
|
|
|
static int net_started;
|
|
|
|
// Set once DHCP has bound, so `ifconfig`-ish output and /proc/net have
|
|
// something true to say rather than reporting 0.0.0.0 forever.
|
|
static int net_bound;
|
|
|
|
// Set once lwIP is initialised and this thread is running its timers. Distinct
|
|
// from net_bound, and the distinction is not academic: inet_new() used to
|
|
// require an *address* before it would hand out a socket, so every AF_INET
|
|
// socket() failed until DHCP answered -- reported as ENFILE, which sends you to
|
|
// look at the file table. It is also simply wrong for 127.0.0.1, which needs no
|
|
// lease and no NIC.
|
|
//
|
|
// Creating a socket is not the same as having somewhere to send: a bind or a
|
|
// connect with no route fails on its own, with an error that says so.
|
|
static int net_ready;
|
|
|
|
int
|
|
net_is_up(void)
|
|
{
|
|
return net_bound;
|
|
}
|
|
|
|
int
|
|
net_is_ready(void)
|
|
{
|
|
return net_ready;
|
|
}
|
|
|
|
// An interface's address and netmask, in network byte order, for callers that
|
|
// have no business including lwIP's headers -- kernel/net/netlink.c is the one
|
|
// that needs them. Zero when nothing is bound yet, which is a real answer: an
|
|
// interface without a lease has no address to report.
|
|
uint
|
|
netif_addr4(struct netif *nif)
|
|
{
|
|
return nif ? ip4_addr_get_u32(netif_ip4_addr(nif)) : 0;
|
|
}
|
|
|
|
uint
|
|
netif_mask4(struct netif *nif)
|
|
{
|
|
return nif ? ip4_addr_get_u32(netif_ip4_netmask(nif)) : 0;
|
|
}
|
|
|
|
// Network to host and back, for a 32-bit quantity. The kernel is little-endian
|
|
// and the wire is big-endian; lwIP's own macros are not reachable outside the
|
|
// files that include its headers.
|
|
uint
|
|
ntohl_u32(uint v)
|
|
{
|
|
return ((v & 0xFF) << 24) | ((v & 0xFF00) << 8) |
|
|
((v >> 8) & 0xFF00) | ((v >> 24) & 0xFF);
|
|
}
|
|
|
|
uint
|
|
htonl_u32(uint v)
|
|
{
|
|
return ntohl_u32(v);
|
|
}
|
|
|
|
// Attach an interface that appeared after boot.
|
|
//
|
|
// netinit() walks the devices that exist when it runs and starts DHCP on each.
|
|
// An interface created later -- by a process claiming /dev/netdev, which is
|
|
// how an LDLS-hosted driver arrives -- misses that entirely, and would come up
|
|
// with no address and no way to ever get one.
|
|
//
|
|
// It lives here rather than in kernel/dev/netuser.c because this is the only
|
|
// file allowed to include lwIP's headers; reaching into the stack from a
|
|
// device driver would put them on every kernel file's include path. See the
|
|
// note on /proc/netif in kernel/dev/procfs.c, which is the same rule.
|
|
int
|
|
net_attach_late(struct device *d)
|
|
{
|
|
int idx;
|
|
|
|
if (!net_ready)
|
|
return -1; // netinit() has not run; it will pick this up
|
|
|
|
lwip_sys_lock();
|
|
idx = net_eth_attach(d);
|
|
if (idx >= 0)
|
|
dhcp_start(net_eth_netif(idx));
|
|
lwip_sys_unlock();
|
|
|
|
if (idx >= 0)
|
|
cprintf("net: %s attached as interface %d, asking for an address\n",
|
|
d->name, idx);
|
|
return idx;
|
|
}
|
|
|
|
// Which interfaces have had their address reported. File scope rather than
|
|
// local to net_thread(), because an interface that goes away has to be able to
|
|
// be announced again if one comes back in its slot.
|
|
static int announced[NETIF_MAX];
|
|
|
|
static void
|
|
net_unannounce(int idx)
|
|
{
|
|
if (idx >= 0 && idx < NETIF_MAX)
|
|
announced[idx] = 0;
|
|
}
|
|
|
|
// The other direction: a device the kernel no longer drives.
|
|
//
|
|
// Called when /dev/pci hands a NIC to a process. Same rule about lwIP headers
|
|
// as net_attach_late above -- the caller is a device file and must not see
|
|
// them.
|
|
int
|
|
net_detach(struct device *d)
|
|
{
|
|
int idx;
|
|
|
|
if (!net_ready)
|
|
return -1;
|
|
|
|
lwip_sys_lock();
|
|
idx = net_eth_detach(d);
|
|
lwip_sys_unlock();
|
|
|
|
if (idx >= 0) {
|
|
net_unannounce(idx);
|
|
cprintf("net: %s is gone; interface %d removed\n", d->name, idx);
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
static void
|
|
net_thread(void)
|
|
{
|
|
// lwIP's own initialisation has to happen in this thread, not in main():
|
|
// it allocates from lwIP's pools and registers timers, and doing that
|
|
// before there is a process context means doing it somewhere that cannot
|
|
// sleep if the allocator ever needs to.
|
|
lwip_sys_init();
|
|
// From here a socket can be created: the pools exist and the timers are
|
|
// about to run. An address may still be minutes away, or never arrive.
|
|
net_ready = 1;
|
|
|
|
lwip_sys_lock();
|
|
lwip_init();
|
|
|
|
// Every bound network device becomes an interface. DHCP is started on each
|
|
// of them: which one answers is the network's business, not ours, and an
|
|
// interface left unconfigured because it happened to be second would be a
|
|
// rule nobody could see.
|
|
int attached = 0;
|
|
for (int i = 0; i < netdev_count(); i++) {
|
|
struct device *d = netdev_at(i);
|
|
int idx = net_eth_attach(d);
|
|
if (idx < 0)
|
|
continue;
|
|
dhcp_start(net_eth_netif(idx));
|
|
attached++;
|
|
kinfo("net: %s attached as interface %d\n", d->name, idx);
|
|
}
|
|
|
|
if (attached == 0) {
|
|
lwip_sys_unlock();
|
|
cprintf("net: no network device; the thread has nothing to drive\n");
|
|
// Not a panic and not an exit: a kernel thread must never return, and
|
|
// a machine with no NIC is an ordinary machine.
|
|
for (;;)
|
|
wait_tick();
|
|
}
|
|
lwip_sys_unlock();
|
|
|
|
kinfo("net: lwIP %d.%d.%d up on %d interface%s, requesting an address\n",
|
|
LWIP_VERSION_MAJOR, LWIP_VERSION_MINOR, LWIP_VERSION_REVISION,
|
|
attached, attached == 1 ? "" : "s");
|
|
|
|
// One bit per interface, so a second one binding later is announced too.
|
|
|
|
|
|
for (;;) {
|
|
int moved;
|
|
|
|
lwip_sys_lock();
|
|
moved = net_eth_poll();
|
|
sys_check_timeouts();
|
|
// Loopback has no driver and no interrupt: a packet sent to 127.0.0.1
|
|
// sits on the loopif's queue until something drains it. With NO_SYS=1
|
|
// that something is this thread.
|
|
netif_poll_all();
|
|
|
|
// Report each address once, when it first arrives. Anyone who has spent
|
|
// an evening wondering whether the guest has an address at all wants
|
|
// this on the console rather than in a file they have to know about.
|
|
// Over every interface there is now, not over the ones there were at
|
|
// boot: an interface can arrive later. /dev/netdev makes one whenever a
|
|
// process becomes a network device, and until this counted them an
|
|
// address on `ldls0` was never reported at all.
|
|
for (int i = 0; i < net_eth_count() && i < NETIF_MAX; i++) {
|
|
struct netif *nif = net_eth_netif(i);
|
|
if (nif == 0 || net_eth_device(i) == 0)
|
|
continue;
|
|
if (announced[i] || ip4_addr_isany_val(*netif_ip4_addr(nif)))
|
|
continue;
|
|
|
|
const ip4_addr_t *a = netif_ip4_addr(nif);
|
|
const ip4_addr_t *m = netif_ip4_netmask(nif);
|
|
const ip4_addr_t *gw = netif_ip4_gw(nif);
|
|
// klog, not cprintf: a lease arrives seconds after the shell
|
|
// prompt does, so on the display this lands in the middle of
|
|
// whatever the user is doing. /proc/netif is where a person looks
|
|
// for an address; the serial line is where the log belongs.
|
|
klog("net: %s %d.%d.%d.%d/%d.%d.%d.%d via %d.%d.%d.%d\n",
|
|
net_eth_device(i)->name,
|
|
ip4_addr1(a), ip4_addr2(a), ip4_addr3(a), ip4_addr4(a),
|
|
ip4_addr1(m), ip4_addr2(m), ip4_addr3(m), ip4_addr4(m),
|
|
ip4_addr1(gw), ip4_addr2(gw), ip4_addr3(gw), ip4_addr4(gw));
|
|
announced[i] = 1;
|
|
net_bound = 1;
|
|
}
|
|
lwip_sys_unlock();
|
|
|
|
// Busy while there is traffic, idle otherwise. Sleeping even 1 ms with
|
|
// frames waiting would cap throughput at one burst per tick.
|
|
if (moved == 0)
|
|
wait_work();
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* /proc/netif */
|
|
/* ------------------------------------------------------------------ */
|
|
//
|
|
// Named netif rather than net, and that is deliberate: on Linux /proc/net is a
|
|
// *directory* of a dozen files, and putting a plain file there would mean
|
|
// anything reaching for the familiar /proc/net/dev got ENOTDIR from a path that
|
|
// looks like it exists. This is one interface's state, so it is named for what
|
|
// it holds and leaves the Linux name free for the day there is a directory to
|
|
// put under it.
|
|
//
|
|
// No snprintf in the kernel, so this appends by hand in the same style as
|
|
// kernel/dev/pci.c.
|
|
|
|
static int
|
|
put_str(char *out, const char *s)
|
|
{
|
|
int n = 0;
|
|
while (s[n]) { out[n] = s[n]; n++; }
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
put_u(char *out, uint64 v)
|
|
{
|
|
char tmp[24];
|
|
int i = 0, n = 0;
|
|
|
|
if (v == 0) { out[0] = '0'; return 1; }
|
|
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (char)(v % 10); v /= 10; }
|
|
while (i--) out[n++] = tmp[i];
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
put_ip4(char *out, const ip4_addr_t *a)
|
|
{
|
|
int n = 0;
|
|
n += put_u(out + n, ip4_addr1(a)); out[n++] = '.';
|
|
n += put_u(out + n, ip4_addr2(a)); out[n++] = '.';
|
|
n += put_u(out + n, ip4_addr3(a)); out[n++] = '.';
|
|
n += put_u(out + n, ip4_addr4(a));
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
put_mac(char *out, const unsigned char *m)
|
|
{
|
|
static const char d[] = "0123456789abcdef";
|
|
int n = 0;
|
|
for (int i = 0; i < 6; i++) {
|
|
if (i) out[n++] = ':';
|
|
out[n++] = d[m[i] >> 4];
|
|
out[n++] = d[m[i] & 0xF];
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// One "name value" pair per line, which is the shape /proc/schfs and
|
|
// /proc/fsstat already use here -- a reader that can scan one can scan all of
|
|
// them.
|
|
static int
|
|
put_field(char *out, const char *name)
|
|
{
|
|
int n = put_str(out, name);
|
|
while (n < 12)
|
|
out[n++] = ' ';
|
|
return n;
|
|
}
|
|
|
|
// One block of fields per interface, separated by a blank line. A reader that
|
|
// can parse one can parse any number, which is the whole reason it is not a
|
|
// single flat list of values: with two NICs a flat list has no way to say which
|
|
// address belongs to which.
|
|
// /proc/resolv.conf -- the nameservers, in the format a resolver expects.
|
|
//
|
|
// SchOS has no DNS client of its own and does not need one: musl contains a
|
|
// complete resolver, and it works over ordinary UDP sockets, which this system
|
|
// has. The only thing it was missing was the one fact a process cannot find
|
|
// out for itself -- *which* nameserver -- because that arrives in a DHCP reply
|
|
// the kernel handled.
|
|
//
|
|
// So this is the mechanism and nothing more. It renders resolv.conf's exact
|
|
// syntax so that putting it where the resolver looks is a copy rather than a
|
|
// translation, and deciding whether to do that is userspace's business; see
|
|
// cmd/resolvconf.
|
|
//
|
|
// Empty until DHCP answers, which is the honest report: a file saying nothing
|
|
// is right, and a file naming a server that does not exist is not.
|
|
int
|
|
net_resolv(char *buf, int cap)
|
|
{
|
|
int len = 0, i;
|
|
|
|
if (cap < 64 * DNS_MAX_SERVERS)
|
|
return 0;
|
|
if (!net_started)
|
|
return 0;
|
|
|
|
lwip_sys_lock();
|
|
for (i = 0; i < DNS_MAX_SERVERS; i++) {
|
|
const ip_addr_t *a = dns_getserver(i);
|
|
|
|
if (a == 0 || ip4_addr_isany_val(*ip_2_ip4(a)))
|
|
continue;
|
|
// put_field pads to a column, which is right for /proc/netif and
|
|
// wrong here: this file is not for looking at, it is for copying to
|
|
// where a resolver reads it.
|
|
len += put_str(buf + len, "nameserver ");
|
|
len += put_ip4(buf + len, ip_2_ip4(a));
|
|
buf[len++] = '\n';
|
|
}
|
|
lwip_sys_unlock();
|
|
return len;
|
|
}
|
|
|
|
int
|
|
net_info(char *buf, int cap)
|
|
{
|
|
int len = 0;
|
|
|
|
// Room for the largest block, so the loop below never needs a bounds check
|
|
// in the middle of writing one.
|
|
if (cap < 256 * (NETIF_MAX + 1))
|
|
return 0;
|
|
|
|
if (!net_started || net_eth_count() == 0) {
|
|
len += put_field(buf + len, "state");
|
|
len += put_str(buf + len, netdev_count() ? "down\n" : "no interface\n");
|
|
return len;
|
|
}
|
|
|
|
for (int i = 0; i < net_eth_count(); i++) {
|
|
struct netif *nif = net_eth_netif(i);
|
|
struct device *dev = net_eth_device(i);
|
|
|
|
if (i)
|
|
buf[len++] = '\n';
|
|
|
|
lwip_sys_lock();
|
|
|
|
len += put_field(buf + len, "interface");
|
|
len += put_str(buf + len, dev->name);
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "driver");
|
|
len += put_str(buf + len, dev->driver ? dev->driver : "-");
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "mac");
|
|
len += put_mac(buf + len, nif->hwaddr);
|
|
buf[len++] = '\n';
|
|
|
|
// Per interface, not per system: with two NICs one can be configured
|
|
// while the other is still asking.
|
|
len += put_field(buf + len, "state");
|
|
len += put_str(buf + len,
|
|
ip4_addr_isany_val(*netif_ip4_addr(nif)) ? "no address\n"
|
|
: "up\n");
|
|
|
|
len += put_field(buf + len, "address");
|
|
len += put_ip4(buf + len, netif_ip4_addr(nif));
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "netmask");
|
|
len += put_ip4(buf + len, netif_ip4_netmask(nif));
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "gateway");
|
|
len += put_ip4(buf + len, netif_ip4_gw(nif));
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "mtu");
|
|
len += put_u(buf + len, nif->mtu);
|
|
buf[len++] = '\n';
|
|
|
|
lwip_sys_unlock();
|
|
|
|
uint64 rx, tx;
|
|
net_eth_counts(i, &rx, &tx);
|
|
|
|
len += put_field(buf + len, "rx_frames");
|
|
len += put_u(buf + len, rx);
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "tx_frames");
|
|
len += put_u(buf + len, tx);
|
|
buf[len++] = '\n';
|
|
}
|
|
|
|
// How the network thread is being woken, for the whole stack rather than
|
|
// per interface. `wake_rx` staying at zero while frames arrive means the
|
|
// receive interrupt is not reaching us and the thread is running on the
|
|
// clock exactly as it did before -- which works, and is 10 ms slower.
|
|
{
|
|
uint64 wrx, wtick;
|
|
net_wake_counts(&wrx, &wtick);
|
|
len += put_field(buf + len, "wake_rx");
|
|
len += put_u(buf + len, wrx);
|
|
buf[len++] = '\n';
|
|
len += put_field(buf + len, "wake_tick");
|
|
len += put_u(buf + len, wtick);
|
|
buf[len++] = '\n';
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
// Start the stack. Called from main() once processes exist.
|
|
void
|
|
netinit(void)
|
|
{
|
|
if (net_started)
|
|
return;
|
|
// Before the check below, not after: the lock it initialises has to be
|
|
// valid on a machine with no NIC too. Nothing can reach a wait there --
|
|
// inet_new refuses while the stack is down -- but a lock whose validity
|
|
// depends on that reasoning holding forever is not worth the two lines
|
|
// saved.
|
|
inet_init();
|
|
if (netdev_count() == 0) {
|
|
// Silent: a machine with no network device is not misconfigured, and
|
|
// this runs on every boot.
|
|
return;
|
|
}
|
|
net_started = 1;
|
|
if (kthread_create(net_thread, "netd") < 0)
|
|
cprintf("net: could not start the network thread\n");
|
|
}
|