-
Notifications
You must be signed in to change notification settings - Fork 7.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding IPv6 support to arduino-esp32 #6712
Conversation
This patch partially depends on: espressif/esp32-arduino-lib-builder#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: espressif#6242 Signed-off-by: Denys Fedoryshchenko <[email protected]>
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 <[email protected]>
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 <[email protected]>
To demonstrate new abilities of dual stack WiFiServer and remoteIP6 we add this example. Signed-off-by: Denys Fedoryshchenko <[email protected]>
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 <[email protected]>
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 <[email protected]>
Additional note, as we use this optional flag, it is addressing many users fears, that they dont want to get IPv6 address on their device. So by default it wont take it, unless they do IPv6(true). |
Any feedback? :) |
To use IPv6 we need proper SLAAC support which is not possible without this option. Compile tested on esp32, esp32s2, esp32s3, esp32c3 Functionality tested in esp32 BasicHttpClient with some minor patches, IPv6 start to work. No significant sketch size increase (probably within rounding bounds): Before: Wrote 875328 bytes (558896 compressed) at 0x00010000 in 9.0 seconds (effective 779.4 kbit/s)... After: Wrote 875328 bytes (558942 compressed) at 0x00010000 in 9.0 seconds (effective 779.8 kbit/s)... This patch part of the efforts mentioned in espressif/arduino-esp32#6242 Proper IPv6 support also was requested in: espressif/arduino-esp32#6626 espressif/arduino-esp32#6590 espressif/arduino-esp32#6283 espressif/arduino-esp32#6703 espressif/arduino-esp32#5624 espressif/arduino-esp32#1261 And many others. Signed-off-by: Denys Fedoryshchenko <[email protected]>
Hi @nuclearcat sorry for the late reply. I have merged the PR in lib-builder and IDF support will come in 2.0.4. |
Thanks! Will keep an eye, maybe meanwhile i will bring some good use-cases for IPv6 for new code |
I am going to use ESP32-S3 to run an IPV6 web server, and I can access index.html in ESP32-S3 FLASH on any networked device through IPV6 address. Can version 2.0.4 implement this function? |
Hi @Meekdai , with patches in this PR - yes. I am not sure vanilla 2.0.4 can run ipv6 web server properly. |
Hi @nuclearcat |
I am afraid it will be a bit difficult then to test ipv6 properly, without manual building, until this PR is merged. |
This PR is really important... how soon can it be merged? (I'm going to see if I can reference the branch in PlatformIO). Trying to get IPv6 working, and have the basic network (i.e. enable and get address) working, except then HTTPClient fails on something like If you need any help testing, let me know. |
I might make docker image with this PR and arduino-cli to build and flash some test arduino programs, if that will be useful. |
@sgryphon we plan to merge this for next major release 2.1.0. |
Is "libraries/WiFi/src/WiFiUdp.cpp" also need to be update ? |
Throwing a spanner into the works, I think this should be re-worked before merging it: #7114 I know a separate IPv6Address.h class has already been added to the core, but I think it is a bad design, and complicates things. e.g. It has meant adding duplicate methods everywhere, so there are both The approach taken in ESP8266 leads to a lot simpler code: https://github.com/esp8266/Arduino/blob/master/cores/esp8266/IPAddress.h This makes IPAddress a wrapper around the Espressif ip_addr_t union, so that it supports both IPv4 and IPv6. This means you only need one The format of an IP address should be an implementation detail, hidden from the user. Suggestion (and I'm happy to help with the coding changes);
|
I implemented it as new classes/functions for better backward compatibility, to avoid breaking existing code. |
Provided you are only adding new features, then it should not break any existing code. e.g. if the old IPAddress had constructors for 4 bytes, that will continue to work, even if you add a second constructor with 16 bytes. The new class would be fully backwards compatible (i.e. can do everything the old class can do). There could be some corner cases where client code is relying on the implementation, rather than the interface, e.g. if it relies on the storage size being 4 bytes, which could be important for a resource constrained device. The ESP8266 approach had a nice solution in that if compiled with IPv6 turned off, then it falls back to the smaller memory footprint. Maybe when considering forward compatibility, i.e.the ability of old code to handle the new features, such as if old code gets handed a new address. Certainly not allowing it at all (by adding a new class) might help in some corner cases, but also mean they are locked out of the new features, with is probably a larger negative. In general I don't think people want to care about the low level details of which IP address they are using. If you recompile with components that support IPv6, and then deploy into an IPv6 only network, the same code will just continue to work (except with slightly larger memory requirements). |
If it is useful, I have put a proposal pull release in to the ArduinoCore-API to add IPv6 support to the standard API: arduino/ArduinoCore-API#169 The code is slightly different from ESP8266. The public interface changes are minimal:
Instead of type(), the ESP8266 implementation has isV4() and isV6(); in ESP8266 there is also a constructor from ip_addr_t, which we would want in the ESP32 implementation (but is not relevant for ArduinoCore-API). There are also a bunch of other constructs in ESP8266, which I have kept out of the minimal implementation, e.g. a |
IPv6 support is now in ArduinoCore-API. I've copied the changes into a branch in my fork, which could be integrated into this change. https://github.com/sgryphon/arduino-esp32/tree/feature/ipaddress-v6-support It means you can avoid things like (With IPv4 work exactly the same as it did before, just new support for IPv6 directly in IPAddress rather than using ip_addr_t). |
Thanks! Since it became a standard approach, i think will work on it this weekend to update my changes |
The build / github actions for this project are a bit more complicated than for ArduinoCore-API, so I'm not sure how to build/compile locally (or run tests). Is there some instructions? |
@@ -95,6 +97,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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One complexity with IPv6 is that with IPv4 usually you will only have one local IP address (public address, private network, or if there is no network a link-local 169.254 address), however with IPv6 you will almost always have multiple addresses -- at least both an autoconfigure link local (fe80: address) as well as an address with the network global prefix, and possibly a ULA prefix as well, and outgoing addresses with privacy enhancements may have rotating outgoing addresses.
One way I was handling this was with the signature IPAddress localIP(uint8_t index = 0)
, returning one value for each index (or NULL / none once they run out), with another signature int8_t getLocalIPs(IPAddress addresses[], uint8_t max)
to get multiple addresses, filling an array up to the max (and returning the number of addresses).
Usually you don't want to do a lot with the local address except display it, but with IPv6 you want to check which one to display.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we can just add globalIP6 and localIP6? This will keep it simple for Arduino users. We can iterate and by prefix identify who is local and who is global.
Privacy enhancements as far as i know is not available in lwip, yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Be careful with terminology not to mix up the concepts of "local" and "link-local", as they are different things.
Local vs remote: Local means this end of the connection, i.e. the IP address of the device, as opposed to remote, which is the other end.
Link-local vs global are scopes: Link-local is confined to the local link only, global is a routeable.
You can have both.
With IPv4 you usually only have one local address, and it usually has a global scope. With IPv6 you have multiple local addresses, both link-local and global scopes.
Generally the global scoped addresses are more useful, e.g. to use as a server address to connect to a device, or when looking in logs. (Although not always, e.g. if the destination machines is on the same link, then IPv6 will use the link-local address.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Examples, from a SIMCOM GSM NB-IoT library I am working on: https://github.com/sgryphon/AdvancedGsmClient/blob/main/src/SIM7020/SIM7020GsmModem.cpp#L470
When I connect the NB-IoT modem, it gets assigned 3 local addresses, plus it's address list includes a localhost address:
Local addresses:
- 2001:8004:4810:0:719d:1439:899a:42d7 - local IPv6 with global scope
- 10.88.134.167 - local IPv4 with global scope
- fe80:0:0:0:719d:1439:899a:42d7 - local IPv6 with link-local scope
- 127.0.0.1 - local IPv4 with link-local scope (loopback addresses count as link-local).
The actual order the addresses are reported by the device is fe80:, 2001:, 10.88, 127.0, however my getLocalIPs()
method sorts them from largest scope (global) to smallest scope (link-local), and then within each scope according to the RFC 6724 precedence rules (which includes IPv6 before IPv4), to get the order listed above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my local desktop computer, sorting my addresses a similar way (but using the bytes; my simple GSM algorithm is using strings and so sorts ':' differently), to sort them in order of usefulness:
- 2407:8800:bc61:1300::8e6 - local IPv6 with global scope
- 2407:8800:bc61:1300:4a23:22d5:6c48:f0e8 - local IPv6 with global scope
- 2407:8800:bc61:1300:4df2:1392:9dd7:d2d7 - local IPv6 with global scope
- 2407:8800:bc61:1300:ee48:5c84:ad87:c65c - local IPv6 with global scope
- 192.168.1.195 - local IPv4 with global scope
- fd7c:e25e:67e8::8e6 - local IPv6 with global scope, in ULA range
- fd7c:e25e:67e8:0:2c73:36d3:d325:93df - local IPv6 with global scope, in ULA range
- fd7c:e25e:67e8:0:9ac7:63c9:5f26:b026 - local IPv6 with global scope, in ULA range
- fd7c:e25e:67e8:0:a9ed:c900:59fa:5449 - local IPv6 with global scope, in ULA range
- fe80::5f65:961a:14b4:6263 - local IPv6 with link-local scope
Global scope is sorted before link-local (because it is more useful to contact me as a server), with the precedence order IPv6, then IPv4, then IPv6 unique local address (ULA).
As a server the ::8e6 address is better, although outgoing logs will use the temporary addresses (probably not an issue for Arduino). If I was communicating with an IPv4 only server, then it would see the IPv4 address.
So, to really investigate I would need to list all of my addresses (e.g. in logs). Even the ULA addresses could be used, for local communications, and the IPv6 link-local address could even be used in some cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really want to stick with separate properties for IPv6 and IPv4, for only one of each type, then you want to try and return a global scope address if possible:
localIP4()
- Shows the local IPv4 address (this will usually be a global scope address); there is usually only one. To be consistent, in case there is more than one IPv4, you should also sort by scope (e.g. if a device has two IP addresses 169.254.1.2 and 10.1.2.3, then show 10.1.2.3 ... although that would be really weird as IPv4 doesn't work that way).localIP6()
- shows the first IPv6 address, sorting global scope before link-local scope.localIP()
- for a single address should show the biggest scope, highest precedence address. i.e. global IPv6 if you have one, otherwise global IPv4, and then finally (if you have no global addresses), then the link-local IPv6.
I do think you should have a getLocalIPs() that returns all of the addresses, which you could then use to select out the other values, e.g. for logging, although in the case of the GSM code I know it only ever gets max 4 addresses (i.e. no temporary, only one IPv6 prefix, etc), so I just log the first 2 address, which will be the one global IPv6 and the one global IPv4.
I started to convert my patch to new IPAddress contributed by @sgryphon: https://github.com/nuclearcat/arduino-esp32/tree/ipv6-support-v2 |
I'll see when I have a chance to look in more detail. In the meanwhile: (1) This may not be applicable for Arduino devices as limited capacity, not a lot of multithreading, can't abort, etc. Plus constraints we may just have to configure as known IPv6 or IPv4. (2) If you look at the code I have in another project: https://github.com/sgryphon/AdvancedGsmClient/blob/main/src/SIM7020/SIM7020GsmModem.cpp#L470 This sorts addresses based on largest scope (i.e. we want to show a global address), and then by the RFC 6724 precedence. (The sorting is done on strings, as IPAddress in ESP32 doesn't have IPv6 yet). |
Closed in favor of #7520 |
This patch partially depends on:
espressif/esp32-arduino-lib-builder#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: #6242
Signed-off-by: Denys Fedoryshchenko [email protected]