Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

IPv6 discussion #9009

sgryphon started this conversation in General
Dec 17, 2023 · 9 comments · 25 replies
Discussion options

Here is a bunch of discussion points around enabling good support for IPv6. Rather than spread across multiple issues and pull requests, I thought I would start a discussion to have a central place to discuss.

Agnostic code

Code should in general be agnostic as to whether the underlying address is IPv4 or IPv6.

e.g. address = hostByName("api.foo.com"); connect(address); should just work, without any change, irrespective of whether it is IPv4 or IPv6.

If api.foo.co currently only has IPv4 addresses, and in the future changes to IPv6, it should not matter to your code.

Having to write additional special code, like a1 = hostByName4(); a2 = hostByName6(); best = chooseBest(a1, a2); connect(best), just to support IPv6 would not be good.

Backwards compatibility

Should not be an issue.

If you compile without IPv6 support; or for pre-compiled have a flag like WIFI_WANT_IP6_BIT that you can turn off/on, then things should work.

  • Code that tries to store values in 4 bytes
  • Output to a string of only 15 characters
  • Compare to a stored string
  • User input of only 15 characters

If IPv6 is not enabled then all functions, like checking the local address or doing a DNS lookup will only ever return IPv4 addresses.

For example in the above code, if your app outputs address.toString() to a 16 character buffer, and crashes when you turn on IPv6, then don't turn it on. You will continue to only get IPv4.

When you fix your app, e.g. increase buffer size, then all you should need to do is enable IPv6 and it will work. (You don't want to also have to update all your code to process IPv6 different from IPv4.)

Multiple addresses

This is a big change you probably need to make in application architecture (more difficult than just increasing string buffer sizes).

With IPv4 you generally only have a single address, e.g. you will have either a public IP address, e.g. 203.0.113.5, or a private IP address, e.g. 192.168.1.10 or 10.1.2.34, or a link-local address, e.g 169.254.0.15.

It is possible for IPv4 to have multiple addresses, but relatively rare.

With IPv6 you almost always have multiple addresses (per interface), e.g. my local computer has 9 addresses (for one interface): a link-local address fe80::db8:3926:aff:7ccf, DHCP address from my delegated prefix 2001:db8:bc61:1300::540, DHCP address for my ULA range fd01:db8:67e8::540, auto-configure EUI-64 addresses from both the delegated prefix and ULA, and randomised RFC 4941 outgoing addresses for privacy.

This means functions like localIP() that return a single value for local IP address are not that useful for IPv6, and any of the addresses (or your IPv4 one for dual stack) may appear in logs.

Similarly hostByName() could always potentially have multiple destination addresses, even for IPv4, but with IPv6 it will be much more common. Still, trying a single connection to "the best" is an okay approach for a simple app (especially if you know you only have one endpoint machine, i.e. not a whole server farm).

Note that backwards compatibility is not a concern: if you don't support IPv4, and so have IPv6 turned off, then hostByName() will never accidentally give you and IPv6 address that you can't handle.

You need to first make sure your app supports IPv6 (which may require no changes if you don't have anything that relies on the fixed IPv4 format), then turn on IPv6, and only then will you potentially get an IPv6 address (if it is the best one).

Multi-address support

This is independent of updating to support IPv6 for existing interfaces, but probably a good idea to make transition easier.

This involves adding functions like localIPs() that will fill an array of local addresses, e.g. for display/debug of all addresses being used. Or hostsByName() that will return an array of addresses to loop through and try.

Algorithms like RFC 6555 Happy Eyeballs help to provide a good experience even when IPv6 is slow or broken, by trying IPv4 in parallel (with a preference for IPv6 if both succeed).

When there are multiple addresses the first one tried should be IPv6, but then the next should be the first IPv4 address; after that, it is a fall back scenario and so any order is okay.

This still needs an array of all addresses (of different types); having separate hostByName4() and hostByName6(), each with only a single address, is not a good solution as it acknowledges multiple addresses but only provides access to two of them.

If you are going to improve code to support multiple addresses, then you will want all addresses, i.e. hostsByName() returning an array.

If you are happy to stick with a single address, then the existing hostByName() is sufficient -- in an IPv4 environment, it will only ever return any IPv4 address; when you support IPv6 and turn IPv6 on, it will return the best address.

Network utilities

While most applications don't care about the type of IP address .. they just want to reach api.google.com by whatever the best available method is... some utility applications may want to specifically test IPv4 vs IPv6.

In those cases, returning an array hostsByName() of multiple addresses is still a better solution, as they can try all addresses. Separately returning hostByName4() and hostByName6() will only give one of each type, which may not be helpful if some addresses are reachable and some not.

Being able to filter the addresses may be useful for some utilities, i.e. hostsByNameByType(type = IPv4), maybe useful in some cases, but even for single type a utility will still want to know if there are multiple addresses (of that type), not just one.

Address selection

When you have multiple addresses then you have the question of address selection, e.g. which do you use, or show on screen for troubleshooting as "my IP address", or use when trying to connect.

RFC 6724 Address Selection covers a similar scenario, but is for low level network connection between hosts.

Example: Host A has addresses 2001:db8:1:2::300, fe80::db8:4:5:6, and 203.0.113.5; Host B has addresses fe80::db8:7:8:9, and 203.0.113.10.

A connection from A to B will use the fe80::db8:4:5:6 as outgoing and fe80::db8:7:8:9 as destination, as they are link-local, i.e. both computers are on the same local network. But if Host B only had 203.0.113.10 (not on the local link), then A would use outgoing 203.0.113.5.

We can use the principles of RFC 6724 when selecting a single address for something like localIP(), by assuming that in most cases the destination will be probably be a global address and smaller scopes are less likely. Instead of the smallest scope first we use the largest.

For Host A this means localIP() would report 2001:db8:1:2::300 as the global IPv6 address.

However for Host B, it would report 203.0.113.10 as the global address, whilst the IPv6 is only a link-local address.

If you are troubleshooting or looking in logs, then those global addresses are more likely to be the ones you want to look for. Sure, Host A might need to connect to an IPv4 only endpoint and uses it's IPv4 address; or Host B might connect to an endpoint on the local network and use the link-local. But if we can only return one, then the best is probably determined by the rules in RFC 6724, with reversed scope.

If possible, the app should be improved to handle multiple addresses, and all relevant addresses reported, as any of them could be needed for troubleshooting.

Similarly, if we only have one address for hostByName() then, when an IPv6 global address is in the results (IPv6 is enabled + local network supports IPv6 and has assigned an address + the destination has an IPv6 address), it should be the one reported, and then IPv4 global, and if neither then IPv6 link-local, etc.

The same rules can be used for ordering the results when returning multiple addresses, i.e. addresses[0] is the same as the best single result.

Usage scenarios

App does not support IPv6

e.g. crashes when you call toString() and try to instert into a small string; other hard coded IPv4 stuff.

Approach:

  • Don't turn on IPv6
  • All code should continue to be backwards compatible and never return any IPv6 results

Want basic use of IPv6

Precondition is your app supports IPv6, e.g. doesn't try to insert an address into a small string. This includes any necessary changes to support IPv6, e.g. long enough output strings, allowing user input, validation, etc.

To actually utilise IPv6 you also need to be accessing IPv6 servers and have IPv6 on local network.

  • Enable IPv6
  • All existing network related functions should accept IPv6, e.g. hostByName() will return an IPv6 address if available; if an IPv6 address is passed into connect() then it will work; etc.
  • IPv4 will also still work, e.g. a hardcoded IPv4 address in connect() will still work.
  • App will still be backwards compatible, e.g. if you run in an IPv4 only network, it will still work.
  • While anything that would fail with IPv6 (e.g. short string output) needs updating, no other changes should be needed in the code. Code address = hostByName(); connect(address) will continue to work, without change (just it will now be using IPv6 instead of IPv4)

Advanced usage of IPv6

Includes happy eyeballs, support of multiple DNS results, full diagnostics information (all addresses), and network utility apps (that care about IPv4 vs IPv6).

  • You still need to make necessary changes, e.g. long enough output areas.
  • Additional changes will be needed to support multiple results, e.g. output multiple addresses
  • New APIs for localIPs() and hostsByName() that return multiple results.
  • As fixed memory print is important, suggest have a signature like the following:
int8_t getLocalIPs(IPAddress addresses[], uint8_t max)

Similar to snprintf, the maximum buffer capacity is passed in, so the code knows when to stop filling; it then returns the number of entries actually added. There are multiple other ways this could be implemented, and any of them could work.

These changes are independent of updating existing functions to support IPv6, and optional to use. i.e. basic IPv6 will still work without needing any change to code (other than necessary ones like being able to output long strings)

This will enable advanced scenarios such as networking utilities and where you want partial support of IPv6 (i.e. you want to turn it on, but don't want to update all code to support it/still have some broken code).

Vision: From a legacy app to supporting IPv6

Happy path

Ideally, you can just enable the IPv6 flag, and deploy into an IPv6 network, with IPv6 servers, and code like address = hostByName("api.foo.com"); connect(address); will just work, with no changes required. At the same time, if you deploy to an IPv4 only network, or try to connect to an IPv4 server, it will also just work.

i.e. Even if api.foo.com has an IPv6 address in DNS, if you are on an IPv4 network, then hostByName() should return the IPv4 address. Anything stored or input as a fixed IPv4 address should also work.

The app should also work on an IPv6 only network, with no code change.

Things like printing out the address from localIP() for debugging should also work, i.e. output the IPv6 address if available, and IPv4 if not.

Such an app will have limitations, e.g. it will only support a single IPv6 address even though usually there will be multiple, as well as an IPv4 address, but should still work.

Legacy app with changes required

Examples:

  • Outputting IPAddress to a string with only space for 15 characters; either you won't be able to see it, or the app might crash.
  • User input only allowing 15 characters
  • Validation checks that expect IPv4
  • Hard coded IPv4 address (advanced scenario, this would break IPv6 only scenarios, even with DNS64/NAT64)

For these cases, you need to leave IPv6 turn off until you fix your app, or can live with the limitations. With IPv6 turned off, all functions should work as before, nothing should crash (i.e. never return an IPv6 result).

Once you have fixed the broken parts above, then it should be as simple as the happy path above, i.e. just enable IPv6.

Different levels of fix will be required for different scenarios. e.g. hard coded IPv4 addresses will work in a dual stack, but not support transition technologies. For example DNS64/NAT64 can allow IPv6 only networks to access IPv4 resources through translating DNS to special IPv6 addresses. If you have a hard coded domain name, then the DNS64 lookup will return a special IPv6 address (that will work to access the IPv4 only server), but a harded coded IPv4 will not.

Advanced scenarios

The happy path still only has one localIP() and one hostByName(), which may not be sufficient e.g. if you want to check logs for all local IPs. For IPv4 in most cases a single address is sufficient, but IPv6 usually multiple IPv6 addresses in addition to the IPv4.

So, a function like getLocalIPs() that fills an array with multiple addresses is needed. Usually this will return a multiple IPv6 and a single IPv4, all of which need to be displayed for diagnostics (as connections could use any of them).

To access these advanced features, app changes will be necessary, but should not be more complicated than necessary.

You must be logged in to vote

Replies: 9 comments 25 replies

Comment options

Does this sound like a plan:

  1. Initial support of IPv6:
  • No change in APIs
  • Only enable IPv6 if your code supports it (e.g. you don't try to stick values into small strings); if you don't enable, then the code should function the same as before.
  • Update implementation to return IPv6 where relevant (e.g. hostByName(), localIP()), but only if it is the "best" address. Where best is the one with the largest scope, preferring IPv6 if available. e.g. prioritise public IPv6 "2001:db8:...", then public IPv4 "203.0.113...", and only after that return link-local IPv6 "fe80:..."
    • If you don't prioritise properly, e.g. put IPv4 before IPv6, then dual stack won't work as intended, but if you put all IPv6 before IPv4 then that won't work either, as you will get link-local IPv6 addresses.
  • Update functions that take IPAddress parameters to work with both v4 and v6 inputs.

Note: This will not support advanced scenarios like network tools that want to ping both v4 and v6, because they will only get a single address result: with enable IPv6 off then it will always be IPv4, but with enable IPv6 on (and if there is an IPv6 result), then a single IPv6 will be returned.

  1. Advanced support -- maybe defer this to a later release, or at least a separate pull request.
  • Add functions to return multiple results, e.g. fill an array
  • Most apps won't care about these; the single result basic implementation will be sufficient, i.e. single IPv6 if enabled (and available), or single IPv4 if not.
  • Advanced scenarios like network tools will be able to get all of the IPv4 and multiple IPv6 addresses, i.e. this means at least 3 addresses (devices will usually have at least 2x IPv6)

Note that an intermediate scenario, where there are IPv6 specific functions that only return a single address, e.g. localIP6(), are not part of my suggested plan, as they are not very useful. They require rewrite for an app that would otherwise already support IPv6 (i.e. they don't do anything that would crash), and with IPv6 a single address is rarely sufficiently useful.

You must be logged in to vote
1 reply
Comment options

I have a working app with dual-stack IPv6, but noticed something strange, on WiFi it prefers IPv4 and on ethernet it prefers IPv6 and I can't understand why. Testing with WebsocketClient. Is this expected or should investigate it further as a bug?

Release v3.3.0. Thank you!

Comment options

I think the only way you can have an almost "no effort" transition to IPv6 is when your code doesn't do anything with IP-addresses at all.
Thus all access to the outside world is then done via hostnames.

Right now the API code also allows to have an string formatted IP-address as 'hostname', thus this should also be possible to use IPv6-formatting as hostname (e.g. [aaaa:bbbb::f] )

However when your code is using IPAddress, you may run into the many issues you described.

I think the simplest way to grasp for beginners is to have some structure which can store 16 byte IPv6 and also deal with IPv4.
After all there is already an official "IPv4-mapped IPv6 addresses" system, so why not use this and let the code of the IPAddress class handle this (so don't store extra flags on derived info).
This also needs some proper testing of the code. For example; Is a default constructed IPAddress object with only 0's IPv4 or IPv6?
So the code should not check first for IPv4/IPv6, but rather if it is an unset (or "ANY") address.

IMHO the sizeof(IPAddress) should not exceed 16 bytes.
Or am I missing some use case where a property of an IPv6 address cannot be determined simply by its 128-bit address?

This also requires adding extra API calls to get multiple results.
We have multiple options here, each with their own drawbacks.

  • Hand over an user allocated array for the API call to fill and return the amount of available addresses
  • Use iterators to get the 'next' address.

The first one makes the user responsible for memory management.
The second one requires keeping the structure to iterate until it is freed, much like WiFi scan results.

I think the second one is the better option here as it also allows for async use where you could make a DNS lookup request with a callback function and/or an event.

You must be logged in to vote
2 replies
Comment options

Multiple questions, some thoughts:

  1. Using the address itself to determine the type.

There are both IPv4-Compatible IPv6 addresses (deprecated) and IPv4-Mapped IPv6 Addresses.

You probably could use IPv4-Mapped addresses to store the type, and save an extra bit of storage. (As they are usually treated as if they were IPv4)

  1. The any/zero address

The main difference seems to be by convention binding to "0.0.0.0" means any incoming IPv4 address whilst "::" means any incoming address (4 or 6).

We probably could still distinguish by storing IPv4 as mapped addresses.

  1. Size of 16 bytes

Well, see the discussion on link-local addresses, which are often useless without the zone. This would make IPAddress represent a Scoped IPv6 Address, with an extra byte (or more) for the zone ID numeric.

LWIP includes zone in ip6_addr as a byte; in the unix library specifications sin6_addr is just the address and it is sockaddr_in6 that has a sin6_scope_id field (as a uint32).

  1. Ways to get multiple addresses

RFC 4038 is a useful read for some application aspecates of IPv6 transition, and seems to favour an iterator type approach. For almost any reasonable implementation you are going to need multiple addresses.

Note another useful document is RFC 4291 for addressing.

Comment options

I think the any/zero address isn't a problem here as you often only would use it in the constructor and when comparing.
And for comparing with the any/zero address it might be very useful to consider it to be both IPv4 and IPv6.
For example a user would expect if (ip == IPADDR_ANY) to return true when it hasn't been set. For this specific address it is irrelevant whether it is IPv4 or IPv6.

This also makes other compares quite simple, as there is no need to check whether you're comparing the same types.
Just compare all 16 bytes and you're done.
The only tricky part would be when checking for IP ranges. (not subnet)
II think this trickyness warrants an extra function: operator<= to be added to the IPAddress class.
And while we're at it, subnet range checks should also be part of this class since both the network address and the IP-address to check are of type IP-address.
For example:

bool inSubnet(const IPAddress & other, const IPAddress & subnetmask);
bool inSubnet(const IPAddress & other, const uint8_t & cdir);

Not 100% sure if the non-CDIR version is officially allowed for IPv6.

About my 16 byte remark, I forgot about the zone.
But I still believe there is no need to store the type as it is derived information.

Comment options

IPv6 Scoped addresses

Link-local addresses apply to only a single link, but may be duplicated if a machine has multiple interfaces. In such a situation, with multiple link-local addresses, you need to know the zone -- which interface to use.

In IPv4 link-local addresses are rare, because usually an interface only has one address; they only configure a link-local if DHCP fails, and if they receive an address later they will remove the link-local address. Link-local addresses are thus rarely used with IPv4.

With IPv6 however, an interface will usually have multiple addresses; it will continue to have a link-local address, maybe get a global address from a router advertised prefix (e.g. based on EUI-64, RFC 3513), may also get addresses from DHCP, and may then use random nodes for outgoing privacy (RFC 4941); it may also have multiple prefixes (e.g. global and ULA).

This means that IPv6 link-local addresses are much more widely used, especially in IPv6 only protocols like Matter (and Thread).

The scoped address architecture is described in RFC 4007, with a numerical index for the multiple zones within a scope. The text representation uses the format <address>%<zone_id>, e.g. fe80::1234%1

Although not strictly part of the address itself, a link-local address is often useless without the zone identifier (if you have multiple interfaces, and the default is not suitable), so the zone identifier has been included in the IPv6 construct for networking libraries such as LWIP.

There is also an optional to use text identifiers as alternative indicators for zones, e.g. fe80::1234%ne0, that may be more human-readable (e.g. for debugging).

The current IPAddress structure has been copied from ArduinoCore, with the proposal that Zone ID be added as a field (including to and from text representations).

(I also suggest that this be contributed back upstream to ArduinoCore, so there is a consistent implementation across platforms.)

You must be logged in to vote
7 replies
Comment options

Or maybe, when you say "your local subnet ... reachable without a gateway" you are talking about private addressing in IPv4 (e.g. 192.168.x), commonly used by local networks and not routable over the global internet? That is a very different thing. Although not routable over the global network, private address ranges are routeable (e.g. companies have large 10.x networks).

There is no exact equivalent in IPv6 of private address ranges; their function is provided by Unique Local Addresses, which are not globally routable, but also don't overlap (statistically).

Comment options

Note that scoped addresses do apply to IPv4 link-local addresses (see section 3.1 of the RFC), but given that you need multiple network interfaces, and then need multiple to not have routeable addresses (because having a routeable address drops the IPv4 link-local), then having to worry about representing the scope wasn't standardised (left up to implementors).

The big difference in IPv6 is that you don't lose your link-local when you get a routable address, and can even end up with multiple link-local (e.g. a router might have both fe80::1 and a EUI-64 based link-local). So, a standardised way of handling scope prefixes becomes relevant for IPv6.

Comment options

Or maybe, when you say "your local subnet ... reachable without a gateway" you are talking about private addressing in IPv4 (e.g. 192.168.x), commonly used by local networks and not routable over the global internet?

Nope, I meant "reachable without using a gateway".

For example, take a few servers (or VMs on the same server) which have all a global reachable ("public") IP-address within the same subnet.
Assume 1.2.3.x/24
For administrative purposes, the network administrator has split up these into /24 subnets and the gateway is located at .254 in that subnet.
Thus if a host on 1.2.3.2 needs to talk to 1.2.3.3, there is no need to communicate via the gateway as those are directly reachable.
So regardless whether you have "unroutable" ranges or "public" ranges, each needs a gateway to route beyond their own subnet.

In my local network (at home) I have several subnets and layer-3 switches which act as gateway between the subnets.
For example 192.168.10.0/24 and 192.168.4.0/24 (resp. VLAN10 and VLAN4)
To reach a host on VLAN4 from VLAN10, you need to use a gateway.
The gateways are at .254 in each subnet.
Thus routing between "non-routable addresses" even needs a gateway, but as long as the hosts stay in their own subnet you don't need to use a gateway.

Comment options

"link local is used extremely often on IPv4. It is just your local subnet, or in other words all that is reachable without using a gateway."

Yes, connections to machine on the local link network, i.e. without going through a gateway, are quite common.

These could be a home private range subnet 192.168.10.64/26, or even a large flat subnet 1.2.x.x/16 is possible to have configured without a gateway. Those are on the link-local network, but using global or private range addresses. IPv6 can use the same, i.e. communicate between two public or ULA addresses on the link-local network.

However I was talking about IPv4 link-local addresses being rare, not IPv4 link-local networks. So both are correct, just talking about different things. Sorry for the confusion.

For addressing there are also link-local addresses in IPv4 169.254.0.0/16, which are used in zeroconf and discarded when other IPv4 addresses are obtained, so you rarely see them or use them to communicate in link-local IPv4 (you use addresses like 191.168.10.70).

IPv6 for link-local network you can either use global addresses, e.g. 2407:8800:bc61:1300::50, or because they retain their IPv6 link-local addresses they have a choice to use either global or link-local specific addresses (a choice not usually available in IPv4).

Comment options

Summary:

  • 1.2.3.4/24, 2407:8800:bc61:1340:b0d0:71b6:518a:57d8/64 = are routable over the global (public) internet
  • 192.168.10.0/24, fd7c:e25e:67e8:40::540/64 = are not routable over the global (public) internet, but are routable over private networks (the IPv6 ULAs are also, statistically, unique, so you can easily join and route between private networks)
  • 169.254.5.6/16, fe80::f949:fb86:d418:aaa4/64 = link-local addresses, not routable (only valid within the local link); if a machine has multiple links they need a scope ID "fe80::f949:fb86:d418:aaa4%et1", "169.254.5.6%3"

An machine will have all IPv6 address types, and more. They usually only have one IPv4 address type (per interface).

Comment options

I've now IPv6 working on my project with the current pending PR code to add IPv6 to IPAddress.

While working on it, I also came across this enum in esp_netif_ip_addr.h:

typedef enum {
 ESP_IP6_ADDR_IS_UNKNOWN,
 ESP_IP6_ADDR_IS_GLOBAL,
 ESP_IP6_ADDR_IS_LINK_LOCAL,
 ESP_IP6_ADDR_IS_SITE_LOCAL,
 ESP_IP6_ADDR_IS_UNIQUE_LOCAL,
 ESP_IP6_ADDR_IS_IPV4_MAPPED_IPV6
} esp_ip6_addr_type_t;

I'm not 100% sure whether all of these can be derived from any given IPv6 address.
If this is not possible, then I guess we do need this type byte.
But if it can be derived from the IPv6 address, then I strongly opt for not storing redundant info in the IPAddress object.

You must be logged in to vote
10 replies
Comment options

Hmm, I wonder if it is one per advertised prefix? The link-local is generated automatically. For the global address it needs to wait for a RA (router advertisement) that includes the global prefixes. A default home network probably only has one (delegated from the ISP), but I also have a ULA prefix configured in my router. If there are two advertised prefixes it would likely end up with 3 IPv6 addresses (otherwise, how would it pick which of the advertised prefixes to use).

I also have DHCPv6 running, that will give additional addresses. My computers get them, but most devices don't (e.g. android doesn't have a DHCPv6 client, etc)

Comment options

As far as I know all my other devices have several randomized IPv6 addresses, so it is for sure configured here to allow for non-EUI-64 addresses.
Maybe you need to specifically request those?
Or perhaps there is some flag you need to set in the DHCPv6 request? Or there are multiple given on each request, but the ESP only takes the first which is probably the EUI-64 version?
Enough theories :)

Comment options

An EUI-64 address does not use DHCPv6. DHCPv6 addresses are optional.

  • EUI-64 is a local only thing, used for link-local, and SlAAc addresses.
  • Random outgoing addresses are a privacy extension to SlAAc.
  • DHCPv6 addresses are optional, and there is probably no client implemented.

Checking the LwIP docs, stateful DHCPv6 (for addresses) is not implemented yet: https://www.nongnu.org/lwip/2_1_x/group__dhcp6.html

So with LwIP all you will get is a SlAAc address (probably EUI-64), and maybe the privacy extensions. (Stateless DHCPv6 is just for providing DNS details.)

IPv4:

  • In IPv4, DHCPv4 is the standard mechanism to dynamically assign addresses, so clients basically have to implement it, e.g. so the ESP32 will get an IPv4 address from your network;
  • The fallback (if there is no DHCPv4 server running) is a link-local 169.254.0.0/16 address (rare for this). (If you time out and assign a link-local, then later on get a DHCPv4 address, the link-local is removed)
  • You can, of course, also statically configure an address.

IPv6: Very different.

  • You automatically get a link-local address fe80::/64, usually based on EUI-64 (your MAC address), but you could assign differently (e.g. router fe80::1). You keep this address
  • Stateless autoconfiguration: When you received Router Advertisement (RA) messages they may contain instructions for one or more prefixes. You can combine this with your EUI-64 to get a global address. See RFC 2462 IPv6 Stateless Address Autoconfiguration (SlAAc). A network may have multiple prefixes (e.g. both a delegated global prefix and a ULA)
  • Random outgoing addresses. Once you know the prefix, you can also use random outgoing addresses for privacy (so two connections can't be correlated). See RFC 4941 Privacy Extensions.
  • DHCPv6 also exists, and can be turned on in RA messages. Stateless DHCP is used in conjunction with SlAAc to provide DNS server details.
  • Stateful DHCPv6, for addresses, is optional, not all devices implement it. For devices such as my laptop that does run a DHCPv6 client, it also gets an assigned address (one for each prefix). Many IoT devices do not implement this (as unlike IPv4, SlAAc is a suitable solution). You can usually tell DHCPv6 addresses as they won't be EUI-64 or random, but something short, e.g. 2407:8800:bc61:1300::540 (the first part is the prefix, the assigned DHCPv6 interface identifier is 0:0:0:540, with the zeros then shortened).
  • You keep all the addresses at the same time (and the privacy ones will rotate)
Comment options

I'll see if I can get my M5Stack up and running, which is ESP32 based, and see if it configures SlAAc addresses for both prefixes on my network (yours only gets one global address as it only has one prefix).

A quick check of LwIP docs indicates this should work.

I quick search of LwIP for "4941" didn't turn up anything -- were those other devices with random addresses ESP32, or something else? Because it sounds like they are using RFC 4941 (e.g. my laptop does, but you can turn it on or off).

Comment options

I quick search of LwIP for "4941" didn't turn up anything -- were those other devices with random addresses ESP32, or something else? Because it sounds like they are using RFC 4941 (e.g. my laptop does, but you can turn it on or off).

Other devices, like mobile phones, Raspberry Pi (thus Linux), PC, Servers.
My switches only have IPv4 configured, so VLANs other than the one my router is on will not allow for IPv6 routing. These only act as a IPv4 gateway between VLANs.
Even if the switches would support stateful IPv6, I don't think I would like them to even have global scope addresses.

N.B. The test ESP's with IPv6 are connected to the same VLAN as my router.
N.B.2 My access points only seem to have an EUI-64 global scope address.

Comment options

IPv6-only networks are not working

I put up an issue for this: #9143

(I need to add a simple demo sketch).

Dual-stack works, but IPv6-only isn't working... looks like a DNS issue.

You must be logged in to vote
0 replies
Comment options

Inconsistency of local address accessors in WiFiSTA.h

We have both localIP() and localIPv6(), however there seems to have been confusion between local (as opposed to remote) and link-local addresses. We have also introduced a globalIPv6() accessor, so IPv6 is inconsistent with accessors per type (which IPv4 does not have).

Note that localIP(), for IPv4, could return you a global address, private range address, or link-local IPv4 address, depending on the scenario.

I don't think either of the new accessors is necessary, and we should just have a single IPAddress localIP(uint8_t address_no = 0), similar to dnsIP(), which returns addresses as relevant, and is fully backwards compatible (including with IPv4-only).

This would be similar to localIP() in WiFiClient.h, which could be either IPv6 or IPv4 depending on the connection (as could remoteIP()).

I have raised an issue: #9144

I may have time to work on the code, but wanted to sound out what others thought.

You must be logged in to vote
0 replies
Comment options

I would like to configure my ESP32-S2 to use IPv6, however the example doesn't work. Im using Arduino IDE with the 2.0.14 esp32 board package, I get the following error

 error: 'class WiFiClass' has no member named 'softAPenableIPv6'; did you mean 'softAPenableIpV6'?
 WiFi.softAPenableIPv6();
 ^~~~~~~~~~~~~~~~
 softAPenableIpV6
'class WiFiClass' has no member named 'enableIPv6'; did you mean 'enableIpV6'?
 WiFi.enableIPv6();
 ^~~~~~~~~~
 enableIpV6

Does the ESP32-S2 do not support ipv6 or am I misssing something else?

You must be logged in to vote
1 reply
Comment options

IPv6 is coming in v3.0.0

Comment options

I know the Network Refactoring branch has already been merged, but only just got around to testing IPv6, #8760.

It is still not working that well for IPv6 (these issues were there before the refactoring). I tested across multiple network types:

Network Dual-Stack IPv6 IPv4 TLS Dual-Stack
IPv4 (Shadow) IPv4 Not Possible Yes Yes
Dual-stack+NAT64 (Astral) IPv4 (1) DNS fail Yes (1) DNS fail
IPv6+NAT64 (Wildspace) DNS fail DNS fail DNS fail DNS fail

(1) Dual-stack initially worked for some, but then started failing. I suspect it is lack of support for IPv6, and because I have DNS64 everything has an IPv6 address. Could be a race condition that worked before DNS64 was resolved.

Some of the errors:

Dual-stack+NAT64 network, IPv6 destination:

[ 69767][I][Core2Logger.cpp:198] log(): [Core2Logger] CORE2: Button 2, scenario 1, v0.1.0-156-gaf345f5
[ 69779][I][Core2Logger.cpp:198] log(): [Core2Logger] CORE2: Global IPv6 2407:8800:bc61:1340:a3a:f2ff:fe65:db28
[ 69794][I][Core2Logger.cpp:198] log(): [Core2Logger] CORE2: IPv4 192.168.1.154
[ 69802][I][Core2Logger.cpp:198] log(): [Core2Logger] CORE2: Link-Local IPv6 fe80::a3a:f2ff:fe65:db28%st1
sta: <UP>
 ether 08:3A:F2:65:DB:28
 inet 192.168.1.154 netmask 255.255.255.0 broadcast 192.168.1.255
 gateway 192.168.1.1 dns 192.168.1.1
 inet6 fe80::a3a:f2ff:fe65:db28%st1 type LINK_LOCAL
 inet6 2407:8800:bc61:1340:a3a:f2ff:fe65:db28 type GLOBAL
 inet6 fd7c:e25e:67e8:40:a3a:f2ff:fe65:db28 type UNIQUE_LOCAL
[ 69840][I][Core2Logger.cpp:198] log(): [Core2Logger] CORE2: DNS0 192.168.1.1
[ 69854][I][Core2Logger.cpp:198] log(): [Core2Logger] CORE2: DNS1 0.0.0.0
[ 69862][I][Core2Logger.cpp:198] log(): [Core2Logger] CORE2: URL: http://v6.ipv6-test.com/api/myip.php
[ 69875][D][HTTPClient.cpp:303] beginInternal(): protocol: http, host: v6.ipv6-test.com port: 80 url: /api/myip.php
[ 69885][D][HTTPClient.cpp:598] sendRequest(): request type: 'GET' redirCount: 0
[ 76427][E][NetworkManager.cpp:125] hostByName(): DNS Failed for 'v6.ipv6-test.com' with error '-5' and result '-1'
[ 81441][I][NetworkClient.cpp:269] connect(): select returned due to timeout 5000 ms for fd 48
[ 81450][D][HTTPClient.cpp:1163] connect(): failed connect to v6.ipv6-test.com:80
[ 81458][W][HTTPClient.cpp:1486] returnError(): error(-1): connection refused
[ 81465][E][Core2Logger.cpp:192] log(): [Core2Logger] CORE2: HTTP GET error -1: connection refused
You must be logged in to vote
3 replies
Comment options

I played around with NetworkManager::hostByName(), which calls the LWIP DNS functions directly dns_gethostbyname_addrtype() in dns.h, including handling the callbacks.

Examples I have tested directly in ESP-IDF use lwip_getaddrinfo() in netdb.c, which appears to be a higher level function. In general C standards getaddrinfo() is preferred over gethostbyname(), although I presume the addrtype is meant to support IPv6.

The examples I have tested with lwip_getaddrinfo() work for 8/9 scenarios (combinations of network type and destination address), with an issue calling a dual-stack server from an IPv6-only network (I have a bug fix up for LWIP to address this).

The netdb.c interface is also simpler, as it doesn't use callbacks (they are resolved internally).

I am going to see if I can get getaddrinfo() working.

Comment options

let us know. we could switch to it (you are welcome to provide PR too) if you find it working better

Comment options

I've put up an initial PR #9439

This is working a lot better across IPv6, with two more things to do:

  1. IPv6-only appears to have no DNS at all. Having spent some time looking as ESP-IDF, I think I know the issue and what flags to use when building the libraries. I will work out how to make those changes and test it.

  2. For dual-stack hosts, IPv4 is preferred. From looking at ESP-IDF this also breaks IPv6 only network to dual-stack host (as it tries to use the IPv4 address). This is an internal thing in LWIP (and ESP-LWIP). I have a PR fix up for ESP-LWIP for this (and looking at upstream LWIP). In the meanwhile, I have a work around in one of the ESP-IDF examples (another PR up for that), which I may be able to use to get it working.

Comment options

List of Arduino-ESP32 IPv6 related PRs:

Other PRs for ESP-IDF:

You must be logged in to vote
1 reply
Comment options

With the Lib Builder fix, and the two Arduino-ESP32 fixes, then almost all scenarios work:

Network Dual-Stack IPv6 IPv4 TLS Dual-Stack
IPv4 (Shadow) Yes Not Possible Yes Yes
Dual-stack+NAT64 (Astral) IPv4 Yes Yes Yes
IPv6+NAT64 (Wildspace) Yes Yes NAT64 fail

With the LWIP fix, then all scenarios work in ESP-IDF. I guess I could do a libs build from my PR branch, just to test everything together for Arduino, but it would be a special branch linked to an unmerged PR, so only useful for temporary testing.

If there is anyone who can help with getting the ESP-LWIP fix reviewed, that would make a lot of the others simpler. (But I suspect it will take a while, hence the workaround in one of the Arduino PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /