microsoft / microsoft/ebpf-for-windows
Linux compatibility: IPv4-mapped AF_INET6 connect invokes cgroup/connect4 instead of connect6
- Dominant language
- C
- Stars
- 3.6k
- Forks
- 311
- Avg merge
- 6d 10h
- Merged PRs (30d)
- 21
Description
## Describe the bug
An `AF_INET6` dual-stack socket (`IPV6_V6ONLY=0`) connecting to an IPv4 destination through an IPv4-mapped IPv6 address, for example `[::ffff:169.254.169.254]`, invokes `BPF_CGROUP_INET4_CONNECT` (`cgroup/connect4`) on eBPF for Windows.
Linux invokes `BPF_CGROUP_INET6_CONNECT` (`cgroup/connect6`) for the same socket API operation because the hook runs from the IPv6 protocol `pre_connect` path before the IPv4-mapped destination is translated for IPv4 transport.
This is a source-semantics compatibility issue: a portable policy attached only to `cgroup/connect6` observes this request on Linux but misses it on Windows. For security policies, this can create an unintended policy gap.
The current Windows behavior appears intentional and is covered by the `v4_mapped` IPv4 test groups, but it differs from Linux hook selection semantics.
## OS information
All Windows versions supported by the current implementation. This is a code-level behavior in `netebpfext`, not an OS-version-specific observation.
## Steps taken to reproduce bug
1. Create an `AF_INET6` TCP or UDP socket.
2. Set `IPV6_V6ONLY` to `0` (dual-stack).
3. Attach one counter program to `cgroup/connect4` and another to `cgroup/connect6`.
4. Call `connect()` with a `sockaddr_in6` destination such as `[::ffff:169.254.169.254]:80`.
5. Compare which counter increments on Windows and Linux.
## Expected behavior
Match Linux semantics:
- Invoke `cgroup/connect6`, since the user supplied an `AF_INET6` / `sockaddr_in6` request.
- Present `ctx->family == AF_INET6`.
- Preserve the IPv4-mapped address in `ctx->user_ip6`.
- Do not invoke `cgroup/connect4` for this request.
Internal WFP processing may still translate the destination to IPv4 after the eBPF hook runs.
## Actual outcome
Windows invokes `cgroup/connect4`.
At `FWPM_LAYER_ALE_CONNECT_REDIRECT_V6`, `net_ebpf_extension_sock_addr_redirect_connection_classify()` detects the IPv4-mapped address. `_net_ebpf_extension_sock_addr_should_invoke_ebpf_program()` selects the IPv4 attach type and skips the IPv6 attach type. `_convert_ipv4_mapped_to_ipv4()` then changes the public context to `AF_INET`, clears the 16-byte `user_ip6` union storage, and writes the destination to `user_ip4` before invoking the eBPF program.
Consequently, the program cannot determine from the public `bpf_sock_addr_t` whether the request originated as native IPv4 or as an IPv4-mapped `AF_INET6` request. The implementation retains a private `v4_mapped` bit in `net_ebpf_sock_addr_t`, but it is not exposed to the program.
## Additional details
Relevant implementation:
- `netebpfext/net_ebpf_ext_sock_addr.c`: `_net_ebpf_extension_sock_addr_should_invoke_ebpf_program()`
- `netebpfext/net_ebpf_ext_sock_addr.c`: `_convert_ipv4_mapped_to_ipv4()`
- `netebpfext/net_ebpf_ext_sock_addr.c`: `_cgroup_inet4_connect_filter_parameters` installs an IPv6 redirect-layer filter specifically for dual-stack processing
- `tests/connect_redirect/connect_redirect_tests.cpp`: `v4_mapped` cases are declared as IPv4 authorization and redirection test groups
Linux reference behavior:
- `tcp_v6_pre_connect()` invokes `BPF_CGROUP_RUN_PROG_INET6_CONNECT()` before `tcp_v6_connect()` converts a mapped destination and calls `tcp_v4_connect()`.
- `udpv6_pre_connect()` similarly invokes `BPF_CGROUP_RUN_PROG_INET6_CONNECT_LOCK()` for an `AF_INET6` address before `__ip6_datagram_connect()` handles the mapped destination.
A behavior change may require compatibility planning because existing Windows programs and tests can depend on the current `connect4` dispatch. At minimum, the mismatch should be explicitly documented; preferably, hook dispatch and context semantics should match Linux.
Contributor guide
Assessment
This issue has not been assessed yet.