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>
244 lines
8 KiB
C
244 lines
8 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// rtnetlink, enough of it that getifaddrs(3) works.
|
|
//
|
|
// Why this exists rather than a patch to whatever program wanted it: asking
|
|
// "what interfaces does this machine have, and what addresses do they carry" is
|
|
// something every networked program does, and on Linux there is exactly one
|
|
// answer -- a NETLINK_ROUTE socket. musl implements getifaddrs() over it, so a
|
|
// libc call that looks like it should be a syscall is really a small protocol
|
|
// conversation, and without this side of it every caller fails identically.
|
|
//
|
|
// It is a *responder*, not a socket family in the general sense. There is no
|
|
// kernel-to-userspace event stream, no multicast groups, nothing subscribes to
|
|
// link state. A request arrives, the whole answer is generated into the
|
|
// socket's buffer, and reads drain it. That shape is chosen deliberately:
|
|
// getifaddrs sends a dump request and reads until NLMSG_DONE, so generating the
|
|
// answer at send time means a read never has to block and MSG_DONTWAIT -- which
|
|
// musl passes -- is trivially honoured.
|
|
//
|
|
// The information is not this file's. Interfaces come from the netdev class
|
|
// (include/netdev.h) and addresses from lwIP's netifs, exactly as /proc/netif
|
|
// reads them. Nothing here knows what an e1000 is.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "mmu.h"
|
|
#include "proc.h"
|
|
#include "fs.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "file.h"
|
|
#include "errno.h"
|
|
#include "device.h"
|
|
#include "netdev.h"
|
|
|
|
#define _KERNEL_SOCKET_INTERNALS
|
|
#include "socket.h"
|
|
|
|
#include "lwip/netif.h"
|
|
#include "lwip/ip4_addr.h"
|
|
|
|
// Writing into a fixed buffer, with every append bounds-checked against the
|
|
// same `cap`. A netlink dump is the kind of code where a length field and a
|
|
// memmove disagree quietly, so there is one helper and everything goes through
|
|
// it.
|
|
struct nlbuf {
|
|
char *p;
|
|
uint len;
|
|
uint cap;
|
|
int full;
|
|
};
|
|
|
|
static void *
|
|
nl_take(struct nlbuf *b, uint n)
|
|
{
|
|
if (b->full || b->len + n > b->cap) {
|
|
b->full = 1;
|
|
return 0;
|
|
}
|
|
void *at = b->p + b->len;
|
|
memset(at, 0, n);
|
|
b->len += n;
|
|
return at;
|
|
}
|
|
|
|
// One attribute: header, payload, padding to a 4-byte boundary. The header's
|
|
// length counts itself and the payload but *not* the padding -- getting that
|
|
// backwards is the classic netlink bug, and it desynchronises the reader's walk
|
|
// rather than failing outright.
|
|
static void
|
|
nl_attr(struct nlbuf *b, uint16 type, const void *val, uint vlen)
|
|
{
|
|
uint total = RTA_ALIGN(sizeof(struct rtattr) + vlen);
|
|
struct rtattr *a = nl_take(b, total);
|
|
if (!a)
|
|
return;
|
|
a->rta_type = type;
|
|
a->rta_len = (uint16)(sizeof(struct rtattr) + vlen);
|
|
memmove((char *)a + sizeof(struct rtattr), val, vlen);
|
|
}
|
|
|
|
// Start a message, returning where its length must be written once the
|
|
// attributes are known.
|
|
static struct nlmsghdr *
|
|
nl_msg(struct nlbuf *b, uint16 type, uint32 seq, uint body)
|
|
{
|
|
struct nlmsghdr *h = nl_take(b, sizeof(*h) + NLMSG_ALIGN(body));
|
|
if (!h)
|
|
return 0;
|
|
h->nlmsg_type = type;
|
|
h->nlmsg_flags = NLM_F_MULTI;
|
|
h->nlmsg_seq = seq;
|
|
h->nlmsg_pid = 0;
|
|
return h;
|
|
}
|
|
|
|
static void
|
|
nl_done(struct nlbuf *b, uint32 seq)
|
|
{
|
|
struct nlmsghdr *h = nl_take(b, sizeof(*h));
|
|
if (!h)
|
|
return;
|
|
h->nlmsg_len = sizeof(*h);
|
|
h->nlmsg_type = NLMSG_DONE;
|
|
h->nlmsg_seq = seq;
|
|
}
|
|
|
|
// Interface index. Linux numbers from 1 and reserves nothing, but getifaddrs
|
|
// matches RTM_NEWADDR records to RTM_NEWLINK records *by index*, so the two
|
|
// dumps below have to agree. Loopback is 1 and the netdevs follow.
|
|
#define NL_LO_INDEX 1
|
|
#define NL_DEV_BASE 2
|
|
|
|
static void
|
|
dump_links(struct nlbuf *b, uint32 seq)
|
|
{
|
|
// Loopback first, and it is not a netdev: it has no driver, no MAC and no
|
|
// entry in the device model. It is a netif lwIP creates, and a program
|
|
// enumerating interfaces still expects to see it.
|
|
{
|
|
struct nlmsghdr *h = nl_msg(b, RTM_NEWLINK, seq, sizeof(struct ifinfomsg));
|
|
if (!h) return;
|
|
struct ifinfomsg *ifi = (struct ifinfomsg *)((char *)h + sizeof(*h));
|
|
ifi->ifi_family = 0; /* AF_UNSPEC */
|
|
ifi->ifi_type = ARPHRD_LOOPBACK;
|
|
ifi->ifi_index = NL_LO_INDEX;
|
|
ifi->ifi_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
|
|
nl_attr(b, IFLA_IFNAME, "lo", 3);
|
|
h->nlmsg_len = b->len - (uint)((char *)h - b->p);
|
|
}
|
|
|
|
for (int i = 0; i < netdev_count(); i++) {
|
|
struct device *d = netdev_at(i);
|
|
if (!d) continue;
|
|
|
|
struct nlmsghdr *h = nl_msg(b, RTM_NEWLINK, seq, sizeof(struct ifinfomsg));
|
|
if (!h) return;
|
|
struct ifinfomsg *ifi = (struct ifinfomsg *)((char *)h + sizeof(*h));
|
|
ifi->ifi_family = 0;
|
|
ifi->ifi_type = ARPHRD_ETHER;
|
|
ifi->ifi_index = NL_DEV_BASE + i;
|
|
ifi->ifi_flags = IFF_BROADCAST | IFF_MULTICAST
|
|
| (netdev_link_up(d) ? (IFF_UP | IFF_RUNNING) : 0);
|
|
|
|
nl_attr(b, IFLA_IFNAME, d->name, (uint)strlen(d->name) + 1);
|
|
const unsigned char *mac = netdev_mac(d);
|
|
if (mac)
|
|
nl_attr(b, IFLA_ADDRESS, mac, 6);
|
|
|
|
h->nlmsg_len = b->len - (uint)((char *)h - b->p);
|
|
}
|
|
}
|
|
|
|
// How many leading ones are in a netmask. getifaddrs does not use the prefix
|
|
// length itself -- it reconstructs the netmask from it -- so an answer that
|
|
// disagrees with the netmask is a wrong answer nobody notices until something
|
|
// computes a broadcast address.
|
|
static uchar
|
|
prefix_of(uint32 mask_net_order)
|
|
{
|
|
uint32 m = ntohl_u32(mask_net_order);
|
|
uchar n = 0;
|
|
while (m & 0x80000000u) { n++; m <<= 1; }
|
|
return n;
|
|
}
|
|
|
|
static void
|
|
addr_msg(struct nlbuf *b, uint32 seq, int index, uint32 addr, uchar prefix,
|
|
const char *label, int loopback)
|
|
{
|
|
struct nlmsghdr *h = nl_msg(b, RTM_NEWADDR, seq, sizeof(struct ifaddrmsg));
|
|
if (!h) return;
|
|
struct ifaddrmsg *ifa = (struct ifaddrmsg *)((char *)h + sizeof(*h));
|
|
ifa->ifa_family = AF_INET;
|
|
ifa->ifa_prefixlen = prefix;
|
|
ifa->ifa_flags = 0;
|
|
ifa->ifa_scope = loopback ? 254 : 0; /* RT_SCOPE_HOST : UNIVERSE */
|
|
ifa->ifa_index = (uint32)index;
|
|
|
|
// IFA_ADDRESS and IFA_LOCAL are the same on anything that is not a
|
|
// point-to-point link. musl reads whichever arrives and treats a second one
|
|
// as the peer address, so sending both in this order is what makes it record
|
|
// the address rather than a destination.
|
|
nl_attr(b, IFA_ADDRESS, &addr, 4);
|
|
nl_attr(b, IFA_LOCAL, &addr, 4);
|
|
nl_attr(b, IFA_LABEL, label, (uint)strlen(label) + 1);
|
|
|
|
h->nlmsg_len = b->len - (uint)((char *)h - b->p);
|
|
}
|
|
|
|
static void
|
|
dump_addrs(struct nlbuf *b, uint32 seq)
|
|
{
|
|
addr_msg(b, seq, NL_LO_INDEX, htonl_u32(0x7F000001), 8, "lo", 1);
|
|
|
|
for (int i = 0; i < netdev_count(); i++) {
|
|
struct device *d = netdev_at(i);
|
|
if (!d) continue;
|
|
struct netif *nif = net_eth_netif(i);
|
|
if (!nif) continue;
|
|
|
|
uint32 a = netif_addr4(nif);
|
|
if (a == 0)
|
|
continue; /* no lease yet: not an address */
|
|
addr_msg(b, seq, NL_DEV_BASE + i, a, prefix_of(netif_mask4(nif)), d->name, 0);
|
|
}
|
|
}
|
|
|
|
// A request has arrived. Generate the whole answer now; see the note at the top
|
|
// about why this is not done lazily on read.
|
|
//
|
|
// Returns the number of bytes of `req` consumed, which is all of them: a
|
|
// malformed request is answered with an empty dump rather than an error,
|
|
// because the caller's next move is a read either way and an error message it
|
|
// does not expect is worse than nothing to read.
|
|
int
|
|
netlink_request(const char *req, uint reqlen, char *out, uint outcap)
|
|
{
|
|
struct nlbuf b = { .p = out, .len = 0, .cap = outcap, .full = 0 };
|
|
|
|
if (reqlen < sizeof(struct nlmsghdr))
|
|
return 0;
|
|
|
|
const struct nlmsghdr *h = (const struct nlmsghdr *)req;
|
|
uint32 seq = h->nlmsg_seq;
|
|
|
|
switch (h->nlmsg_type) {
|
|
case RTM_GETLINK: dump_links(&b, seq); break;
|
|
case RTM_GETADDR: dump_addrs(&b, seq); break;
|
|
default: /* nothing to say about it */
|
|
break;
|
|
}
|
|
nl_done(&b, seq);
|
|
|
|
// Truncation would hand the reader a half-written message and it would walk
|
|
// off the end of it. Better to answer with only the terminator: an empty
|
|
// interface list is a legible answer, a corrupt one is not.
|
|
if (b.full) {
|
|
b.len = 0; b.full = 0;
|
|
nl_done(&b, seq);
|
|
}
|
|
return (int)b.len;
|
|
}
|