From b23e05b1fe746ae2e65967651bb6a1631504847b Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Fri, 28 Feb 2025 15:20:00 +1100 Subject: [PATCH] fix(vpn): fail early if wintun.dll is not present (#16707) 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 --- vpn/tun_windows.go | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/vpn/tun_windows.go b/vpn/tun_windows.go index a70cb8f28d..52778a8a9d 100644 --- a/vpn/tun_windows.go +++ b/vpn/tun_windows.go @@ -25,7 +25,12 @@ import ( "github.com/coder/retry" ) -const tunName = "Coder" +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 @@ -44,12 +49,35 @@ func GetNetworkingStack(t *Tunnel, _ *StartRequest, logger slog.Logger) (Network // Set the name and GUID for the TUN interface. tun.WintunTunnelType = tunName - guid, err := windows.GUIDFromString("{0ed1515d-04a4-4c46-abae-11ad07cf0e6d}") + guid, err := windows.GUIDFromString(tunGUID) if err != nil { - panic(err) + 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)