Skip to content

Commit

Permalink
Merge pull request #7890 from rancher-sandbox/fix-iptables-host-ip
Browse files Browse the repository at this point in the history
 Fix iptables scanner to assign correct HostIP
  • Loading branch information
jandubois authored Dec 11, 2024
2 parents 9557e46 + f0ba7ea commit f692d77
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 53 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ libxfixes
libxkbcommon
libxrandr
limactl
limaiptables
limaloc
linds
linebreak
Expand Down
31 changes: 16 additions & 15 deletions src/go/guestagent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,15 @@ func main() {
}

if *enableKubernetes {
group.Go(func() error {
k8sServiceListenerIP := net.ParseIP(*k8sServiceListenerAddr)
k8sServiceListenerIP := net.ParseIP(*k8sServiceListenerAddr)

if k8sServiceListenerIP == nil || !(k8sServiceListenerIP.Equal(net.IPv4zero) ||
k8sServiceListenerIP.Equal(net.IPv4(127, 0, 0, 1))) {
log.Fatalf("empty or none valid input for Kubernetes service listener IP address %s. "+
"Valid options are 0.0.0.0 and 127.0.0.1.", *k8sServiceListenerAddr)
}
if k8sServiceListenerIP == nil || !(k8sServiceListenerIP.Equal(net.IPv4zero) ||
k8sServiceListenerIP.Equal(net.IPv4(127, 0, 0, 1))) {
log.Fatalf("empty or invalid input for Kubernetes service listener IP address %s. "+
"Valid options are 0.0.0.0 and 127.0.0.1.", *k8sServiceListenerAddr)
}

group.Go(func() error {
// Watch for kube
err := kube.WatchForServices(ctx,
*configPath,
Expand All @@ -190,19 +190,20 @@ func main() {
if err != nil {
return fmt.Errorf("kubernetes service watcher failed: %w", err)
}
return nil
})

group.Go(func() error {
iptablesScanner := iptables.NewIptablesScanner()
iptablesHandler := iptables.New(ctx, portTracker, iptablesScanner, k8sServiceListenerIP, iptablesUpdateInterval)
err := iptablesHandler.ForwardPorts()
if err != nil {
return fmt.Errorf("iptables port forwarding failed: %w", err)
}
return nil
})
}

group.Go(func() error {
err := iptables.ForwardPorts(ctx, portTracker, iptablesUpdateInterval)
if err != nil {
return fmt.Errorf("iptables port forwarding failed: %w", err)
}
return nil
})

group.Go(func() error {
procScanner, err := procnet.NewProcNetScanner(ctx, portTracker, procNetScanInterval)
if err != nil {
Expand Down
66 changes: 39 additions & 27 deletions src/go/guestagent/pkg/iptables/iptables.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,61 @@ package iptables

import (
"context"
"crypto/sha256"
"encoding/hex"
"net"
"strconv"
"strings"
"time"

"github.com/Masterminds/log-go"
"github.com/docker/go-connections/nat"
"github.com/lima-vm/lima/pkg/guestagent/iptables"
limaiptables "github.com/lima-vm/lima/pkg/guestagent/iptables"
"github.com/rancher-sandbox/rancher-desktop/src/go/guestagent/pkg/tracker"
"github.com/rancher-sandbox/rancher-desktop/src/go/guestagent/pkg/utils"
)

// Iptables manages port forwarding for ports identified in iptables DNAT rules.
// It is primarily responsible for handling port mappings in Kubernetes environments that
// are not exposed via the Kubernetes API. The package scans iptables for these port and uses
// the k8sServiceListenerAddr setting for the hostIP property to create a port mapping and
// forwards them to both the API tracker and the WSL Proxy for proper routing and handling.
type Iptables struct {
context context.Context
apiTracker tracker.Tracker
scanner Scanner
listenerIP net.IP
// time, in seconds, to wait between updating.
updateInterval time.Duration
}

func New(ctx context.Context, tracker tracker.Tracker, iptablesScanner Scanner, listenerIP net.IP, updateInterval time.Duration) *Iptables {
return &Iptables{
context: ctx,
apiTracker: tracker,
scanner: iptablesScanner,
listenerIP: listenerIP,
updateInterval: updateInterval,
}
}

// ForwardPorts forwards ports found in iptables DNAT. In some environments,
// like WSL, ports defined using the CNI portmap plugin happen through iptables.
// These ports are not sent to places like /proc/net/tcp and are not picked up
// as part of the normal forwarding system. This function detects those ports
// and binds them so that they are picked up.
// The argument is a time, in seconds, to wait between updating.
func ForwardPorts(ctx context.Context, tracker tracker.Tracker, updateInterval time.Duration) error {
var ports []iptables.Entry
// and binds them to k8sServiceListenerAddr so that they are picked up.
func (i *Iptables) ForwardPorts() error {
var ports []limaiptables.Entry

ticker := time.NewTicker(updateInterval)
ticker := time.NewTicker(i.updateInterval)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
case <-i.context.Done():
return nil
case <-ticker.C:
}
// Detect ports for forward
newPorts, err := iptables.GetPorts()
newPorts, err := i.scanner.GetPorts()
if err != nil {
// iptables exiting with an exit status of 4 means there
// is a resource problem. For example, something else is
Expand All @@ -69,7 +91,7 @@ func ForwardPorts(ctx context.Context, tracker tracker.Tracker, updateInterval t
// Remove old forwards
for _, p := range removed {
name := entryToString(p)
if err := tracker.Remove(generateID(name)); err != nil {
if err := i.apiTracker.Remove(utils.GenerateID(name)); err != nil {
log.Warnf("iptables scanner failed to remove portmap for %s: %w", name, err)
continue
}
Expand All @@ -88,18 +110,14 @@ func ForwardPorts(ctx context.Context, tracker tracker.Tracker, updateInterval t
continue
}
portBinding := nat.PortBinding{
// We can set the hostIP to INADDR_ANY the API Tracker will determine
// the admin installation and can adjust this to localhost accordingly
HostIP: "0.0.0.0",
HostIP: i.listenerIP.String(),
HostPort: port,
}
if pb, ok := portMap[portMapKey]; ok {
portMap[portMapKey] = append(pb, portBinding)
} else {
if _, ok := portMap[portMapKey]; !ok {
portMap[portMapKey] = []nat.PortBinding{portBinding}
}
name := entryToString(p)
if err := tracker.Add(generateID(name), portMap); err != nil {
if err := i.apiTracker.Add(utils.GenerateID(name), portMap); err != nil {
log.Errorf("iptables scanner failed to forward portmap for %s: %s", name, err)
continue
}
Expand All @@ -114,8 +132,8 @@ func ForwardPorts(ctx context.Context, tracker tracker.Tracker, updateInterval t
// licensed under the Apache 2.
//
//nolint:nonamedreturns
func comparePorts(oldPorts, newPorts []iptables.Entry) (added, removed []iptables.Entry) {
oldPortMap := make(map[string]iptables.Entry, len(oldPorts))
func comparePorts(oldPorts, newPorts []limaiptables.Entry) (added, removed []limaiptables.Entry) {
oldPortMap := make(map[string]limaiptables.Entry, len(oldPorts))
portExistMap := make(map[string]bool, len(oldPorts))
for _, oldPort := range oldPorts {
key := entryToString(oldPort)
Expand All @@ -139,12 +157,6 @@ func comparePorts(oldPorts, newPorts []iptables.Entry) (added, removed []iptable
return
}

func entryToString(ip iptables.Entry) string {
func entryToString(ip limaiptables.Entry) string {
return net.JoinHostPort(ip.IP.String(), strconv.Itoa(ip.Port))
}

func generateID(entry string) string {
hasher := sha256.New()
hasher.Write([]byte(entry))
return hex.EncodeToString(hasher.Sum(nil))
}
Loading

0 comments on commit f692d77

Please sign in to comment.