From 8e05e03b438e5936343ca1bf5253ed92fdcd1e63 Mon Sep 17 00:00:00 2001 From: Nino Kodabande Date: Wed, 21 Aug 2024 12:21:11 -0700 Subject: [PATCH 1/2] Remove all vtunnel refrences All vtunnel refrences is removed out of the code, the guest agent now only supports the namespaced network. Signed-off-by: Nino Kodabande --- src/go/guestagent/main.go | 108 +--- src/go/guestagent/pkg/containerd/events.go | 18 +- src/go/guestagent/pkg/forwarder/vtunnel.go | 36 +- src/go/guestagent/pkg/kube/watcher_linux.go | 6 - .../guestagent/pkg/tracker/apitracker_test.go | 19 + .../guestagent/pkg/tracker/vtunneltracker.go | 119 ----- .../pkg/tracker/vtunneltracker_test.go | 499 ------------------ src/go/guestagent/pkg/types/portmapping.go | 19 +- 8 files changed, 61 insertions(+), 763 deletions(-) delete mode 100644 src/go/guestagent/pkg/tracker/vtunneltracker.go delete mode 100644 src/go/guestagent/pkg/tracker/vtunneltracker_test.go diff --git a/src/go/guestagent/main.go b/src/go/guestagent/main.go index ead21e9c729..bcf66d95b2d 100644 --- a/src/go/guestagent/main.go +++ b/src/go/guestagent/main.go @@ -52,31 +52,13 @@ var ( containerdSock = flag.String("containerdSock", containerdSocketFile, "file path for Containerd socket address") - vtunnelAddr = flag.String("vtunnelAddr", vtunnelPeerAddr, "peer address for Vtunnel in IP:PORT format") - enablePrivilegedService = flag.Bool("privilegedService", false, "enable Privileged Service mode") - k8sServiceListenerAddr = flag.String("k8sServiceListenerAddr", net.IPv4zero.String(), + k8sServiceListenerAddr = flag.String("k8sServiceListenerAddr", net.IPv4zero.String(), "address to bind Kubernetes services to on the host, valid options are 0.0.0.0 or 127.0.0.1") adminInstall = flag.Bool("adminInstall", false, "indicates if Rancher Desktop is installed as admin or not") k8sAPIPort = flag.String("k8sAPIPort", "6443", "K8sAPI port number to forward to rancher-desktop wsl-proxy as a static portMapping event") ) -// Flags can only be enabled in the following combination: -// +======================+==============================================+ -// | | Default Network | Namespaced Network | -// +----------------------+------------------------+---------------------+ -// | | Admin | Non-Admin | Admin | Non-Admin | -// +======================+============+===========+=========+===========+ -// | privilegedService | enable | disable | disable | disable | -// +----------------------+------------+-----------+---------+-----------+ -// | docker Or containerd | enable | disable | enable | enable | -// +----------------------+------------+-----------+---------+-----------+ -// | iptables | disable or | enable | disable | disable | -// | | **enable | | | | -// +----------------------+------------+-----------+---------+-----------+ -// ** iptables can be enable for the default network with admin when older -// versions of k8s are used that do not support the service watcher API. - const ( wslInfName = "eth0" iptablesUpdateInterval = 3 * time.Second @@ -84,7 +66,6 @@ const ( socketRetryTimeout = 2 * time.Minute dockerSocketFile = "/var/run/docker.sock" containerdSocketFile = "/run/k3s/containerd/containerd.sock" - vtunnelPeerAddr = "127.0.0.1:3040" ) func main() { @@ -131,52 +112,38 @@ func main() { var portTracker tracker.Tracker - if *enablePrivilegedService { - if *vtunnelAddr == "" { - log.Fatal("-vtunnelAddr must be provided when -privilegedService is enabled.") - } - - wslAddr, err := getWSLAddr(wslInfName) + forwarder := forwarder.NewWSLProxyForwarder("/run/wsl-proxy.sock") + portTracker = tracker.NewAPITracker(forwarder, tracker.GatewayBaseURL, *adminInstall) + // Manually register the port for K8s API, we would + // only want to send this manual port mapping if both + // of the following conditions are met: + // 1) if kubernetes is enabled + // 2) when wsl-proxy for wsl-integration is enabled + if *enableKubernetes { + port, err := nat.NewPort("tcp", *k8sAPIPort) if err != nil { - log.Fatalf("failure getting WSL IP addresses: %v", err) + log.Fatalf("failed to parse port for k8s API: %v", err) } - - forwarder := forwarder.NewVTunnelForwarder(*vtunnelAddr) - portTracker = tracker.NewVTunnelTracker(forwarder, wslAddr) - } else { - forwarder := forwarder.NewWSLProxyForwarder("/run/wsl-proxy.sock") - portTracker = tracker.NewAPITracker(forwarder, tracker.GatewayBaseURL, *adminInstall) - // Manually register the port for K8s API, we would - // only want to send this manual port mapping if both - // of the following conditions are met: - // 1) if kubernetes is enabled - // 2) when wsl-proxy for wsl-integration is enabled - if *enableKubernetes { - port, err := nat.NewPort("tcp", *k8sAPIPort) - if err != nil { - log.Fatalf("failed to parse port for k8s API: %v", err) - } - k8sAPIPortMapping := types.PortMapping{ - Remove: false, - Ports: nat.PortMap{ - port: []nat.PortBinding{ - { - HostIP: "127.0.0.1", - HostPort: *k8sAPIPort, - }, + k8sAPIPortMapping := types.PortMapping{ + Remove: false, + Ports: nat.PortMap{ + port: []nat.PortBinding{ + { + HostIP: "127.0.0.1", + HostPort: *k8sAPIPort, }, }, - } - if err := forwarder.Send(k8sAPIPortMapping); err != nil { - log.Fatalf("failed to send a static portMapping event to wsl-proxy: %v", err) - } - log.Debugf("successfully forwarded k8s API port [%s] to wsl-proxy", *k8sAPIPort) + }, + } + if err := forwarder.Send(k8sAPIPortMapping); err != nil { + log.Fatalf("failed to send a static portMapping event to wsl-proxy: %v", err) } + log.Debugf("successfully forwarded k8s API port [%s] to wsl-proxy", *k8sAPIPort) } if *enableContainerd { group.Go(func() error { - eventMonitor, err := containerd.NewEventMonitor(*containerdSock, portTracker, *enablePrivilegedService) + eventMonitor, err := containerd.NewEventMonitor(*containerdSock, portTracker) if err != nil { return fmt.Errorf("error initializing containerd event monitor: %w", err) } @@ -220,7 +187,7 @@ func main() { // of the legacy network, requiring listeners only. In listenerOnlyMode, we create // TCP listeners on 127.0.0.1 to enable automatic port forwarding mechanisms, // particularly in WSLv2 environments. - listenerOnlyMode := *enableIptables && !*enablePrivilegedService && !*adminInstall + listenerOnlyMode := *enableIptables && !*adminInstall // Watch for kube err := kube.WatchForServices(ctx, *configPath, @@ -281,28 +248,3 @@ func tryConnectAPI(ctx context.Context, socketFile string, verify func(context.C } } } - -// Gets the wsl interface address by doing a lookup by name -// for wsl we do a lookup for 'eth0'. -func getWSLAddr(infName string) ([]types.ConnectAddrs, error) { - inf, err := net.InterfaceByName(infName) - if err != nil { - return nil, err - } - - addrs, err := inf.Addrs() - if err != nil { - return nil, err - } - - connectAddrs := make([]types.ConnectAddrs, 0) - - for _, addr := range addrs { - connectAddrs = append(connectAddrs, types.ConnectAddrs{ - Network: addr.Network(), - Addr: addr.String(), - }) - } - - return connectAddrs, nil -} diff --git a/src/go/guestagent/pkg/containerd/events.go b/src/go/guestagent/pkg/containerd/events.go index fce52742455..2c428784a29 100644 --- a/src/go/guestagent/pkg/containerd/events.go +++ b/src/go/guestagent/pkg/containerd/events.go @@ -51,9 +51,8 @@ var ( // EventMonitor monitors the Containerd API // for container events. type EventMonitor struct { - containerdClient *containerd.Client - portTracker tracker.Tracker - enablePrivilegedService bool + containerdClient *containerd.Client + portTracker tracker.Tracker } // NewEventMonitor creates and returns a new Event Monitor for @@ -62,7 +61,6 @@ type EventMonitor struct { func NewEventMonitor( containerdSock string, portTracker tracker.Tracker, - enablePrivilegedService bool, ) (*EventMonitor, error) { client, err := containerd.New(containerdSock, containerd.WithDefaultNamespace(containerdNamespace.Default)) if err != nil { @@ -70,9 +68,8 @@ func NewEventMonitor( } return &EventMonitor{ - containerdClient: client, - portTracker: portTracker, - enablePrivilegedService: enablePrivilegedService, + containerdClient: client, + portTracker: portTracker, }, nil } @@ -294,13 +291,6 @@ func (e *EventMonitor) updateListener( portMappings nat.PortMap, action func(context.Context, net.IP, int) error, ) { - // Only create listeners for the default network when the PrivilegedService is enabled. - // Otherwise, creating listeners can conflict with the proxy listeners that are created - // by the namespaced network’s port exposing API. - if !e.enablePrivilegedService { - return - } - for _, portBindings := range portMappings { for _, portBinding := range portBindings { port, err := strconv.Atoi(portBinding.HostPort) diff --git a/src/go/guestagent/pkg/forwarder/vtunnel.go b/src/go/guestagent/pkg/forwarder/vtunnel.go index 32421567a8b..b46f1aa118d 100644 --- a/src/go/guestagent/pkg/forwarder/vtunnel.go +++ b/src/go/guestagent/pkg/forwarder/vtunnel.go @@ -1,5 +1,5 @@ /* -Copyright © 2022 SUSE LLC +Copyright © 2024 SUSE LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -12,47 +12,17 @@ limitations under the License. */ // Package forwarder implements a forwarding mechanism to forward -// port mappings over Vtunnel. +// port mappings over the network. package forwarder import ( - "encoding/json" - "net" - "github.com/rancher-sandbox/rancher-desktop/src/go/guestagent/pkg/types" ) // Forwarder is the interface that wraps the Send method which // to forward the port mappings. type Forwarder interface { - // Send sends the give port mappings to the VTunnel Peer via + // Send sends the give port mappings to the Peer via // a tcp connection. Send(portMapping types.PortMapping) error } - -// VTunnelForwarder forwards the PortMappings to VTunnel Peer process. -type VTunnelForwarder struct { - peerAddr string -} - -func NewVTunnelForwarder(peerAddr string) *VTunnelForwarder { - return &VTunnelForwarder{ - peerAddr: peerAddr, - } -} - -// Send forwards the port mappings to Vtunnel Peer. -func (v *VTunnelForwarder) Send(portMapping types.PortMapping) error { - conn, err := net.Dial("tcp", v.peerAddr) - if err != nil { - return err - } - defer conn.Close() - - err = json.NewEncoder(conn).Encode(portMapping) - if err != nil { - return err - } - - return nil -} diff --git a/src/go/guestagent/pkg/kube/watcher_linux.go b/src/go/guestagent/pkg/kube/watcher_linux.go index 264ac0afee1..0e86e9912f5 100644 --- a/src/go/guestagent/pkg/kube/watcher_linux.go +++ b/src/go/guestagent/pkg/kube/watcher_linux.go @@ -13,12 +13,6 @@ limitations under the License. // Package kube watches Kubernetes for NodePort and LoadBalancer service types. // It exposes the services as follows: -// - [default network - admin install]: It uses vtunnel tracker to forward the -// port mappings to the host in conjunction with the automatic port forwarding -// mechanism that is found in WSLv2. -// - [default network - non-admin install]: It creates TCP listeners on 127.0.0.1, -// so that it can be picked up by the automatic port forwarding mechanisms found -// in WSLv2 on the default network with the non-admin install. // - [namespaced network - admin install]: It uses API tracker to expose the ports // on the host through host-switch.exe // - [namespaced network - non-admin install]: It uses API tracker to expose the ports diff --git a/src/go/guestagent/pkg/tracker/apitracker_test.go b/src/go/guestagent/pkg/tracker/apitracker_test.go index c649331086d..f1599f37718 100644 --- a/src/go/guestagent/pkg/tracker/apitracker_test.go +++ b/src/go/guestagent/pkg/tracker/apitracker_test.go @@ -23,6 +23,7 @@ import ( "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/docker/go-connections/nat" "github.com/rancher-sandbox/rancher-desktop/src/go/guestagent/pkg/tracker" + guestagentType "github.com/rancher-sandbox/rancher-desktop/src/go/guestagent/pkg/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -620,3 +621,21 @@ func TestNonAdminInstall(t *testing.T) { func ipPortBuilder(ip, port string) string { return ip + ":" + port } + +type testForwarder struct { + receivedPortMappings []guestagentType.PortMapping + sendErr error + failCondition func(guestagentType.PortMapping) error +} + +func (v *testForwarder) Send(portMapping guestagentType.PortMapping) error { + if v.failCondition != nil { + if err := v.failCondition(portMapping); err != nil { + return err + } + } + + v.receivedPortMappings = append(v.receivedPortMappings, portMapping) + + return v.sendErr +} diff --git a/src/go/guestagent/pkg/tracker/vtunneltracker.go b/src/go/guestagent/pkg/tracker/vtunneltracker.go deleted file mode 100644 index bc79287d0dc..00000000000 --- a/src/go/guestagent/pkg/tracker/vtunneltracker.go +++ /dev/null @@ -1,119 +0,0 @@ -/* -Copyright © 2023 SUSE LLC -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package tracker implements a tracking mechanism to keep track -// of the ports during various container event types e.g start, stop -package tracker - -import ( - "errors" - "fmt" - - "github.com/docker/go-connections/nat" - "github.com/rancher-sandbox/rancher-desktop/src/go/guestagent/pkg/forwarder" - "github.com/rancher-sandbox/rancher-desktop/src/go/guestagent/pkg/types" -) - -var ErrRemoveAll = errors.New("failed to remove all portMappings") - -// VTunnelTracker keeps track of port mappings and forwards -// them to the privileged service on the host over AF_VSOCK -// tunnel (vtunnel). -type VTunnelTracker struct { - portStorage *portStorage - vtunnelForwarder forwarder.Forwarder - wslAddrs []types.ConnectAddrs - *ListenerTracker -} - -// NewVTunnelTracker creates a new Port Tracker. -func NewVTunnelTracker(vtunnelForwarder forwarder.Forwarder, wslAddrs []types.ConnectAddrs) *VTunnelTracker { - return &VTunnelTracker{ - portStorage: newPortStorage(), - vtunnelForwarder: vtunnelForwarder, - wslAddrs: wslAddrs, - ListenerTracker: NewListenerTracker(), - } -} - -// Add a container ID and port mapping to the tracker and calls the -// vtunnel forwarder to send the port mappings to privileged service. -func (p *VTunnelTracker) Add(containerID string, portMap nat.PortMap) error { - if len(portMap) == 0 { - return nil - } - - err := p.vtunnelForwarder.Send(types.PortMapping{ - Remove: false, - Ports: portMap, - ConnectAddrs: p.wslAddrs, - }) - if err != nil { - return err - } - - p.portStorage.add(containerID, portMap) - - return nil -} - -// Get gets a port mapping by container ID from the tracker. -func (p *VTunnelTracker) Get(containerID string) nat.PortMap { - return p.portStorage.get(containerID) -} - -// Remove deletes a container ID and port mapping from the tracker and calls the -// vtunnel forwarder to send the port mappings to privileged service. -func (p *VTunnelTracker) Remove(containerID string) error { - portMap := p.portStorage.get(containerID) - if len(portMap) != 0 { - err := p.vtunnelForwarder.Send(types.PortMapping{ - Remove: true, - Ports: portMap, - ConnectAddrs: p.wslAddrs, - }) - if err != nil { - return err - } - - p.portStorage.remove(containerID) - } - - return nil -} - -// RemoveAll removes all the port bindings from the tracker. -func (p *VTunnelTracker) RemoveAll() error { - defer p.portStorage.removeAll() - - allPortMappings := p.portStorage.getAll() - - var errs []error - - for _, portMap := range allPortMappings { - err := p.vtunnelForwarder.Send(types.PortMapping{ - Remove: true, - Ports: portMap, - ConnectAddrs: p.wslAddrs, - }) - if err != nil { - errs = append(errs, err) - } - } - - if len(errs) != 0 { - return fmt.Errorf("%w: %+v", ErrRemoveAll, errs) - } - - return nil -} diff --git a/src/go/guestagent/pkg/tracker/vtunneltracker_test.go b/src/go/guestagent/pkg/tracker/vtunneltracker_test.go deleted file mode 100644 index b1d5a126271..00000000000 --- a/src/go/guestagent/pkg/tracker/vtunneltracker_test.go +++ /dev/null @@ -1,499 +0,0 @@ -/* -Copyright © 2023 SUSE LLC -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package tracker_test - -import ( - "encoding/json" - "errors" - "reflect" - "testing" - - "github.com/docker/go-connections/nat" - "github.com/rancher-sandbox/rancher-desktop/src/go/guestagent/pkg/tracker" - "github.com/rancher-sandbox/rancher-desktop/src/go/guestagent/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestVTunnelTrackerAdd(t *testing.T) { - t.Parallel() - - wslConnectAddr := []types.ConnectAddrs{{Network: "tcp", Addr: "192.168.0.1"}} - forwarder := testForwarder{} - vtunnelTracker := tracker.NewVTunnelTracker(&forwarder, wslConnectAddr) - - portMapping := nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: hostPort, - }, - }, - } - err := vtunnelTracker.Add(containerID, portMapping) - require.NoError(t, err) - - portMapping2 := nat.PortMap{ - "443/tcp": []nat.PortBinding{ - { - HostIP: hostIP2, - HostPort: hostPort2, - }, - }, - } - err = vtunnelTracker.Add(containerID2, portMapping2) - require.NoError(t, err) - - assert.ElementsMatch(t, forwarder.receivedPortMappings, - []types.PortMapping{ - { - Remove: false, - Ports: portMapping, - ConnectAddrs: wslConnectAddr, - }, { - Remove: false, - Ports: portMapping2, - ConnectAddrs: wslConnectAddr, - }, - }) - - actualPortMapping := vtunnelTracker.Get(containerID) - assert.Equal(t, actualPortMapping, portMapping) - - actualPortMapping = vtunnelTracker.Get(containerID2) - assert.Equal(t, actualPortMapping, portMapping2) -} - -func TestVTunnelTrackerAddOverride(t *testing.T) { - t.Parallel() - - wslConnectAddr := []types.ConnectAddrs{{Network: "tcp", Addr: "192.168.0.1"}} - forwarder := testForwarder{} - vtunnelTracker := tracker.NewVTunnelTracker(&forwarder, wslConnectAddr) - - portMapping := nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: hostPort, - }, - }, - "443/tcp": []nat.PortBinding{ - { - HostIP: hostIP2, - HostPort: hostPort2, - }, - }, - } - err := vtunnelTracker.Add(containerID, portMapping) - require.NoError(t, err) - - assert.ElementsMatch(t, forwarder.receivedPortMappings, - []types.PortMapping{ - { - Remove: false, - Ports: portMapping, - ConnectAddrs: wslConnectAddr, - }, - }) - - actualPortMapping := vtunnelTracker.Get(containerID) - assert.Equal(t, actualPortMapping, portMapping) - - portMapping2 := nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: hostPort, - }, - }, - "8080/tcp": []nat.PortBinding{ - { - HostIP: hostIP2, - HostPort: "8080", - }, - }, - } - - err = vtunnelTracker.Add(containerID, portMapping2) - require.NoError(t, err) - - secondCallIndex := 1 - assert.Equal(t, - types.PortMapping{ - Remove: false, - Ports: portMapping2, - ConnectAddrs: wslConnectAddr, - }, - forwarder.receivedPortMappings[secondCallIndex]) - - actualPortMapping = vtunnelTracker.Get(containerID) - assert.Equal(t, actualPortMapping, portMapping2) -} - -func TestVTunnelTrackerAddEmptyPortMap(t *testing.T) { - t.Parallel() - - wslConnectAddr := []types.ConnectAddrs{{Network: "tcp", Addr: "192.168.0.1"}} - forwarder := testForwarder{} - forwarder.sendErr = errSend - vtunnelTracker := tracker.NewVTunnelTracker(&forwarder, wslConnectAddr) - - portMapping := nat.PortMap{} - err := vtunnelTracker.Add(containerID, portMapping) - require.NoError(t, err) - - assert.Empty(t, forwarder.receivedPortMappings, 0) - - actualPortMapping := vtunnelTracker.Get(containerID) - assert.Empty(t, actualPortMapping, 0) -} - -func TestVTunnelTrackerAddWithError(t *testing.T) { - t.Parallel() - - wslConnectAddr := []types.ConnectAddrs{{Network: "tcp", Addr: "192.168.0.1"}} - forwarder := testForwarder{} - forwarder.sendErr = errSend - vtunnelTracker := tracker.NewVTunnelTracker(&forwarder, wslConnectAddr) - - portMapping := nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: hostPort, - }, - }, - } - err := vtunnelTracker.Add(containerID, portMapping) - require.ErrorIs(t, err, errSend) - - assert.ElementsMatch(t, forwarder.receivedPortMappings, - []types.PortMapping{ - { - Remove: false, - Ports: portMapping, - ConnectAddrs: wslConnectAddr, - }, - }) - - actualPortMapping := vtunnelTracker.Get(containerID) - assert.Empty(t, actualPortMapping, 0) -} - -func TestVTunnelTrackerRemove(t *testing.T) { - t.Parallel() - - wslConnectAddr := []types.ConnectAddrs{{Network: "tcp", Addr: "192.168.0.1"}} - forwarder := testForwarder{} - vtunnelTracker := tracker.NewVTunnelTracker(&forwarder, wslConnectAddr) - - portMapping := nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: hostPort, - }, - }, - } - err := vtunnelTracker.Add(containerID, portMapping) - require.NoError(t, err) - - portMapping2 := nat.PortMap{ - "443/tcp": []nat.PortBinding{ - { - HostIP: hostIP2, - HostPort: hostPort2, - }, - }, - } - err = vtunnelTracker.Add(containerID2, portMapping2) - require.NoError(t, err) - - assert.Len(t, forwarder.receivedPortMappings, 2) - - err = vtunnelTracker.Remove(containerID) - require.NoError(t, err) - - removeRequestIndex := 2 - assert.Equal(t, - types.PortMapping{ - Remove: true, - Ports: portMapping, - ConnectAddrs: wslConnectAddr, - }, forwarder.receivedPortMappings[removeRequestIndex]) - - actualPortMapping := vtunnelTracker.Get(containerID) - assert.Nil(t, actualPortMapping) - - actualPortMapping = vtunnelTracker.Get(containerID2) - assert.Equal(t, nat.PortMap{ - "443/tcp": []nat.PortBinding{ - { - HostIP: hostIP2, - HostPort: hostPort2, - }, - }, - }, actualPortMapping) -} - -func TestVTunnelTrackerRemoveZeroLengthPortMap(t *testing.T) { - t.Parallel() - - wslConnectAddr := []types.ConnectAddrs{{Network: "tcp", Addr: "192.168.0.1"}} - forwarder := testForwarder{} - vtunnelTracker := tracker.NewVTunnelTracker(&forwarder, wslConnectAddr) - - portMapping := nat.PortMap{} - err := vtunnelTracker.Add(containerID, portMapping) - require.NoError(t, err) - - err = vtunnelTracker.Remove(containerID) - require.NoError(t, err) -} - -func TestVTunnelTrackerRemoveError(t *testing.T) { - t.Parallel() - - wslConnectAddr := []types.ConnectAddrs{{Network: "tcp", Addr: "192.168.0.1"}} - forwarder := testForwarder{} - vtunnelTracker := tracker.NewVTunnelTracker(&forwarder, wslConnectAddr) - - portMapping := nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: hostPort, - }, - }, - } - err := vtunnelTracker.Add(containerID, portMapping) - require.NoError(t, err) - - portMapping2 := nat.PortMap{ - "443/tcp": []nat.PortBinding{ - { - HostIP: hostIP2, - HostPort: hostPort2, - }, - }, - } - err = vtunnelTracker.Add(containerID2, portMapping2) - require.NoError(t, err) - - assert.Len(t, forwarder.receivedPortMappings, 2) - - forwarder.sendErr = errSend - err = vtunnelTracker.Remove(containerID) - require.Error(t, err) - - removeRequestIndex := 2 - assert.Equal(t, - types.PortMapping{ - Remove: true, - Ports: portMapping, - ConnectAddrs: wslConnectAddr, - }, forwarder.receivedPortMappings[removeRequestIndex]) - - actualPortMapping := vtunnelTracker.Get(containerID) - assert.Equal(t, nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: hostPort, - }, - }, - }, actualPortMapping) - - actualPortMapping = vtunnelTracker.Get(containerID2) - assert.Equal(t, nat.PortMap{ - "443/tcp": []nat.PortBinding{ - { - HostIP: hostIP2, - HostPort: hostPort2, - }, - }, - }, actualPortMapping) -} - -func TestVTunnelTrackerRemoveAll(t *testing.T) { - t.Parallel() - - wslConnectAddr := []types.ConnectAddrs{{Network: "tcp", Addr: "192.168.0.1"}} - forwarder := testForwarder{} - vtunnelTracker := tracker.NewVTunnelTracker(&forwarder, wslConnectAddr) - - portMapping := nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: hostPort, - }, - }, - } - err := vtunnelTracker.Add(containerID, portMapping) - require.NoError(t, err) - - portMapping2 := nat.PortMap{ - "443/tcp": []nat.PortBinding{ - { - HostIP: hostIP2, - HostPort: hostPort2, - }, - }, - } - err = vtunnelTracker.Add(containerID2, portMapping2) - require.NoError(t, err) - - err = vtunnelTracker.RemoveAll() - require.NoError(t, err) - - actualPortMapping := vtunnelTracker.Get(containerID) - assert.Nil(t, actualPortMapping) - - actualPortMapping = vtunnelTracker.Get(containerID2) - assert.Nil(t, actualPortMapping) - - assert.ElementsMatch(t, forwarder.receivedPortMappings, []types.PortMapping{ - { - Remove: false, - Ports: portMapping, - ConnectAddrs: wslConnectAddr, - }, - { - Remove: false, - Ports: portMapping2, - ConnectAddrs: wslConnectAddr, - }, - { - Remove: true, - Ports: portMapping, - ConnectAddrs: wslConnectAddr, - }, - { - Remove: true, - Ports: portMapping2, - ConnectAddrs: wslConnectAddr, - }, - }) -} - -func TestVTunnelTrackerRemoveAllError(t *testing.T) { - t.Parallel() - - wslConnectAddr := []types.ConnectAddrs{{Network: "tcp", Addr: "192.168.0.1"}} - forwarder := testForwarder{} - vtunnelTracker := tracker.NewVTunnelTracker(&forwarder, wslConnectAddr) - - portMapping := nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: hostPort, - }, - }, - } - err := vtunnelTracker.Add(containerID, portMapping) - require.NoError(t, err) - - portMapping2 := nat.PortMap{ - "443/tcp": []nat.PortBinding{ - { - HostIP: hostIP2, - HostPort: hostPort2, - }, - }, - } - err = vtunnelTracker.Add(containerID2, portMapping2) - require.NoError(t, err) - - forwarder.failCondition = func(pm types.PortMapping) error { - if _, ok := pm.Ports["443/tcp"]; ok { - return &json.UnsupportedValueError{ - Value: reflect.Value{}, - Str: "Not Supported!", - } - } - - return nil - } - err = vtunnelTracker.RemoveAll() - require.ErrorIs(t, err, tracker.ErrRemoveAll) - - actualPortMapping := vtunnelTracker.Get(containerID) - assert.Nil(t, actualPortMapping) - - actualPortMapping = vtunnelTracker.Get(containerID2) - assert.Nil(t, actualPortMapping) - - assert.ElementsMatch(t, forwarder.receivedPortMappings, []types.PortMapping{ - { - Remove: false, - Ports: portMapping, - ConnectAddrs: wslConnectAddr, - }, - { - Remove: false, - Ports: portMapping2, - ConnectAddrs: wslConnectAddr, - }, - { - Remove: true, - Ports: portMapping, - ConnectAddrs: wslConnectAddr, - }, - }) -} - -func TestVTunnelTrackerGet(t *testing.T) { - t.Parallel() - - wslConnectAddr := []types.ConnectAddrs{{Network: "tcp", Addr: "192.168.0.1"}} - forwarder := testForwarder{} - vtunnelTracker := tracker.NewVTunnelTracker(&forwarder, wslConnectAddr) - - portMapping := nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: hostIP, - HostPort: hostPort, - }, - }, - } - err := vtunnelTracker.Add(containerID, portMapping) - require.NoError(t, err) - - actualPortMap := vtunnelTracker.Get(containerID) - assert.Equal(t, portMapping, actualPortMap) -} - -var errSend = errors.New("error from Send") - -type testForwarder struct { - receivedPortMappings []types.PortMapping - sendErr error - failCondition func(types.PortMapping) error -} - -func (v *testForwarder) Send(portMapping types.PortMapping) error { - if v.failCondition != nil { - if err := v.failCondition(portMapping); err != nil { - return err - } - } - - v.receivedPortMappings = append(v.receivedPortMappings, portMapping) - - return v.sendErr -} diff --git a/src/go/guestagent/pkg/types/portmapping.go b/src/go/guestagent/pkg/types/portmapping.go index 894beea3ccc..41c155db87f 100644 --- a/src/go/guestagent/pkg/types/portmapping.go +++ b/src/go/guestagent/pkg/types/portmapping.go @@ -17,22 +17,23 @@ package types import "github.com/docker/go-connections/nat" -// PortMapping is used to send Port/IP list over -// the Vtunnel to the RD Privileged Service. +// PortMapping represents the mapping of ports and addresses to be communicated +// over the network. It includes a flag (remove) on whether to add or remove port mappings +// and specifies the backend addresses to connect to. type PortMapping struct { - // Remove indicates whether to remove or add the entry + // Remove indicates whether the port mappings should be removed (true) or added (false) Remove bool `json:"remove"` - // Ports are the port mappings for both IPV4 and IPV6 + // Ports contains the port mappings for both IPv4 and IPv6 addresses Ports nat.PortMap `json:"ports"` - // ConnectAddrs are the backend addresses to connect to + // ConnectAddrs lists the backend addresses for connections ConnectAddrs []ConnectAddrs `json:"connectAddrs"` } -// ConnectAddrs represent the address for WSL interface -// inside the VM, this address is usually available on eth0. +// ConnectAddrs defines a network address used for the WSL interface inside +// the VM. Typically, this address is found on the eth0 interface. type ConnectAddrs struct { - // Network indicates the available networks for the address (for example, "tcp", "udp") + // Network specifies the protocol or network type for the address (e.g., "tcp", "udp") Network string `json:"network"` - // Address is either IPV4 or IPV6 (for example, "192.0.2.1:25", "[2001:db8::1]:80") + // Addr is the network address, which can be either IPv4 or IPv6 (e.g., "192.0.2.1:25", "[2001:db8::1]:80") Addr string `json:"addr"` } From ece9febe1dde42c6092caeedb9117be0d7933d6f Mon Sep 17 00:00:00 2001 From: Nino Kodabande Date: Wed, 21 Aug 2024 12:32:32 -0700 Subject: [PATCH 2/2] Add a link to the main document in README Signed-off-by: Nino Kodabande --- src/go/guestagent/README.md | 48 +------------------------------------ 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/src/go/guestagent/README.md b/src/go/guestagent/README.md index b584deab6f8..a6af71ffb54 100644 --- a/src/go/guestagent/README.md +++ b/src/go/guestagent/README.md @@ -1,47 +1 @@ -# Rancher Desktop Agent - -The Rancher Desktop guest agent runs in Rancher Desktop VMs providing helper -services. It currently has the following functionality: - -## Port forwarding with privileged helper: - -When the Rancher Desktop Privileged Service is enabled on the host Windows machine via an admin installation of Rancher Desktop, the guest agent watches for port binding events from corresponding backend's API and emits them to the host via a virtual tunnel. - -```mermaid -flowchart LR; - subgraph Host["HOST"] - rd{"Rancher Desktop"} - ps(("Privileged Service")) - rd <-.-> ps - end - subgraph VM["WSL VM"] - rdagent(("Guest Agent")) - api(("moby/containerd \n events API")) - rdagent <-.-> api - end - ps <---> |Vtunnel| rdagent -``` - -### moby port forwarding (WSL) - -Rancher Desktop Guest Agent subscribes to [docker event API](https://docs.docker.com/engine/api/v1.41/#tag/System/operation/SystemEvents) to monitor the newly created published ports. It will then forwards the newly published ports over a `AF_VSOCK` tunnel (Rancher Desktop's `vtunnel`) to [Rancher Desktop Privileged Service](https://github.com/rancher-sandbox/rancher-desktop/tree/main/src/go/privileged-service) that runs on the host machine. - -### containerd port forwarding (WSL) - -When using the containerd backend, the behaviour of Rancher Desktop Guest Agent is very similar to when the moby backend is enabled. It monitors containerd's event API for the newly created published ports. It will then forwards the newly published ports over a `AF_VSOCK` tunnel (Rancher Desktop's `vtunnel`) to Rancher Desktop Privileged Service that runs on the host machine. - -## When Privileged Service is disabled: - -When the Rancher Desktop Privileged Service is not enabled on the host Windows machine via a non admin installation of Rancher Desktop, the guest agent watches the iptables for newly added rules. - -### containerd port forwarding (WSL) when no privileged service is enabled - -When Rancher Desktop Privileged Service is not enabled (non-admin installation), Rancher Desktop Agent falls back to the following behaviour. -In Windows Subsystem for Linux, WSL automatically forwards ports opened on `127.0.0.1` or `0.0.0.0` by opening the corresponding port on `127.0.0.1` on the host (running Windows). However, `containerd` (as configured by `nerdctl`) just sets up `iptables` rules rather than actually listening, meaning this isn't caught by the normal mechanisms. Rancher Desktop Agent therefore creates the listeners so that they get picked up and forwarded automatically. Note that the listeners will never receive any traffic, as the `iptables` rules are in place to forward the traffic before it reaches the application. This is not necessary -for Lima, as that already does the `iptables` scanning (the core of the code has been lifted from Lima). - -## Kubernetes NodePort forwarding - -In newer versions of Kubernetes†, `kubelet` no longer creates a listener for NodePort services. We therefore need to create those listeners manually, so that port forward works correctly as in the container port forwarding above. - -† 1.21.12+, 1.22.10+, 1.23.7+, 1.24+ +[Rancher Desktop Guest Agent](/docs/networking/windows/rancher-desktop-guest-agent.md) \ No newline at end of file