mirror of
https://github.com/agessaman/MeshCore.git
synced 2026-10-23 04:21:28 +00:00
fix(network): harden automatic link transitions
This commit is contained in:
1 parent
2c21406380
commit
50543c9b47
6 files changed
+48
-10
No files matched your search
@@ -455,7 +455,8 @@ class AutomaticNetworkInterface final : public NetworkInterface {
|
||||
// Hardware availability and stored credentials are configuration. Current
|
||||
// link/DHCP state is runtime state and must not permanently suppress the
|
||||
// MQTT task that monitors for a late cable or lease.
|
||||
return _ethernet_started || (wifi_ssid && wifi_ssid[0] != '\0');
|
||||
(void)wifi_ssid;
|
||||
return true;
|
||||
}
|
||||
bool isAutomatic() const override { return true; }
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ static constexpr MQTTTransitionActions mqttActions(NetworkTransition transition)
|
||||
return {
|
||||
transition == NetworkTransition::Down || transition == NetworkTransition::Switched,
|
||||
transition == NetworkTransition::Up || transition == NetworkTransition::Switched,
|
||||
transition == NetworkTransition::Up || transition == NetworkTransition::Switched,
|
||||
transition == NetworkTransition::Switched,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -160,7 +160,8 @@ static inline NetworkMedium automaticSelection(
|
||||
|
||||
if (input.selected == NetworkMedium::WiFi) {
|
||||
if (input.ethernet_connected &&
|
||||
input.ethernet_stable_ms >= kEthernetFailbackStableMs) {
|
||||
(!input.wifi_connected ||
|
||||
input.ethernet_stable_ms >= kEthernetFailbackStableMs)) {
|
||||
return NetworkMedium::Ethernet;
|
||||
}
|
||||
return NetworkMedium::WiFi;
|
||||
|
||||
@@ -1252,7 +1252,10 @@ void MQTTBridge::mqttTaskLoop() {
|
||||
// Wait a bit for WiFi to start connecting
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
bool network_was_connected = _network->isConnected();
|
||||
// Treat the first post-settle sample as an edge. A fast Wi-Fi association
|
||||
// may complete during the delay above, before the task has sampled state;
|
||||
// initializing from the live value would then defer NTP until its retry.
|
||||
bool network_was_connected = false;
|
||||
unsigned long last_ntp_attempt = 0;
|
||||
|
||||
// Main task loop
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <DNSServer.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <bootloader_random.h>
|
||||
#include <esp_system.h>
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
@@ -23,6 +24,24 @@
|
||||
// so an untouched password field never overwrites the stored value.
|
||||
static const char SECRET_SENTINEL[] = "********";
|
||||
|
||||
// esp_random() has a true entropy source only while RF is active. Ethernet
|
||||
// LAN mode deliberately keeps Wi-Fi and Bluetooth down, so temporarily enable
|
||||
// the bootloader RNG source while generating authentication secrets there.
|
||||
class WebConfigEntropyGuard {
|
||||
bool _enabled;
|
||||
|
||||
public:
|
||||
WebConfigEntropyGuard()
|
||||
: _enabled(activeNetworkInterface().medium() ==
|
||||
NetworkMedium::Ethernet) {
|
||||
if (_enabled) bootloader_random_enable();
|
||||
}
|
||||
|
||||
~WebConfigEntropyGuard() {
|
||||
if (_enabled) bootloader_random_disable();
|
||||
}
|
||||
};
|
||||
|
||||
// Key classification (allowlist, secret detection, slot-prefix parsing) lives in
|
||||
// helpers/WebConfigKeys.h so it can be unit-tested on the host. Thin aliases keep
|
||||
// the call sites below readable.
|
||||
@@ -262,8 +281,11 @@ bool WebConfigServer::startLanMode(IPAddress ip, bool initial_setup, char reply[
|
||||
}
|
||||
_initial_setup = initial_setup;
|
||||
if (_initial_setup) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
sprintf(&_setup_code[i * 4], "%04X", (unsigned)(esp_random() & 0xffff));
|
||||
{
|
||||
WebConfigEntropyGuard entropy;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
sprintf(&_setup_code[i * 4], "%04X", (unsigned)(esp_random() & 0xffff));
|
||||
}
|
||||
}
|
||||
_setup_code[12] = 0;
|
||||
} else {
|
||||
@@ -730,7 +752,13 @@ void WebConfigServer::handleLogin(AsyncWebServerRequest* req) {
|
||||
}
|
||||
_login_fails = 0;
|
||||
_login_lock_until = 0;
|
||||
for (int i = 0; i < 4; i++) sprintf(&_session_token[i * 8], "%08lx", (unsigned long)esp_random());
|
||||
{
|
||||
WebConfigEntropyGuard entropy;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
sprintf(&_session_token[i * 8], "%08lx",
|
||||
(unsigned long)esp_random());
|
||||
}
|
||||
}
|
||||
_session_last_seen = now;
|
||||
|
||||
AsyncWebServerResponse* res = req->beginResponse(200, "application/json", "{\"ok\":true}");
|
||||
|
||||
@@ -17,7 +17,6 @@ static inline bool beginConfiguredCH390(const char* hostname = nullptr) {
|
||||
if (hostname && hostname[0] != '\0') {
|
||||
if (!CH390.setHostname(hostname)) {
|
||||
Serial.printf("Network: invalid Ethernet hostname %s\n", hostname);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!CH390.begin(config)) return false;
|
||||
|
||||
@@ -9,11 +9,11 @@ TEST(NetworkPolicy, MqttDownDisconnectsSlotsWithoutRequestingImmediateRetry) {
|
||||
EXPECT_FALSE(actions.reset_reconnect_backoff);
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, MqttUpRequestsImmediateRetryWithoutDisconnectingSlots) {
|
||||
TEST(NetworkPolicy, MqttUpRetriesWithoutClearingBrokerCircuitBreaker) {
|
||||
const auto actions = NetworkPolicy::mqttActions(NetworkTransition::Up);
|
||||
EXPECT_FALSE(actions.stop_started_slots);
|
||||
EXPECT_TRUE(actions.retry_disconnected_slots_now);
|
||||
EXPECT_TRUE(actions.reset_reconnect_backoff);
|
||||
EXPECT_FALSE(actions.reset_reconnect_backoff);
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, NoTransitionHasNoMqttSideEffects) {
|
||||
@@ -69,6 +69,12 @@ TEST(NetworkPolicy, FailbackRequiresStableEthernet) {
|
||||
EXPECT_EQ(NetworkMedium::Ethernet, NetworkPolicy::automaticSelection(input));
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, DeadWifiFailsBackToReadyEthernetImmediately) {
|
||||
const NetworkPolicy::AutomaticSelectionInput input = {
|
||||
NetworkMedium::WiFi, true, false, true, false, 0, 0};
|
||||
EXPECT_EQ(NetworkMedium::Ethernet, NetworkPolicy::automaticSelection(input));
|
||||
}
|
||||
|
||||
TEST(NetworkPolicy, SwitchingLockPinsTheCurrentMedium) {
|
||||
const NetworkPolicy::AutomaticSelectionInput input = {
|
||||
NetworkMedium::WiFi, true, true, true, true,
|
||||
|
||||
Reference in new issue
Block a user