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

IPv6 scoped link-local addresses (RFC4007) #15264

Open
foxxx0 opened this issue Dec 10, 2024 · 6 comments · May be fixed by #15263
Open

IPv6 scoped link-local addresses (RFC4007) #15264

foxxx0 opened this issue Dec 10, 2024 · 6 comments · May be fixed by #15263

Comments

@foxxx0
Copy link

foxxx0 commented Dec 10, 2024

Currently the crystal stdlib does not allow specifying scoped IPv6 link-local addresses.
The specifics around these are described in IETF RFC4007:
https://datatracker.ietf.org/doc/html/rfc4007

The gist of it is:
Per definition IPv6 link-local addresses are scoped to their specific interface (hence the link-local). This also means, that you can have the same link-local address on multiple interfaces.
Without the ability to specify the corresponding interface as part of the address, using them is pretty much impossible in a meaningful manner.
As per RFC4007, these scoped IPv6 link-local addresses look like e.g.: fe80::1%eth0.

The kernels and C libraries on the various platforms support exactly that via the sin6_scope_id property in the sockaddr_in6 struct.

In order to support these in the crystal stdlib it would require some adjustments to Socket::Address parsing, its stucts and adding LibC bindings to if_nameotindex() and optionally if_indextoname().

This should be possible without breaking changes to existing interfaces.

There aren't really any alternatives that I could imagine, such a feature should be supported by the stdlib itself with its native datatypes.

Due to me failing to properly consult the contributing guidelines, I tackled this topic the wrong way around and have already created an implementation of this feature in #15263 .

To separate the discussion of the feature itself and its actual implementation in the stdlib, this feature-request issue is designated for the former.

@RX14
Copy link
Contributor

RX14 commented Dec 10, 2024

I think the biggest challenge is about how the zone_id gets exposed and passed around, zone_id can be an interface name on creation of the address, but that info gets lost pretty quickly and it's turned into a numeric interface ID. I believe it's sensible to provide the indextoname API at the same time, so there needs to be naming on the crystal side for both the always-numeric sin6_scope_id and the textual interface name. The parsing from address string part doesn't require any interface change at all, just additional parsing logic. The IPAddress.v6 constructor will need to take both the numeric and string IDs.

@foxxx0
Copy link
Author

foxxx0 commented Dec 10, 2024

Since the crystal Socket structs are tightly coupled with the LibC bindings, I think it would be best to always just store the numerical interface index into something like @scope_id which would represent sin6_scope_id.
If, at any point, the user wants to get the interface name via that numerical index, they could just use if_indextoname().

Additionally, sin6_scope_id is only valid for link-local addresses, i.e. those starting with fe80::. For all other non-link-local addresses this property is simply 0 (zero). If we were to always keep the interface name around as well, we would potentially just have an empty String instance lingering around for most cases.

While that could be a nice-to-have thing in some scenarios, I think the cons outweigh the potential benefits.
As long as we provide the means to lookup the interface name from an interface index, that should suffice imho.

@RX14
Copy link
Contributor

RX14 commented Dec 11, 2024

Yes, I agree on only storing the numeric id, just wondering how to name the result of turning that back into an interface name. Should it be named just interface_name, or zone or scope? Are there any cases where the scope of a v6 address is not just an interface name? I guess if any usecase comes up it's easy enough to detect the different type of address and handle it, but I'd like to be sure. Of course, I've never seen a v6 addr which has a scope and isn't link local, but I can't be sure there isn't an obscure RFC somewhere.

@foxxx0
Copy link
Author

foxxx0 commented Dec 11, 2024

As mentioned in the inital description as well as in the IETF RFC4007, such interface identifiers are only valid / allowed for link-local IPv6 addresses. In all other cases, this property is supposed to just be 0 (zero).

You can also immediately specify the numerical interface index as the identifier in the address, e.g. fe80::1%3, where the scope_id / sin6_scope_id would then immediately become 3 without any lookups. If there is no network interface with index 3 enumerated in the kernel, you'll run into socket exception soon afterwards.

For ease-of-use I'm preferring the ability to specify the interface name instead, and I'd expect the implementation to lookup its corresponding interface index for me (as per the RFC), so that I don't have to that.

Within the kernel/libc structs, sin6_scope_id is always the numerical interface index though.

As for naming the "reverse" operating, I'd lean towards something like scope_interface, if we name the numerical variant scope_id. Other possibilities could be scope_iface or scope_device. However, I don't really know where I would put such a wrapper method around if_indextoname() into the stdlib, but I do think it would be quite handy to have for avoiding all of that cumbersome LibC::Char* and uninitialized StaticArray(UInt8) mess when just wanting to lookup the interface name by index.

@RX14
Copy link
Contributor

RX14 commented Dec 11, 2024

RFC 4007 actually goes to great pains to differentiate between ipv6 link-local addressing, and the more general construction of scopes and the %<zone_id> syntax for any type of non-global addressing. After doing some research myself, I'm convinced that link-local addressing is the only scope-specific addressing that uses zone identifiers.

As for naming, the specification seems clear that "scope" refers to the generic type, i.e. link-local, site-local, global, etc. and "zone" refers to the instance of that scope. So best that it's zone_id like the spec, and not scope_id like the kernel. Then, convinced that link-local is the only implementation of zone id syntax, we could just name the conversion back to an interface name link_local_interface. Though, I am open to other suggestions. zone_interface doesn't seem quite right to me.

@ysbaddaden
Copy link
Contributor

@RX14 these names make sense to me.

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

Successfully merging a pull request may close this issue.

4 participants