feat(cli): add get link.dns for resolver, gateway and lease DNS

Shows lwIP's live global DNS server list next to the DNS each medium's DHCP lease installed, so a node that is linked but failing to resolve can be diagnosed remotely.
This commit is contained in:
agessaman committed 2026-10-04 19:55:54 -07:00
1 parent f88f6656a4
commit ffab6f565f
6 files changed
+93

No files matched your search

+86
View File
@@ -33,6 +33,38 @@ 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';
}
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));
}
}
static esp_err_t readDnsServersOnTcpipThread(void* ctx) {
ip_addr_t* servers = static_cast<ip_addr_t*>(ctx);
for (int i = 0; i < DNS_MAX_SERVERS; i++) {
const ip_addr_t* server = dns_getserver(i);
servers[i] = (server != nullptr) ? *server : *IP_ADDR_ANY;
}
return ESP_OK;
}
// lwIP's server list is global, so this is what every socket resolves through.
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]);
}
class NetworkLinkBase : public NetworkLink {
protected:
std::atomic<uint64_t> _outage_bits{AlertFaultPolicy::packOutageSnapshot({false, 0, 0})};
@@ -144,6 +176,36 @@ class NetworkLinkBase : public NetworkLink {
public:
void snapshotDns() { esp_netif_tcpip_exec(snapshotDnsOnTcpipThread, this); }
// "none" until this medium has held a lease that carried a resolver.
void formatLeaseDns(char* out, size_t out_size) const {
struct Copy {
const NetworkLinkBase* self;
ip_addr_t servers[kDnsServers];
bool captured;
} copy = {this, {}, false};
esp_netif_tcpip_exec([](void* ctx) -> esp_err_t {
Copy* c = static_cast<Copy*>(ctx);
c->captured = c->self->_dns_captured;
for (int i = 0; i < kDnsServers; i++) c->servers[i] = c->self->_dns_snapshot[i];
return ESP_OK;
}, &copy);
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]);
}
void formatDnsFor(char* reply, size_t reply_size, IPAddress gateway) const {
char current[64];
char lease[40];
formatCurrentDnsServers(current, sizeof(current));
formatLeaseDns(lease, sizeof(lease));
snprintf(reply, reply_size, "> dns:%s gw:%s lease:%s",
current, gateway.toString().c_str(), lease);
}
// A medium that has never held a lease with a resolver leaves the current one
// alone rather than blanking it.
void restoreDns() { esp_netif_tcpip_exec(restoreDnsOnTcpipThread, this); }
@@ -324,6 +386,10 @@ class WiFiNetworkLink final : public NetworkLinkBase {
statusName(),
localIP().toString().c_str());
}
void formatDns(char* reply, size_t reply_size) const override {
formatDnsFor(reply, reply_size, WiFi.gatewayIP());
}
IPAddress gatewayIP() const { return WiFi.gatewayIP(); }
};
#if defined(NETWORK_PREFER_ETHERNET)
@@ -471,6 +537,10 @@ class EthernetNetworkLink final : public NetworkLinkBase {
snprintf(reply, reply_size, "> ethernet:%s ip=%s",
statusName(), localIP().toString().c_str());
}
void formatDns(char* reply, size_t reply_size) const override {
formatDnsFor(reply, reply_size, CH390.gatewayIP());
}
IPAddress gatewayIP() const { return CH390.gatewayIP(); }
EventState eventState() const {
return static_cast<EventState>(
@@ -936,6 +1006,22 @@ class AutomaticNetworkLink final : public NetworkLink {
wifiConfigured() ? "yes" : "no", wifi_started ? "yes" : "no",
(wifi_started && _wifi.isConnected()) ? "up" : "down");
}
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];
formatCurrentDnsServers(current, sizeof(current));
_ethernet.formatLeaseDns(eth_lease, sizeof(eth_lease));
_wifi.formatLeaseDns(wifi_lease, sizeof(wifi_lease));
const IPAddress gateway = selected == NetworkMedium::Ethernet ? _ethernet.gatewayIP()
: selected == NetworkMedium::WiFi ? _wifi.gatewayIP()
: IPAddress();
snprintf(reply, reply_size,
"> dns:%s selected:%s gw:%s\neth-lease:%s wifi-lease:%s",
current, NetworkPolicy::mediumName(selected),
gateway.toString().c_str(), eth_lease, wifi_lease);
}
unsigned long connectedAtMillis() const override {
const NetworkMedium selected = _selected.load(std::memory_order_acquire);
return selected == NetworkMedium::None