Merge branch 'feat/dhcps_support_hostname_per_client' into 'master'

[lwip/dhcps]: Add support for reporting clients hostname

Closes IDFGH-9326

See merge request espressif/esp-idf!42094
This commit is contained in:
David Čermák
2025-09-26 21:47:50 +08:00
6 changed files with 173 additions and 7 deletions

View File

@@ -152,11 +152,31 @@ typedef struct {
bool preferred; /*!< The default preference of the address */ bool preferred; /*!< The default preference of the address */
} ip_event_add_ip6_t; } ip_event_add_ip6_t;
/** Event structure for IP_EVENT_ASSIGNED_IP_TO_CLIENT event */ /** Event structure for IP_EVENT_ASSIGNED_IP_TO_CLIENT event
*
* This event is posted when a local DHCP server (e.g., SoftAP) assigns an IPv4
* address to a client. The structure carries the assigned IPv4 address and the
* client's MAC address. If DHCP server support is disabled via Kconfig
* (CONFIG_LWIP_DHCPS=n), the \c ip field is not populated.
*
* If enabled by Kconfig (CONFIG_LWIP_DHCPS_REPORT_CLIENT_HOSTNAME=y), the
* optional DHCP client hostname (option 12) is also included in the \c hostname
* field. The hostname is a null-terminated UTF-8 string and may be empty if the
* client did not provide one. Its maximum length (including the terminator) is
* bounded by CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN; longer hostnames are truncated.
* The value is sanitized to contain only letters, digits, dot, dash, and
* underscore. Applications should treat this field as untrusted input.
*/
typedef struct { typedef struct {
esp_netif_t *esp_netif; /*!< Pointer to the associated netif handle */ esp_netif_t *esp_netif; /*!< Pointer to the associated netif handle */
esp_ip4_addr_t ip; /*!< IP address which was assigned to the station */ esp_ip4_addr_t ip; /*!< IP address which was assigned to the station */
uint8_t mac[6]; /*!< MAC address of the connected client */ uint8_t mac[6]; /*!< MAC address of the connected client */
/* Client hostname as provided via DHCP option 12 (if available). */
#ifndef CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN
#define CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN 64
#endif
#define ESP_NETIF_HOSTNAME_MAX_LEN CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN
char hostname[ESP_NETIF_HOSTNAME_MAX_LEN]; /*!< Optional DHCP client hostname (may be empty string) */
} ip_event_assigned_ip_to_client_t; } ip_event_assigned_ip_to_client_t;
/** Compatibility event structure for ip_event_ap_staipassigned_t event /** Compatibility event structure for ip_event_ap_staipassigned_t event

View File

@@ -1129,6 +1129,18 @@ static void esp_netif_dhcps_cb(void* arg, uint8_t ip[4], uint8_t mac[6])
ESP_LOGI(TAG, "DHCP server assigned IP to a client, IP is: " IPSTR, IP2STR(&evt.ip)); ESP_LOGI(TAG, "DHCP server assigned IP to a client, IP is: " IPSTR, IP2STR(&evt.ip));
ESP_LOGD(TAG, "Client's MAC: %x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); ESP_LOGD(TAG, "Client's MAC: %x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
#if CONFIG_LWIP_DHCPS_REPORT_CLIENT_HOSTNAME
/* Try to fetch hostname for this MAC if available */
if (esp_netif && esp_netif->dhcps) {
/* Ensure zero-terminated even if not found */
if (!dhcps_get_hostname_on_mac(esp_netif->dhcps, mac, evt.hostname, sizeof(evt.hostname))) {
if (sizeof(evt.hostname) > 0) {
evt.hostname[0] = '\0';
}
}
}
#endif
int ret = esp_event_post(IP_EVENT, IP_EVENT_ASSIGNED_IP_TO_CLIENT, &evt, sizeof(evt), 0); int ret = esp_event_post(IP_EVENT, IP_EVENT_ASSIGNED_IP_TO_CLIENT, &evt, sizeof(evt), 0);
if (ESP_OK != ret) { if (ESP_OK != ret) {
ESP_LOGE(TAG, "dhcps cb: failed to post IP_EVENT_ASSIGNED_IP_TO_CLIENT (%x)", ret); ESP_LOGE(TAG, "dhcps cb: failed to post IP_EVENT_ASSIGNED_IP_TO_CLIENT (%x)", ret);

View File

@@ -405,6 +405,15 @@ menu "LWIP"
Enabling this option allows the device to run the DHCP server Enabling this option allows the device to run the DHCP server
(to dynamically assign IPv4 addresses to clients). (to dynamically assign IPv4 addresses to clients).
config LWIP_DHCPS_REPORT_CLIENT_HOSTNAME
bool "DHCPS: Report client hostname in assigned-IP event"
default y
depends on LWIP_DHCPS
help
When enabled, the DHCP server parses client hostname (DHCP option 12)
and makes it available via IP_EVENT_ASSIGNED_IP_TO_CLIENT. This adds
a small amount of RAM usage per lease to store the hostname.
config LWIP_DHCPS_LEASE_UNIT config LWIP_DHCPS_LEASE_UNIT
int "Multiplier for lease time, in seconds" int "Multiplier for lease time, in seconds"
range 1 3600 range 1 3600
@@ -425,6 +434,16 @@ menu "LWIP"
After this number is exceeded, DHCP server removes of the oldest device After this number is exceeded, DHCP server removes of the oldest device
from it's address pool, without notification. from it's address pool, without notification.
config LWIP_DHCPS_MAX_HOSTNAME_LEN
int "Maximum client hostname length stored by DHCPS"
range 1 255
default 64
depends on LWIP_DHCPS_REPORT_CLIENT_HOSTNAME
help
Maximum number of bytes stored per-client for DHCP option 12 (hostname),
including the terminating null when used in esp-netif events. Longer hostnames
will be truncated.
config LWIP_DHCPS_STATIC_ENTRIES config LWIP_DHCPS_STATIC_ENTRIES
bool "Enable ARP static entries" bool "Enable ARP static entries"
default y default y

View File

@@ -50,6 +50,7 @@
#define DHCPRELEASE 7 #define DHCPRELEASE 7
#define DHCP_OPTION_SUBNET_MASK 1 #define DHCP_OPTION_SUBNET_MASK 1
#define DHCP_OPTION_HOST_NAME 12
#define DHCP_OPTION_ROUTER 3 #define DHCP_OPTION_ROUTER 3
#define DHCP_OPTION_DNS_SERVER 6 #define DHCP_OPTION_DNS_SERVER 6
#define DHCP_OPTION_REQ_IPADDR 50 #define DHCP_OPTION_REQ_IPADDR 50
@@ -142,6 +143,11 @@ struct dhcps_t {
struct udp_pcb *dhcps_pcb; struct udp_pcb *dhcps_pcb;
dhcps_handle_state state; dhcps_handle_state state;
bool has_declined_ip; bool has_declined_ip;
#if CONFIG_LWIP_DHCPS_REPORT_CLIENT_HOSTNAME
/* Temporary storage for option 12 parsed from the current packet */
char opt_hostname[CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN];
bool opt_hostname_present;
#endif
}; };
@@ -923,6 +929,33 @@ static u8_t parse_options(dhcps_t *dhcps, u8_t *optptr, s16_t len)
type = *(optptr + 2); type = *(optptr + 2);
break; break;
#if CONFIG_LWIP_DHCPS_REPORT_CLIENT_HOSTNAME
case DHCP_OPTION_HOST_NAME: {
/* option format: code(1) len(1) value(len) */
u8_t olen = *(optptr + 1);
const u8_t *oval = optptr + 2;
if (olen > 0) {
/* clamp to configured max, keep room for NUL */
size_t copy_len = olen;
if (copy_len >= CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN) {
copy_len = CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN - 1;
}
size_t j = 0;
for (; j < copy_len; ++j) {
char c = (char)oval[j];
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '_') {
dhcps->opt_hostname[j] = c;
} else {
dhcps->opt_hostname[j] = '-';
}
}
dhcps->opt_hostname[j] = '\0';
dhcps->opt_hostname_present = true;
}
break;
}
#endif
case DHCP_OPTION_REQ_IPADDR://50 case DHCP_OPTION_REQ_IPADDR://50
if (memcmp((char *) &client.addr, (char *) optptr + 2, 4) == 0) { if (memcmp((char *) &client.addr, (char *) optptr + 2, 4) == 0) {
#if DHCPS_DEBUG #if DHCPS_DEBUG
@@ -1111,7 +1144,7 @@ POOL_CHECK:
return 4; return 4;
} }
s16_t ret = parse_options(dhcps, &m->options[4], len);; s16_t ret = parse_options(dhcps, &m->options[4], len);
if (ret == DHCPS_STATE_RELEASE || ret == DHCPS_STATE_NAK || ret == DHCPS_STATE_DECLINE) { if (ret == DHCPS_STATE_RELEASE || ret == DHCPS_STATE_NAK || ret == DHCPS_STATE_DECLINE) {
if (pnode != NULL) { if (pnode != NULL) {
@@ -1131,6 +1164,21 @@ POOL_CHECK:
memset(&dhcps->client_address, 0x0, sizeof(dhcps->client_address)); memset(&dhcps->client_address, 0x0, sizeof(dhcps->client_address));
} }
#if CONFIG_LWIP_DHCPS_REPORT_CLIENT_HOSTNAME
/* If we parsed a hostname from options and we have a lease entry, store it */
if (pdhcps_pool != NULL) {
if (dhcps->opt_hostname_present) {
size_t n = strnlen(dhcps->opt_hostname, CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN);
if (n > 0) {
memset(pdhcps_pool->hostname, 0, sizeof(pdhcps_pool->hostname));
memcpy(pdhcps_pool->hostname, dhcps->opt_hostname, n);
} else {
pdhcps_pool->hostname[0] = '\0';
}
}
}
#endif
#if DHCPS_DEBUG #if DHCPS_DEBUG
DHCPS_LOG("dhcps: xid changed\n"); DHCPS_LOG("dhcps: xid changed\n");
DHCPS_LOG("dhcps: client_address.addr = %x\n", dhcps->client_address.addr); DHCPS_LOG("dhcps: client_address.addr = %x\n", dhcps->client_address.addr);
@@ -1619,4 +1667,40 @@ err_t dhcps_dns_getserver(dhcps_t *dhcps, ip4_addr_t *dnsserver)
return dhcps_dns_getserver_by_type(dhcps, dnsserver, DNS_TYPE_MAIN); return dhcps_dns_getserver_by_type(dhcps, dnsserver, DNS_TYPE_MAIN);
} }
#if CONFIG_LWIP_DHCPS_REPORT_CLIENT_HOSTNAME
bool dhcps_get_hostname_on_mac(dhcps_t *dhcps, const u8_t *mac, char *out, size_t out_len)
{
if ((dhcps == NULL) || (mac == NULL) || (out == NULL) || (out_len == 0)) {
return false;
}
list_node *pnode = dhcps->plist;
while (pnode) {
struct dhcps_pool *pool = pnode->pnode;
if (memcmp(pool->mac, mac, sizeof(pool->mac)) == 0) {
size_t maxcpy = (out_len > 0) ? out_len - 1 : 0;
size_t srclen = 0;
/* hostname may be empty string */
srclen = strnlen(pool->hostname, CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN);
if (maxcpy > 0) {
if (srclen > maxcpy) srclen = maxcpy;
if (srclen) memcpy(out, pool->hostname, srclen);
out[srclen] = '\0';
}
return true;
}
pnode = pnode->pnext;
}
return false;
}
#else
bool dhcps_get_hostname_on_mac(dhcps_t *dhcps, const u8_t *mac, char *out, size_t out_len)
{
LWIP_UNUSED_ARG(dhcps);
LWIP_UNUSED_ARG(mac);
if (out && out_len) {
out[0] = '\0';
}
return false;
}
#endif
#endif // ESP_DHCPS #endif // ESP_DHCPS

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -8,6 +8,7 @@
#include "sdkconfig.h" #include "sdkconfig.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h>
#include "lwip/ip_addr.h" #include "lwip/ip_addr.h"
#include "lwip/err.h" #include "lwip/err.h"
@@ -78,6 +79,12 @@ struct dhcps_pool{
ip4_addr_t ip; ip4_addr_t ip;
u8_t mac[6]; u8_t mac[6];
u32_t lease_timer; u32_t lease_timer;
/* Optional: client's hostname from DHCP option 12, if provided */
#if CONFIG_LWIP_DHCPS
#if CONFIG_LWIP_DHCPS_REPORT_CLIENT_HOSTNAME
char hostname[CONFIG_LWIP_DHCPS_MAX_HOSTNAME_LEN];
#endif
#endif
}; };
typedef u32_t dhcps_time_t; typedef u32_t dhcps_time_t;
@@ -167,6 +174,16 @@ err_t dhcps_set_option_info(dhcps_t *dhcps, u8_t op_id, void *opt_info, u32_t op
*/ */
bool dhcp_search_ip_on_mac(dhcps_t *dhcps, u8_t *mac, ip4_addr_t *ip); bool dhcp_search_ip_on_mac(dhcps_t *dhcps, u8_t *mac, ip4_addr_t *ip);
/**
* @brief Tries to find client hostname corresponding to the supplied MAC
* @param dhcps Pointer to the DHCP handle
* @param mac Supplied MAC address
* @param out Output buffer to receive the hostname (null-terminated)
* @param out_len Size of the output buffer
* @return True if the hostname has been found and copied (may be empty string if not provided by client)
*/
bool dhcps_get_hostname_on_mac(dhcps_t *dhcps, const u8_t *mac, char *out, size_t out_len);
/** /**
* @brief Sets the DNS server address for the DHCP server * @brief Sets the DNS server address for the DHCP server
* @param dhcps Pointer to the DHCP handle * @param dhcps Pointer to the DHCP handle

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@@ -103,6 +103,15 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base,
ESP_LOGI(TAG_STA, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip)); ESP_LOGI(TAG_STA, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0; s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_ASSIGNED_IP_TO_CLIENT) {
const ip_event_assigned_ip_to_client_t *e = (const ip_event_assigned_ip_to_client_t *)event_data;
#if CONFIG_LWIP_DHCPS_REPORT_CLIENT_HOSTNAME
ESP_LOGI(TAG_AP, "Assigned IP to client: " IPSTR ", MAC=" MACSTR ", hostname='%s'",
IP2STR(&e->ip), MAC2STR(e->mac), e->hostname);
#else
ESP_LOGI(TAG_AP, "Assigned IP to client: " IPSTR ", MAC=" MACSTR,
IP2STR(&e->ip), MAC2STR(e->mac));
#endif
} }
} }
@@ -203,6 +212,11 @@ void app_main(void)
&wifi_event_handler, &wifi_event_handler,
NULL, NULL,
NULL)); NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_ASSIGNED_IP_TO_CLIENT,
&wifi_event_handler,
NULL,
NULL));
/*Initialize WiFi */ /*Initialize WiFi */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();