platform: Add http proxy support for Windows
This commit is contained in:
@@ -16,6 +16,7 @@ type PlatformInterface interface {
|
||||
|
||||
UsePlatformInterface() bool
|
||||
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||
ProcessPlatformOptions(options option.TunPlatformOptions) error
|
||||
|
||||
UsePlatformDefaultInterfaceMonitor() bool
|
||||
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
||||
|
||||
@@ -97,7 +97,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if networkManager.AutoDetectInterface() && !disableDefaultBind {
|
||||
if platformInterface != nil {
|
||||
if platformInterface != nil && platformInterface.UsePlatformNetworkInterfaces() {
|
||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||
|
||||
@@ -107,7 +107,7 @@ func (l *Listener) Start() error {
|
||||
} else {
|
||||
listenAddrString = listenAddr.String()
|
||||
}
|
||||
systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS)
|
||||
systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize system proxy")
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ type AndroidSystemProxy struct {
|
||||
isEnabled bool
|
||||
}
|
||||
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*AndroidSystemProxy, error) {
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool, bypassDomain []string) (*AndroidSystemProxy, error) {
|
||||
userId := os.Getuid()
|
||||
var (
|
||||
useRish bool
|
||||
|
||||
@@ -24,7 +24,7 @@ type DarwinSystemProxy struct {
|
||||
isEnabled bool
|
||||
}
|
||||
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool, bypassDomain []string) (*DarwinSystemProxy, error) {
|
||||
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
|
||||
if interfaceMonitor == nil {
|
||||
return nil, E.New("missing interface monitor")
|
||||
|
||||
@@ -24,7 +24,7 @@ type LinuxSystemProxy struct {
|
||||
isEnabled bool
|
||||
}
|
||||
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool, bypassDomain []string) (*LinuxSystemProxy, error) {
|
||||
hasGSettings := common.Error(exec.LookPath("gsettings")) == nil
|
||||
kWriteConfigCmds := []string{
|
||||
"kwriteconfig5",
|
||||
|
||||
@@ -9,6 +9,6 @@ import (
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (SystemProxy, error) {
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool, bypassDomain []string) (SystemProxy, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/wininet"
|
||||
@@ -10,13 +11,15 @@ import (
|
||||
type WindowsSystemProxy struct {
|
||||
serverAddr M.Socksaddr
|
||||
supportSOCKS bool
|
||||
bypassDomain []string
|
||||
isEnabled bool
|
||||
}
|
||||
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*WindowsSystemProxy, error) {
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool, bypassDomain []string) (*WindowsSystemProxy, error) {
|
||||
return &WindowsSystemProxy{
|
||||
serverAddr: serverAddr,
|
||||
supportSOCKS: supportSOCKS,
|
||||
bypassDomain: bypassDomain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -25,7 +28,7 @@ func (p *WindowsSystemProxy) IsEnabled() bool {
|
||||
}
|
||||
|
||||
func (p *WindowsSystemProxy) Enable() error {
|
||||
err := wininet.SetSystemProxy("http://"+p.serverAddr.String(), "")
|
||||
err := wininet.SetSystemProxy("http://"+p.serverAddr.String(), strings.Join(p.bypassDomain, ";"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -68,13 +68,20 @@ func (s *windowsService) Execute(arguments []string, requests <-chan svc.ChangeR
|
||||
serviceLogError(err)
|
||||
return
|
||||
}
|
||||
statuses <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
|
||||
statuses <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.AcceptSessionChange}
|
||||
runtime.GC()
|
||||
for request := range requests {
|
||||
if request.Cmd == svc.Interrogate {
|
||||
statuses <- request.CurrentStatus
|
||||
continue
|
||||
}
|
||||
if request.Cmd == svc.SessionChange {
|
||||
err = d.handlePlatformSessionChange(request.EventType, uint32(request.EventData))
|
||||
if err != nil {
|
||||
serviceLogError(E.Cause(err, "handle session change"))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if request.Cmd == svc.Stop || request.Cmd == svc.Shutdown {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func addPlatformServiceCommands() {
|
||||
&commandServiceFlagAllowUnsafeInstallation,
|
||||
"allow-unsafe-installation-directory-permissions",
|
||||
false,
|
||||
"skip installation path ancestor permission validation",
|
||||
"skip installation path security validation and permission hardening",
|
||||
)
|
||||
commandService.AddCommand(commandServiceInstall)
|
||||
commandService.AddCommand(commandServiceUninstall)
|
||||
|
||||
@@ -60,11 +60,13 @@ func (s *desktopService) StartService(ctx context.Context, request *StartService
|
||||
if ownerUserID != "" && ownerUserID != identity.UserID {
|
||||
return nil, status.Error(codes.PermissionDenied, "the service is owned by another user")
|
||||
}
|
||||
if ownerUserID == "" {
|
||||
err = saveOwner(identity.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.daemon.preparePlatformOwnerLocked(identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = saveOwner(identity.UserID, identity.SessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentOptions, err := loadStartOptions(identity.UserID)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
@@ -105,6 +107,14 @@ func (s *desktopService) ClaimService(ctx context.Context, empty *emptypb.Empty)
|
||||
return nil, err
|
||||
}
|
||||
if ownerUserID == identity.UserID {
|
||||
err = s.daemon.preparePlatformOwnerLocked(identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = saveOwner(identity.UserID, identity.SessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
if ownerUserID != "" {
|
||||
@@ -114,7 +124,11 @@ func (s *desktopService) ClaimService(ctx context.Context, empty *emptypb.Empty)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = saveOwner(identity.UserID)
|
||||
err = s.daemon.preparePlatformOwnerLocked(identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = saveOwner(identity.UserID, identity.SessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -136,6 +150,14 @@ func (s *desktopService) TakeOverService(ctx context.Context, empty *emptypb.Emp
|
||||
return nil, err
|
||||
}
|
||||
if ownerUserID == identity.UserID {
|
||||
err = s.daemon.preparePlatformOwnerLocked(identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = saveOwner(identity.UserID, identity.SessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
if ownerUserID != "" {
|
||||
@@ -143,12 +165,22 @@ func (s *desktopService) TakeOverService(ctx context.Context, empty *emptypb.Emp
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.daemon.platform != nil {
|
||||
err = s.daemon.platform.ReleaseOwner()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
err = s.daemon.configureWorkingDirectoryLocked(userWorkingDirectory(identity.UserID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = saveOwner(identity.UserID)
|
||||
err = s.daemon.preparePlatformOwnerLocked(identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = saveOwner(identity.UserID, identity.SessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -157,13 +189,17 @@ func (s *desktopService) TakeOverService(ctx context.Context, empty *emptypb.Emp
|
||||
}
|
||||
|
||||
func (d *Daemon) cleanFailedStartLocked(ownerUserID string, options startOptions, startError error) error {
|
||||
var platformError error
|
||||
if d.platform != nil {
|
||||
platformError = d.platform.ResetPlatformOptions()
|
||||
}
|
||||
closeError := d.startedService.CloseService()
|
||||
directory := userWorkingDirectory(ownerUserID)
|
||||
crashReportError := tagUnownedReports(filepath.Join(directory, crashReportsDirectoryName), ownerUserID)
|
||||
oomReportError := tagUnownedReports(filepath.Join(directory, oomReportsDirectoryName), ownerUserID)
|
||||
options.WasRunning = false
|
||||
snapshotError := saveStartOptions(ownerUserID, options)
|
||||
return E.Errors(startError, closeError, crashReportError, oomReportError, snapshotError)
|
||||
return E.Errors(startError, platformError, closeError, crashReportError, oomReportError, snapshotError)
|
||||
}
|
||||
|
||||
func (s *desktopService) GetWorkingDirectory(ctx context.Context, empty *emptypb.Empty) (*WorkingDirectoryInfo, error) {
|
||||
|
||||
@@ -52,14 +52,39 @@ func (h *managedHandler) ServiceReload() error {
|
||||
}
|
||||
|
||||
func (h *managedHandler) SystemProxyStatus() (*daemon.SystemProxyStatus, error) {
|
||||
return &daemon.SystemProxyStatus{}, nil
|
||||
if h.daemon.platform == nil {
|
||||
return &daemon.SystemProxyStatus{}, nil
|
||||
}
|
||||
return h.daemon.platform.SystemProxyStatus()
|
||||
}
|
||||
|
||||
func (h *managedHandler) SetSystemProxyEnabled(enabled bool) error {
|
||||
if !enabled {
|
||||
return nil
|
||||
if h.daemon.platform == nil {
|
||||
if !enabled {
|
||||
return nil
|
||||
}
|
||||
return status.Error(codes.FailedPrecondition, "the system proxy is not available")
|
||||
}
|
||||
return status.Error(codes.FailedPrecondition, "the system proxy is not available")
|
||||
ownerUserID, err := loadOwner()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options, err := loadStartOptions(ownerUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
previousEnabled := options.systemProxyEnabled()
|
||||
err = h.daemon.platform.SetSystemProxyEnabled(enabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.SystemProxyEnabled = &enabled
|
||||
err = saveStartOptions(ownerUserID, options)
|
||||
if err != nil {
|
||||
rollbackError := h.daemon.platform.SetSystemProxyEnabled(previousEnabled)
|
||||
return E.Errors(err, rollbackError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *managedHandler) TriggerNativeCrash() error {
|
||||
|
||||
@@ -520,6 +520,32 @@ func (c *windowsAuthenticatedConnection) peerConnectionIdentity() peerIdentity {
|
||||
return c.identity
|
||||
}
|
||||
|
||||
func (c *windowsAuthenticatedConnection) duplicateImpersonationToken() (windows.Token, error) {
|
||||
var processToken windows.Token
|
||||
err := windows.OpenProcessToken(c.parentProcess, windows.TOKEN_QUERY|windows.TOKEN_DUPLICATE, &processToken)
|
||||
if err != nil {
|
||||
return 0, E.Cause(err, "open application token")
|
||||
}
|
||||
defer processToken.Close()
|
||||
return duplicateImpersonationToken(processToken)
|
||||
}
|
||||
|
||||
func (d *Daemon) duplicatePeerImpersonationToken(identity peerIdentity) (windows.Token, error) {
|
||||
d.peerAccess.Lock()
|
||||
defer d.peerAccess.Unlock()
|
||||
for connection, connectionIdentity := range d.peerConnections {
|
||||
if connectionIdentity != identity {
|
||||
continue
|
||||
}
|
||||
windowsConnection, loaded := connection.(*windowsAuthenticatedConnection)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
return windowsConnection.duplicateImpersonationToken()
|
||||
}
|
||||
return 0, E.New("authenticated application connection is no longer available")
|
||||
}
|
||||
|
||||
func (d *Daemon) registerPeerConnection(connection peerConnection) {
|
||||
d.peerAccess.Lock()
|
||||
defer d.peerAccess.Unlock()
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/daemon"
|
||||
)
|
||||
|
||||
type daemonPlatform interface {
|
||||
adapter.PlatformInterface
|
||||
PrepareOwner(identity peerIdentity) error
|
||||
RestoreOwner(state ownerState) error
|
||||
ReleaseOwner() error
|
||||
ResetPlatformOptions() error
|
||||
SetSystemProxyPreference(enabled bool)
|
||||
SystemProxyStatus() (*daemon.SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(enabled bool) error
|
||||
HandleSessionChange(eventType uint32, sessionID uint32, state ownerState) (uint32, bool, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
func (d *Daemon) preparePlatformOwnerLocked(identity peerIdentity) error {
|
||||
if d.platform == nil {
|
||||
return nil
|
||||
}
|
||||
return d.platform.PrepareOwner(identity)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
func newPlatformInterface(daemon *Daemon) (daemonPlatform, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/settings"
|
||||
"github.com/sagernet/sing-box/daemon"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var regDisablePredefinedCacheEx = windows.NewLazySystemDLL("advapi32.dll").NewProc("RegDisablePredefinedCacheEx")
|
||||
|
||||
type windowsPlatformInterface struct {
|
||||
daemon *Daemon
|
||||
access sync.Mutex
|
||||
ownerUserID string
|
||||
sessionID uint32
|
||||
token windows.Token
|
||||
systemProxy *settings.WindowsSystemProxy
|
||||
systemProxyEnabled bool
|
||||
}
|
||||
|
||||
func newPlatformInterface(daemonInstance *Daemon) (daemonPlatform, error) {
|
||||
result, _, _ := regDisablePredefinedCacheEx.Call()
|
||||
if result != 0 {
|
||||
return nil, E.Cause(syscall.Errno(result), "disable predefined registry handle cache")
|
||||
}
|
||||
return &windowsPlatformInterface{
|
||||
daemon: daemonInstance,
|
||||
systemProxyEnabled: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) Initialize(networkManager adapter.NetworkManager) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UsePlatformAutoDetectInterfaceControl() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) AutoDetectInterfaceControl(fd int) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UsePlatformInterface() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) ProcessPlatformOptions(options option.TunPlatformOptions) error {
|
||||
if options.HTTPProxy == nil || !options.HTTPProxy.Enabled {
|
||||
return nil
|
||||
}
|
||||
httpProxyOptions := options.HTTPProxy
|
||||
systemProxy, err := settings.NewSystemProxy(
|
||||
context.Background(),
|
||||
M.ParseSocksaddrHostPort(httpProxyOptions.Server, httpProxyOptions.ServerPort),
|
||||
false,
|
||||
[]string(httpProxyOptions.BypassDomain),
|
||||
)
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize system proxy")
|
||||
}
|
||||
p.access.Lock()
|
||||
if p.systemProxy != nil {
|
||||
p.access.Unlock()
|
||||
return E.New("only one enabled `tun.platform.http_proxy` is supported")
|
||||
}
|
||||
p.systemProxy = systemProxy
|
||||
err = p.applySystemProxyLocked()
|
||||
if err != nil {
|
||||
rollbackError := p.disableSystemProxyLocked()
|
||||
p.systemProxy = nil
|
||||
p.access.Unlock()
|
||||
return E.Errors(E.Cause(err, "set system proxy"), rollbackError)
|
||||
}
|
||||
p.access.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UsePlatformDefaultInterfaceMonitor() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UsePlatformNetworkInterfaces() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) NetworkInterfaces() ([]adapter.NetworkInterface, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UnderNetworkExtension() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) NetworkExtensionIncludeAllNetworks() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) ClearDNSCache() {
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) RequestPermissionForWIFIState() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) ReadWIFIState() adapter.WIFIState {
|
||||
return adapter.WIFIState{}
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UsePlatformConnectionOwnerFinder() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) FindConnectionOwner(request *adapter.FindConnectionOwnerRequest) (*adapter.ConnectionOwner, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UsePlatformWIFIMonitor() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UsePlatformNotification() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) SendNotification(notification *adapter.Notification) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) MyInterfaceAddress() []netip.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UsePlatformNeighborResolver() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UsePlatformShell() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) CheckPlatformShell() error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) OpenShellSession(user *adapter.PlatformUser, command string, environ []string, term string, rows int32, cols int32) (adapter.ShellSession, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) LookupUser(username string) (*adapter.PlatformUser, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) LookupSFTPServer() (string, error) {
|
||||
return "", os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) ReadSystemSSHHostKey() ([]byte, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) TailscaleHostname() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) UsePlatformBridge() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) CreateBridge(options adapter.BridgeOptions) (adapter.BridgeSession, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) PrepareOwner(identity peerIdentity) error {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
if listenAddress != "" {
|
||||
p.ownerUserID = identity.UserID
|
||||
p.sessionID = identity.SessionID
|
||||
return p.applySystemProxyLocked()
|
||||
}
|
||||
if p.token != 0 && p.ownerUserID == identity.UserID && p.sessionID == identity.SessionID {
|
||||
return p.applySystemProxyLocked()
|
||||
}
|
||||
token, err := p.daemon.duplicatePeerImpersonationToken(identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = validateImpersonationToken(token, identity.UserID, identity.SessionID)
|
||||
if err != nil {
|
||||
token.Close()
|
||||
return err
|
||||
}
|
||||
err = p.replaceOwnerTokenLocked(identity.UserID, identity.SessionID, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) RestoreOwner(state ownerState) error {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
p.ownerUserID = state.UserID
|
||||
p.sessionID = state.SessionID
|
||||
if listenAddress != "" {
|
||||
return nil
|
||||
}
|
||||
if state.SessionID == 0 {
|
||||
return E.New("missing owner session")
|
||||
}
|
||||
token, err := querySessionImpersonationToken(state.SessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = validateImpersonationToken(token, state.UserID, state.SessionID)
|
||||
if err != nil {
|
||||
token.Close()
|
||||
return err
|
||||
}
|
||||
p.token = token
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) ReleaseOwner() error {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
return p.releaseOwnerLocked()
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) ResetPlatformOptions() error {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
err := p.disableSystemProxyLocked()
|
||||
if err == nil {
|
||||
p.systemProxy = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) SetSystemProxyPreference(enabled bool) {
|
||||
p.access.Lock()
|
||||
p.systemProxyEnabled = enabled
|
||||
p.access.Unlock()
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) SystemProxyStatus() (*daemon.SystemProxyStatus, error) {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
available := p.systemProxy != nil
|
||||
return &daemon.SystemProxyStatus{
|
||||
Available: available,
|
||||
Enabled: available && p.systemProxyEnabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) SetSystemProxyEnabled(enabled bool) error {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
if p.systemProxy == nil {
|
||||
if !enabled {
|
||||
p.systemProxyEnabled = false
|
||||
return nil
|
||||
}
|
||||
return E.New("the system proxy is not available")
|
||||
}
|
||||
previousEnabled := p.systemProxyEnabled
|
||||
p.systemProxyEnabled = enabled
|
||||
err := p.applySystemProxyLocked()
|
||||
if err != nil {
|
||||
p.systemProxyEnabled = previousEnabled
|
||||
rollbackError := p.applySystemProxyLocked()
|
||||
return E.Errors(err, rollbackError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) HandleSessionChange(eventType uint32, sessionID uint32, state ownerState) (uint32, bool, error) {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
if eventType == windows.WTS_SESSION_LOGOFF {
|
||||
if p.sessionID != sessionID {
|
||||
return 0, false, nil
|
||||
}
|
||||
return 0, false, p.releaseOwnerLocked()
|
||||
}
|
||||
if eventType != windows.WTS_SESSION_LOGON &&
|
||||
eventType != windows.WTS_CONSOLE_CONNECT &&
|
||||
eventType != windows.WTS_REMOTE_CONNECT &&
|
||||
eventType != windows.WTS_SESSION_UNLOCK {
|
||||
return 0, false, nil
|
||||
}
|
||||
token, err := querySessionImpersonationToken(sessionID)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
userID, tokenSessionID, err := impersonationTokenIdentity(token)
|
||||
if err != nil {
|
||||
token.Close()
|
||||
return 0, false, err
|
||||
}
|
||||
if userID != state.UserID || tokenSessionID != sessionID {
|
||||
token.Close()
|
||||
return 0, false, nil
|
||||
}
|
||||
err = p.replaceOwnerTokenLocked(userID, sessionID, token)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
return sessionID, true, nil
|
||||
}
|
||||
|
||||
func (d *Daemon) handlePlatformSessionChange(eventType uint32, sessionID uint32) error {
|
||||
d.lifecycleAccess.Lock()
|
||||
defer d.lifecycleAccess.Unlock()
|
||||
if d.closed || d.platform == nil {
|
||||
return nil
|
||||
}
|
||||
state, err := loadOwnerState()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
newSessionID, changed, err := d.platform.HandleSessionChange(eventType, sessionID, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
return saveOwner(state.UserID, newSessionID)
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) Close() error {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
systemProxyError := p.disableSystemProxyLocked()
|
||||
p.systemProxy = nil
|
||||
ownerError := p.closeOwnerTokenLocked()
|
||||
return E.Errors(systemProxyError, ownerError)
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) applySystemProxyLocked() error {
|
||||
if p.systemProxy == nil {
|
||||
return nil
|
||||
}
|
||||
if p.systemProxyEnabled {
|
||||
if p.systemProxy.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
return p.runUserOperationLocked(p.systemProxy.Enable)
|
||||
}
|
||||
return p.disableSystemProxyLocked()
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) disableSystemProxyLocked() error {
|
||||
if p.systemProxy == nil || !p.systemProxy.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
return p.runUserOperationLocked(p.systemProxy.Disable)
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) runUserOperationLocked(operation func() error) error {
|
||||
if listenAddress != "" {
|
||||
return operation()
|
||||
}
|
||||
if p.token == 0 {
|
||||
return nil
|
||||
}
|
||||
return runImpersonated(p.token, operation)
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) replaceOwnerTokenLocked(userID string, sessionID uint32, token windows.Token) error {
|
||||
err := p.disableSystemProxyLocked()
|
||||
if err != nil {
|
||||
return E.Errors(err, token.Close())
|
||||
}
|
||||
err = p.closeOwnerTokenLocked()
|
||||
if err != nil {
|
||||
return E.Errors(err, token.Close())
|
||||
}
|
||||
p.ownerUserID = userID
|
||||
p.sessionID = sessionID
|
||||
p.token = token
|
||||
err = p.applySystemProxyLocked()
|
||||
if err != nil {
|
||||
return E.Errors(err, p.closeOwnerTokenLocked())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) releaseOwnerLocked() error {
|
||||
err := p.disableSystemProxyLocked()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.closeOwnerTokenLocked()
|
||||
p.ownerUserID = ""
|
||||
p.sessionID = 0
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *windowsPlatformInterface) closeOwnerTokenLocked() error {
|
||||
if p.token == 0 {
|
||||
return nil
|
||||
}
|
||||
err := p.token.Close()
|
||||
p.token = 0
|
||||
return err
|
||||
}
|
||||
|
||||
func runImpersonated(token windows.Token, operation func() error) error {
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
err := windows.SetThreadToken(nil, token)
|
||||
if err != nil {
|
||||
runtime.UnlockOSThread()
|
||||
result <- E.Cause(err, "impersonate owner")
|
||||
return
|
||||
}
|
||||
operationError := operation()
|
||||
revertError := windows.RevertToSelf()
|
||||
if revertError == nil {
|
||||
runtime.UnlockOSThread()
|
||||
} else {
|
||||
revertError = E.Cause(revertError, "revert owner impersonation")
|
||||
}
|
||||
result <- E.Errors(operationError, revertError)
|
||||
}()
|
||||
return <-result
|
||||
}
|
||||
|
||||
func querySessionImpersonationToken(sessionID uint32) (windows.Token, error) {
|
||||
var primaryToken windows.Token
|
||||
err := windows.WTSQueryUserToken(sessionID, &primaryToken)
|
||||
if err != nil {
|
||||
return 0, E.Cause(err, "query session user token")
|
||||
}
|
||||
defer primaryToken.Close()
|
||||
return duplicateImpersonationToken(primaryToken)
|
||||
}
|
||||
|
||||
func duplicateImpersonationToken(token windows.Token) (windows.Token, error) {
|
||||
var duplicatedToken windows.Token
|
||||
err := windows.DuplicateTokenEx(
|
||||
token,
|
||||
windows.TOKEN_QUERY|windows.TOKEN_IMPERSONATE,
|
||||
nil,
|
||||
windows.SecurityImpersonation,
|
||||
windows.TokenImpersonation,
|
||||
&duplicatedToken,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, E.Cause(err, "duplicate owner impersonation token")
|
||||
}
|
||||
return duplicatedToken, nil
|
||||
}
|
||||
|
||||
func validateImpersonationToken(token windows.Token, expectedUserID string, expectedSessionID uint32) error {
|
||||
userID, sessionID, err := impersonationTokenIdentity(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if userID != expectedUserID || sessionID != expectedSessionID {
|
||||
return E.New("owner token identity does not match authenticated application")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func impersonationTokenIdentity(token windows.Token) (string, uint32, error) {
|
||||
user, err := token.GetTokenUser()
|
||||
if err != nil {
|
||||
return "", 0, E.Cause(err, "query owner token user")
|
||||
}
|
||||
userID := user.User.Sid.String()
|
||||
if userID == "" {
|
||||
return "", 0, E.New("owner token has an invalid user SID")
|
||||
}
|
||||
var sessionID uint32
|
||||
var returnLength uint32
|
||||
err = windows.GetTokenInformation(
|
||||
token,
|
||||
windows.TokenSessionId,
|
||||
(*byte)(unsafe.Pointer(&sessionID)),
|
||||
uint32(unsafe.Sizeof(sessionID)),
|
||||
&returnLength,
|
||||
)
|
||||
if err != nil {
|
||||
return "", 0, E.Cause(err, "query owner token session")
|
||||
}
|
||||
return userID, sessionID, nil
|
||||
}
|
||||
|
||||
var _ daemonPlatform = (*windowsPlatformInterface)(nil)
|
||||
@@ -60,11 +60,14 @@ func secureWindowsInstallation(executablePath string, allowUnsafeInstallation bo
|
||||
if !bytes.Equal(daemonSigner, applicationSigner) {
|
||||
return "", E.New("installed application and daemon have different signing certificates")
|
||||
}
|
||||
if allowUnsafeInstallation {
|
||||
return daemonPath, nil
|
||||
}
|
||||
volumeRoot, err := validateFixedNTFSVolume(installationDirectory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = validateInstallationAncestors(filepath.Dir(installationDirectory), volumeRoot, !allowUnsafeInstallation)
|
||||
err = validateInstallationAncestors(filepath.Dir(installationDirectory), volumeRoot, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -95,18 +98,10 @@ func installedApplicationPath(daemonPath string) (string, string, error) {
|
||||
return installationDirectory, filepath.Join(installationDirectory, applicationExecutableName), nil
|
||||
}
|
||||
|
||||
func secureWindowsWorkingDirectory(path string) error {
|
||||
serviceUserID, err := windowsServiceSID()
|
||||
if err != nil {
|
||||
return E.Cause(err, "create daemon service SID")
|
||||
}
|
||||
func windowsWorkingDirectorySecurityDescriptors(serviceUserID *windows.SID) (string, string, error) {
|
||||
serviceUserIDString := serviceUserID.String()
|
||||
if serviceUserIDString == "" {
|
||||
return E.New("daemon service has an invalid SID")
|
||||
}
|
||||
err = validateTreeHasNoReparsePoints(path)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", "", E.New("daemon service has an invalid SID")
|
||||
}
|
||||
directoryDescriptor := fmt.Sprintf(
|
||||
"O:SYG:SYD:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;%s)",
|
||||
@@ -116,9 +111,35 @@ func secureWindowsWorkingDirectory(path string) error {
|
||||
"O:SYG:SYD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;%s)",
|
||||
serviceUserIDString,
|
||||
)
|
||||
return directoryDescriptor, fileDescriptor, nil
|
||||
}
|
||||
|
||||
func secureWindowsWorkingDirectory(path string, serviceUserID *windows.SID) error {
|
||||
directoryDescriptor, fileDescriptor, err := windowsWorkingDirectorySecurityDescriptors(serviceUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = validateTreeHasNoReparsePoints(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return applyProtectedTree(path, directoryDescriptor, fileDescriptor)
|
||||
}
|
||||
|
||||
func secureWindowsWorkingDirectoryRoot(path string, serviceUserID *windows.SID) error {
|
||||
directorySecurityDescriptor, _, err := windowsWorkingDirectorySecurityDescriptors(serviceUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
directoryDescriptor, err := windows.SecurityDescriptorFromString(directorySecurityDescriptor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return winio.RunWithPrivilege(winio.SeRestorePrivilege, func() error {
|
||||
return applyProtectedFileSecurity(path, directoryDescriptor)
|
||||
})
|
||||
}
|
||||
|
||||
func windowsServiceSID() (*windows.SID, error) {
|
||||
serviceNameUTF16 := utf16.Encode([]rune(strings.ToUpper(serviceName)))
|
||||
serviceNameContent := make([]byte, len(serviceNameUTF16)*2)
|
||||
@@ -137,6 +158,14 @@ func windowsServiceSID() (*windows.SID, error) {
|
||||
}
|
||||
|
||||
func validateProtectedWindowsWorkingDirectory(path string, serviceUserID *windows.SID) error {
|
||||
return validateWindowsWorkingDirectory(path, serviceUserID, false)
|
||||
}
|
||||
|
||||
func validateRepairableWindowsWorkingDirectory(path string, serviceUserID *windows.SID) error {
|
||||
return validateWindowsWorkingDirectory(path, serviceUserID, true)
|
||||
}
|
||||
|
||||
func validateWindowsWorkingDirectory(path string, serviceUserID *windows.SID, allowAdditionalAccessControlEntries bool) error {
|
||||
attributes, err := windowsFileAttributes(path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -177,7 +206,9 @@ func validateProtectedWindowsWorkingDirectory(path string, serviceUserID *window
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if discretionaryAccessControlList == nil || discretionaryAccessControlList.AceCount != 3 {
|
||||
if discretionaryAccessControlList == nil ||
|
||||
(!allowAdditionalAccessControlEntries && discretionaryAccessControlList.AceCount != 3) ||
|
||||
(allowAdditionalAccessControlEntries && discretionaryAccessControlList.AceCount < 3) {
|
||||
return E.New("daemon working directory has unexpected access control entries")
|
||||
}
|
||||
administratorsUserID, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
|
||||
@@ -195,16 +226,24 @@ func validateProtectedWindowsWorkingDirectory(path string, serviceUserID *window
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if accessControlEntry.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE ||
|
||||
accessControlEntry.Header.AceFlags != windows.OBJECT_INHERIT_ACE|windows.CONTAINER_INHERIT_ACE ||
|
||||
uint32(accessControlEntry.Mask) != 0x001F01FF {
|
||||
if accessControlEntry.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE {
|
||||
return E.New("daemon working directory has an unsafe access control entry")
|
||||
}
|
||||
userID := (*windows.SID)(unsafe.Pointer(&accessControlEntry.SidStart)).String()
|
||||
seen, exists := expectedUsers[userID]
|
||||
if !exists || seen {
|
||||
if !exists {
|
||||
if allowAdditionalAccessControlEntries {
|
||||
continue
|
||||
}
|
||||
return E.New("daemon working directory grants access to an unexpected principal")
|
||||
}
|
||||
if accessControlEntry.Header.AceFlags != windows.OBJECT_INHERIT_ACE|windows.CONTAINER_INHERIT_ACE ||
|
||||
uint32(accessControlEntry.Mask) != 0x001F01FF {
|
||||
return E.New("daemon working directory has an unsafe access control entry")
|
||||
}
|
||||
if seen {
|
||||
return E.New("daemon working directory has a duplicate access control entry")
|
||||
}
|
||||
expectedUsers[userID] = true
|
||||
}
|
||||
for _, seen := range expectedUsers {
|
||||
@@ -498,18 +537,19 @@ func ensureWindowsWorkingDirectory(path string) error {
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if created {
|
||||
err = secureWindowsWorkingDirectory(path)
|
||||
if !created {
|
||||
err = validateRepairableWindowsWorkingDirectory(path, serviceUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = secureWindowsWorkingDirectoryRoot(path, serviceUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = validateProtectedWindowsWorkingDirectory(path, serviceUserID)
|
||||
err = secureWindowsWorkingDirectory(path, serviceUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if created {
|
||||
return nil
|
||||
}
|
||||
return secureWindowsWorkingDirectory(path)
|
||||
return validateProtectedWindowsWorkingDirectory(path, serviceUserID)
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/daemon"
|
||||
"github.com/sagernet/sing-box/experimental/libbox"
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
@@ -33,6 +35,7 @@ type Daemon struct {
|
||||
closed bool
|
||||
peerAccess sync.Mutex
|
||||
peerConnections map[peerConnection]peerIdentity
|
||||
platform daemonPlatform
|
||||
}
|
||||
|
||||
func newDaemon() (*Daemon, error) {
|
||||
@@ -41,6 +44,14 @@ func newDaemon() (*Daemon, error) {
|
||||
logger: log.StdLogger(),
|
||||
runtimeWorkingDirectory: workingDirectory,
|
||||
}
|
||||
platformInterface, err := newPlatformInterface(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.platform = platformInterface
|
||||
if platformInterface != nil {
|
||||
service.MustRegister[adapter.PlatformInterface](ctx, platformInterface)
|
||||
}
|
||||
d.startedService = daemon.NewStartedService(daemon.ServiceOptions{
|
||||
Context: ctx,
|
||||
LogMaxLines: 3000,
|
||||
@@ -107,13 +118,14 @@ func (d *Daemon) restore() {
|
||||
if d.closed {
|
||||
return
|
||||
}
|
||||
ownerUserID, err := loadOwner()
|
||||
ownerState, err := loadOwnerState()
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
d.logger.Warn("load owner: ", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ownerUserID := ownerState.UserID
|
||||
ownerWorkingDirectory := userWorkingDirectory(ownerUserID)
|
||||
err = d.configureWorkingDirectoryLocked(ownerWorkingDirectory)
|
||||
if err != nil {
|
||||
@@ -138,6 +150,13 @@ func (d *Daemon) restore() {
|
||||
if !options.WasRunning {
|
||||
return
|
||||
}
|
||||
if d.platform != nil {
|
||||
d.platform.SetSystemProxyPreference(options.systemProxyEnabled())
|
||||
err = d.platform.RestoreOwner(ownerState)
|
||||
if err != nil {
|
||||
d.logger.Warn("restore owner session: ", err)
|
||||
}
|
||||
}
|
||||
configContent, err := loadServiceConfig(ownerUserID)
|
||||
if err != nil {
|
||||
d.logger.Error("restore service: ", err)
|
||||
@@ -189,7 +208,18 @@ func (d *Daemon) startServiceLocked(ownerUserID string, configContent string, op
|
||||
OomMemoryLimit: options.OOMMemoryLimit,
|
||||
})
|
||||
d.startedService.SetOOMKillerOptions(options.OOMKillerEnabled, options.OOMKillerDisabled, uint64(options.OOMMemoryLimit))
|
||||
return d.startedService.StartOrReloadService(configContent, nil)
|
||||
if d.platform != nil {
|
||||
d.platform.SetSystemProxyPreference(options.systemProxyEnabled())
|
||||
err = d.platform.ResetPlatformOptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = d.startedService.StartOrReloadService(configContent, nil)
|
||||
if err != nil && d.platform != nil {
|
||||
return E.Errors(err, d.platform.ResetPlatformOptions())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Daemon) stopServiceLocked(ownerUserID string) error {
|
||||
@@ -197,6 +227,12 @@ func (d *Daemon) stopServiceLocked(ownerUserID string) error {
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if d.platform != nil {
|
||||
err = d.platform.ResetPlatformOptions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if d.startedService.Instance() != nil {
|
||||
err = d.startedService.CloseService()
|
||||
if err != nil {
|
||||
@@ -222,8 +258,14 @@ func (d *Daemon) Close() {
|
||||
d.lifecycleAccess.Unlock()
|
||||
d.server.Stop()
|
||||
d.lifecycleAccess.Lock()
|
||||
if d.platform != nil {
|
||||
_ = d.platform.ResetPlatformOptions()
|
||||
}
|
||||
_ = d.startedService.CloseService()
|
||||
d.startedService.Close()
|
||||
if d.platform != nil {
|
||||
_ = d.platform.Close()
|
||||
}
|
||||
d.lifecycleAccess.Unlock()
|
||||
}
|
||||
|
||||
@@ -285,7 +327,18 @@ func (a *daemonAuthorizer) Authorize(ctx context.Context, method string) error {
|
||||
if ownerProtectedMethod(method) {
|
||||
a.daemon.lifecycleAccess.Lock()
|
||||
defer a.daemon.lifecycleAccess.Unlock()
|
||||
return a.daemon.authorizeOwnerLocked(identity.UserID)
|
||||
err = a.daemon.authorizeOwnerLocked(identity.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = a.daemon.preparePlatformOwnerLocked(identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a.daemon.platform != nil {
|
||||
return saveOwner(identity.UserID, identity.SessionID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return status.Error(codes.PermissionDenied, "the service is not available")
|
||||
}
|
||||
@@ -308,6 +361,16 @@ func (a *daemonAuthorizer) InvokeUnary(ctx context.Context, method string, handl
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = a.daemon.preparePlatformOwnerLocked(identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if a.daemon.platform != nil {
|
||||
err = saveOwner(identity.UserID, identity.SessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return handler()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,14 +18,16 @@ const (
|
||||
)
|
||||
|
||||
type startOptions struct {
|
||||
WasRunning bool `json:"was_running"`
|
||||
OOMKillerEnabled bool `json:"oom_killer_enabled"`
|
||||
OOMKillerDisabled bool `json:"oom_killer_disabled"`
|
||||
OOMMemoryLimit int64 `json:"oom_memory_limit"`
|
||||
WasRunning bool `json:"was_running"`
|
||||
OOMKillerEnabled bool `json:"oom_killer_enabled"`
|
||||
OOMKillerDisabled bool `json:"oom_killer_disabled"`
|
||||
OOMMemoryLimit int64 `json:"oom_memory_limit"`
|
||||
SystemProxyEnabled *bool `json:"system_proxy_enabled,omitempty"`
|
||||
}
|
||||
|
||||
type ownerState struct {
|
||||
UserID string `json:"user_id"`
|
||||
UserID string `json:"user_id"`
|
||||
SessionID uint32 `json:"session_id,omitempty"`
|
||||
}
|
||||
|
||||
func userWorkingDirectory(userID string) string {
|
||||
@@ -34,25 +36,37 @@ func userWorkingDirectory(userID string) string {
|
||||
}
|
||||
|
||||
func loadOwner() (string, error) {
|
||||
content, err := os.ReadFile(filepath.Join(workingDirectory, ownerFileName))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
state, err := json.UnmarshalExtended[ownerState](content)
|
||||
state, err := loadOwnerState()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return state.UserID, nil
|
||||
}
|
||||
|
||||
func saveOwner(userID string) error {
|
||||
content, err := json.Marshal(ownerState{UserID: userID})
|
||||
func loadOwnerState() (ownerState, error) {
|
||||
content, err := os.ReadFile(filepath.Join(workingDirectory, ownerFileName))
|
||||
if err != nil {
|
||||
return ownerState{}, err
|
||||
}
|
||||
state, err := json.UnmarshalExtended[ownerState](content)
|
||||
if err != nil {
|
||||
return ownerState{}, err
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func saveOwner(userID string, sessionID uint32) error {
|
||||
content, err := json.Marshal(ownerState{UserID: userID, SessionID: sessionID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return atomicfile.WriteFile(filepath.Join(workingDirectory, ownerFileName), content, 0o600)
|
||||
}
|
||||
|
||||
func (o startOptions) systemProxyEnabled() bool {
|
||||
return o.SystemProxyEnabled == nil || *o.SystemProxyEnabled
|
||||
}
|
||||
|
||||
func loadServiceConfig(userID string) (string, error) {
|
||||
content, err := os.ReadFile(filepath.Join(userWorkingDirectory(userID), serviceConfigFileName))
|
||||
if err != nil {
|
||||
|
||||
@@ -86,6 +86,10 @@ func (s *platformInterfaceStub) OpenInterface(options *tun.Options, platformOpti
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) ProcessPlatformOptions(options option.TunPlatformOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) UsePlatformDefaultInterfaceMonitor() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -84,6 +84,10 @@ func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformO
|
||||
return tun.New(*options)
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) ProcessPlatformOptions(options option.TunPlatformOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func myTunAddress(options *tun.Options) []netip.Addr {
|
||||
addresses := make([]netip.Addr, 0, len(options.Inet4Address)+len(options.Inet6Address))
|
||||
for _, prefix := range options.Inet4Address {
|
||||
|
||||
@@ -259,7 +259,7 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
|
||||
func (t *Endpoint) start() error {
|
||||
if t.platformInterface != nil {
|
||||
if t.platformInterface != nil && t.platformInterface.UsePlatformNetworkInterfaces() {
|
||||
err := t.network.UpdateInterfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -335,7 +335,7 @@ func (t *Endpoint) start() error {
|
||||
controlFunc = control.Append(controlFunc, bindFunc)
|
||||
}
|
||||
netns.SetControlFunc(controlFunc)
|
||||
} else if runtime.GOOS == "android" && t.platformInterface != nil {
|
||||
} else if runtime.GOOS == "android" && t.platformInterface != nil && t.platformInterface.UsePlatformAutoDetectInterfaceControl() {
|
||||
netns.SetControlFunc(func(network, address string, c syscall.RawConn) error {
|
||||
return control.Raw(c, func(fd uintptr) error {
|
||||
return t.platformInterface.AutoDetectInterfaceControl(int(fd))
|
||||
@@ -471,7 +471,7 @@ func (t *Endpoint) watchState() {
|
||||
}
|
||||
reportedAuthURL = authURL
|
||||
t.logger.Info("Waiting for authentication: ", authURL)
|
||||
if t.platformInterface != nil {
|
||||
if t.platformInterface != nil && t.platformInterface.UsePlatformNotification() {
|
||||
err := t.platformInterface.SendNotification(&adapter.Notification{
|
||||
Identifier: "tailscale-authentication",
|
||||
TypeName: "Tailscale Authentication Notifications",
|
||||
|
||||
+11
-7
@@ -367,7 +367,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
t.tunOptions.NetNs = manager.ResolvePath(t.tunOptions.NetNs)
|
||||
}
|
||||
}
|
||||
if t.platformInterface == nil {
|
||||
if t.platformInterface == nil || C.IsWindows {
|
||||
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
|
||||
for _, routeRuleSet := range t.routeRuleSet {
|
||||
ipSets := routeRuleSet.ExtractIPSet()
|
||||
@@ -432,12 +432,16 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
t.logger.Trace("creating stack")
|
||||
t.tunIf = tunInterface
|
||||
var (
|
||||
forwarderBindInterface bool
|
||||
includeAllNetworks bool
|
||||
)
|
||||
if t.platformInterface != nil {
|
||||
forwarderBindInterface = true
|
||||
err = t.platformInterface.ProcessPlatformOptions(t.platformOptions)
|
||||
if err != nil {
|
||||
closeError := t.tunIf.Close()
|
||||
t.tunIf = nil
|
||||
return E.Errors(E.Cause(err, "process platform options"), closeError)
|
||||
}
|
||||
}
|
||||
var includeAllNetworks bool
|
||||
if t.platformInterface != nil && t.platformInterface.UnderNetworkExtension() {
|
||||
includeAllNetworks = t.platformInterface.NetworkExtensionIncludeAllNetworks()
|
||||
}
|
||||
tunStack, err := tun.NewStack(t.stack, tun.StackOptions{
|
||||
@@ -448,7 +452,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
ICMPTimeout: C.ICMPTimeout,
|
||||
Handler: t,
|
||||
Logger: t.logger,
|
||||
ForwarderBindInterface: forwarderBindInterface,
|
||||
ForwarderBindInterface: C.IsDarwin,
|
||||
InterfaceFinder: t.networkManager.InterfaceFinder(),
|
||||
IncludeAllNetworks: includeAllNetworks,
|
||||
})
|
||||
|
||||
+2
-2
@@ -108,7 +108,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options
|
||||
return nil, E.New("`auto_detect_interface` is required by `default_network_strategy`")
|
||||
}
|
||||
}
|
||||
usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil
|
||||
usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil && nm.platformInterface.UsePlatformDefaultInterfaceMonitor()
|
||||
enforceInterfaceMonitor := options.AutoDetectInterface
|
||||
if !usePlatformDefaultInterfaceMonitor {
|
||||
networkMonitor, err := tun.NewNetworkUpdateMonitor(logger)
|
||||
@@ -503,7 +503,7 @@ func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interfa
|
||||
vpnStatus = "disabled"
|
||||
}
|
||||
options = append(options, "vpn "+vpnStatus)
|
||||
} else if r.platformInterface != nil {
|
||||
} else if r.platformInterface != nil && r.platformInterface.UsePlatformNetworkInterfaces() {
|
||||
networkInterface := common.Find(r.networkInterfaces.Load(), func(it adapter.NetworkInterface) bool {
|
||||
return it.Interface.Index == defaultInterface.Index
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user