From 5f376de57f3590eec5e191a2c235c46aa0b631c5 Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Tue, 27 Aug 2019 19:05:57 +0300 Subject: [PATCH 1/9] test: etharp_gratuitous --- code/espurna/wifi.ino | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.ino index 4d50f30603..87dd011aea 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.ino @@ -9,6 +9,8 @@ Copyright (C) 2016-2019 by Xose Pérez #include "JustWifi.h" #include +#include + uint32_t _wifi_scan_client_id = 0; bool _wifi_wps_running = false; bool _wifi_smartconfig_running = false; @@ -685,4 +687,31 @@ void wifiLoop() { _wifiCheckAP(); } + if (!wifiConnected()) { + return; + } + + // Gratuitous arp + static unsigned long last_arp = 0; + if (millis() - last_arp < 5000) { + return; + } + + unsigned long start = millis(); + netif *n = netif_list; + bool res = false; + while (n) { + if ((n->flags & NETIF_FLAG_LINK_UP) || (n->flags & NETIF_FLAG_UP)) { + DEBUG_MSG_P(PSTR("sending arp...\n")); + etharp_gratuitous(n); + res = true; + } else { + DEBUG_MSG_P(PSTR("netif not yet up...\n")); + } + n = n->next; + } + + if (res) DEBUG_MSG_P(PSTR("Sent Gratuitous ARP : Time Took - %d\n"), millis() - start); + last_arp = millis(); + } From 142a36b9f8d2a6a0bb55e6d4509950ac3d3b1db7 Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Thu, 29 Aug 2019 18:58:19 +0300 Subject: [PATCH 2/9] proper checks before calling etharp_gratuitous, move includes --- code/espurna/config/general.h | 20 +++++++ code/espurna/config/prototypes.h | 6 ++ code/espurna/wifi.ino | 96 +++++++++++++++++++++++--------- 3 files changed, 95 insertions(+), 27 deletions(-) diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index df52203364..dc04f65e0a 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -554,6 +554,26 @@ #define WIFI_PROPAGATION_CONST 4 // This is typically something between 2.7 to 4.3 (free space is 2) #endif +// ref: https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/kconfig.html#config-lwip-esp-gratuitous-arp +// ref: https://github.com/xoseperez/espurna/pull/1877#issuecomment-525612546 +// +// Help solve compatibility issues with some routers. +// If the ARP table of the AP is old, and the AP doesn't send ARP request to update it's ARP table, +// this will lead to the STA sending IP packet fail. Thus we send gratuitous ARP periodically to let AP update it's ARP table. + +#ifndef WIFI_GRATUITOUS_ARP_SUPPORT +#define WIFI_GRATUITOUS_ARP_SUPPORT 1 +#endif + +// Interval is randomized on each boot in range from ..._MIN to ..._MAX +#ifndef WIFI_GRATUITOUS_ARP_INTERVAL_MIN +#define WIFI_GRATUITOUS_ARP_INTERVAL_MIN 15 +#endif + +#ifndef WIFI_GRATUITOUS_ARP_INTERVAL_MAX +#define WIFI_GRATUITOUS_ARP_INTERVAL_MAX 30 +#endif + // ----------------------------------------------------------------------------- // WEB // ----------------------------------------------------------------------------- diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index 322d64c363..b434e7e3e2 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -349,6 +349,12 @@ using wifi_callback_f = std::function +#else // LWIP_VERSION_MAJOR >= 2 +#include +#endif + // ----------------------------------------------------------------------------- // THERMOSTAT // ----------------------------------------------------------------------------- diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.ino index 87dd011aea..070ef0aa9f 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.ino @@ -9,14 +9,17 @@ Copyright (C) 2016-2019 by Xose Pérez #include "JustWifi.h" #include -#include - uint32_t _wifi_scan_client_id = 0; bool _wifi_wps_running = false; bool _wifi_smartconfig_running = false; bool _wifi_smartconfig_initial = false; uint8_t _wifi_ap_mode = WIFI_AP_FALLBACK; +#if WIFI_GRATUITOUS_ARP_SUPPORT +unsigned long _wifi_gratuitous_arp_interval = 0; +unsigned long _wifi_gratuitous_arp_last = 0; +#endif + // ----------------------------------------------------------------------------- // PRIVATE // ----------------------------------------------------------------------------- @@ -86,6 +89,11 @@ void _wifiConfigure() { sleep_mode = constrain(sleep_mode, 0, 2); WiFi.setSleepMode(static_cast(sleep_mode)); + + #if WIFI_GRATUITOUS_ARP_SUPPORT + _wifi_gratuitous_arp_last = millis(); + #endif + } void _wifiScan(uint32_t client_id = 0) { @@ -493,6 +501,47 @@ void _wifiWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& #endif +// ----------------------------------------------------------------------------- +// SUPPORT +// ----------------------------------------------------------------------------- + +#if WIFI_GRATUITOUS_ARP_SUPPORT + +// ref: lwip src/core/netif.c netif_issue_reports(...) +// ref: esp-lwip/core/ipv4/etharp.c garp_tmr() +// TODO: only for ipv4, need (?) a different method with ipv6 +bool _wifiSendGratuitousArp() { + + bool result = false; + for (netif* interface = netif_list; interface != nullptr; interface = interface->next) { + if ( + (interface->flags & NETIF_FLAG_ETHARP) + && (interface->hwaddr_len == ETHARP_HWADDR_LEN) + #if LWIP_VERSION_MAJOR != 1 + && (interface->flags & NETIF_FLAG_LINK_UP) + && (!ip4_addr_isany_val(*netif_ip4_addr(interface))) + #else + && (!ip_addr_isany(&interface->ip_addr)) + #endif + && (interface->flags & NETIF_FLAG_UP) + ) { + etharp_gratuitous(interface); + result = true; + } + } + + return result; +} + +void _wifiSendGratuitousArp(unsigned long interval) { + if (millis() - _wifi_gratuitous_arp_last > interval) { + _wifi_gratuitous_arp_last = millis(); + _wifiSendGratuitousArp(); + } +} + +#endif // WIFI_GRATUITOUS_ARP_SUPPORT + // ----------------------------------------------------------------------------- // INFO // ----------------------------------------------------------------------------- @@ -660,6 +709,19 @@ void wifiSetup() { espurnaRegisterLoop(wifiLoop); espurnaRegisterReload(_wifiConfigure); + // Periodic gratuitous arp to keep the ip alive + #if WIFI_GRATUITOUS_ARP_SUPPORT + + #if WIFI_GRATUITOUS_ARP_INTERVAL_MIN == WIFI_GRATUITOUS_ARP_INTERVAL_MAX + _wifi_gratuitous_arp_interval = getSetting("wifiGarpMax", WIFI_GRATUITOUS_ARP_INTERVAL_MAX).toInt(); + #else + _wifi_gratuitous_arp_interval = secureRandom( + getSetting("wifiGarpMin", WIFI_GRATUITOUS_ARP_INTERVAL_MIN).toInt(), + getSetting("wifiGarpMax", WIFI_GRATUITOUS_ARP_INTERVAL_MAX).toInt()); + #endif + + #endif // WIFI_GRATUITOUS_ARP_SUPPORT + } void wifiLoop() { @@ -687,31 +749,11 @@ void wifiLoop() { _wifiCheckAP(); } - if (!wifiConnected()) { - return; - } - - // Gratuitous arp - static unsigned long last_arp = 0; - if (millis() - last_arp < 5000) { - return; - } - - unsigned long start = millis(); - netif *n = netif_list; - bool res = false; - while (n) { - if ((n->flags & NETIF_FLAG_LINK_UP) || (n->flags & NETIF_FLAG_UP)) { - DEBUG_MSG_P(PSTR("sending arp...\n")); - etharp_gratuitous(n); - res = true; - } else { - DEBUG_MSG_P(PSTR("netif not yet up...\n")); + #if WIFI_GRATUITOUS_ARP_SUPPORT + // Only send out gra arp when in STA mode + if (_wifi_gratuitous_arp_interval && ((WiFi.getMode() & WIFI_AP) == 0)) { + _wifiSendGratuitousArp(_wifi_gratuitous_arp_interval); } - n = n->next; - } - - if (res) DEBUG_MSG_P(PSTR("Sent Gratuitous ARP : Time Took - %d\n"), millis() - start); - last_arp = millis(); + #endif } From 7199d247a84f6ea6b337c3761c7b3adf0024c4d3 Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Thu, 29 Aug 2019 19:06:25 +0300 Subject: [PATCH 3/9] disable at runtime --- code/espurna/wifi.ino | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.ino index 070ef0aa9f..f213f0fe05 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.ino @@ -711,15 +711,10 @@ void wifiSetup() { // Periodic gratuitous arp to keep the ip alive #if WIFI_GRATUITOUS_ARP_SUPPORT - - #if WIFI_GRATUITOUS_ARP_INTERVAL_MIN == WIFI_GRATUITOUS_ARP_INTERVAL_MAX - _wifi_gratuitous_arp_interval = getSetting("wifiGarpMax", WIFI_GRATUITOUS_ARP_INTERVAL_MAX).toInt(); - #else - _wifi_gratuitous_arp_interval = secureRandom( - getSetting("wifiGarpMin", WIFI_GRATUITOUS_ARP_INTERVAL_MIN).toInt(), - getSetting("wifiGarpMax", WIFI_GRATUITOUS_ARP_INTERVAL_MAX).toInt()); - #endif - + _wifi_gratuitous_arp_interval = getSetting("wifiGarpIntvl", secureRandom( + WIFI_GRATUITOUS_ARP_INTERVAL_MIN, WIFI_GRATUITOUS_ARP_INTERVAL_MAX + )).toInt(); + DEBUG_MSG_P(PSTR("[WIFI] garp interval=%u\n"), _wifi_gratuitous_arp_interval); #endif // WIFI_GRATUITOUS_ARP_SUPPORT } From 732cc7ec16cfe02eaba96da8a696818b820ac78c Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Thu, 29 Aug 2019 19:15:04 +0300 Subject: [PATCH 4/9] ms values --- code/espurna/config/general.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index dc04f65e0a..020da8bc7e 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -565,13 +565,13 @@ #define WIFI_GRATUITOUS_ARP_SUPPORT 1 #endif -// Interval is randomized on each boot in range from ..._MIN to ..._MAX +// Interval is randomized on each boot in range from ..._MIN to ..._MAX (ms) #ifndef WIFI_GRATUITOUS_ARP_INTERVAL_MIN -#define WIFI_GRATUITOUS_ARP_INTERVAL_MIN 15 +#define WIFI_GRATUITOUS_ARP_INTERVAL_MIN 15000 #endif #ifndef WIFI_GRATUITOUS_ARP_INTERVAL_MAX -#define WIFI_GRATUITOUS_ARP_INTERVAL_MAX 30 +#define WIFI_GRATUITOUS_ARP_INTERVAL_MAX 30000 #endif // ----------------------------------------------------------------------------- From 40da16482cd99b5b03eca4e991050444fcc44240 Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Thu, 29 Aug 2019 19:15:14 +0300 Subject: [PATCH 5/9] reload --- code/espurna/wifi.ino | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.ino index f213f0fe05..262492f8f8 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.ino @@ -92,6 +92,11 @@ void _wifiConfigure() { #if WIFI_GRATUITOUS_ARP_SUPPORT _wifi_gratuitous_arp_last = millis(); + _wifi_gratuitous_arp_interval = getSetting("wifiGarpIntvl", secureRandom( + WIFI_GRATUITOUS_ARP_INTERVAL_MIN, WIFI_GRATUITOUS_ARP_INTERVAL_MAX + )).toInt(); + + DEBUG_MSG_P(PSTR("[WIFI] garp interval=%u\n"), _wifi_gratuitous_arp_interval); #endif } @@ -709,14 +714,6 @@ void wifiSetup() { espurnaRegisterLoop(wifiLoop); espurnaRegisterReload(_wifiConfigure); - // Periodic gratuitous arp to keep the ip alive - #if WIFI_GRATUITOUS_ARP_SUPPORT - _wifi_gratuitous_arp_interval = getSetting("wifiGarpIntvl", secureRandom( - WIFI_GRATUITOUS_ARP_INTERVAL_MIN, WIFI_GRATUITOUS_ARP_INTERVAL_MAX - )).toInt(); - DEBUG_MSG_P(PSTR("[WIFI] garp interval=%u\n"), _wifi_gratuitous_arp_interval); - #endif // WIFI_GRATUITOUS_ARP_SUPPORT - } void wifiLoop() { From 4c4a0409906ac5d2c514db4283aae2cbad1c5cb8 Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Thu, 29 Aug 2019 19:28:20 +0300 Subject: [PATCH 6/9] debug --- code/espurna/wifi.ino | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.ino index 262492f8f8..072cfaca28 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.ino @@ -95,8 +95,6 @@ void _wifiConfigure() { _wifi_gratuitous_arp_interval = getSetting("wifiGarpIntvl", secureRandom( WIFI_GRATUITOUS_ARP_INTERVAL_MIN, WIFI_GRATUITOUS_ARP_INTERVAL_MAX )).toInt(); - - DEBUG_MSG_P(PSTR("[WIFI] garp interval=%u\n"), _wifi_gratuitous_arp_interval); #endif } From 3fdf59e69382b414fa2836cbc4e924d0ab99478f Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Fri, 30 Aug 2019 01:22:34 +0300 Subject: [PATCH 7/9] reword --- code/espurna/config/general.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 020da8bc7e..d06c2d4b2b 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -557,9 +557,8 @@ // ref: https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/kconfig.html#config-lwip-esp-gratuitous-arp // ref: https://github.com/xoseperez/espurna/pull/1877#issuecomment-525612546 // -// Help solve compatibility issues with some routers. -// If the ARP table of the AP is old, and the AP doesn't send ARP request to update it's ARP table, -// this will lead to the STA sending IP packet fail. Thus we send gratuitous ARP periodically to let AP update it's ARP table. +// Broadcast gratuitous ARP periodically to update ARP tables on the AP and all devices on the same network. +// Helps to solve compatibility issues when ESP fails to timely reply to ARP requests, causing the device's ARP table entry to expire. #ifndef WIFI_GRATUITOUS_ARP_SUPPORT #define WIFI_GRATUITOUS_ARP_SUPPORT 1 From b97c6ab9a83722c0dafea54c536de5906f8a8526 Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Fri, 30 Aug 2019 02:51:20 +0300 Subject: [PATCH 8/9] filter by ifnum instead of checking for AP mode --- code/espurna/wifi.ino | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.ino index 072cfaca28..9fb1ddb902 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.ino @@ -520,11 +520,12 @@ bool _wifiSendGratuitousArp() { if ( (interface->flags & NETIF_FLAG_ETHARP) && (interface->hwaddr_len == ETHARP_HWADDR_LEN) - #if LWIP_VERSION_MAJOR != 1 + && (interface->num == STATION_IF) + #if LWIP_VERSION_MAJOR == 1 + && (!ip_addr_isany(&interface->ip_addr)) + #else && (interface->flags & NETIF_FLAG_LINK_UP) && (!ip4_addr_isany_val(*netif_ip4_addr(interface))) - #else - && (!ip_addr_isany(&interface->ip_addr)) #endif && (interface->flags & NETIF_FLAG_UP) ) { @@ -741,7 +742,7 @@ void wifiLoop() { #if WIFI_GRATUITOUS_ARP_SUPPORT // Only send out gra arp when in STA mode - if (_wifi_gratuitous_arp_interval && ((WiFi.getMode() & WIFI_AP) == 0)) { + if (_wifi_gratuitous_arp_interval) { _wifiSendGratuitousArp(_wifi_gratuitous_arp_interval); } #endif From 7264f7e073ebb7717f251a1da2451d24a1702317 Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Fri, 30 Aug 2019 09:33:00 +0300 Subject: [PATCH 9/9] drop station_if check 2.3.0/lwip1 builds netif->num increments on for each sta or ap lwip2 keeps those constant, but that seems like a implementation detail might break in the future anyways... --- code/espurna/wifi.ino | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/espurna/wifi.ino b/code/espurna/wifi.ino index 9fb1ddb902..e7a74b31ad 100644 --- a/code/espurna/wifi.ino +++ b/code/espurna/wifi.ino @@ -520,13 +520,12 @@ bool _wifiSendGratuitousArp() { if ( (interface->flags & NETIF_FLAG_ETHARP) && (interface->hwaddr_len == ETHARP_HWADDR_LEN) - && (interface->num == STATION_IF) #if LWIP_VERSION_MAJOR == 1 && (!ip_addr_isany(&interface->ip_addr)) #else - && (interface->flags & NETIF_FLAG_LINK_UP) && (!ip4_addr_isany_val(*netif_ip4_addr(interface))) #endif + && (interface->flags & NETIF_FLAG_LINK_UP) && (interface->flags & NETIF_FLAG_UP) ) { etharp_gratuitous(interface);