Skip to content

Commit 9946cfc

Browse files
borkmanntklauser
authored andcommitted
cilium, socklb: Terminate also v4-in-v6 mapped sockets
[ upstream commit b99a60b ] @m0untains reported that v4-in-v6 mapped sockets connecting to a v4 UDP backend are not properly terminated: - Bind a SOCK_DGRAM AF_INET socket in a server. - Create a corresponding kubernetes Service for this server. - In a client, create a SOCK_DGRAM AF_INET6 socket. Configure the address to use a v4-mapped-on-v6 address type. E.g. if the kubernetes service address is 10.2.3.4, configure the address the client will connect to as ::ffff:10.2.3.4. - Use the connect() + send() syscalls to create a long-lived udp socket, and send packets at some interval (e.g. every 10 seconds) from the client to the server. Note: using sendto(), i.e. a short-lived socket effectively works around the issue, and does not produce the undesired behavior. - Restart the server - Notice how the packets from the client are still sent to the old server IP address. For the client v4-in-v6 case we store the revnat entry in cilium_lb4_reverse_sk map. When the backend goes down, we iterate all clients and the current logic derives where to iterate in netlink based on the backend's address family (in this case v4). But given the client is a v6 socket, it will never be found from the v4 iteration. So this means for all v4 backends, we also need to iterate all v6 sockets in addition to try and find a match. Closes: cilium#39470 Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
1 parent cabd410 commit 9946cfc

1 file changed

Lines changed: 15 additions & 1 deletion

File tree

pkg/datapath/sockets/sockets.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func Destroy(filter SocketFilter) error {
6666
// sockets.
6767
switch protocol {
6868
case unix.IPPROTO_UDP:
69+
redo:
6970
err := filterAndDestroyUDPSockets(family, func(sock netlink.SocketID, err error) {
7071
if err != nil {
7172
errs = errors.Join(errs, fmt.Errorf("UDP socket with filter [%v]: %w", filter, err))
@@ -86,7 +87,20 @@ func Destroy(filter SocketFilter) error {
8687
if err != nil {
8788
return fmt.Errorf("failed to get sockets with filter %v: %w", filter, err)
8889
}
89-
90+
// After we iterated all IPv4 sockets, we now need to do the same
91+
// also for IPv6 client sockets given they can have IPv4-in-IPv6
92+
// mapped addresses and got connected to the terminating IPv4
93+
// backend this way.
94+
//
95+
// Note that socketlb placed the revnat entry into the IPv4-related
96+
// map (not the IPv6 one!). The DestroyCB looks up the right revnat
97+
// BPF map based on the filter.DestIp which in our case is an IPv4
98+
// address. The filter.DestIp stores the IPv4 address internally
99+
// as IPv4-mapped IPv6 form as per net.IP.
100+
if family == syscall.AF_INET {
101+
family = syscall.AF_INET6
102+
goto redo
103+
}
90104
default:
91105
return fmt.Errorf("unsupported protocol for socket destroy: %d", protocol)
92106
}

0 commit comments

Comments
 (0)