Skip to content
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

Inconsistency in station local address functions IPv4 vs IPv6 (alpha 3) #9144

Closed
1 task done
sgryphon opened this issue Jan 19, 2024 · 30 comments
Closed
1 task done
Assignees
Labels
Area: WiFi Issue related to WiFi Status: Awaiting triage Issue is waiting for triage
Milestone

Comments

@sgryphon
Copy link
Contributor

sgryphon commented Jan 19, 2024

Board

m5stack-core2

Device Description

M5Stack Core2 ESP32 IoT Development Kit for AWS IoT Kit, https://shop.m5stack.com/products/m5stack-core2-esp32-iot-development-kit-for-aws-iot-edukit?variant=37687799251116

Hardware Configuration

Just the M5Stack Core2

Version

latest master (checkout manually)

IDE Name

PlatformIO

Operating System

Ubuntu 22.04

Flash frequency

240Mhz

PSRAM enabled

yes

Upload speed

115200

Description

Issue

The localIPv6() and globalIPv6() accessors in WiFiSTA.h are implemented inconsistently with how localIP() is implemented, and how localIP() and remoteIP() are used in WiFiClient.h.

What should happen

  • globalIPv6() should be removed
  • localIP() should continue to return the local host global scope IPv4 address
  • localIPv6() should change to be consistent and return the local host global scope IPv6 address, if available
  • If a global scope IPv6 address is not available, then localIPv6() should return (in priority order) a local host ULA IPv6 address, or a local host link-local scope IPv6 address.
    • This is consistent with localIP() which may return the local host link-local scope IPv4 address (if there is no global scope IPv4 and link-local IPv4 is enable in LWIP)
  • Some mechanism should be provided to get the other additional IPv6 addresses

Mechanism for additional IPv6 addresses

Ideally:

  • For additional IPv6 addresses, change the signature to IPAddress localIPv6(uint8_t address_no = 0), similar to dnsIP(dns_no).
    • Return prioritised as above, with 0 (i.e. just default localIP()) returning global scope if available
    • 1, 2, etc returning subsequent addresses
    • Once addresses have run out, return empty result.
    • Common scenario examples:
      a. Single global scope IPv6 address, 0 = global scope address, 1 = link-local scope address
      b. No global scope IPv6 address, 0 = link-local scope address

Alternative:

  • Add globalScopeIPv6() to return a global scope IPv6 (same as current globalIPv6())
    • May not have have a global scope IPv6 address if not available on the network (e.g. no RA)
  • Add linkScopeIPv6() to return a link-local scope IPv6 (same as current localIPv6())
    • This avoids using the term 'local' with a different meaning; linkLocalScopeIPv6() address could be another option, which uses the correct name and does not have inconsistent use of 'local', but may still cause confusion. linkScopeIPv6() completely avoids the use of the word 'local'.
    • A node will always have a link-local IPv6 (if IPv6 is enabled), as it is auto-configured.
  • A drawback of this approach is it only gets 2 addresses when there may be more

Sketch

No directly relevant; the issue is inconsistent naming.

Debug Message

n/a

Other Steps to Reproduce

Background

The new (alpha 3) code in WiFiSTA.h header has several accessors to get the different IP addresses of the local station:

    IPAddress localIP();
    IPAddress localIPv6();
    IPAddress globalIPv6();

This is only alpha, so it would be good to see the naming here fixed before release.

However the usage of the terms here is inconsistent, with some confusion between local as opposed to remote and the concept of link-local addresses.

For comparison WiFiClient.h header (and WiFiUdp.h) has the following accessors, representing a connection where localIP() is the local end and remoteIP() is the remote end:

    IPAddress remoteIP() const;
    IPAddress localIP() const;

As IPAddress now supports IPv6, these connection address could be a local, and remote, IPv6 address, or IPv4.

The WiFiSTA.h accessor of localIP() is similar, in that it represents the local (as opposed to remote), usually an IPv4 private network address, e.g. 192.168.0.x. In the current implementation it is limited to IPv4 addresses.

Whilst it happens to usually be a private address range, a company with a public IPv4 address allocation could use it for WiFi, and the LWIP option CONFIG_LWIP_AUTOIP could also allow the device to self-assign an address in the IPv4 local-link range 169.256/16 range (see RFC 3927).

So localIP() could be a private IPv4 address, global IPv4 address, or a link-local IPv4 address.

However localIP() does not give you IPv6 addresses, even if they are the only ones available (e.g. IPv6 only network).

There is a localIPv6() accessor, however it appears to have been misinterpretted as being for link-local addresses only, an apparent confusion between local and link-local. This is inconsistent with the IPv4 version which is usually a private or global address (but could be link local).

Further the confusing accessor globalIPv6() has been introduced.

To show the best/most appropriate local address client code needs to check globalIPv6() first, then localIP(), then localIPv6(), when all they want to know is what is their IP address.

Only allowing a single return result is probably okay for IPv4, where multiple addresses are rare, but for IPv6 not only is it common to have at least two addresses (you keep your link-local address when you acquire a global one), but it is also common to have multiple global addresses (although LWIP doesn't yet support DHCPv6 or private extensions, so commonly will only have 2).

I have checked existing issues, online documentation and the Troubleshooting Guide

  • I confirm I have checked existing issues, online documentation and Troubleshooting guide.

Historical note - original suggestions:

  • localIPv6() and globalIPv6() should be removed
  • localIP() should return the most relevant IPAddress, of any type, for the station, i.e. same as localIP() for WiFiClient.h, it could be IPv6 or IPv4.
  • The priority order should be, if available: global IPv6 address, global IPv4 address, link-local IPv6 address, link-local IPv4 address, or the empty address.
  • This means on any IPv4 only network, or when enableIPv6() is not set, localIP() will always return an IPv4 address (backwards compatible).
  • Only an IPv6-only system, localIP() will return the local (as opposed to remote) IPv6 address, with a preference for a global IPv6 address, if available, over a link-local address.
@sgryphon sgryphon added the Status: Awaiting triage Issue is waiting for triage label Jan 19, 2024
@P-R-O-C-H-Y P-R-O-C-H-Y added Area: WiFi Issue related to WiFi and removed Status: Awaiting triage Issue is waiting for triage labels Jan 19, 2024
@P-R-O-C-H-Y
Copy link
Member

@me-no-dev Please take a look as you are working on the network refactoring.

@TD-er
Copy link
Contributor

TD-er commented Feb 6, 2024

What should happen
localIPv6() and globalIPv6() should be removed

Nope!

I can somewhat agree to the idea that localIP() should return the preferred type, but then there should be an extra function (or a type parameter for localIP() ) to get the IPv4 address specifically.

You still should be able to get the IPv6 info without having to revert to ESP-IDF functions.

@me-no-dev
Copy link
Member

Client.localIP() have nothing to do with IF.localIP(). For Client this is the local IP of the socket. It could be v4 or v6. Same goes for remoteIP(). Those have a really different meaning.

I do not agree that anything needs changing. Interface can have different IPs, so we allow them to be queried. Sockets have specific IPs.

@TD-er
Copy link
Contributor

TD-er commented Feb 6, 2024

The signature should be changed to IPAddress localIP(uint8_t address_no = 0), similar to dnsIP(dns_no)

Rather use an enum type.
Especially if you want to use "0" as the preferred type. ("0" could then be the "preferred" type)
You should be able to just ask for a specific type (ipv4/IPv6 link local/IPv6 global) and not having to loop over all available IPs to finally get some that looks like a global or local IPv6.

Client.localIP() have nothing to do with IF.localIP(). For Client this is the local IP of the socket. It could be v4 or v6. Same goes for remoteIP(). Those have a really different meaning.

And this does work perfectly fine.
Whenever I connect to my ESP node via IPv4 or IPv6 (local or global), the client IP is from the same type as what I'm accessing.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 7, 2024

I guess I'm asking for two things here, one I think I have agreement and the other not.

(1) usage of the term local (and global)
(2) distinction between IPv6 and IPv4

#1 is the more important item.

For #2 for your local address, having separate methods for IPv6 and IPv4 is acceptable. They are not of a lot of use, except for diagnostics, and then you often want to separate out nice headings for them. So if people want to keep localIP() and localIPv6() separate that may be okay.

However for #1, the usage of globalIPv6 and then saeparate localIPv6 to mean link-local is just wrong. localIPv6() should work like localIP() and give you the IPv6 address (a global one if available), of the local station.

(If you do want separate methods to give you global and link-local of the local station, then call then globalIPv6() and linkLocalIPv6() ... don't use localIP() and localIPv6() to mean different things for local. Note that localIP(), the IPv4 version, actually gives you a global address).

The integer parameter (default 0) wasn't meant to be an enum or preferred indicator, but an index, because you may have, for example, 3 IPv6 addresses, so localIPv6(0) would get you the first, localIPv6(1) the second, and locaIPv6(2) the third. Calling localIPv6(3) or more would return empty. This is similar to how dnsIP(int) works.

The ordering of the source addresses, even if limited to IPv6 only, should ideally follow RFC 6724. Even if you don't follow this ordering, you should still be able to access all the IPv6 addresses.

@TD-er
Copy link
Contributor

TD-er commented Feb 7, 2024

(If you do want separate methods to give you global and link-local of the local station, then call then globalIPv6() and linkLocalIPv6() ... don't use localIP() and localIPv6() to mean different things for local. Note that localIP(), the IPv4 version, actually gives you a global address).

The last part of your remark does illustrate why it doesn't make things more clear.
So IMHO localIP() as function name for the generic call to get the preferred IP for this node may even be wrong, just for the simple fact that this might also return a global IPv4 address if the interface doesn't have a local IPv6 (is this even possible?)
After all the preference only differentiates between IPv4 and IPv6.

For IPv4 there isn't really a difference between global and local IP. Sure some ranges will not ever be routed to the internet and there is also a range for "zeroconf" IPv4.

For #2 for your local address, having separate methods for IPv6 and IPv4 is acceptable. They are not of a lot of use, except for diagnostics, and then you often want to separate out nice headings for them. So if people want to keep localIP() and localIPv6() separate that may be okay.

But for diagnostics and some specific use cases where you really want to get the IPv4 address, you should still have some function to get the IPv4 address.
For example in my p2p protocol, I do announce the host in the network so others know how to reach it.
This does require both the IPv4 and IPv6 address to be present in the message, regardless of my preferred IPv4/IPv6 setting. This allows hosts to pick whatever IP they prefer (or can use).

For this I don't want to loop through several function calls until I have the IPv4 type.
So with your proposal, I either need to go through this loop or need to have a separate localIPv4() function.
But this is also a bad name as we already concluded since for IPv4 it can also be "global".

Like I said, I do understand why it might be useful to have a single function to return an IP address based on the preferred type.
But I don't agree with the suggested changes as they seem to complicate things in such a way which seems to be not necessary.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 7, 2024

So IMHO localIP() as function name for the generic call to get the preferred IP for this node may even be wrong, just for the simple fact that this might also return a global IPv4 address if the interface doesn't have a local IPv6 (is this even possible?)

There seems to be a lot of confusion here.

Location of address:

  • Local = my computer / current node. These are all the addresses on my network interface(s) used for outgoing connections and/or receiving incoming connections.
  • Remote = other computer / other node

Categories of addresses (applies to both IPv6 and IPv4)

  • Link-local = scoped only the the local network, e.g. IPv4 169.254.0.0/16 network (RFC 3927), or IPv6 fe80:: network.
  • Private range (IPv4 only)
  • Global = generally assigned from higher up and routeable to other networks (exact definition varies)

An IPv4 connection between two nodes might use link-local addresses, your "local link-local" address might be 169.254.0.1 which connects to a "remote link-local" address of 169.254.0.2. From the other nodes point of view local & remote are reversed (but both are still link-local).

There appears to be some confusion of using the word local as a short hand reference to link-local, which is just confusing.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 7, 2024

For IPv4 there isn't really a difference between global and local IP.

That statement makes no sense; even if you mean between global and link-local, it still makes no sense.

Here are the local addressess for my laptop.

  • 169.254.227.185/16 ... scope link is an IPv4 local link-local address
  • 192.168.1.209/24 ... scope global is an IPv4 local global address (global scope as defined by RFC 6724)

Yes, an IPv4 address can be both local (as opposed to remote) and global (as a scope), but you can also have a local (as opposed to remote) and link-local (as a scope) address.

Other computers on the network, which are (from my point of view) remote, could also have remote link-local and remote global destination addresses.
local-addresses

@TD-er
Copy link
Contributor

TD-er commented Feb 7, 2024

I didn't think anyone would still actually be using those 169.254 addresses.

Just to be sure, I checked several of my machines here (Windows/Linux) and none is using 169.254.x.x
I know it was once introduced as some kind of zero-conf idea, but I always thought it never caught on as it became almost synonym for "Whenever you see that, you have got a problem with your network".

Anyway, like I said I can understand the desire to have a single function to get the preferred IP type of your host's IP-address. (deliberately not calling it 'local' to avoid confusion here)
But IMHO this would require an extra function to get the IPv4 address and still functions to get localIPv6 and globalIPv6.
Probably those last 2 should need some index if you want to fetch them all.
Not sure if it would make sense to have multiple IPv4, unless we're talking about IP per interface.

With SPI Ethernet devices, you can have multiple Ethernet interfaces (even with the espressif/arduino-esp32 code) and of course you have WiFi which can have an IP for STA and for AP mode.

So how would those be specified when querying? Also which is the "preferred" interface? What IP-address from which interface would you expect when calling localIP() ?
Let's hope we won't need to keep enumerating over them to finally have one with the desired zone appended to the localIPv6 address.
I think we won't have several globalIPv6 addresses on different interfaces, even though it would be possible from a routing perspective. (probably metrics needed to specify the "costs" for using a specific interface)

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 7, 2024

For IPv4 there isn't really a difference between global and local IP.

Sorry. Maybe you meant "Most local IPv4 addresses are global scope addresses", which is true, but has nothing to do with being local.

Because "Most remote IPv4 addresses are global scope addresses" is also true. So there are a lot of global IPv4 addresses that are not local -- they are remote.

This is just because most IPv4 addresses (either local or remote) are global scope addresses.

Link-local scope IPv4 addresses (169.254.0.0/16) are uncommon to see because the standard says don't assign one if you have DHCP, and if you do have one and receive DHCP then replace it. Unlike IPv6 where you keep (rather than replace) your link-local scope addresses when you get a global scope addresses. By default link-local IPv4 addresses are also disabled in LWIP.

the preferred IP type of your host's IP address (deliberately not calling it local)

But you should call it local here. The word local (by itself) means your host's IP address.

and still functions to get localIPv6 and globalIPv6

This is where the word local (by itself) is being used inconsistently.

Maybe you meant "functions to get link-local scope and global scope IP addresses".

In my screen shot above:

  • fe80::2ab8:3926:aff:7ccf/64 scope link is a local host link-local scope address
  • 2407:8800:bc61:1340:f949:fb86:d418:aaa4/64 scope globalis a local host global scope address

I.e. an address can be both for the local host and have a global scope.

Current the IPv4 function:

  • localIP() means get the IP address of your local host; usually this is a global scope address, e.g. 192.168.1.209. This is the same meaning (local host) where the function name localIP() is used elsewhere

Current the way the IPv6 functions are named:

  • localIPv6() is inconsisent. It does not return a local host global scope address like the IPv4 version, but instead returns a local host link-local scope address. I think it should function the same as localIP(), e.g. return 2407:8800:bc61:1340:f949:fb86:d418:aaa4

Using localIP() to get the IPv4 and globalIPv6() to get the equivalent IPv6 makes no sense; they should have the same name.

To be able to get all IPv6 addresses, I propose an index, similar to dnsIP(), so:

  • localIP(), on the WiFi interface, returns 192.168.1.209 (global scope).
  • localIPv6() (which is the same as localIPv6(0)), on the WiFi interface, returns 2407:8800:bc61:1340:f949:fb86:d418:aaa4 (global scope).
  • localIPv6(1), on the WiFi interface, returns fd7c:e25e:67e8:40:f949:fb86:d418:aaa4 (ULA address).
  • localIPv6(2), on the WiFi interface, returns fe80::f949:fb86:d418:aaa4 (link-local scope).
  • localIPv6(3), and above, returns empty

The order of the above addresses is based the section 2.4 precedence table of RFC 6724.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 7, 2024

So IMHO localIP() as function name for the generic call to get the preferred IP for this node may even be wrong, just for the simple fact that this might also return a global IPv4 address if the interface doesn't have a local IPv6 (is this even possible?)
After all the preference only differentiates between IPv4 and IPv6.

There is nothing wrong with a local host having a global scope address -- in fact normally this is the case, e.g. my local host has lots of global scope addresses: 192.168.1.209, 2407:8800:bc61:1340:f949:fb86:d418:aaa4, etc.

I'm not comfortable with the word preferred, even if I used it in the past, I didn't mean anything to do with user selected preference. There is a term in RFC 6724 for precedence.

The main purpose for showing a local host address I presume is so it can be display and used by another system as a destination address (another use would be for examining logs). The main criteria here is appropriate scope; in the general case of a global scope connection this means prioritise global scope over link-local scope (both addressess will work fine in the local host network).

I'm happy with the discussion of separate discussions for IPv4 vs IPv6, and will update the suggestion above.

@TD-er
Copy link
Contributor

TD-er commented Feb 8, 2024

I assume the purpose of this issue is to make it more clear to users right?
I don't consider myself to be an inexperienced user regarding networks, especially considering the target audience of Arduino platforms.

But when I look at your last proposed function call examples, I'm completely lost as to me it makes absolutely no sense to call the function localIPv6() and the first/default one to return is clearly a global scope IPv6.
Also apparently limit it to only 1 of those, where I can see 8 of those when I run ifconfig on my Linux host.
Then the next one is an ULA address (which I don't see on my Linux hosts and also never have seen on my Espressif nodes) and as last the one which looks -to me- as a the type I would expect to see when calling this function. (only missing the zone, which is clearly needed for link-local addresses)

I still don't get the semantics here and why this is needed to make it so complicated.

You could perhaps introduce some enum to make it absolutely clear there are only 3 types you can query:

  • global scope
  • ULA
  • link-local scope

But using numbers is confusing and really hard to read the code.
Especially if it will not be limited to these 3 but is variable when you may have several IPv6 global link addresses.

Also comparing the index here to the index used for DNS servers is confusing as for DNS servers there is not really a difference apart from maybe a preferred order.
The suggested index here will return completely different types. It might reflect some preferred order, but the result is an IP-address for a different scope.

Just wondering;
What do you return when calling this function while you don't have a global scope IP-address (yet)?
How should the software handle this?
For example on my ESP's I get a link-local address quickly after connecting to the access point, but it takes a few seconds to get the global scope IP.

Then there is something else to consider here...
If you let inexperienced users return the global link IP as first/default option, what effects will this have on security as it will suggest to users to make this stuff available from the entire globe.
I think the default for (all) IoT stuff should be "local first, global as last resort"
I know you still need to open up stuff in your router as even global link IPv6 addressed devices will not be made directly available from the big bad internet (at least on decent routers/modems). But that's just a single click away.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 8, 2024

to call the function localIPv6() and the first/default one to return is clearly a global scope IPv6. ...
last the one which looks -to me- as a the type I would expect to see when calling this function

It sounds to me like you expect the function localIPv6() to return a local host link-local scope IPv6 address. (I expect it to return a local host global scope IPv6 address)

But you expect localIP() to return a local host global scope IPv4 address?

That is an inconsistent interpretation of local, the first being local meaning link-local scope, and the second being local host scope.

for DNS servers there is not really a difference apart from maybe a preferred order

I have a network configured where the two configured DNS servers are, in precedence order, fe80::1 (a remote host link-local scope IPv6 address, for my local router) and 2001:4860:4860::6464 (a remote host global scope IPv6 address)

If you let inexperienced users return the global link IP as first/default option, what effects will this have on security
I think the default for (all) IoT stuff should be "local first, global as last resort"

It should have no effect on security; I don't understand what your security concerns are. What are you using the returned value for? Display to the user to connect to (e.g. with a browser) -- most browsers don't understand link-local addresses? Putting into logs for troubleshooting?

I think here you are using the word local in a third meaning, to mean IPv4 private address range; note that the IPv4 private address range is still considered global scope. They also are not limited to local-host network, as an organisation may have multiple connected private address ranges.

The nature of private address ranges means that to allow incoming connections requires explicit set up of a port or host mapping, whereas allowing incoming access to IPv6 requires opening a firewall which may be easier to make a mistake with?

If that is the case, then use the correct term private address ranges.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 8, 2024

What do you return when calling this function while you don't have a global scope IP-address (yet)?

I have updated the original text with a new suggestion that keeps IPv4 and IPv6 separate, but still tries to fix the inconsistent naming.

I presume you mean the function I have now called localIPv6(int index = 0)

  • If you only have an auto-configured link-local IPv6 address then localIPv6() (same as localIPv6(0)) will return the link-local address, and localIPv6(1) and above will return nothing.
  • Later, when an RA arrives, say with two global scope prefixes, then localIPv6() will return one of those global scope addresses, localIPv6(1) the second global scope address, and localIPv6(2) the link-local scope address

The indexes are not an enum of types (like 0 = global scope), but a precedence order, just like DNS.

localIPv6(0) could be a global scope or link-local scope, or any other scope. Same as dns(0) could be a global scope or link-local scope, etc.

How should the software handle this?

My thoughts:

Assuming the purpose is diagnostics, i.e. you want to look in logs and traces, then the software UI should display all IP addresses of the device. Usually this is separate labels for IPv4 and IPv6, which is why I am okay with separate functions.

The IPv4 label should show the localIP() address

The IPv6 label should show a list of addresses from localIPv6(0), localIPv6(1), etc, until you run out. When you first connect, if you have not yet received the RA, this list will only show the link-local scope address; later it will show all of the addresses generated from the received prefixes.

Another purpose might be for showing the address to connect to, e.g. a web browser config. For this, 2 labels, or even just 1 label, is sufficient.

With 2 labels, IPv4 should show localIP() and IPv6 should show localIPv6(). Use the precedence ranking (global scope first), because the global scope IPv6 can be put into a browser and will work if you are on the local network and if you are on a network that has a configured route.

If there is no global scope IPv6 (or ULA), then localIPv6() will show a link-local IPv6 address, but this may be useless. You may be able to ping it on the local network, but most browsers don't support link-local so that won't work. It is simply less useful for "address to connect to".

For example on my ESP's I get a link-local address quickly after connecting to the access point, but it takes a few seconds to get the global scope IP.

Yes, because link-local has an autoconfigure mechanism, whereas prefixed addresses need to wait to receive RA (router advertisement) with the prefix details.

Both the link-local scope and global scope addresses are local host addresses.

The IPv4 you get from DHCP also needs to wait for DHCP responses to be received, and is usually a global scope address (for the local host).

@TD-er
Copy link
Contributor

TD-er commented Feb 8, 2024

The point I am trying to make here is that in order to get the link-local IP, you need to iterate over all IPs to finally get the one you need.
In your suggested functions you can't just pick a specific offset and just know it is of type such-and-such.
Even though it is clearly accessible as such in the underlaying SDK/libs.

As I mentioned before, I totally get it when you have a single function (maybe even without the index parameter, not even have it default to 0) to get the IP-type with the highest precedence.
But why should you need to iterate and filter over IPs to get the ones you need while in order to iterate over them you also need to use different calls in the SDK/libs to "join" the different types in an iterable list?

About the security aspect....
I do prefer to use the smallest scope where possible as most users won't know about the possible security issues when possibly exposing units to the internet. (and they will)
Maybe this is a bad choice for IPv6 and if so then I will be the first to revert this. But until it has been proven otherwise, I think this is the better approach for self-built IoT stuff (and IoT vendors should also consider local first, but their investors probably won't agree with me)
Also -as far as I know- the link-local IP is always based on the MAC and thus the most predictable/constant one to use.

Anyway, after a night sleep, I think I now somewhat understand what your intention is with this issue.
However I am still not really convinced we need to get rid of the separate functions to get a specific IP type.
If only needed for more "advanced" use cases like diagnostics or whatever this user "TD-er" may come up with ;) then I don't see how it should be more restrictive to use.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 8, 2024

The point I am trying to make here is that in order to get the link-local IP, you need to iterate over all IPs to finally get the one you need.

Why would you want to do this? What is the use case for wanting to specifically know a link-local scope address.

They are largely useless for a user (they are great for automated functions) -- a global scope address can do everything a link-local can and is in general more usable, e.g. browsers don't generally support link-local addresses.

If you don't have a global scope address, sure a link-local is better than nothing (but that is what you would get anyway, if they are ranked).

I am also not opposed to alternative solutions, just mostly to solve the inconsistent naming.

You could use:

  • localIP() for local IPv4 (usually local host global scope IPv4, but could potentially both others)
  • globalScopeIPv6() for the local host global scope IPv6
  • linkScopeIPv6() (or linkLocalScopeIPv6()`) for the local host link-local scope IPv6

Just don't use different meanings of local, i.e. don't use localIP() to mean local (host) IPv4 (probably global scope) and localIPv6() to mean link-local scope IPv6 (of the local host).

About the security aspect.... I do prefer to use the smallest scope where possible

Showing the more useful IPv6 address first doesn't help or hinder the fact that the address is still there. (Obscurity is not a good form of security). If the unit is exposed (badly configure IPv6 firewall or IPv4 NAT forwarding) then the ordering of address results doesn't affect that at all.

Also, what not apply this to IPv4, i.e. enable link-local IPv4 in LWIP and use that?

-as far as I know- link-local IP is always based on the MAC

You can always read the specifications to clarify what you know.

  • RFC 4291 is based on creating interface identifiers following EUI64, which are based on the MAC address
  • RFC 8981 has a discussion of using random interface identifiers

The link-local address is formed by combing fe80:: with the interface identifier, which could be one of the methods above. Yes, MAC is common, but not the only way.

It is also possible to assign static link-local addresses, e.g. it is common to assign fe80::1 to a gateway router on the downstream interfaces, which makes for easy mass configuration. e.g. I have my home router set up this way.

Note that LWIP only supports RA (Router Advertisement), and not DHCPv6 stateful, so all other addresses are based on the same interface identifier. i.e. global scope addresses are (in LWIP) always based on the MAC as well. Router advertisements are discussed in RFC4861

However one important thing to note is that most browsers do not support link-local addresses, so it is often useless for web-based configuration. Other tools, e.g. ping, may work, but for a common scenario of web based configuration in an IPv6 environment you need to use a global scope address (e.g. a global delegated address or ULA prefix).

@TD-er
Copy link
Contributor

TD-er commented Feb 8, 2024

However one important thing to note is that most browsers do not support link-local addresses, so it is often useless for web-based configuration. Other tools, e.g. ping, may work, but for a common scenario of web based configuration in an IPv6 environment you need to use a global scope address (e.g. a global delegated address or ULA prefix).

Which browsers don't support link-local addresses?
I just tested on Chrome and Firefox (on Windows) and both work perfectly fine when accessing a node using a link-local IP.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 8, 2024

Which browsers don't support link-local addresses?
I just tested on Chrome and Firefox (on Windows) and both work perfectly fine when accessing a node using a link-local IP.

No current browsers properly support link-local addresses; in particular they do not support specifying zone id. Here is the bug for Firefox, opened 12 years ago, with the latest comment, from 2 days ago, saying they still won't support it (basically because the RFC to specify URLs is broken and hasn't been fixed/agree upon yet, for many years; Chrome has a similar bug). https://bugzilla.mozilla.org/show_bug.cgi?id=700999

Windows, but not Linux, supports an optional part of the link-local specification for a default zone id. So, in a browser on Windows if you enter a link-local address without the scope id, then the browser will pass it to the networking subsystem no problem.

If the device you want happens to be on the default zone, then it will work, because Windows supports a default zone id, not because the browser supports it.

If you have 2 or more zones (2 or more network interfaces on your machine) and the device you want is not on the default zone, then it won't work. It won't work on Linux either (because Linux doesn't support default zones).

Just count yourself lucky, but realise that in many other situations link-local won't work.

Using a global scope address, such as a ULA, is a much better solution. Like using a 192.168.0.0 address in IPv4, rather than 169.254.0.0 address -- no one uses the 169.254 addresses, they all use 192.168 addresses. The closest thing in IPv6 is a ULA, and is fully supported (unlike link-local).

e.g. if you are setting up a access point then for IPv4 you might advertise a 192.168.0.0 range via DHCP and tell users to connect to the AP, get an address from DHCP and then enter http://192.168.0.1/ in their browser to access the web interface.

In IPv6 you would do similar, but use a ULA. Set up your access point with a static ULA like fd00::1, advertise the fd00:: prefix via RA (Router Advertisement), tell users to connect to the AP, they will get an address from RA, and then enter http://[fd00::1]/ in their browser to connect to the web interface.

This will work on on both Linux and Windows, Android phones, etc, and irrespective of if you have one network interface or many.

If you have lots of devices and want to clearly distinguish them, e.g. by scanning a QR code, then you can use a full random ULA, along with the MAC, and get a long address like http://[fd7c:e25e:67e8:40:d9f6:2780:3f9e:176b]/ that you then encode into a QR code link. Depends if you want a generic easy to enter value (like IPv4. actually shorter), or a unique value.

@TD-er
Copy link
Contributor

TD-er commented Feb 8, 2024

But does the current Espressif implementation support ULA addresses?
I haven't tried setting a static IPv6 address myself and not sure yet if there are functions to generate a ULA address.

Also is there a way to prevent probing for a global scope address in Arduino/Espressif code?

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 8, 2024

But does the current Espressif implementation support ULA addresses?

A ULA is just a particular group of IPv6 addresses. I have a ULA prefix advertised on my local network and did managed to get dual stack station working on my M5Stack ESP32 and it receives the prefix (and generates the address -- it doesn't treat it any different than other prefixes).

Single stack IPv6-only didn't fully work (DNS issues) but it also got the ULA. I have got ESP-IDF working with IPv6-only and DNS (and ULA).

For AP mode if you are setting a static address, well it is static so you just set what you want. e.g. rather than random you could use a simple fixed address; "fd00::1" or "fd00:1::1" is like picking 192.168.0.1 for IPv4.

I haven't tried setting a static IPv6 address myself and not sure yet if there are functions to generate a ULA address.

I haven't tried this either. Should be relatively straight forward to set the static address (and work as a station), but for an AP you would want to also send RA messages with the prefix (similar to DHCP for an IPv4 station) -- I don't know if this is supported yet.

Also is there a way to prevent probing for a global scope address in Arduino/Espressif code?

I don't know about Arduino, but there is an ESP-IDF for autoconfigure, which is listening to the RA messages and then generating based on received prefixes. But I don't think there is a way to say listen to ULA prefixes but ignore other global ones. (Technically ULAs are still global scope, but systems like RFC 6724 give the range a lower precedence).

But generally you woudn't want to do this. You want a device to adapt to and work with whatever network you connect it to (without having to recompile).

e.g. my mobile phone will connect to an IPv4-only network, dual stack, IPv6-only, etc, and simultaneously support all the different configurations and just use what is available. (Although Android does not support DHCPv6 Stateless, so if you want IPv6-only DNS to work you need to use RDNSS -- my laptop supports both DHCPv6 Stateless and RDNSS, so will work when either, or both, are available).

@TD-er
Copy link
Contributor

TD-er commented Feb 8, 2024

For sure I don't want to recompile for some networks.
I was just thinking that it might be nice to have a setting to disable global scope IPv6 so the user has to enable it.
But if this requires a recompile, then it is for sure not an option.

The reason I asked about the ULA is that it seems to be the better option for link-local scope, but since I don't have them on my network, it seems like you might need to make changes to your network to make it work (or at least make it routable in your network across interfaces).
And just generating random addresses or random prefixes doesn't seem to be the way to go.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 8, 2024

Disabling at runtime might make sense. On a phone/laptop/etc you probably wouldn't want to do this often, but you certainly can go into Windows/Linux/etc and disable IPv4 / IPv6, etc, override DNS, and so on. You can't decide which RA prefixes to ignore (or DHCP assignments), but you can turn off automatic assignment and use a static address instead; i.e. use automatic RA for initial setup, then set a fixed static address with ULA only to override.

For an IoT device it makes sense to be able to lock it down after install if you really want to, i.e. if there is no IPv4 then turn it off, etc. But it would be an advanced configuration, and for consumer devices you need a way to reset.

ULA is that it seems to be the better option for link-local scope

ULA address are global scope addresses. Maybe you meant a better option than link-local? Certainly if a device is running as a temporary AP for configuration then ULA would be the thing to use.

To assign ULA addresses you just pick a range and configure it on your router, but your router needs to support it. Like IPv4, if you don't have a DHCP service on your router you won't get them; and you can pick whatever range you want and configure DHCP for it, e.g. 192.168.0.0 range, or 10.0.1.0 range, etc. Same with IPv6 Router Advertisement, although if you were configuring a router (rather than a temporary AP), I would pick a full random ULA prefix.

On my router, which is running OpenWrt, the setting is in Network > Interfaces > Global network options > IPv6 ULA-Prefix

@TD-er
Copy link
Contributor

TD-er commented Feb 8, 2024

Well the whole idea is that users should not need to adapt settings to their network to make things work.
And as you stated, link-local addresses may not work in all browsers, but ULA should.

Also ULA addresses should not be routable from the internet, right?
Of course there can always be translations present -as far as I understood as I haven't used any ULA addresses myself- but it seems like you should enable those yourself in your network. (right?)
So given all these assumptions, I was thinking it might be the safer option for those who hardly know about IPv6 to try and not expose the IoT devices to the big bad internet unless they do it themselves.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 8, 2024

But kind of getting off topic here. The point of this issue was the inconsistent naming between localIP() meaning local host IPv4 address (usually a global scope IPv4 address), and localIPv6() meaning a link-local scope IPv6 address.

Assuming localIP() stays, with it's current meaning (a global scope address for the local host), for backwards compatibility, then:

  • localIPv6() should either have the same meaning, i.e. usually returning the global scope IPv6 address for the local host, or just be removed.

Then to get the link-local address, if you want a specific function for it, you could use linkScopeIPv6() and then a corresponding globalScopeIPv6(), both of which will still return local host addresses but for the link and global scopes.

Although I'm not sure of the use case for these; for diagnostics they are incomplete, and if it was for showing an address to connect to then the global one would generally be more useful but not guaranteed to be there.

You may still want a function, e.g. (just to use different name) localAllIPv6(int index = 0) that can be used to loop through all local IPv6 addresses (like looping through all DNS addresses). This would be more useful for a diagnostics page, e.g. to correlate to network logs or similar.

@sgryphon
Copy link
Contributor Author

sgryphon commented Feb 8, 2024

I was thinking it might be the safer option for those who hardly know about IPv6 to try and not expose the IoT devices to the big bad internet unless they do it themselves.

I think the default options are fine for security. Even using a delegated range, the firewall on your router will by default prevent incoming connections, but allow outgoing. The same with NAT44 for IPv4; outbound connections from your devices will be allowed and incoming not (because by default there is no port mapping; although things like UPnP or other NAT traversal mechanisms are available).

e.g. Your mobile phone, or your laptop, will just connect to your network and get global addresses, but are safe. If it wasn't safe, accessing your phone is probably more valuable than your light switch or temperature sensor. For the average home tinkerer, their default router config is secure enough.

I guess if you write an open IoT device, with no security itself, that just accepts any message it receives, then it is risky (if your router firewall was messed with) ... but that is risky anyway, as anyone one the local network can access it. Best to use a framework that has built in secure access, e.g. Matter.

Any determined attack probably isn't going to be stopped by using a private addresses / ULA (i.e. addresses only on your local networks) -- if they compromise your router they would just use it as a jump box anyway, and you just have a false sense of security (look up "eggshell security").

@VojtechBartoska VojtechBartoska added this to the 3.1.0 milestone Feb 20, 2024
@VojtechBartoska
Copy link
Contributor

Will be covered in #8760

@VojtechBartoska VojtechBartoska moved this from Todo to In Progress in Arduino ESP32 Core Project Roadmap Mar 11, 2024
@sgryphon
Copy link
Contributor Author

Naming conflict is now fixed (in the networking PR branch). New functions use linkLocalIPv6(), and globalIPv6(), each of which has a clear meaning (as scopes) and is less likely to be confused with localIP() (which has a global scope address, i.e. is the IPv4 equivalent of globalIPv6()).

@me-no-dev
Copy link
Member

Note: new naming is for the new APIs. For compatibility, we have left the old APIs mostly intact. New APIs apply to ETH, WiFi.STA and WiFi.AP, which all extend the new NetworkInterface class

@me-no-dev
Copy link
Member

@sgryphon can we close this?

@sgryphon
Copy link
Contributor Author

sgryphon commented Apr 3, 2024

@sgryphon can we close this?

Yes, happy to close this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: WiFi Issue related to WiFi Status: Awaiting triage Issue is waiting for triage
Projects
Development

No branches or pull requests

5 participants