Ground-Zerro / Phobos Public
Code Issues Pull requests Actions Releases View on GitHub ↗
7.6 KB go
/* SPDX-License-Identifier: MIT
 *
 * Copyright (C) 2019-2026 WireGuard LLC. All Rights Reserved.
 */

package tunnel

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"runtime"
	"time"

	"golang.org/x/sys/windows"
	"golang.org/x/sys/windows/svc"
	"golang.org/x/sys/windows/svc/mgr"
	"golang.zx2c4.com/wireguard/windows/conf"
	"golang.zx2c4.com/wireguard/windows/driver"
	"golang.zx2c4.com/wireguard/windows/elevate"
	"golang.zx2c4.com/wireguard/windows/ringlogger"
	"golang.zx2c4.com/wireguard/windows/services"
	"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
	"golang.zx2c4.com/wireguard/windows/wintun"
)

type tunnelService struct {
	Path string
}

func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
	serviceState := svc.StartPending
	changes <- svc.Status{State: serviceState}

	var watcher *interfaceWatcher
	var adapter *driver.Adapter
	var socks5Adapter *wintun.Adapter
	var socks5 *socks5Tunnel
	var luid winipcfg.LUID
	var config *conf.Config
	var obfuscator *obfuscation
	var err error
	serviceError := services.ErrorSuccess

	defer func() {
		svcSpecificEC, exitCode = services.DetermineErrorCode(err, serviceError)
		logErr := services.CombineErrors(err, serviceError)
		if logErr != nil {
			log.Println(logErr)
		}
		serviceState = svc.StopPending
		changes <- svc.Status{State: serviceState}

		stopIt := make(chan bool, 1)
		go func() {
			t := time.NewTicker(time.Second * 30)
			for {
				select {
				case <-t.C:
					t.Stop()
					buf := make([]byte, 1024)
					for {
						n := runtime.Stack(buf, true)
						if n < len(buf) {
							buf = buf[:n]
							break
						}
						buf = make([]byte, 2*len(buf))
					}
					lines := bytes.Split(buf, []byte{'\n'})
					log.Println("Failed to shutdown after 30 seconds. Probably dead locked. Printing stack and killing.")
					for _, line := range lines {
						if len(bytes.TrimSpace(line)) > 0 {
							log.Println(string(line))
						}
					}
					os.Exit(777)
					return
				case <-stopIt:
					t.Stop()
					return
				}
			}
		}()

		adapterUp := adapter != nil || socks5Adapter != nil
		if logErr == nil && adapterUp && config != nil {
			logErr = runScriptCommand(config.Interface.PreDown, config.Name)
		}
		if watcher != nil {
			watcher.Destroy()
		}
		socks5.stop()
		if adapter != nil {
			adapter.Close()
		}
		if socks5Adapter != nil {
			socks5Adapter.Close()
		}
		obfuscator.stop()
		if logErr == nil && adapterUp && config != nil {
			_ = runScriptCommand(config.Interface.PostDown, config.Name)
		}
		stopIt <- true
		log.Println("Shutting down")
	}()

	var logFile string
	logFile, err = conf.LogFile(true)
	if err != nil {
		serviceError = services.ErrorRingloggerOpen
		return
	}
	err = ringlogger.InitGlobalLogger(logFile, "TUN")
	if err != nil {
		serviceError = services.ErrorRingloggerOpen
		return
	}

	config, err = conf.LoadFromPath(service.Path)
	if err != nil {
		serviceError = services.ErrorLoadConfiguration
		return
	}
	config.DeduplicateNetworkEntries()

	log.SetPrefix(fmt.Sprintf("[%s] ", config.Name))

	services.PrintStarting()

	if services.StartedAtBoot() {
		if m, err := mgr.Connect(); err == nil {
			if lockStatus, err := m.LockStatus(); err == nil && lockStatus.IsLocked {
				/* If we don't do this, then the driver installation will block forever, because
				 * installing a network adapter starts the driver service too. Apparently at boot time,
				 * Windows 8.1 locks the SCM for each service start, creating a deadlock if we don't
				 * announce that we're running before starting additional services.
				 */
				log.Printf("SCM locked for %v by %s, marking service as started", lockStatus.Age, lockStatus.Owner)
				serviceState = svc.Running
				changes <- svc.Status{State: serviceState}
			}
			m.Disconnect()
		}
	}

	evaluateStaticPitfalls()

	log.Println("Watching network interfaces")
	watcher, err = watchInterface()
	if err != nil {
		serviceError = services.ErrorSetNetConfig
		return
	}

	log.Println("Resolving DNS names")
	err = config.ResolveEndpoints()
	if err != nil {
		serviceError = services.ErrorDNSLookup
		return
	}

	log.Println("Creating network adapter")
	if config.IsSocks5() {
		socks5Adapter, err = createSocks5Adapter(config)
		if err != nil {
			serviceError = services.ErrorCreateNetworkAdapter
			return
		}
		luid = socks5Adapter.LUID()
	} else {
		for i := range 15 {
			if i > 0 {
				time.Sleep(time.Second)
				log.Printf("Retrying adapter creation after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err)
			}
			adapter, err = driver.CreateAdapter(config.Name, "Phobos", deterministicGUID(config))
			if err == nil || !services.StartedAtBoot() {
				break
			}
		}
		if err != nil {
			err = fmt.Errorf("Error creating adapter: %w", err)
			serviceError = services.ErrorCreateNetworkAdapter
			return
		}
		luid = adapter.LUID()
		driverVersion, err := driver.RunningVersion()
		if err != nil {
			log.Printf("Warning: unable to determine driver version: %v", err)
		} else {
			log.Printf("Using WireGuardNT/%d.%d", (driverVersion>>16)&0xffff, driverVersion&0xffff)
		}
		err = adapter.SetLogging(driver.AdapterLogOn)
		if err != nil {
			err = fmt.Errorf("Error enabling adapter logging: %w", err)
			serviceError = services.ErrorCreateNetworkAdapter
			return
		}
	}

	err = runScriptCommand(config.Interface.PreUp, config.Name)
	if err != nil {
		serviceError = services.ErrorRunScript
		return
	}

	err = enableFirewall(config, luid)
	if err != nil {
		serviceError = services.ErrorFirewall
		return
	}

	if config.IsSocks5() {
		socks5, err = startSocks5Tunnel(config, socks5Adapter, luid)
	} else {
		obfuscator, err = startObfuscation(config, luid)
	}
	if err != nil {
		err = fmt.Errorf("Error starting obfuscator: %w", err)
		serviceError = services.ErrorBindSocketsToDefaultRoutes
		return
	}

	log.Println("Dropping privileges")
	err = elevate.DropAllPrivileges(true)
	if err != nil {
		serviceError = services.ErrorDropPrivileges
		return
	}

	var configured tunnelAdapter = socks5
	if !config.IsSocks5() {
		log.Println("Setting interface configuration")
		err = adapter.SetConfiguration(config.ToDriverConfiguration())
		if err != nil {
			serviceError = services.ErrorDeviceSetConfig
			return
		}
		err = adapter.SetAdapterState(driver.AdapterStateUp)
		if err != nil {
			serviceError = services.ErrorDeviceBringUp
			return
		}
		configured = driverAdapter{adapter}
	}

	if config.IsSocks5() {
		err = socks5.watchDefaultRoutes(luid)
	} else {
		err = obfuscator.watchDefaultRoutes(luid)
	}
	if err != nil {
		serviceError = services.ErrorBindSocketsToDefaultRoutes
		return
	}

	watcher.Configure(configured, config, luid)

	err = runScriptCommand(config.Interface.PostUp, config.Name)
	if err != nil {
		serviceError = services.ErrorRunScript
		return
	}

	changes <- svc.Status{State: serviceState, Accepts: svc.AcceptStop | svc.AcceptShutdown}

	var started bool
	for {
		select {
		case c := <-r:
			switch c.Cmd {
			case svc.Stop, svc.Shutdown:
				return
			case svc.Interrogate:
				changes <- c.CurrentStatus
			default:
				log.Printf("Unexpected service control request #%d\n", c)
			}
		case <-watcher.started:
			if !started {
				serviceState = svc.Running
				changes <- svc.Status{State: serviceState, Accepts: svc.AcceptStop | svc.AcceptShutdown}
				log.Println("Startup complete")
				started = true
			}
		case e := <-watcher.errors:
			serviceError, err = e.serviceError, e.err
			return
		}
	}
}

func Run(confPath string) error {
	name, err := conf.NameFromPath(confPath)
	if err != nil {
		return err
	}
	serviceName, err := conf.ServiceNameOfTunnel(name)
	if err != nil {
		return err
	}
	return svc.Run(serviceName, &tunnelService{confPath})
}