Skip to content

Commit a6608bd

Browse files
authored
Merge pull request #37 from rancher-sandbox/add-rdd-guest
Add rdd-guest socket bridge for Windows Docker socket forwarding
2 parents a17c39c + c57f71b commit a6608bd

6 files changed

Lines changed: 174 additions & 0 deletions

File tree

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ WORKDIR /rd/rd-init
1919
RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \
2020
go build -ldflags '-s -w' -o /go/bin/rd-init .
2121

22+
WORKDIR /rd/rdd-guest
23+
RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod \
24+
CGO_ENABLED=0 go build -ldflags '-s -w' -o /go/bin/rdd-guest .
25+
2226
FROM registry.opensuse.org/opensuse/bci/kiwi:10 AS builder
2327
ARG type=qcow2.xz
2428
ARG NERDCTL_VERSION

config.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ if [[ ${kiwi_profiles:-} =~ wsl ]]; then
121121
systemctl enable network-setup
122122
systemctl enable rancher-desktop-guest-agent.service
123123
systemctl enable wsl-proxy.service
124+
systemctl enable rdd-guest.service
124125
# Do not manage /tmp; that is managed by WSL.
125126
mkdir -p /usr/local/lib/tmpfiles.d
126127
touch /usr/local/lib/tmpfiles.d/fs-tmp.conf
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[Unit]
2+
Description=Rancher Desktop Guest Socket Bridge
3+
Documentation=https://github.com/rancher-sandbox/rancher-desktop-daemon
4+
ConditionVirtualization=wsl
5+
6+
[Service]
7+
ExecStart=/usr/local/bin/rdd-guest
8+
Restart=on-failure
9+
RestartSec=5s
10+
11+
[Install]
12+
WantedBy=rancher-desktop.target

src/rdd-guest/go.mod

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module github.com/rancher-sandbox/rancher-desktop-opensuse/src/rdd-guest
2+
3+
go 1.23.0
4+
5+
require github.com/mdlayher/vsock v1.2.1
6+
7+
require (
8+
github.com/mdlayher/socket v0.5.1 // indirect
9+
golang.org/x/net v0.26.0 // indirect
10+
golang.org/x/sync v0.7.0 // indirect
11+
golang.org/x/sys v0.21.0 // indirect
12+
)

src/rdd-guest/go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
2+
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3+
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
4+
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
5+
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
6+
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
7+
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
8+
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
9+
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
10+
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
11+
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
12+
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

src/rdd-guest/main.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Package main is the rdd-guest agent that runs inside the Lima/WSL2 VM.
2+
// It listens on a vsock port and forwards connections to the Docker socket,
3+
// enabling the Windows host to reach /var/run/docker.sock via Hyper-V vsock.
4+
package main
5+
6+
import (
7+
"context"
8+
"errors"
9+
"io"
10+
"log"
11+
"net"
12+
"os"
13+
"os/signal"
14+
"sync"
15+
"syscall"
16+
"time"
17+
18+
"github.com/mdlayher/vsock"
19+
)
20+
21+
const (
22+
vsockPort = 6660
23+
dockerSockPath = "/var/run/docker.sock"
24+
)
25+
26+
func main() {
27+
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
28+
defer stop()
29+
30+
l, err := vsock.Listen(vsockPort, nil)
31+
if err != nil {
32+
log.Fatalf("vsock listen: %v", err)
33+
}
34+
35+
log.Printf("rdd-guest: listening on vsock port %d", vsockPort)
36+
37+
go func() {
38+
<-ctx.Done()
39+
if err := l.Close(); err != nil {
40+
log.Printf("rdd-guest: close listener: %v", err)
41+
}
42+
}()
43+
44+
for {
45+
conn, err := l.Accept()
46+
if err != nil {
47+
if ctx.Err() != nil {
48+
return
49+
}
50+
log.Printf("rdd-guest: accept: %v", err)
51+
if errors.Is(err, syscall.ECONNABORTED) {
52+
continue
53+
}
54+
select {
55+
case <-time.After(time.Second):
56+
case <-ctx.Done():
57+
return
58+
}
59+
continue
60+
}
61+
go handleConn(ctx, conn)
62+
}
63+
}
64+
65+
// TODO: once rancher-desktop-daemon is public, replace the inlined halfCloser,
66+
// pipe(), and handleConn() here with a direct import of pkg/socketbridge, which
67+
// contains the implementations (HalfCloser interface, Pipe function).
68+
69+
// halfCloser is a net.Conn that can independently close the write side.
70+
type halfCloser interface {
71+
net.Conn
72+
CloseWrite() error
73+
}
74+
75+
// handleConn forwards bytes between the vsock connection and the Docker socket.
76+
// It rejects connections that do not originate from the Windows host (CID 2 /
77+
// vsock.Host): any process in any WSL2 distro shares the same vsock namespace
78+
// and could otherwise gain root Docker API access.
79+
func handleConn(ctx context.Context, vsockConn net.Conn) {
80+
defer func() {
81+
if err := vsockConn.Close(); err != nil {
82+
log.Printf("rdd-guest: close vsock conn: %v", err)
83+
}
84+
}()
85+
86+
addr, ok := vsockConn.RemoteAddr().(*vsock.Addr)
87+
if !ok || addr.ContextID != vsock.Host {
88+
log.Printf("rdd-guest: rejected connection from %v", vsockConn.RemoteAddr())
89+
return
90+
}
91+
92+
dockerConn, err := (&net.Dialer{}).DialContext(ctx, "unix", dockerSockPath)
93+
if err != nil {
94+
log.Printf("rdd-guest: dial docker: %v", err)
95+
return
96+
}
97+
defer func() {
98+
if err := dockerConn.Close(); err != nil {
99+
log.Printf("rdd-guest: close docker conn: %v", err)
100+
}
101+
}()
102+
103+
vsockHC, ok := vsockConn.(halfCloser)
104+
if !ok {
105+
log.Printf("rdd-guest: vsock conn from %v does not support CloseWrite", vsockConn.RemoteAddr())
106+
return
107+
}
108+
dockerHC, ok := dockerConn.(halfCloser)
109+
if !ok {
110+
log.Printf("rdd-guest: docker conn for %v does not support CloseWrite", vsockConn.RemoteAddr())
111+
return
112+
}
113+
pipe(vsockHC, dockerHC)
114+
}
115+
116+
// pipe bidirectionally proxies between a and b until both directions are done.
117+
func pipe(a, b halfCloser) {
118+
var wg sync.WaitGroup
119+
120+
forward := func(dst, src halfCloser) {
121+
defer wg.Done()
122+
_, err := io.Copy(dst, src)
123+
if err != nil && !errors.Is(err, io.EOF) {
124+
log.Printf("rdd-guest: copy: %v", err)
125+
}
126+
_ = dst.CloseWrite()
127+
}
128+
129+
wg.Add(2)
130+
go forward(a, b)
131+
go forward(b, a)
132+
wg.Wait()
133+
}

0 commit comments

Comments
 (0)