194 lines
5.7 KiB
Go
194 lines
5.7 KiB
Go
//go:build windows
|
|
|
|
// Package wintunruntime installs and preloads the pinned embedded Wintun DLL.
|
|
package wintunruntime
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"golang.org/x/sys/windows"
|
|
"golang.zx2c4.com/wintun"
|
|
|
|
"remlink/internal/appdir"
|
|
)
|
|
|
|
const (
|
|
Version = "0.14.1"
|
|
DLLName = "wintun.dll"
|
|
DLLSHA256AMD64 = "e5da8447dc2c320edc0fc52fa01885c103de8c118481f683643cacc3220dafce"
|
|
)
|
|
|
|
var (
|
|
preloadOnce sync.Once
|
|
preloadPath string
|
|
preloadErr error
|
|
// Keep the module referenced for the process lifetime. The upstream Go
|
|
// binding subsequently resolves the already-loaded DLL by base name.
|
|
preloadHandle windows.Handle
|
|
)
|
|
|
|
// Install writes the embedded signed DLL into one portable package directory.
|
|
// An existing verified file is reused; an unexpected file is replaced.
|
|
func Install(packageDirectory string) (string, error) {
|
|
if packageDirectory == "" {
|
|
return "", errors.New("package directory must not be empty")
|
|
}
|
|
data, expectedHash, err := assetBytes()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := os.MkdirAll(packageDirectory, 0o750); err != nil {
|
|
return "", fmt.Errorf("create Wintun runtime directory: %w", err)
|
|
}
|
|
target := filepath.Join(packageDirectory, DLLName)
|
|
|
|
verified, err := fileHasSHA256Stable(target, expectedHash)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if verified {
|
|
return target, nil
|
|
}
|
|
if err := writeAtomically(target, data); err != nil {
|
|
return "", err
|
|
}
|
|
verified, err = fileHasSHA256Stable(target, expectedHash)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !verified {
|
|
return "", errors.New("installed Wintun DLL failed SHA-256 verification")
|
|
}
|
|
return target, nil
|
|
}
|
|
|
|
// PreloadDefault installs and loads Wintun beside the running executable before
|
|
// the upstream lazy binding attempts to resolve wintun.dll by its base name.
|
|
func PreloadDefault() (string, error) {
|
|
root, err := appdir.Executable()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return Preload(root)
|
|
}
|
|
|
|
// Preload installs and loads Wintun exactly once for the process.
|
|
func Preload(packageDirectory string) (string, error) {
|
|
preloadOnce.Do(func() {
|
|
preloadPath, preloadErr = Install(packageDirectory)
|
|
if preloadErr != nil {
|
|
return
|
|
}
|
|
preloadHandle, preloadErr = windows.LoadLibraryEx(
|
|
preloadPath,
|
|
0,
|
|
windows.LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR|windows.LOAD_LIBRARY_SEARCH_SYSTEM32,
|
|
)
|
|
if preloadErr != nil {
|
|
preloadErr = fmt.Errorf("preload Wintun DLL %q: %w", preloadPath, preloadErr)
|
|
}
|
|
})
|
|
return preloadPath, preloadErr
|
|
}
|
|
|
|
// Probe installs/preloads the DLL and asks the official binding for its version.
|
|
func Probe() (path string, version string, err error) {
|
|
path, err = PreloadDefault()
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
version = wintun.Version()
|
|
if version == "unknown" {
|
|
return "", "", errors.New("official Wintun binding could not resolve the preloaded DLL")
|
|
}
|
|
return path, version, nil
|
|
}
|
|
|
|
func fileHasSHA256(path string, expected [sha256.Size]byte) (bool, error) {
|
|
data, err := os.ReadFile(path)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, fmt.Errorf("read existing Wintun DLL: %w", err)
|
|
}
|
|
return sha256.Sum256(data) == expected, nil
|
|
}
|
|
|
|
func fileHasSHA256Stable(path string, expected [sha256.Size]byte) (bool, error) {
|
|
var lastErr error
|
|
for attempt := 0; attempt < 16; attempt++ {
|
|
verified, err := fileHasSHA256(path, expected)
|
|
if err == nil {
|
|
return verified, nil
|
|
}
|
|
if !errors.Is(err, windows.ERROR_ACCESS_DENIED) && !errors.Is(err, windows.ERROR_SHARING_VIOLATION) {
|
|
return false, err
|
|
}
|
|
lastErr = err
|
|
time.Sleep(time.Duration(attempt+1) * time.Millisecond)
|
|
}
|
|
return false, lastErr
|
|
}
|
|
|
|
func writeAtomically(target string, data []byte) error {
|
|
temporary, err := os.CreateTemp(filepath.Dir(target), ".wintun-*.tmp")
|
|
if err != nil {
|
|
return fmt.Errorf("create temporary Wintun DLL: %w", err)
|
|
}
|
|
temporaryPath := temporary.Name()
|
|
defer os.Remove(temporaryPath)
|
|
|
|
if _, err := temporary.Write(data); err != nil {
|
|
temporary.Close()
|
|
return fmt.Errorf("write temporary Wintun DLL: %w", err)
|
|
}
|
|
if err := temporary.Sync(); err != nil {
|
|
temporary.Close()
|
|
return fmt.Errorf("sync temporary Wintun DLL: %w", err)
|
|
}
|
|
if err := temporary.Close(); err != nil {
|
|
return fmt.Errorf("close temporary Wintun DLL: %w", err)
|
|
}
|
|
if err := os.Chmod(temporaryPath, 0o644); err != nil {
|
|
return fmt.Errorf("set Wintun DLL permissions: %w", err)
|
|
}
|
|
|
|
from, err := windows.UTF16PtrFromString(temporaryPath)
|
|
if err != nil {
|
|
return fmt.Errorf("encode temporary Wintun DLL path: %w", err)
|
|
}
|
|
to, err := windows.UTF16PtrFromString(target)
|
|
if err != nil {
|
|
return fmt.Errorf("encode Wintun DLL target path: %w", err)
|
|
}
|
|
// MoveFileExW publishes the verified bytes in one replace operation. This
|
|
// avoids a remove/rename gap where another launch from the same package could
|
|
// observe the DLL path as missing. Concurrent publishers
|
|
// may briefly hold a Windows file handle; accept their verified result or
|
|
// retry only the documented sharing/access failures.
|
|
for attempt := 0; attempt < 8; attempt++ {
|
|
if verified, verifyErr := fileHasSHA256(target, sha256.Sum256(data)); verifyErr == nil && verified {
|
|
return nil
|
|
}
|
|
err = windows.MoveFileEx(from, to, windows.MOVEFILE_REPLACE_EXISTING|windows.MOVEFILE_WRITE_THROUGH)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if !errors.Is(err, windows.ERROR_ACCESS_DENIED) && !errors.Is(err, windows.ERROR_SHARING_VIOLATION) {
|
|
return fmt.Errorf("install Wintun DLL: %w", err)
|
|
}
|
|
time.Sleep(time.Duration(attempt+1) * time.Millisecond)
|
|
}
|
|
if verified, verifyErr := fileHasSHA256(target, sha256.Sum256(data)); verifyErr == nil && verified {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("install Wintun DLL after concurrent publish retries: %w", err)
|
|
}
|