From 9abe7b1af92617737b7f8bb0475ea982a141f1e5 Mon Sep 17 00:00:00 2001 From: s-hadinger <49731213+s-hadinger@users.noreply.github.com> Date: Sat, 17 Dec 2022 10:08:35 +0100 Subject: [PATCH] Support for IPv6 DNS records (AAAA) and IPv6 ``Ping`` for ESP32 and ESP8266 (#17417) --- CHANGELOG.md | 1 + lib/default/DnsClient/library.properties | 7 - lib/default/DnsClient/src/DnsClient.cpp | 334 ------------------ lib/default/DnsClient/src/DnsClient.h | 42 --- .../ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp | 140 +++++++- .../ESP32-to-ESP8266-compat/src/ESP8266WiFi.h | 14 +- tasmota/include/tasmota_types.h | 2 +- tasmota/tasmota.ino | 3 - tasmota/tasmota_support/support_command.ino | 1 - tasmota/tasmota_support/support_wifi.ino | 76 ++-- tasmota/tasmota_xdrv_driver/xdrv_38_ping.ino | 91 ++++- .../xdrv_82_esp32_ethernet.ino | 1 + 12 files changed, 280 insertions(+), 432 deletions(-) delete mode 100644 lib/default/DnsClient/library.properties delete mode 100644 lib/default/DnsClient/src/DnsClient.cpp delete mode 100644 lib/default/DnsClient/src/DnsClient.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 378d5f44a..a709ac006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## [12.3.1.1] 20221216 ### Added +- Support for IPv6 DNS records (AAAA) and IPv6 ``Ping`` for ESP32 and ESP8266 ### Breaking Changed diff --git a/lib/default/DnsClient/library.properties b/lib/default/DnsClient/library.properties deleted file mode 100644 index 7ac2d2393..000000000 --- a/lib/default/DnsClient/library.properties +++ /dev/null @@ -1,7 +0,0 @@ -name=DnsClient -version=1.0 -author=MCQN Ltd, Theo Arends -maintainer=Theo -sentence=Dns client allowing timeout selection. -paragraph=This class uses WifiUdp. -architectures=esp8266,esp32 diff --git a/lib/default/DnsClient/src/DnsClient.cpp b/lib/default/DnsClient/src/DnsClient.cpp deleted file mode 100644 index 19bc4444d..000000000 --- a/lib/default/DnsClient/src/DnsClient.cpp +++ /dev/null @@ -1,334 +0,0 @@ -/* - DnsClient.cpp - DNS client for Arduino - - SPDX-FileCopyrightText: 2009-2010 MCQN Ltd. and Theo Arends - - SPDX-License-Identifier: GPL-3.0-only -*/ - -// Arduino DNS client for WizNet5100-based Ethernet shield -// (c) Copyright 2009-2010 MCQN Ltd. -// Released under Apache License, version 2.0 - -#include "DnsClient.h" - -// Various flags and header field values for a DNS message -#define UDP_HEADER_SIZE 8 -#define DNS_HEADER_SIZE 12 -#define TTL_SIZE 4 -#define QUERY_FLAG (0) -#define RESPONSE_FLAG (1<<15) -#define QUERY_RESPONSE_MASK (1<<15) -#define OPCODE_STANDARD_QUERY (0) -#define OPCODE_INVERSE_QUERY (1<<11) -#define OPCODE_STATUS_REQUEST (2<<11) -#define OPCODE_MASK (15<<11) -#define AUTHORITATIVE_FLAG (1<<10) -#define TRUNCATION_FLAG (1<<9) -#define RECURSION_DESIRED_FLAG (1<<8) -#define RECURSION_AVAILABLE_FLAG (1<<7) -#define RESP_NO_ERROR (0) -#define RESP_FORMAT_ERROR (1) -#define RESP_SERVER_FAILURE (2) -#define RESP_NAME_ERROR (3) -#define RESP_NOT_IMPLEMENTED (4) -#define RESP_REFUSED (5) -#define RESP_MASK (15) -#define TYPE_A (0x0001) -#define CLASS_IN (0x0001) -#define LABEL_COMPRESSION_MASK (0xC0) -// Port number that DNS servers listen on -#define DNS_PORT 53 - -// Possible return codes from ProcessResponse -#define SUCCESS 1 -#define TIMED_OUT -1 -#define INVALID_SERVER -2 -#define TRUNCATED -3 -#define INVALID_RESPONSE -4 - -#ifndef htons -#define htons(x) ( ((x)<< 8 & 0xFF00) | ((x)>> 8 & 0x00FF) ) -#endif - -void DNSClient::begin(const IPAddress& aDNSServer) { - iDNSServer = aDNSServer; - iRequestId = 0; -} - -void DNSClient::setTimeout(uint32_t aTimeout) { - iTimeout = aTimeout; -} - -int DNSClient::getHostByName(const char* aHostname, IPAddress& aResult) { - // See if it's a numeric IP address - if (aResult.fromString(aHostname)) { - // It is, our work here is done - return SUCCESS; - } - - // Check we've got a valid DNS server to use - if ((0xFFFFFFFF == (uint32_t)iDNSServer) || (0 == (uint32_t)iDNSServer)) { - return INVALID_SERVER; - } - - int ret = 0; - // Find a socket to use - if (iUdp.begin(1024+(millis() & 0xF)) == 1) { - // Try up to three times - int retries = 0; -// while ((retries < 3) && (ret <= 0)) { - // Send DNS request - ret = iUdp.beginPacket(iDNSServer, DNS_PORT); - if (ret != 0) { - // Now output the request data - ret = BuildRequest(aHostname); - if (ret != 0) { - // And finally send the request - ret = iUdp.endPacket(); - if (ret != 0) { - // Now wait for a response - int wait_retries = 0; - ret = TIMED_OUT; - while ((wait_retries < 3) && (ret == TIMED_OUT)) { - ret = ProcessResponse(iTimeout, aResult); - wait_retries++; - } - } - } - } - retries++; -// } - // We're done with the socket now - iUdp.stop(); - } - return ret; -} - -int DNSClient::BuildRequest(const char* aName) { - // Build header - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // | ID | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // |QR| Opcode |AA|TC|RD|RA| Z | RCODE | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // | QDCOUNT | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // | ANCOUNT | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // | NSCOUNT | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // | ARCOUNT | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // As we only support one request at a time at present, we can simplify - // some of this header - iRequestId = millis(); // generate a random ID - uint16_t twoByteBuffer; - - // FIXME We should also check that there's enough space available to write to, rather - // FIXME than assume there's enough space (as the code does at present) - iUdp.write((uint8_t*)&iRequestId, sizeof(iRequestId)); - - twoByteBuffer = htons(QUERY_FLAG | OPCODE_STANDARD_QUERY | RECURSION_DESIRED_FLAG); - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - - twoByteBuffer = htons(1); // One question record - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - - twoByteBuffer = 0; // Zero answer records - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - // and zero additional records - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - - // Build question - const char* start =aName; - const char* end =start; - uint8_t len; - // Run through the name being requested - while (*end) { - // Find out how long this section of the name is - end = start; - while (*end && (*end != '.') ) { - end++; - } - - if (end-start > 0) { - // Write out the size of this section - len = end-start; - iUdp.write(&len, sizeof(len)); - // And then write out the section - iUdp.write((uint8_t*)start, end-start); - } - start = end+1; - } - - // We've got to the end of the question name, so terminate it with a zero-length section - len = 0; - iUdp.write(&len, sizeof(len)); - // Finally the type and class of question - twoByteBuffer = htons(TYPE_A); - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - - twoByteBuffer = htons(CLASS_IN); // Internet class of question - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - // Success! Everything buffered okay - return SUCCESS; -} - -int DNSClient::ProcessResponse(uint32_t aTimeout, IPAddress& aAddress) { - uint32_t startTime = millis(); - - // Wait for a response packet - while(iUdp.parsePacket() <= 0) { - if ((millis() - startTime) > aTimeout) { - return TIMED_OUT; - } - delay(20); - } - - // We've had a reply! - // Read the UDP header - uint8_t header[DNS_HEADER_SIZE]; // Enough space to reuse for the DNS header - // Check that it's a response from the right server and the right port - if ( (iDNSServer != iUdp.remoteIP()) || (iUdp.remotePort() != DNS_PORT) ) { - // It's not from who we expected - return INVALID_SERVER; - } - - // Read through the rest of the response - if (iUdp.available() < DNS_HEADER_SIZE) { - return TRUNCATED; - } - iUdp.read(header, DNS_HEADER_SIZE); - - uint16_t staging; // Staging used to avoid type-punning warnings - memcpy(&staging, &header[2], sizeof(uint16_t)); - uint16_t header_flags = htons(staging); - memcpy(&staging, &header[0], sizeof(uint16_t)); - // Check that it's a response to this request - if ( (iRequestId != staging) || ((header_flags & QUERY_RESPONSE_MASK) != (uint16_t)RESPONSE_FLAG) ) { - // Mark the entire packet as read - iUdp.flush(); - return INVALID_RESPONSE; - } - // Check for any errors in the response (or in our request) - // although we don't do anything to get round these - if ( (header_flags & TRUNCATION_FLAG) || (header_flags & RESP_MASK) ) { - // Mark the entire packet as read - iUdp.flush(); - return -5; // INVALID_RESPONSE; - } - - // And make sure we've got (at least) one answer - memcpy(&staging, &header[6], sizeof(uint16_t)); - uint16_t answerCount = htons(staging); - if (answerCount == 0 ) { - // Mark the entire packet as read - iUdp.flush(); - return -6; // INVALID_RESPONSE; - } - - // Skip over any questions - memcpy(&staging, &header[4], sizeof(uint16_t)); - for (uint32_t i = 0; i < htons(staging); i++) { - // Skip over the name - uint8_t len; - do { - iUdp.read(&len, sizeof(len)); - if (len > 0) { - // Don't need to actually read the data out for the string, just - // advance ptr to beyond it - while(len--) { - iUdp.read(); // we don't care about the returned byte - } - } - } while (len != 0); - - // Now jump over the type and class - for (uint32_t i = 0; i < 4; i++) { - iUdp.read(); // we don't care about the returned byte - } - } - - // Now we're up to the bit we're interested in, the answer - // There might be more than one answer (although we'll just use the first - // type A answer) and some authority and additional resource records but - // we're going to ignore all of them. - - for (uint32_t i = 0; i < answerCount; i++) { - // Skip the name - uint8_t len; - do { - iUdp.read(&len, sizeof(len)); - if ((len & LABEL_COMPRESSION_MASK) == 0) { - // It's just a normal label - if (len > 0) { - // And it's got a length - // Don't need to actually read the data out for the string, - // just advance ptr to beyond it - while(len--) { - iUdp.read(); // we don't care about the returned byte - } - } - } else { - // This is a pointer to a somewhere else in the message for the - // rest of the name. We don't care about the name, and RFC1035 - // says that a name is either a sequence of labels ended with a - // 0 length octet or a pointer or a sequence of labels ending in - // a pointer. Either way, when we get here we're at the end of - // the name - // Skip over the pointer - iUdp.read(); // we don't care about the returned byte - // And set len so that we drop out of the name loop - len = 0; - } - } while (len != 0); - - // Check the type and class - uint16_t answerType; - uint16_t answerClass; - iUdp.read((uint8_t*)&answerType, sizeof(answerType)); - iUdp.read((uint8_t*)&answerClass, sizeof(answerClass)); - - // Ignore the Time-To-Live as we don't do any caching - for (uint32_t i = 0; i < TTL_SIZE; i++) { - iUdp.read(); // We don't care about the returned byte - } - - // And read out the length of this answer - // Don't need header_flags anymore, so we can reuse it here - iUdp.read((uint8_t*)&header_flags, sizeof(header_flags)); - - if ( (htons(answerType) == TYPE_A) && (htons(answerClass) == CLASS_IN) ) { - if (htons(header_flags) != 4) { - // It's a weird size - // Mark the entire packet as read - iUdp.flush(); - return -9; // INVALID_RESPONSE; - } - iUdp.read(aAddress.raw_address(), 4); -// uint32_t address; -// iUdp.read((uint8_t*)&address, sizeof(address)); -// aAddress = (IPAddress)address; - - // Check we've got a valid address - if ((0xFFFFFFFF != (uint32_t)aAddress) && (0 != (uint32_t)aAddress)) { - return SUCCESS; - } - } else { - // This isn't an answer type we're after, move onto the next one - for (uint32_t i = 0; i < htons(header_flags); i++) { - iUdp.read(); // we don't care about the returned byte - } - } - } - - // Mark the entire packet as read - iUdp.flush(); - - // If we get here then we haven't found an answer - return -10; // INVALID_RESPONSE; -} diff --git a/lib/default/DnsClient/src/DnsClient.h b/lib/default/DnsClient/src/DnsClient.h deleted file mode 100644 index 42f29b190..000000000 --- a/lib/default/DnsClient/src/DnsClient.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - DnsClient.h - DNS client for Arduino - - SPDX-FileCopyrightText: 2009-2010 MCQN Ltd. and Theo Arends - - SPDX-License-Identifier: GPL-3.0-only -*/ - -// Arduino DNS client for WizNet5100-based Ethernet shield -// (c) Copyright 2009-2010 MCQN Ltd. -// Released under Apache License, version 2.0 - -#ifndef DNSClient_h -#define DNSClient_h - -#include -#include -#include - -class DNSClient { -public: - void begin(const IPAddress& aDNSServer); - void setTimeout(uint32_t aTimeout = 1000); - - /* Resolve the given hostname to an IP address. - @param aHostname Name to be resolved - @param aResult IPAddress structure to store the returned IP address - @result 1 if aIPAddrString was successfully converted to an IP address, else error code - */ - int getHostByName(const char* aHostname, IPAddress& aResult); - -protected: - int BuildRequest(const char* aName); - int ProcessResponse(uint32_t aTimeout, IPAddress& aAddress); - - IPAddress iDNSServer; - uint16_t iRequestId; - uint16_t iTimeout = 1000; - WiFiUDP iUdp; -}; - -#endif diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp index 43ec90479..030ec6321 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp @@ -28,6 +28,55 @@ #undef WiFi #endif +#include "tasmota_options.h" +#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(); + 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(); + return ret; +} + +wl_status_t WiFiClass32::begin(const char* ssid, const char *passphrase, int32_t channel, const uint8_t* bssid, bool connect) { + saveDNS(); + wl_status_t ret = WiFiClass::begin(ssid, passphrase, channel, bssid, connect); + restoreDNS(); + return ret; +} + +wl_status_t WiFiClass32::begin(char* ssid, char *passphrase, int32_t channel, const uint8_t* bssid, bool connect) { + saveDNS(); + wl_status_t ret = WiFiClass::begin(ssid, passphrase, channel, bssid, connect); + restoreDNS(); + return ret; +} +wl_status_t WiFiClass32::begin() { + saveDNS(); + wl_status_t ret = WiFiClass::begin(); + restoreDNS(); + return ret; +} + +void WiFiClass32::saveDNS(void) { + // save the DNS servers + for (uint32_t i=0; i", IPAddress(ipaddr).toString().c_str(), (int) callback_arg, ip_addr_counter); + uint32_t cb_counter = (uint32_t) callback_arg; + if (cb_counter != ip_addr_counter) { return; } // the response is from a previous request, ignore + + if (ipaddr != nullptr) { + dns_ipaddr = *ipaddr; + } else { + dns_ipaddr = *IP4_ADDR_ANY; // set to IPv4 0.0.0.0 + } + WiFiClass32::dnsDone(); + // AddLog(LOG_LEVEL_DEBUG, "WIF: dns_found=%s", ipaddr ? IPAddress(*ipaddr).toString().c_str() : ""); +} +// We need this helper method to access protected methods from WiFiGeneric +void WiFiClass32::dnsDone(void) { + setStatusBits(WIFI_DNS_DONE_BIT); +} + +/** + * Resolve the given hostname to an IP address. + * @param aHostname Name to be resolved + * @param aResult IPAddress structure to store the returned IP address + * @return 1 if aIPAddrString was successfully converted to an IP address, + * else error code + */ +int WiFiClass32::hostByName(const char* aHostname, IPAddress& aResult, int32_t timer_ms) +{ + ip_addr_t addr; + 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 + + 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; +#ifdef USE_IPV6 + v4v6priority = WifiDNSGetIPv6Priority() ? LWIP_DNS_ADDRTYPE_IPV6_IPV4 : LWIP_DNS_ADDRTYPE_IPV4_IPV6; +#endif // USE_IPV6 + err_t err = dns_gethostbyname_addrtype(aHostname, &dns_ipaddr, &wifi32_dns_found_callback, (void*) ip_addr_counter, v4v6priority); + // Serial.printf("DNS: dns_gethostbyname_addrtype errg=%i counter=%i\n", err, ip_addr_counter); + if(err == ERR_OK && !ip_addr_isany_val(dns_ipaddr)) { +#ifdef USE_IPV6 + aResult = dns_ipaddr; +#else // USE_IPV6 + aResult = ip_addr_get_ip4_u32(&dns_ipaddr); +#endif // USE_IPV6 + } else if(err == ERR_INPROGRESS) { + waitStatusBits(WIFI_DNS_DONE_BIT, timer_ms); //real internal timeout in lwip library is 14[s] + clearStatusBits(WIFI_DNS_DONE_BIT); + } + + if (!ip_addr_isany_val(dns_ipaddr)) { +#ifdef USE_IPV6 + aResult = dns_ipaddr; +#else // USE_IPV6 + aResult = ip_addr_get_ip4_u32(&dns_ipaddr); +#endif // USE_IPV6 + return true; + } + return false; +} + +int WiFiClass32::hostByName(const char* aHostname, IPAddress& aResult) +{ + return hostByName(aHostname, aResult, WifiDNSGetTimeout()); } void wifi_station_disconnect() { diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h index 1c1630b4c..d0274733d 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h @@ -37,6 +37,11 @@ typedef enum WiFiPhyMode class WiFiClass32 : public WiFiClass { public: + wl_status_t begin(const char* wpa2_ssid, wpa2_auth_method_t method, const char* wpa2_identity=NULL, const char* wpa2_username=NULL, const char *wpa2_password=NULL, const char* ca_pem=NULL, const char* client_crt=NULL, const char* client_key=NULL, int32_t channel=0, const uint8_t* bssid=0, bool connect=true); + wl_status_t begin(const char* ssid, const char *passphrase = NULL, int32_t channel = 0, const uint8_t* bssid = NULL, bool connect = true); + wl_status_t begin(char* ssid, char *passphrase = NULL, int32_t channel = 0, const uint8_t* bssid = NULL, bool connect = true); + wl_status_t begin(); + static void hostname(const char* aHostname) { WiFi.setHostname(aHostname); @@ -51,7 +56,14 @@ public: static void forceSleepWake(); static bool getNetworkInfo(uint8_t i, String &ssid, uint8_t &encType, int32_t &rssi, uint8_t* &bssid, int32_t &channel, bool &hidden_scan); - bool IPv6(bool state); // make sure it always exists even with older Arduino framework + static void dnsDone(void); // used by the callback to stop the dns timer + int hostByName(const char* aHostname, IPAddress& aResult, int32_t timer_ms); + int hostByName(const char* aHostname, IPAddress& aResult); + + void saveDNS(void); + void restoreDNS(void); +protected: + ip_addr_t dns_save[DNS_MAX_SERVERS] = {}; }; void wifi_station_disconnect(); diff --git a/tasmota/include/tasmota_types.h b/tasmota/include/tasmota_types.h index 4ee133e04..5a268a666 100644 --- a/tasmota/include/tasmota_types.h +++ b/tasmota/include/tasmota_types.h @@ -182,7 +182,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t use_esp32_temperature : 1; // bit 0 (v12.1.1.1) - SetOption146 - (ESP32) Show ESP32 internal temperature sensor uint32_t mqtt_disable_sserialrec : 1; // bit 1 (v12.1.1.2) - SetOption147 - (MQTT) Disable publish SSerialReceived MQTT messages, you must use event trigger rules instead. uint32_t artnet_autorun : 1; // bit 2 (v12.2.0.4) - SetOption148 - (Light) start DMX ArtNet at boot, listen to UDP port as soon as network is up - uint32_t spare03 : 1; // bit 3 + uint32_t dns_ipv6_priority : 1; // bit 3 (v12.2.0.6) - SetOption149 - (Wifi) prefer IPv6 DNS resolution to IPv4 address when available. Requires `#define USE_IPV6` uint32_t spare04 : 1; // bit 4 uint32_t spare05 : 1; // bit 5 uint32_t spare06 : 1; // bit 6 diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 479dce33a..67bf25cd5 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -42,7 +42,6 @@ // Libraries #include // Ota #include // Ota -#include // Any getHostByName #ifdef ESP32 #ifdef USE_TLS #include "HTTPUpdateLight.h" // Ota over HTTPS for ESP32 @@ -193,7 +192,6 @@ struct XDRVMAILBOX { char *command; } XdrvMailbox; -DNSClient DnsClient; WiFiUDP PortUdp; // UDP Syslog and Alexa #ifdef ESP32 @@ -634,7 +632,6 @@ void setup(void) { TasmotaGlobal.init_state = INIT_GPIOS; SetPowerOnState(); - DnsClient.setTimeout(Settings->dns_timeout); WifiConnect(); AddLog(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s - %s " D_VERSION " %s%s-" ARDUINO_CORE_RELEASE "(%s)"), diff --git a/tasmota/tasmota_support/support_command.ino b/tasmota/tasmota_support/support_command.ino index 07fba6fe4..ca5ec268a 100644 --- a/tasmota/tasmota_support/support_command.ino +++ b/tasmota/tasmota_support/support_command.ino @@ -2516,7 +2516,6 @@ void CmndDnsTimeout(void) { // Set timeout between 100 and 20000 mSec if ((XdrvMailbox.payload >= 100) && (XdrvMailbox.payload <= 20000)) { Settings->dns_timeout = XdrvMailbox.payload; - DnsClient.setTimeout(Settings->dns_timeout); } ResponseCmndNumber(Settings->dns_timeout); } diff --git a/tasmota/tasmota_support/support_wifi.ino b/tasmota/tasmota_support/support_wifi.ino index cc03a8fde..4d8867573 100644 --- a/tasmota/tasmota_support/support_wifi.ino +++ b/tasmota/tasmota_support/support_wifi.ino @@ -500,6 +500,7 @@ void CreateLinkLocalIPv6(void) } // +#include "lwip/dns.h" void WifiDumpAddressesIPv6(void) { for (netif* intf = netif_list; intf != nullptr; intf = intf->next) { @@ -511,6 +512,8 @@ void WifiDumpAddressesIPv6(void) 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()); } #endif // USE_IPV6 @@ -890,37 +893,56 @@ void wifiKeepAlive(void) { } #endif // ESP8266 +// expose a function to be called by WiFi32 +int32_t WifiDNSGetTimeout(void) { + return Settings->dns_timeout; +} +// read Settings for DNS IPv6 priority +bool WifiDNSGetIPv6Priority(void) { +#ifdef USE_IPV6 + // we prioritize IPv6 only if a global IPv6 address is available, otherwise revert to IPv4 if we have one as well + // 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 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) { + v6prio = false; // revert to IPv4 first + } + + // any change of state requires a dns cache clear + if (had_v6prio != v6prio) { + dns_clear_cache(); + had_v6prio = v6prio; + } + + return v6prio; +#endif // USE_IPV6 + return false; +} + bool WifiHostByName(const char* aHostname, IPAddress& aResult) { -#ifdef ESP8266 - if (WiFi.hostByName(aHostname, aResult, Settings->dns_timeout)) { + uint32_t dns_start = millis(); + bool success = WiFi.hostByName(aHostname, aResult, Settings->dns_timeout); + uint32_t dns_end = millis(); + if (success) { // Host name resolved if (0xFFFFFFFF != (uint32_t)aResult) { + AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI "DNS resolved '%s' (%s) in %i ms"), aHostname, aResult.toString().c_str(), dns_end - dns_start); return true; } } -#else - // DnsClient can't do one-shot mDNS queries so use WiFi.hostByName() for *.local - aResult = (uint32_t) 0x00000000L; // indirectly force to be IPv4, since the client touches the binary format later - size_t hostname_len = strlen(aHostname); - if (strstr_P(aHostname, PSTR(".local")) == &aHostname[hostname_len] - 6) { - if (WiFi.hostByName(aHostname, aResult)) { - // Host name resolved - if (0xFFFFFFFF != (uint32_t)aResult) { - AddLog(LOG_LEVEL_DEBUG, "WIF: Resolving '%s' (%s)", aHostname, aResult.toString().c_str()); - return true; - } - } - } else { - // Use this instead of WiFi.hostByName or connect(host_name,.. to block less if DNS server is not found - uint32_t dns_address = (!TasmotaGlobal.global_state.eth_down) ? Settings->eth_ipv4_address[3] : Settings->ipv4_address[3]; - DnsClient.begin((IPAddress)dns_address); - if (1 == DnsClient.getHostByName(aHostname, aResult)) { - AddLog(LOG_LEVEL_DEBUG, "WIF: Resolving '%s' (%s)", aHostname, aResult.toString().c_str()); - return true; - } - } -#endif - AddLog(LOG_LEVEL_DEBUG, PSTR("DNS: Unable to resolve '%s'"), aHostname); + AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI "DNS failed for %s after %i ms"), aHostname, dns_end - dns_start); return false; } @@ -1091,6 +1113,7 @@ 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 @@ -1104,7 +1127,7 @@ void WifiEvents(arduino_event_t *event) { 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; @@ -1114,6 +1137,7 @@ 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; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_38_ping.ino b/tasmota/tasmota_xdrv_driver/xdrv_38_ping.ino index de9cfb61f..ba7914c5f 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_38_ping.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_38_ping.ino @@ -64,6 +64,9 @@ extern "C" { // globals Ping_t *ping_head = nullptr; // head of the Linked List for ping objects struct raw_pcb *t_ping_pcb = nullptr; // registered with first ping, deregistered after last ping, the same pcb is used for all packets +#ifdef USE_IPV6 + struct raw_pcb *t_ping6_pcb = nullptr; // IPv6 version, registered with first ping, deregistered after last ping, the same pcb is used for all packets +#endif // USE_IPV6 // ================================================================================ // Find the Ping object indexed by IP address @@ -75,7 +78,6 @@ extern "C" { Ping_t *ping = ping_head; while (ping != nullptr) { if (ip_addr_cmp(&ping->ip, ip)) { - // if (ping->ip == ip) { return ping; } ping = ping->next; @@ -119,6 +121,31 @@ extern "C" { iecho->chksum = inet_chksum(iecho, len); } + +#ifdef USE_IPV6 + // Prepare a echo ICMP6 request + // + void t_ping_prepare_echo6(struct icmp6_echo_hdr *iecho6, uint16_t len, Ping_t *ping) { + // TODO + size_t data_len = len - sizeof(struct icmp6_echo_hdr); + + iecho6->type = ICMP6_TYPE_EREQ; + iecho6->code = 0; + iecho6->chksum = 0; + iecho6->id = Ping_ID; + ping->seq_num++; + if (ping->seq_num == 0x7fff) { ping->seq_num = 0; } + + iecho6->seqno = htons(ping->seq_num); + + /* fill the additional data buffer with some data */ + for (uint32_t i = 0; i < data_len; i++) { + ((char*)iecho6)[sizeof(struct icmp6_echo_hdr) + i] = (char)i; + } + // checksum is calculated by lwip + } +#endif // USE_IPV6 + // // send the ICMP packet // @@ -130,11 +157,31 @@ extern "C" { p = pbuf_alloc(PBUF_IP, ping_size, PBUF_RAM); if (!p) { return; } if ((p->len == p->tot_len) && (p->next == nullptr)) { - struct icmp_echo_hdr *iecho; - iecho = (struct icmp_echo_hdr *) p->payload; - t_ping_prepare_echo(iecho, ping_size, ping); - raw_sendto(raw, p, &ping->ip); +#ifdef USE_IPV6 + // different format for IPv4 and IPv6 packets + if (IP_IS_V6_VAL(ping->ip)) { + // IPv6 + struct icmp6_echo_hdr *iecho6; + iecho6 = (struct icmp6_echo_hdr *) p->payload; + t_ping_prepare_echo6(iecho6, ping_size, ping); + // set parameters for checksum handling + t_ping6_pcb->chksum_reqd = 1; + t_ping6_pcb->chksum_offset = offsetof(icmp6_echo_hdr, chksum); + + // AddLog(LOG_LEVEL_DEBUG, "PNG: sending ICMP6(%i-%i)=%*_H", p->len, ping_size, p->len, p->payload); + raw_sendto(t_ping6_pcb, p, &ping->ip); + // AddLog(LOG_LEVEL_DEBUG, "PNG: sending ICMP6(%i-%i)=%*_H", p->len, ping_size, p->len, p->payload); + } else +#endif // USE_IPV6 + { + // IPv4 + struct icmp_echo_hdr *iecho; + iecho = (struct icmp_echo_hdr *) p->payload; + t_ping_prepare_echo(iecho, ping_size, ping); + raw_sendto(t_ping_pcb, p, &ping->ip); + // AddLog(LOG_LEVEL_DEBUG, "PNG: sending ICMP4(%i-%i)=%*_H", p->len, ping_size, p->len, p->payload); + } } pbuf_free(p); } @@ -148,7 +195,7 @@ extern "C" { if (ping->to_send_count > 0) { ping->to_send_count--; // have we sent all packets? - t_ping_send(t_ping_pcb, ping); + t_ping_send(t_ping_pcb, ping); // ICMP can also send ICMP6 sys_timeout(Ping_timeout_ms, t_ping_timeout, ping); sys_timeout(Ping_coarse, t_ping_coarse_tmr, ping); @@ -165,17 +212,31 @@ extern "C" { // Reveived packet // static uint8_t ICACHE_FLASH_ATTR t_ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr) { + // AddLog(LOG_LEVEL_DEBUG, "PNG: from %s pub(%i)=%*_H", IPAddress(*addr).toString().c_str(), p->len, p->len, p->payload); Ping_t *ping = t_ping_find(addr); if (nullptr == ping) { // unknown source address return 0; // don't eat the packet and ignore it } - if (pbuf_header( p, -PBUF_TRANSPORT_HLEN)==0) { + size_t pbuf_header_len = PBUF_TRANSPORT_HLEN; + bool ipv6 = false; +#ifdef USE_IPV6 + if (pcb == t_ping6_pcb) { + pbuf_header_len = PBUF_IP_HLEN; + ipv6 = true; + } +#endif // USE_IPV6 + if (pbuf_header(p, -pbuf_header_len)==0) { + // AddLog(LOG_LEVEL_DEBUG, "PNG: received(%i)=%*_H", p->len, p->len, p->payload); struct icmp_echo_hdr *iecho; iecho = (struct icmp_echo_hdr *)p->payload; - if ((iecho->id == Ping_ID) && (iecho->seqno == htons(ping->seq_num)) && iecho->type == ICMP_ER) { + uint8_t icmp_resp_type = ICMP_ER; +#ifdef USE_IPV6 + icmp_resp_type = (ipv6 ? ICMP6_TYPE_EREP : ICMP_ER); +#endif // USE_IPV6 + if ((iecho->id == Ping_ID) && (iecho->seqno == htons(ping->seq_num)) && iecho->type == icmp_resp_type) { if (iecho->seqno != ping->seqno){ // debounce already received packet /* do some ping result processing */ @@ -212,8 +273,16 @@ extern "C" { t_ping_pcb = raw_new(IP_PROTO_ICMP); raw_recv(t_ping_pcb, t_ping_recv, nullptr); // we cannot register data structure here as we can only register one - raw_bind(t_ping_pcb, IP_ADDR_ANY); + raw_bind(t_ping_pcb, IP4_ADDR_ANY); } +#ifdef USE_IPV6 + if (nullptr == t_ping6_pcb) { + t_ping6_pcb = raw_new(IP6_NEXTH_ICMP6); + + raw_recv(t_ping6_pcb, t_ping_recv, nullptr); // we cannot register data structure here as we can only register one + raw_bind(t_ping6_pcb, IP6_ADDR_ANY); + } +#endif // USE_IPV6 } // we have finsihed a ping series, deallocated if no more ongoing @@ -221,6 +290,10 @@ extern "C" { if (nullptr == ping_head) { // deregister only if no ping is flying raw_remove(t_ping_pcb); t_ping_pcb = nullptr; +#ifdef USE_IPV6 + raw_remove(t_ping6_pcb); + t_ping6_pcb = nullptr; +#endif // USE_IPV6 } } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino b/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino index 406d3f94f..c7f193536 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_82_esp32_ethernet.ino @@ -116,6 +116,7 @@ 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 break; case ARDUINO_EVENT_ETH_DISCONNECTED: