2026-01-07 23:05:08 +01:00
//go:build windows
package tun
import (
2026-09-28 08:15:40 +03:30
"bytes"
2026-09-08 10:51:35 +08:00
"context"
2026-03-19 18:18:07 +08:00
"crypto/md5"
2026-04-13 21:38:10 +08:00
"encoding/binary"
go_errors "errors"
"net"
"net/netip"
2026-09-28 08:15:40 +03:30
"os/exec"
"path/filepath"
"slices"
"strconv"
"strings"
2026-04-13 21:38:10 +08:00
"sync"
2026-09-28 08:15:40 +03:30
"syscall"
2026-09-08 10:51:35 +08:00
"time"
2026-03-19 18:18:07 +08:00
"unsafe"
2026-01-25 19:19:05 +01:00
2026-04-13 21:38:10 +08:00
"github.com/xtls/xray-core/common/errors"
2026-09-28 08:15:40 +03:30
"github.com/xtls/xray-core/transport/internet"
2026-01-07 23:05:08 +01:00
"golang.org/x/sys/windows"
"golang.zx2c4.com/wintun"
2026-04-13 21:38:10 +08:00
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
2026-01-25 19:19:05 +01:00
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
2026-01-07 23:05:08 +01:00
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
2026-01-25 19:19:05 +01:00
//go:linkname procyield runtime.procyield
func procyield ( cycles uint32 )
2026-01-07 23:05:08 +01:00
// WindowsTun is an object that handles tun network interface on Windows
// current version is heavily stripped to do nothing more,
// then create a network interface, to be provided as endpoint to gVisor ip stack
type WindowsTun struct {
2026-04-13 21:38:10 +08:00
sync . RWMutex
2026-09-08 10:51:35 +08:00
options * Config
adapter * wintun . Adapter
session wintun . Session
readWait windows . Handle
luid winipcfg . LUID
cbr winipcfg . ChangeCallback
cbi winipcfg . ChangeCallback
2026-09-28 08:15:40 +03:30
wfp windows . Handle
resolver * savedResolver
skipStop chan struct {}
skipDone chan struct {}
2026-09-08 10:51:35 +08:00
closed bool
2026-01-07 23:05:08 +01:00
}
// WindowsTun implements Tun
var _ Tun = ( * WindowsTun )( nil )
2026-01-25 19:19:05 +01:00
// WindowsTun implements GVisorDevice
var _ GVisorDevice = ( * WindowsTun )( nil )
2026-01-07 23:05:08 +01:00
// NewTun creates a Wintun interface with the given name. Should a Wintun
// interface with the same name exist, it tried to be reused.
2026-04-13 21:38:10 +08:00
func NewTun ( options * Config ) ( Tun , error ) {
2026-01-07 23:05:08 +01:00
// instantiate wintun adapter
2026-07-18 06:21:33 +08:00
adapter , err := open ( options . Name , options . Desc )
2026-01-07 23:05:08 +01:00
if err != nil {
return nil , err
}
// start the interface with ring buffer capacity of 8 MiB
session , err := adapter . StartSession ( 0x800000 )
if err != nil {
_ = adapter . Close ()
return nil , err
}
tun := & WindowsTun {
2026-01-25 19:19:05 +01:00
options : options ,
adapter : adapter ,
session : session ,
readWait : session . ReadWaitEvent (),
2026-04-13 21:38:10 +08:00
luid : winipcfg . LUID ( adapter . LUID ()),
2026-01-07 23:05:08 +01:00
}
return tun , nil
}
2026-07-18 06:21:33 +08:00
func open ( name , desc string ) ( * wintun . Adapter , error ) {
2026-03-19 18:18:07 +08:00
// generate a deterministic GUID from the adapter name
id := md5 . Sum ([] byte ( name ))
guid := ( * windows . GUID )( unsafe . Pointer ( & id [ 0 ]))
2026-09-27 23:39:17 +03:00
// try to open existing adapter by name
adapter , err := wintun . OpenAdapter ( name )
if err == nil {
return adapter , nil
}
2026-01-07 23:05:08 +01:00
// try to create adapter anew
2026-09-27 23:39:17 +03:00
adapter , err = wintun . CreateAdapter ( name , desc , guid )
2026-01-07 23:05:08 +01:00
if err == nil {
return adapter , nil
}
return nil , err
}
2026-09-08 10:51:35 +08:00
func ( t * WindowsTun ) Start () ( err error ) {
var address4 , address6 bool
addresses := make ([] netip . Prefix , 0 , len ( t . options . Gateway ))
for _ , cidr := range t . options . Gateway {
prefix := netip . MustParsePrefix ( cidr )
if prefix . Addr (). Is4 () {
address4 = true
} else {
address6 = true
}
addresses = append ( addresses , prefix )
2026-04-13 21:38:10 +08:00
}
2026-09-08 10:51:35 +08:00
dns := make ([] netip . Addr , 0 , len ( t . options . DNS ))
for _ , ip := range t . options . DNS {
dns = append ( dns , netip . MustParseAddr ( ip ))
}
var route4 , route6 bool
2026-04-13 21:38:10 +08:00
routesMap := make ( map [ winipcfg . RouteData ] struct {})
2026-09-08 10:51:35 +08:00
for _ , cidr := range t . options . AutoSystemRoutingTable {
prefix := netip . MustParsePrefix ( cidr )
2026-04-13 21:38:10 +08:00
route := winipcfg . RouteData {
2026-09-08 10:51:35 +08:00
Destination : prefix . Masked (),
2026-04-13 21:38:10 +08:00
Metric : 0 ,
}
2026-09-08 10:51:35 +08:00
if prefix . Addr (). Is4 () {
route4 = true
2026-04-13 21:38:10 +08:00
route . NextHop = netip . IPv4Unspecified ()
} else {
2026-09-08 10:51:35 +08:00
route6 = true
2026-04-13 21:38:10 +08:00
route . NextHop = netip . IPv6Unspecified ()
}
routesMap [ route ] = struct {}{}
}
routesData := make ([] * winipcfg . RouteData , 0 , len ( routesMap ))
for route := range routesMap {
r := route
routesData = append ( routesData , & r )
}
2026-09-08 10:51:35 +08:00
var retryTimes int
var firstErr error
startOver :
if retryTimes > 0 {
if retryTimes > 15 {
return windows . ERROR_NOT_FOUND
2026-04-13 21:38:10 +08:00
}
2026-09-08 10:51:35 +08:00
errors . LogErrorInner ( context . Background (), firstErr , "Interface configuration failed, retrying attempt " , retryTimes , "/15" )
time . Sleep ( time . Second )
2026-04-13 21:38:10 +08:00
}
2026-09-08 10:51:35 +08:00
retryTimes ++
for _ , family := range [] winipcfg . AddressFamily { windows . AF_INET , windows . AF_INET6 } {
if family == windows . AF_INET && route4 || family == windows . AF_INET6 && route6 {
err = t . luid . SetRoutesForFamily ( family , routesData )
if err != nil {
firstErr = errors . New ( "unable to set routes" ). Base ( err )
if err == windows . ERROR_NOT_FOUND {
goto startOver
}
return firstErr
}
2026-04-13 21:38:10 +08:00
}
2026-09-08 10:51:35 +08:00
if family == windows . AF_INET && address4 || family == windows . AF_INET6 && address6 {
err = t . luid . SetIPAddressesForFamily ( family , addresses )
if err != nil {
firstErr = errors . New ( "unable to set ips" ). Base ( err )
if err == windows . ERROR_NOT_FOUND {
goto startOver
}
return firstErr
}
2026-04-13 21:38:10 +08:00
}
2026-09-08 10:51:35 +08:00
ipif , err := t . luid . IPInterface ( family )
2026-04-13 21:38:10 +08:00
if err != nil {
return err
}
ipif . RouterDiscoveryBehavior = winipcfg . RouterDiscoveryDisabled
ipif . DadTransmits = 0
ipif . ManagedAddressConfigurationSupported = false
ipif . OtherStatefulConfigurationSupported = false
2026-09-08 10:51:35 +08:00
if family == windows . AF_INET && ( address4 || route4 ) || family == windows . AF_INET6 && ( address6 || route6 ) {
ipif . NLMTU = t . options . MTU
2026-04-13 21:38:10 +08:00
}
2026-09-08 10:51:35 +08:00
if family == windows . AF_INET && route4 || family == windows . AF_INET6 && route6 {
ipif . UseAutomaticMetric = false
ipif . Metric = 0
2026-04-13 21:38:10 +08:00
}
2026-09-08 10:51:35 +08:00
err = ipif . Set ()
2026-04-13 21:38:10 +08:00
if err != nil {
2026-09-08 10:51:35 +08:00
firstErr = errors . New ( "unable to set metric and MTU" ). Base ( err )
if err == windows . ERROR_NOT_FOUND {
goto startOver
}
return firstErr
2026-04-13 21:38:10 +08:00
}
2026-09-08 10:51:35 +08:00
err = t . luid . SetDNS ( family , dns , nil )
2026-04-13 21:38:10 +08:00
if err != nil {
2026-09-08 10:51:35 +08:00
firstErr = errors . New ( "unable to set DNS" ). Base ( err )
if err == windows . ERROR_NOT_FOUND {
goto startOver
}
return firstErr
2026-04-13 21:38:10 +08:00
}
}
2026-09-28 08:15:40 +03:30
// Windows lists the TUN's DNS servers among the system's ones, which Go's
// resolver queries for Xray's own lookups past the TUN, where they lead
// nowhere or back into Xray. Not skipped are those another interface uses
// as well, as that could leave no server at all. As those can change at
// any time, they are looked at again as often as Go rereads its servers.
if len ( dns ) > 0 {
skipped , err := tunOnlyDNS ( t . luid , dns )
if err != nil {
skipped = dns
}
internet . SkipDNSServers ( skipped )
t . skipStop , t . skipDone = make ( chan struct {}), make ( chan struct {})
go func () {
defer close ( t . skipDone )
ticker := time . NewTicker ( 5 * time . Second )
defer ticker . Stop ()
for {
select {
case <- ticker . C :
if skipped , err := tunOnlyDNS ( t . luid , dns ); err == nil {
internet . SkipDNSServers ( skipped )
}
case <- t . skipStop :
return
}
}
}()
}
// Keep Windows from registering the TUN's addresses, and the host name
// with them, through dynamic DNS updates. Best effort.
if address4 || address6 {
if err := disableDNSRegistration ( t . luid , dns ); err != nil {
errors . LogDebugInner ( context . Background (), err , "[tun] unable to disable DNS registration" )
}
}
2026-09-30 02:31:00 +03:30
// With autoSystemWfpBlockLeak, once the system routes lead to the TUN,
// keep DNS if dns is set, and IPv6 if the TUN cannot carry it (no IPv6
// address, or no IPv6 route to it), from leaving through the other
// interfaces.
if blockDNS , blockIPv6 := len ( dns ) > 0 , ! address6 || ! route6 ; t . options . AutoSystemWfpBlockLeak && ( route4 || route6 ) && ( blockDNS || blockIPv6 ) {
2026-09-28 08:15:40 +03:30
if t . wfp , err = blockLeaks ( t . luid , blockDNS , blockIPv6 ); err != nil {
what := "DNS and IPv6"
if ! blockIPv6 {
what = "DNS"
} else if ! blockDNS {
what = "IPv6"
}
// Rather no TUN than a leaking one. Before Windows 10 the filters are
// untested, and sing-box's broke its TUN there (SagerNet/sing-box#3659),
// so older versions only get a warning.
if major , _ , _ := windows . RtlGetNtVersionNumbers (); major >= 10 {
2026-09-30 02:31:00 +03:30
return errors . New ( "unable to block " , what , " outside the TUN (set autoSystemWfpBlockLeak to false to run without)" ). Base ( err )
2026-09-28 08:15:40 +03:30
}
errors . LogWarningInner ( context . Background (), err , "[tun] unable to block " , what , " outside the TUN, leaks are possible" )
} else {
errors . LogInfo ( context . Background (), "[tun] outside the TUN, blocked DNS: " , blockDNS , ", blocked IPv6: " , blockIPv6 )
if blockDNS {
covered := slices . Clone ( addresses )
for _ , route := range routesData {
covered = append ( covered , route . Destination )
}
for _ , server := range dnsOutsideTUN ( dns , covered ) {
errors . LogWarning ( context . Background (), "[tun] DNS server " , server , " is in neither gateway nor autoSystemRoutingTable, so queries to it cannot go through the TUN and are blocked" )
}
// With updater, the dialer controllers bind Xray's own sockets
// to the physical interface.
if updater != nil {
t . resolver = resolveOnOwn ()
}
}
}
}
if len ( dns ) > 0 || route4 || route6 {
if err := flushDNSCache (); err != nil {
errors . LogInfoInner ( context . Background (), err , "[tun] unable to flush DNS cache" )
}
}
2026-04-13 21:38:10 +08:00
if updater != nil {
2026-09-28 08:15:40 +03:30
// Only a registered callback goes into the fields: a nil pointer in
// them would not compare equal to nil in Close.
cbr , err := winipcfg . RegisterRouteChangeCallback ( func ( notificationType winipcfg . MibNotificationType , route * winipcfg . MibIPforwardRow2 ) {
2026-09-08 10:51:35 +08:00
updater . Update ()
})
if err != nil {
return err
}
2026-09-28 08:15:40 +03:30
t . cbr = cbr
cbi , err := winipcfg . RegisterInterfaceChangeCallback ( func ( notificationType winipcfg . MibNotificationType , iface * winipcfg . MibIPInterfaceRow ) {
2026-04-13 21:38:10 +08:00
updater . Update ()
})
if err != nil {
return err
}
2026-09-28 08:15:40 +03:30
t . cbi = cbi
2026-04-13 21:38:10 +08:00
}
2026-01-07 23:05:08 +01:00
return nil
}
func ( t * WindowsTun ) Close () error {
2026-04-13 21:38:10 +08:00
t . Lock ()
defer t . Unlock ()
if t . closed {
return nil
}
t . closed = true
2026-09-08 10:51:35 +08:00
if t . cbr != nil {
t . cbr . Unregister ()
}
if t . cbi != nil {
t . cbi . Unregister ()
}
if t . luid != 0 {
t . luid . FlushRoutes ( windows . AF_INET )
t . luid . FlushIPAddresses ( windows . AF_INET )
t . luid . FlushDNS ( windows . AF_INET )
t . luid . FlushRoutes ( windows . AF_INET6 )
t . luid . FlushIPAddresses ( windows . AF_INET6 )
t . luid . FlushDNS ( windows . AF_INET6 )
}
2026-09-28 08:15:40 +03:30
if t . wfp != 0 {
closeWFPEngine ( t . wfp )
}
if t . resolver != nil {
t . resolver . restore ()
}
if t . skipStop != nil {
close ( t . skipStop )
<- t . skipDone
}
internet . SkipDNSServers ( nil )
if len ( t . options . DNS ) > 0 || len ( t . options . AutoSystemRoutingTable ) > 0 {
flushDNSCache ()
}
2026-09-08 10:51:35 +08:00
if t . session != ( wintun . Session {}) {
t . session . End ()
}
if t . adapter != nil {
t . adapter . Close ()
2026-04-13 21:38:10 +08:00
}
2026-01-07 23:05:08 +01:00
return nil
}
2026-09-28 08:15:40 +03:30
type savedResolver struct {
preferGo bool
dial func ( ctx context . Context , network , address string ) ( net . Conn , error )
}
// resolveOnOwn has Go resolve the names Xray would otherwise ask Windows for,
// on Xray's own sockets, which the dialer controllers bind to the physical
// interface, and skipping the TUN's DNS servers, as localdns does. Windows'
// resolver runs in the DNS Client service, whose queries the DNS filter lets
// through the TUN only, so Xray's own lookups, like of an outbound's server
// domain, would go into Xray again and could end up waiting on themselves.
//
// It changes net.DefaultResolver for the whole process, which covers every
// lookup that would reach Windows' resolver; restore undoes it.
func resolveOnOwn () * savedResolver {
saved := & savedResolver { net . DefaultResolver . PreferGo , net . DefaultResolver . Dial }
dialer := & net . Dialer { Control : func ( network , address string , c syscall . RawConn ) error {
for _ , ctl := range internet . Controllers {
if err := ctl ( network , address , c ); err != nil {
return err
}
}
return nil
}}
// Go's resolver moves on to the next server right away when a dial fails.
net . DefaultResolver . Dial = func ( ctx context . Context , network , address string ) ( net . Conn , error ) {
if internet . IsSkippedDNSServer ( address ) {
return nil , errors . New ( "skipped DNS server " , address )
}
return dialer . DialContext ( ctx , network , address )
}
net . DefaultResolver . PreferGo = true
return saved
}
func ( s * savedResolver ) restore () {
net . DefaultResolver . PreferGo = s . preferGo
net . DefaultResolver . Dial = s . dial
}
// tunOnlyDNS returns those of servers, the TUN's DNS servers, that Go's
// resolver does not also get from another interface: one that is up and has
// a gateway, as it reads them.
func tunOnlyDNS ( tun winipcfg . LUID , servers [] netip . Addr ) ([] netip . Addr , error ) {
adapters , err := winipcfg . GetAdaptersAddresses ( windows . AF_UNSPEC , winipcfg . GAAFlagIncludeGateways )
if err != nil {
return nil , err
}
var others [] netip . Addr
for _ , adapter := range adapters {
if adapter . LUID == tun || adapter . OperStatus != winipcfg . IfOperStatusUp || adapter . FirstGatewayAddress == nil {
continue
}
for server := adapter . FirstDNSServerAddress ; server != nil ; server = server . Next {
if addr , ok := netip . AddrFromSlice ( server . Address . IP ()); ok {
others = append ( others , addr . Unmap ())
}
}
}
return slices . DeleteFunc ( slices . Clone ( servers ), func ( server netip . Addr ) bool {
return slices . Contains ( others , server . Unmap ())
}), nil
}
// disableDNSRegistration turns off the dynamic DNS registration of the
// interface's addresses. dns are its DNS servers.
func disableDNSRegistration ( luid winipcfg . LUID , dns [] netip . Addr ) error {
guid , err := luid . GUID ()
if err != nil {
return err
}
err = winipcfg . SetInterfaceDnsSettings ( * guid , & winipcfg . DnsInterfaceSettings {
Version : winipcfg . DnsInterfaceSettingsVersion1 ,
Flags : winipcfg . DnsInterfaceSettingsFlagRegistrationEnabled ,
})
if err == nil || ! go_errors . Is ( err , windows . ERROR_PROC_NOT_FOUND ) {
return err
}
return disableDNSRegistrationByNetsh ( luid , dns )
}
// disableDNSRegistrationByNetsh does it for Windows before 10 1809, which
// lacks SetInterfaceDnsSettings. The setting is the interface's, not the
// address family's, but netsh only applies it along with a DNS server, which
// replaces the IPv4 ones, so they are set again afterwards.
func disableDNSRegistrationByNetsh ( luid winipcfg . LUID , dns [] netip . Addr ) error {
row , err := luid . Interface ()
if err != nil {
return err
}
server := "127.0.0.1" // any will do when there is no IPv4 one
if i := slices . IndexFunc ( dns , netip . Addr . Is4 ); i >= 0 {
server = dns [ i ]. String ()
}
err = runNetsh ( "interface" , "ipv4" , "set" , "dnsservers" , "name=" + strconv . FormatUint ( uint64 ( row . InterfaceIndex ), 10 ), "source=static" , "address=" + server , "register=none" , "validate=no" )
return errors . Combine ( err , luid . SetDNS ( windows . AF_INET , dns , nil ))
}
// runNetsh runs netsh.exe from the system directory. netsh reports some
// failures, like a syntax error, only in its output, even with exit code 0,
// so any output counts as a failure.
func runNetsh ( args ... string ) error {
system32 , err := windows . GetSystemDirectory ()
if err != nil {
return err
}
cmd := exec . Command ( filepath . Join ( system32 , "netsh.exe" ), args ... )
cmd . SysProcAttr = & syscall . SysProcAttr { HideWindow : true }
output , err := cmd . CombinedOutput ()
if output = bytes . TrimSpace ( output ); err != nil || len ( output ) > 0 {
return errors . New ( "netsh " , strings . Join ( args , " " ), ": " , string ( output )). Base ( err )
}
return nil
}
2026-04-13 21:38:10 +08:00
func ( t * WindowsTun ) Name () ( string , error ) {
row , err := t . luid . Interface ()
if err != nil {
return "" , err
}
return row . Alias (), nil
}
func ( t * WindowsTun ) Index () ( int , error ) {
row , err := t . luid . Interface ()
if err != nil {
return 0 , err
}
return int ( row . InterfaceIndex ), nil
}
2026-01-25 19:19:05 +01:00
// WritePacket implements GVisorDevice method to write one packet to the tun device
func ( t * WindowsTun ) WritePacket ( packetBuffer * stack . PacketBuffer ) tcpip . Error {
2026-04-13 21:38:10 +08:00
t . RLock ()
defer t . RUnlock ()
if t . closed {
return & tcpip . ErrClosedForSend {}
}
2026-01-25 19:19:05 +01:00
// request buffer from Wintun
packet , err := t . session . AllocateSendPacket ( packetBuffer . Size ())
if err != nil {
return & tcpip . ErrAborted {}
}
// copy the bytes of slices that compose the packet into the allocated buffer
var index int
for _ , packetElement := range packetBuffer . AsSlices () {
index += copy ( packet [ index :], packetElement )
}
// signal Wintun to send that buffer as the packet
t . session . SendPacket ( packet )
return nil
}
// ReadPacket implements GVisorDevice method to read one packet from the tun device
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
// which will make the stack call Wait which should implement desired push-back
func ( t * WindowsTun ) ReadPacket () ( byte , * stack . PacketBuffer , error ) {
packet , err := t . session . ReceivePacket ()
2026-04-13 21:38:10 +08:00
if go_errors . Is ( err , windows . ERROR_NO_MORE_ITEMS ) {
2026-01-25 19:19:05 +01:00
return 0 , nil , ErrQueueEmpty
}
if err != nil {
return 0 , nil , err
}
version := packet [ 0 ] >> 4
packetBuffer := buffer . MakeWithView ( buffer . NewViewWithData ( packet ))
return version , stack . NewPacketBuffer ( stack . PacketBufferOptions {
Payload : packetBuffer ,
IsForwardedPacket : true ,
OnRelease : func () {
t . session . ReleaseReceivePacket ( packet )
},
}), nil
}
func ( t * WindowsTun ) Wait () {
procyield ( 1 )
_ , _ = windows . WaitForSingleObject ( t . readWait , windows . INFINITE )
}
2026-01-07 23:05:08 +01:00
func ( t * WindowsTun ) newEndpoint () ( stack . LinkEndpoint , error ) {
2026-04-15 15:40:19 +03:00
return & LinkEndpoint { deviceMTU : t . options . MTU , device : t }, nil
2026-04-13 21:38:10 +08:00
}
const (
IP_UNICAST_IF = 31
IPV6_UNICAST_IF = 31
)
func setinterface ( network , address string , fd uintptr , iface * net . Interface ) error {
var index [ 4 ] byte
binary . BigEndian . PutUint32 ( index [:], uint32 ( iface . Index ))
2026-04-15 15:40:19 +03:00
var err1 , err2 , err3 , err4 error
2026-04-13 21:38:10 +08:00
switch network {
case "tcp6" , "udp6" , "ip6" :
2026-04-15 15:40:19 +03:00
err1 = windows . SetsockoptInt ( windows . Handle ( fd ), windows . IPPROTO_IPV6 , IPV6_UNICAST_IF , iface . Index )
2026-04-13 21:38:10 +08:00
if network == "udp6" {
2026-04-15 15:40:19 +03:00
err2 = windows . SetsockoptInt ( windows . Handle ( fd ), windows . IPPROTO_IPV6 , windows . IPV6_MULTICAST_IF , iface . Index )
}
fallthrough
case "tcp4" , "udp4" , "ip4" :
err3 = windows . SetsockoptInt ( windows . Handle ( fd ), windows . IPPROTO_IP , IP_UNICAST_IF , * ( * int )( unsafe . Pointer ( & index [ 0 ])))
if network == "udp4" || network == "udp6" {
err4 = windows . SetsockoptInt ( windows . Handle ( fd ), windows . IPPROTO_IP , windows . IP_MULTICAST_IF , * ( * int )( unsafe . Pointer ( & index [ 0 ])))
2026-04-13 21:38:10 +08:00
}
default :
2026-04-15 15:40:19 +03:00
panic ( network + " " + address )
2026-04-13 21:38:10 +08:00
}
2026-04-15 15:40:19 +03:00
return errors . Combine ( err1 , err2 , err3 , err4 )
2026-01-07 23:05:08 +01:00
}
2026-06-24 19:06:00 +08:00
func findOutboundInterface ( tunIndex int , fixedName string ) ( * net . Interface , error ) {
if fixedName != "" {
2026-09-08 10:51:35 +08:00
return net . InterfaceByName ( fixedName )
2026-06-24 19:06:00 +08:00
}
2026-09-08 10:51:35 +08:00
r , err := winipcfg . GetIPForwardTable2 ( windows . AF_UNSPEC )
if err != nil {
return nil , err
2026-06-24 19:06:00 +08:00
}
2026-09-08 10:51:35 +08:00
lowestMetric := ^ uint32 ( 0 )
index := uint32 ( 0 )
lowestMetricWifi := ^ uint32 ( 0 )
indexWifi := uint32 ( 0 )
for i := range r {
if r [ i ]. DestinationPrefix . PrefixLength != 0 || r [ i ]. InterfaceIndex == uint32 ( tunIndex ) {
2026-06-24 19:06:00 +08:00
continue
}
2026-09-08 10:51:35 +08:00
ifrow , err := r [ i ]. InterfaceLUID . Interface ()
if err != nil || ifrow . OperStatus != winipcfg . IfOperStatusUp {
2026-06-24 19:06:00 +08:00
continue
}
2026-09-08 10:51:35 +08:00
iface , err := r [ i ]. InterfaceLUID . IPInterface ( windows . AF_INET )
if err != nil {
iface , err = r [ i ]. InterfaceLUID . IPInterface ( windows . AF_INET6 )
if err != nil {
continue
}
2026-06-24 19:06:00 +08:00
}
2026-09-08 10:51:35 +08:00
if ifrow . Type == windows . IF_TYPE_IEEE80211 {
if r [ i ]. Metric + iface . Metric < lowestMetricWifi {
lowestMetricWifi = r [ i ]. Metric + iface . Metric
indexWifi = r [ i ]. InterfaceIndex
}
2026-06-24 19:06:00 +08:00
continue
}
2026-09-08 10:51:35 +08:00
if r [ i ]. Metric + iface . Metric < lowestMetric {
lowestMetric = r [ i ]. Metric + iface . Metric
index = r [ i ]. InterfaceIndex
2026-06-24 19:06:00 +08:00
}
}
2026-09-08 10:51:35 +08:00
if indexWifi != 0 {
index = indexWifi
2026-06-24 19:06:00 +08:00
}
2026-09-08 10:51:35 +08:00
return net . InterfaceByIndex ( int ( index ))
2026-06-24 19:06:00 +08:00
}