From 305b42583073aa1435d15ff5773c049827fa8d51 Mon Sep 17 00:00:00 2001 From: Antonio Ojea Date: Tue, 9 Mar 2021 11:02:38 +0100 Subject: [PATCH] use happy-eyeballs for port-forwarding golang has enabled RFC 6555 Fast Fallback (aka HappyEyeballs) by default in 1.12. It means that if a host resolves to both IPv6 and IPv4, it will try to connect to any of those addresses and use the working connection. However, the implementation uses go routines to start both connections in parallel, and this has limitations when running inside a namespace, so we try to the connections serially, trying IPv4 first for keeping the same behaviour. xref https://github.com/golang/go/issues/44922 Signed-off-by: Antonio Ojea --- pkg/cri/server/sandbox_portforward_linux.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/cri/server/sandbox_portforward_linux.go b/pkg/cri/server/sandbox_portforward_linux.go index 32b062456..33e3e836a 100644 --- a/pkg/cri/server/sandbox_portforward_linux.go +++ b/pkg/cri/server/sandbox_portforward_linux.go @@ -62,12 +62,24 @@ func (c *criService) portForward(ctx context.Context, id string, port int32, str log.G(ctx).Infof("Executing port forwarding in network namespace %q", netNSPath) err = netNSDo(func(_ ns.NetNS) error { defer stream.Close() - // TODO: hardcoded to tcp4 because localhost resolves to ::1 by default if the system has IPv6 enabled. - // Theoretically happy eyeballs will try IPv6 first and fallback to IPv4 - // but resolving localhost doesn't seem to return and IPv4 address, thus failing the connection. + // localhost can resolve to both IPv4 and IPv6 addresses in dual-stack systems + // but the application can be listening in one of the IP families only. + // golang has enabled RFC 6555 Fast Fallback (aka HappyEyeballs) by default in 1.12 + // It means that if a host resolves to both IPv6 and IPv4, it will try to connect to any + // of those addresses and use the working connection. + // However, the implementation uses go routines to start both connections in parallel, + // and this cases that the connection is done outside the namespace, so we try to connect + // serially. + // We try IPv4 first to keep current behavior and we fallback to IPv6 if the connection fails. + // xref https://github.com/golang/go/issues/44922 + var conn net.Conn conn, err := net.Dial("tcp4", fmt.Sprintf("localhost:%d", port)) if err != nil { - return errors.Wrapf(err, "failed to dial %d", port) + var errV6 error + conn, errV6 = net.Dial("tcp6", fmt.Sprintf("localhost:%d", port)) + if errV6 != nil { + return fmt.Errorf("failed to connect to localhost:%d inside namespace %q, IPv4: %v IPv6 %v ", port, id, err, errV6) + } } defer conn.Close()