mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-25 16:52:29 +00:00
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
227 lines
7.3 KiB
C
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;
|
|
}
|