From 2363620d74642b4ec1c7ea04fc7762ec50f5e9e9 Mon Sep 17 00:00:00 2001 From: Hayato Kiwata Date: Fri, 3 Jan 2025 18:05:05 +0000 Subject: [PATCH] fix: allow to propagate the address specified in -p option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When containerd is running as a root user and an address other than `127.0.0.1`, such as `127.0.0.2`, is specified in `-p` when `nerdctl run` is used to start a container, the combination of the address and port number allows connections to an application running in the container. Specifically, `curl 127.0.0.2:8080` will access a nginx application running on port `80` in the container created by the following command. ``` $ sudo nerdctl run --name nginx -d -p 127.0.0.2:8080:80 nginx 7abce48d42ef809613365ffaa54d9526e8388c78f5f5c2d8a2850628543ca27e $ sudo nerdctl ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7abce48d42ef docker.io/library/nginx:latest "/docker-entrypoint.…" 3 seconds ago Up 127.0.0.2:8080->80/tcp nginx $ curl 127.0.0.2:8080 ... ``` On the other hand, when running containerd as a non-root user, similarly, suppose we start a container with an address other than `127.0.0.1`, such as `127.0.0.2` for -p. In this case, curl `127.0.0.2:8080` will not connect to a nginx application running inside the container. Details are described below. ``` $ nerdctl run --name nginx -d -p 127.0.0.2:8080:80 nginx 7687f9612afe6847fb3946254718fafef935ffdc05f9cbb4496fc40dd35a6abc $ nerdctl ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7687f9612afe docker.io/library/nginx:latest "/docker-entrypoint.…" 5 seconds ago Up 127.0.0.2:8080->80/tcp nginx $ curl 127.0.0.2:8080 curl: (56) Recv failure: Connection reset by peer ``` When a connection is requested from the outside to the container in Parent NetNS, the rootlesskit port driver relays the connection from Parent NetNS to Child NetNS. When, for example, an address such as `127.0.0.2` is specified in -p, Child NetNS need to establish a connection to the specified address. However, the current implementation will establish a connection to `127.0.0.1` even when `127.0.0.2` is specified in -p. The behavior of not propagating the address specified by -p is assumed at this time, as described in the following document. - https://github.com/rootless-containers/rootlesskit/blob/master/docs/port.md#port-drivers > --port-driver Throughput Source IP > ... > builtin 30.0 Gbps Always 127.0.0.1 > ... > The builtin driver is fast, but be aware that the source IP is not propagated and always set to 127.0.0.1. However, an issue to improve this behavior is reported below. - https://github.com/containerd/nerdctl/issues/3539 Therefore, this commit fixes when running containerd as a non-root user, the address specified in -p will be propagated. Signed-off-by: Hayato Kiwata --- pkg/port/builtin/child/child.go | 5 +++++ pkg/port/builtin/msg/msg.go | 37 ++++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/pkg/port/builtin/child/child.go b/pkg/port/builtin/child/child.go index 83ab638f..2a9e0098 100644 --- a/pkg/port/builtin/child/child.go +++ b/pkg/port/builtin/child/child.go @@ -123,6 +123,11 @@ func (d *childDriver) handleConnectRequest(c *net.UnixConn, req *msg.Request) er ip := req.IP if ip == "" { ip = "127.0.0.1" + if req.ParentIP != "" { + if req.ParentIP != req.HostGatewayIP && req.ParentIP != "0.0.0.0" { + ip = req.ParentIP + } + } } else { p := net.ParseIP(ip) if p == nil { diff --git a/pkg/port/builtin/msg/msg.go b/pkg/port/builtin/msg/msg.go index f77468ec..4bb12d5d 100644 --- a/pkg/port/builtin/msg/msg.go +++ b/pkg/port/builtin/msg/msg.go @@ -19,10 +19,12 @@ const ( // Request and Response are encoded as JSON with uint32le length header. type Request struct { - Type string // "init" or "connect" - Proto string // "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6" - IP string - Port int + Type string // "init" or "connect" + Proto string // "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6" + IP string + Port int + ParentIP string + HostGatewayIP string } // Reply may contain FD as OOB @@ -48,14 +50,33 @@ func Initiate(c *net.UnixConn) error { return c.CloseRead() } +func hostGatewayIP() string { + addrs, err := net.InterfaceAddrs() + if err != nil { + return "" + } + + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String() + } + } + } + + return "" +} + // ConnectToChild connects to the child UNIX socket, and obtains TCP or UDP socket FD // that corresponds to the port spec. func ConnectToChild(c *net.UnixConn, spec port.Spec) (int, error) { req := Request{ - Type: RequestTypeConnect, - Proto: spec.Proto, - Port: spec.ChildPort, - IP: spec.ChildIP, + Type: RequestTypeConnect, + Proto: spec.Proto, + Port: spec.ChildPort, + IP: spec.ChildIP, + ParentIP: spec.ParentIP, + HostGatewayIP: hostGatewayIP(), } if _, err := lowlevelmsgutil.MarshalToWriter(c, &req); err != nil { return 0, err