wireguard源码分析(十二)

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"
)

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 luid winipcfg.LUID
	var config *conf.Config
	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
				}
			}
		}()

		if logErr == nil && adapter != nil && config != nil {
			logErr = runScriptCommand(config.Interface.PreDown, config.Name)
		}
		if watcher != nil {
			watcher.Destroy()
		}
		if adapter != nil {
			adapter.Close()
		}
		if logErr == nil && adapter != nil && 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")
	for i := 0; i < 15; i++ {
		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, "WireGuard", 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
	}

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

	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
	}
	watcher.Configure(adapter, 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})
}

这段Go代码实现了一个在Windows服务环境中运行的隧道服务,主要用于管理和配置WireGuard隧道。它处理了服务的启动、配置、网络适配器的创建、权限的降低以及服务的停止和监控等多个方面。以下是对代码的详细分析:

代码结构与功能

1. tunnelService 结构体
type tunnelService struct {
	Path string
}
  • tunnelService 结构体只有一个字段 Path,用于存储配置文件的路径。这个结构体实现了 svc.Handler 接口,用于响应Windows服务控制管理器(SCM)的请求。
2. Execute 方法
func (service *tunnelService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
  • 这是 tunnelService 结构体的核心方法,用于响应服务控制管理器(SCM)的请求,处理服务的启动和停止逻辑。
初始化与服务状态报告
  • 服务启动时,首先将状态设置为 StartPending,并通过 changes 通道报告状态给SCM。
  • 初始化了一些关键变量,例如 watcher(网络接口监视器)、adapter(网络适配器)、luid(本地接口标识符)和 config(隧道配置)。
延迟处理函数
  • defer 语句在函数结束时执行,用于清理资源和处理可能的错误。这包括:
    • 确保服务在退出时进入 StopPending 状态。
    • 检测可能的死锁情况,打印堆栈跟踪并终止程序。
    • 在适配器关闭之前,运行配置中的 PreDownPostDown 脚本命令。
日志初始化
  • 使用 ringlogger.InitGlobalLogger 初始化全局日志记录器,用于记录隧道服务的所有日志输出。
加载配置并处理启动逻辑
  • 加载配置文件,处理网络接口配置和DNS解析。
  • 创建网络适配器,并在系统刚启动时处理适配器创建可能失败的情况。
  • 在适配器创建成功后,设置适配器的配置并将其状态设置为“启动”。
降低权限
  • 通过 elevate.DropAllPrivileges(true) 减少服务的权限,以降低潜在的安全风险。
运行脚本和配置防火墙
  • 在适配器启动前后分别运行 PreUpPostUp 脚本,以确保配置的生效。
  • 配置防火墙规则,确保隧道流量的安全。
服务状态更新与监控
  • 服务进入运行状态 svc.Running 后,通过 changes 通道通知SCM,接受停止和关机请求。
  • 使用 watcher 监控网络接口的状态变化,并根据变化调整服务状态。
3. Run 函数
func Run(confPath string) error {
  • Run 函数用于启动 tunnelService 服务。它根据配置文件路径生成服务名称,并调用 svc.Run 来启动服务。

总结与分析

关键功能与设计理念
  1. 服务管理: 代码实现了一个典型的Windows服务,通过与SCM交互,管理服务的启动、停止和运行状态。这使得隧道服务能够在Windows系统启动时自动运行,并在系统关机时正常停止。

  2. 网络适配器管理: 代码负责创建和配置WireGuard网络适配器,包括处理适配器的日志记录、配置设置和状态管理等。这部分确保了隧道服务能够正确地在网络适配器上执行,并保证流量的正确路由。

  3. 权限管理: 在服务启动后,代码会降低服务的权限,从而减少安全风险。权限降低是在完成所有需要高权限操作之后进行的,这是一个安全的设计。

  4. 错误处理与日志记录: 代码在多个关键点使用了错误处理,并通过全局日志记录器记录错误信息。这使得服务在运行过程中更加稳健,便于调试和问题诊断。

  5. 脚本执行: 在服务启动和关闭过程中,代码会执行配置中的脚本(例如 PreUpPostUp 等),这些脚本用于执行额外的配置操作,如配置路由或防火墙规则。

  6. 安全考虑: 代码通过降低权限、监控服务状态、防止死锁等措施来增强服务的安全性和可靠性。

总体而言,这段代码设计严谨,功能齐全,适合在Windows环境中作为系统服务运行,提供安全稳定的WireGuard隧道服务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值