Files
coder/cli/vpndaemon_windows_linux_shared.go
Michael Suchacz adc7775405 feat(vpn): add Linux support for vpn-daemon and OS networking stack (#22051)
This change adds Linux support for Desktop VPN by aligning Linux
behavior with the existing Windows daemon implementation and adding a
Linux networking stack implementation.

### What changed
- Consolidated the daemon command implementation into a shared file:
  - `cli/vpndaemon_windows_linux.go` (`//go:build windows || linux`)
- Consolidated daemon tests into a shared file:
- `cli/vpndaemon_windows_linux_test.go` (`//go:build windows || linux`)
- Removed Linux-only duplicate daemon files:
  - `cli/vpndaemon_linux.go`
  - `cli/vpndaemon_linux_test.go`
- Removed unsupported-platform stubs per current supported OS targets:
  - `cli/vpndaemon_other.go`
  - `vpn/tun.go`
- Kept Linux networking stack implementation in:
  - `vpn/tun_linux.go`

### Notes
- Linux now uses the same `rpc-read-handle` / `rpc-write-handle` flags
and env vars as Windows.
- The daemon logs to stderr (via CLI logger sinks), and does not forward
logs over the RPC pipe.
2026-02-12 12:14:56 +01:00

79 lines
2.3 KiB
Go

//go:build windows || linux
package cli
import (
"golang.org/x/xerrors"
"cdr.dev/slog/v3"
"cdr.dev/slog/v3/sloggers/sloghuman"
"github.com/coder/coder/v2/vpn"
"github.com/coder/serpent"
)
func (*RootCmd) vpnDaemonRun() *serpent.Command {
var (
rpcReadHandleInt int64
rpcWriteHandleInt int64
)
cmd := &serpent.Command{
Use: "run",
Short: "Run the VPN daemon on Windows and Linux.",
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
),
Options: serpent.OptionSet{
{
Flag: "rpc-read-handle",
Env: "CODER_VPN_DAEMON_RPC_READ_HANDLE",
Description: "The handle for the pipe to read from the RPC connection.",
Value: serpent.Int64Of(&rpcReadHandleInt),
Required: true,
},
{
Flag: "rpc-write-handle",
Env: "CODER_VPN_DAEMON_RPC_WRITE_HANDLE",
Description: "The handle for the pipe to write to the RPC connection.",
Value: serpent.Int64Of(&rpcWriteHandleInt),
Required: true,
},
},
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
sinks := []slog.Sink{
sloghuman.Sink(inv.Stderr),
}
logger := inv.Logger.AppendSinks(sinks...).Leveled(slog.LevelDebug)
if rpcReadHandleInt < 0 || rpcWriteHandleInt < 0 {
return xerrors.Errorf("rpc-read-handle (%v) and rpc-write-handle (%v) must be positive", rpcReadHandleInt, rpcWriteHandleInt)
}
if rpcReadHandleInt == rpcWriteHandleInt {
return xerrors.Errorf("rpc-read-handle (%v) and rpc-write-handle (%v) must be different", rpcReadHandleInt, rpcWriteHandleInt)
}
// The manager passes the read and write descriptors directly to the
// daemon, so we can open the RPC pipe from the raw values.
logger.Info(ctx, "opening bidirectional RPC pipe", slog.F("rpc_read_handle", rpcReadHandleInt), slog.F("rpc_write_handle", rpcWriteHandleInt))
pipe, err := vpn.NewBidirectionalPipe(uintptr(rpcReadHandleInt), uintptr(rpcWriteHandleInt))
if err != nil {
return xerrors.Errorf("create bidirectional RPC pipe: %w", err)
}
defer pipe.Close()
logger.Info(ctx, "starting VPN tunnel")
tunnel, err := vpn.NewTunnel(ctx, logger, pipe, vpn.NewClient(), vpn.UseOSNetworkingStack())
if err != nil {
return xerrors.Errorf("create new tunnel for client: %w", err)
}
defer tunnel.Close()
<-ctx.Done()
return nil
},
}
return cmd
}