SchOS/kernel/net/netlink.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

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;
}