IPv6 subnet precedence and fallback in host-interfaces

Currently we differentiate IPv4-subnets from IPv6-subnets in host-interfaces through subnet() and subnet6() methods.

This automatically leads to IPv4-subnets getting precedence over IPv6 ones in most cases, because a lot of code using interfaces is only using the subnet()-method.
This leads to bugs like the following:

A way to solve this is to make all procedures in foreman (and plugins) to define its own rules of precedence and fallback, e.g. if IPv6-only hosts should be created or IPv6 should be preferred.

However, I think this would be better solved by offering theses rules centralized, maybe in the interface or even globally.

A possible solution might be to add a subnet4()-method, which always returns the IPv4-subnet (and nil if none is configured.
The subnet()-method could then be rewritten to either return the configured IPv4- or IPv6-subnet, based on previously set rules of precedence and fallback.

I like this idea. It’s explicit and the user knows what to expect.

In a clean environment I like this idea. The only complexity here is that changing the API is always painful and there are many templates out there. Have you thought of a migration plan?

This change will require fixing many places in core, plugins and templates where reciever expects IPv4-formatted address and other details. It can be a challenge. I’d probably recommend to deprecate subnet call, create subnet4 and subnet6 and subnet6or4 or something similar to indicate this may return one or another. Using deprecation warning and test failures we would be able to find those issues more easily.

Also where would we set the priority?

You can still use subnet(). First you can change it to subnet(any=false) and log a deprecation if any is false. Then templates can be updated to use subnet(any=true). In the next version we can swap the default.

There can be a global setting “Subnet preference” which allows 6, 4 and 4, 6 but I doubt that such a preference actually makes sense. If you have IPv6, most (all?) operating systems prefer this by default. How many people have created /etc/gai.conf to prefer IPv4 over IPv6?

Additional you can introduce subnets which is roughly equal to [subnet6, subnet4].compact. Not sure how useful that is though since you often need a different option for 4 vs 6.

As I’m the one to blame for this, note that there is Subnet::Ipv4 and Subnet::Ipv6 STI classes and Subnet is just a compatibility layer. So when you access Subnet.new, you actually get Subnet::Ipv4.new. Making host.subnet reference the IPv4 subnet was made for compatibility reasons and to keep the APIs stable.
When adding the IPv6 support to core I quickly learned that it’s a good thing to treat the protocol separately because the protocols are different.

Generally speaking, I think it’s good that we prefer IPv4 all over the codebase:

  • IPv6-only datacenter networks are a rare thing afaik
  • We don’t support unattended deployments via IPv6 only at all, do we?
1 Like

If it’s good to treat them separate, then I think we should have explicit subnet4 and subnet6 macros in our templates. Plain subnet should be deprecated.

This will force people to think about dual stack/ipv6, which I think is a good thing.

3 Likes

I’m wondering if this is worth it. We basically break a lot of core APIs for little gain (adding the number 4). I would try to focus on something that has real customer value. :slight_smile:

1 Like

I agree with @ekohl that we should not try to do common subnet method and have two separate ones to bring this to attention when writing code and templates.

I also agree with @TimoGoebel that IPv6 for provisioning is rather rare and the note about braeking everything is worth it.

I am fine if we start preparing the codebase and templates for subnet4 method yet without creating deprecation warning (which will break many plugin tests). Later on we can revisit this and add deprecation warning. But I feel like this is only postponing the break-everything part :slight_smile:

1 Like