Support scoped IPv6 addresses in SocketAddress.init(ipAddress:port:)#3525
Merged
glbrntt merged 8 commits intoapple:mainfrom Mar 6, 2026
Merged
Conversation
Motivation: `SocketAddress.init(ipAddress:port:)` uses `inet_pton` to parse IP address strings. `inet_pton` does not support the `%scope` suffix used in IPv6 link-local addresses (e.g., `fe80::1%eth0`), causing these addresses to fail with `failedToParseIPString`. This forces downstream consumers like grpc-swift-nio-transport to reimplement scoped IPv6 parsing using `getaddrinfo` themselves. Modifications: When the IP address string contains a `%` character, fall back to `getaddrinfo` with `AI_NUMERICHOST` instead of `inet_pton`. This properly parses the `%scope` suffix and sets `sin6_scope_id` in the resulting `sockaddr_in6`. The scoped path is extracted into a private `_parseScopedIPv6` helper, guarded with `#if !os(Windows) && !os(WASI)`. The non-scoped `inet_pton` path is completely unchanged. Added 5 tests: named scope (`%lo`), numeric scope (`%1`), scoped vs non-scoped inequality, invalid scopes rejected, non-scoped regression. Result: `SocketAddress(ipAddress: "fe80::1%eth0", port: 80)` now succeeds and preserves the scope ID in `sin6_scope_id`. Non-scoped addresses continue to work exactly as before.
c4b7844 to
5045525
Compare
Closed
5 tasks
Lukasa
reviewed
Feb 26, 2026
Lukasa
approved these changes
Mar 3, 2026
macOS's getaddrinfo silently accepts empty scopes and nonexistent interface names with scope_id == 0, unlike Linux which rejects them. Add pre-validation for empty scope strings and post-validation that scope_id is nonzero. Also make scoped IPv6 tests use the actual loopback interface index instead of hardcoding 1.
Contributor
Author
|
I fixed the tests |
Contributor
Author
|
@Lukasa mind kicking off CI? it should pass this time. |
Contributor
Author
|
the previous failures don't seem related to this pr |
This was referenced Mar 6, 2026
glbrntt
pushed a commit
to grpc/grpc-swift-nio-transport
that referenced
this pull request
Mar 17, 2026
## Summary This PR preserves IPv6 scope IDs (e.g., `%eth0` in `fe80::1%eth0`) when converting between gRPC and NIO socket address types. **Updated**: Now significantly simplified since swift-nio natively supports scoped IPv6 addresses as of [apple/swift-nio#3525](apple/swift-nio#3525) (merged March 6, 2026). ## Motivation IPv6 link-local addresses (`fe80::/10`) require a scope ID to identify which network interface to use. Without preservation of the scope ID, `connect()` fails with `EINVAL` on link-local addresses. ## Changes ### Scope ID Preservation (NIO → gRPC) In `NIOSocketAddress+GRPCSocketAddress.swift`, when converting from NIO's `SocketAddress` to gRPC's `SocketAddress.IPv6`: - Reads `sin6_scope_id` from the underlying `sockaddr_in6` - Uses `if_indextoname()` to convert scope ID to interface name - Appends the scope to the host string (e.g., `fe80::1` → `fe80::1%eth0`) ### Simplified Scope ID Parsing (gRPC → NIO) **Previously**: Used `getaddrinfo` with `AI_NUMERICHOST` to parse scoped IPv6 addresses since `inet_pton` doesn't support `%scope` suffixes. **Now**: Simply calls `SocketAddress.init(ipAddress:port:)` since swift-nio now natively handles scoped IPv6 addresses. This removes ~30 lines of workaround code and makes the implementation much cleaner. ## Dependencies **Requires**: swift-nio main branch (will change to a version requirement once swift-nio releases version containing #3525) Related: - swift-nio PR: apple/swift-nio#3525 - Previous grpc-swift-nio-transport PR: #146 ## Testing - ✅ Builds successfully with swift-nio main branch - ✅ Existing tests in DNSResolverTests verify scope preservation - ✅ NIOSocketAddressConversionTests verify bidirectional conversion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
SocketAddress.init(ipAddress:port:)usesinet_ptonto parse IP address strings.inet_ptondoes not support the%scopesuffix used in IPv6 link-local addresses (e.g.,fe80::1%eth0), causing these addresses to fail withfailedToParseIPString. This forces downstream consumers like grpc-swift-nio-transport to reimplement scoped IPv6 parsing usinggetaddrinfothemselves.Related: grpc/grpc-swift-nio-transport#147
Modifications
When the IP address string contains a
%character, fall back togetaddrinfowithAI_NUMERICHOSTinstead ofinet_pton. This properly parses the%scopesuffix and setssin6_scope_idin the resultingsockaddr_in6. The scoped path is extracted into a private_parseScopedIPv6helper, guarded with#if !os(Windows) && !os(WASI). The non-scopedinet_ptonpath is completely unchanged.Added 5 tests:
fe80::1%lo) — verifiessin6_scope_idmatchesif_nametoindexfe80::1%1) — verifies numeric index parsingsin6_scope_idmeans different addresses::1still works withscope_id == 0Result
SocketAddress(ipAddress: "fe80::1%eth0", port: 80)now succeeds and preserves the scope ID insin6_scope_id. Non-scoped addresses continue to work exactly as before.Context
This came up while working on IPv6 link-local support in grpc-swift-nio-transport#146, where the workaround was to use
getaddrinfodirectly in the gRPC layer. The reviewer suggested upstreaming this to swift-nio so the workaround can be removed.