Skip to content

Commit

Permalink
Create loopback iptables rules for localhost
Browse files Browse the repository at this point in the history
When a container uses the host network driver (--network=host), it shares the host's network namespace.
In this case, it's important to consider the IP address the container's process is bound to. For example:

`nerdctl run --network=host -d python:slim python -m http.server 8020 --bind 127.0.0.1`

vs

`nerdctl run --network=host -d python:slim python -m http.server 8020`

If the process is bound to 127.0.0.1 (localhost), we need to create additional iptables rules to allow access
to the container’s bound port from outside the network namespace (e.g., localhost:8020).

This change ensures that the appropriate iptables rules are created and removed when a container's port is
bound to localhost, enabling external access to the bound port.

Signed-off-by: Nino Kodabande <[email protected]>
  • Loading branch information
Nino-K committed Nov 13, 2024
1 parent 4771876 commit bc3af5b
Showing 1 changed file with 61 additions and 0 deletions.
61 changes: 61 additions & 0 deletions src/go/guestagent/pkg/procnet/scanner_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"encoding/hex"
"fmt"
"net"
"os/exec"
"strconv"
"time"

Expand All @@ -29,6 +30,13 @@ import (
"github.com/rancher-sandbox/rancher-desktop/src/go/guestagent/pkg/tracker"
)

type action string

const (
Append action = "append"
Delete action = "delete"
)

func ForwardPorts(ctx context.Context, tracker tracker.Tracker, scanInterval time.Duration) error {
ticker := time.NewTicker(scanInterval)
defer ticker.Stop()
Expand Down Expand Up @@ -63,6 +71,10 @@ func ForwardPorts(ctx context.Context, tracker tracker.Tracker, scanInterval tim
})
if err != nil {
log.Errorf("procnet scanner failed to add port: %s", err)
continue
}
if err = execLoopbackIPtablesRule(bindings, port, Append); err != nil {
log.Errorf("procnet scanner creating loopback iptable rules for portbinding: %v failed: %s", bindings, err)

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

portbinding is not a recognized word. (unrecognized-spelling)
}
}
}
Expand All @@ -74,6 +86,11 @@ func ForwardPorts(ctx context.Context, tracker tracker.Tracker, scanInterval tim
err := tracker.Remove(generateID(fmt.Sprintf("%s/%s", port.Proto(), port.Port())))
if err != nil {
log.Errorf("procnet scanner failed to remove port: %s", err)
continue
}

if err = execLoopbackIPtablesRule(previousBindings, port, Delete); err != nil {
log.Errorf("procnet scanner deleting loopback iptable rules for portbinding: %v failed: %s", previousBindings, err)

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

portbinding is not a recognized word. (unrecognized-spelling)
}
}
}
Expand All @@ -83,6 +100,50 @@ func ForwardPorts(ctx context.Context, tracker tracker.Tracker, scanInterval tim
}
}

// execLoopbackIPtablesRule modifies iptables NAT rules to handle loopback traffic for a specified port
// and protocol. This function is only necessary when the container is using the host network driver
// (i.e., with --network=host), as in this case the container shares the host's network namespace.
//
// When using the host network driver, the network traffic that is bound to 127.0.0.1 would need to be
// redirected from outside of the network namespace to the localhost (127.0.0.1). This function adds or
// removes DNAT rules that allow traffic to be forwarded to the specified port on localhost, based on
// the provided 'action' ('append' or 'delete').
//
// The function iterates over the provided list of port bindings and, for each binding where the HostIP
// is set to 127.0.0.1, it constructs and executes the corresponding iptables command to either add or
// delete the appropriate DNAT rule.
//
// The iptables rule ensures that incoming traffic from outside of the network namespace (i.e.,
// from the host windows machine) on the specified port and protocol is redirected to the same
// port on localhost, where the container's service can be accessed.
//
// Example iptables rule when 'action' is "append":
// iptables -t nat -A PREROUTING -p tcp --dport 8009 -j DNAT --to-destination 127.0.0.1:8009
//
// Example iptables rule when 'action' is "delete":
// iptables -t nat -D PREROUTING -p tcp --dport 8009 -j DNAT --to-destination 127.0.0.1:8009

func execLoopbackIPtablesRule(bindings []nat.PortBinding, portProto nat.Port, action action) error {
for _, binding := range bindings {
if binding.HostIP == "127.0.0.1" {
// iptables -t nat -D PREROUTING -p tcp --dport 8009 -j DNAT --to-destination 127.0.0.1:8009
iptablesCmd := exec.Command("iptables",
"--table", "nat",
fmt.Sprintf("--%s", action), "PREROUTING",
"--protocol", portProto.Proto(),
"--dport", binding.HostPort,
"--jump", "DNAT",
"--to-destination", fmt.Sprintf("%s:%s", binding.HostIP, binding.HostPort),
)
if err := iptablesCmd.Run(); err != nil {
return err
}
log.Debugf("running the following iptables rule [%s] for port bindings: %v", iptablesCmd.String(), binding)
}
}
return nil
}

func addValidProtoEntryToPortMap(entry procnettcp.Entry, portMap nat.PortMap) error {
switch entry.Kind {
case procnettcp.TCP:
Expand Down

0 comments on commit bc3af5b

Please sign in to comment.