Skip to content

IPv6 APIs #12600

Description

@darconeous

There are already various bugs tracking adding IPv6 to the various ports:

But, even with these changes, there are some critical missing parts: the APIs. For example, <netif>.ifconfig() returns a tuple of the IPv4 address configuration. However, there is no API to get the IPv6 addresses of the device, and it looks like such a thing isn't even defined yet.

So I wanted to open this issue to discuss potential API additions to help make IPv6 a first-class citizen in micropython.

Specifically, we need the ability to:

  1. List the current IPv6 addresses assigned to an interface, along with the status of each address
  2. Statically configure IPv6 addresses, along with adding the associated on-link routes
  3. Turn on/off SLAAC (if support is compiled in)
  4. Turn on/off DHCPv6 (if support is compiled in)
  5. Set a preference for IPv6 vs IPv4 when performing name lookups that have both A and AAAA records.

We currently don't have any way to do any of this from Micropython, so I think we should at least start to define what this should look like.

For compatibility reasons, I think we should avoid extending the ifconfig() method. Lots of code seems pretty dependent on how this method works. I was thinking something like this as a starting point:

  • <netif>.MAX_IPV6_ADDRS - constant describing the maximum number of IPv6 addresses that can be assigned to this interface. (Compile-time default seems to be 3)
  • <netif>.v6_addrs() - Returns a list of tuples, one tuple per address. Each tuple would have the following fields:
    1. IPv6 Address (as a string?)
    2. Flags (Temporary, Deprecated, SLAAC, DHCPv6, Static, on-link, etc)
    3. remaining preferred lifetime (or None)
    4. remaining valid lifetime (or None)
  • <netif>.v6_del_addr(<addr>) - Removes the given IPv6 address.
  • <netif>.v6_add_addr(<addr>, <prefix-len>) - Adds the given IPv6 address, optionally adding a route for other on-link devices with the same prefix.
  • <netif>.config('host-lookup-pref', <'v6'|'v4'>) - Configures the host lookup preference when there are both A and AAAA records.
  • <netif>.config('slaac_en', <True|False>) - Turns on or off SLAAC.
  • <netif>.config('dhcpv6_en', <True|False>) - Turns on or off DHCPv6.

I'm sure this could be improved upon, but wanted to go ahead and put this out there as a starting point.

Thoughts?

Activity

  1. felixdoerre commented on Oct 10, 2023

    @felixdoerre
    Contributor

    I think this API suggestion is quite comprehensive and should address all needs I can think of. I would also like to have a way to determine from python if IPv6 support has been compiled in, but that can probably be accomplished by checking for the existence of any of these ipv6-specific methods. As for SLAAC/DHCPv6, I think there should also be a way to detect support, even if it is, calling config('slaac_en', value) and catching an exception.

    Using config to configure lwip seems a bit off to me, as currently this only configures options of the underlying hardware (as emphasized by the fact that this method is implemented per hardware backend). With hostname we actually already have such a parameter, that was once set through config, but has now moved into its own method. ipconfig as in "configure lwip" does not seem to be that far of for me :), as this method is already only implemented once, for lwip.

    Also having an IPv6-only interface feels a bit clunky to me. To have "IPv6 as a first-class citizen" I think that we should have an interface that supports managing both IPv4 and IPv6. We can leave "ifconfig" as a "legacy-interface" (even maybe marked as deprecated and to be removed in at an appropriate time in the future).

    So maybe let's have:

    • <netif>.addrs(<'v6'|'v4'>)
    • <netif>.del_addr(<addr>) that can take v4 and v6 addresses
    • <netif>.add_addr(<addr>, <prefix len>) (does LWIP support having multiple IPv4 Addresses on an interface?)
    • <netif>.config_ip('dhcp_en', <True|False>)
    • ...

    In this case we might want a different way to check for compile-time support for IPv4/IPv6/DHCP/DHCPv6/SLAAC.

    I'm not sure if we should touch hostname again in that same effort to unify. It can by "dynamically" changed in lwip through netif_set_hostname (which the current implementation does not allow to do from python), on the other hand, for the main usage, in dhcp requests, it mostly matters if it is set at the very beginning (although, DHCP renews leases, and mDNS behavior will probably change immediately). Micropython itself wouldn't have to store the hostname and could just directly forward it to lwip. However it seems some ports use the hostname in a port-specific manner, so maybe we shouldn't touch hostname for now.

  2. nullr0ute commented on Oct 10, 2023

    @nullr0ute

    I would think having a single interface for IPv4/IPv6 would be the best way, none of this should need to be special cased, as far as any app running it's just a IP socket (or what ever) and what the underlying transport is shouldn't matter.

  3. felixdoerre commented on Oct 10, 2023

    @felixdoerre
    Contributor

    I think the app socket-interface was never up for discussion. That interface is currently uniform and with #9108 can already be used for connections to IPv4 and IPv6 targets.

    Regarding the API, I had one more thought of what might be missing. Gateway and DNS. For IPv4, DNS and gateway are currently set in one go with the ipv4 address through ifconfig. If we want to deprecate ifconfig, this setting should be exposed through config_ip as well. Setting an IPv6 default gateway does not seem to be supported in LWIP, but setting an IPv6 DNS server seems possible, if you don't have DHCPv6 enabled/active.

  4. dpgeorge commented on Oct 11, 2023

    @dpgeorge
    Member

    @jimmo , @projectgus and I had a discussion about this topic. The summary is follows.

    There are two main parts to support IPv6. The first is in the TCP/IP stack, which for the most part is lwIP. There's not too much to do in that regard (at least not any API to design), except enable and expose lwIP's IPv6 support.

    The second is as discussed in this issue, about configuring IPv6 on various interfaces. We propose to separate configuration of the ETH/MAC layer and the IP layer. The ETH/MAC layer is configured by the existing nic.config() method. We propose to then add a new configuration method for IP configuration, both IPv4 and IPv6, called something like nic.ipconfig(). It would follow the same way the other config method works, being able to set values via keyword arguments, and retrieve them via a string.

    For example:

    • interface.ipconfig(slaac=True) -- enable SLAAC
    • interface.ipconfig(dhcpv6=True)
    • interface.ipconfig(add6="<ipv6 address>") -- add an address
    • interface.ipconfig("addrs6") -- get all v6 addresses
    • interface.ipconfig(prefer=6)
  5. darconeous commented on Oct 12, 2023

    @darconeous
    Author

    @dpgeorge : Does overloading so much functionality into a single method really make enough of a difference in micropython to justify the unfriendliness of such a super-method?

    I kinda liked @felixdoerre's multiple method approach.

  6. felixdoerre commented on Oct 14, 2023

    @felixdoerre
    Contributor

    Yes, I think managing a list through ipconfig(add_addr="10.0.5.2"), ipconfig("addrs"), ipconfig(remove_addr="10.0.5.2") is a bit wired (especially, as ipconfig("add_addr") doesn't make any sense, and one probably wouldn't want to implement ipconfig(addrs="10.0.5.3") or even ipconfig(addrs=["10.0.5.3", "fe80::42"]) which a user might expect given the other variants). While the other properties that we discuss (apart from maybe dns) are just single properties that you want to query or set, the list of currently active ips is not, because one wants to edit it instead of overriding. <netif>.config also allows querying for all parameters that can be set.

    So I would split the interface, having explicit methods for manipulating the IP list and an ipconfig method to handle the rest. Regarding the key to specify IPv4 vs IPv6, I think that "4" and "6" are fine.

    Here is my current attempt at describing a complete API:

    • <netif>.MAX_IPV6_ADDRS
    • <netif>.addrs(4/6), maybe <netif>.addrs() should give all?
    • <netif>.del_addr(<addr>) that can take v4 and v6 addresses
    • <netif>.add_addr(<addr>, <prefix len>)
    • <netif>.ipconfig(dhcp=True) / <netif>ipconfig("dhcp")
    • <netif>.ipconfig(dhcpv6=True) / ..
    • <netif>.ipconfig(slaac=True)
      The other properties don't seem to be interface-specific but global. I assume it is possible to compile micropython with multiple network interface implementations, so I think the global config be done on a method that is not bound to a specific network interface instance, like hostname, that would mean:
    • network.ipconfig(prefer=6)
    • network.ipconfig(gateway="192.168.0.1")
    • network.ipconfig(gateway="fe80::1")
    • network.ipconfig(dns="192.168.0.1")
    • network.ipconfig(dns="fe80::1")

    Compile-time support for features could be determined by querying the appropriate properties:

    • <netif>.ipconfig("slaac") throws a "ValueError"
    • <netif>.addrs(6) throws a "ValueError", if IPv6 is not enabled (as opposed to an empty list, if support is enabled, but there are not IPv6 addresses assigned)
    • <netif>.addrs(4) throws a "ValueError" , if IPv4 is not enabled
  7. HeMan commented on Oct 14, 2023

    @HeMan

    One note is that lwIP currently only supports stateless DHCPv6, ie getting non address information like DNS and NTP. That could however change in the future.

  8. dpgeorge commented on Oct 18, 2023

    @dpgeorge
    Member

    Does overloading so much functionality into a single method really make enough of a difference in micropython to justify the unfriendliness of such a super-method?

    Well, it's more for consistency with the other parts of the API, eg the existing .config() method. And an .ipconfig() method also provides a place to put further extensions/config options in the future, without adding a new method for each one.

    In general, having many small functions does end up costing more in terms of code size (flash/ROM usage) compared to one single super-function.

    Regardless of that, another point in favour of a singe .ipconfig() method (as pointed out to me by @jimmo) is that you can then configure everything at once, by passing in multiple keyword arguments in the one call. And even better than that, you can have a single dict object of settings that you can pass to this method using standard ** arg passing.

    For example:

    if on_network_a:
        ip_settings = dict(dhcpv6=True, prefer=6, ip4_dns='8.8.8.8')
    elif on_network_b:
        ip_settings = dict(dhcpv6=False, prefer=4, dhcpv4=True)
    else:
        ip_settings = json.load(open('ip_settings.json'))
    
    interface.ipconfig(**ip_settings)

    IMO that's much cleaner and easier to maintain than calling individual functions (and note that it's configuring both IPv4 and IPv6 at the same time). I also think this will be the main pattern used to configure the IP settings, doing it all at one point in the code. And we should optimise the API for that common use case (and you can still make individual settings if needed).

  9. dpgeorge commented on Mar 19, 2024

    @dpgeorge
    Member

    IPv6 configuration (for lwIP NICs) has been implemented in 1c6012b

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementFeature requests, new feature implementations

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions