- 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
553 lines
16 KiB
C
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");
|
|
}
|