Network: static IP addressing
net.dhcp = false applies ip/mask/gw/dns on link up (DHCP client stopped). PTP/TX/SAP sockets are bound to the address, so a changed IP setup reboots the device; with DHCP on, the static fields (UI fills them with the lease) don't count as a change. Validation: contiguous netmask, not the network or broadcast address, gateway in the subnet. The page follows the device to the new address after saving. Checked: DHCP -> static .245 (PTP, TX, SAP, SDP origin, mDNS on the new address) -> DHCP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,10 +11,13 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "lldp.h"
|
||||
#include "lwip/inet.h"
|
||||
#include "mdns.h"
|
||||
|
||||
static const char *TAG = "net";
|
||||
|
||||
static const char *const ADDR_KEYS[] = { "dhcp", "ip", "mask", "gw", "dns" };
|
||||
|
||||
// AES67 / untagged interface.
|
||||
static const char NET_DEFAULTS[] =
|
||||
"{\"hostname\":\"aes67\",\"vlan\":false,\"web_on\":\"both\",\"dhcp\":true,"
|
||||
@@ -25,6 +28,48 @@ static const char INET_DEFAULTS[] =
|
||||
|
||||
static esp_netif_t *s_netif;
|
||||
static char s_link[24] = "down";
|
||||
static cJSON *s_addr; // addressing in use since boot (ADDR_KEYS of "net")
|
||||
|
||||
// The keys that define the IP setup. With DHCP the static fields don't matter (the UI fills them
|
||||
// with the lease), so they don't count as a change.
|
||||
static cJSON *addressing(const cJSON *g)
|
||||
{
|
||||
cJSON *a = cJSON_CreateObject();
|
||||
int n = cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(g, "dhcp")) ? 1 : sizeof(ADDR_KEYS) / sizeof(ADDR_KEYS[0]);
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON_AddItemToObject(a, ADDR_KEYS[i], cJSON_Duplicate(cJSON_GetObjectItemCaseSensitive(g, ADDR_KEYS[i]), true));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static uint32_t addr_of(const cJSON *g, const char *key)
|
||||
{
|
||||
const cJSON *v = cJSON_GetObjectItemCaseSensitive(g, key);
|
||||
return cJSON_IsString(v) && v->valuestring[0] ? inet_addr(v->valuestring) : 0;
|
||||
}
|
||||
|
||||
// Static addressing: stop the DHCP client and set ours. On link up, like IDF's static IP example.
|
||||
static void set_static(void)
|
||||
{
|
||||
if (cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(s_addr, "dhcp"))) {
|
||||
return;
|
||||
}
|
||||
esp_err_t err = esp_netif_dhcpc_stop(s_netif);
|
||||
if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
|
||||
ESP_LOGE(TAG, "DHCP client stop: %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_netif_ip_info_t ip = {
|
||||
.ip.addr = addr_of(s_addr, "ip"), .netmask.addr = addr_of(s_addr, "mask"), .gw.addr = addr_of(s_addr, "gw"),
|
||||
};
|
||||
err = esp_netif_set_ip_info(s_netif, &ip);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "static IP: %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_netif_dns_info_t dns = { .ip.type = ESP_IPADDR_TYPE_V4, .ip.u_addr.ip4.addr = addr_of(s_addr, "dns") };
|
||||
if (dns.ip.u_addr.ip4.addr) {
|
||||
esp_netif_set_dns_info(s_netif, ESP_NETIF_DNS_MAIN, &dns);
|
||||
}
|
||||
}
|
||||
|
||||
static void eth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
@@ -43,6 +88,7 @@ static void eth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
snprintf(s_link, sizeof(s_link), "up, %s Mbps %s", speed == ETH_SPEED_100M ? "100" : "10",
|
||||
duplex == ETH_DUPLEX_FULL ? "full" : "half");
|
||||
set_static();
|
||||
break;
|
||||
}
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
@@ -66,10 +112,27 @@ static void ip_event(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
static bool check_addr(const cJSON *g, char *err, size_t n)
|
||||
{
|
||||
bool dhcp = cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(g, "dhcp"));
|
||||
return cfg_check_ipv4(g, "ip", dhcp, err, n) &&
|
||||
cfg_check_ipv4(g, "mask", dhcp, err, n) &&
|
||||
cfg_check_ipv4(g, "gw", true, err, n) &&
|
||||
cfg_check_ipv4(g, "dns", true, err, n);
|
||||
if (!cfg_check_ipv4(g, "ip", dhcp, err, n) || !cfg_check_ipv4(g, "mask", dhcp, err, n) ||
|
||||
!cfg_check_ipv4(g, "gw", true, err, n) || !cfg_check_ipv4(g, "dns", true, err, n)) {
|
||||
return false;
|
||||
}
|
||||
if (dhcp) {
|
||||
return true;
|
||||
}
|
||||
uint32_t ip = ntohl(addr_of(g, "ip")), mask = ntohl(addr_of(g, "mask")), gw = ntohl(addr_of(g, "gw"));
|
||||
if (mask == 0 || (~mask & (~mask + 1)) != 0 || mask == 0xffffffff) {
|
||||
snprintf(err, n, "mask: not a valid netmask");
|
||||
return false;
|
||||
}
|
||||
if ((ip & ~mask) == 0 || (ip & ~mask) == ~mask) {
|
||||
snprintf(err, n, "ip: network or broadcast address of the subnet");
|
||||
return false;
|
||||
}
|
||||
if (gw && ((gw & mask) != (ip & mask) || gw == ip)) {
|
||||
snprintf(err, n, "gw: must be another address in the IP's subnet");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// RFC 1123 label: letters, digits, hyphen; not starting or ending with a hyphen.
|
||||
@@ -134,6 +197,13 @@ static void net_apply(const cJSON *g)
|
||||
ESP_LOGI(TAG, "mDNS: now %s.local", host);
|
||||
}
|
||||
lldp_changed();
|
||||
// New addressing: PTP/TX/SAP sockets are bound to the address, so it takes a reboot.
|
||||
cJSON *a = addressing(g);
|
||||
if (!cJSON_Compare(a, s_addr, true)) {
|
||||
ESP_LOGW(TAG, "IP setup changed: rebooting to apply it");
|
||||
web_reboot_later(1500);
|
||||
}
|
||||
cJSON_Delete(a);
|
||||
}
|
||||
|
||||
static void net_status(cJSON *st)
|
||||
@@ -169,7 +239,7 @@ static void net_status(cJSON *st)
|
||||
|
||||
esp_err_t aes67_net_init(esp_eth_handle_t eth)
|
||||
{
|
||||
// Applied so far: hostname (DHCP, mDNS, LLDP). Static IP and VLAN come later.
|
||||
// Applied: hostname (DHCP, mDNS, LLDP) live; DHCP/static addressing at boot. VLAN comes later.
|
||||
ESP_ERROR_CHECK(cfg_register("net", NET_DEFAULTS, net_validate, net_apply));
|
||||
ESP_ERROR_CHECK(cfg_register("inet", INET_DEFAULTS, inet_validate, NULL));
|
||||
|
||||
@@ -189,6 +259,13 @@ esp_err_t aes67_net_init(esp_eth_handle_t eth)
|
||||
// Hostname in DHCP requests (option 12), so the router's lease list shows it.
|
||||
cJSON *net = cfg_get("net");
|
||||
ESP_ERROR_CHECK(esp_netif_set_hostname(s_netif, cJSON_GetObjectItemCaseSensitive(net, "hostname")->valuestring));
|
||||
s_addr = addressing(net);
|
||||
if (!cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(net, "dhcp"))) {
|
||||
ESP_LOGI(TAG, "static IP %s/%s gw %s dns %s", cJSON_GetObjectItemCaseSensitive(net, "ip")->valuestring,
|
||||
cJSON_GetObjectItemCaseSensitive(net, "mask")->valuestring,
|
||||
cJSON_GetObjectItemCaseSensitive(net, "gw")->valuestring,
|
||||
cJSON_GetObjectItemCaseSensitive(net, "dns")->valuestring);
|
||||
}
|
||||
cJSON_Delete(net);
|
||||
mdns_setup();
|
||||
ESP_ERROR_CHECK(lldp_start(eth, s_netif));
|
||||
|
||||
Reference in New Issue
Block a user