/* 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})
}