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

Fix so_original_dst retrieval in dual-stack mode #2841

Merged
merged 2 commits into from
Apr 5, 2024

Conversation

alpeb
Copy link
Member

@alpeb alpeb commented Mar 26, 2024

In 9ee109a we generalized the so_original_dst function to first retrieve the socket's SO_DOMAIN in order to determine what constants to use to retrieve the original destination, that would work for both IP families.

This works fine when the socket is bound to 0.0.0.0 and receives a connection to the pod's IPv4 address, or when bound to :: and receives a connection to the pod's IPv6 address. When the cluster is in dual-stack mode and we bind the socket to ::, it properly receives connections targeted at both the pod's IPv4 and IPv6 addresses, but in the former case the getsockopt call to retrieve the original destination fails with:

WARN ThreadId(01) inbound: linkerd_app_core::serve: Server failed to accept connection error=No such file or directory (os error 2)

The problem is that in this case we're dealing with an IPv4-mapped IPv6 address so SO_DOMAIN returns AF_INET6 but apparently in this case we require AF_INET when interrogating getsockopt for the original destination.

To fix, instead of asking for SO_DOMAIN we determine the peer's IP family and use that to set the appropriate constants for getsocketopt.

In 9ee109a we generalized the `so_original_dst` function to first retrieve the socket's `SO_DOMAIN` in order to determine what constants to use to retrieve the original destination, that would work for both IP families.

This works fine when the socket is bound to `0.0.0.0` and receives a connection to the pod's IPv4 address, or when bound to `::` and receives a connection to the pod's IPv6 address.
When the cluster is in dual-stack mode and we bind the socket to `::`, it properly receives connections targeted at both the pod's IPv4 and IPv6 addresses, but in the former case the `getsockopt` call to retrieve the original destination fails with:

```
WARN ThreadId(01) inbound: linkerd_app_core::serve: Server failed to accept connection error=No such file or directory (os error 2)
```

The problem is that in this case we're dealing with an IPv4-mapped IPv6 address so `SO_DOMAIN` returns `AF_INET6` but apparently in this case we require `AF_INET` when interrogating `getsockopt` for the original destination.

To fix, instead of asking for `SO_DOMAIN` we determine the peer's IP family and use that to set the appropriate constants for `getsocketopt`.
@alpeb alpeb requested a review from a team as a code owner March 26, 2024 17:36
Copy link
Member

@olix0r olix0r left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a mind towards (1) keeping unsafe code as minimal as possible and (2) avoiding needless per connection work, I'm curious if we can rework this slightly:

  • Can we keep our unsafe code in distinct so_original_dst_v4 and so_original_dst_v6 functions so that the determination logic is handled in safe code?
  • We should not need to invoke peer_addr() again. This incurs another syscall. In BindWithOrigDst, we should be able to add the type constraint B::Addrs: Param<Remote<ClientAddr>> and then get the already-read peer address from the socket metadata. This would be something like
        let incoming = incoming.map(|res| {
            let (inner, tcp) = res?;
            let is_ipv4 = match inner.param() {
                Remote(ClientAddr(SocketAddr::V4(_)))) => true,
                Remote(ClientAddr(SocketAddr::V6(a))) => a.ip().to_ipv4_mapped().is_some(),
            };
            let orig_dst = if is_ipv4 {
                orig_dst_addr_v4(&tcp)?;
            } else {
                orig_dst_addr_v6(&tcp)?;
            };
            let addrs = Addrs { inner, orig_dst };
            Ok((addrs, tcp))
        })

@alpeb
Copy link
Member Author

alpeb commented Apr 1, 2024

Ah that makes a lot of sense. I've pushed the suggested changes 👍

@olix0r olix0r merged commit 7338f6b into main Apr 5, 2024
15 checks passed
@olix0r olix0r deleted the alpeb/original-dst-dual-stack branch April 5, 2024 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants