mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
Prevents the VPN startup from hanging for 5 minutes due to a startup backoff if `wintun.dll` cannot be loaded. Because the `wintun` package doesn't expose an easy `Load() error` method for us, the only way for us to force it to load (without unwanted side effects) is through `wintun.Version()` which doesn't return an error message. So, we call that function so the `wintun` package loads the DLL and configures the logging properly, then we try to load the DLL ourselves. `LoadLibraryEx` will not load the library multiple times and returns a reference to the existing library. Closes https://github.com/coder/coder-desktop-windows/issues/24
161 lines
5.3 KiB
Go
161 lines
5.3 KiB
Go
//go:build windows
|
|
|
|
package vpn
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/dblohm7/wingoes/com"
|
|
"github.com/tailscale/wireguard-go/tun"
|
|
"golang.org/x/sys/windows"
|
|
"golang.org/x/sys/windows/svc"
|
|
"golang.org/x/xerrors"
|
|
"golang.zx2c4.com/wintun"
|
|
"tailscale.com/net/dns"
|
|
"tailscale.com/net/netmon"
|
|
"tailscale.com/net/tstun"
|
|
"tailscale.com/types/logger"
|
|
"tailscale.com/util/winutil"
|
|
"tailscale.com/wgengine/router"
|
|
|
|
"cdr.dev/slog"
|
|
"github.com/coder/coder/v2/tailnet"
|
|
"github.com/coder/retry"
|
|
)
|
|
|
|
const (
|
|
tunName = "Coder"
|
|
tunGUID = "{0ed1515d-04a4-4c46-abae-11ad07cf0e6d}"
|
|
|
|
wintunDLL = "wintun.dll"
|
|
)
|
|
|
|
func GetNetworkingStack(t *Tunnel, _ *StartRequest, logger slog.Logger) (NetworkStack, error) {
|
|
// Initialize COM process-wide so Tailscale can make calls to the windows
|
|
// network APIs to read/write adapter state.
|
|
comProcessType := com.ConsoleApp
|
|
isSvc, err := svc.IsWindowsService()
|
|
if err != nil {
|
|
return NetworkStack{}, xerrors.Errorf("svc.IsWindowsService failed: %w", err)
|
|
}
|
|
if isSvc {
|
|
comProcessType = com.Service
|
|
}
|
|
if err := com.StartRuntime(comProcessType); err != nil {
|
|
return NetworkStack{}, xerrors.Errorf("could not initialize COM: com.StartRuntime(%d): %w", comProcessType, err)
|
|
}
|
|
|
|
// Set the name and GUID for the TUN interface.
|
|
tun.WintunTunnelType = tunName
|
|
guid, err := windows.GUIDFromString(tunGUID)
|
|
if err != nil {
|
|
return NetworkStack{}, xerrors.Errorf("could not parse GUID %q: %w", tunGUID, err)
|
|
}
|
|
tun.WintunStaticRequestedGUID = &guid
|
|
|
|
// Ensure wintun.dll is available, and fail early if it's not to avoid
|
|
// hanging for 5 minutes in tstunNewWithWindowsRetries.
|
|
//
|
|
// First, we call wintun.Version() to make the wintun package attempt to
|
|
// load wintun.dll. This allows the wintun package to set the logging
|
|
// callback in the DLL before we load it ourselves.
|
|
_ = wintun.Version()
|
|
|
|
// Then, we try to load wintun.dll ourselves so we get a better error
|
|
// message if there was a problem. This call matches the wintun package, so
|
|
// we're loading it in the same way.
|
|
//
|
|
// Note: this leaks the handle to wintun.dll, but since it's already loaded
|
|
// it wouldn't be freed anyways.
|
|
const (
|
|
LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200
|
|
LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
|
|
)
|
|
_, err = windows.LoadLibraryEx(wintunDLL, 0, LOAD_LIBRARY_SEARCH_APPLICATION_DIR|LOAD_LIBRARY_SEARCH_SYSTEM32)
|
|
if err != nil {
|
|
return NetworkStack{}, xerrors.Errorf("could not load %q, it should be in the same directory as the executable (in Coder Desktop, this should have been installed automatically): %w", wintunDLL, err)
|
|
}
|
|
|
|
tunDev, tunName, err := tstunNewWithWindowsRetries(tailnet.Logger(logger.Named("net.tun.device")), tunName)
|
|
if err != nil {
|
|
return NetworkStack{}, xerrors.Errorf("create tun device: %w", err)
|
|
}
|
|
logger.Info(context.Background(), "tun created", slog.F("name", tunName))
|
|
|
|
wireguardMonitor, err := netmon.New(tailnet.Logger(logger.Named("net.wgmonitor")))
|
|
|
|
coderRouter, err := router.New(tailnet.Logger(logger.Named("net.router")), tunDev, wireguardMonitor)
|
|
if err != nil {
|
|
return NetworkStack{}, xerrors.Errorf("create router: %w", err)
|
|
}
|
|
|
|
dnsConfigurator, err := dns.NewOSConfigurator(tailnet.Logger(logger.Named("net.dns")), tunName)
|
|
if err != nil {
|
|
return NetworkStack{}, xerrors.Errorf("create dns configurator: %w", err)
|
|
}
|
|
|
|
return NetworkStack{
|
|
WireguardMonitor: nil, // default is fine
|
|
TUNDevice: tunDev,
|
|
Router: coderRouter,
|
|
DNSConfigurator: dnsConfigurator,
|
|
}, nil
|
|
}
|
|
|
|
// tstunNewOrRetry is a wrapper around tstun.New that retries on Windows for certain
|
|
// errors.
|
|
//
|
|
// This is taken from Tailscale:
|
|
// https://github.com/tailscale/tailscale/blob/3abfbf50aebbe3ba57dc749165edb56be6715c0a/cmd/tailscaled/tailscaled_windows.go#L107
|
|
func tstunNewWithWindowsRetries(logf logger.Logf, tunName string) (_ tun.Device, devName string, _ error) {
|
|
r := retry.New(250*time.Millisecond, 10*time.Second)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
|
defer cancel()
|
|
for r.Wait(ctx) {
|
|
dev, devName, err := tstun.New(logf, tunName)
|
|
if err == nil {
|
|
return dev, devName, err
|
|
}
|
|
if errors.Is(err, windows.ERROR_DEVICE_NOT_AVAILABLE) || windowsUptime() < 10*time.Minute {
|
|
// Wintun is not installing correctly. Dump the state of NetSetupSvc
|
|
// (which is a user-mode service that must be active for network devices
|
|
// to install) and its dependencies to the log.
|
|
winutil.LogSvcState(logf, "NetSetupSvc")
|
|
}
|
|
}
|
|
|
|
return nil, "", ctx.Err()
|
|
}
|
|
|
|
var (
|
|
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
getTickCount64Proc = kernel32.NewProc("GetTickCount64")
|
|
)
|
|
|
|
func windowsUptime() time.Duration {
|
|
r, _, _ := getTickCount64Proc.Call()
|
|
return time.Duration(int64(r)) * time.Millisecond
|
|
}
|
|
|
|
// TODO(@dean): implement a way to install/uninstall the wintun driver, most
|
|
// likely as a CLI command
|
|
//
|
|
// This is taken from Tailscale:
|
|
// https://github.com/tailscale/tailscale/blob/3abfbf50aebbe3ba57dc749165edb56be6715c0a/cmd/tailscaled/tailscaled_windows.go#L543
|
|
func uninstallWinTun(logf logger.Logf) {
|
|
dll := windows.NewLazyDLL("wintun.dll")
|
|
if err := dll.Load(); err != nil {
|
|
logf("Cannot load wintun.dll for uninstall: %v", err)
|
|
return
|
|
}
|
|
|
|
logf("Removing wintun driver...")
|
|
err := wintun.Uninstall()
|
|
logf("Uninstall: %v", err)
|
|
}
|
|
|
|
// TODO(@dean): remove
|
|
var _ = uninstallWinTun
|