Files
SUB-OS/net/ipv4/dhcp.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

221 lines
7.2 KiB
C

// DHCP (Dynamic Host Configuration Protocol) client for SUB-OS.
//
// Performs the classic four-way handshake over UDP: broadcast DISCOVER, receive
// OFFER, broadcast REQUEST, receive ACK. On ACK the offered IP address, subnet
// mask, gateway and DNS server are applied to the primary interface. Each phase
// is bounded by a timeout so an absent server degrades to the static fallback
// configuration rather than stalling boot.
#include <net/dhcp.h>
#include <net/udp.h>
#include <net/net.h>
#include <mm/kmalloc.h>
#include <lib/string.h>
#include <lib/printf.h>
#include <kernel/printk.h>
#include <arch/arch.h>
#define DHCP_SERVER_PORT 67
#define DHCP_CLIENT_PORT 68
#define DHCP_MAGIC 0x63825363u
#define DHCP_TIMEOUT_MS 2500
// BOOTP message ops and DHCP message types (option 53).
#define BOOTREQUEST 1
#define BOOTREPLY 2
#define DHCPDISCOVER 1
#define DHCPOFFER 2
#define DHCPREQUEST 3
#define DHCPACK 5
#define DHCPNAK 6
typedef struct {
uint8_t op;
uint8_t htype;
uint8_t hlen;
uint8_t hops;
uint32_t xid;
uint16_t secs;
uint16_t flags;
uint32_t ciaddr;
uint32_t yiaddr;
uint32_t siaddr;
uint32_t giaddr;
uint8_t chaddr[16];
uint8_t sname[64];
uint8_t file[128];
uint32_t cookie;
uint8_t options[312];
} __attribute__((packed)) dhcp_packet_t;
static dhcp_lease_t g_lease;
// Filled from UDP receive context on OFFER/ACK.
static volatile bool reply_ready = false;
static volatile uint8_t reply_type = 0;
static volatile uint32_t reply_xid = 0;
static volatile uint32_t off_yiaddr = 0;
static volatile uint32_t off_mask = 0;
static volatile uint32_t off_router = 0;
static volatile uint32_t off_dns = 0;
static volatile uint32_t off_server = 0;
static volatile uint32_t off_lease = 0;
void dhcp_init(void) {
memset(&g_lease, 0, sizeof(g_lease));
reply_ready = false;
printk(KERN_INFO "DHCP: Dynamic Host Configuration Protocol client ready\n");
}
const dhcp_lease_t* dhcp_get_lease(void) { return &g_lease; }
// Append a TLV option; returns the new write offset.
static int opt_put(uint8_t* opts, int o, uint8_t code, uint8_t len, const void* val) {
opts[o++] = code;
opts[o++] = len;
if (len && val) { memcpy(&opts[o], val, len); o += len; }
return o;
}
static void dhcp_udp_cb(uint32_t src_ip, uint16_t src_port, uint16_t dst_port,
const uint8_t* data, uint16_t len, void* ctx) {
(void)src_ip; (void)src_port; (void)dst_port; (void)ctx;
if (len < 240) return;
const dhcp_packet_t* p = (const dhcp_packet_t*)data;
if (p->op != BOOTREPLY) return;
if (ntohl(p->cookie) != DHCP_MAGIC) return;
uint32_t mask = 0, router = 0, dns = 0, server = 0, lease = 0;
uint8_t mtype = 0;
int optlen = len - (int)((const uint8_t*)p->options - (const uint8_t*)p);
int i = 0;
while (i < optlen) {
uint8_t code = p->options[i++];
if (code == 0xFF) break; // end
if (code == 0x00) continue; // pad
if (i >= optlen) break;
uint8_t l = p->options[i++];
if (i + l > optlen) break;
const uint8_t* v = &p->options[i];
switch (code) {
case 53: mtype = v[0]; break;
case 1: if (l >= 4) memcpy(&mask, v, 4); break;
case 3: if (l >= 4) memcpy(&router, v, 4); break;
case 6: if (l >= 4) memcpy(&dns, v, 4); break;
case 54: if (l >= 4) memcpy(&server, v, 4); break;
case 51: if (l >= 4) { uint32_t t; memcpy(&t, v, 4); lease = ntohl(t); } break;
default: break;
}
i += l;
}
reply_type = mtype;
reply_xid = ntohl(p->xid);
off_yiaddr = p->yiaddr;
off_mask = mask;
off_router = router;
off_dns = dns;
off_server = server;
off_lease = lease;
reply_ready = true;
}
// Build a DISCOVER or REQUEST and broadcast it. requested_ip/server are set for
// REQUEST (0 for DISCOVER).
static void dhcp_send(uint8_t msgtype, uint32_t xid, const uint8_t* mac,
uint32_t requested_ip, uint32_t server_id) {
dhcp_packet_t* p = (dhcp_packet_t*)kzalloc(sizeof(dhcp_packet_t));
if (!p) return;
p->op = BOOTREQUEST;
p->htype = 1; // Ethernet
p->hlen = 6;
p->xid = htonl(xid);
p->flags = htons(0x8000); // ask the server to broadcast its reply
memcpy(p->chaddr, mac, 6);
p->cookie = htonl(DHCP_MAGIC);
int o = 0;
o = opt_put(p->options, o, 53, 1, &msgtype);
if (msgtype == DHCPREQUEST) {
if (requested_ip) o = opt_put(p->options, o, 50, 4, &requested_ip);
if (server_id) o = opt_put(p->options, o, 54, 4, &server_id);
}
uint8_t plist[4] = {1, 3, 6, 15}; // mask, router, dns, domain
o = opt_put(p->options, o, 55, sizeof(plist), plist);
p->options[o++] = 0xFF; // end
int fixed = (int)((uint8_t*)p->options - (uint8_t*)p);
udp_send_broadcast(DHCP_CLIENT_PORT, DHCP_SERVER_PORT, p, (uint16_t)(fixed + o));
kfree(p);
}
static bool wait_reply(uint32_t xid, uint8_t want_type) {
uint64_t t0 = pit_get_ticks();
uint64_t wait_ticks = DHCP_TIMEOUT_MS / 10;
while (pit_get_ticks() - t0 < wait_ticks) {
if (reply_ready && reply_xid == xid && reply_type == want_type) return true;
arch_halt();
}
return false;
}
int dhcp_request_lease(void) {
net_if_t* nif = net_get_primary_if();
if (!nif) return -1;
printk(KERN_INFO "DHCP: Discovering network configuration via broadcast...\n");
if (udp_bind(DHCP_CLIENT_PORT, dhcp_udp_cb, NULL) != 0) {
printk(ANSI_YELLOW "DHCP: client port 68 unavailable\n" ANSI_RESET);
return -1;
}
uint32_t xid = (uint32_t)(0x53554200u ^ pit_get_ticks());
int rc = -1;
// DISCOVER -> OFFER
reply_ready = false;
dhcp_send(DHCPDISCOVER, xid, nif->mac, 0, 0);
if (wait_reply(xid, DHCPOFFER)) {
uint32_t offered = off_yiaddr;
uint32_t server = off_server;
char ips[16]; ip_to_str(offered, ips);
printk(KERN_INFO "DHCP: OFFER received: %s\n", ips);
// REQUEST -> ACK
reply_ready = false;
dhcp_send(DHCPREQUEST, xid, nif->mac, offered, server);
if (wait_reply(xid, DHCPACK)) {
nif->ip = off_yiaddr;
if (off_mask) nif->subnet = off_mask;
if (off_router) nif->gateway = off_router;
if (off_dns) nif->dns = off_dns;
g_lease.ip = nif->ip;
g_lease.mask = nif->subnet;
g_lease.gateway = nif->gateway;
g_lease.dns = nif->dns;
g_lease.server = off_server;
g_lease.lease_secs = off_lease ? off_lease : 86400;
g_lease.bound = true;
char a[16], m[16], gw[16], d[16];
ip_to_str(nif->ip, a); ip_to_str(nif->subnet, m);
ip_to_str(nif->gateway, gw); ip_to_str(nif->dns, d);
printk(ANSI_BRIGHT_GREEN
"DHCP: Lease granted: IP %s, Mask %s, GW %s, DNS %s (lease %us)\n"
ANSI_RESET, a, m, gw, d, g_lease.lease_secs);
rc = 0;
}
}
udp_unbind(DHCP_CLIENT_PORT);
if (rc != 0) {
char a[16]; ip_to_str(nif->ip, a);
printk(ANSI_YELLOW "DHCP: no response; keeping static config %s\n" ANSI_RESET, a);
}
return rc;
}