Files
claude-os/src/dhcp.c
AI d7ce0d5856 Implement DHCP client subsystem and dhcp app (AI)
- Created src/dhcp.h: DHCP packet struct, lease info struct, message types,
  options codes, client states, discover/receive/get_lease API
- Created src/dhcp.c: DHCP client with DISCOVER/OFFER/REQUEST/ACK flow,
  manual IP+UDP header construction for broadcast, option parsing for
  subnet mask/router/DNS/lease time/server ID, lease table, auto-applies
  configuration to ethernet interface on ACK, sysfs /sys/dhcp/status
- Created apps/dhcp/dhcp.c: reads /sys/dhcp/status to display DHCP info
- Kernel calls dhcp_init() at boot
- Tested: clean boot, DHCP initialized, dhcp app in CPIO
2026-02-24 07:35:20 +00:00

553 lines
16 KiB
C

/**
* @file dhcp.c
* @brief DHCP client implementation.
*
* Implements the DHCP client protocol (RFC 2131) for automatic IPv4
* address configuration. Communicates via UDP (port 68→67) over
* Ethernet broadcasts.
*
* Since we don't have a full UDP stack yet, DHCP packets are
* constructed manually with IP+UDP headers and sent via the
* Ethernet subsystem directly.
*
* Status is exposed via sysfs at /sys/dhcp.
*/
#include "dhcp.h"
#include "ethernet.h"
#include "ipv4.h"
#include "arp.h"
#include "sysfs.h"
#include <string.h>
/* Debug print helpers */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/* ================================================================
* UDP header (for DHCP — minimal inline UDP)
* ================================================================ */
/** UDP header structure. */
typedef struct __attribute__((packed)) udp_header {
uint16_t src_port;
uint16_t dst_port;
uint16_t length;
uint16_t checksum;
} udp_header_t;
#define UDP_HLEN 8
/* ================================================================
* Global state
* ================================================================ */
/** Maximum number of tracked leases (one per interface). */
#define MAX_LEASES 8
/** DHCP lease table. */
static dhcp_lease_t leases[MAX_LEASES];
static uint32_t lease_count = 0;
/** Simple pseudo-random XID counter. */
static uint32_t xid_counter = 0x12345678;
/* ================================================================
* Helpers
* ================================================================ */
/**
* Compute Internet checksum.
*/
static uint16_t inet_checksum(const void *data, uint32_t len) {
const uint16_t *words = (const uint16_t *)data;
uint32_t sum = 0;
while (len > 1) { sum += *words++; len -= 2; }
if (len == 1) sum += *(const uint8_t *)words;
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
return (uint16_t)(~sum);
}
/**
* Get or create a lease for an interface.
*/
static dhcp_lease_t *get_lease(uint32_t iface_idx) {
for (uint32_t i = 0; i < lease_count; i++) {
if (leases[i].iface_idx == (uint8_t)iface_idx) return &leases[i];
}
if (lease_count >= MAX_LEASES) return NULL;
dhcp_lease_t *l = &leases[lease_count++];
memset(l, 0, sizeof(dhcp_lease_t));
l->iface_idx = (uint8_t)iface_idx;
return l;
}
/**
* Format an IP as dotted decimal into buf, return chars written.
*/
static int fmt_ip(uint32_t ip, char *buf, uint32_t size) {
int pos = 0;
for (int i = 0; i < 4; i++) {
if (i > 0 && pos < (int)size - 1) buf[pos++] = '.';
uint8_t o = (uint8_t)((ip >> (24 - i * 8)) & 0xFF);
if (o >= 100 && pos < (int)size - 3) {
buf[pos++] = (char)('0' + o / 100);
buf[pos++] = (char)('0' + (o % 100) / 10);
buf[pos++] = (char)('0' + o % 10);
} else if (o >= 10 && pos < (int)size - 2) {
buf[pos++] = (char)('0' + o / 10);
buf[pos++] = (char)('0' + o % 10);
} else if (pos < (int)size - 1) {
buf[pos++] = (char)('0' + o);
}
}
if (pos < (int)size) buf[pos] = '\0';
return pos;
}
/* ================================================================
* DHCP packet construction
* ================================================================ */
/**
* Build and send a DHCP packet wrapped in IP+UDP.
*
* Since we may not have a full UDP stack, we construct the whole
* IP+UDP+DHCP frame manually and send it as a broadcast Ethernet frame.
*/
static int dhcp_send_packet(uint32_t iface_idx, dhcp_packet_t *dhcp_pkt,
uint32_t dhcp_len) {
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (!iface) return -1;
/* Total sizes */
uint32_t udp_len = UDP_HLEN + dhcp_len;
uint32_t ip_len = IPV4_HLEN + udp_len;
if (ip_len > ETH_MTU) return -1;
/* Build combined IP+UDP+DHCP packet */
uint8_t pkt[1500];
memset(pkt, 0, sizeof(pkt));
/* IPv4 header */
ipv4_header_t *ip = (ipv4_header_t *)pkt;
ip->ihl_version = 0x45;
ip->tos = 0;
ip->total_length = htons((uint16_t)ip_len);
ip->identification = htons(xid_counter & 0xFFFF);
ip->flags_fragoff = 0;
ip->ttl = 64;
ip->protocol = IP_PROTO_UDP;
ip->checksum = 0;
ip->src_ip = htonl(iface->ip_addr); /* 0.0.0.0 if not yet configured */
ip->dst_ip = htonl(0xFFFFFFFF); /* Broadcast */
ip->checksum = inet_checksum(ip, IPV4_HLEN);
/* UDP header */
udp_header_t *udp = (udp_header_t *)(pkt + IPV4_HLEN);
udp->src_port = htons(DHCP_CLIENT_PORT);
udp->dst_port = htons(DHCP_SERVER_PORT);
udp->length = htons((uint16_t)udp_len);
udp->checksum = 0; /* UDP checksum optional in IPv4 */
/* DHCP payload */
memcpy(pkt + IPV4_HLEN + UDP_HLEN, dhcp_pkt, dhcp_len);
/* Send as broadcast Ethernet frame */
static const uint8_t bcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
return ethernet_send(iface, bcast, ETHERTYPE_IPV4, pkt, ip_len);
}
/**
* Add a DHCP option to the options buffer.
* Returns new offset.
*/
static uint32_t add_option(uint8_t *opts, uint32_t off,
uint8_t code, uint8_t len, const void *data) {
opts[off++] = code;
opts[off++] = len;
memcpy(&opts[off], data, len);
return off + len;
}
/* ================================================================
* DHCP protocol
* ================================================================ */
int dhcp_discover(uint32_t iface_idx) {
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (!iface) return -1;
dhcp_lease_t *lease = get_lease(iface_idx);
if (!lease) return -1;
/* Generate transaction ID */
xid_counter += 0x1234;
lease->xid = xid_counter;
lease->state = DHCP_STATE_DISCOVER;
/* Build DHCP DISCOVER packet */
dhcp_packet_t pkt;
memset(&pkt, 0, sizeof(pkt));
pkt.op = 1; /* BOOTREQUEST */
pkt.htype = 1; /* Ethernet */
pkt.hlen = 6;
pkt.hops = 0;
pkt.xid = htonl(lease->xid);
pkt.secs = 0;
pkt.flags = htons(0x8000); /* Broadcast flag */
pkt.ciaddr = 0;
pkt.yiaddr = 0;
pkt.siaddr = 0;
pkt.giaddr = 0;
memcpy(pkt.chaddr, iface->mac, 6);
pkt.magic = htonl(DHCP_MAGIC_COOKIE);
/* Options */
uint32_t off = 0;
/* Option 53: DHCP Message Type = DISCOVER */
uint8_t msg_type = DHCP_DISCOVER;
off = add_option(pkt.options, off, DHCP_OPT_MSG_TYPE, 1, &msg_type);
/* Option 55: Parameter Request List */
uint8_t params[] = {
DHCP_OPT_SUBNET_MASK,
DHCP_OPT_ROUTER,
DHCP_OPT_DNS,
};
off = add_option(pkt.options, off, DHCP_OPT_PARAM_LIST,
sizeof(params), params);
/* End option */
pkt.options[off++] = DHCP_OPT_END;
offset_print(" DHCP: sending DISCOVER on ");
offset_print(iface->name);
offset_print("\n");
return dhcp_send_packet(iface_idx, &pkt, DHCP_FIXED_SIZE + off);
}
/**
* Send a DHCP REQUEST message.
*/
static int dhcp_send_request(uint32_t iface_idx, dhcp_lease_t *lease) {
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (!iface) return -1;
dhcp_packet_t pkt;
memset(&pkt, 0, sizeof(pkt));
pkt.op = 1;
pkt.htype = 1;
pkt.hlen = 6;
pkt.xid = htonl(lease->xid);
pkt.flags = htons(0x8000);
memcpy(pkt.chaddr, iface->mac, 6);
pkt.magic = htonl(DHCP_MAGIC_COOKIE);
uint32_t off = 0;
/* Option 53: DHCP Message Type = REQUEST */
uint8_t msg_type = DHCP_REQUEST;
off = add_option(pkt.options, off, DHCP_OPT_MSG_TYPE, 1, &msg_type);
/* Option 50: Requested IP */
uint32_t req_ip = htonl(lease->ip_addr);
off = add_option(pkt.options, off, DHCP_OPT_REQUESTED_IP, 4, &req_ip);
/* Option 54: Server Identifier */
uint32_t srv_ip = htonl(lease->server_ip);
off = add_option(pkt.options, off, DHCP_OPT_SERVER_ID, 4, &srv_ip);
/* Option 55: Parameter Request List */
uint8_t params[] = {
DHCP_OPT_SUBNET_MASK,
DHCP_OPT_ROUTER,
DHCP_OPT_DNS,
};
off = add_option(pkt.options, off, DHCP_OPT_PARAM_LIST,
sizeof(params), params);
pkt.options[off++] = DHCP_OPT_END;
lease->state = DHCP_STATE_REQUESTING;
offset_print(" DHCP: sending REQUEST on ");
offset_print(iface->name);
offset_print("\n");
return dhcp_send_packet(iface_idx, &pkt, DHCP_FIXED_SIZE + off);
}
/**
* Parse DHCP options from a received packet.
*/
static void parse_options(const uint8_t *opts, uint32_t len,
uint8_t *msg_type, dhcp_lease_t *lease) {
uint32_t i = 0;
while (i < len) {
uint8_t code = opts[i++];
if (code == DHCP_OPT_END) break;
if (code == 0) continue; /* Padding */
if (i >= len) break;
uint8_t opt_len = opts[i++];
if (i + opt_len > len) break;
switch (code) {
case DHCP_OPT_MSG_TYPE:
if (opt_len >= 1) *msg_type = opts[i];
break;
case DHCP_OPT_SUBNET_MASK:
if (opt_len >= 4) {
uint32_t val;
memcpy(&val, &opts[i], 4);
lease->netmask = ntohl(val);
}
break;
case DHCP_OPT_ROUTER:
if (opt_len >= 4) {
uint32_t val;
memcpy(&val, &opts[i], 4);
lease->gateway = ntohl(val);
}
break;
case DHCP_OPT_DNS:
if (opt_len >= 4) {
uint32_t val;
memcpy(&val, &opts[i], 4);
lease->dns_server = ntohl(val);
}
break;
case DHCP_OPT_LEASE_TIME:
if (opt_len >= 4) {
uint32_t val;
memcpy(&val, &opts[i], 4);
lease->lease_time = ntohl(val);
}
break;
case DHCP_OPT_SERVER_ID:
if (opt_len >= 4) {
uint32_t val;
memcpy(&val, &opts[i], 4);
lease->server_ip = ntohl(val);
}
break;
default:
break;
}
i += opt_len;
}
}
void dhcp_receive(const void *data, uint32_t len, uint32_t iface_idx) {
if (len < DHCP_FIXED_SIZE) return;
const dhcp_packet_t *pkt = (const dhcp_packet_t *)data;
/* Must be a BOOTREPLY */
if (pkt->op != 2) return;
/* Check magic cookie */
if (ntohl(pkt->magic) != DHCP_MAGIC_COOKIE) return;
dhcp_lease_t *lease = get_lease(iface_idx);
if (!lease) return;
/* Match transaction ID */
if (ntohl(pkt->xid) != lease->xid) return;
/* Parse options */
uint8_t msg_type = 0;
uint32_t opts_len = len - DHCP_FIXED_SIZE;
if (opts_len > sizeof(pkt->options)) opts_len = sizeof(pkt->options);
parse_options(pkt->options, opts_len, &msg_type, lease);
/* Store offered IP */
uint32_t offered_ip = ntohl(pkt->yiaddr);
switch (msg_type) {
case DHCP_OFFER:
if (lease->state != DHCP_STATE_DISCOVER) break;
lease->ip_addr = offered_ip;
offset_print(" DHCP: received OFFER ");
print_hex(offered_ip);
offset_print("\n");
/* Send REQUEST */
dhcp_send_request(iface_idx, lease);
break;
case DHCP_ACK:
if (lease->state != DHCP_STATE_REQUESTING) break;
lease->ip_addr = offered_ip;
lease->state = DHCP_STATE_BOUND;
/* Apply configuration to the interface */
{
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (iface) {
iface->ip_addr = lease->ip_addr;
iface->netmask = lease->netmask;
iface->gateway = lease->gateway;
}
}
offset_print(" DHCP: BOUND on ");
{
eth_iface_t *iface2 = ethernet_get_iface(iface_idx);
if (iface2) offset_print(iface2->name);
}
offset_print(" IP ");
print_hex(lease->ip_addr);
offset_print("\n");
break;
case DHCP_NAK:
lease->state = DHCP_STATE_FAILED;
offset_print(" DHCP: received NAK\n");
break;
default:
break;
}
}
const dhcp_lease_t *dhcp_get_lease(uint32_t iface_idx) {
for (uint32_t i = 0; i < lease_count; i++) {
if (leases[i].iface_idx == (uint8_t)iface_idx) return &leases[i];
}
return NULL;
}
const char *dhcp_state_name(uint8_t state) {
switch (state) {
case DHCP_STATE_IDLE: return "idle";
case DHCP_STATE_DISCOVER: return "discovering";
case DHCP_STATE_REQUESTING: return "requesting";
case DHCP_STATE_BOUND: return "bound";
case DHCP_STATE_RENEWING: return "renewing";
case DHCP_STATE_FAILED: return "failed";
default: return "unknown";
}
}
/* ================================================================
* Sysfs: /sys/dhcp
*
* /sys/dhcp/
* status - overall DHCP status
* ================================================================ */
static void append(char *buf, uint32_t size, int *pos, const char *s) {
while (*s && *pos < (int)size - 1) buf[(*pos)++] = *s++;
}
static int dhcp_sysfs_list(void *ctx, const char *path, uint32_t idx,
sysfs_entry_t *out) {
(void)ctx;
if (path[0] == '\0') {
if (idx == 0) {
memset(out, 0, sizeof(sysfs_entry_t));
strncpy(out->name, "status", SYSFS_MAX_NAME - 1);
out->is_dir = 0;
return 0;
}
return -1;
}
return -1;
}
static int dhcp_sysfs_read(void *ctx, const char *path, char *buf,
uint32_t buf_size) {
(void)ctx;
if (strcmp(path, "status") != 0) return -1;
int pos = 0;
if (lease_count == 0) {
append(buf, buf_size, &pos, "No DHCP leases.\n");
buf[pos] = '\0';
return pos;
}
for (uint32_t i = 0; i < lease_count; i++) {
dhcp_lease_t *l = &leases[i];
eth_iface_t *iface = ethernet_get_iface(l->iface_idx);
if (iface) {
append(buf, buf_size, &pos, iface->name);
} else {
append(buf, buf_size, &pos, "?");
}
append(buf, buf_size, &pos, ": ");
append(buf, buf_size, &pos, dhcp_state_name(l->state));
append(buf, buf_size, &pos, "\n");
if (l->state == DHCP_STATE_BOUND) {
char ip_buf[16];
append(buf, buf_size, &pos, " IP: ");
fmt_ip(l->ip_addr, ip_buf, sizeof(ip_buf));
append(buf, buf_size, &pos, ip_buf);
append(buf, buf_size, &pos, "\n");
append(buf, buf_size, &pos, " Netmask: ");
fmt_ip(l->netmask, ip_buf, sizeof(ip_buf));
append(buf, buf_size, &pos, ip_buf);
append(buf, buf_size, &pos, "\n");
append(buf, buf_size, &pos, " Gateway: ");
fmt_ip(l->gateway, ip_buf, sizeof(ip_buf));
append(buf, buf_size, &pos, ip_buf);
append(buf, buf_size, &pos, "\n");
append(buf, buf_size, &pos, " DNS: ");
fmt_ip(l->dns_server, ip_buf, sizeof(ip_buf));
append(buf, buf_size, &pos, ip_buf);
append(buf, buf_size, &pos, "\n");
append(buf, buf_size, &pos, " Server: ");
fmt_ip(l->server_ip, ip_buf, sizeof(ip_buf));
append(buf, buf_size, &pos, ip_buf);
append(buf, buf_size, &pos, "\n");
}
}
buf[pos] = '\0';
return pos;
}
static int dhcp_sysfs_write(void *ctx, const char *path, const char *buf,
uint32_t size) {
(void)ctx;
(void)path;
(void)buf;
(void)size;
return -1;
}
static sysfs_ops_t dhcp_sysfs_ops = {
.list = dhcp_sysfs_list,
.read = dhcp_sysfs_read,
.write = dhcp_sysfs_write,
};
/* ================================================================
* Initialization
* ================================================================ */
void dhcp_init(void) {
memset(leases, 0, sizeof(leases));
lease_count = 0;
sysfs_register("dhcp", &dhcp_sysfs_ops, NULL);
offset_print(" DHCP: initialized\n");
}