package tunnel
import (
"errors"
"fmt"
"log"
"sync"
"time"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/conf"
"golang.zx2c4.com/wireguard/windows/driver"
"golang.zx2c4.com/wireguard/windows/services"
"golang.zx2c4.com/wireguard/windows/tunnel/firewall"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)
type interfaceWatcherError struct {
serviceError services.Error
err error
}
type interfaceWatcherEvent struct {
luid winipcfg.LUID
family winipcfg.AddressFamily
}
type interfaceWatcher struct {
errors chan interfaceWatcherError
started chan winipcfg.AddressFamily
conf *conf.Config
adapter *driver.Adapter
luid winipcfg.LUID
setupMutex sync.Mutex
interfaceChangeCallback winipcfg.ChangeCallback
changeCallbacks4 []winipcfg.ChangeCallback
changeCallbacks6 []winipcfg.ChangeCallback
storedEvents []interfaceWatcherEvent
watchdog *time.Timer
}
func (iw *interfaceWatcher) setup(family winipcfg.AddressFamily) {
iw.watchdog.Stop()
var changeCallbacks *[]winipcfg.ChangeCallback
var ipversion string
if family == windows.AF_INET {
changeCallbacks = &iw.changeCallbacks4
ipversion = "v4"
} else if family == windows.AF_INET6 {
changeCallbacks = &iw.changeCallbacks6
ipversion = "v6"
} else {
return
}
if len(*changeCallbacks) != 0 {
for _, cb := range *changeCallbacks {
cb.Unregister()
}
*changeCallbacks = nil
}
var err error
if iw.conf.Interface.MTU == 0 {
log.Printf("Monitoring MTU of default %s routes", ipversion)
*changeCallbacks, err = monitorMTU(family, iw.luid)
if err != nil {
iw.errors <- interfaceWatcherError{services.ErrorMonitorMTUChanges, err}
return
}
}
log.Printf("Setting device %s addresses", ipversion)
err = configureInterface(family, iw.conf, iw.luid)
if err != nil {
iw.errors <- interfaceWatcherError{services.ErrorSetNetConfig, err}
return
}
evaluateDynamicPitfalls(family, iw.conf, iw.luid)
iw.started <- family
}
func watchInterface() (*interfaceWatcher, error) {
iw := &interfaceWatcher{
errors: make(chan interfaceWatcherError, 2),
started: make(chan winipcfg.AddressFamily, 4),
}
iw.watchdog = time.AfterFunc(time.Duration(1<<63-1), func() {
iw.errors <- interfaceWatcherError{services.ErrorCreateNetworkAdapter, errors.New("TCP/IP interface for adapter did not appear after one minute")}
})
iw.watchdog.Stop()
var err error
iw.interfaceChangeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
iw.setupMutex.Lock()
defer iw.setupMutex.Unlock()
if notificationType != winipcfg.MibAddInstance {
return
}
if iw.luid == 0 {
iw.storedEvents = append(iw.storedEvents, interfaceWatcherEvent{iface.InterfaceLUID, iface.Family})
return
}
if iface.InterfaceLUID != iw.luid {
return
}
iw.setup(iface.Family)
if state, err := iw.adapter.AdapterState(); err == nil && state == driver.AdapterStateDown {
log.Println("Reinitializing adapter configuration")
err = iw.adapter.SetConfiguration(iw.conf.ToDriverConfiguration())
if err != nil {
log.Println(fmt.Errorf("%v: %w", services.ErrorDeviceSetConfig, err))
}
err = iw.adapter.SetAdapterState(driver.AdapterStateUp)
if err != nil {
log.Println(fmt.Errorf("%v: %w", services.ErrorDeviceBringUp, err))
}
}
})
if err != nil {
return nil, fmt.Errorf("unable to register interface change callback: %w", err)
}
return iw, nil
}
func (iw *interfaceWatcher) Configure(adapter *driver.Adapter, conf *conf.Config, luid winipcfg.LUID) {
iw.setupMutex.Lock()
defer iw.setupMutex.Unlock()
iw.watchdog.Reset(time.Minute)
iw.adapter, iw.conf, iw.luid = adapter, conf, luid
for _, event := range iw.storedEvents {
if event.luid == luid {
iw.setup(event.family)
}
}
iw.storedEvents = nil
}
func (iw *interfaceWatcher) Destroy() {
iw.setupMutex.Lock()
iw.watchdog.Stop()
changeCallbacks4 := iw.changeCallbacks4
changeCallbacks6 := iw.changeCallbacks6
interfaceChangeCallback := iw.interfaceChangeCallback
luid := iw.luid
iw.setupMutex.Unlock()
if interfaceChangeCallback != nil {
interfaceChangeCallback.Unregister()
}
for _, cb := range changeCallbacks4 {
cb.Unregister()
}
for _, cb := range changeCallbacks6 {
cb.Unregister()
}
iw.setupMutex.Lock()
if interfaceChangeCallback == iw.interfaceChangeCallback {
iw.interfaceChangeCallback = nil
}
for len(changeCallbacks4) > 0 && len(iw.changeCallbacks4) > 0 {
iw.changeCallbacks4 = iw.changeCallbacks4[1:]
changeCallbacks4 = changeCallbacks4[1:]
}
for len(changeCallbacks6) > 0 && len(iw.changeCallbacks6) > 0 {
iw.changeCallbacks6 = iw.changeCallbacks6[1:]
changeCallbacks6 = changeCallbacks6[1:]
}
firewall.DisableFirewall()
if luid != 0 && iw.luid == luid {
// It seems that the Windows networking stack doesn't like it when we destroy interfaces that have active
// routes, so to be certain, just remove everything before destroying.
luid.FlushRoutes(windows.AF_INET)
luid.FlushIPAddresses(windows.AF_INET)
luid.FlushDNS(windows.AF_INET)
luid.FlushRoutes(windows.AF_INET6)
luid.FlushIPAddresses(windows.AF_INET6)
luid.FlushDNS(windows.AF_INET6)
}
iw.setupMutex.Unlock()
}
这段Go代码实现了一个网络接口的监视器 interfaceWatcher
,用于监控和管理WireGuard网络适配器在Windows系统中的配置和状态变更。它能够监听网络接口的变化,处理配置的更新,并且在适配器状态改变时进行适当的调整。以下是对这段代码的详细分析。
主要结构体和类型
-
interfaceWatcherError
:- 包含两个字段:
serviceError
:表示服务相关的错误类型。err
:具体的错误信息。
- 包含两个字段:
-
interfaceWatcherEvent
:- 包含两个字段:
luid
:表示接口的唯一标识符(LUID)。family
:表示地址族(IPv4 或 IPv6)。
- 包含两个字段:
-
interfaceWatcher
:- 主要的监视器结构体,包含如下字段:
errors
:用于传递错误的通道。started
:用于标记启动的通道。conf
:存储接口的配置信息。adapter
:表示WireGuard驱动的适配器。luid
:接口的LUID。setupMutex
:互斥锁,确保配置操作的线程安全。interfaceChangeCallback
:接口变化的回调函数。changeCallbacks4
和changeCallbacks6
:分别存储IPv4和IPv6的变更回调。storedEvents
:存储发生的接口变更事件,等待处理。watchdog
:用于超时检测的定时器。
- 主要的监视器结构体,包含如下字段:
主要函数
-
setup
:- 负责初始化或重新配置指定地址族(IPv4或IPv6)的网络接口。
- 停止
watchdog
定时器,防止超时。 - 清除并重新注册变更回调。
- 设置MTU(最大传输单元)和接口的IP地址。
- 评估动态配置的潜在问题(例如动态IP分配)。
-
watchInterface
:- 创建并初始化一个
interfaceWatcher
实例。 - 注册接口变更的回调函数
interfaceChangeCallback
。 - 在适配器状态发生变化时,调用
setup
函数进行配置。 - 如果注册回调失败,则返回错误信息。
- 创建并初始化一个
-
Configure
:- 配置接口监视器,更新监视器的适配器、配置和LUID。
- 在设置完这些值后,会重新触发之前存储的事件并进行处理。
- 重置
watchdog
定时器为1分钟,用于监测配置超时。
-
Destroy
:- 销毁
interfaceWatcher
实例,清理相关的回调和状态。 - 停止
watchdog
定时器。 - 注销所有的变更回调函数。
- 清理防火墙配置。
- 删除接口上的所有路由、IP地址和DNS设置,确保接口被安全地销毁。
- 销毁
代码逻辑分析
-
事件驱动的配置管理:
interfaceWatcher
通过监听网络接口的变化来动态配置和调整WireGuard适配器。回调函数interfaceChangeCallback
是事件驱动的核心,它捕捉到接口的添加事件(MibAddInstance
),然后触发相关的配置操作。 -
线程安全:使用
sync.Mutex
进行锁定,确保setup
、Configure
和Destroy
函数在多线程环境中不会发生竞态条件。 -
错误处理和监控:通过
errors
通道传递错误,watchdog
定时器监控接口配置的超时情况。如果超时,watchdog
会触发一个错误事件。 -
资源清理:
Destroy
函数非常注意资源的清理,尤其是在处理Windows网络堆栈时,确保所有的路由、IP地址和DNS设置被移除,以避免潜在的问题。
代码的应用场景
这段代码主要应用于管理和维护WireGuard的网络适配器配置,确保适配器在各种网络变化(如接口添加、状态变更)时能够自动适应,并且在不需要时可以安全地销毁和清理。它在需要高可靠性和自动化网络配置管理的环境中非常有用,如在Windows系统上运行WireGuard的VPN服务。