diff --git a/uboot-mtk-20220606/failsafe/failsafe.c b/uboot-mtk-20220606/failsafe/failsafe.c index 760f24d8f..4aea80c2b 100644 --- a/uboot-mtk-20220606/failsafe/failsafe.c +++ b/uboot-mtk-20220606/failsafe/failsafe.c @@ -17,6 +17,7 @@ #endif #include #include +#include #include #include #include @@ -477,8 +478,14 @@ int start_web_failsafe(void) httpd_register_uri_handler(inst, "/version", &version_handler, NULL); httpd_register_uri_handler(inst, "", ¬_found_handler, NULL); + if (IS_ENABLED(CONFIG_MTK_DHCPD)) + mtk_dhcpd_start(); + net_loop(TCP); + if (IS_ENABLED(CONFIG_MTK_DHCPD)) + mtk_dhcpd_stop(); + return 0; } diff --git a/uboot-mtk-20220606/include/net/mtk_dhcpd.h b/uboot-mtk-20220606/include/net/mtk_dhcpd.h new file mode 100644 index 000000000..b78723f85 --- /dev/null +++ b/uboot-mtk-20220606/include/net/mtk_dhcpd.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Yuzhii0718 + * + * All rights reserved. + * + * This file is part of the project bl-mt798x-dhcpd + * You may not use, copy, modify or distribute this file except in compliance with the license agreement. + * + * Minimal DHCPv4 server for MediaTek web failsafe. + */ + +#ifndef __NET_MTK_DHCPD_H__ +#define __NET_MTK_DHCPD_H__ + +int mtk_dhcpd_start(void); +void mtk_dhcpd_stop(void); + +#endif /* __NET_MTK_DHCPD_H__ */ diff --git a/uboot-mtk-20220606/net/Kconfig b/uboot-mtk-20220606/net/Kconfig index b81d20a6d..7d432f4ec 100644 --- a/uboot-mtk-20220606/net/Kconfig +++ b/uboot-mtk-20220606/net/Kconfig @@ -34,6 +34,47 @@ config HTTPD default n depends on TCP +config MTK_DHCPD + bool "Minimal DHCP server for MediaTek web failsafe" + default y + depends on HTTPD + help + Enable a minimal DHCPv4 server that answers DHCPDISCOVER/DHCPREQUEST + while the MediaTek web failsafe UI (httpd) is running. + +config MTK_DHCPD_USE_CONFIG_IP + bool "Use CONFIG_IPADDR/CONFIG_NETMASK and configurable pool" + default n + depends on MTK_DHCPD + help + Use CONFIG_IPADDR and CONFIG_NETMASK as DHCP server address/netmask. + Pool start host and size are taken from the options below. + If disabled, fallback defaults will be used. + Warning: DO NOT enable this if in U-Boot 2022. + +config MTK_DHCPD_POOL_START_HOST + int "DHCP pool start host address" + default 100 + depends on MTK_DHCPD_USE_CONFIG_IP + help + Host part for pool start. For example, 100 -> x.x.x.100. + +config MTK_DHCPD_POOL_SIZE + int "DHCP pool size" + default 101 + depends on MTK_DHCPD_USE_CONFIG_IP + help + Pool size, e.g. 101 means .100 ~ .200. + +config MTK_DHCPD_ENHANCED + bool "Enhanced DHCP server behavior" + default y + depends on MTK_DHCPD + help + Enable enhanced DHCPREQUEST validation and stable IP allocation. + Adds Server Identifier checks, DHCPNAK support, and hash-based pool + allocation with collision avoidance. + config BOOTDEV_ETH bool "Enable bootdev for ethernet" depends on BOOTSTD diff --git a/uboot-mtk-20220606/net/Makefile b/uboot-mtk-20220606/net/Makefile index f752005b6..5029421cf 100644 --- a/uboot-mtk-20220606/net/Makefile +++ b/uboot-mtk-20220606/net/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_PROT_UDP) += udp.o obj-$(CONFIG_TCP) += tcp.o obj-$(CONFIG_HTTPD) += httpd.o +obj-$(CONFIG_MTK_DHCPD) += mtk_dhcpd.o # Disable this warning as it is triggered by: # sprintf(buf, index ? "foo%d" : "foo", index) diff --git a/uboot-mtk-20220606/net/mtk_dhcpd.c b/uboot-mtk-20220606/net/mtk_dhcpd.c new file mode 100644 index 000000000..941671777 --- /dev/null +++ b/uboot-mtk-20220606/net/mtk_dhcpd.c @@ -0,0 +1,773 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Yuzhii0718 + * + * All rights reserved. + * + * This file is part of the project bl-mt798x-dhcpd + * You may not use, copy, modify or distribute this file except in compliance with the license agreement. + * + * Minimal DHCPv4 server for MediaTek web failsafe. + * + * Goals: + * - Provide IP/netmask/gateway/DNS to a directly connected PC + * - Auto-start with web failsafe (httpd) + * - Small and self-contained + */ + +#include +#include + +#include + +#define DHCPD_SERVER_PORT 67 +#define DHCPD_CLIENT_PORT 68 + +/* + * BOOTP/DHCP has a historical minimum message size of 300 bytes. + * Some clients (notably Windows) may ignore shorter replies. + */ +#define DHCPD_MIN_BOOTP_LEN 300 + +/* BOOTP/DHCP message header (RFC 2131) */ +struct dhcpd_pkt { + u8 op; + u8 htype; + u8 hlen; + u8 hops; + u32 xid; + u16 secs; + u16 flags; + u32 ciaddr; + u32 yiaddr; + u32 siaddr; + u32 giaddr; + u8 chaddr[16]; + u8 sname[64]; + u8 file[128]; + u8 vend[312]; +} __packed; + +#define BOOTREQUEST 1 +#define BOOTREPLY 2 + +#define HTYPE_ETHER 1 +#define HLEN_ETHER 6 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPNAK 6 +#define DHCPACK 5 + +#define DHCP_OPTION_PAD 0 +#define DHCP_OPTION_SUBNET_MASK 1 +#define DHCP_OPTION_ROUTER 3 +#define DHCP_OPTION_DNS_SERVER 6 +#define DHCP_OPTION_REQ_IPADDR 50 +#define DHCP_OPTION_LEASE_TIME 51 +#define DHCP_OPTION_MSG_TYPE 53 +#define DHCP_OPTION_SERVER_ID 54 +#define DHCP_OPTION_MESSAGE 56 +#define DHCP_OPTION_END 255 + +#define DHCP_FLAG_BROADCAST 0x8000 + +#define DHCPD_DEFAULT_IP_STR "192.168.1.1" +#define DHCPD_DEFAULT_NETMASK_STR "255.255.255.0" +#define DHCPD_DEFAULT_POOL_START_HOST 100 +#define DHCPD_DEFAULT_POOL_SIZE 101 + +#define DHCPD_MAX_CLIENTS 8 + +static const u8 dhcp_magic_cookie[4] = { 99, 130, 83, 99 }; + +struct dhcpd_lease { + bool used; + u8 mac[6]; + struct in_addr ip; +}; + +static struct dhcpd_lease leases[DHCPD_MAX_CLIENTS]; +static u32 next_ip_host; + +static rxhand_f *prev_udp_handler; +static bool dhcpd_running; + +static struct in_addr dhcpd_get_server_ip(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return string_to_ip(CONFIG_IPADDR); +#else + if (net_ip.s_addr) + return net_ip; + + return string_to_ip(DHCPD_DEFAULT_IP_STR); +#endif +} + +static struct in_addr dhcpd_get_netmask(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return string_to_ip(CONFIG_NETMASK); +#else + if (net_netmask.s_addr) + return net_netmask; + + return string_to_ip(DHCPD_DEFAULT_NETMASK_STR); +#endif +} + +static struct in_addr dhcpd_get_gateway(void) +{ + if (net_gateway.s_addr) + return net_gateway; + + return dhcpd_get_server_ip(); +} + +static struct in_addr dhcpd_get_dns(void) +{ + if (net_dns_server.s_addr) + return net_dns_server; + + return dhcpd_get_server_ip(); +} + +static u32 dhcpd_get_pool_start_host(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return (u32)CONFIG_MTK_DHCPD_POOL_START_HOST; +#else + return DHCPD_DEFAULT_POOL_START_HOST; +#endif +} + +static u32 dhcpd_get_pool_size(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return (u32)CONFIG_MTK_DHCPD_POOL_SIZE; +#else + return DHCPD_DEFAULT_POOL_SIZE; +#endif +} + +static void dhcpd_get_pool_range(u32 *start, u32 *end) +{ + struct in_addr server_ip = dhcpd_get_server_ip(); + struct in_addr netmask = dhcpd_get_netmask(); + u32 ip_host = ntohl(server_ip.s_addr); + u32 mask = ntohl(netmask.s_addr); + u32 host_mask = ~mask; + u32 host_start = dhcpd_get_pool_start_host(); + u32 size = dhcpd_get_pool_size(); + u32 net = ip_host & mask; + u32 s, e, max_host; + + if (!size) + size = 1; + + max_host = host_mask; + host_start &= host_mask; + if (host_start == 0) + host_start = 1; + + s = net | host_start; + e = s + size - 1; + + if ((e & mask) != net || e > (net | max_host)) + e = net | max_host; + + if (start) + *start = s; + if (end) + *end = e; +} + +static bool dhcpd_mac_equal(const u8 *a, const u8 *b) +{ + return memcmp(a, b, 6) == 0; +} + +static struct dhcpd_lease *dhcpd_find_lease(const u8 *mac) +{ + int i; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (leases[i].used && dhcpd_mac_equal(leases[i].mac, mac)) + return &leases[i]; + } + + return NULL; +} + +static bool dhcpd_ip_in_pool(u32 ip_host) +{ + u32 start, end; + + dhcpd_get_pool_range(&start, &end); + + return ip_host >= start && ip_host <= end; +} + +#ifdef CONFIG_MTK_DHCPD_ENHANCED +static bool dhcpd_ip_is_allocated(u32 ip_host) +{ + int i; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (!leases[i].used) + continue; + if (ntohl(leases[i].ip.s_addr) == ip_host) + return true; + } + + return false; +} + +static bool dhcpd_ip_allocated_to_mac(u32 ip_host, const u8 *mac) +{ + int i; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (!leases[i].used) + continue; + if (ntohl(leases[i].ip.s_addr) != ip_host) + continue; + return dhcpd_mac_equal(leases[i].mac, mac); + } + + return false; +} + +static u32 dhcpd_mac_hash(const u8 *mac) +{ + u32 h = 2166136261u; + int i; + + for (i = 0; i < 6; i++) { + h ^= mac[i]; + h *= 16777619u; + } + + return h; +} +#endif + +static struct in_addr dhcpd_alloc_ip(const u8 *mac) +{ + struct dhcpd_lease *l; + u32 start, end; + int i; + struct in_addr ip; + u32 pool_size; + + l = dhcpd_find_lease(mac); + if (l && dhcpd_ip_in_pool(ntohl(l->ip.s_addr))) + return l->ip; + + dhcpd_get_pool_range(&start, &end); + + pool_size = end >= start ? (end - start + 1) : 0; + +#ifdef CONFIG_MTK_DHCPD_ENHANCED + if (pool_size) { + u32 hash = dhcpd_mac_hash(mac); + u32 off = hash % pool_size; + + for (i = 0; i < (int)pool_size; i++) { + u32 cand = start + ((off + i) % pool_size); + if (!dhcpd_ip_is_allocated(cand)) { + ip.s_addr = htonl(cand); + return ip; + } + } + } +#else + if (!next_ip_host) + next_ip_host = start; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + int idx; + + idx = i; + if (!leases[idx].used) { + leases[idx].used = true; + memcpy(leases[idx].mac, mac, 6); + leases[idx].ip.s_addr = htonl(next_ip_host); + + next_ip_host++; + if (next_ip_host > end) + next_ip_host = start; + + return leases[idx].ip; + } + } +#endif + + /* No free slot: just return the first address in pool */ + ip.s_addr = htonl(start); + return ip; +} + +static u8 dhcpd_parse_msg_type(const struct dhcpd_pkt *bp, unsigned int len) +{ + unsigned int fixed = offsetof(struct dhcpd_pkt, vend); + const u8 *opt; + unsigned int optlen; + + if (len < fixed + 4) + return 0; + + opt = (const u8 *)bp->vend; + optlen = len - fixed; + + if (memcmp(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie))) + return 0; + + opt += 4; + optlen -= 4; + + while (optlen) { + u8 code; + u8 olen; + + code = *opt++; + optlen--; + + if (code == DHCP_OPTION_PAD) + continue; + if (code == DHCP_OPTION_END) + break; + + if (!optlen) + break; + olen = *opt++; + optlen--; + + if (olen > optlen) + break; + + if (code == DHCP_OPTION_MSG_TYPE) { + if (olen >= 1) + return opt[0]; + } + + opt += olen; + optlen -= olen; + } + + return 0; +} + +static bool dhcpd_parse_req_ip(const struct dhcpd_pkt *bp, unsigned int len, + struct in_addr *req_ip) +{ + unsigned int fixed = offsetof(struct dhcpd_pkt, vend); + const u8 *opt; + unsigned int optlen; + + if (len < fixed + 4) + return false; + + opt = (const u8 *)bp->vend; + optlen = len - fixed; + + if (memcmp(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie))) + return false; + + opt += 4; + optlen -= 4; + + while (optlen) { + u8 code; + u8 olen; + + code = *opt++; + optlen--; + + if (code == DHCP_OPTION_PAD) + continue; + if (code == DHCP_OPTION_END) + break; + + if (!optlen) + break; + olen = *opt++; + optlen--; + + if (olen > optlen) + break; + + if (code == DHCP_OPTION_REQ_IPADDR && olen == 4) { + memcpy(&req_ip->s_addr, opt, 4); + return true; + } + + opt += olen; + optlen -= olen; + } + + return false; +} + +#ifdef CONFIG_MTK_DHCPD_ENHANCED +static bool dhcpd_parse_server_id(const struct dhcpd_pkt *bp, unsigned int len, + struct in_addr *server_ip) +{ + unsigned int fixed = offsetof(struct dhcpd_pkt, vend); + const u8 *opt; + unsigned int optlen; + + if (len < fixed + 4) + return false; + + opt = (const u8 *)bp->vend; + optlen = len - fixed; + + if (memcmp(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie))) + return false; + + opt += 4; + optlen -= 4; + + while (optlen) { + u8 code; + u8 olen; + + code = *opt++; + optlen--; + + if (code == DHCP_OPTION_PAD) + continue; + if (code == DHCP_OPTION_END) + break; + + if (!optlen) + break; + olen = *opt++; + optlen--; + + if (olen > optlen) + break; + + if (code == DHCP_OPTION_SERVER_ID && olen == 4) { + memcpy(&server_ip->s_addr, opt, 4); + return true; + } + + opt += olen; + optlen -= olen; + } + + return false; +} + +static void dhcpd_process_lease(const u8 *mac, struct in_addr ip) +{ + struct dhcpd_lease *l; + int i; + + l = dhcpd_find_lease(mac); + if (l) { + l->ip = ip; + return; + } + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (!leases[i].used) { + leases[i].used = true; + memcpy(leases[i].mac, mac, 6); + leases[i].ip = ip; + return; + } + } + + /* Fallback: replace the first entry */ + leases[0].used = true; + memcpy(leases[0].mac, mac, 6); + leases[0].ip = ip; +} + +static bool dhcpd_same_subnet(struct in_addr a, struct in_addr b, + struct in_addr mask) +{ + return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr); +} +#endif + +static u8 *dhcpd_opt_add_u8(u8 *p, u8 code, u8 val) +{ + *p++ = code; + *p++ = 1; + *p++ = val; + return p; +} + +static u8 *dhcpd_opt_add_u32(u8 *p, u8 code, __be32 val) +{ + *p++ = code; + *p++ = 4; + memcpy(p, &val, 4); + return p + 4; +} + +static u8 *dhcpd_opt_add_inaddr(u8 *p, u8 code, struct in_addr addr) +{ + return dhcpd_opt_add_u32(p, code, addr.s_addr); +} + +static int dhcpd_send_reply(const struct dhcpd_pkt *req, unsigned int req_len, + u8 dhcp_msg_type, struct in_addr yiaddr, + const char *nak_message) +{ + struct dhcpd_pkt *bp; + struct in_addr server_ip, netmask, gw, dns; + struct in_addr bcast; + uchar *pkt; + uchar *payload; + int eth_hdr_size; + u8 *opt; + int payload_len; + __be32 lease; + + (void)req_len; + + server_ip = dhcpd_get_server_ip(); + netmask = dhcpd_get_netmask(); + gw = dhcpd_get_gateway(); + dns = dhcpd_get_dns(); + + bcast.s_addr = 0xFFFFFFFF; + + pkt = net_tx_packet; + eth_hdr_size = net_set_ether(pkt, net_bcast_ethaddr, PROT_IP); + net_set_udp_header(pkt + eth_hdr_size, bcast, + DHCPD_CLIENT_PORT, DHCPD_SERVER_PORT, 0); + + payload = pkt + eth_hdr_size + IP_UDP_HDR_SIZE; + bp = (struct dhcpd_pkt *)payload; + memset(bp, 0, sizeof(*bp)); + + bp->op = BOOTREPLY; + bp->htype = HTYPE_ETHER; + bp->hlen = HLEN_ETHER; + bp->hops = 0; + bp->xid = req->xid; + bp->secs = req->secs; + bp->flags = htons(DHCP_FLAG_BROADCAST); + bp->ciaddr = 0; + bp->yiaddr = yiaddr.s_addr; + bp->siaddr = server_ip.s_addr; + bp->giaddr = 0; + memcpy(bp->chaddr, req->chaddr, sizeof(bp->chaddr)); + + opt = (u8 *)bp->vend; + memcpy(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie)); + opt += 4; + + opt = dhcpd_opt_add_u8(opt, DHCP_OPTION_MSG_TYPE, dhcp_msg_type); + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_SERVER_ID, server_ip); + + if (dhcp_msg_type != DHCPNAK) { + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_SUBNET_MASK, netmask); + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_ROUTER, gw); + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_DNS_SERVER, dns); + + lease = htonl(3600); + opt = dhcpd_opt_add_u32(opt, DHCP_OPTION_LEASE_TIME, lease); + } else if (nak_message && *nak_message) { + size_t msg_len = strlen(nak_message); + u8 len = msg_len > 240 ? 240 : (u8)msg_len; + + *opt++ = DHCP_OPTION_MESSAGE; + *opt++ = len; + memcpy(opt, nak_message, len); + opt += len; + } + + *opt++ = DHCP_OPTION_END; + + payload_len = (int)((uintptr_t)opt - (uintptr_t)payload); + if (payload_len < DHCPD_MIN_BOOTP_LEN) + payload_len = DHCPD_MIN_BOOTP_LEN; + + /* Update UDP header with actual payload length */ + net_set_udp_header(pkt + eth_hdr_size, bcast, + DHCPD_CLIENT_PORT, DHCPD_SERVER_PORT, payload_len); + + net_send_packet(pkt, eth_hdr_size + IP_UDP_HDR_SIZE + payload_len); + + return 0; +} + +static void dhcpd_handle_packet(uchar *pkt, unsigned int dport, + struct in_addr sip, unsigned int sport, + unsigned int len) +{ + const struct dhcpd_pkt *bp = (const struct dhcpd_pkt *)pkt; + u8 msg_type; + struct in_addr yiaddr; + struct in_addr req_ip; + + (void)sip; + + if (!dhcpd_running) + return; + + if (dport != DHCPD_SERVER_PORT || sport != DHCPD_CLIENT_PORT) + return; + + if (len < offsetof(struct dhcpd_pkt, vend)) + return; + + if (bp->op != BOOTREQUEST) + return; + + if (bp->htype != HTYPE_ETHER || bp->hlen != HLEN_ETHER) + return; + + msg_type = dhcpd_parse_msg_type(bp, len); + if (!msg_type) + return; + + debug_cond(DEBUG_DEV_PKT, "dhcpd: msg=%u from %pM\n", msg_type, bp->chaddr); + + switch (msg_type) { + case DHCPDISCOVER: + yiaddr = dhcpd_alloc_ip(bp->chaddr); + debug_cond(DEBUG_DEV_PKT, "dhcpd: offer %pI4\n", &yiaddr); + dhcpd_send_reply(bp, len, DHCPOFFER, yiaddr, NULL); + break; + case DHCPREQUEST: + +#ifdef CONFIG_MTK_DHCPD_ENHANCED + { + struct in_addr server_id; + struct in_addr server_ip = dhcpd_get_server_ip(); + struct in_addr netmask = dhcpd_get_netmask(); + bool has_server_id; + bool has_req_ip; + u32 ip_host; + struct in_addr zero_ip; + + zero_ip.s_addr = 0; + has_server_id = dhcpd_parse_server_id(bp, len, &server_id); + if (has_server_id && server_id.s_addr != server_ip.s_addr) + return; + + has_req_ip = dhcpd_parse_req_ip(bp, len, &req_ip); + if (!has_req_ip) { + yiaddr = dhcpd_alloc_ip(bp->chaddr); + dhcpd_process_lease(bp->chaddr, yiaddr); + dhcpd_send_reply(bp, len, DHCPACK, yiaddr, NULL); + break; + } + + ip_host = ntohl(req_ip.s_addr); + if (!dhcpd_same_subnet(req_ip, server_ip, netmask)) { + dhcpd_send_reply(bp, len, DHCPNAK, zero_ip, "bad subnet"); + break; + } + if (!dhcpd_ip_in_pool(ip_host)) { + dhcpd_send_reply(bp, len, DHCPNAK, zero_ip, "outside pool"); + break; + } + if (dhcpd_ip_is_allocated(ip_host) && + !dhcpd_ip_allocated_to_mac(ip_host, bp->chaddr)) { + dhcpd_send_reply(bp, len, DHCPNAK, zero_ip, "in use"); + break; + } + + yiaddr = req_ip; + dhcpd_process_lease(bp->chaddr, yiaddr); + dhcpd_send_reply(bp, len, DHCPACK, yiaddr, NULL); + } +#else + /* If client requests a specific IP, validate it */ + if (dhcpd_parse_req_ip(bp, len, &req_ip)) { + u32 ip_host = ntohl(req_ip.s_addr); + if (dhcpd_ip_in_pool(ip_host)) { + yiaddr = req_ip; + } else { + yiaddr = dhcpd_alloc_ip(bp->chaddr); + } + } else { + yiaddr = dhcpd_alloc_ip(bp->chaddr); + } + dhcpd_send_reply(bp, len, DHCPACK, yiaddr, NULL); +#endif + break; + default: + break; + } +} + +static void dhcpd_udp_handler(uchar *pkt, unsigned int dport, + struct in_addr sip, unsigned int sport, + unsigned int len) +{ + dhcpd_handle_packet(pkt, dport, sip, sport, len); + + if (prev_udp_handler) + prev_udp_handler(pkt, dport, sip, sport, len); +} + +int mtk_dhcpd_start(void) +{ + struct in_addr pool_start; + u32 pool_start_host, pool_end_host; + + /* + * Be robust against net_init()/net_clear_handlers() resetting handlers. + * If we're already running but the UDP handler is no longer ours, re-hook. + */ + if (dhcpd_running) { + rxhand_f *cur = net_get_udp_handler(); + + if (cur != dhcpd_udp_handler) { + prev_udp_handler = cur; + net_set_udp_handler(dhcpd_udp_handler); + } + return 0; + } + + /* Ensure we have a usable local IP, otherwise UDP replies will use 0.0.0.0 */ + if (!net_ip.s_addr) + net_ip = dhcpd_get_server_ip(); + if (!net_netmask.s_addr) + net_netmask = dhcpd_get_netmask(); + if (!net_gateway.s_addr) + net_gateway = net_ip; + if (!net_dns_server.s_addr) + net_dns_server = net_ip; + + memset(leases, 0, sizeof(leases)); + + dhcpd_get_pool_range(&pool_start_host, &pool_end_host); + pool_start.s_addr = htonl(pool_start_host); + next_ip_host = ntohl(pool_start.s_addr); + + prev_udp_handler = net_get_udp_handler(); + net_set_udp_handler(dhcpd_udp_handler); + + dhcpd_running = true; + + return 0; +} + +void mtk_dhcpd_stop(void) +{ + if (!dhcpd_running) + return; + + /* + * If the network loop already cleared handlers, don't resurrect another + * handler here. We only restore the previous handler if we are still + * installed. + */ + if (net_get_udp_handler() == dhcpd_udp_handler) + net_set_udp_handler(prev_udp_handler); + prev_udp_handler = NULL; + dhcpd_running = false; +} diff --git a/uboot-mtk-20220606/net/net.c b/uboot-mtk-20220606/net/net.c index a943d245e..e2b12980f 100644 --- a/uboot-mtk-20220606/net/net.c +++ b/uboot-mtk-20220606/net/net.c @@ -118,6 +118,9 @@ #include "wol.h" #endif #include "tcp.h" +#if defined(CONFIG_MTK_DHCPD) +#include +#endif /** BOOTP EXTENTIONS **/ @@ -448,6 +451,15 @@ restart: debug_cond(DEBUG_INT_STATE, "--- net_loop Init\n"); net_init_loop(); +#if defined(CONFIG_MTK_DHCPD) + /* + * net_init() clears UDP handlers on first call. + * For web failsafe (TCP), enable the minimal DHCP server after init. + */ + if (protocol == TCP) + mtk_dhcpd_start(); +#endif + switch (net_check_prereq(protocol)) { case 1: /* network not configured */ diff --git a/uboot-mtk-20230718-09eda825/failsafe/failsafe.c b/uboot-mtk-20230718-09eda825/failsafe/failsafe.c index 61aba9f5f..5a60cb4fd 100644 --- a/uboot-mtk-20230718-09eda825/failsafe/failsafe.c +++ b/uboot-mtk-20230718-09eda825/failsafe/failsafe.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -409,8 +410,14 @@ int start_web_failsafe(void) httpd_register_uri_handler(inst, "/version", &version_handler, NULL); httpd_register_uri_handler(inst, "", ¬_found_handler, NULL); + if (IS_ENABLED(CONFIG_MTK_DHCPD)) + mtk_dhcpd_start(); + net_loop(MTK_TCP); + if (IS_ENABLED(CONFIG_MTK_DHCPD)) + mtk_dhcpd_stop(); + return 0; } diff --git a/uboot-mtk-20230718-09eda825/include/net/mtk_dhcpd.h b/uboot-mtk-20230718-09eda825/include/net/mtk_dhcpd.h new file mode 100644 index 000000000..b78723f85 --- /dev/null +++ b/uboot-mtk-20230718-09eda825/include/net/mtk_dhcpd.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Yuzhii0718 + * + * All rights reserved. + * + * This file is part of the project bl-mt798x-dhcpd + * You may not use, copy, modify or distribute this file except in compliance with the license agreement. + * + * Minimal DHCPv4 server for MediaTek web failsafe. + */ + +#ifndef __NET_MTK_DHCPD_H__ +#define __NET_MTK_DHCPD_H__ + +int mtk_dhcpd_start(void); +void mtk_dhcpd_stop(void); + +#endif /* __NET_MTK_DHCPD_H__ */ diff --git a/uboot-mtk-20230718-09eda825/net/Kconfig b/uboot-mtk-20230718-09eda825/net/Kconfig index b352b23a7..a31605095 100644 --- a/uboot-mtk-20230718-09eda825/net/Kconfig +++ b/uboot-mtk-20230718-09eda825/net/Kconfig @@ -265,6 +265,46 @@ config MTK_HTTPD help Enable mediatek httpd framework that allows some customized features. +config MTK_DHCPD + bool "Minimal DHCP server for MediaTek web failsafe" + default y + depends on MTK_HTTPD + help + Enable a minimal DHCPv4 server that answers DHCPDISCOVER/DHCPREQUEST + while the MediaTek web failsafe UI (httpd) is running. + +config MTK_DHCPD_USE_CONFIG_IP + bool "Use CONFIG_IPADDR/CONFIG_NETMASK and configurable pool" + default y + depends on MTK_DHCPD + help + Use CONFIG_IPADDR and CONFIG_NETMASK as DHCP server address/netmask. + Pool start host and size are taken from the options below. + If disabled, fallback defaults will be used. + +config MTK_DHCPD_POOL_START_HOST + int "DHCP pool start host address" + default 100 + depends on MTK_DHCPD_USE_CONFIG_IP + help + Host part for pool start. For example, 100 -> x.x.x.100. + +config MTK_DHCPD_POOL_SIZE + int "DHCP pool size" + default 101 + depends on MTK_DHCPD_USE_CONFIG_IP + help + Pool size, e.g. 101 means .100 ~ .200. + +config MTK_DHCPD_ENHANCED + bool "Enhanced DHCP server behavior" + default y + depends on MTK_DHCPD + help + Enable enhanced DHCPREQUEST validation and stable IP allocation. + Adds Server Identifier checks, DHCPNAK support, and hash-based pool + allocation with collision avoidance. + config NET_FORCE_IPADDR bool "Use ipaddr and netmask with CONFIG_IPADDR and CONFIG_NETMASK" default n diff --git a/uboot-mtk-20230718-09eda825/net/Makefile b/uboot-mtk-20230718-09eda825/net/Makefile index 6a88aa59b..d32036892 100644 --- a/uboot-mtk-20230718-09eda825/net/Makefile +++ b/uboot-mtk-20230718-09eda825/net/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_PROT_TCP) += tcp.o obj-$(CONFIG_CMD_WGET) += wget.o obj-$(CONFIG_MTK_TCP) += mtk_tcp.o obj-$(CONFIG_MTK_HTTPD) += mtk_httpd.o +obj-$(CONFIG_MTK_DHCPD) += mtk_dhcpd.o # Disable this warning as it is triggered by: # sprintf(buf, index ? "foo%d" : "foo", index) diff --git a/uboot-mtk-20230718-09eda825/net/mtk_dhcpd.c b/uboot-mtk-20230718-09eda825/net/mtk_dhcpd.c new file mode 100644 index 000000000..941671777 --- /dev/null +++ b/uboot-mtk-20230718-09eda825/net/mtk_dhcpd.c @@ -0,0 +1,773 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Yuzhii0718 + * + * All rights reserved. + * + * This file is part of the project bl-mt798x-dhcpd + * You may not use, copy, modify or distribute this file except in compliance with the license agreement. + * + * Minimal DHCPv4 server for MediaTek web failsafe. + * + * Goals: + * - Provide IP/netmask/gateway/DNS to a directly connected PC + * - Auto-start with web failsafe (httpd) + * - Small and self-contained + */ + +#include +#include + +#include + +#define DHCPD_SERVER_PORT 67 +#define DHCPD_CLIENT_PORT 68 + +/* + * BOOTP/DHCP has a historical minimum message size of 300 bytes. + * Some clients (notably Windows) may ignore shorter replies. + */ +#define DHCPD_MIN_BOOTP_LEN 300 + +/* BOOTP/DHCP message header (RFC 2131) */ +struct dhcpd_pkt { + u8 op; + u8 htype; + u8 hlen; + u8 hops; + u32 xid; + u16 secs; + u16 flags; + u32 ciaddr; + u32 yiaddr; + u32 siaddr; + u32 giaddr; + u8 chaddr[16]; + u8 sname[64]; + u8 file[128]; + u8 vend[312]; +} __packed; + +#define BOOTREQUEST 1 +#define BOOTREPLY 2 + +#define HTYPE_ETHER 1 +#define HLEN_ETHER 6 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPNAK 6 +#define DHCPACK 5 + +#define DHCP_OPTION_PAD 0 +#define DHCP_OPTION_SUBNET_MASK 1 +#define DHCP_OPTION_ROUTER 3 +#define DHCP_OPTION_DNS_SERVER 6 +#define DHCP_OPTION_REQ_IPADDR 50 +#define DHCP_OPTION_LEASE_TIME 51 +#define DHCP_OPTION_MSG_TYPE 53 +#define DHCP_OPTION_SERVER_ID 54 +#define DHCP_OPTION_MESSAGE 56 +#define DHCP_OPTION_END 255 + +#define DHCP_FLAG_BROADCAST 0x8000 + +#define DHCPD_DEFAULT_IP_STR "192.168.1.1" +#define DHCPD_DEFAULT_NETMASK_STR "255.255.255.0" +#define DHCPD_DEFAULT_POOL_START_HOST 100 +#define DHCPD_DEFAULT_POOL_SIZE 101 + +#define DHCPD_MAX_CLIENTS 8 + +static const u8 dhcp_magic_cookie[4] = { 99, 130, 83, 99 }; + +struct dhcpd_lease { + bool used; + u8 mac[6]; + struct in_addr ip; +}; + +static struct dhcpd_lease leases[DHCPD_MAX_CLIENTS]; +static u32 next_ip_host; + +static rxhand_f *prev_udp_handler; +static bool dhcpd_running; + +static struct in_addr dhcpd_get_server_ip(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return string_to_ip(CONFIG_IPADDR); +#else + if (net_ip.s_addr) + return net_ip; + + return string_to_ip(DHCPD_DEFAULT_IP_STR); +#endif +} + +static struct in_addr dhcpd_get_netmask(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return string_to_ip(CONFIG_NETMASK); +#else + if (net_netmask.s_addr) + return net_netmask; + + return string_to_ip(DHCPD_DEFAULT_NETMASK_STR); +#endif +} + +static struct in_addr dhcpd_get_gateway(void) +{ + if (net_gateway.s_addr) + return net_gateway; + + return dhcpd_get_server_ip(); +} + +static struct in_addr dhcpd_get_dns(void) +{ + if (net_dns_server.s_addr) + return net_dns_server; + + return dhcpd_get_server_ip(); +} + +static u32 dhcpd_get_pool_start_host(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return (u32)CONFIG_MTK_DHCPD_POOL_START_HOST; +#else + return DHCPD_DEFAULT_POOL_START_HOST; +#endif +} + +static u32 dhcpd_get_pool_size(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return (u32)CONFIG_MTK_DHCPD_POOL_SIZE; +#else + return DHCPD_DEFAULT_POOL_SIZE; +#endif +} + +static void dhcpd_get_pool_range(u32 *start, u32 *end) +{ + struct in_addr server_ip = dhcpd_get_server_ip(); + struct in_addr netmask = dhcpd_get_netmask(); + u32 ip_host = ntohl(server_ip.s_addr); + u32 mask = ntohl(netmask.s_addr); + u32 host_mask = ~mask; + u32 host_start = dhcpd_get_pool_start_host(); + u32 size = dhcpd_get_pool_size(); + u32 net = ip_host & mask; + u32 s, e, max_host; + + if (!size) + size = 1; + + max_host = host_mask; + host_start &= host_mask; + if (host_start == 0) + host_start = 1; + + s = net | host_start; + e = s + size - 1; + + if ((e & mask) != net || e > (net | max_host)) + e = net | max_host; + + if (start) + *start = s; + if (end) + *end = e; +} + +static bool dhcpd_mac_equal(const u8 *a, const u8 *b) +{ + return memcmp(a, b, 6) == 0; +} + +static struct dhcpd_lease *dhcpd_find_lease(const u8 *mac) +{ + int i; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (leases[i].used && dhcpd_mac_equal(leases[i].mac, mac)) + return &leases[i]; + } + + return NULL; +} + +static bool dhcpd_ip_in_pool(u32 ip_host) +{ + u32 start, end; + + dhcpd_get_pool_range(&start, &end); + + return ip_host >= start && ip_host <= end; +} + +#ifdef CONFIG_MTK_DHCPD_ENHANCED +static bool dhcpd_ip_is_allocated(u32 ip_host) +{ + int i; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (!leases[i].used) + continue; + if (ntohl(leases[i].ip.s_addr) == ip_host) + return true; + } + + return false; +} + +static bool dhcpd_ip_allocated_to_mac(u32 ip_host, const u8 *mac) +{ + int i; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (!leases[i].used) + continue; + if (ntohl(leases[i].ip.s_addr) != ip_host) + continue; + return dhcpd_mac_equal(leases[i].mac, mac); + } + + return false; +} + +static u32 dhcpd_mac_hash(const u8 *mac) +{ + u32 h = 2166136261u; + int i; + + for (i = 0; i < 6; i++) { + h ^= mac[i]; + h *= 16777619u; + } + + return h; +} +#endif + +static struct in_addr dhcpd_alloc_ip(const u8 *mac) +{ + struct dhcpd_lease *l; + u32 start, end; + int i; + struct in_addr ip; + u32 pool_size; + + l = dhcpd_find_lease(mac); + if (l && dhcpd_ip_in_pool(ntohl(l->ip.s_addr))) + return l->ip; + + dhcpd_get_pool_range(&start, &end); + + pool_size = end >= start ? (end - start + 1) : 0; + +#ifdef CONFIG_MTK_DHCPD_ENHANCED + if (pool_size) { + u32 hash = dhcpd_mac_hash(mac); + u32 off = hash % pool_size; + + for (i = 0; i < (int)pool_size; i++) { + u32 cand = start + ((off + i) % pool_size); + if (!dhcpd_ip_is_allocated(cand)) { + ip.s_addr = htonl(cand); + return ip; + } + } + } +#else + if (!next_ip_host) + next_ip_host = start; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + int idx; + + idx = i; + if (!leases[idx].used) { + leases[idx].used = true; + memcpy(leases[idx].mac, mac, 6); + leases[idx].ip.s_addr = htonl(next_ip_host); + + next_ip_host++; + if (next_ip_host > end) + next_ip_host = start; + + return leases[idx].ip; + } + } +#endif + + /* No free slot: just return the first address in pool */ + ip.s_addr = htonl(start); + return ip; +} + +static u8 dhcpd_parse_msg_type(const struct dhcpd_pkt *bp, unsigned int len) +{ + unsigned int fixed = offsetof(struct dhcpd_pkt, vend); + const u8 *opt; + unsigned int optlen; + + if (len < fixed + 4) + return 0; + + opt = (const u8 *)bp->vend; + optlen = len - fixed; + + if (memcmp(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie))) + return 0; + + opt += 4; + optlen -= 4; + + while (optlen) { + u8 code; + u8 olen; + + code = *opt++; + optlen--; + + if (code == DHCP_OPTION_PAD) + continue; + if (code == DHCP_OPTION_END) + break; + + if (!optlen) + break; + olen = *opt++; + optlen--; + + if (olen > optlen) + break; + + if (code == DHCP_OPTION_MSG_TYPE) { + if (olen >= 1) + return opt[0]; + } + + opt += olen; + optlen -= olen; + } + + return 0; +} + +static bool dhcpd_parse_req_ip(const struct dhcpd_pkt *bp, unsigned int len, + struct in_addr *req_ip) +{ + unsigned int fixed = offsetof(struct dhcpd_pkt, vend); + const u8 *opt; + unsigned int optlen; + + if (len < fixed + 4) + return false; + + opt = (const u8 *)bp->vend; + optlen = len - fixed; + + if (memcmp(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie))) + return false; + + opt += 4; + optlen -= 4; + + while (optlen) { + u8 code; + u8 olen; + + code = *opt++; + optlen--; + + if (code == DHCP_OPTION_PAD) + continue; + if (code == DHCP_OPTION_END) + break; + + if (!optlen) + break; + olen = *opt++; + optlen--; + + if (olen > optlen) + break; + + if (code == DHCP_OPTION_REQ_IPADDR && olen == 4) { + memcpy(&req_ip->s_addr, opt, 4); + return true; + } + + opt += olen; + optlen -= olen; + } + + return false; +} + +#ifdef CONFIG_MTK_DHCPD_ENHANCED +static bool dhcpd_parse_server_id(const struct dhcpd_pkt *bp, unsigned int len, + struct in_addr *server_ip) +{ + unsigned int fixed = offsetof(struct dhcpd_pkt, vend); + const u8 *opt; + unsigned int optlen; + + if (len < fixed + 4) + return false; + + opt = (const u8 *)bp->vend; + optlen = len - fixed; + + if (memcmp(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie))) + return false; + + opt += 4; + optlen -= 4; + + while (optlen) { + u8 code; + u8 olen; + + code = *opt++; + optlen--; + + if (code == DHCP_OPTION_PAD) + continue; + if (code == DHCP_OPTION_END) + break; + + if (!optlen) + break; + olen = *opt++; + optlen--; + + if (olen > optlen) + break; + + if (code == DHCP_OPTION_SERVER_ID && olen == 4) { + memcpy(&server_ip->s_addr, opt, 4); + return true; + } + + opt += olen; + optlen -= olen; + } + + return false; +} + +static void dhcpd_process_lease(const u8 *mac, struct in_addr ip) +{ + struct dhcpd_lease *l; + int i; + + l = dhcpd_find_lease(mac); + if (l) { + l->ip = ip; + return; + } + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (!leases[i].used) { + leases[i].used = true; + memcpy(leases[i].mac, mac, 6); + leases[i].ip = ip; + return; + } + } + + /* Fallback: replace the first entry */ + leases[0].used = true; + memcpy(leases[0].mac, mac, 6); + leases[0].ip = ip; +} + +static bool dhcpd_same_subnet(struct in_addr a, struct in_addr b, + struct in_addr mask) +{ + return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr); +} +#endif + +static u8 *dhcpd_opt_add_u8(u8 *p, u8 code, u8 val) +{ + *p++ = code; + *p++ = 1; + *p++ = val; + return p; +} + +static u8 *dhcpd_opt_add_u32(u8 *p, u8 code, __be32 val) +{ + *p++ = code; + *p++ = 4; + memcpy(p, &val, 4); + return p + 4; +} + +static u8 *dhcpd_opt_add_inaddr(u8 *p, u8 code, struct in_addr addr) +{ + return dhcpd_opt_add_u32(p, code, addr.s_addr); +} + +static int dhcpd_send_reply(const struct dhcpd_pkt *req, unsigned int req_len, + u8 dhcp_msg_type, struct in_addr yiaddr, + const char *nak_message) +{ + struct dhcpd_pkt *bp; + struct in_addr server_ip, netmask, gw, dns; + struct in_addr bcast; + uchar *pkt; + uchar *payload; + int eth_hdr_size; + u8 *opt; + int payload_len; + __be32 lease; + + (void)req_len; + + server_ip = dhcpd_get_server_ip(); + netmask = dhcpd_get_netmask(); + gw = dhcpd_get_gateway(); + dns = dhcpd_get_dns(); + + bcast.s_addr = 0xFFFFFFFF; + + pkt = net_tx_packet; + eth_hdr_size = net_set_ether(pkt, net_bcast_ethaddr, PROT_IP); + net_set_udp_header(pkt + eth_hdr_size, bcast, + DHCPD_CLIENT_PORT, DHCPD_SERVER_PORT, 0); + + payload = pkt + eth_hdr_size + IP_UDP_HDR_SIZE; + bp = (struct dhcpd_pkt *)payload; + memset(bp, 0, sizeof(*bp)); + + bp->op = BOOTREPLY; + bp->htype = HTYPE_ETHER; + bp->hlen = HLEN_ETHER; + bp->hops = 0; + bp->xid = req->xid; + bp->secs = req->secs; + bp->flags = htons(DHCP_FLAG_BROADCAST); + bp->ciaddr = 0; + bp->yiaddr = yiaddr.s_addr; + bp->siaddr = server_ip.s_addr; + bp->giaddr = 0; + memcpy(bp->chaddr, req->chaddr, sizeof(bp->chaddr)); + + opt = (u8 *)bp->vend; + memcpy(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie)); + opt += 4; + + opt = dhcpd_opt_add_u8(opt, DHCP_OPTION_MSG_TYPE, dhcp_msg_type); + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_SERVER_ID, server_ip); + + if (dhcp_msg_type != DHCPNAK) { + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_SUBNET_MASK, netmask); + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_ROUTER, gw); + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_DNS_SERVER, dns); + + lease = htonl(3600); + opt = dhcpd_opt_add_u32(opt, DHCP_OPTION_LEASE_TIME, lease); + } else if (nak_message && *nak_message) { + size_t msg_len = strlen(nak_message); + u8 len = msg_len > 240 ? 240 : (u8)msg_len; + + *opt++ = DHCP_OPTION_MESSAGE; + *opt++ = len; + memcpy(opt, nak_message, len); + opt += len; + } + + *opt++ = DHCP_OPTION_END; + + payload_len = (int)((uintptr_t)opt - (uintptr_t)payload); + if (payload_len < DHCPD_MIN_BOOTP_LEN) + payload_len = DHCPD_MIN_BOOTP_LEN; + + /* Update UDP header with actual payload length */ + net_set_udp_header(pkt + eth_hdr_size, bcast, + DHCPD_CLIENT_PORT, DHCPD_SERVER_PORT, payload_len); + + net_send_packet(pkt, eth_hdr_size + IP_UDP_HDR_SIZE + payload_len); + + return 0; +} + +static void dhcpd_handle_packet(uchar *pkt, unsigned int dport, + struct in_addr sip, unsigned int sport, + unsigned int len) +{ + const struct dhcpd_pkt *bp = (const struct dhcpd_pkt *)pkt; + u8 msg_type; + struct in_addr yiaddr; + struct in_addr req_ip; + + (void)sip; + + if (!dhcpd_running) + return; + + if (dport != DHCPD_SERVER_PORT || sport != DHCPD_CLIENT_PORT) + return; + + if (len < offsetof(struct dhcpd_pkt, vend)) + return; + + if (bp->op != BOOTREQUEST) + return; + + if (bp->htype != HTYPE_ETHER || bp->hlen != HLEN_ETHER) + return; + + msg_type = dhcpd_parse_msg_type(bp, len); + if (!msg_type) + return; + + debug_cond(DEBUG_DEV_PKT, "dhcpd: msg=%u from %pM\n", msg_type, bp->chaddr); + + switch (msg_type) { + case DHCPDISCOVER: + yiaddr = dhcpd_alloc_ip(bp->chaddr); + debug_cond(DEBUG_DEV_PKT, "dhcpd: offer %pI4\n", &yiaddr); + dhcpd_send_reply(bp, len, DHCPOFFER, yiaddr, NULL); + break; + case DHCPREQUEST: + +#ifdef CONFIG_MTK_DHCPD_ENHANCED + { + struct in_addr server_id; + struct in_addr server_ip = dhcpd_get_server_ip(); + struct in_addr netmask = dhcpd_get_netmask(); + bool has_server_id; + bool has_req_ip; + u32 ip_host; + struct in_addr zero_ip; + + zero_ip.s_addr = 0; + has_server_id = dhcpd_parse_server_id(bp, len, &server_id); + if (has_server_id && server_id.s_addr != server_ip.s_addr) + return; + + has_req_ip = dhcpd_parse_req_ip(bp, len, &req_ip); + if (!has_req_ip) { + yiaddr = dhcpd_alloc_ip(bp->chaddr); + dhcpd_process_lease(bp->chaddr, yiaddr); + dhcpd_send_reply(bp, len, DHCPACK, yiaddr, NULL); + break; + } + + ip_host = ntohl(req_ip.s_addr); + if (!dhcpd_same_subnet(req_ip, server_ip, netmask)) { + dhcpd_send_reply(bp, len, DHCPNAK, zero_ip, "bad subnet"); + break; + } + if (!dhcpd_ip_in_pool(ip_host)) { + dhcpd_send_reply(bp, len, DHCPNAK, zero_ip, "outside pool"); + break; + } + if (dhcpd_ip_is_allocated(ip_host) && + !dhcpd_ip_allocated_to_mac(ip_host, bp->chaddr)) { + dhcpd_send_reply(bp, len, DHCPNAK, zero_ip, "in use"); + break; + } + + yiaddr = req_ip; + dhcpd_process_lease(bp->chaddr, yiaddr); + dhcpd_send_reply(bp, len, DHCPACK, yiaddr, NULL); + } +#else + /* If client requests a specific IP, validate it */ + if (dhcpd_parse_req_ip(bp, len, &req_ip)) { + u32 ip_host = ntohl(req_ip.s_addr); + if (dhcpd_ip_in_pool(ip_host)) { + yiaddr = req_ip; + } else { + yiaddr = dhcpd_alloc_ip(bp->chaddr); + } + } else { + yiaddr = dhcpd_alloc_ip(bp->chaddr); + } + dhcpd_send_reply(bp, len, DHCPACK, yiaddr, NULL); +#endif + break; + default: + break; + } +} + +static void dhcpd_udp_handler(uchar *pkt, unsigned int dport, + struct in_addr sip, unsigned int sport, + unsigned int len) +{ + dhcpd_handle_packet(pkt, dport, sip, sport, len); + + if (prev_udp_handler) + prev_udp_handler(pkt, dport, sip, sport, len); +} + +int mtk_dhcpd_start(void) +{ + struct in_addr pool_start; + u32 pool_start_host, pool_end_host; + + /* + * Be robust against net_init()/net_clear_handlers() resetting handlers. + * If we're already running but the UDP handler is no longer ours, re-hook. + */ + if (dhcpd_running) { + rxhand_f *cur = net_get_udp_handler(); + + if (cur != dhcpd_udp_handler) { + prev_udp_handler = cur; + net_set_udp_handler(dhcpd_udp_handler); + } + return 0; + } + + /* Ensure we have a usable local IP, otherwise UDP replies will use 0.0.0.0 */ + if (!net_ip.s_addr) + net_ip = dhcpd_get_server_ip(); + if (!net_netmask.s_addr) + net_netmask = dhcpd_get_netmask(); + if (!net_gateway.s_addr) + net_gateway = net_ip; + if (!net_dns_server.s_addr) + net_dns_server = net_ip; + + memset(leases, 0, sizeof(leases)); + + dhcpd_get_pool_range(&pool_start_host, &pool_end_host); + pool_start.s_addr = htonl(pool_start_host); + next_ip_host = ntohl(pool_start.s_addr); + + prev_udp_handler = net_get_udp_handler(); + net_set_udp_handler(dhcpd_udp_handler); + + dhcpd_running = true; + + return 0; +} + +void mtk_dhcpd_stop(void) +{ + if (!dhcpd_running) + return; + + /* + * If the network loop already cleared handlers, don't resurrect another + * handler here. We only restore the previous handler if we are still + * installed. + */ + if (net_get_udp_handler() == dhcpd_udp_handler) + net_set_udp_handler(prev_udp_handler); + prev_udp_handler = NULL; + dhcpd_running = false; +} diff --git a/uboot-mtk-20230718-09eda825/net/net.c b/uboot-mtk-20230718-09eda825/net/net.c index 550e92f10..d60334f1d 100644 --- a/uboot-mtk-20230718-09eda825/net/net.c +++ b/uboot-mtk-20230718-09eda825/net/net.c @@ -127,6 +127,9 @@ #if defined(CONFIG_MTK_TCP) #include "mtk_tcp.h" #endif +#if defined(CONFIG_MTK_DHCPD) +#include +#endif #include "dhcpv6.h" #include "net_rand.h" @@ -492,6 +495,15 @@ restart: debug_cond(DEBUG_INT_STATE, "--- net_loop Init\n"); net_init_loop(); +#if defined(CONFIG_MTK_DHCPD) + /* + * net_init() clears UDP handlers on first call. + * For web failsafe (MTK_TCP), enable the minimal DHCP server after init. + */ + if (protocol == MTK_TCP) + mtk_dhcpd_start(); +#endif + if (!test_eth_enabled()) return 0; diff --git a/uboot-mtk-20250711/failsafe/failsafe.c b/uboot-mtk-20250711/failsafe/failsafe.c index 91aa02260..bbc46d52c 100644 --- a/uboot-mtk-20250711/failsafe/failsafe.c +++ b/uboot-mtk-20250711/failsafe/failsafe.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -409,8 +410,14 @@ int start_web_failsafe(void) httpd_register_uri_handler(inst, "/version", &version_handler, NULL); httpd_register_uri_handler(inst, "", ¬_found_handler, NULL); + if (IS_ENABLED(CONFIG_MTK_DHCPD)) + mtk_dhcpd_start(); + net_loop(MTK_TCP); + if (IS_ENABLED(CONFIG_MTK_DHCPD)) + mtk_dhcpd_stop(); + return 0; } diff --git a/uboot-mtk-20250711/include/net/mtk_dhcpd.h b/uboot-mtk-20250711/include/net/mtk_dhcpd.h new file mode 100644 index 000000000..b78723f85 --- /dev/null +++ b/uboot-mtk-20250711/include/net/mtk_dhcpd.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Yuzhii0718 + * + * All rights reserved. + * + * This file is part of the project bl-mt798x-dhcpd + * You may not use, copy, modify or distribute this file except in compliance with the license agreement. + * + * Minimal DHCPv4 server for MediaTek web failsafe. + */ + +#ifndef __NET_MTK_DHCPD_H__ +#define __NET_MTK_DHCPD_H__ + +int mtk_dhcpd_start(void); +void mtk_dhcpd_stop(void); + +#endif /* __NET_MTK_DHCPD_H__ */ diff --git a/uboot-mtk-20250711/net/Kconfig b/uboot-mtk-20250711/net/Kconfig index fa82f9ce0..7c3fac454 100644 --- a/uboot-mtk-20250711/net/Kconfig +++ b/uboot-mtk-20250711/net/Kconfig @@ -233,6 +233,46 @@ config MTK_HTTPD help Enable mediatek httpd framework that allows some customized features. +config MTK_DHCPD + bool "Minimal DHCP server for MediaTek web failsafe" + default y + depends on MTK_HTTPD + help + Enable a minimal DHCPv4 server that answers DHCPDISCOVER/DHCPREQUEST + while the MediaTek web failsafe UI (httpd) is running. + +config MTK_DHCPD_USE_CONFIG_IP + bool "Use CONFIG_IPADDR/CONFIG_NETMASK and configurable pool" + default y + depends on MTK_DHCPD + help + Use CONFIG_IPADDR and CONFIG_NETMASK as DHCP server address/netmask. + Pool start host and size are taken from the options below. + If disabled, fallback defaults will be used. + +config MTK_DHCPD_POOL_START_HOST + int "DHCP pool start host address" + default 100 + depends on MTK_DHCPD_USE_CONFIG_IP + help + Host part for pool start. For example, 100 -> x.x.x.100. + +config MTK_DHCPD_POOL_SIZE + int "DHCP pool size" + default 101 + depends on MTK_DHCPD_USE_CONFIG_IP + help + Pool size, e.g. 101 means .100 ~ .200. + +config MTK_DHCPD_ENHANCED + bool "Enhanced DHCP server behavior" + default y + depends on MTK_DHCPD + help + Enable enhanced DHCPREQUEST validation and stable IP allocation. + Adds Server Identifier checks, DHCPNAK support, and hash-based pool + allocation with collision avoidance. + config NET_FORCE_IPADDR bool "Use ipaddr and netmask with CONFIG_IPADDR and CONFIG_NETMASK" default n diff --git a/uboot-mtk-20250711/net/Makefile b/uboot-mtk-20250711/net/Makefile index 8d82b35d7..3e70167cb 100644 --- a/uboot-mtk-20250711/net/Makefile +++ b/uboot-mtk-20250711/net/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_PROT_TCP) += tcp.o obj-$(CONFIG_WGET) += wget.o obj-$(CONFIG_MTK_TCP) += mtk_tcp.o obj-$(CONFIG_MTK_HTTPD) += mtk_httpd.o +obj-$(CONFIG_MTK_DHCPD) += mtk_dhcpd.o # Disable this warning as it is triggered by: # sprintf(buf, index ? "foo%d" : "foo", index) diff --git a/uboot-mtk-20250711/net/mtk_dhcpd.c b/uboot-mtk-20250711/net/mtk_dhcpd.c new file mode 100644 index 000000000..941671777 --- /dev/null +++ b/uboot-mtk-20250711/net/mtk_dhcpd.c @@ -0,0 +1,773 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Yuzhii0718 + * + * All rights reserved. + * + * This file is part of the project bl-mt798x-dhcpd + * You may not use, copy, modify or distribute this file except in compliance with the license agreement. + * + * Minimal DHCPv4 server for MediaTek web failsafe. + * + * Goals: + * - Provide IP/netmask/gateway/DNS to a directly connected PC + * - Auto-start with web failsafe (httpd) + * - Small and self-contained + */ + +#include +#include + +#include + +#define DHCPD_SERVER_PORT 67 +#define DHCPD_CLIENT_PORT 68 + +/* + * BOOTP/DHCP has a historical minimum message size of 300 bytes. + * Some clients (notably Windows) may ignore shorter replies. + */ +#define DHCPD_MIN_BOOTP_LEN 300 + +/* BOOTP/DHCP message header (RFC 2131) */ +struct dhcpd_pkt { + u8 op; + u8 htype; + u8 hlen; + u8 hops; + u32 xid; + u16 secs; + u16 flags; + u32 ciaddr; + u32 yiaddr; + u32 siaddr; + u32 giaddr; + u8 chaddr[16]; + u8 sname[64]; + u8 file[128]; + u8 vend[312]; +} __packed; + +#define BOOTREQUEST 1 +#define BOOTREPLY 2 + +#define HTYPE_ETHER 1 +#define HLEN_ETHER 6 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPNAK 6 +#define DHCPACK 5 + +#define DHCP_OPTION_PAD 0 +#define DHCP_OPTION_SUBNET_MASK 1 +#define DHCP_OPTION_ROUTER 3 +#define DHCP_OPTION_DNS_SERVER 6 +#define DHCP_OPTION_REQ_IPADDR 50 +#define DHCP_OPTION_LEASE_TIME 51 +#define DHCP_OPTION_MSG_TYPE 53 +#define DHCP_OPTION_SERVER_ID 54 +#define DHCP_OPTION_MESSAGE 56 +#define DHCP_OPTION_END 255 + +#define DHCP_FLAG_BROADCAST 0x8000 + +#define DHCPD_DEFAULT_IP_STR "192.168.1.1" +#define DHCPD_DEFAULT_NETMASK_STR "255.255.255.0" +#define DHCPD_DEFAULT_POOL_START_HOST 100 +#define DHCPD_DEFAULT_POOL_SIZE 101 + +#define DHCPD_MAX_CLIENTS 8 + +static const u8 dhcp_magic_cookie[4] = { 99, 130, 83, 99 }; + +struct dhcpd_lease { + bool used; + u8 mac[6]; + struct in_addr ip; +}; + +static struct dhcpd_lease leases[DHCPD_MAX_CLIENTS]; +static u32 next_ip_host; + +static rxhand_f *prev_udp_handler; +static bool dhcpd_running; + +static struct in_addr dhcpd_get_server_ip(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return string_to_ip(CONFIG_IPADDR); +#else + if (net_ip.s_addr) + return net_ip; + + return string_to_ip(DHCPD_DEFAULT_IP_STR); +#endif +} + +static struct in_addr dhcpd_get_netmask(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return string_to_ip(CONFIG_NETMASK); +#else + if (net_netmask.s_addr) + return net_netmask; + + return string_to_ip(DHCPD_DEFAULT_NETMASK_STR); +#endif +} + +static struct in_addr dhcpd_get_gateway(void) +{ + if (net_gateway.s_addr) + return net_gateway; + + return dhcpd_get_server_ip(); +} + +static struct in_addr dhcpd_get_dns(void) +{ + if (net_dns_server.s_addr) + return net_dns_server; + + return dhcpd_get_server_ip(); +} + +static u32 dhcpd_get_pool_start_host(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return (u32)CONFIG_MTK_DHCPD_POOL_START_HOST; +#else + return DHCPD_DEFAULT_POOL_START_HOST; +#endif +} + +static u32 dhcpd_get_pool_size(void) +{ +#ifdef CONFIG_MTK_DHCPD_USE_CONFIG_IP + return (u32)CONFIG_MTK_DHCPD_POOL_SIZE; +#else + return DHCPD_DEFAULT_POOL_SIZE; +#endif +} + +static void dhcpd_get_pool_range(u32 *start, u32 *end) +{ + struct in_addr server_ip = dhcpd_get_server_ip(); + struct in_addr netmask = dhcpd_get_netmask(); + u32 ip_host = ntohl(server_ip.s_addr); + u32 mask = ntohl(netmask.s_addr); + u32 host_mask = ~mask; + u32 host_start = dhcpd_get_pool_start_host(); + u32 size = dhcpd_get_pool_size(); + u32 net = ip_host & mask; + u32 s, e, max_host; + + if (!size) + size = 1; + + max_host = host_mask; + host_start &= host_mask; + if (host_start == 0) + host_start = 1; + + s = net | host_start; + e = s + size - 1; + + if ((e & mask) != net || e > (net | max_host)) + e = net | max_host; + + if (start) + *start = s; + if (end) + *end = e; +} + +static bool dhcpd_mac_equal(const u8 *a, const u8 *b) +{ + return memcmp(a, b, 6) == 0; +} + +static struct dhcpd_lease *dhcpd_find_lease(const u8 *mac) +{ + int i; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (leases[i].used && dhcpd_mac_equal(leases[i].mac, mac)) + return &leases[i]; + } + + return NULL; +} + +static bool dhcpd_ip_in_pool(u32 ip_host) +{ + u32 start, end; + + dhcpd_get_pool_range(&start, &end); + + return ip_host >= start && ip_host <= end; +} + +#ifdef CONFIG_MTK_DHCPD_ENHANCED +static bool dhcpd_ip_is_allocated(u32 ip_host) +{ + int i; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (!leases[i].used) + continue; + if (ntohl(leases[i].ip.s_addr) == ip_host) + return true; + } + + return false; +} + +static bool dhcpd_ip_allocated_to_mac(u32 ip_host, const u8 *mac) +{ + int i; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (!leases[i].used) + continue; + if (ntohl(leases[i].ip.s_addr) != ip_host) + continue; + return dhcpd_mac_equal(leases[i].mac, mac); + } + + return false; +} + +static u32 dhcpd_mac_hash(const u8 *mac) +{ + u32 h = 2166136261u; + int i; + + for (i = 0; i < 6; i++) { + h ^= mac[i]; + h *= 16777619u; + } + + return h; +} +#endif + +static struct in_addr dhcpd_alloc_ip(const u8 *mac) +{ + struct dhcpd_lease *l; + u32 start, end; + int i; + struct in_addr ip; + u32 pool_size; + + l = dhcpd_find_lease(mac); + if (l && dhcpd_ip_in_pool(ntohl(l->ip.s_addr))) + return l->ip; + + dhcpd_get_pool_range(&start, &end); + + pool_size = end >= start ? (end - start + 1) : 0; + +#ifdef CONFIG_MTK_DHCPD_ENHANCED + if (pool_size) { + u32 hash = dhcpd_mac_hash(mac); + u32 off = hash % pool_size; + + for (i = 0; i < (int)pool_size; i++) { + u32 cand = start + ((off + i) % pool_size); + if (!dhcpd_ip_is_allocated(cand)) { + ip.s_addr = htonl(cand); + return ip; + } + } + } +#else + if (!next_ip_host) + next_ip_host = start; + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + int idx; + + idx = i; + if (!leases[idx].used) { + leases[idx].used = true; + memcpy(leases[idx].mac, mac, 6); + leases[idx].ip.s_addr = htonl(next_ip_host); + + next_ip_host++; + if (next_ip_host > end) + next_ip_host = start; + + return leases[idx].ip; + } + } +#endif + + /* No free slot: just return the first address in pool */ + ip.s_addr = htonl(start); + return ip; +} + +static u8 dhcpd_parse_msg_type(const struct dhcpd_pkt *bp, unsigned int len) +{ + unsigned int fixed = offsetof(struct dhcpd_pkt, vend); + const u8 *opt; + unsigned int optlen; + + if (len < fixed + 4) + return 0; + + opt = (const u8 *)bp->vend; + optlen = len - fixed; + + if (memcmp(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie))) + return 0; + + opt += 4; + optlen -= 4; + + while (optlen) { + u8 code; + u8 olen; + + code = *opt++; + optlen--; + + if (code == DHCP_OPTION_PAD) + continue; + if (code == DHCP_OPTION_END) + break; + + if (!optlen) + break; + olen = *opt++; + optlen--; + + if (olen > optlen) + break; + + if (code == DHCP_OPTION_MSG_TYPE) { + if (olen >= 1) + return opt[0]; + } + + opt += olen; + optlen -= olen; + } + + return 0; +} + +static bool dhcpd_parse_req_ip(const struct dhcpd_pkt *bp, unsigned int len, + struct in_addr *req_ip) +{ + unsigned int fixed = offsetof(struct dhcpd_pkt, vend); + const u8 *opt; + unsigned int optlen; + + if (len < fixed + 4) + return false; + + opt = (const u8 *)bp->vend; + optlen = len - fixed; + + if (memcmp(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie))) + return false; + + opt += 4; + optlen -= 4; + + while (optlen) { + u8 code; + u8 olen; + + code = *opt++; + optlen--; + + if (code == DHCP_OPTION_PAD) + continue; + if (code == DHCP_OPTION_END) + break; + + if (!optlen) + break; + olen = *opt++; + optlen--; + + if (olen > optlen) + break; + + if (code == DHCP_OPTION_REQ_IPADDR && olen == 4) { + memcpy(&req_ip->s_addr, opt, 4); + return true; + } + + opt += olen; + optlen -= olen; + } + + return false; +} + +#ifdef CONFIG_MTK_DHCPD_ENHANCED +static bool dhcpd_parse_server_id(const struct dhcpd_pkt *bp, unsigned int len, + struct in_addr *server_ip) +{ + unsigned int fixed = offsetof(struct dhcpd_pkt, vend); + const u8 *opt; + unsigned int optlen; + + if (len < fixed + 4) + return false; + + opt = (const u8 *)bp->vend; + optlen = len - fixed; + + if (memcmp(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie))) + return false; + + opt += 4; + optlen -= 4; + + while (optlen) { + u8 code; + u8 olen; + + code = *opt++; + optlen--; + + if (code == DHCP_OPTION_PAD) + continue; + if (code == DHCP_OPTION_END) + break; + + if (!optlen) + break; + olen = *opt++; + optlen--; + + if (olen > optlen) + break; + + if (code == DHCP_OPTION_SERVER_ID && olen == 4) { + memcpy(&server_ip->s_addr, opt, 4); + return true; + } + + opt += olen; + optlen -= olen; + } + + return false; +} + +static void dhcpd_process_lease(const u8 *mac, struct in_addr ip) +{ + struct dhcpd_lease *l; + int i; + + l = dhcpd_find_lease(mac); + if (l) { + l->ip = ip; + return; + } + + for (i = 0; i < DHCPD_MAX_CLIENTS; i++) { + if (!leases[i].used) { + leases[i].used = true; + memcpy(leases[i].mac, mac, 6); + leases[i].ip = ip; + return; + } + } + + /* Fallback: replace the first entry */ + leases[0].used = true; + memcpy(leases[0].mac, mac, 6); + leases[0].ip = ip; +} + +static bool dhcpd_same_subnet(struct in_addr a, struct in_addr b, + struct in_addr mask) +{ + return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr); +} +#endif + +static u8 *dhcpd_opt_add_u8(u8 *p, u8 code, u8 val) +{ + *p++ = code; + *p++ = 1; + *p++ = val; + return p; +} + +static u8 *dhcpd_opt_add_u32(u8 *p, u8 code, __be32 val) +{ + *p++ = code; + *p++ = 4; + memcpy(p, &val, 4); + return p + 4; +} + +static u8 *dhcpd_opt_add_inaddr(u8 *p, u8 code, struct in_addr addr) +{ + return dhcpd_opt_add_u32(p, code, addr.s_addr); +} + +static int dhcpd_send_reply(const struct dhcpd_pkt *req, unsigned int req_len, + u8 dhcp_msg_type, struct in_addr yiaddr, + const char *nak_message) +{ + struct dhcpd_pkt *bp; + struct in_addr server_ip, netmask, gw, dns; + struct in_addr bcast; + uchar *pkt; + uchar *payload; + int eth_hdr_size; + u8 *opt; + int payload_len; + __be32 lease; + + (void)req_len; + + server_ip = dhcpd_get_server_ip(); + netmask = dhcpd_get_netmask(); + gw = dhcpd_get_gateway(); + dns = dhcpd_get_dns(); + + bcast.s_addr = 0xFFFFFFFF; + + pkt = net_tx_packet; + eth_hdr_size = net_set_ether(pkt, net_bcast_ethaddr, PROT_IP); + net_set_udp_header(pkt + eth_hdr_size, bcast, + DHCPD_CLIENT_PORT, DHCPD_SERVER_PORT, 0); + + payload = pkt + eth_hdr_size + IP_UDP_HDR_SIZE; + bp = (struct dhcpd_pkt *)payload; + memset(bp, 0, sizeof(*bp)); + + bp->op = BOOTREPLY; + bp->htype = HTYPE_ETHER; + bp->hlen = HLEN_ETHER; + bp->hops = 0; + bp->xid = req->xid; + bp->secs = req->secs; + bp->flags = htons(DHCP_FLAG_BROADCAST); + bp->ciaddr = 0; + bp->yiaddr = yiaddr.s_addr; + bp->siaddr = server_ip.s_addr; + bp->giaddr = 0; + memcpy(bp->chaddr, req->chaddr, sizeof(bp->chaddr)); + + opt = (u8 *)bp->vend; + memcpy(opt, dhcp_magic_cookie, sizeof(dhcp_magic_cookie)); + opt += 4; + + opt = dhcpd_opt_add_u8(opt, DHCP_OPTION_MSG_TYPE, dhcp_msg_type); + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_SERVER_ID, server_ip); + + if (dhcp_msg_type != DHCPNAK) { + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_SUBNET_MASK, netmask); + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_ROUTER, gw); + opt = dhcpd_opt_add_inaddr(opt, DHCP_OPTION_DNS_SERVER, dns); + + lease = htonl(3600); + opt = dhcpd_opt_add_u32(opt, DHCP_OPTION_LEASE_TIME, lease); + } else if (nak_message && *nak_message) { + size_t msg_len = strlen(nak_message); + u8 len = msg_len > 240 ? 240 : (u8)msg_len; + + *opt++ = DHCP_OPTION_MESSAGE; + *opt++ = len; + memcpy(opt, nak_message, len); + opt += len; + } + + *opt++ = DHCP_OPTION_END; + + payload_len = (int)((uintptr_t)opt - (uintptr_t)payload); + if (payload_len < DHCPD_MIN_BOOTP_LEN) + payload_len = DHCPD_MIN_BOOTP_LEN; + + /* Update UDP header with actual payload length */ + net_set_udp_header(pkt + eth_hdr_size, bcast, + DHCPD_CLIENT_PORT, DHCPD_SERVER_PORT, payload_len); + + net_send_packet(pkt, eth_hdr_size + IP_UDP_HDR_SIZE + payload_len); + + return 0; +} + +static void dhcpd_handle_packet(uchar *pkt, unsigned int dport, + struct in_addr sip, unsigned int sport, + unsigned int len) +{ + const struct dhcpd_pkt *bp = (const struct dhcpd_pkt *)pkt; + u8 msg_type; + struct in_addr yiaddr; + struct in_addr req_ip; + + (void)sip; + + if (!dhcpd_running) + return; + + if (dport != DHCPD_SERVER_PORT || sport != DHCPD_CLIENT_PORT) + return; + + if (len < offsetof(struct dhcpd_pkt, vend)) + return; + + if (bp->op != BOOTREQUEST) + return; + + if (bp->htype != HTYPE_ETHER || bp->hlen != HLEN_ETHER) + return; + + msg_type = dhcpd_parse_msg_type(bp, len); + if (!msg_type) + return; + + debug_cond(DEBUG_DEV_PKT, "dhcpd: msg=%u from %pM\n", msg_type, bp->chaddr); + + switch (msg_type) { + case DHCPDISCOVER: + yiaddr = dhcpd_alloc_ip(bp->chaddr); + debug_cond(DEBUG_DEV_PKT, "dhcpd: offer %pI4\n", &yiaddr); + dhcpd_send_reply(bp, len, DHCPOFFER, yiaddr, NULL); + break; + case DHCPREQUEST: + +#ifdef CONFIG_MTK_DHCPD_ENHANCED + { + struct in_addr server_id; + struct in_addr server_ip = dhcpd_get_server_ip(); + struct in_addr netmask = dhcpd_get_netmask(); + bool has_server_id; + bool has_req_ip; + u32 ip_host; + struct in_addr zero_ip; + + zero_ip.s_addr = 0; + has_server_id = dhcpd_parse_server_id(bp, len, &server_id); + if (has_server_id && server_id.s_addr != server_ip.s_addr) + return; + + has_req_ip = dhcpd_parse_req_ip(bp, len, &req_ip); + if (!has_req_ip) { + yiaddr = dhcpd_alloc_ip(bp->chaddr); + dhcpd_process_lease(bp->chaddr, yiaddr); + dhcpd_send_reply(bp, len, DHCPACK, yiaddr, NULL); + break; + } + + ip_host = ntohl(req_ip.s_addr); + if (!dhcpd_same_subnet(req_ip, server_ip, netmask)) { + dhcpd_send_reply(bp, len, DHCPNAK, zero_ip, "bad subnet"); + break; + } + if (!dhcpd_ip_in_pool(ip_host)) { + dhcpd_send_reply(bp, len, DHCPNAK, zero_ip, "outside pool"); + break; + } + if (dhcpd_ip_is_allocated(ip_host) && + !dhcpd_ip_allocated_to_mac(ip_host, bp->chaddr)) { + dhcpd_send_reply(bp, len, DHCPNAK, zero_ip, "in use"); + break; + } + + yiaddr = req_ip; + dhcpd_process_lease(bp->chaddr, yiaddr); + dhcpd_send_reply(bp, len, DHCPACK, yiaddr, NULL); + } +#else + /* If client requests a specific IP, validate it */ + if (dhcpd_parse_req_ip(bp, len, &req_ip)) { + u32 ip_host = ntohl(req_ip.s_addr); + if (dhcpd_ip_in_pool(ip_host)) { + yiaddr = req_ip; + } else { + yiaddr = dhcpd_alloc_ip(bp->chaddr); + } + } else { + yiaddr = dhcpd_alloc_ip(bp->chaddr); + } + dhcpd_send_reply(bp, len, DHCPACK, yiaddr, NULL); +#endif + break; + default: + break; + } +} + +static void dhcpd_udp_handler(uchar *pkt, unsigned int dport, + struct in_addr sip, unsigned int sport, + unsigned int len) +{ + dhcpd_handle_packet(pkt, dport, sip, sport, len); + + if (prev_udp_handler) + prev_udp_handler(pkt, dport, sip, sport, len); +} + +int mtk_dhcpd_start(void) +{ + struct in_addr pool_start; + u32 pool_start_host, pool_end_host; + + /* + * Be robust against net_init()/net_clear_handlers() resetting handlers. + * If we're already running but the UDP handler is no longer ours, re-hook. + */ + if (dhcpd_running) { + rxhand_f *cur = net_get_udp_handler(); + + if (cur != dhcpd_udp_handler) { + prev_udp_handler = cur; + net_set_udp_handler(dhcpd_udp_handler); + } + return 0; + } + + /* Ensure we have a usable local IP, otherwise UDP replies will use 0.0.0.0 */ + if (!net_ip.s_addr) + net_ip = dhcpd_get_server_ip(); + if (!net_netmask.s_addr) + net_netmask = dhcpd_get_netmask(); + if (!net_gateway.s_addr) + net_gateway = net_ip; + if (!net_dns_server.s_addr) + net_dns_server = net_ip; + + memset(leases, 0, sizeof(leases)); + + dhcpd_get_pool_range(&pool_start_host, &pool_end_host); + pool_start.s_addr = htonl(pool_start_host); + next_ip_host = ntohl(pool_start.s_addr); + + prev_udp_handler = net_get_udp_handler(); + net_set_udp_handler(dhcpd_udp_handler); + + dhcpd_running = true; + + return 0; +} + +void mtk_dhcpd_stop(void) +{ + if (!dhcpd_running) + return; + + /* + * If the network loop already cleared handlers, don't resurrect another + * handler here. We only restore the previous handler if we are still + * installed. + */ + if (net_get_udp_handler() == dhcpd_udp_handler) + net_set_udp_handler(prev_udp_handler); + prev_udp_handler = NULL; + dhcpd_running = false; +} diff --git a/uboot-mtk-20250711/net/net.c b/uboot-mtk-20250711/net/net.c index e357d0755..419c27c13 100644 --- a/uboot-mtk-20250711/net/net.c +++ b/uboot-mtk-20250711/net/net.c @@ -131,6 +131,9 @@ #if defined(CONFIG_MTK_TCP) #include "mtk_tcp.h" #endif +#if defined(CONFIG_MTK_DHCPD) +#include +#endif /** BOOTP EXTENTIONS **/ @@ -499,6 +502,15 @@ restart: debug_cond(DEBUG_INT_STATE, "--- net_loop Init\n"); net_init_loop(); +#if defined(CONFIG_MTK_DHCPD) + /* + * net_init() clears UDP handlers on first call. + * For web failsafe (MTK_TCP), enable the minimal DHCP server after init. + */ + if (protocol == MTK_TCP) + mtk_dhcpd_start(); +#endif + if (!test_eth_enabled()) return 0;