Skip to content

Commit

Permalink
Merge pull request #7371 from rancher-sandbox/remove-vtunnel-guest-agent
Browse files Browse the repository at this point in the history
Remove vtunnel guest agent
  • Loading branch information
Nino-K committed Aug 23, 2024
2 parents 4ab3c63 + ece9feb commit 683f5cf
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 810 deletions.
48 changes: 1 addition & 47 deletions src/go/guestagent/README.md
Original file line number Diff line number Diff line change
@@ -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)
108 changes: 25 additions & 83 deletions src/go/guestagent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,39 +52,20 @@ 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
socketInterval = 5 * time.Second
socketRetryTimeout = 2 * time.Minute
dockerSocketFile = "/var/run/docker.sock"
containerdSocketFile = "/run/k3s/containerd/containerd.sock"
vtunnelPeerAddr = "127.0.0.1:3040"
)

func main() {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
18 changes: 4 additions & 14 deletions src/go/guestagent/pkg/containerd/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -62,17 +61,15 @@ 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 {
return nil, err
}

return &EventMonitor{
containerdClient: client,
portTracker: portTracker,
enablePrivilegedService: enablePrivilegedService,
containerdClient: client,
portTracker: portTracker,
}, nil
}

Expand Down Expand Up @@ -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)
Expand Down
36 changes: 3 additions & 33 deletions src/go/guestagent/pkg/forwarder/vtunnel.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}
6 changes: 0 additions & 6 deletions src/go/guestagent/pkg/kube/watcher_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions src/go/guestagent/pkg/tracker/apitracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 683f5cf

Please sign in to comment.