From 2a668d2e19607769f41ffb6ce281a7919d4b7f17 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Tue, 23 Aug 2022 20:45:24 +1000 Subject: [PATCH 01/12] Update header --- cores/esp32/IPAddress.h | 49 ++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/cores/esp32/IPAddress.h b/cores/esp32/IPAddress.h index 3bedd4f8749..2f38b96b1ad 100644 --- a/cores/esp32/IPAddress.h +++ b/cores/esp32/IPAddress.h @@ -26,13 +26,23 @@ // A class to make it easier to handle and pass around IP addresses +#define IPADDRESS_V4_BYTES_INDEX 12 +#define IPADDRESS_V4_DWORD_INDEX 3 + +enum IPType +{ + IPv4, + IPv6 +}; + class IPAddress: public Printable { private: union { - uint8_t bytes[4]; // IPv4 address - uint32_t dword; + uint8_t bytes[16]; + uint32_t dword[4]; } _address; + IPType _type; // Access the raw byte array containing the address. Because this returns a pointer // to the internal structure rather than a copy of the address this function should only @@ -40,41 +50,37 @@ class IPAddress: public Printable // stored. uint8_t* raw_address() { - return _address.bytes; + return _type == IPv4 ? &_address.bytes[IPADDRESS_V4_BYTES_INDEX] : _address.bytes; } public: // Constructors IPAddress(); + IPAddress(IPType ip_type); IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet); + IPAddress(uint8_t o1, uint8_t o2, uint8_t o3, uint8_t o4, uint8_t o5, uint8_t o6, uint8_t o7, uint8_t o8, uint8_t o9, uint8_t o10, uint8_t o11, uint8_t o12, uint8_t o13, uint8_t o14, uint8_t o15, uint8_t o16); IPAddress(uint32_t address); IPAddress(const uint8_t *address); + IPAddress(IPType ip_type, const uint8_t *address); virtual ~IPAddress() {} bool fromString(const char *address); bool fromString(const String &address) { return fromString(address.c_str()); } - // Overloaded cast operator to allow IPAddress objects to be used where a pointer - // to a four-byte uint8_t array is expected + // Overloaded cast operator to allow IPAddress objects to be used where a + // uint32_t is expected operator uint32_t() const { - return _address.dword; - } - bool operator==(const IPAddress& addr) const - { - return _address.dword == addr._address.dword; + return _type == IPv4 ? _address.dword[IPADDRESS_V4_DWORD_INDEX] : 0; } + + bool operator==(const IPAddress& addr) const; + bool operator!=(const IPAddress& addr) const { return !(*this == addr); }; bool operator==(const uint8_t* addr) const; // Overloaded index operator to allow getting and setting individual octets of the address - uint8_t operator[](int index) const - { - return _address.bytes[index]; - } - uint8_t& operator[](int index) - { - return _address.bytes[index]; - } + uint8_t operator[](int index) const; + uint8_t& operator[](int index); // Overloaded copy operators to allow initialisation of IPAddress objects from other types IPAddress& operator=(const uint8_t *address); @@ -83,14 +89,21 @@ class IPAddress: public Printable virtual size_t printTo(Print& p) const; String toString() const; + IPType type() { return _type; } + friend class EthernetClass; friend class UDP; friend class Client; friend class Server; friend class DhcpClass; friend class DNSClient; + +protected: + bool fromString4(const char *address); + bool fromString6(const char *address); }; // changed to extern because const declaration creates copies in BSS of INADDR_NONE for each CPP unit that includes it extern IPAddress INADDR_NONE; +extern IPAddress IN6ADDR_ANY; #endif From f93c766cafee8bc733f75cd253e20eadbd1d4a7c Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Tue, 23 Aug 2022 21:01:41 +1000 Subject: [PATCH 02/12] Update IPAddress implementation to support V6 --- cores/esp32/IPAddress.cpp | 265 +++++++++++++++++++++++++++++++++++--- 1 file changed, 244 insertions(+), 21 deletions(-) diff --git a/cores/esp32/IPAddress.cpp b/cores/esp32/IPAddress.cpp index 0575363f254..6192a293cc3 100644 --- a/cores/esp32/IPAddress.cpp +++ b/cores/esp32/IPAddress.cpp @@ -21,77 +21,223 @@ #include #include -IPAddress::IPAddress() +IPAddress::IPAddress() : IPAddress(IPv4) {} + +IPAddress::IPAddress(IPType ip_type) { - _address.dword = 0; + _type = ip_type; + memset(_address.bytes, 0, sizeof(_address.bytes)); } IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) { - _address.bytes[0] = first_octet; - _address.bytes[1] = second_octet; - _address.bytes[2] = third_octet; - _address.bytes[3] = fourth_octet; + _type = IPv4; + memset(_address.bytes, 0, sizeof(_address.bytes)); + _address.bytes[IPADDRESS_V4_BYTES_INDEX] = first_octet; + _address.bytes[IPADDRESS_V4_BYTES_INDEX + 1] = second_octet; + _address.bytes[IPADDRESS_V4_BYTES_INDEX + 2] = third_octet; + _address.bytes[IPADDRESS_V4_BYTES_INDEX + 3] = fourth_octet; +} + +IPAddress::IPAddress(uint8_t o1, uint8_t o2, uint8_t o3, uint8_t o4, uint8_t o5, uint8_t o6, uint8_t o7, uint8_t o8, uint8_t o9, uint8_t o10, uint8_t o11, uint8_t o12, uint8_t o13, uint8_t o14, uint8_t o15, uint8_t o16) { + _type = IPv6; + _address.bytes[0] = o1; + _address.bytes[1] = o2; + _address.bytes[2] = o3; + _address.bytes[3] = o4; + _address.bytes[4] = o5; + _address.bytes[5] = o6; + _address.bytes[6] = o7; + _address.bytes[7] = o8; + _address.bytes[8] = o9; + _address.bytes[9] = o10; + _address.bytes[10] = o11; + _address.bytes[11] = o12; + _address.bytes[12] = o13; + _address.bytes[13] = o14; + _address.bytes[14] = o15; + _address.bytes[15] = o16; } IPAddress::IPAddress(uint32_t address) { - _address.dword = address; + // IPv4 only + _type = IPv4; + memset(_address.bytes, 0, sizeof(_address.bytes)); + _address.dword[IPADDRESS_V4_DWORD_INDEX] = address; + + // NOTE on conversion/comparison and uint32_t: + // These conversions are host platform dependent. + // There is a defined integer representation of IPv4 addresses, + // based on network byte order (will be the value on big endian systems), + // e.g. http://2398766798 is the same as http://142.250.70.206, + // However on little endian systems the octets 0x83, 0xFA, 0x46, 0xCE, + // in that order, will form the integer (uint32_t) 3460758158 . } -IPAddress::IPAddress(const uint8_t *address) +IPAddress::IPAddress(const uint8_t *address) : IPAddress(IPv4, address) {} + +IPAddress::IPAddress(IPType ip_type, const uint8_t *address) { - memcpy(_address.bytes, address, sizeof(_address.bytes)); + _type = ip_type; + if (ip_type == IPv4) { + memset(_address.bytes, 0, sizeof(_address.bytes)); + memcpy(&_address.bytes[IPADDRESS_V4_BYTES_INDEX], address, sizeof(uint32_t)); + } else { + memcpy(_address.bytes, address, sizeof(_address.bytes)); + } } IPAddress& IPAddress::operator=(const uint8_t *address) { - memcpy(_address.bytes, address, sizeof(_address.bytes)); + // IPv4 only conversion from byte pointer + _type = IPv4; + memset(_address.bytes, 0, sizeof(_address.bytes)); + memcpy(&_address.bytes[IPADDRESS_V4_BYTES_INDEX], address, sizeof(uint32_t)); return *this; } IPAddress& IPAddress::operator=(uint32_t address) { - _address.dword = address; + // IPv4 conversion + // See note on conversion/comparison and uint32_t + _type = IPv4; + memset(_address.bytes, 0, sizeof(_address.bytes)); + _address.dword[IPADDRESS_V4_DWORD_INDEX] = address; return *this; } bool IPAddress::operator==(const uint8_t* addr) const { - return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0; + return (addr._type == _type) + && (memcmp(addr._address.bytes, _address.bytes, sizeof(_address.bytes)) == 0); +} + +bool IPAddress::operator==(const uint8_t* addr) const +{ + // IPv4 only comparison to byte pointer + // Can't support IPv6 as we know our type, but not the length of the pointer + return _type == IPv4 && memcmp(addr, &_address.bytes[IPADDRESS_V4_BYTES_INDEX], sizeof(uint32_t)) == 0; +} + +uint8_t IPAddress::operator[](int index) const { + if (_type == IPv4) { + return _address.bytes[IPADDRESS_V4_BYTES_INDEX + index]; + } + return _address.bytes[index]; +} + +uint8_t& IPAddress::operator[](int index) { + if (_type == IPv4) { + return _address.bytes[IPADDRESS_V4_BYTES_INDEX + index]; + } + return _address.bytes[index]; } size_t IPAddress::printTo(Print& p) const { size_t n = 0; - for(int i = 0; i < 3; i++) { - n += p.print(_address.bytes[i], DEC); + + if (_type == IPv6) { + // IPv6 IETF canonical format: compress left-most longest run of two or more zero fields, lower case + int8_t longest_start = -1; + int8_t longest_length = 1; + int8_t current_start = -1; + int8_t current_length = 0; + for (int8_t f = 0; f < 8; f++) { + if (_address.bytes[f * 2] == 0 && _address.bytes[f * 2 + 1] == 0) { + if (current_start == -1) { + current_start = f; + current_length = 1; + } else { + current_length++; + } + if (current_length > longest_length) { + longest_start = current_start; + longest_length = current_length; + } + } else { + current_start = -1; + } + } + for (int f = 0; f < 8; f++) { + if (f < longest_start || f >= longest_start + longest_length) { + uint8_t c1 = _address.bytes[f * 2] >> 4; + uint8_t c2 = _address.bytes[f * 2] & 0xf; + uint8_t c3 = _address.bytes[f * 2 + 1] >> 4; + uint8_t c4 = _address.bytes[f * 2 + 1] & 0xf; + if (c1 > 0) { + n += p.print((char)(c1 < 10 ? '0' + c1 : 'a' + c1 - 10)); + } + if (c1 > 0 || c2 > 0) { + n += p.print((char)(c2 < 10 ? '0' + c2 : 'a' + c2 - 10)); + } + if (c1 > 0 || c2 > 0 || c3 > 0) { + n += p.print((char)(c3 < 10 ? '0' + c3 : 'a' + c3 - 10)); + } + n += p.print((char)(c4 < 10 ? '0' + c4 : 'a' + c4 - 10)); + if (f < 7) { + n += p.print(':'); + } + } else if (f == longest_start) { + if (longest_start == 0) { + n += p.print(':'); + } + n += p.print(':'); + } + } + return n; + } + + // IPv4 + for (int i =0; i < 3; i++) + { + n += p.print(_address.bytes[IPADDRESS_V4_BYTES_INDEX + i], DEC); n += p.print('.'); } - n += p.print(_address.bytes[3], DEC); + n += p.print(_address.bytes[IPADDRESS_V4_BYTES_INDEX + 3], DEC); return n; } String IPAddress::toString() const { + if (_type == IPv6) + { + StreamString s; + s.reserve(40); + printTo(s); + return s; + } + + // IPv4 char szRet[16]; sprintf(szRet,"%u.%u.%u.%u", _address.bytes[0], _address.bytes[1], _address.bytes[2], _address.bytes[3]); return String(szRet); } bool IPAddress::fromString(const char *address) +{ + if (!fromString4(address)) + { + return fromString6(address); + } + return true; +} + +bool IPAddress::fromString4(const char *address) { // TODO: add support for "a", "a.b", "a.b.c" formats - uint16_t acc = 0; // Accumulator + int16_t acc = -1; // Accumulator uint8_t dots = 0; + memset(_address.bytes, 0, sizeof(_address.bytes)); while (*address) { char c = *address++; if (c >= '0' && c <= '9') { - acc = acc * 10 + (c - '0'); + acc = (acc < 0) ? (c - '0') : acc * 10 + (c - '0'); if (acc > 255) { // Value out of [0..255] range return false; @@ -100,11 +246,15 @@ bool IPAddress::fromString(const char *address) else if (c == '.') { if (dots == 3) { - // Too much dots (there must be 3 dots) + // Too many dots (there must be 3 dots) return false; } - _address.bytes[dots++] = acc; - acc = 0; + if (acc < 0) { + /* No value between dots, e.g. '1..' */ + return false; + } + _address.bytes[IPADDRESS_V4_BYTES_INDEX + dots++] = acc; + acc = -1; } else { @@ -117,7 +267,80 @@ bool IPAddress::fromString(const char *address) // Too few dots (there must be 3 dots) return false; } - _address.bytes[3] = acc; + if (acc < 0) { + /* No value between dots, e.g. '1..' */ + return false; + } + _address.bytes[IPADDRESS_V4_BYTES_INDEX + 3] = acc; + _type = IPv4; + return true; +} + +bool IPAddress::fromString6(const char *address) { + uint32_t acc = 0; // Accumulator + int colons = 0, double_colons = -1; + + while (*address) + { + char c = tolower(*address++); + if (isalnum(c) && c <= 'f') { + if (c >= 'a') + c -= 'a' - '0' - 10; + acc = acc * 16 + (c - '0'); + if (acc > 0xffff) + // Value out of range + return false; + } + else if (c == ':') { + if (*address == ':') { + if (double_colons >= 0) { + // :: allowed once + return false; + } + if (*address != '\0' && *(address + 1) == ':') { + // ::: not allowed + return false; + } + // remember location + double_colons = colons + !!acc; + address++; + } else if (*address == '\0') { + // can't end with a single colon + return false; + } + if (colons == 7) + // too many separators + return false; + _address.bytes[colons * 2] = acc >> 8; + _address.bytes[colons * 2 + 1] = acc & 0xff; + colons++; + acc = 0; + } + else + // Invalid char + return false; + } + + if (double_colons == -1 && colons != 7) { + // Too few separators + return false; + } + if (double_colons > -1 && colons > 6) { + // Too many segments (double colon must be at least one zero field) + return false; + } + _address.bytes[colons * 2] = acc >> 8; + _address.bytes[colons * 2 + 1] = acc & 0xff; + colons++; + + if (double_colons != -1) { + for (int i = colons * 2 - double_colons * 2 - 1; i >= 0; i--) + _address.bytes[16 - colons * 2 + double_colons * 2 + i] = _address.bytes[double_colons * 2 + i]; + for (int i = double_colons * 2; i < 16 - colons * 2 + double_colons * 2; i++) + _address.bytes[i] = 0; + } + + _type = IPv6; return true; } From 184ad9a3f9652de9805fa61782b90b3716e69fdb Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Fri, 26 Aug 2022 10:05:32 +1000 Subject: [PATCH 03/12] Fix cut and paste error --- cores/esp32/IPAddress.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp32/IPAddress.cpp b/cores/esp32/IPAddress.cpp index 6192a293cc3..3ccea6c2792 100644 --- a/cores/esp32/IPAddress.cpp +++ b/cores/esp32/IPAddress.cpp @@ -107,7 +107,7 @@ IPAddress& IPAddress::operator=(uint32_t address) return *this; } -bool IPAddress::operator==(const uint8_t* addr) const +bool IPAddress::operator==(const IPAddress& addr) const { return (addr._type == _type) && (memcmp(addr._address.bytes, _address.bytes, sizeof(_address.bytes)) == 0); From e15cc725f1458e9dae7b30d06bb248dc9ce0ca92 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Fri, 26 Aug 2022 11:01:39 +1000 Subject: [PATCH 04/12] Explicit not equals --- cores/esp32/IPAddress.cpp | 6 ++++++ cores/esp32/IPAddress.h | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cores/esp32/IPAddress.cpp b/cores/esp32/IPAddress.cpp index 3ccea6c2792..29f5dfed9b5 100644 --- a/cores/esp32/IPAddress.cpp +++ b/cores/esp32/IPAddress.cpp @@ -113,6 +113,12 @@ bool IPAddress::operator==(const IPAddress& addr) const && (memcmp(addr._address.bytes, _address.bytes, sizeof(_address.bytes)) == 0); } +bool IPAddress::operator!=(const IPAddress& addr) const +{ + return (addr._type != _type) + || (memcmp(addr._address.bytes, _address.bytes, sizeof(_address.bytes)) != 0); +} + bool IPAddress::operator==(const uint8_t* addr) const { // IPv4 only comparison to byte pointer diff --git a/cores/esp32/IPAddress.h b/cores/esp32/IPAddress.h index 2f38b96b1ad..83c48f197b6 100644 --- a/cores/esp32/IPAddress.h +++ b/cores/esp32/IPAddress.h @@ -75,7 +75,7 @@ class IPAddress: public Printable } bool operator==(const IPAddress& addr) const; - bool operator!=(const IPAddress& addr) const { return !(*this == addr); }; + bool operator!=(const IPAddress& addr) const; bool operator==(const uint8_t* addr) const; // Overloaded index operator to allow getting and setting individual octets of the address From d2178b188def88f3d30e9d87a7e916cf611233d5 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Fri, 26 Aug 2022 11:04:32 +1000 Subject: [PATCH 05/12] Explicitly reference StreamString --- cores/esp32/IPAddress.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cores/esp32/IPAddress.cpp b/cores/esp32/IPAddress.cpp index 29f5dfed9b5..4579f65fd29 100644 --- a/cores/esp32/IPAddress.cpp +++ b/cores/esp32/IPAddress.cpp @@ -20,6 +20,7 @@ #include #include #include +#include IPAddress::IPAddress() : IPAddress(IPv4) {} From cd126c005494528ad08792c42ce159debbdcb47a Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Fri, 26 Aug 2022 14:30:31 +1000 Subject: [PATCH 06/12] Remove != test (it is causing a conflict) --- cores/esp32/IPAddress.cpp | 6 ------ cores/esp32/IPAddress.h | 1 - 2 files changed, 7 deletions(-) diff --git a/cores/esp32/IPAddress.cpp b/cores/esp32/IPAddress.cpp index 4579f65fd29..6766b4a26d2 100644 --- a/cores/esp32/IPAddress.cpp +++ b/cores/esp32/IPAddress.cpp @@ -114,12 +114,6 @@ bool IPAddress::operator==(const IPAddress& addr) const && (memcmp(addr._address.bytes, _address.bytes, sizeof(_address.bytes)) == 0); } -bool IPAddress::operator!=(const IPAddress& addr) const -{ - return (addr._type != _type) - || (memcmp(addr._address.bytes, _address.bytes, sizeof(_address.bytes)) != 0); -} - bool IPAddress::operator==(const uint8_t* addr) const { // IPv4 only comparison to byte pointer diff --git a/cores/esp32/IPAddress.h b/cores/esp32/IPAddress.h index 83c48f197b6..a86bc392978 100644 --- a/cores/esp32/IPAddress.h +++ b/cores/esp32/IPAddress.h @@ -75,7 +75,6 @@ class IPAddress: public Printable } bool operator==(const IPAddress& addr) const; - bool operator!=(const IPAddress& addr) const; bool operator==(const uint8_t* addr) const; // Overloaded index operator to allow getting and setting individual octets of the address From c36cc3b42caf3648c07a2e2c2c76d1d3a7f7a7ce Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sat, 7 May 2022 21:39:56 +0300 Subject: [PATCH 07/12] IPv6 support: Add proper link-local and SLAAC in STA and WifiMulti This patch partially depends on: https://github.com/espressif/esp32-arduino-lib-builder/pull/67 Without this patch we will get only Link local IPv6 (still useful for MDNS and etc). With patch we will get also global IPv6 address by SLAAC. By default IPv6 disabled, until it is properly tested. Tested on BasicHttpClient by adding: wifiMulti.IPv6(true); before: wifiMulti.addAP() call Enabling Core Debug Level: verbose If IP6 obtained, in logs will be visible: [ 8028][V][WiFiGeneric.cpp:380] _arduino_event_cb(): IF[0] Got IPv6: IP Index: 0, Zone: 2, fe80:0000:0000:0000:xxxx:xxxx:xxxx:xxxx [ 8028][D][WiFiGeneric.cpp:852] _eventCallback(): Arduino Event: 8 - STA_GOT_IP6 [ 11028][V][WiFiGeneric.cpp:380] _arduino_event_cb(): IF[0] Got IPv6: IP Index: 1, Zone: 0, 2a0d:yyyy:0000:4000:yyyy:yyyy:yyyy:yyyy [ 11028][D][WiFiGeneric.cpp:852] _eventCallback(): Arduino Event: 8 - STA_GOT_IP6 This is linked to: https://github.com/espressif/arduino-esp32/issues/6242 Signed-off-by: Denys Fedoryshchenko --- libraries/WiFi/src/WiFiGeneric.cpp | 4 ++-- libraries/WiFi/src/WiFiGeneric.h | 1 + libraries/WiFi/src/WiFiMulti.cpp | 7 +++++++ libraries/WiFi/src/WiFiMulti.h | 2 ++ libraries/WiFi/src/WiFiSTA.cpp | 13 +++++++++++++ libraries/WiFi/src/WiFiSTA.h | 1 + 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/libraries/WiFi/src/WiFiGeneric.cpp b/libraries/WiFi/src/WiFiGeneric.cpp index a94b1419414..847177d1bb7 100644 --- a/libraries/WiFi/src/WiFiGeneric.cpp +++ b/libraries/WiFi/src/WiFiGeneric.cpp @@ -943,8 +943,8 @@ esp_err_t WiFiGenericClass::_eventCallback(arduino_event_t *event) } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_CONNECTED) { WiFiSTAClass::_setStatus(WL_IDLE_STATUS); setStatusBits(STA_CONNECTED_BIT); - - //esp_netif_create_ip6_linklocal(esp_netifs[ESP_IF_WIFI_STA]); + if (getStatusBits() & WIFI_WANT_IP6_BIT) + esp_netif_create_ip6_linklocal(esp_netifs[ESP_IF_WIFI_STA]); } else if(event->event_id == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) { uint8_t reason = event->event_info.wifi_sta_disconnected.reason; // Reason 0 causes crash, use reason 1 (UNSPECIFIED) instead diff --git a/libraries/WiFi/src/WiFiGeneric.h b/libraries/WiFi/src/WiFiGeneric.h index 2f670a34d05..552e3c2f007 100644 --- a/libraries/WiFi/src/WiFiGeneric.h +++ b/libraries/WiFi/src/WiFiGeneric.h @@ -138,6 +138,7 @@ static const int WIFI_SCANNING_BIT = BIT11; static const int WIFI_SCAN_DONE_BIT= BIT12; static const int WIFI_DNS_IDLE_BIT = BIT13; static const int WIFI_DNS_DONE_BIT = BIT14; +static const int WIFI_WANT_IP6_BIT = BIT15; typedef enum { WIFI_RX_ANT0 = 0, diff --git a/libraries/WiFi/src/WiFiMulti.cpp b/libraries/WiFi/src/WiFiMulti.cpp index 3d69e481293..9e7f03c6531 100644 --- a/libraries/WiFi/src/WiFiMulti.cpp +++ b/libraries/WiFi/src/WiFiMulti.cpp @@ -30,6 +30,7 @@ WiFiMulti::WiFiMulti() { + ipv6_support = false; } WiFiMulti::~WiFiMulti() @@ -160,6 +161,8 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) log_i("[WIFI] Connecting BSSID: %02X:%02X:%02X:%02X:%02X:%02X SSID: %s Channel: %d (%d)", bestBSSID[0], bestBSSID[1], bestBSSID[2], bestBSSID[3], bestBSSID[4], bestBSSID[5], bestNetwork.ssid, bestChannel, bestNetworkDb); WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID); + if (ipv6_support == true) + WiFi.IPv6(true); status = WiFi.status(); auto startTime = millis(); @@ -202,3 +205,7 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout) return status; } + +void WiFiMulti::IPv6(bool state) { + ipv6_support = state; +} diff --git a/libraries/WiFi/src/WiFiMulti.h b/libraries/WiFi/src/WiFiMulti.h index 38ddb5d9f95..bbeb78dc860 100644 --- a/libraries/WiFi/src/WiFiMulti.h +++ b/libraries/WiFi/src/WiFiMulti.h @@ -42,10 +42,12 @@ class WiFiMulti bool addAP(const char* ssid, const char *passphrase = NULL); + void IPv6(bool state); uint8_t run(uint32_t connectTimeout=5000); private: std::vector APlist; + bool ipv6_support; }; #endif /* WIFICLIENTMULTI_H_ */ diff --git a/libraries/WiFi/src/WiFiSTA.cpp b/libraries/WiFi/src/WiFiSTA.cpp index 7bcafea1d3e..d8075868475 100644 --- a/libraries/WiFi/src/WiFiSTA.cpp +++ b/libraries/WiFi/src/WiFiSTA.cpp @@ -732,6 +732,19 @@ bool WiFiSTAClass::enableIpV6() return esp_netif_create_ip6_linklocal(get_esp_interface_netif(ESP_IF_WIFI_STA)) == ESP_OK; } +/** + * Enable IPv6 support on the station interface. + * @return true on success + */ +bool WiFiSTAClass::IPv6(bool state) +{ + if (state) + WiFiGenericClass::setStatusBits(WIFI_WANT_IP6_BIT); + else + WiFiGenericClass::clearStatusBits(WIFI_WANT_IP6_BIT); + return true; +} + /** * Get the station interface IPv6 address. * @return IPv6Address diff --git a/libraries/WiFi/src/WiFiSTA.h b/libraries/WiFi/src/WiFiSTA.h index b8bb855c198..6892c996b30 100644 --- a/libraries/WiFi/src/WiFiSTA.h +++ b/libraries/WiFi/src/WiFiSTA.h @@ -84,6 +84,7 @@ class WiFiSTAClass uint8_t subnetCIDR(); bool enableIpV6(); + bool IPv6(bool state); IPv6Address localIPv6(); // STA WiFi info From cf3116605942024735fa5535c7ff08c60f87e1b8 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sun, 8 May 2022 08:51:23 +0300 Subject: [PATCH 08/12] Add IPv6 support to WiFiServer One of most useful features of IPv6 to have arduino accessible from internet, without any port forward and etc. Change is fairly trivial and backward compatible with old code, tested with WiFiTelnetToSerial and AsyncUDPServer. Signed-off-by: Denys Fedoryshchenko --- libraries/WiFi/src/WiFiServer.cpp | 27 ++++++++++++++++++--------- libraries/WiFi/src/WiFiServer.h | 1 - 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/libraries/WiFi/src/WiFiServer.cpp b/libraries/WiFi/src/WiFiServer.cpp index db21858125b..ad2cb7fb364 100644 --- a/libraries/WiFi/src/WiFiServer.cpp +++ b/libraries/WiFi/src/WiFiServer.cpp @@ -47,8 +47,8 @@ WiFiClient WiFiServer::available(){ _accepted_sockfd = -1; } else { - struct sockaddr_in _client; - int cs = sizeof(struct sockaddr_in); + struct sockaddr_in6 _client; + int cs = sizeof(struct sockaddr_in6); #ifdef ESP_IDF_VERSION_MAJOR client_sock = lwip_accept(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs); #else @@ -76,14 +76,23 @@ void WiFiServer::begin(uint16_t port, int enable){ if(port){ _port = port; } - struct sockaddr_in server; - sockfd = socket(AF_INET , SOCK_STREAM, 0); + struct sockaddr_in6 server; + sockfd = socket(AF_INET6 , SOCK_STREAM, 0); if (sockfd < 0) return; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); - server.sin_family = AF_INET; - server.sin_addr.s_addr = _addr; - server.sin_port = htons(_port); + server.sin6_family = AF_INET6; + if (_addr.type() == IPv4) { + log_e("---------------- IPv4"); + memcpy(server.sin6_addr.s6_addr+11, (uint8_t*)&_addr[0], 4); + server.sin6_addr.s6_addr[10] = 0xFF; + server.sin6_addr.s6_addr[11] = 0xFF; + } else { + log_e("---------------- IPv6"); + memcpy(server.sin6_addr.s6_addr, (uint8_t*)&_addr[0], 16); + } + memset(server.sin6_addr.s6_addr, 0x0, 16); + server.sin6_port = htons(_port); if(bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) return; if(listen(sockfd , _max_clients) < 0) @@ -106,8 +115,8 @@ bool WiFiServer::hasClient() { if (_accepted_sockfd >= 0) { return true; } - struct sockaddr_in _client; - int cs = sizeof(struct sockaddr_in); + struct sockaddr_in6 _client; + int cs = sizeof(struct sockaddr_in6); #ifdef ESP_IDF_VERSION_MAJOR _accepted_sockfd = lwip_accept(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs); #else diff --git a/libraries/WiFi/src/WiFiServer.h b/libraries/WiFi/src/WiFiServer.h index 346986abad5..d933231bdbb 100644 --- a/libraries/WiFi/src/WiFiServer.h +++ b/libraries/WiFi/src/WiFiServer.h @@ -37,7 +37,6 @@ class WiFiServer : public Server { public: void listenOnLocalhost(){} - // _addr(INADDR_ANY) is the same as _addr() ==> 0.0.0.0 WiFiServer(uint16_t port=80, uint8_t max_clients=4):sockfd(-1),_accepted_sockfd(-1),_addr(),_port(port),_max_clients(max_clients),_listening(false),_noDelay(false) { log_v("WiFiServer::WiFiServer(port=%d, ...)", port); } From add482abfbdc416b94307b1f7449519302c4177c Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sat, 27 Aug 2022 22:22:44 +0300 Subject: [PATCH 09/12] WiFiClient::remoteIP and remoteIP6 IPv6 support For RemoteIP and AF_INET6 socket i added support ip6 to ip4 mapping, so .remoteIP will return IPv4 address on dual stack socket, if available. Scenarios tested: WiFiTelnetToSerial, wifiMulti.IPv6(true), connect both from IPv4 and IPv6 WiFiTelnetToSerial, wifiMulti.IPv6(true); but set to listen on IPv4 only. WiFiTelnetToSerial, IPv6 disabled, with or without bind to specific IP4. AsyncUDPServer, without IPv6 support. Signed-off-by: Denys Fedoryshchenko --- libraries/WiFi/src/WiFiClient.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/libraries/WiFi/src/WiFiClient.cpp b/libraries/WiFi/src/WiFiClient.cpp index 6e56e94ac53..ee68d7cefa1 100644 --- a/libraries/WiFi/src/WiFiClient.cpp +++ b/libraries/WiFi/src/WiFiClient.cpp @@ -23,6 +23,11 @@ #include #include +#define IN6_IS_ADDR_V4MAPPED(a) \ + ((((__const uint32_t *) (a))[0] == 0) \ + && (((__const uint32_t *) (a))[1] == 0) \ + && (((__const uint32_t *) (a))[2] == htonl (0xffff))) + #define WIFI_CLIENT_DEF_CONN_TIMEOUT_MS (3000) #define WIFI_CLIENT_MAX_WRITE_RETRY (10) #define WIFI_CLIENT_SELECT_TIMEOUT_US (1000000) @@ -562,8 +567,24 @@ IPAddress WiFiClient::remoteIP(int fd) const struct sockaddr_storage addr; socklen_t len = sizeof addr; getpeername(fd, (struct sockaddr*)&addr, &len); - struct sockaddr_in *s = (struct sockaddr_in *)&addr; - return IPAddress((uint32_t)(s->sin_addr.s_addr)); + + // IPv4 socket, old way + if (((struct sockaddr*)&addr)->sa_family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return IPAddress((uint32_t)(s->sin_addr.s_addr)); + } + + // IPv6, but it might be IPv4 mapped address + if (((struct sockaddr*)&addr)->sa_family == AF_INET6) { + struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)&addr; + if (IN6_IS_ADDR_V4MAPPED(saddr6->sin6_addr.un.u32_addr)) { + return IPAddress(IPv4, (uint8_t*)saddr6->sin6_addr.s6_addr+12); + } else { + return IPAddress(IPv6, (uint8_t*)(saddr6->sin6_addr.s6_addr)); + } + } + log_e("WiFiClient::remoteIP Not AF_INET or AF_INET6?"); + return (IPAddress(0,0,0,0)); } uint16_t WiFiClient::remotePort(int fd) const From 63552ff9e105b23498d144884bcbd9e528ca8eaa Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sun, 8 May 2022 08:56:52 +0300 Subject: [PATCH 10/12] Add WiFiTelnetToSerialIPv6 example To demonstrate new abilities of dual stack WiFiServer and remoteIP6 we add this example. Signed-off-by: Denys Fedoryshchenko --- .../WiFiTelnetToSerialIPv6.ino | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino diff --git a/libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino b/libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino new file mode 100644 index 00000000000..99b1c0c82e9 --- /dev/null +++ b/libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino @@ -0,0 +1,132 @@ +/* + WiFiTelnetToSerial - Example Transparent UART to Telnet Server for ESP32 + + Copyright (c) 2017 Hristo Gochkov. All rights reserved. + This file is part of the ESP32 WiFi library for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include +#include + +WiFiMulti wifiMulti; + +//Even this example state IPv6, it is dual stack and compatible with IPv4 too + +//how many clients should be able to telnet to this ESP32 +#define MAX_SRV_CLIENTS 1 +const char* ssid = "**********"; +const char* password = "**********"; + +WiFiServer server(23); +WiFiClient serverClients[MAX_SRV_CLIENTS]; + +void setup() { + Serial.begin(115200); + Serial.println("\nConnecting"); + + wifiMulti.IPv6(true); + wifiMulti.addAP(ssid, password); + wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2"); + wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3"); + + Serial.println("Connecting Wifi "); + for (int loops = 10; loops > 0; loops--) { + if (wifiMulti.run() == WL_CONNECTED) { + Serial.println(""); + Serial.print("WiFi connected "); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + break; + } + else { + Serial.println(loops); + delay(1000); + } + } + if (wifiMulti.run() != WL_CONNECTED) { + Serial.println("WiFi connect failed"); + delay(1000); + ESP.restart(); + } + + //start UART and the server + Serial1.begin(9600); + server.begin(); + server.setNoDelay(true); + + Serial.print("Ready! Use 'telnet "); + Serial.print(WiFi.localIP()); + Serial.println(" 23' to connect"); +} + +void loop() { + uint8_t i; + if (wifiMulti.run() == WL_CONNECTED) { + //check if there are any new clients + if (server.hasClient()){ + for(i = 0; i < MAX_SRV_CLIENTS; i++){ + //find free/disconnected spot + if (!serverClients[i] || !serverClients[i].connected()){ + if(serverClients[i]) serverClients[i].stop(); + serverClients[i] = server.available(); + if (!serverClients[i]) Serial.println("available broken"); + Serial.print("New client: "); + Serial.print(i); Serial.print(' '); + Serial.println(serverClients[i].remoteIP6()); + break; + } + } + if (i >= MAX_SRV_CLIENTS) { + //no free/disconnected spot so reject + server.available().stop(); + } + } + //check clients for data + for(i = 0; i < MAX_SRV_CLIENTS; i++){ + if (serverClients[i] && serverClients[i].connected()){ + if(serverClients[i].available()){ + //get data from the telnet client and push it to the UART + while(serverClients[i].available()) Serial1.write(serverClients[i].read()); + } + } + else { + if (serverClients[i]) { + serverClients[i].stop(); + } + } + } + //check UART for data + if(Serial1.available()){ + size_t len = Serial1.available(); + uint8_t sbuf[len]; + Serial1.readBytes(sbuf, len); + //push UART data to all connected telnet clients + for(i = 0; i < MAX_SRV_CLIENTS; i++){ + if (serverClients[i] && serverClients[i].connected()){ + serverClients[i].write(sbuf, len); + delay(1); + } + } + } + } + else { + Serial.println("WiFi not connected!"); + for(i = 0; i < MAX_SRV_CLIENTS; i++) { + if (serverClients[i]) serverClients[i].stop(); + } + delay(1000); + } +} From 3b1bc58e1fbc5292085895102c13abc4a7c55a53 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sun, 8 May 2022 17:10:04 +0300 Subject: [PATCH 11/12] Add IPv6 to WifiClient (client) We need to be able to connect to remote servers over IPv6 too, and thats need different approach in DNS queries and connect(). As i'm trying to keep maximum compatibility, i introduce different behaviour if IPv6 is enabled, and backward compatible (as much as possible), if IPv6 is not enabled. IN future when IPv6 functions are tested well enough, it can be simplified. This implementation tested on esp32 in following scenarios using BasicHttpClient: IPv6 true: IPv6 only website (caveat 1) - OK Website with A and AAAA is present (caveat 1) - OK IPv4 only website - OK IPv6 not enabled: IPv6 only website - wont open (expected) Website with A and AAAA is present - OK, opens over IPv4 IPv4 only website - OK caveat 1 - sometimes SLAAC is slower than DHCPv4, so we might have status WL_CONNECTED, but IPv6 global scope is not ready yet. Signed-off-by: Denys Fedoryshchenko --- libraries/WiFi/src/WiFiClient.cpp | 39 ++++++++++++++++++----- libraries/WiFi/src/WiFiGeneric.cpp | 50 ++++++++++++++++++++++++++++++ libraries/WiFi/src/WiFiGeneric.h | 6 ++++ 3 files changed, 88 insertions(+), 7 deletions(-) diff --git a/libraries/WiFi/src/WiFiClient.cpp b/libraries/WiFi/src/WiFiClient.cpp index ee68d7cefa1..cf8cde4a7b2 100644 --- a/libraries/WiFi/src/WiFiClient.cpp +++ b/libraries/WiFi/src/WiFiClient.cpp @@ -215,22 +215,34 @@ int WiFiClient::connect(IPAddress ip, uint16_t port) { return connect(ip,port,_timeout); } + int WiFiClient::connect(IPAddress ip, uint16_t port, int32_t timeout) { + struct sockaddr_storage serveraddr; _timeout = timeout; - int sockfd = socket(AF_INET, SOCK_STREAM, 0); + int sockfd = -1; + + if (ip.type() == IPv6) { + struct sockaddr_in6 *tmpaddr = (struct sockaddr_in6 *)&serveraddr; + memset((char *) tmpaddr, 0, sizeof(struct sockaddr_in)); + sockfd = socket(AF_INET6, SOCK_STREAM, 0); + tmpaddr->sin6_family = AF_INET6; + memcpy(tmpaddr->sin6_addr.un.u8_addr, &ip[0], 16); + tmpaddr->sin6_port = htons(port); + } else { + struct sockaddr_in *tmpaddr = (struct sockaddr_in *)&serveraddr; + memset((char *) tmpaddr, 0, sizeof(struct sockaddr_in)); + sockfd = socket(AF_INET, SOCK_STREAM, 0); + tmpaddr->sin_family = AF_INET; + tmpaddr->sin_addr.s_addr = ip; + tmpaddr->sin_port = htons(port); + } if (sockfd < 0) { log_e("socket: %d", errno); return 0; } fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK ); - uint32_t ip_addr = ip; - struct sockaddr_in serveraddr; - memset((char *) &serveraddr, 0, sizeof(serveraddr)); - serveraddr.sin_family = AF_INET; - memcpy((void *)&serveraddr.sin_addr.s_addr, (const void *)(&ip_addr), 4); - serveraddr.sin_port = htons(port); fd_set fdset; struct timeval tv; FD_ZERO(&fdset); @@ -299,6 +311,19 @@ int WiFiClient::connect(const char *host, uint16_t port) int WiFiClient::connect(const char *host, uint16_t port, int32_t timeout) { + if (WiFiGenericClass::getStatusBits() & WIFI_WANT_IP6_BIT) { + ip_addr_t srv6; + if(!WiFiGenericClass::hostByName6(host, srv6)){ + return 0; + } + if (srv6.type == IPADDR_TYPE_V4) { + IPAddress ip(srv6.u_addr.ip4.addr); + return connect(ip, port, timeout); + } else { + IPAddress ip(IPv6, (uint8_t*)&srv6.u_addr.ip6.addr[0]); + return connect(ip, port, timeout); + } + } IPAddress srv((uint32_t)0); if(!WiFiGenericClass::hostByName(host, srv)){ return 0; diff --git a/libraries/WiFi/src/WiFiGeneric.cpp b/libraries/WiFi/src/WiFiGeneric.cpp index 847177d1bb7..3f8a9d797ca 100644 --- a/libraries/WiFi/src/WiFiGeneric.cpp +++ b/libraries/WiFi/src/WiFiGeneric.cpp @@ -1446,6 +1446,25 @@ static void wifi_dns_found_callback(const char *name, const ip_addr_t *ipaddr, v xEventGroupSetBits(_arduino_event_group, WIFI_DNS_DONE_BIT); } +/** + * IPv6 compatible DNS callback + * @param name + * @param ipaddr + * @param callback_arg + */ +static void wifi_dns6_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) +{ + struct dns_api_msg *msg = (struct dns_api_msg *)callback_arg; + + if(ipaddr && !msg->result) { + msg->ip_addr = *ipaddr; + msg->result = 1; + } else { + msg->result = -1; + } + xEventGroupSetBits(_arduino_event_group, WIFI_DNS_DONE_BIT); +} + /** * Resolve the given hostname to an IP address. * @param aHostname Name to be resolved @@ -1473,6 +1492,37 @@ int WiFiGenericClass::hostByName(const char* aHostname, IPAddress& aResult) return (uint32_t)aResult != 0; } +/** + * Resolve the given hostname to an IP6 address. + * @param aHostname Name to be resolved + * @param aResult IPv6Address structure to store the returned IP address + * @return 1 if aHostname was successfully converted to an IP address, + * else error code + */ +int WiFiGenericClass::hostByName6(const char* aHostname, ip_addr_t& aResult) +{ + ip_addr_t addr; + struct dns_api_msg arg; + + memset(&arg, 0x0, sizeof(arg)); + waitStatusBits(WIFI_DNS_IDLE_BIT, 16000); + clearStatusBits(WIFI_DNS_IDLE_BIT | WIFI_DNS_DONE_BIT); + + err_t err = dns_gethostbyname_addrtype(aHostname, &addr, &wifi_dns6_found_callback, + &arg, LWIP_DNS_ADDRTYPE_IPV6_IPV4); + if(err == ERR_OK) { + aResult = addr; + } else if(err == ERR_INPROGRESS) { + waitStatusBits(WIFI_DNS_DONE_BIT, 15000); //real internal timeout in lwip library is 14[s] + clearStatusBits(WIFI_DNS_DONE_BIT); + if (arg.result == 1) { + aResult = arg.ip_addr; + } + } + setStatusBits(WIFI_DNS_IDLE_BIT); + return (uint32_t)err == ERR_OK || (err == ERR_INPROGRESS && arg.result == 1); +} + IPAddress WiFiGenericClass::calculateNetworkID(IPAddress ip, IPAddress subnet) { IPAddress networkID; diff --git a/libraries/WiFi/src/WiFiGeneric.h b/libraries/WiFi/src/WiFiGeneric.h index 552e3c2f007..f969cfc7266 100644 --- a/libraries/WiFi/src/WiFiGeneric.h +++ b/libraries/WiFi/src/WiFiGeneric.h @@ -152,6 +152,11 @@ typedef enum { WIFI_TX_ANT_AUTO } wifi_tx_ant_t; +struct dns_api_msg { + ip_addr_t ip_addr; + int result; +}; + class WiFiGenericClass { public: @@ -213,6 +218,7 @@ class WiFiGenericClass public: static int hostByName(const char *aHostname, IPAddress &aResult); + static int hostByName6(const char *aHostname, ip_addr_t& aResult); static IPAddress calculateNetworkID(IPAddress ip, IPAddress subnet); static IPAddress calculateBroadcast(IPAddress ip, IPAddress subnet); From 7fa27673008c8192ef031811520543756d86890a Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sun, 27 Nov 2022 22:39:40 +0200 Subject: [PATCH 12/12] WiFiTelnetToSerialIPv6.ino: fix obsolete remoteIP6 call to remoteIP Example contained API from previous IPv6 implementation, fixing it. Signed-off-by: Denys Fedoryshchenko --- .../examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino b/libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino index 99b1c0c82e9..05e02771dae 100644 --- a/libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino +++ b/libraries/WiFi/examples/WiFiTelnetToSerialIPv6/WiFiTelnetToSerialIPv6.ino @@ -85,7 +85,7 @@ void loop() { if (!serverClients[i]) Serial.println("available broken"); Serial.print("New client: "); Serial.print(i); Serial.print(' '); - Serial.println(serverClients[i].remoteIP6()); + Serial.println(serverClients[i].remoteIP()); break; } }