mirror of
https://github.com/esphome/esphome.git
synced 2026-09-21 20:18:43 +00:00
[mdns] Guard LEAmDNS main loop calls against lwIP re-entrancy on ESP8266
LEAmDNS update() and close() can yield inside UdpContext::sendTimeout() while a send fails; an mDNS packet arriving then re-enters LEAmDNS from lwIP on the same UdpContext and both sides free the same tx pbufs, which corrupts the heap and shows up later as crashes such as the NULL dereference in noise_handshakestate_new during the API handshake. mdns_esp8266.cpp now owns a MDNSResponder subclass (NO_GLOBAL_MDNS) whose receive handler only counts packets while a main loop call is running and drains them from the main loop afterwards. The roaming gate from #18785 and the is_roaming() accessor it needed are no longer required.
This commit is contained in:
@@ -192,6 +192,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
if CORE.using_arduino:
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("ESP8266mDNS", None)
|
||||
# mdns_esp8266.cpp owns a guarded MDNSResponder instead of the library global
|
||||
cg.add_build_flag("-DNO_GLOBAL_MDNS")
|
||||
elif CORE.is_rp2:
|
||||
cg.add_library("LEAmDNS", None)
|
||||
|
||||
|
||||
@@ -13,8 +13,56 @@
|
||||
|
||||
namespace esphome::mdns {
|
||||
|
||||
// Main-loop calls into LEAmDNS (update(), close()) can yield inside UdpContext::sendTimeout();
|
||||
// a packet arriving then re-enters LEAmDNS from lwIP on the same UdpContext and both sides free
|
||||
// the same tx pbufs (#18760). Received packets are only counted during such a call and drained
|
||||
// from the main loop afterwards.
|
||||
class GuardedMDNSResponder : public ::esp8266::MDNSImplementation::MDNSResponder {
|
||||
public:
|
||||
void update_guarded() {
|
||||
this->run_guarded_([this]() { this->update(); });
|
||||
}
|
||||
void close_guarded() {
|
||||
this->run_guarded_([this]() { this->close(); });
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename F> void run_guarded_(F &&fn) {
|
||||
UdpContext *ctx = this->m_pUDPContext;
|
||||
if (ctx == nullptr) {
|
||||
fn();
|
||||
return;
|
||||
}
|
||||
// Set every time: a restart replaces the context together with its stock handler
|
||||
ctx->onRx([this]() { this->on_rx_(); });
|
||||
this->pending_rx_ = 0;
|
||||
this->in_loop_call_ = true;
|
||||
fn();
|
||||
// Still counting here, so packets arriving during a yield in the drain queue behind it
|
||||
while (this->pending_rx_ > 0) {
|
||||
this->pending_rx_--;
|
||||
this->_process(false);
|
||||
}
|
||||
this->in_loop_call_ = false;
|
||||
}
|
||||
|
||||
// lwIP receive callback (SYS context)
|
||||
void on_rx_() {
|
||||
if (this->in_loop_call_) {
|
||||
this->pending_rx_++;
|
||||
} else {
|
||||
this->_callProcess();
|
||||
}
|
||||
}
|
||||
|
||||
volatile bool in_loop_call_{false};
|
||||
volatile uint8_t pending_rx_{0};
|
||||
};
|
||||
|
||||
static GuardedMDNSResponder mdns_responder; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
static void register_esp8266(MDNSComponent *, StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
|
||||
MDNS.begin(App.get_name().c_str());
|
||||
mdns_responder.begin(App.get_name().c_str());
|
||||
|
||||
for (const auto &service : services) {
|
||||
// Strip the leading underscore from the proto and service_type. While it is
|
||||
@@ -30,10 +78,10 @@ static void register_esp8266(MDNSComponent *, StaticVector<MDNSService, MDNS_SER
|
||||
service_type++;
|
||||
}
|
||||
uint16_t port = service.port.value();
|
||||
MDNS.addService(FPSTR(service_type), FPSTR(proto), port);
|
||||
mdns_responder.addService(FPSTR(service_type), FPSTR(proto), port);
|
||||
for (const auto &record : service.txt_records) {
|
||||
MDNS.addServiceTxt(FPSTR(service_type), FPSTR(proto), FPSTR(MDNS_STR_ARG(record.key)),
|
||||
FPSTR(MDNS_STR_ARG(record.value)));
|
||||
mdns_responder.addServiceTxt(FPSTR(service_type), FPSTR(proto), FPSTR(MDNS_STR_ARG(record.key)),
|
||||
FPSTR(MDNS_STR_ARG(record.value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,19 +89,7 @@ static void register_esp8266(MDNSComponent *, StaticVector<MDNSService, MDNS_SER
|
||||
#ifdef USE_MDNS_EVENT_DRIVEN_POLLING
|
||||
void MDNSComponent::start_polling_window_() {
|
||||
// uint32_t-ID set_interval/set_timeout already does atomic cancel-and-add.
|
||||
this->set_interval(MDNS_POLL_ID, MDNS_UPDATE_INTERVAL_MS, []() {
|
||||
#ifdef USE_MDNS_WIFI_LISTENER
|
||||
// MDNS.update() can suspend the loop in UdpContext::sendTimeout() while a send is
|
||||
// failing (radio off-channel during a roam scan, or mid reconnect); an incoming
|
||||
// packet then re-enters LEAmDNS from lwIP and corrupts shared UdpContext state.
|
||||
// Skip the tick while the radio cannot transmit (#18760), but keep polling while
|
||||
// the AP is serving clients (AP-only or fallback AP with the STA down).
|
||||
auto *wifi = wifi::global_wifi_component;
|
||||
if (wifi->is_roaming() || (!wifi->is_connected() && !wifi->is_ap_active()))
|
||||
return;
|
||||
#endif
|
||||
MDNS.update();
|
||||
});
|
||||
this->set_interval(MDNS_POLL_ID, MDNS_UPDATE_INTERVAL_MS, []() { mdns_responder.update_guarded(); });
|
||||
this->set_timeout(MDNS_POLL_STOP_ID, MDNS_POLL_WINDOW_MS, [this]() { this->cancel_interval(MDNS_POLL_ID); });
|
||||
}
|
||||
#endif
|
||||
@@ -81,7 +117,7 @@ void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::
|
||||
#endif
|
||||
|
||||
void MDNSComponent::on_shutdown() {
|
||||
MDNS.close();
|
||||
mdns_responder.close_guarded();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
|
||||
@@ -476,10 +476,6 @@ class WiFiComponent final : public Component {
|
||||
/// True while a post-connect roaming scan holds the radio off-channel.
|
||||
bool is_roaming_scan_active() const { return this->roaming_state_ == RoamingState::SCANNING; }
|
||||
|
||||
/// True while a post-connect roam is in progress (scanning off-channel, reassociating,
|
||||
/// or recovering from a failed roam).
|
||||
bool is_roaming() const { return this->roaming_state_ != RoamingState::IDLE; }
|
||||
|
||||
#ifdef USE_ESP32
|
||||
/// esp_netif handle of the station interface, used by network for default-route
|
||||
/// arbitration. nullptr until wifi_lazy_init_() has run.
|
||||
|
||||
Reference in New Issue
Block a user