fix(mqtt): FR-011 Heap-Korruption bei TLS-Verbindungsabbruch
- _rebuildClient(): WiFiClientSecure + PubSubClient vor jedem Reconnect-Versuch sauber delete + new auf Core 0 - mbedTLS startet damit immer mit sauberem Heap-Kontext - Verhindert CORRUPT HEAP / PANIC wenn Broker die TLS-Session unerwartet abbricht (MBEDTLS_ERR_NET_CONN_RESET) - _broker/_port als Members gecacht fuer _rebuildClient()
This commit is contained in:
parent
c61a67f812
commit
1ef0464be9
|
|
@ -19,6 +19,23 @@ Status: `[ ]` = offen · `[x]` = erledigt
|
|||
|
||||
## Offen
|
||||
|
||||
- [ ] **FR-011** Bug: PANIC/EXCEPTION durch Heap-Korruption bei MQTT-Verbindungsabbruch (TLS)
|
||||
- **Symptom**: ESP32 crasht mit `CORRUPT HEAP: Bad tail` + `assert failed: multi_heap_free` wenn der MQTT-Broker nicht erreichbar ist und eine bestehende TLS-Session unerwartet abbricht
|
||||
- **Fehlermeldung**: `(-76) UNKNOWN ERROR CODE (004C)` = `MBEDTLS_ERR_NET_CONN_RESET`
|
||||
- **Crash-Kette**:
|
||||
1. Broker bricht TLS-Verbindung ab (Connection reset by peer)
|
||||
2. `PubSubClient::connected()` → `WiFiClientSecure::connected()` → `available()`
|
||||
3. `available()` erkennt EOF → ruft intern `stop()` → `stop_ssl_socket()` auf
|
||||
4. `mbedtls_ssl_free()` wird auf einem bereits inkonsistenten SSL-Kontext aufgerufen
|
||||
5. Heap-Corruption → `multi_heap_free` assert → PANIC
|
||||
- **Backtrace-Frames**: `multi_heap_free` ← `heap_caps_free` ← `esp_mbedtls_mem_free` ← `mbedtls_free` ← `mbedtls_ssl_free` ← `stop_ssl_socket` ← `WiFiClientSecure::stop()` ← `WiFiClientSecure::available()` ← `WiFiClientSecure::connected()` ← `PubSubClient::connected()` ← `MqttClient::_taskLoop()`
|
||||
- **Root Cause**: `WiFiClientSecure`-Objekt (`_espClient`) wird nach einem TLS-Verbindungsabbruch nicht neu erstellt — der interne mbedTLS-State ist korrupt, beim nächsten `connected()`-Aufruf crasht `mbedtls_ssl_free()`
|
||||
- **Fix-Strategie**: Nach jedem fehlgeschlagenen Verbindungsversuch (`rc == -2` oder `!client->connected()`) das `WiFiClientSecure`-Objekt zerstören und neu auf Core 0 erstellen (`delete _espClient; _espClient = new WiFiClientSecure(); _espClient->setInsecure(); _client->setClient(*_espClient)`). Damit startet mbedTLS immer mit einem sauberen Kontext.
|
||||
- **Reproduktion**: Broker stoppen während aktiver TLS-Session → innerhalb weniger Minuten PANIC
|
||||
- Betroffene Dateien: `src/mqtt_client.cpp`, `include/mqtt_client.h`
|
||||
- Commit: `TODO`
|
||||
- Version: 1.4.1
|
||||
|
||||
---
|
||||
|
||||
## Erledigt
|
||||
|
|
@ -46,7 +63,7 @@ Status: `[ ]` = offen · `[x]` = erledigt
|
|||
- **Heartbeat `lasercutter/status`**: Feld `"display_on": true/false` hinzugefügt
|
||||
- **`DisplayManager`**: `setEnabled()` / `isEnabled()` via MAX7219 SHUTDOWN-Modus
|
||||
- Betroffene Dateien: `src/web_server.cpp`, `src/mqtt_client.cpp`, `include/config.h`, `include/display_manager.h`, `src/display_manager.cpp`
|
||||
- Commit: `TODO`
|
||||
- Commit: `c61a67f`
|
||||
- Version: 1.4.0
|
||||
|
||||
### Version 1.2.0
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@
|
|||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.71891835"
|
||||
inkscape:cx="584.90648"
|
||||
inkscape:cy="639.15464"
|
||||
inkscape:cx="634.98171"
|
||||
inkscape:cy="835.28262"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1009"
|
||||
inkscape:window-x="1912"
|
||||
|
|
@ -149,56 +149,62 @@
|
|||
xml:space="preserve"
|
||||
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
|
||||
x="204.76865"
|
||||
y="256.40359"
|
||||
y="262.22452"
|
||||
id="text4"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4"
|
||||
style="fill:#000000;stroke-width:0.2"
|
||||
x="204.76865"
|
||||
y="256.40359">Freie Testzeit</tspan><tspan
|
||||
y="262.22452">Freie Testzeit</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
style="fill:#000000;stroke-width:0.2"
|
||||
x="204.76865"
|
||||
y="256.40359"
|
||||
y="262.22452"
|
||||
id="tspan5" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
|
||||
x="203.7793"
|
||||
y="265.51187"
|
||||
y="271.33279"
|
||||
id="text6"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6"
|
||||
style="fill:#000000;stroke-width:0.2"
|
||||
x="203.7793"
|
||||
y="265.51187">in Sekunden</tspan></text>
|
||||
y="271.33279">in Sekunden</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:5.29167px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
|
||||
x="144.94814"
|
||||
y="256.01343"
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:5.29167px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#422020;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
|
||||
x="226.37459"
|
||||
y="252.96211"
|
||||
id="text4-0"><tspan
|
||||
id="tspan4-6"
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:5.29167px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:0.2"
|
||||
x="144.94814"
|
||||
y="256.01343"
|
||||
sodipodi:role="line">€ Laserzeit</tspan><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:5.29167px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:0.2"
|
||||
x="144.94814"
|
||||
y="256.01343"
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:5.29167px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:0.2;stroke:#422020;stroke-opacity:1"
|
||||
x="226.37459"
|
||||
y="252.96211"
|
||||
sodipodi:role="line">€ Laserzeit in Sekunden </tspan><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:5.29167px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:0.2;stroke:#422020;stroke-opacity:1"
|
||||
x="226.37459"
|
||||
y="252.96211"
|
||||
id="tspan5-1"
|
||||
sodipodi:role="line" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:5.29167px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
|
||||
x="144.02959"
|
||||
y="265.11499"
|
||||
id="text6-3"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6-8"
|
||||
x="-218.73421"
|
||||
y="225.72589"
|
||||
id="text4-0-7"
|
||||
transform="rotate(-89.846243)"><tspan
|
||||
id="tspan4-6-6"
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:5.29167px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:0.2"
|
||||
x="144.02959"
|
||||
y="265.11499">Sekunden</tspan></text>
|
||||
x="-218.73421"
|
||||
y="225.72589"
|
||||
sodipodi:role="line">umlaufend</tspan><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-size:5.29167px;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:0.2"
|
||||
x="-218.73421"
|
||||
y="225.72589"
|
||||
id="tspan5-1-1"
|
||||
sodipodi:role="line" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
|
||||
|
|
@ -228,7 +234,7 @@
|
|||
sodipodi:role="line"
|
||||
id="tspan8"
|
||||
x="241.88585"
|
||||
y="126.91018"></tspan></text>
|
||||
y="126.91018" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:600;font-size:7.40833px;line-height:0;font-family:'Open Sans';-inkscape-font-specification:'Open Sans, Semi-Bold';text-align:end;writing-mode:lr-tb;direction:ltr;text-anchor:end;fill:#000000;stroke:#241212;stroke-width:0.2;stroke-linejoin:bevel;stroke-opacity:1"
|
||||
|
|
@ -296,18 +302,18 @@
|
|||
y="265.51187">Error</tspan></text>
|
||||
<path
|
||||
style="fill:none;stroke:#241212;stroke-width:0.6;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 148,250.00001 v 20 h 66.99999 l 0,-20"
|
||||
d="m 148,255.82088 v 20 h 66.99999 v -20"
|
||||
id="path6"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#241212;stroke-width:0.6;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 114.50001,250.00001 v 20 H 148 v -20"
|
||||
id="path6-6"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#241212;stroke-width:0.6;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 216,172.81966 -0.13385,-22.4101 -101.04619,-0.15132 0.11406,22.56145"
|
||||
id="path6-3"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#422020;stroke-width:0.6;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 115,244.00001 v 3 h 104.99999 c -0.0471,0.12248 0,-37.00001 0,-37.00001 h -3"
|
||||
id="path6-3-7"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -94,6 +94,14 @@ private:
|
|||
// Verbindungsaufbau (blockierend - NUR aus MQTT-Task auf Core 0 aufrufen!)
|
||||
bool reconnect();
|
||||
|
||||
// TLS-Client komplett abreissen und neu aufbauen (Fix: Heap-Korruption bei
|
||||
// unerwartetem SSL-Verbindungsabbruch durch kaputten mbedTLS-Kontext)
|
||||
void _rebuildClient();
|
||||
|
||||
// Gecachte Broker-Einstellungen fuer _rebuildClient()
|
||||
char _broker[64];
|
||||
uint16_t _port;
|
||||
|
||||
// Heartbeat-Publish (lasercutter/status)
|
||||
void publishHeartbeat();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ build_flags =
|
|||
-DCORE_DEBUG_LEVEL=1 ; 0=keine, 1=Fehler, 3=Info, 5=Verbose
|
||||
-DARDUINO_LOOP_STACK_SIZE=8192
|
||||
-DELEGANTOTA_USE_ASYNC_WEBSERVER=1
|
||||
-DFIRMWARE_VERSION='"1.4.0"' ; Semantic Versioning – hier erhöhen bei neuem Release
|
||||
-DFIRMWARE_VERSION='"1.4.1"' ; Semantic Versioning – hier erhöhen bei neuem Release
|
||||
lib_deps =
|
||||
majicDesigns/MD_Parola @ ^3.7.3
|
||||
majicDesigns/MD_MAX72XX @ ^3.5.1
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ MqttClient::MqttClient()
|
|||
{
|
||||
_clientId[0] = '\0';
|
||||
_resetReason[0] = '\0';
|
||||
_broker[0] = '\0';
|
||||
_port = 0;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
|
@ -200,6 +202,43 @@ void MqttClient::publishHeartbeat() {
|
|||
LOG_I("MQTT", "Heartbeat: %s -> %s", buf, ok ? "OK" : "FEHLER");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// _rebuildClient() - TLS-Client sauber abreissen und neu aufbauen
|
||||
// MUSS auf Core 0 aufgerufen werden (mbedtls Heap-Kontext-Binding)
|
||||
// --------------------------------------------------------------------------
|
||||
void MqttClient::_rebuildClient() {
|
||||
// Alten Client sauber schliessen und freigeben
|
||||
if (_client) {
|
||||
_client->disconnect();
|
||||
delete _client;
|
||||
_client = nullptr;
|
||||
}
|
||||
if (_secureClient) {
|
||||
delete _secureClient;
|
||||
_secureClient = nullptr;
|
||||
}
|
||||
if (_wifiClient) {
|
||||
delete _wifiClient;
|
||||
_wifiClient = nullptr;
|
||||
}
|
||||
|
||||
// Frische Objekte auf Core 0 anlegen
|
||||
_wifiClient = new WiFiClient();
|
||||
_secureClient = new WiFiClientSecure();
|
||||
|
||||
if (_port == 8883) {
|
||||
_secureClient->setInsecure();
|
||||
_client = new PubSubClient(*_secureClient);
|
||||
} else {
|
||||
_client = new PubSubClient(*_wifiClient);
|
||||
}
|
||||
_client->setServer(_broker, _port);
|
||||
_client->setCallback(MqttClient::onMessage);
|
||||
_client->setKeepAlive(60);
|
||||
_client->setBufferSize(512);
|
||||
LOG_I("MQTT", "Client neu aufgebaut (sauberer TLS-Kontext)");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// reconnect() - Verbindungsaufbau, non-blocking
|
||||
// --------------------------------------------------------------------------
|
||||
|
|
@ -255,6 +294,11 @@ void MqttClient::_taskLoop() {
|
|||
const char* broker = cfg.mqttBroker[0] != '\0' ? cfg.mqttBroker : DEFAULT_MQTT_BROKER;
|
||||
uint16_t port = cfg.mqttPort > 0 ? cfg.mqttPort : DEFAULT_MQTT_PORT;
|
||||
|
||||
// Broker/Port für _rebuildClient() sichern
|
||||
strncpy(_broker, broker, sizeof(_broker) - 1);
|
||||
_broker[sizeof(_broker) - 1] = '\0';
|
||||
_port = port;
|
||||
|
||||
// Netzwerk-Objekte auf Core 0 anlegen (mbedtls/WiFiClientSecure bindet
|
||||
// Heap-Kontexte an den erstellenden Task/Core -> kein Cross-Core-Zugriff).
|
||||
_wifiClient = new WiFiClient();
|
||||
|
|
@ -284,6 +328,9 @@ void MqttClient::_taskLoop() {
|
|||
uint32_t now = millis();
|
||||
if (now - _lastReconnectMs >= MQTT_RECONNECT_MS) {
|
||||
_lastReconnectMs = now;
|
||||
// Sicherstellen, dass kein korrupter mbedTLS-Kontext vom letzten
|
||||
// Verbindungsabbruch uebrig bleibt (Fix FR-011: Heap-Korruption)
|
||||
_rebuildClient();
|
||||
reconnect(); // blockiert hier (TLS); Core 1 laeuft unbehelligt
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user