Introduce stronger types for link-local addresses and unnumbered peers#10082
Introduce stronger types for link-local addresses and unnumbered peers#10082jgallagher wants to merge 26 commits intomainfrom
Conversation
| impl UserSpecifiedUplinkAddressConfig { | ||
| /// String representation for [`UplinkAddress::LinkLocal`] when | ||
| /// serializing/deserializing [`UserSpecifiedUplinkAddressConfig`]. | ||
| pub const LINK_LOCAL: &str = "link-local"; |
There was a problem hiding this comment.
This is the first breaking change w.r.t. RSS. In the RSS config files passed to wicket, specifying a link-local uplink address now requires:
address = "link-local"instead of passing "0.0.0.0" or "::"; both of those will now be rejected by wicket (with an error message suggesting "link-local" instead).
There's a similar change below for BGP peer addresses, where we're now required to say
addr = "unnumbered"instead of "0.0.0.0" or "::" or omitting it entirely (all of which are similarly rejected with an error message suggesting "unnumbered").
(cc @taspelund who suggested "unnumbered" specifically)
There was a problem hiding this comment.
One clarification on "link-local": do we support (or plan to support) explicit link-local uplink addresses? Or do/will we only support auto-derived link-local uplink addresses?
It seems like the current use of "link-local" is meant to represent an auto derived address, but the terminology seems like it would be slightly at odds with an explicit link-local address.
i.e.
"link-local" makes it seem like the only alternative is a routable address. But if we allow someone to configure a specific link-local address (e.g. address = fe80::beef) then the "link-local" string seems less meaningful since it doesn't quite capture the full nuance of it being both link-local and auto derived.
Hopefully I'm making sense and not just rambling
There was a problem hiding this comment.
A good question but not for me 😅. A couple options here in terms of this representation:
UplinkAddress::Address { .. }could disallow link-local addresses in the innerIpNetjust like it disallows0.0.0.0/nand::/non this branch, although I'm not sure what to name the inner type if we do that;SpecifiedIpNetseems okay for "IpNet that isn't using an unspecified addr", but I don't know what to call "IpNet that isn't using an unspecified addr or a link-local address"UplinkAddress::Address { .. }should allow explicit link-local addresses, and we renameUplinkAddress::LinkLocalto something likeUplinkAddress::AutoLinkLocal
Is one of those correct in terms of what we want to support?
There was a problem hiding this comment.
do we support (or plan to support) explicit link-local uplink addresses? Or do/will we only support auto-derived link-local uplink addresses?
Not sure I'm the correct person to answer this either, but looking at illumos today it would seem that we do not support explicit link local addressing, since the addrconf address type does not appear to allow an explicit address, and if you create a static ipv6 it automatically creates an addrconf link local address under the hood.
ipadm create-addr [-t] -T static [-d] -a
[local|remote=]addr[/prefixlen]... addrobj
Create an address on the specified IP interface using static
configuration. The address will be enabled but can disabled
using the ipadm disable-addr subcommand. Note that addrconf
address configured on the interface is required to configure
static IPv6 address on the same interface. This takes the
following options:
-a,--address
Specify the address. The local or remote prefix can be
used for a point-to-point interface. In this case, both
addresses must be given. Otherwise, the equal sign ("=")
should be omitted and the address should be provided by
itself without second address.
-d,--down
The address is down.
-t,--temporary
Temporary, not persistent across reboots.
...
ipadm create-addr [-t] -T addrconf [-i interface-id] [-p
{stateful|stateless}={yes|no}]... addrobj
Create an auto-configured address on the specified IP interface.
This takes the following options:
-i,--interface-id
Specify the interface ID to be used.
-p,--prop
Specify which method of auto-configuration should be
used.
-t,--temporary
Temporary, not persistent across reboots.
Something that might be worthwhile to distinguish is that this address configuration is creating a v6 link-local address, because technically there is a v4 link-local address space too (169.254.0.0/16).
| routes = [{nexthop = "192.168.1.199", destination = "0.0.0.0/0"}] | ||
| # Addresses associated with this port. | ||
| addresses = [{address = "192.168.1.30/24"}] | ||
| addresses = [{address = {type = "address", ip_net = "192.168.1.30/24"}}] |
There was a problem hiding this comment.
This is the second breaking change w.r.t. RSS configs - in the TOML files that specify the full config (that are only used in development, which does include a4x2), we now have to use the somewhat-more-annoying tagged representation for uplink addresses and BGP peer addresses, since these are now enums instead of Option<IpAddr> or Option<IpNet>.
We could make this use the more flexible parsing we're using now in wicket, but I didn't love that because that would affect our OpenAPI schema for real RSS config handoffs from sled-agent to Nexus, and eventually the external API if we reuse these types there. The wicket representation shows up in the OpenAPI spec as just "string", which happens to have a bunch of rules around it that can't be easily expressed in OpenAPI (e.g., "must be either unnumbered or an IP address other than 0.0.0.0 or ::"). The wicketd OpenAPI spec does have this problem, but it's only used by wicket, not the rest of the control plane or the external API.
We could potentially define different types for "reading config-rss.toml from disk" and "to use in OpenAPI", but that seemed like a bunch of duplication that is maybe even more confusing than the two different types of formatting. Feedback very welcome.
There was a problem hiding this comment.
Are we tracking making this change to the lab configs, or customer versions and templates for them?
There was a problem hiding this comment.
This change doesn't apply to any of the "real" RSS configs, for labs or customers; the only breaking change for those is #10082 (comment), which only applies to link-local addresses or unnumbered peers ("regular" IPs still parse the same way in the real path). I don't think any of the lab configs use those; I'll ask around about customer templates once this is ready to land.
| /// Associate a VLAN ID with a BGP peer session. | ||
| #[serde(default)] | ||
| pub vlan_id: Option<u16>, | ||
| /// Router lifetime in seconds for unnumbered BGP peers. |
There was a problem hiding this comment.
Huh, I just noticed this comment. Is router_lifetime only meaningful if addr is an unnumbered peer? If so, should we put it inside the RouterPeerAddress enum?
pub enum RouterPeerAddress {
Unnumbered { lifetime: RouterLifetimeConfig },
Numbered { ip: SpecifiedIpAddr },
}
bnaecker
left a comment
There was a problem hiding this comment.
Looks great, thanks for doing all this! I have a few minor bits, but nothing blocking.
| use sled_agent_types::early_networking::TxEqConfig; | ||
| use sled_agent_types::early_networking::UplinkAddressConfig; | ||
| use sled_agent_types::early_networking::WriteNetworkConfigRequest; | ||
| use sled_agent_types::early_networking::{ |
There was a problem hiding this comment.
Style nit: these are group-imported, but looks like everything else is on its own line. Could we do the same here for consistency?
| pub rack_network_config: RackNetworkConfig, | ||
|
|
||
| /// IPs or subnets allowed to make requests to user-facing services | ||
| #[serde(default = "default_allowed_source_ips")] |
There was a problem hiding this comment.
This is totally just idle curiosity, but do we need this default? This is deserialized by converting fallibly from the unvalidated version below, which does have and need that default.
| routes = [{nexthop = "192.168.1.199", destination = "0.0.0.0/0"}] | ||
| # Addresses associated with this port. | ||
| addresses = [{address = "192.168.1.30/24"}] | ||
| addresses = [{address = {type = "address", ip_net = "192.168.1.30/24"}}] |
There was a problem hiding this comment.
Are we tracking making this change to the lab configs, or customer versions and templates for them?
This is a big chunk of #9832. Stealing from the doc comments in
sled-agent/types/versions/src/stronger_bgp_unnumbered_types/early_networking.rs, the primary changes in this PR are:SpecifiedIpNet, a newtype wrapper aroundIpNetthat does not allow unspecified IP addresses.SpecifiedIpAddr, a newtype wrapper aroundIpAddrthat does not allow unspecified IP addresses.UplinkAddress, a stronger type for specifying possibly-link-local IP nets. This is the new type ofUplinkAddressConfig::address, which was previously anOption<IpNet>where bothNoneandSome(UNSPECIFIED)were treated as link-local.RouterPeerAddress, a stronger type for specifying possibly-unnumbered BGP peer addresses. This is the new type ofBgpPeerConfig::addr, which was previously anIpAddrwhere an unspecified address was treated as unnumbered.The rest of the changes are fallout from those: adding new types for any the that contains
UplinkAddressConfigorBgpPeerConfig, since those changed, and updating all the places that create or consume any of those types. I'm hoping this PR is pretty straightforward to review despite its size, because much of the size is either tests or all the noise of redefining a bunch of big structs with a single field changed.The two main things I'd consider part of #9832 that are NOT addressed in this PR:
NULL,0.0.0.0, or::. Fixing this will require a db migration, so I want to do that separately.A third thing we could consider is whether to push this stronger typing down to maghemite too.
For now, in all these cases we convert to or from the stronger types primarily through obnoxiously-long method names that should stick out like sore thumbs (
RouterPeerAddress::from_optional_ip_treating_unspecified_as_unnumbered()and friends). This should make it obvious where we're switching from strong types to weaker or vice versa.There are a couple of breaking changes in how we specify RSS configs. I'll leave a couple comments below with more details.