From 4735f6614f81cc81824a7608c14f1d9ff3fdc458 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sat, 7 May 2022 21:39:56 +0300 Subject: [PATCH 1/6] 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 5d31341481a..25de03ab96d 100644 --- a/libraries/WiFi/src/WiFiGeneric.cpp +++ b/libraries/WiFi/src/WiFiGeneric.cpp @@ -866,8 +866,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; log_w("Reason: %u - %s", reason, reason2str(reason)); diff --git a/libraries/WiFi/src/WiFiGeneric.h b/libraries/WiFi/src/WiFiGeneric.h index 62642b43dc7..22caa17f529 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 7734f5d79b5..dc8ff8ee6bc 100644 --- a/libraries/WiFi/src/WiFiSTA.cpp +++ b/libraries/WiFi/src/WiFiSTA.cpp @@ -726,6 +726,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 e8711c0c2f4d4253ac4e666c30efb2747ade3243 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sun, 8 May 2022 00:49:06 +0300 Subject: [PATCH 2/6] 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 --- cores/esp32/Client.h | 1 + libraries/WiFi/src/WiFiClient.cpp | 53 +++++++++++++++++++++++++++++-- libraries/WiFi/src/WiFiClient.h | 7 ++++ libraries/WiFi/src/WiFiMulti.cpp | 1 - 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/cores/esp32/Client.h b/cores/esp32/Client.h index 962cacc51da..c575cde0c66 100644 --- a/cores/esp32/Client.h +++ b/cores/esp32/Client.h @@ -22,6 +22,7 @@ #include "Print.h" #include "Stream.h" #include "IPAddress.h" +#include "IPv6Address.h" class Client: public Stream { diff --git a/libraries/WiFi/src/WiFiClient.cpp b/libraries/WiFi/src/WiFiClient.cpp index 69a691995bd..e00e9f0f35f 100644 --- a/libraries/WiFi/src/WiFiClient.cpp +++ b/libraries/WiFi/src/WiFiClient.cpp @@ -561,8 +561,52 @@ 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)); + + // Old way, IPv4 + 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)) { + struct sockaddr_in addr4; + memset(&addr4, 0, sizeof(addr4)); + addr4.sin_family = AF_INET; + addr4.sin_port = saddr6->sin6_port; + memcpy(&addr4.sin_addr.s_addr, saddr6->sin6_addr.s6_addr+12, sizeof(addr4.sin_addr.s_addr)); + return IPAddress((uint32_t)(addr4.sin_addr.s_addr)); + } + log_e("WiFiClient::remoteIP IPv6 to IPv4 cannot convert"); + return (IPAddress(0,0,0,0)); + } + log_e("WiFiClient::remoteIP Not AF_INET or AF_INET6?"); + return (IPAddress(0,0,0,0)); +} + +IPv6Address WiFiClient::remoteIP6(int fd) const +{ + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(fd, (struct sockaddr*)&addr, &len); + + // IPv4 socket we can print as IPv6 mapped + if (((struct sockaddr*)&addr)->sa_family == AF_INET) { + uint8_t buffer[16] = { 0 }; + buffer[10] = 0xFF; + buffer[11] = 0xFF; + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + memcpy(&buffer[12], (uint8_t*)&s->sin_addr.s_addr, 4); + return IPv6Address(buffer); + } + // IPv6 + if (((struct sockaddr*)&addr)->sa_family == AF_INET6) { + struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)&addr; + return (IPv6Address(saddr6->sin6_addr.s6_addr)); + } + log_e("WiFiClient::remoteIP Not AF_INET or AF_INET6?"); + return (IPv6Address()); } uint16_t WiFiClient::remotePort(int fd) const @@ -579,6 +623,11 @@ IPAddress WiFiClient::remoteIP() const return remoteIP(fd()); } +IPv6Address WiFiClient::remoteIP6() const +{ + return remoteIP6(fd()); +} + uint16_t WiFiClient::remotePort() const { return remotePort(fd()); diff --git a/libraries/WiFi/src/WiFiClient.h b/libraries/WiFi/src/WiFiClient.h index 5aaa6ba115e..510beb1427a 100644 --- a/libraries/WiFi/src/WiFiClient.h +++ b/libraries/WiFi/src/WiFiClient.h @@ -95,6 +95,8 @@ class WiFiClient : public ESPLwIPClient IPAddress remoteIP() const; IPAddress remoteIP(int fd) const; + IPv6Address remoteIP6() const; + IPv6Address remoteIP6(int fd) const; uint16_t remotePort() const; uint16_t remotePort(int fd) const; IPAddress localIP() const; @@ -106,4 +108,9 @@ class WiFiClient : public ESPLwIPClient using Print::write; }; +#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))) + #endif /* _WIFICLIENT_H_ */ diff --git a/libraries/WiFi/src/WiFiMulti.cpp b/libraries/WiFi/src/WiFiMulti.cpp index 9e7f03c6531..52c9b2bf41e 100644 --- a/libraries/WiFi/src/WiFiMulti.cpp +++ b/libraries/WiFi/src/WiFiMulti.cpp @@ -30,7 +30,6 @@ WiFiMulti::WiFiMulti() { - ipv6_support = false; } WiFiMulti::~WiFiMulti() From 4f63daceaa98b5a932016a3065188381ef6de78d Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sun, 8 May 2022 08:51:23 +0300 Subject: [PATCH 3/6] 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 | 18 +++++++++--------- libraries/WiFi/src/WiFiServer.h | 17 +++++++++++++---- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/libraries/WiFi/src/WiFiServer.cpp b/libraries/WiFi/src/WiFiServer.cpp index db21858125b..03e3c797e83 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,14 @@ 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; + server.sin6_addr = _addr; + server.sin6_port = htons(_port); if(bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) return; if(listen(sockfd , _max_clients) < 0) @@ -106,8 +106,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..d516e5502d2 100644 --- a/libraries/WiFi/src/WiFiServer.h +++ b/libraries/WiFi/src/WiFiServer.h @@ -23,12 +23,14 @@ #include "Server.h" #include "WiFiClient.h" #include "IPAddress.h" +#include "lwip/inet.h" +#include "lwip/netdb.h" class WiFiServer : public Server { private: int sockfd; int _accepted_sockfd = -1; - IPAddress _addr; + in6_addr _addr; uint16_t _port; uint8_t _max_clients; bool _listening; @@ -37,12 +39,19 @@ 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) { + WiFiServer(uint16_t port=80, uint8_t max_clients=4):sockfd(-1),_accepted_sockfd(-1),_port(port),_max_clients(max_clients),_listening(false),_noDelay(false) { log_v("WiFiServer::WiFiServer(port=%d, ...)", port); + _addr = IN6ADDR_ANY_INIT; } - WiFiServer(const IPAddress& addr, uint16_t port=80, uint8_t max_clients=4):sockfd(-1),_accepted_sockfd(-1),_addr(addr),_port(port),_max_clients(max_clients),_listening(false),_noDelay(false) { + WiFiServer(const IPAddress& addr, uint16_t port=80, uint8_t max_clients=4):sockfd(-1),_accepted_sockfd(-1),_port(port),_max_clients(max_clients),_listening(false),_noDelay(false) { log_v("WiFiServer::WiFiServer(addr=%s, port=%d, ...)", addr.toString().c_str(), port); + char buffer[64] = { 0 }; + // Print IPv4 in IPv6 notation + sprintf(buffer, "::FFFF:%s", addr.toString().c_str()); + int rc = inet_pton(AF_INET6, buffer, &_addr); + if (rc != 1) { + log_e("WiFiServer::WiFiServer unable to resolve address, rc=%d", rc); + } } ~WiFiServer(){ end();} WiFiClient available(); From 9337b5c8499023ac15fff578d5cc53763ad6d830 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sun, 8 May 2022 08:56:52 +0300 Subject: [PATCH 4/6] 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 7b27e78c93428313f5af46c44fdcadc00e910d63 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sun, 8 May 2022 17:10:04 +0300 Subject: [PATCH 5/6] 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 | 42 ++++++++++++++++++++----- libraries/WiFi/src/WiFiClient.h | 2 ++ libraries/WiFi/src/WiFiGeneric.cpp | 49 ++++++++++++++++++++++++++++++ libraries/WiFi/src/WiFiGeneric.h | 6 ++++ 4 files changed, 92 insertions(+), 7 deletions(-) diff --git a/libraries/WiFi/src/WiFiClient.cpp b/libraries/WiFi/src/WiFiClient.cpp index e00e9f0f35f..d9587f6ff95 100644 --- a/libraries/WiFi/src/WiFiClient.cpp +++ b/libraries/WiFi/src/WiFiClient.cpp @@ -210,22 +210,43 @@ int WiFiClient::connect(IPAddress ip, uint16_t port) { return connect(ip,port,_timeout); } + int WiFiClient::connect(IPAddress ip, uint16_t port, int32_t timeout) { + ip_addr_t dstip; + dstip = IPADDR_ANY_TYPE_INIT; + IP_ADDR4(&dstip, ip[0], ip[1], ip[2], ip[3]); + return connect(dstip, port, timeout); +} + +int WiFiClient::connect(ip_addr_t 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_IS_V6(&ip)) { + sockfd = socket(AF_INET6, SOCK_STREAM, 0); + struct sockaddr_in6 *tmpaddr = (struct sockaddr_in6 *)&serveraddr; + memset((char *) tmpaddr, 0, sizeof(struct sockaddr_in6)); + tmpaddr->sin6_family = AF_INET6; + inet6_addr_from_ip6addr(&tmpaddr->sin6_addr, ip_2_ip6(&ip)); + tmpaddr->sin6_port = htons(port); + } + if (sockfd == -1 && IP_IS_V4(&ip)) { + sockfd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in *tmpaddr = (struct sockaddr_in *)&serveraddr; + memset((char *) tmpaddr, 0, sizeof(struct sockaddr_in)); + tmpaddr->sin_family = AF_INET; + tmpaddr->sin_addr.s_addr = ip4_addr_get_u32(&ip.u_addr.ip4); + 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); @@ -294,6 +315,13 @@ 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; + } + return connect(srv6, port, timeout); + } IPAddress srv((uint32_t)0); if(!WiFiGenericClass::hostByName(host, srv)){ return 0; diff --git a/libraries/WiFi/src/WiFiClient.h b/libraries/WiFi/src/WiFiClient.h index 510beb1427a..e66772f9c2d 100644 --- a/libraries/WiFi/src/WiFiClient.h +++ b/libraries/WiFi/src/WiFiClient.h @@ -24,6 +24,7 @@ #include "Arduino.h" #include "Client.h" #include +#include "lwip/ip_addr.h" class WiFiClientSocketHandle; class WiFiClientRxBuffer; @@ -49,6 +50,7 @@ class WiFiClient : public ESPLwIPClient WiFiClient(); WiFiClient(int fd); ~WiFiClient(); + int connect(ip_addr_t ip, uint16_t port, int32_t timeout); int connect(IPAddress ip, uint16_t port); int connect(IPAddress ip, uint16_t port, int32_t timeout); int connect(const char *host, uint16_t port); diff --git a/libraries/WiFi/src/WiFiGeneric.cpp b/libraries/WiFi/src/WiFiGeneric.cpp index 25de03ab96d..7b9b1c31b09 100644 --- a/libraries/WiFi/src/WiFiGeneric.cpp +++ b/libraries/WiFi/src/WiFiGeneric.cpp @@ -1335,6 +1335,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 @@ -1362,6 +1381,36 @@ 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 22caa17f529..6957e68d73e 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: @@ -210,6 +215,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 01dd9c45136b421cbf1d96330624fda52912c189 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sun, 8 May 2022 17:50:46 +0300 Subject: [PATCH 6/6] IPv6 support for WiFiClientSecure / ssl_client Similar to implementation in WiFiClient, we implement WiFiClientSecure with IPv6 support with minimal differences. 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 using https:// urls: 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/WiFiClientSecure/src/ssl_client.cpp | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/libraries/WiFiClientSecure/src/ssl_client.cpp b/libraries/WiFiClientSecure/src/ssl_client.cpp index 6ab9b10570d..92d429e2669 100644 --- a/libraries/WiFiClientSecure/src/ssl_client.cpp +++ b/libraries/WiFiClientSecure/src/ssl_client.cpp @@ -60,6 +60,7 @@ int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t p int ret, flags; int enable = 1; log_v("Free internal heap before TLS %u", ESP.getFreeHeap()); + bool is_ipv4 = true; if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure && !useRootCABundle) { return -1; @@ -67,24 +68,45 @@ int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t p log_v("Starting socket"); ssl_client->socket = -1; + struct sockaddr_storage serveraddr; + ip_addr_t dstip; + IPAddress srv((uint32_t)0); + + if (WiFiGenericClass::getStatusBits() & WIFI_WANT_IP6_BIT) { + ip_addr_t srv6; + if(!WiFiGenericClass::hostByName6(host, srv6)){ + return -1; + } + if (IP_IS_V6(&srv6)) { + is_ipv4 = false; + ssl_client->socket = lwip_socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + struct sockaddr_in6 *tmpaddr = (struct sockaddr_in6 *)&serveraddr; + memset((char *) tmpaddr, 0, sizeof(struct sockaddr_in6)); + tmpaddr->sin6_family = AF_INET6; + inet6_addr_from_ip6addr(&tmpaddr->sin6_addr, ip_2_ip6(&srv6)); + tmpaddr->sin6_port = htons(port); + } else { + srv = ip4_addr_get_u32(&srv6.u_addr.ip4); + } + } else { + if(!WiFiGenericClass::hostByName(host, srv)) + return -1; + } + if (is_ipv4 == true) { + ssl_client->socket = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + struct sockaddr_in *serv_addr = (struct sockaddr_in*)&serveraddr; + memset(serv_addr, 0, sizeof(serv_addr)); + serv_addr->sin_family = AF_INET; + serv_addr->sin_addr.s_addr = srv; + serv_addr->sin_port = htons(port); + } - ssl_client->socket = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ssl_client->socket < 0) { log_e("ERROR opening socket"); return ssl_client->socket; } - IPAddress srv((uint32_t)0); - if(!WiFiGenericClass::hostByName(host, srv)){ - return -1; - } - fcntl( ssl_client->socket, F_SETFL, fcntl( ssl_client->socket, F_GETFL, 0 ) | O_NONBLOCK ); - struct sockaddr_in serv_addr; - memset(&serv_addr, 0, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - serv_addr.sin_addr.s_addr = srv; - serv_addr.sin_port = htons(port); if(timeout <= 0){ timeout = 30000; // Milli seconds. @@ -97,7 +119,7 @@ int start_ssl_client(sslclient_context *ssl_client, const char *host, uint32_t p tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; - int res = lwip_connect(ssl_client->socket, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); + int res = lwip_connect(ssl_client->socket, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); if (res < 0 && errno != EINPROGRESS) { log_e("connect on fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); close(ssl_client->socket);