rancher-sandbox / rancher-desktop

Container Management and Kubernetes on the Desktop
https://rancherdesktop.io
Apache License 2.0
6.04k stars 285 forks source link

Fix docker compose network #7820

Open Nino-K opened 4 days ago

Nino-K commented 4 days ago

When Docker Compose is used to deploy container(s) bound to localhost, the guest agent binds the port to the loopback address on the host. However, the service remains inaccessible due to TCP (RST) resets. This issue affects both container engines. Below are the specific details for each container engine:

Moby: When a container is created using Docker Compose, the API propagates a map of network names to network settings. This PR checks for the container.NetworkSettings.Networks property to handle network configuration correctly.

Additionally, Docker Compose, by default, creates the following rules in the DOCKER chain:

DNAT       tcp  --  anywhere             localhost            tcp dpt:80 to:172.18.0.2:80
DNAT       tcp  --  anywhere             anywhere             tcp dpt:80 to::80

The second rule can be problematic because it uses a wildcard IPv6 address (::), which can match any incoming TCP traffic destined for port 80. If no service is listening on IPv6, this can result in a TCP RST (reset) response sent back to the client.

To prevent this issue, we delete the wildcard IPv6 rule before adding our custom rule:

DNAT       tcp  --  anywhere             anywhere             tcp dpt:80 to:172.18.0.2:80

Note: Even if the enable_ipv6 property is set to false in Docker's Compose configuration, Docker still creates the wildcard IPv6 rule in iptables. Therefore, we must manually remove it to avoid this issue.

Containerd:

Previously, the network name was extracted from the following path (/etc/cni/net.d/) of the CNI plugin, and the code was always looking at nerdctl-bridge.conflist, regardless of the actual container network configuration. This approach didn't work in scenarios where the CNI plugin creates additional entries under /etc/cni/net.d/, such as with Docker Compose deployments.

To resolve this, the new approach retrieves the network name from the container's labels instead of relying on the net.d path. This change ensures compatibility with setups like the following Docker Compose configuration:

    services:
      nginx:
        container_name: nginx
        image: nginx
        ports:
          - '127.0.0.1:99:80'
      nginx22:
        container_name: nginx2
        image: nginx
        ports:
          - '101:80'

With this setup, the following iptables rules are created for each service in their corresponding CNI chain:

For nginx service (ports bound to 127.0.0.1:99:80):

    Chain CNI-DN-ffd10b94f0dbc8084b99f (1 references)
    num  target     prot opt source               destination
    1    CNI-HOSTPORT-SETMARK  tcp  --  10.4.1.0/24          localhost            tcp dpt:99
    2    CNI-HOSTPORT-SETMARK  tcp  --  localhost            localhost            tcp dpt:99
    3    DNAT                  tcp  --  anywhere             localhost            tcp dpt:99 to:10.4.1.19:80
    4    DNAT                  tcp  --  anywhere             anywhere             tcp dpt:99 to:10.4.1.19:80

For nginx2 service (ports bound to 101:80):

    Chain CNI-DN-ce90fc2b73da27b388f7c (1 references)
    num  target     prot opt source               destination
    1    CNI-HOSTPORT-SETMARK  tcp  --  10.4.1.0/24          anywhere             tcp dpt:101
    2    CNI-HOSTPORT-SETMARK  tcp  --  localhost            anywhere             tcp dpt:101
    3    DNAT                  tcp  --  anywhere             anywhere             tcp dpt:101 to:10.4.1.20:80

The new approach ensures that when a service binds to localhost, an additional iptables rule is added in the corresponding CNI chain to allow traffic to the destination IP (anywhere) instead of restricting it to localhost only.

Fixes: https://github.com/rancher-sandbox/rancher-desktop/issues/7720 and a user comment here: https://github.com/rancher-sandbox/rancher-desktop/issues/6515#issuecomment-2225114007