mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-07-19 21:08:40 +08:00
191 lines
5.4 KiB
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")
|
|
}
|