Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
244 lines
8.1 KiB
C
244 lines
8.1 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/dev/netdev.h) and addresses from lwIP's netifs, exactly as /proc/netif
|
|
// reads them. Nothing here knows what an e1000 is.
|
|
|
|
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "arch/mmu.h"
|
|
#include "core/proc.h"
|
|
#include "fs/fs.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/file.h"
|
|
#include "uapi/errno.h"
|
|
#include "dev/device.h"
|
|
#include "dev/netdev.h"
|
|
|
|
#define _KERNEL_SOCKET_INTERNALS
|
|
#include "net/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;
|
|
}
|