From a24dade1c01a12a9b921f939d037deb4a8df7267 Mon Sep 17 00:00:00 2001 From: agessaman Date: Sun, 4 Oct 2026 20:45:07 -0700 Subject: [PATCH] fix(network): snapshot and restore every lwIP DNS slot DHCP can fill all DNS_MAX_SERVERS (3) slots, but only two were saved and restored, so a third server from one medium survived a switch to the other. link.dns now drops trailing empty slots to keep remote replies under 160 bytes. --- MQTT_IMPLEMENTATION.md | 2 +- scripts/webconfig_mock_server.py | 2 +- src/helpers/NetworkLink.cpp | 46 +++++++++++++++++++------------- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/MQTT_IMPLEMENTATION.md b/MQTT_IMPLEMENTATION.md index ffb60eb6..c55e1ff9 100644 --- a/MQTT_IMPLEMENTATION.md +++ b/MQTT_IMPLEMENTATION.md @@ -558,7 +558,7 @@ These settings apply across all MQTT slots: - `get wifi.pwd` - Get WiFi password - `get link.status` - Get the selected network medium, connection status, IP, signal when available, and uptime - `get link.diag` - Explain automatic Ethernet selection using controller initialization, link event, IP, WiFi fallback, route-lock, and reason state -- `get link.dns` - Show the live DNS resolver list, the selected medium's gateway, and the DNS each medium's DHCP lease installed (`-` = empty slot, `none` = never leased) +- `get link.dns` - Show the live DNS resolver list, the selected medium's gateway, and the DNS each medium's DHCP lease installed (trailing empty slots are omitted; `-` = empty slot or empty list, `none` = never leased) - `get wifi.status` - WiFi-only compatibility alias; reports n/a when another medium is selected - `get wifi.powersave` - Get WiFi power save mode (none/min/max) diff --git a/scripts/webconfig_mock_server.py b/scripts/webconfig_mock_server.py index ff2ec98a..e8f728ea 100644 --- a/scripts/webconfig_mock_server.py +++ b/scripts/webconfig_mock_server.py @@ -574,7 +574,7 @@ GETTERS = { "link.diag": lambda c: ( "why:ethernet-not-enabled selected:wifi\n" "wifi:state:connected ip:192.168.1.42"), - "link.dns": lambda c: "dns:192.168.1.1,-,- gw:192.168.1.1 lease:192.168.1.1,-", + "link.dns": lambda c: "dns:192.168.1.1 gw:192.168.1.1 lease:192.168.1.1", "mqtt.status": lambda c: cli_mqtt_status(c), "mqtt.presets": lambda c: "\n".join( "%2d. %s%s" % (i + 1, n, "" if nd == "none" else " (needs %s)" % nd) diff --git a/src/helpers/NetworkLink.cpp b/src/helpers/NetworkLink.cpp index 39386623..a2e39ab5 100644 --- a/src/helpers/NetworkLink.cpp +++ b/src/helpers/NetworkLink.cpp @@ -33,18 +33,28 @@ extern void tcpipInit(); namespace { -static void appendDnsAddr(char* out, size_t out_size, const ip_addr_t& addr) { - const size_t len = strlen(out); - if (len >= out_size) return; - if (len > 0 && len + 1 < out_size) { - out[len] = ','; - out[len + 1] = '\0'; +// Trailing empty slots are dropped to keep remote replies short; "-" marks an +// empty slot before a populated one, or a list with nothing in it. +static void formatDnsList(char* out, size_t out_size, const ip_addr_t* servers, int count) { + int last = count - 1; + while (last >= 0 && ip_addr_isany_val(servers[last])) last--; + out[0] = '\0'; + if (last < 0) { + snprintf(out, out_size, "-"); + return; } - const size_t at = strlen(out); - if (ip_addr_isany_val(addr)) { - snprintf(out + at, out_size - at, "-"); - } else { - ipaddr_ntoa_r(&addr, out + at, (int)(out_size - at)); + for (int i = 0; i <= last; i++) { + size_t len = strlen(out); + if (i > 0 && len + 1 < out_size) { + out[len++] = ','; + out[len] = '\0'; + } + if (len + 1 >= out_size) return; + if (ip_addr_isany_val(servers[i])) { + snprintf(out + len, out_size - len, "-"); + } else { + ipaddr_ntoa_r(&servers[i], out + len, (int)(out_size - len)); + } } } @@ -61,8 +71,7 @@ static esp_err_t readDnsServersOnTcpipThread(void* ctx) { static void formatCurrentDnsServers(char* out, size_t out_size) { ip_addr_t servers[DNS_MAX_SERVERS] = {}; esp_netif_tcpip_exec(readDnsServersOnTcpipThread, servers); - out[0] = '\0'; - for (int i = 0; i < DNS_MAX_SERVERS; i++) appendDnsAddr(out, out_size, servers[i]); + formatDnsList(out, out_size, servers, DNS_MAX_SERVERS); } class NetworkLinkBase : public NetworkLink { @@ -74,7 +83,7 @@ class NetworkLinkBase : public NetworkLink { bool _status_initialized = false; bool _last_connected = false; unsigned long _last_status_check = 0; - static constexpr int kDnsServers = 2; + static constexpr int kDnsServers = DNS_MAX_SERVERS; // Only ever touched on the lwIP thread (see snapshotDns()), so no atomics. ip_addr_t _dns_snapshot[kDnsServers] = {}; bool _dns_captured = false; @@ -189,17 +198,16 @@ class NetworkLinkBase : public NetworkLink { for (int i = 0; i < kDnsServers; i++) c->servers[i] = c->self->_dns_snapshot[i]; return ESP_OK; }, ©); - out[0] = '\0'; if (!copy.captured) { snprintf(out, out_size, "none"); return; } - for (int i = 0; i < kDnsServers; i++) appendDnsAddr(out, out_size, copy.servers[i]); + formatDnsList(out, out_size, copy.servers, kDnsServers); } void formatDnsFor(char* reply, size_t reply_size, IPAddress gateway) const { char current[64]; - char lease[40]; + char lease[64]; formatCurrentDnsServers(current, sizeof(current)); formatLeaseDns(lease, sizeof(lease)); snprintf(reply, reply_size, "> dns:%s gw:%s lease:%s", @@ -1009,8 +1017,8 @@ class AutomaticNetworkLink final : public NetworkLink { void formatDns(char* reply, size_t reply_size) const override { const NetworkMedium selected = _selected.load(std::memory_order_acquire); char current[64]; - char eth_lease[40]; - char wifi_lease[40]; + char eth_lease[64]; + char wifi_lease[64]; formatCurrentDnsServers(current, sizeof(current)); _ethernet.formatLeaseDns(eth_lease, sizeof(eth_lease)); _wifi.formatLeaseDns(wifi_lease, sizeof(wifi_lease));