sing-tun/monitor_windows.go
2025-03-19 20:37:02 +08:00

115 lines
2.6 KiB
Go

package tun
import (
"sync"
"github.com/sagernet/sing-tun/internal/winipcfg"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/common/x/list"
"golang.org/x/sys/windows"
)
type networkUpdateMonitor struct {
routeListener *winipcfg.RouteChangeCallback
interfaceListener *winipcfg.InterfaceChangeCallback
access sync.Mutex
callbacks list.List[NetworkUpdateCallback]
logger logger.Logger
}
func NewNetworkUpdateMonitor(logger logger.Logger) (NetworkUpdateMonitor, error) {
return &networkUpdateMonitor{
logger: logger,
}, nil
}
func (m *networkUpdateMonitor) Start() error {
routeListener, err := winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) {
m.emit()
})
if err != nil {
return err
}
m.routeListener = routeListener
interfaceListener, err := winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
m.emit()
})
if err != nil {
routeListener.Unregister()
return err
}
m.interfaceListener = interfaceListener
return nil
}
func (m *networkUpdateMonitor) Close() error {
if m.routeListener != nil {
m.routeListener.Unregister()
m.routeListener = nil
}
if m.interfaceListener != nil {
m.interfaceListener.Unregister()
m.interfaceListener = nil
}
return nil
}
func (m *defaultInterfaceMonitor) checkUpdate() error {
rows, err := winipcfg.GetIPForwardTable2(windows.AF_INET)
if err != nil {
return err
}
lowestMetric := ^uint32(0)
alias := ""
var index int
for _, row := range rows {
if row.DestinationPrefix.PrefixLength != 0 {
continue
}
ifrow, err := row.InterfaceLUID.Interface()
if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp {
continue
}
if ifrow.Type == winipcfg.IfTypePropVirtual || ifrow.Type == winipcfg.IfTypeSoftwareLoopback {
continue
}
iface, err := row.InterfaceLUID.IPInterface(windows.AF_INET)
if err != nil {
continue
}
if !iface.Connected {
continue
}
metric := row.Metric + iface.Metric
if metric < lowestMetric {
lowestMetric = metric
alias = ifrow.Alias()
index = int(ifrow.InterfaceIndex)
}
}
if alias == "" {
return ErrNoRoute
}
newInterface, err := m.interfaceFinder.ByIndex(index)
if err != nil {
return E.Cause(err, "find updated interface: ", alias)
}
oldInterface := m.defaultInterface.Swap(newInterface)
if oldInterface != nil && oldInterface.Equals(*newInterface) {
return nil
}
m.emit(newInterface, 0)
return nil
}