sing-box/common/windivert/driver_windows.go

191 lines
5.4 KiB
Go

//go:build windows
package windivert
import (
"errors"
"runtime"
"time"
"github.com/sagernet/sing-box/internal/winmutex"
E "github.com/sagernet/sing/common/exceptions"
"golang.org/x/sys/windows"
)
const (
driverServiceName = "WinDivert"
driverDeviceName = `\\.\WinDivert`
driverInstallMutexName = "WinDivertDriverInstallMutex"
driverInstallMutexTimeout = 90 * time.Second
driverReadyTimeout = 60 * time.Second
driverStateRefreshInterval = 50 * time.Millisecond
)
var driverDevName, _ = windows.UTF16PtrFromString(driverDeviceName)
func acquireDevice() (windows.Handle, error) {
device, err := openDevice()
if err == nil {
return device, nil
}
fatalErr := driverOpenFatal(err)
if fatalErr != nil {
return 0, fatalErr
}
if runtime.GOARCH == "386" {
var isWow64 bool
err = windows.IsWow64Process(windows.CurrentProcess(), &isWow64)
if err == nil && isWow64 {
return 0, E.New("windivert: 386 build detected running under WOW64 on a 64-bit kernel; use the amd64 build")
}
}
device, err = winmutex.WithLock(driverInstallMutexName, driverInstallMutexTimeout, installAndOpenDevice)
if err != nil && device != 0 {
closeErr := windows.CloseHandle(device)
if closeErr != nil {
closeErr = E.Cause(closeErr, "windivert: close device after install lock failure")
}
return 0, E.Errors(err, closeErr)
}
return device, err
}
func driverOpenFatal(err error) error {
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
return E.Cause(err, "windivert: open device (administrator required)")
}
if errors.Is(err, windows.ERROR_FILE_NOT_FOUND) ||
errors.Is(err, windows.ERROR_PATH_NOT_FOUND) ||
errors.Is(err, windows.ERROR_NO_SUCH_DEVICE) {
return nil
}
return E.Cause(err, "windivert: open device")
}
func installAndOpenDevice() (windows.Handle, error) {
device, err := openDevice()
if err == nil {
return device, nil
}
fatalErr := driverOpenFatal(err)
if fatalErr != nil {
return 0, fatalErr
}
sysPath, sysFile, err := extractVerified()
if err != nil {
return 0, err
}
defer sysFile.Close()
sysPathW, err := windows.UTF16PtrFromString(sysPath)
if err != nil {
return 0, E.Cause(err, "windivert: utf16 driver path")
}
manager, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS)
if err != nil {
return 0, E.Cause(err, "windivert: open SCM")
}
defer windows.CloseServiceHandle(manager)
serviceNameW, _ := windows.UTF16PtrFromString(driverServiceName)
deadline := time.Now().Add(driverReadyTimeout)
for {
serviceErr := tryInstallService(manager, serviceNameW, sysPathW)
if serviceErr != nil && !driverServiceTransient(serviceErr) {
return 0, serviceErr
}
device, err = openDevice()
if err == nil {
return device, nil
}
fatalErr = driverOpenFatal(err)
if fatalErr != nil {
return 0, fatalErr
}
if time.Now().After(deadline) {
openErr := E.Cause(err, "windivert: open device after driver readiness timeout")
if serviceErr != nil {
return 0, E.Errors(serviceErr, openErr)
}
return 0, openErr
}
time.Sleep(driverStateRefreshInterval)
}
}
func driverServiceTransient(err error) bool {
return errors.Is(err, windows.ERROR_SERVICE_MARKED_FOR_DELETE) ||
errors.Is(err, windows.ERROR_SERVICE_DISABLED) ||
errors.Is(err, windows.ERROR_OBJECT_ALREADY_EXISTS)
}
func tryInstallService(manager windows.Handle, serviceNameW, sysPathW *uint16) error {
service, err := openOrCreateService(manager, serviceNameW, sysPathW)
if err != nil {
return err
}
defer windows.CloseServiceHandle(service)
err = windows.StartService(service, 0, nil)
if err == nil {
// Mark for deletion so the driver unregisters when the last handle
// closes or on next reboot. Matches the upstream DLL's behavior:
// only the process that actually started the service takes on the
// cleanup responsibility. If another process already started it,
// we leave DeleteService to them.
_ = windows.DeleteService(service)
return nil
}
if errors.Is(err, windows.ERROR_SERVICE_ALREADY_RUNNING) {
return nil
}
if errors.Is(err, windows.ERROR_SERVICE_DISABLED) {
// The disabled check precedes the running check: a running service
// marked for deletion reports ERROR_SERVICE_DISABLED instead of
// ERROR_SERVICE_ALREADY_RUNNING. The device is nonetheless up.
var status windows.SERVICE_STATUS
queryErr := windows.QueryServiceStatus(service, &status)
if queryErr == nil && status.CurrentState == windows.SERVICE_RUNNING {
return nil
}
}
return E.Cause(err, "windivert: start service")
}
func openOrCreateService(manager windows.Handle, serviceNameW, sysPathW *uint16) (windows.Handle, error) {
service, err := windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS)
if err == nil {
return service, nil
}
service, err = windows.CreateService(
manager,
serviceNameW,
serviceNameW,
windows.SERVICE_ALL_ACCESS,
windows.SERVICE_KERNEL_DRIVER,
windows.SERVICE_DEMAND_START,
windows.SERVICE_ERROR_NORMAL,
sysPathW,
nil, nil, nil, nil, nil,
)
if err == nil {
return service, nil
}
if errors.Is(err, windows.ERROR_SERVICE_EXISTS) {
service, err = windows.OpenService(manager, serviceNameW, windows.SERVICE_ALL_ACCESS)
if err == nil {
return service, nil
}
}
return 0, wrapDriverInstallError(err)
}
func wrapDriverInstallError(err error) error {
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
return E.Cause(err, "windivert: installing the kernel driver requires Administrator privileges")
}
return E.Cause(err, "windivert: create service")
}