/** * @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 /* 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"); }