From ef4138bdaabad976eba1a2c29f981ab96e38284b Mon Sep 17 00:00:00 2001 From: s-hadinger <49731213+s-hadinger@users.noreply.github.com> Date: Tue, 27 Dec 2022 21:59:34 +0100 Subject: [PATCH] Support for IPv6 only networks on Ethernet (not yet Wifi) (#17527) --- CHANGELOG.md | 1 + .../ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp | 94 +++-- .../ESP32-to-ESP8266-compat/src/ESP8266WiFi.h | 8 +- tasmota/tasmota_support/support_command.ino | 45 ++- tasmota/tasmota_support/support_tasmota.ino | 8 +- tasmota/tasmota_support/support_wifi.ino | 339 +++++++++++++++--- .../xdrv_01_9_webserver.ino | 52 +-- .../tasmota_xdrv_driver/xdrv_02_9_mqtt.ino | 4 +- .../xdrv_52_3_berry_tasmota.ino | 8 +- .../xdrv_82_esp32_ethernet.ino | 71 +++- 10 files changed, 492 insertions(+), 138 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9795865a..569c21eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - Berry crypto add ``HKDF_HMAC_SHA256`` - Support for up to 3 single phase modbus energy monitoring device using generic Energy Modbus driver - Berry crypto add ``SPAKE2P_Matter`` for Matter support +- Support for IPv6 only networks on Ethernet (not yet Wifi) ### Breaking Changed diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp index 030ec6321..e2522942d 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp @@ -21,6 +21,9 @@ #include #include +extern void AddLog(uint32_t loglevel, PGM_P formatP, ...); +enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE}; + // // Wifi // @@ -32,49 +35,97 @@ #include "lwip/dns.h" wl_status_t WiFiClass32::begin(const char* wpa2_ssid, wpa2_auth_method_t method, const char* wpa2_identity, const char* wpa2_username, const char *wpa2_password, const char* ca_pem, const char* client_crt, const char* client_key, int32_t channel, const uint8_t* bssid, bool connect) { - saveDNS(); + scrubDNS(); wl_status_t ret = WiFiClass::begin(wpa2_ssid, method, wpa2_identity, wpa2_username, wpa2_password, ca_pem, client_crt, client_key, channel, bssid, connect); - restoreDNS(); + scrubDNS(); return ret; } wl_status_t WiFiClass32::begin(const char* ssid, const char *passphrase, int32_t channel, const uint8_t* bssid, bool connect) { - saveDNS(); + scrubDNS(); wl_status_t ret = WiFiClass::begin(ssid, passphrase, channel, bssid, connect); - restoreDNS(); + scrubDNS(); return ret; } wl_status_t WiFiClass32::begin(char* ssid, char *passphrase, int32_t channel, const uint8_t* bssid, bool connect) { - saveDNS(); + scrubDNS(); wl_status_t ret = WiFiClass::begin(ssid, passphrase, channel, bssid, connect); - restoreDNS(); + scrubDNS(); return ret; } wl_status_t WiFiClass32::begin() { - saveDNS(); + scrubDNS(); wl_status_t ret = WiFiClass::begin(); - restoreDNS(); + scrubDNS(); return ret; } -void WiFiClass32::saveDNS(void) { - // save the DNS servers - for (uint32_t i=0; i: DNS: from(%s %s) to (%s %s) has4/6:%i-%i", dns_entry0.c_str(), dns_entry1.c_str(), IPAddress(dns_getserver(0)).toString().c_str(), IPAddress(dns_getserver(1)).toString().c_str(), has_v4, has_v6); } void WiFiClass32::setSleepMode(int iSleepMode) { @@ -190,6 +241,7 @@ int WiFiClass32::hostByName(const char* aHostname, IPAddress& aResult, int32_t t aResult = (uint32_t) 0; // by default set to IPv4 0.0.0.0 dns_ipaddr = *IP4_ADDR_ANY; // by default set to IPv4 0.0.0.0 + scrubDNS(); // internal calls to reconnect can zero the DNS servers, save DNS for future use ip_addr_counter++; // increase counter, from now ignore previous responses clearStatusBits(WIFI_DNS_IDLE_BIT | WIFI_DNS_DONE_BIT); uint8_t v4v6priority = LWIP_DNS_ADDRTYPE_IPV4; diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h index d0274733d..0206b0ec5 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h @@ -60,10 +60,12 @@ public: int hostByName(const char* aHostname, IPAddress& aResult, int32_t timer_ms); int hostByName(const char* aHostname, IPAddress& aResult); - void saveDNS(void); - void restoreDNS(void); + void scrubDNS(void); protected: - ip_addr_t dns_save[DNS_MAX_SERVERS] = {}; + ip_addr_t dns_save4[DNS_MAX_SERVERS] = {}; // IPv4 DNS servers +#ifdef USE_IPV6 + ip_addr_t dns_save6[DNS_MAX_SERVERS] = {}; // IPv6 DNS servers +#endif // USE_IPV6 }; void wifi_station_disconnect(); diff --git a/tasmota/tasmota_support/support_command.ino b/tasmota/tasmota_support/support_command.ino index ca5ec268a..56ef3213f 100644 --- a/tasmota/tasmota_support/support_command.ino +++ b/tasmota/tasmota_support/support_command.ino @@ -804,44 +804,51 @@ void CmndStatus(void) if ((0 == payload) || (5 == payload)) { #ifdef USE_IPV6 if (5 == payload) { WifiDumpAddressesIPv6(); } -#endif // USE_IPV6 + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS5_NETWORK "\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" + D_CMND_IPADDRESS "\":\"%_I\",\"" D_JSON_GATEWAY "\":\"%_I\",\"" D_JSON_SUBNETMASK "\":\"%_I\",\"" + D_JSON_DNSSERVER "1\":\"%s\",\"" D_JSON_DNSSERVER "2\":\"%s\",\"" + D_JSON_MAC "\":\"%s\"" + ",\"" D_JSON_IP6_GLOBAL "\":\"%s\",\"" D_JSON_IP6_LOCAL "\":\"%s\""), + TasmotaGlobal.hostname, + (uint32_t)WiFi.localIP(), Settings->ipv4_address[1], Settings->ipv4_address[2], + DNSGetIPStr(0).c_str(), DNSGetIPStr(1).c_str(), + WiFi.macAddress().c_str() + ,WifiGetIPv6Str().c_str(), WifiGetIPv6LinkLocalStr().c_str()); +#else // USE_IPV6 Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS5_NETWORK "\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%_I\",\"" D_JSON_GATEWAY "\":\"%_I\",\"" D_JSON_SUBNETMASK "\":\"%_I\",\"" D_JSON_DNSSERVER "1\":\"%_I\",\"" D_JSON_DNSSERVER "2\":\"%_I\",\"" - D_JSON_MAC "\":\"%s\"" -#ifdef USE_IPV6 - ",\"" D_JSON_IP6_GLOBAL "\":\"%s\",\"" D_JSON_IP6_LOCAL "\":\"%s\"" -#endif // USE_IPV6 - ), + D_JSON_MAC "\":\"%s\""), TasmotaGlobal.hostname, (uint32_t)WiFi.localIP(), Settings->ipv4_address[1], Settings->ipv4_address[2], Settings->ipv4_address[3], Settings->ipv4_address[4], - WiFi.macAddress().c_str() -#ifdef USE_IPV6 - ,WifiGetIPv6().c_str(), WifiGetIPv6LinkLocal().c_str() + WiFi.macAddress().c_str()); #endif // USE_IPV6 - ); #ifdef USE_TASMESH ResponseAppend_P(PSTR(",\"SoftAPMac\":\"%s\""), WiFi.softAPmacAddress().c_str()); #endif // USE_TASMESH #if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET) +#ifdef USE_IPV6 + ResponseAppend_P(PSTR(",\"Ethernet\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" + D_CMND_IPADDRESS "\":\"%_I\",\"" D_JSON_GATEWAY "\":\"%_I\",\"" D_JSON_SUBNETMASK "\":\"%_I\",\"" + D_JSON_DNSSERVER "1\":\"%s\",\"" D_JSON_DNSSERVER "2\":\"%s\",\"" + D_JSON_MAC "\":\"%s\",\"" D_JSON_IP6_GLOBAL "\":\"%s\",\"" D_JSON_IP6_LOCAL "\":\"%s\"}"), + EthernetHostname(), + (uint32_t)EthernetLocalIP(), Settings->eth_ipv4_address[1], Settings->eth_ipv4_address[2], + DNSGetIPStr(0).c_str(), DNSGetIPStr(1).c_str(), + EthernetMacAddress().c_str(), + EthernetGetIPv6Str().c_str(), EthernetGetIPv6LinkLocalStr().c_str()); +#else // USE_IPV6 ResponseAppend_P(PSTR(",\"Ethernet\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%_I\",\"" D_JSON_GATEWAY "\":\"%_I\",\"" D_JSON_SUBNETMASK "\":\"%_I\",\"" D_JSON_DNSSERVER "1\":\"%_I\",\"" D_JSON_DNSSERVER "2\":\"%_I\",\"" - D_JSON_MAC "\":\"%s\"" - -#ifdef USE_IPV6 - ",\"" D_JSON_IP6_GLOBAL "\":\"%s\",\"" D_JSON_IP6_LOCAL "\":\"%s\"" -#endif // USE_IPV6 - "}"), + D_JSON_MAC "\":\"%s\"}"), EthernetHostname(), (uint32_t)EthernetLocalIP(), Settings->eth_ipv4_address[1], Settings->eth_ipv4_address[2], Settings->eth_ipv4_address[3], Settings->eth_ipv4_address[4], EthernetMacAddress().c_str() -#ifdef USE_IPV6 - ,EthernetGetIPv6().c_str(), EthernetGetIPv6LinkLocal().c_str() + #endif // USE_IPV6 - ); #endif // USE_ETHERNET ResponseAppend_P(PSTR(",\"" D_CMND_WEBSERVER "\":%d,\"HTTP_API\":%d,\"" D_CMND_WIFICONFIG "\":%d,\"" D_CMND_WIFIPOWER "\":%s}}"), Settings->webserver, Settings->flag5.disable_referer_chk, Settings->sta_config, WifiGetOutputPower().c_str()); diff --git a/tasmota/tasmota_support/support_tasmota.ino b/tasmota/tasmota_support/support_tasmota.ino index f8234b9d3..f5da6c033 100644 --- a/tasmota/tasmota_support/support_tasmota.ino +++ b/tasmota/tasmota_support/support_tasmota.ino @@ -1576,14 +1576,10 @@ void Every250mSeconds(void) if (Settings->webserver) { #ifdef ESP8266 - if (!WifiIsInManagerMode()) { StartWebserver(Settings->webserver, WiFi.localIP()); } + if (!WifiIsInManagerMode()) { StartWebserver(Settings->webserver); } #endif // ESP8266 #ifdef ESP32 -#ifdef USE_ETHERNET - StartWebserver(Settings->webserver, (EthernetLocalIP()) ? EthernetLocalIP() : WiFi.localIP()); -#else - StartWebserver(Settings->webserver, WiFi.localIP()); -#endif + StartWebserver(Settings->webserver); #endif // ESP32 #ifdef USE_DISCOVERY diff --git a/tasmota/tasmota_support/support_wifi.ino b/tasmota/tasmota_support/support_wifi.ino index d03b55699..1baa6652a 100644 --- a/tasmota/tasmota_support/support_wifi.ino +++ b/tasmota/tasmota_support/support_wifi.ino @@ -41,6 +41,7 @@ const uint8_t WIFI_CHECK_SEC = 20; // seconds const uint8_t WIFI_RETRY_OFFSET_SEC = WIFI_RETRY_SECONDS; // seconds #include // Wifi, MQTT, Ota, WifiManager +#include "lwip/dns.h" int WifiGetRssiAsQuality(int rssi) { int quality = 0; @@ -458,37 +459,110 @@ void WifiSetState(uint8_t state) } } +/*****************************************************************************************************\ + * IP detection revised for full IPv4 / IPv6 support + * + * In general, each interface (Wifi/Eth) can have 1x IPv4 and + * 2x IPv6 (Global routable address and Link-Local starting witn fe80:...) + * + * We always use an IPv4 address if one is assigned, and revert to + * IPv6 only on networks that are v6 only. + * Ethernet calls can be safely used even if the USE_ETHERNET is not enabled + * + * New APIs: + * - general form is: + * `bool XXXGetIPYYY(IPAddress*)` returns `true` if the address exists and copies the address + * if the pointer is non-null. + * `bool XXXHasIPYYY()` same as above but only returns `true` or `false` + * `String XXXGetIPYYYStr()` returns the IP as a `String` or empty `String` if none + * + * `XXX` can be `Wifi` or `Eth` + * `YYY` can be `` for any address, `v6` for IPv6 global address or `v6LinkLocal` for Link-local + * + * - Legacy `Wifi.localIP()` and `ETH.localIP()` always return IPv4 and nothing on IPv6 only networks + * + * - v4/v6: + * `WifiGetIP`, `WifiGetIPStr`, `WifiHasIP`: get preferred v4/v6 address for Wifi + * `EthernetGetIP`, `EthernetGetIPStr`, `EthernetHasIP`: get preferred v4/v6 for Ethernet + * + * - Main IP to be used dual stack v4/v6 + * `hasIP`, `IPGetListeningAddress`, `IPGetListeningAddressStr`: any IP to listen to for Web Server + * IPv4 is always preferred, and Eth is preferred over Wifi. + * `IPForUrl`: converts v4/v6 to use in URL, enclosing v6 in [] + * + * - v6 only: + * `WifiGetIPv6`, `WifiGetIPv6Str`, `WifiHasIPv6` + * `WifiGetIPv6LinkLocal`, `WifiGetIPv6LinkLocalStr` + * `EthernetGetIPv6, `EthernetHasIPv6`, `EthernetGetIPv6Str` + * `EthernetGetIPv6LinkLocal`, `EthernetGetIPv6LinkLocalStr` + * + * - v4 only: + * `WifiGetIPv4`, `WifiGetIPv4Str`, `WifiHasIPv4` + * `EthernetGetIPv4`, `EthernetGetIPv4Str`, `EthernetHasIPv4` + * + * - DNS reporting actual values used (not the Settings): + * `DNSGetIP(n)`, `DNSGetIPStr(n)` with n=`0`/`1` (same dns for Wifi and Eth) +\*****************************************************************************************************/ +// IPv4 for Wifi +// Returns only IPv6 global address (no loopback and no link-local) +bool WifiGetIPv4(IPAddress *ip) +{ + uint32_t wifi_uint = (uint32_t) WiFi.localIP(); + if (ip != nullptr) { *ip = wifi_uint; } + return wifi_uint != 0; +} +bool WifiHasIPv4(void) +{ + return WifiGetIPv4(nullptr); +} +String WifiGetIPv4Str(void) +{ + IPAddress ip; + return WifiGetIPv4(&ip) ? ip.toString() : String(); +} + +bool EthernetGetIPv4(IPAddress *ip) +{ +#if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET) + uint32_t wifi_uint = (uint32_t) EthernetLocalIP(); + if (ip != nullptr) { *ip = wifi_uint; } + return wifi_uint != 0; +#else + if (ip != nullptr) { *ip = (uint32_t)0; } + return false; +#endif +} +bool EthernetHasIPv4(void) +{ + return EthernetGetIPv4(nullptr); +} +String EthernetGetIPv4Str(void) +{ + IPAddress ip; + return EthernetGetIPv4(&ip) ? ip.toString() : String(); +} + #ifdef USE_IPV6 // // Scan through all interfaces to find a global or local IPv6 address // Arg: // is_local: is the address Link-Local (true) or Global (false) // if_type: possible values are "st" for Wifi STA, "en" for Ethernet, "lo" for localhost (not useful) -static String WifiFindIPv6(bool is_local, const char * if_type = "st") { +// Returns `true` if found +bool WifiFindIPv6(IPAddress *ip, bool is_local, const char * if_type = "st") { for (netif* intf = netif_list; intf != nullptr; intf = intf->next) { if (intf->name[0] == if_type[0] && intf->name[1] == if_type[1]) { for (uint32_t i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { ip_addr_t *ipv6 = &intf->ip6_addr[i]; if (IP_IS_V6_VAL(*ipv6) && !ip_addr_isloopback(ipv6) && !ip_addr_isany(ipv6) && ((bool)ip_addr_islinklocal(ipv6) == is_local)) { - return IPAddress(ipv6).toString(); + if (ip != nullptr) { *ip = *ipv6; } + return true; } } } } - return String(); + return false; } - -// Returns only IPv6 global address (no loopback and no link-local) -String WifiGetIPv6(void) -{ - return WifiFindIPv6(false, "st"); -} - -String WifiGetIPv6LinkLocal(void) -{ - return WifiFindIPv6(true, "st"); -} - // add an IPv6 link-local address to all netif void CreateLinkLocalIPv6(void) { @@ -499,6 +573,81 @@ void CreateLinkLocalIPv6(void) #endif // ESP32 } + +// Returns only IPv6 global address (no loopback and no link-local) +bool WifiGetIPv6(IPAddress *ip) +{ + return WifiFindIPv6(ip, false, "st"); +} +bool WifiHasIPv6(void) +{ + return WifiGetIPv6(nullptr); +} +String WifiGetIPv6Str(void) +{ + IPAddress ip; + return WifiGetIPv6(&ip) ? ip.toString() : String(); +} + +bool WifiGetIPv6LinkLocal(IPAddress *ip) +{ + return WifiFindIPv6(ip, true, "st"); +} +String WifiGetIPv6LinkLocalStr(void) +{ + IPAddress ip; + return WifiGetIPv6LinkLocal(&ip) ? ip.toString() : String(); +} + + +// Returns only IPv6 global address (no loopback and no link-local) +bool EthernetGetIPv6(IPAddress *ip) +{ + return WifiFindIPv6(ip, false, "en"); +} +bool EthernetHasIPv6(void) +{ + return EthernetGetIPv6(nullptr); +} +String EthernetGetIPv6Str(void) +{ + IPAddress ip; + return EthernetGetIPv6(&ip) ? ip.toString() : String(); +} + +bool EthernetGetIPv6LinkLocal(IPAddress *ip) +{ + return WifiFindIPv6(ip, true, "en"); +} +bool EthernetHasIPv6LinkLocal(void) +{ + return EthernetGetIPv6LinkLocal(nullptr); +} +String EthernetGetIPv6LinkLocalStr(void) +{ + IPAddress ip; + return EthernetGetIPv6LinkLocal(&ip) ? ip.toString() : String(); +} + +bool DNSGetIP(IPAddress *ip, uint32_t idx) +{ +#ifdef ESP32 + WiFi.scrubDNS(); // internal calls to reconnect can zero the DNS servers, restore the previous values +#endif + const ip_addr_t *ip_dns = dns_getserver(idx); + if (!ip_addr_isany(ip_dns)) { + if (ip != nullptr) { *ip = *ip_dns; } + return true; + } + *ip = *IP4_ADDR_ANY; + return false; +} +String DNSGetIPStr(uint32_t idx) +{ + IPAddress ip; + return DNSGetIP(&ip, idx) ? ip.toString() : String(F("0.0.0.0")); +} + // #include "lwip/dns.h" void WifiDumpAddressesIPv6(void) @@ -507,31 +656,144 @@ void WifiDumpAddressesIPv6(void) if (!ip_addr_isany_val(intf->ip_addr)) AddLog(LOG_LEVEL_DEBUG, "WIF: '%c%c' IPv4 %s", intf->name[0], intf->name[1], IPAddress(intf->ip_addr).toString().c_str()); for (uint32_t i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { if (!ip_addr_isany_val(intf->ip6_addr[i])) - AddLog(LOG_LEVEL_DEBUG, "WIF: '%c%c' IPv6 %s %s", intf->name[0], intf->name[1], + AddLog(LOG_LEVEL_DEBUG, "IP : '%c%c' IPv6 %s %s", intf->name[0], intf->name[1], IPAddress(intf->ip6_addr[i]).toString().c_str(), ip_addr_islinklocal(&intf->ip6_addr[i]) ? "local" : ""); } } - AddLog(LOG_LEVEL_DEBUG, "WIF: DNS(0): %s", IPAddress(dns_getserver(0)).toString().c_str()); - AddLog(LOG_LEVEL_DEBUG, "WIF: DNS(1): %s", IPAddress(dns_getserver(1)).toString().c_str()); + AddLog(LOG_LEVEL_DEBUG, "IP : DNS: %s %s", IPAddress(dns_getserver(0)).toString().c_str(), IPAddress(dns_getserver(1)).toString().c_str()); + AddLog(LOG_LEVEL_DEBUG, "WIF: v4IP: %_I v6IP: %s mainIP: %s", (uint32_t) WiFi.localIP(), WifiGetIPv6Str().c_str(), WifiGetIPStr().c_str()); +#if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET) + AddLog(LOG_LEVEL_DEBUG, "ETH: v4IP %_I v6IP: %s mainIP: %s", (uint32_t) EthernetLocalIP(), EthernetGetIPv6Str().c_str(), EthernetGetIPStr().c_str()); +#endif + AddLog(LOG_LEVEL_DEBUG, "IP : ListeningIP %s", IPGetListeningAddressStr().c_str()); } #endif // USE_IPV6 -// Check to see if we have any routable IP address -bool WifiHasIP(void) { +// Returns the IP address on which we listen (used for Web UI mainly) +// +// If IPv4 is set, it is preferred. +// If only IPv6, return the routable global address +bool IPGetListeningAddress(IPAddress * ip) +{ + if (ip == nullptr) return HasIP(); // no value added for this method if no parameter + #ifdef USE_IPV6 -#ifdef ESP32 - return !WiFi.localIP().isAny(); -#else // ESP32 - const ip_addr_t &ipaddr = (ip_addr_t)WiFi.localIP(); - return !ip_addr_isany_val(ipaddr); -#endif // ESP32 + // collect both Wifi and Eth IPs and choose an IPv4 if any (Eth has priority) + IPAddress ip_wifi; + bool has_wifi = WifiGetIP(&ip_wifi); + +#if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET) + IPAddress ip_eth; + bool has_eth = EthernetGetIP(&ip_eth); + if (has_wifi && has_eth) { + if (ip_eth.isV4()) { *ip = ip_eth; return true; } + if (ip_wifi.isV4()) { *ip = ip_wifi; return true; } + // both addresses are v6, return ETH + *ip = ip_eth; + return true; + } + // from here only wifi or eth may be valid + if (has_eth) { *ip = ip_eth; return true; } +#endif + + if (has_wifi) { *ip = ip_wifi; return true; } + + *ip = IPAddress(); + return false; +#else // USE_IPV6 +#if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET) + if (EthernetGetIP(ip)) { return true; } +#endif + if (WifiGetIP(ip)) { return true; } + *ip = IPAddress(); + return false; +#endif // USE_IPV6 +} + +String IPGetListeningAddressStr(void) +{ + IPAddress ip; + if (IPGetListeningAddress(&ip)) { + return ip.toString(); + } else { + return String(); + } +} + +// Because of IPv6, we can't test an IP address agains (uint32_t)0L anymore +// This test would work only for IPv4 assigned addresses. +// We must now use the following instead +inline bool IPIsValid(const IPAddress & ip) +{ +#ifdef USE_IPV6 + return !ip_addr_isany_val((const ip_addr_t &)ip); +#else + return static_cast(ip) != 0; +#endif +} + +// Because of IPv6, URL encoding of IP address needs to be adapted +// IPv4: address is "x.x.x.x" +// IPv6: address is enclosed in brackets "[x.x::x.x...]" +String IPForUrl(const IPAddress & ip) +{ +#ifdef USE_IPV6 + if (ip.isV4()) { + return ip.toString().c_str(); + } else { + String s('['); + s += ip.toString().c_str(); + s += ']'; + return s; + } +#else + return ip.toString().c_str(); +#endif +} + +// Check to see if we have any routable IP address +// IPv4 has always priority +// Copy the value of the IP if pointer provided (optional) +bool WifiGetIP(IPAddress *ip) { +#ifdef USE_IPV6 + if ((uint32_t)WiFi.localIP() != 0) { + if (ip != nullptr) { *ip = WiFi.localIP(); } + return true; + } + IPAddress lip; + if (WifiGetIPv6(&lip)) { + if (ip != nullptr) { *ip = lip; } + return true; + } + if (ip != nullptr) { *ip = IPAddress(); } + return false; #else // IPv4 only + if (ip != nullptr) { *ip = WiFi.localIP(); } return (uint32_t)WiFi.localIP() != 0; #endif // USE_IPV6 } +bool WifiHasIP(void) { + return WifiGetIP(nullptr); +} + +String WifiGetIPStr(void) +{ + IPAddress ip; + return WifiGetIP(&ip) ? ip.toString() : String(); +} + +// Has a routable IP, whether IPv4 or IPv6, Wifi or Ethernet +bool HasIP(void) { + if (WifiHasIP()) return true; +#if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET) + if (EthernetHasIP()) return true; +#endif + return false; +} + void WifiCheckIp(void) { #ifdef USE_IPV6 if (WL_CONNECTED == WiFi.status()) { @@ -904,20 +1166,14 @@ bool WifiDNSGetIPv6Priority(void) { // Any change in logic needs to clear the DNS cache static bool had_v6prio = false; - const ip_addr_t &local_ip = (ip_addr_t)WiFi.localIP(); - bool has_v4 = !ip_addr_isany_val(local_ip) && IP_IS_V4_VAL(local_ip); - bool has_v6 = WifiGetIPv6().length() != 0; -#ifdef USE_ETHERNET - const ip_addr_t &local_ip_eth = (ip_addr_t)EthernetLocalIP(); - has_v4 = has_v4 || (!ip_addr_isany_val(local_ip_eth) && IP_IS_V4_VAL(local_ip_eth)); - has_v6 = has_v6 || EthernetGetIPv6().length() != 0; -#endif - + bool has_v4 = WifiHasIPv4() || EthernetHasIPv4(); + bool has_v6 = WifiHasIPv6() || EthernetHasIPv6(); bool v6prio = Settings->flag6.dns_ipv6_priority; - // AddLog(LOG_LEVEL_DEBUG, "WIF: v6 priority was %i, now is %i, has_v4=%i has_v6=%i", had_v6prio, v6prio, has_v4, has_v6); - if (has_v4 && !has_v6 && v6prio) { + if (has_v4 && !has_v6) { v6prio = false; // revert to IPv4 first + } else if (has_v6 && !has_v4) { + v6prio = true; // only IPv6 is available } // any change of state requires a dns cache clear @@ -1107,7 +1363,6 @@ void WifiEvents(arduino_event_t *event) { #ifdef USE_IPV6 case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - case ARDUINO_EVENT_ETH_GOT_IP6: { ip_addr_t ip_addr6; ip_addr_copy_from_ip6(ip_addr6, event->event_info.got_ip6.ip6_info.ip); @@ -1115,21 +1370,17 @@ void WifiEvents(arduino_event_t *event) { AddLog(LOG_LEVEL_DEBUG, PSTR("%s: IPv6 %s %s"), event->event_id == ARDUINO_EVENT_ETH_GOT_IP6 ? "ETH" : "WIF", addr.isLocal() ? PSTR("Local") : PSTR("Global"), addr.toString().c_str()); - WiFi.saveDNS(); // internal calls to reconnect can zero the DNS servers, save DNS for future use } break; #endif // USE_IPV6 case ARDUINO_EVENT_WIFI_STA_GOT_IP: - case ARDUINO_EVENT_ETH_GOT_IP: { ip_addr_t ip_addr4; ip_addr_copy_from_ip4(ip_addr4, event->event_info.got_ip.ip_info.ip); - AddLog(LOG_LEVEL_DEBUG, PSTR("%s: IPv4 %_I, mask %_I, gateway %_I"), - event->event_id == ARDUINO_EVENT_ETH_GOT_IP ? "ETH" : "WIF", + AddLog(LOG_LEVEL_DEBUG, PSTR("WIF: IPv4 %_I, mask %_I, gateway %_I"), event->event_info.got_ip.ip_info.ip.addr, event->event_info.got_ip.ip_info.netmask.addr, event->event_info.got_ip.ip_info.gw.addr); - WiFi.saveDNS(); // internal calls to reconnect can zero the DNS servers, save DNS for future use } break; @@ -1139,12 +1390,12 @@ void WifiEvents(arduino_event_t *event) { break; case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: - WiFi.restoreDNS(); // internal calls to reconnect can zero the DNS servers, restore the previous values Wifi.ipv6_local_link_called = false; break; default: break; } + WiFi.scrubDNS(); // internal calls to reconnect can zero the DNS servers, restore the previous values } #endif // ESP32 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino index 94eaf5d27..de4a3a498 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_01_9_webserver.ino @@ -570,7 +570,8 @@ void WebServer_on(const char * prefix, void (*func)(void), uint8_t method = HTTP #endif // ESP32 } -void StartWebserver(int type, IPAddress ipweb) +// Always listens to all interfaces, so we don't need an IP address anymore +void StartWebserver(int type) { if (!Settings->web_refresh) { Settings->web_refresh = HTTP_REFRESH_TIME; } if (!Web.state) { @@ -610,19 +611,8 @@ void StartWebserver(int type, IPAddress ipweb) Webserver->begin(); // Web server start } if (Web.state != type) { -#ifdef USE_IPV6 - String ipv6_addr = WifiGetIPv6(); - if (ipv6_addr!="") { - AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %_I and IPv6 global address %s "), - NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", (uint32_t)ipweb, ipv6_addr.c_str()); - } else { - AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %_I"), - NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", (uint32_t)ipweb); - } -#else - AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %_I"), - NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", (uint32_t)ipweb); -#endif // USE_IPV6 + AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), + NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", IPGetListeningAddressStr().c_str()); TasmotaGlobal.rules_flag.http_init = 1; Web.state = type; } @@ -663,7 +653,7 @@ void WifiManagerBegin(bool reset_only) DnsServer->setErrorReplyCode(DNSReplyCode::NoError); DnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); - StartWebserver((reset_only ? HTTP_MANAGER_RESET_ONLY : HTTP_MANAGER), WiFi.softAPIP()); + StartWebserver((reset_only ? HTTP_MANAGER_RESET_ONLY : HTTP_MANAGER)); } void PollDnsWebserver(void) @@ -700,12 +690,14 @@ bool HttpCheckPriviledgedAccess(bool autorequestauth = true) referer.toUpperCase(); String hostname = TasmotaGlobal.hostname; hostname.toUpperCase(); + // TODO rework if IPv6 if ((referer.indexOf(hostname) == 7) || (referer.indexOf(WiFi.localIP().toString()) == 7)) { return true; } #if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET) hostname = EthernetHostname(); hostname.toUpperCase(); + // TODO rework if IPv6 if ((referer.indexOf(hostname) == 7) || (referer.indexOf(EthernetLocalIP().toString()) == 7)) { return true; } @@ -898,7 +890,7 @@ void WSContentSendStyle_P(const char* formatP, ...) { #else if ( Settings->flag3.gui_hostname_ip || ( (WiFi.getMode() == WIFI_AP_STA) && (!Web.initial_config) ) ) { #endif - bool lip = (static_cast(WiFi.localIP()) != 0); + bool lip = WifiHasIP(); bool sip = (static_cast(WiFi.softAPIP()) != 0); bool eip = false; if (lip || sip) { @@ -910,7 +902,7 @@ void WSContentSendStyle_P(const char* formatP, ...) { (sip) ? WiFi.softAPIP().toString().c_str() : ""); } #if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET) - eip = (static_cast(EthernetLocalIP()) != 0); + eip = EthernetHasIP(); if (eip) { WSContentSend_P(PSTR("%s%s%s (%s)"), // tasmota-eth.local (192.168.2.13) (lip || sip) ? PSTR("
") : PSTR("

"), @@ -1015,8 +1007,8 @@ void WebRestart(uint32_t type) { #if ((RESTART_AFTER_INITIAL_WIFI_CONFIG) && (AFTER_INITIAL_WIFI_CONFIG_GO_TO_NEW_IP)) // In case of type 3 (New network has been configured) go to the new device's IP in the new Network if (3 == type) { - WSContentSend_P("setTimeout(function(){location.href='http://%_I';},%d);", - (uint32_t)WiFi.localIP(), + WSContentSend_P("setTimeout(function(){location.href='http://%s';},%d);", + IPForUrl(WiFi.localIP()).c_str(), HTTP_RESTART_RECONNECT_TIME ); } else { @@ -2360,11 +2352,11 @@ void HandleInformation(void) WSContentSend_P(PSTR("}1" D_AP "%d " D_SSID " (" D_RSSI ")}2%s (%d%%, %d dBm) 11%c"), Settings->sta_active +1, HtmlEscape(SettingsText(SET_STASSID1 + Settings->sta_active)).c_str(), WifiGetRssiAsQuality(rssi), rssi, pgm_read_byte(&kWifiPhyMode[WiFi.getPhyMode() & 0x3]) ); WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), TasmotaGlobal.hostname, (Mdns.begun) ? PSTR(".local") : ""); #ifdef USE_IPV6 - String ipv6_addr = WifiGetIPv6(); + String ipv6_addr = WifiGetIPv6Str(); if (ipv6_addr != "") { WSContentSend_P(PSTR("}1 IPv6 Global (wifi)}2%s"), ipv6_addr.c_str()); } - ipv6_addr = WifiGetIPv6LinkLocal(); + ipv6_addr = WifiGetIPv6LinkLocalStr(); if (ipv6_addr != "") { WSContentSend_P(PSTR("}1 IPv6 Local (wifi)}2%s"), ipv6_addr.c_str()); } @@ -2378,21 +2370,26 @@ void HandleInformation(void) if (!TasmotaGlobal.global_state.wifi_down) { WSContentSend_P(PSTR("}1" D_GATEWAY "}2%_I"), Settings->ipv4_address[1]); WSContentSend_P(PSTR("}1" D_SUBNET_MASK "}2%_I"), Settings->ipv4_address[2]); +#ifdef USE_IPV6 + WSContentSend_P(PSTR("}1" D_DNS_SERVER "1}2%s"), DNSGetIPStr(0).c_str()); + WSContentSend_P(PSTR("}1" D_DNS_SERVER "2}2%s"), DNSGetIPStr(1).c_str()); +#else // USE_IPV6 WSContentSend_P(PSTR("}1" D_DNS_SERVER "1}2%_I"), Settings->ipv4_address[3]); WSContentSend_P(PSTR("}1" D_DNS_SERVER "2}2%_I"), Settings->ipv4_address[4]); +#endif // USE_IPV6 } #if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET) - if (static_cast(EthernetLocalIP()) != 0) { + if (EthernetHasIP()) { if (show_hr) { WSContentSend_P(PSTR("}1
}2
")); } WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), EthernetHostname(), (Mdns.begun) ? PSTR(".local") : ""); #ifdef USE_IPV6 - String ipv6_eth_addr = EthernetGetIPv6(); + String ipv6_eth_addr = EthernetGetIPv6Str(); if (ipv6_eth_addr != "") { WSContentSend_P(PSTR("}1 IPv6 Global (eth)}2%s"), ipv6_eth_addr.c_str()); } - ipv6_eth_addr = EthernetGetIPv6LinkLocal(); + ipv6_eth_addr = EthernetGetIPv6LinkLocalStr(); if (ipv6_eth_addr != "") { WSContentSend_P(PSTR("}1 IPv6 Local (eth)}2%s"), ipv6_eth_addr.c_str()); } @@ -2403,8 +2400,13 @@ void HandleInformation(void) if (!TasmotaGlobal.global_state.eth_down) { WSContentSend_P(PSTR("}1" D_GATEWAY "}2%_I"), Settings->eth_ipv4_address[1]); WSContentSend_P(PSTR("}1" D_SUBNET_MASK "}2%_I"), Settings->eth_ipv4_address[2]); +#ifdef USE_IPV6 + WSContentSend_P(PSTR("}1" D_DNS_SERVER "1}2%s"), DNSGetIPStr(0).c_str()); + WSContentSend_P(PSTR("}1" D_DNS_SERVER "2}2%s"), DNSGetIPStr(1).c_str()); +#else // USE_IPV6 WSContentSend_P(PSTR("}1" D_DNS_SERVER "1}2%_I"), Settings->eth_ipv4_address[3]); WSContentSend_P(PSTR("}1" D_DNS_SERVER "2}2%_I"), Settings->eth_ipv4_address[4]); +#endif // USE_IPV6 } #endif // USE_ETHERNET WSContentSend_P(PSTR("}1}2 ")); // Empty line @@ -3724,7 +3726,7 @@ bool Xdrv01(uint32_t function) Wifi.wifi_test_AP_TIMEOUT = false; Wifi.wifi_test_counter = 0; Wifi.wifiTest = WIFI_TEST_FINISHED; - AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CMND_SSID " %s: " D_CONNECTED " - " D_IP_ADDRESS " %_I"), SettingsText(Wifi.wifi_Test_Save_SSID2 ? SET_STASSID2 : SET_STASSID1), (uint32_t)WiFi.localIP()); + AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CMND_SSID " %s: " D_CONNECTED " - " D_IP_ADDRESS " %s"), SettingsText(Wifi.wifi_Test_Save_SSID2 ? SET_STASSID2 : SET_STASSID1), WiFi.localIP().toString().c_str()); // TasmotaGlobal.blinks = 255; // Signal wifi connection with blinks if (MAX_WIFI_OPTION != Wifi.old_wificonfig) { TasmotaGlobal.wifi_state_flag = Settings->sta_config = Wifi.old_wificonfig; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_02_9_mqtt.ino b/tasmota/tasmota_xdrv_driver/xdrv_02_9_mqtt.ino index b4407804d..849220e0f 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_02_9_mqtt.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_02_9_mqtt.ino @@ -972,8 +972,8 @@ void MqttConnected(void) { ResponseAppend_P(PSTR(",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%_I\""), TasmotaGlobal.hostname, (uint32_t)WiFi.localIP()); #ifdef USE_IPV6 - ResponseAppend_P(PSTR(",\"" D_JSON_IP6_GLOBAL "\":\"%s\""), WifiGetIPv6().c_str()); - ResponseAppend_P(PSTR(",\"" D_JSON_IP6_LOCAL "\":\"%s\""), WifiGetIPv6LinkLocal().c_str()); + ResponseAppend_P(PSTR(",\"" D_JSON_IP6_GLOBAL "\":\"%s\""), WifiGetIPv6Str().c_str()); + ResponseAppend_P(PSTR(",\"" D_JSON_IP6_LOCAL "\":\"%s\""), WifiGetIPv6LinkLocalStr().c_str()); #endif // USE_IPV6 } #if defined(ESP32) && CONFIG_IDF_TARGET_ESP32 && defined(USE_ETHERNET) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tasmota.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tasmota.ino index c42f6e003..015d505e7 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tasmota.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_tasmota.ino @@ -215,12 +215,12 @@ extern "C" { int32_t rssi = WiFi.RSSI(); bool show_rssi = false; #ifdef USE_IPV6 - String ipv6_addr = WifiGetIPv6(); + String ipv6_addr = WifiGetIPv6Str(); if (ipv6_addr != "") { be_map_insert_str(vm, "ip6", ipv6_addr.c_str()); show_rssi = true; } - ipv6_addr = WifiGetIPv6LinkLocal(); + ipv6_addr = WifiGetIPv6LinkLocalStr(); if (ipv6_addr != "") { be_map_insert_str(vm, "ip6local", ipv6_addr.c_str()); show_rssi = true; @@ -255,11 +255,11 @@ extern "C" { be_map_insert_str(vm, "ip", IPAddress((uint32_t)EthernetLocalIP()).toString().c_str()); // quick fix for IPAddress bug } #ifdef USE_IPV6 - String ipv6_addr = EthernetGetIPv6(); + String ipv6_addr = EthernetGetIPv6Str(); if (ipv6_addr != "") { be_map_insert_str(vm, "ip6", ipv6_addr.c_str()); } - ipv6_addr = EthernetGetIPv6LinkLocal(); + ipv6_addr = EthernetGetIPv6LinkLocalStr(); if (ipv6_addr != "") { be_map_insert_str(vm, "ip6local", ipv6_addr.c_str()); } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino b/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino index c7f193536..a34c8cf0b 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino @@ -116,9 +116,31 @@ void EthernetEvent(arduino_event_t *event) { } TasmotaGlobal.rules_flag.eth_connected = 1; TasmotaGlobal.global_state.eth_down = 0; - WiFi.saveDNS(); // internal calls to reconnect can zero the DNS servers, save DNS for future use + AddLog(LOG_LEVEL_DEBUG, PSTR("ETH: IPv4 %_I, mask %_I, gateway %_I"), + event->event_info.got_ip.ip_info.ip.addr, + event->event_info.got_ip.ip_info.netmask.addr, + event->event_info.got_ip.ip_info.gw.addr); + WiFi.scrubDNS(); // internal calls to reconnect can zero the DNS servers, save DNS for future use break; +#ifdef USE_IPV6 + case ARDUINO_EVENT_ETH_GOT_IP6: + { + ip_addr_t ip_addr6; + ip_addr_copy_from_ip6(ip_addr6, event->event_info.got_ip6.ip6_info.ip); + IPAddress addr(ip_addr6); + AddLog(LOG_LEVEL_DEBUG, PSTR("%s: IPv6 %s %s"), + event->event_id == ARDUINO_EVENT_ETH_GOT_IP6 ? "ETH" : "WIF", + addr.isLocal() ? PSTR("Local") : PSTR("Global"), addr.toString().c_str()); + if (!addr.isLocal()) { // declare network up on IPv6 + TasmotaGlobal.rules_flag.eth_connected = 1; + TasmotaGlobal.global_state.eth_down = 0; + } + WiFi.scrubDNS(); // internal calls to reconnect can zero the DNS servers, save DNS for future use + } + break; +#endif // USE_IPV6 + case ARDUINO_EVENT_ETH_DISCONNECTED: AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_ETH "Disconnected")); TasmotaGlobal.rules_flag.eth_disconnected = 1; @@ -144,19 +166,6 @@ void EthernetSetIp(void) { Settings->eth_ipv4_address[4]); // IPAddress dns2 } -#ifdef USE_IPV6 -// Returns only IPv6 global address (no loopback and no link-local) -String EthernetGetIPv6(void) -{ - return WifiFindIPv6(false, "en"); -} - -String EthernetGetIPv6LinkLocal(void) -{ - return WifiFindIPv6(true, "en"); -} -#endif // USE_IPV6 - void EthernetInit(void) { if (!Settings->flag4.network_ethernet) { return; } if (!PinUsed(GPIO_ETH_PHY_MDC) && !PinUsed(GPIO_ETH_PHY_MDIO)) { @@ -220,6 +229,40 @@ IPAddress EthernetLocalIP(void) { return ETH.localIP(); } +// Check to see if we have any routable IP address +// IPv4 has always priority +// Copy the value of the IP if pointer provided (optional) +bool EthernetGetIP(IPAddress *ip) { +#ifdef USE_IPV6 + if ((uint32_t)ETH.localIP() != 0) { + if (ip != nullptr) { *ip = ETH.localIP(); } + return true; + } + IPAddress lip; + if (EthernetGetIPv6(&lip)) { + if (ip != nullptr) { *ip = lip; } + return true; + } + *ip = IPAddress(); + return false; +#else + // IPv4 only + if (ip != nullptr) { *ip = ETH.localIP(); } + return (uint32_t)ETH.localIP() != 0; +#endif // USE_IPV6 +} +bool EthernetHasIP(void) { + return EthernetGetIP(nullptr); +} +String EthernetGetIPStr(void) { + IPAddress ip; + if (EthernetGetIP(&ip)) { + return ip.toString(); + } else { + return String(); + } +} + char* EthernetHostname(void) { return eth_hostname; }