SchOS/include/socket.h
Nexoniarz f395c39704 net: AF_NETLINK, so getifaddrs() works for everything and not just one program
fastfetch reported "getifaddrs(&ifAddrStruct) failed", and the right answer to
that is not a patch to fastfetch. Asking what interfaces a machine has is
something every networked program does, and on Linux there is exactly one way:
a NETLINK_ROUTE socket. musl implements getifaddrs() over it, so a libc call
that looks like a syscall is really a small protocol conversation -- and without
this side of it every caller fails the same way.

kernel/net/netlink.c is a responder rather than a general socket family. No
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, not a shortcut -- getifaddrs sends a dump and reads
until NLMSG_DONE with MSG_DONTWAIT, so generating at send time means a read
never blocks and the non-blocking flag is honoured for free.

RTM_GETLINK returns one RTM_NEWLINK per interface with IFLA_IFNAME and
IFLA_ADDRESS; RTM_GETADDR returns one RTM_NEWADDR per address with IFA_ADDRESS,
IFA_LOCAL and IFA_LABEL. Anything else gets a bare NLMSG_DONE, because the
caller's next move is a read either way and an unexpected error message is worse
than nothing to read.

The data is not netlink's. Interfaces come from the netdev class and addresses
from lwIP's netifs, exactly as /proc/netif reads them; nothing in the file knows
what an e1000 is. Interface indices matter more than they look -- getifaddrs
matches address records to link records by index, so the two dumps have to
agree. Loopback is 1, netdevs follow from 2.

Verified by reading it back through musl rather than by inspecting the bytes:

    lo     flags=0x49    fam=-1
    eth0   flags=0x1043  fam=17                    (AF_PACKET, the MAC)
    lo     flags=0x49    fam=2  addr=127.0.0.1  mask=255.0.0.0
    eth0   flags=0x1043  fam=2  addr=10.0.2.15  mask=255.255.255.0

Correct flags, addresses and netmasks, and the link records pair with the
address records.

One thing this does not close: fastfetch's Local IP module defaults to
default-route-only, which calls ffNetifGetDefaultRouteIfName() and reads
/proc/net/route. SchOS has no routing table and no such file, so the module
still shows nothing -- now for a reason that is about routing rather than about
enumeration. That is the next file to provide.

socktest still passes; the AF_UNIX and AF_INET paths are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-12 19:23:34 +02:00

214 lines
6.1 KiB
C

#pragma once
#include "types.h"
// Unix domain sockets.
//
// These exist for Schcom. A compositor and its clients have to exchange
// messages, and a pipe cannot do it: a pipe is one-directional, has no
// notion of a connection, and gives no way for a server to be *found* by a
// client that starts later. A socket with a name in the filesystem is
// exactly the missing piece.
//
// AF_INET arrived later, and exactly as planned: underneath the same syscalls
// rather than reshaping them. A socket carries its domain and every entry point
// dispatches on it -- kernel/net/unix.c for AF_UNIX, kernel/net/inet.c (lwIP)
// for AF_INET.
#define AF_UNIX 1
#define AF_LOCAL 1
#define AF_INET 2
// Linux's number, because a musl program passes Linux's constant. getifaddrs()
// is implemented over this, which is why it exists here: enumerating interfaces
// is something every networked program does and nothing else offers.
#define AF_NETLINK 16
#define PF_NETLINK AF_NETLINK
#define NETLINK_ROUTE 0
#define SOCK_STREAM 1
#define SOCK_DGRAM 2
#define SOCK_RAW 3
// Flags ORed into socket()'s and accept4()'s `type`/`flags` argument. Linux's
// values, because a musl program passes Linux's constants.
#define SOCK_NONBLOCK 0x800 // O_NONBLOCK
#define SOCK_CLOEXEC 0x80000 // O_CLOEXEC
// setsockopt/getsockopt levels and names, Linux's numbering.
//
// Only what is listed here is accepted; anything else is answered with
// ENOPROTOOPT rather than a success it did not earn. See the comment above
// sys_setsockopt for why that is worth the risk of failing a caller that never
// checked before.
#define SOL_SOCKET 1
#define IPPROTO_ICMP 1
#define IPPROTO_TCP 6
#define SO_REUSEADDR 2
#define SO_TYPE 3
#define SO_ERROR 4
#define SO_BROADCAST 6
#define SO_SNDBUF 7
#define SO_RCVBUF 8
#define SO_KEEPALIVE 9
#define SO_ACCEPTCONN 30
#define TCP_NODELAY 1
#define UNIX_PATH_MAX 108
#define SHUT_RD 0
#define SHUT_WR 1
#define SHUT_RDWR 2
struct sockaddr_un {
uint16 sun_family;
char sun_path[UNIX_PATH_MAX];
};
// The generic form syscalls take. Same shape as Linux's, so a musl-linked
// program's struct lines up byte for byte.
struct sockaddr {
uint16 sa_family;
char sa_data[UNIX_PATH_MAX];
};
// The AF_INET address. Same layout as Linux's, so a musl-linked program's
// struct lines up byte for byte. sin_port and sin_addr are network byte order
// and stay that way: lwIP wants them so, and converting on the way in and out
// again would be two swaps that cancel.
struct sockaddr_in {
uint16 sin_family;
uint16 sin_port;
uint32 sin_addr;
char sin_zero[8];
};
// ---------------------------------------------------------------------------
// rtnetlink, enough of it for getifaddrs(3)
//
// Every structure below is Linux's, byte for byte, because musl parses what it
// is handed with Linux's own headers. They are not ours to design.
//
// What musl actually does (src/network/netlink.c) is the whole specification
// this has to meet: open PF_NETLINK/SOCK_RAW/NETLINK_ROUTE, send an
// nlmsghdr+rtgenmsg asking RTM_GETLINK with NLM_F_DUMP, read replies until
// NLMSG_DONE, then do it again for RTM_GETADDR on the same socket.
// ---------------------------------------------------------------------------
struct nlmsghdr {
uint32 nlmsg_len;
uint16 nlmsg_type;
uint16 nlmsg_flags;
uint32 nlmsg_seq;
uint32 nlmsg_pid;
};
struct rtgenmsg { uchar rtgen_family; };
struct ifinfomsg {
uchar ifi_family;
uchar __ifi_pad;
uint16 ifi_type; // ARPHRD_*
int32 ifi_index;
uint32 ifi_flags; // IFF_*
uint32 ifi_change;
};
struct ifaddrmsg {
uchar ifa_family;
uchar ifa_prefixlen;
uchar ifa_flags;
uchar ifa_scope;
uint32 ifa_index;
};
struct rtattr {
uint16 rta_len;
uint16 rta_type;
};
#define NLMSG_ERROR 2
#define NLMSG_DONE 3
#define NLM_F_REQUEST 0x001
#define NLM_F_MULTI 0x002
#define NLM_F_DUMP 0x300
#define RTM_NEWLINK 16
#define RTM_GETLINK 18
#define RTM_NEWADDR 20
#define RTM_GETADDR 22
#define IFLA_ADDRESS 1
#define IFLA_IFNAME 3
#define IFA_ADDRESS 1
#define IFA_LOCAL 2
#define IFA_LABEL 3
#define ARPHRD_ETHER 1
#define ARPHRD_LOOPBACK 772
#define IFF_UP 0x1
#define IFF_LOOPBACK 0x8
#define IFF_RUNNING 0x40
#define IFF_BROADCAST 0x2
#define IFF_MULTICAST 0x1000
// Both are four-byte aligned on every architecture Linux supports.
#define NLMSG_ALIGN(n) (((n) + 3) & ~3)
#define RTA_ALIGN(n) (((n) + 3) & ~3)
#ifdef _KERNEL_SOCKET_INTERNALS
#define NSOCK 32 // sockets in the system
#define SOCKBUF 4096 // receive buffer per connected socket
#define NBACKLOG 8
enum sockstate {
SS_FREE = 0,
SS_UNBOUND,
SS_BOUND,
SS_LISTENING,
SS_CONNECTED,
};
// Every field here is protected by the socket table's single lock in
// kernel/net/unix.c -- there is deliberately no per-socket lock; see the
// comment at the top of that file for why.
struct socket {
int ref;
int type;
int state;
int domain; // AF_UNIX or AF_INET
// For AF_INET: the lwIP-backed socket in kernel/net/inet.c. Opaque here so
// that socket.h does not have to drag lwIP's headers into every file that
// wants a struct socket.
void *inet;
char name[UNIX_PATH_MAX]; // the bound path, for a listener
// A connected socket's peer. Writing to a socket appends to the *peer's*
// buffer, which is what makes one buffer per socket enough for a
// bidirectional connection.
struct socket *peer;
int peer_closed;
char data[SOCKBUF];
uint nread, nwrite;
// Connections waiting to be accepted, on a listener.
struct socket *queue[NBACKLOG];
int nqueue;
// Socket options that are genuinely honoured. Kept here rather than only in
// lwIP's pcb because getsockopt has to answer for an AF_UNIX socket too, and
// because a flag set before bind() has to survive until there is a pcb to
// apply it to.
int o_reuseaddr;
int o_keepalive;
int o_broadcast;
int o_nodelay;
};
#endif