Files
SUB-OS/net/ipv4/udp.c
T
subhobhai943andClaude Opus 4.8 cd840d5799 feat(net): real UDP transport, BSD datagram sockets, DNS resolver, DHCP client
Turn the networking stubs into a working transport/application stack on top of
the existing L2/L3 (Ethernet/ARP/IPv4/ICMP) core.

UDP (net/ipv4/udp.c)
  - Port-demux table with bound receive handlers, pseudo-header checksum,
    ephemeral-port allocation, limited-broadcast send (0.0.0.0 -> 255.255.255.255
    for DHCP before the interface has an address), and rx/tx/drop counters.
  - Wired into the IPv4 receive path (IP_PROTO_UDP dispatch in net/core/net.c).

BSD sockets (net/core/socket.c)
  - Real SOCK_DGRAM sockets backed by UDP: bind installs a UDP handler that fills
    a per-socket receive ring; send/sendto and recv/recvfrom move datagrams;
    ephemeral auto-bind, connect() memorises the peer. Socket table + I/O stats
    exposed for introspection.

DNS resolver (net/ipv4/dns.c)
  - Builds and parses real A-record queries over UDP/53 (label encoding,
    compression-pointer aware answer walk), cache-first with TTL, static-hosts
    and dotted-quad fast paths, and a bounded timeout that degrades to fallback
    instead of hanging.

DHCP client (net/ipv4/dhcp.c)
  - Full DISCOVER/OFFER/REQUEST/ACK handshake over UDP broadcast, option parsing
    (mask/router/dns/lease/server-id), applies the lease to the primary
    interface, bounded timeouts keep the static config on no response.

Fix (net/ipv4/dns_cache.c): the cache stored IPs as 0xAABBCCDD numeric while the
rest of the stack uses network-byte-order-in-memory (ip_parse/ip_to_str), so a
resolved address came back byte-swapped (nslookup github.com -> 100.121.82.140).
Seeds and dump now use ip_parse/ip_to_str; verified 140.82.121.4.

Userland (lazybox): live ifconfig (real interface + NIC counters, loopback),
netstat with live UDP bindings and transport stats, new `sockstat`, richer
nslookup. Verified in QEMU: boot clean on x86_64/aarch64/armv8i, login as SUB,
ping/nslookup/dhclient/netstat/sockstat all run; DHCP/DNS wire replies are
blocked by the sandbox's slirp but time out cleanly and fall back as designed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-26 10:20:58 +05:30

227 lines
7.3 KiB
C

// UDP (User Datagram Protocol) transport for SUB-OS.
//
// Provides connectionless datagram delivery on top of the IPv4 layer. Inbound
// datagrams are demultiplexed by destination port to a small table of bound
// handlers (used by the socket layer, the DNS resolver and the DHCP client);
// unclaimed ports are counted as drops. Outbound datagrams are checksummed with
// the standard IPv4 pseudo-header.
#include <net/udp.h>
#include <mm/kmalloc.h>
#include <lib/string.h>
#include <lib/printf.h>
#include <kernel/printk.h>
#define UDP_MAX_BINDINGS 32
#define UDP_EPHEMERAL_LO 49152u
#define UDP_EPHEMERAL_HI 65535u
typedef struct {
uint16_t port;
udp_handler_t handler;
void* ctx;
bool active;
} udp_binding_t;
// Pseudo-header used only for checksum computation (never transmitted).
typedef struct {
uint32_t src_ip;
uint32_t dst_ip;
uint8_t zero;
uint8_t protocol;
uint16_t udp_len;
} __attribute__((packed)) udp_pseudo_header_t;
static udp_binding_t bindings[UDP_MAX_BINDINGS];
static uint16_t next_ephemeral = UDP_EPHEMERAL_LO;
static volatile uint64_t stat_rx = 0;
static volatile uint64_t stat_tx = 0;
static volatile uint64_t stat_drop = 0;
static uint16_t udp_checksum(uint32_t src_ip, uint32_t dst_ip,
const udp_header_t* udp, const void* payload,
uint16_t payload_len) {
udp_pseudo_header_t ph;
ph.src_ip = src_ip;
ph.dst_ip = dst_ip;
ph.zero = 0;
ph.protocol = IP_PROTO_UDP;
ph.udp_len = htons((uint16_t)(sizeof(udp_header_t) + payload_len));
uint32_t sum = 0;
const uint16_t* p = (const uint16_t*)&ph;
for (size_t i = 0; i < sizeof(ph) / 2; i++) sum += *p++;
p = (const uint16_t*)udp;
for (size_t i = 0; i < sizeof(udp_header_t) / 2; i++) sum += *p++;
if (payload && payload_len > 0) {
p = (const uint16_t*)payload;
size_t len = payload_len;
while (len > 1) { sum += *p++; len -= 2; }
if (len > 0) sum += *(const uint8_t*)p;
}
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
uint16_t cksum = (uint16_t)(~sum);
// A zero checksum means "not computed" on the wire; send 0xFFFF instead.
return cksum ? cksum : 0xFFFF;
}
void udp_init(void) {
memset(bindings, 0, sizeof(bindings));
next_ephemeral = UDP_EPHEMERAL_LO;
stat_rx = stat_tx = stat_drop = 0;
printk(KERN_INFO "NET: UDP (User Datagram Protocol) transport layer online\n");
}
int udp_bind(uint16_t port, udp_handler_t handler, void* ctx) {
if (!handler || port == 0) return -1;
for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
if (bindings[i].active && bindings[i].port == port) return -1;
}
for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
if (!bindings[i].active) {
bindings[i].port = port;
bindings[i].handler = handler;
bindings[i].ctx = ctx;
bindings[i].active = true;
return 0;
}
}
return -1;
}
void udp_unbind(uint16_t port) {
for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
if (bindings[i].active && bindings[i].port == port) {
bindings[i].active = false;
bindings[i].handler = NULL;
bindings[i].ctx = NULL;
return;
}
}
}
uint16_t udp_alloc_ephemeral_port(void) {
for (uint32_t tries = 0; tries <= (UDP_EPHEMERAL_HI - UDP_EPHEMERAL_LO); tries++) {
uint16_t candidate = next_ephemeral;
if (next_ephemeral >= UDP_EPHEMERAL_HI) next_ephemeral = UDP_EPHEMERAL_LO;
else next_ephemeral++;
bool taken = false;
for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
if (bindings[i].active && bindings[i].port == candidate) { taken = true; break; }
}
if (!taken) return candidate;
}
return 0;
}
void udp_receive(const uint8_t* packet, uint16_t length, uint32_t src_ip) {
if (!packet || length < sizeof(udp_header_t)) { stat_drop++; return; }
const udp_header_t* udp = (const udp_header_t*)packet;
uint16_t dst_port = ntohs(udp->dst_port);
uint16_t src_port = ntohs(udp->src_port);
uint16_t udp_len = ntohs(udp->length);
if (udp_len < sizeof(udp_header_t) || udp_len > length) { stat_drop++; return; }
const uint8_t* data = packet + sizeof(udp_header_t);
uint16_t data_len = udp_len - sizeof(udp_header_t);
stat_rx++;
for (int i = 0; i < UDP_MAX_BINDINGS; i++) {
if (bindings[i].active && bindings[i].port == dst_port && bindings[i].handler) {
bindings[i].handler(src_ip, src_port, dst_port, data, data_len, bindings[i].ctx);
return;
}
}
// No listener on this port.
stat_drop++;
}
void udp_send(uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
const void* data, uint16_t len) {
uint16_t total = (uint16_t)(sizeof(udp_header_t) + len);
uint8_t* buf = (uint8_t*)kzalloc(total);
if (!buf) return;
udp_header_t* udp = (udp_header_t*)buf;
udp->src_port = htons(src_port);
udp->dst_port = htons(dst_port);
udp->length = htons(total);
udp->checksum = 0;
if (data && len > 0) memcpy(buf + sizeof(udp_header_t), data, len);
net_if_t* nif = net_get_primary_if();
uint32_t src_ip = nif ? nif->ip : 0;
udp->checksum = udp_checksum(src_ip, dst_ip, udp, data, len);
net_send_ip(dst_ip, IP_PROTO_UDP, buf, total);
stat_tx++;
kfree(buf);
}
void udp_send_broadcast(uint16_t src_port, uint16_t dst_port,
const void* data, uint16_t len) {
// Build IPv4 + UDP by hand: src 0.0.0.0, dst 255.255.255.255, sent to the
// Ethernet broadcast address without ARP (the interface has no IP yet).
static const uint8_t bcast_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint16_t udp_total = (uint16_t)(sizeof(udp_header_t) + len);
uint16_t ip_total = (uint16_t)(sizeof(ip_header_t) + udp_total);
uint8_t* buf = (uint8_t*)kzalloc(ip_total);
if (!buf) return;
ip_header_t* ip = (ip_header_t*)buf;
ip->ihl_version = 0x45;
ip->tos = 0;
ip->total_length = htons(ip_total);
ip->id = htons(0x5342); // 'SB'
ip->flags_fragment = 0;
ip->ttl = 64;
ip->protocol = IP_PROTO_UDP;
ip->checksum = 0;
ip->src_ip = 0x00000000;
ip->dst_ip = 0xFFFFFFFF;
// IPv4 header checksum.
{
uint32_t sum = 0;
const uint16_t* p = (const uint16_t*)ip;
for (size_t i = 0; i < sizeof(ip_header_t) / 2; i++) sum += *p++;
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
ip->checksum = (uint16_t)(~sum);
}
udp_header_t* udp = (udp_header_t*)(buf + sizeof(ip_header_t));
udp->src_port = htons(src_port);
udp->dst_port = htons(dst_port);
udp->length = htons(udp_total);
udp->checksum = 0;
if (data && len > 0) memcpy((uint8_t*)udp + sizeof(udp_header_t), data, len);
udp->checksum = udp_checksum(0x00000000, 0xFFFFFFFF, udp, data, len);
net_send_eth(bcast_mac, ETHERTYPE_IPV4, buf, ip_total);
stat_tx++;
kfree(buf);
}
void udp_get_stats(uint64_t* rx, uint64_t* tx, uint64_t* drop) {
if (rx) *rx = stat_rx;
if (tx) *tx = stat_tx;
if (drop) *drop = stat_drop;
}
int udp_get_bindings(uint16_t* ports_out, int max) {
int n = 0;
for (int i = 0; i < UDP_MAX_BINDINGS && n < max; i++) {
if (bindings[i].active) ports_out[n++] = bindings[i].port;
}
return n;
}