mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-30 13:05:43 +00:00
Like sing-box's strict_route, strictRoute is now false by default, so the Windows Filtering Platform filters are only added when it is set to true (together with autoSystemRoutingTable). With unset meaning false, strict_route becomes a plain bool field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
623 lines
18 KiB
Go
623 lines
18 KiB
Go
//go:build windows
|
|
|
|
package tun
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/md5"
|
|
"encoding/binary"
|
|
go_errors "errors"
|
|
"net"
|
|
"net/netip"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/transport/internet"
|
|
"golang.org/x/sys/windows"
|
|
"golang.zx2c4.com/wintun"
|
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
)
|
|
|
|
//go:linkname procyield runtime.procyield
|
|
func procyield(cycles uint32)
|
|
|
|
// 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 {
|
|
sync.RWMutex
|
|
|
|
options *Config
|
|
adapter *wintun.Adapter
|
|
session wintun.Session
|
|
readWait windows.Handle
|
|
luid winipcfg.LUID
|
|
cbr winipcfg.ChangeCallback
|
|
cbi winipcfg.ChangeCallback
|
|
wfp windows.Handle
|
|
resolver *savedResolver
|
|
skipStop chan struct{}
|
|
skipDone chan struct{}
|
|
closed bool
|
|
}
|
|
|
|
// WindowsTun implements Tun
|
|
var _ Tun = (*WindowsTun)(nil)
|
|
|
|
// WindowsTun implements GVisorDevice
|
|
var _ GVisorDevice = (*WindowsTun)(nil)
|
|
|
|
// NewTun creates a Wintun interface with the given name. Should a Wintun
|
|
// interface with the same name exist, it tried to be reused.
|
|
func NewTun(options *Config) (Tun, error) {
|
|
// instantiate wintun adapter
|
|
adapter, err := open(options.Name, options.Desc)
|
|
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{
|
|
options: options,
|
|
adapter: adapter,
|
|
session: session,
|
|
readWait: session.ReadWaitEvent(),
|
|
luid: winipcfg.LUID(adapter.LUID()),
|
|
}
|
|
|
|
return tun, nil
|
|
}
|
|
|
|
func open(name, desc string) (*wintun.Adapter, error) {
|
|
// generate a deterministic GUID from the adapter name
|
|
id := md5.Sum([]byte(name))
|
|
guid := (*windows.GUID)(unsafe.Pointer(&id[0]))
|
|
// try to open existing adapter by name
|
|
adapter, err := wintun.OpenAdapter(name)
|
|
if err == nil {
|
|
return adapter, nil
|
|
}
|
|
// try to create adapter anew
|
|
adapter, err = wintun.CreateAdapter(name, desc, guid)
|
|
if err == nil {
|
|
return adapter, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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
|
|
routesMap := make(map[winipcfg.RouteData]struct{})
|
|
for _, cidr := range t.options.AutoSystemRoutingTable {
|
|
prefix := netip.MustParsePrefix(cidr)
|
|
route := winipcfg.RouteData{
|
|
Destination: prefix.Masked(),
|
|
Metric: 0,
|
|
}
|
|
if prefix.Addr().Is4() {
|
|
route4 = true
|
|
route.NextHop = netip.IPv4Unspecified()
|
|
} else {
|
|
route6 = true
|
|
route.NextHop = netip.IPv6Unspecified()
|
|
}
|
|
routesMap[route] = struct{}{}
|
|
}
|
|
routesData := make([]*winipcfg.RouteData, 0, len(routesMap))
|
|
for route := range routesMap {
|
|
r := route
|
|
routesData = append(routesData, &r)
|
|
}
|
|
|
|
var retryTimes int
|
|
var firstErr error
|
|
startOver:
|
|
if retryTimes > 0 {
|
|
if retryTimes > 15 {
|
|
return windows.ERROR_NOT_FOUND
|
|
}
|
|
errors.LogErrorInner(context.Background(), firstErr, "Interface configuration failed, retrying attempt ", retryTimes, "/15")
|
|
time.Sleep(time.Second)
|
|
}
|
|
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
|
|
}
|
|
}
|
|
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
|
|
}
|
|
}
|
|
ipif, err := t.luid.IPInterface(family)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
|
|
ipif.DadTransmits = 0
|
|
ipif.ManagedAddressConfigurationSupported = false
|
|
ipif.OtherStatefulConfigurationSupported = false
|
|
if family == windows.AF_INET && (address4 || route4) || family == windows.AF_INET6 && (address6 || route6) {
|
|
ipif.NLMTU = t.options.MTU
|
|
}
|
|
if family == windows.AF_INET && route4 || family == windows.AF_INET6 && route6 {
|
|
ipif.UseAutomaticMetric = false
|
|
ipif.Metric = 0
|
|
}
|
|
err = ipif.Set()
|
|
if err != nil {
|
|
firstErr = errors.New("unable to set metric and MTU").Base(err)
|
|
if err == windows.ERROR_NOT_FOUND {
|
|
goto startOver
|
|
}
|
|
return firstErr
|
|
}
|
|
err = t.luid.SetDNS(family, dns, nil)
|
|
if err != nil {
|
|
firstErr = errors.New("unable to set DNS").Base(err)
|
|
if err == windows.ERROR_NOT_FOUND {
|
|
goto startOver
|
|
}
|
|
return firstErr
|
|
}
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
}
|
|
|
|
// With strictRoute, 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.StrictRoute && (route4 || route6) && (blockDNS || blockIPv6) {
|
|
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 {
|
|
return errors.New("unable to block ", what, " outside the TUN (set strictRoute to false to run without)").Base(err)
|
|
}
|
|
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")
|
|
}
|
|
}
|
|
|
|
if updater != nil {
|
|
// 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) {
|
|
updater.Update()
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t.cbr = cbr
|
|
cbi, err := winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
|
|
updater.Update()
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t.cbi = cbi
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *WindowsTun) Close() error {
|
|
t.Lock()
|
|
defer t.Unlock()
|
|
if t.closed {
|
|
return nil
|
|
}
|
|
t.closed = true
|
|
|
|
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)
|
|
}
|
|
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()
|
|
}
|
|
if t.session != (wintun.Session{}) {
|
|
t.session.End()
|
|
}
|
|
if t.adapter != nil {
|
|
t.adapter.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
|
func (t *WindowsTun) WritePacket(packetBuffer *stack.PacketBuffer) tcpip.Error {
|
|
t.RLock()
|
|
defer t.RUnlock()
|
|
if t.closed {
|
|
return &tcpip.ErrClosedForSend{}
|
|
}
|
|
|
|
// 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()
|
|
if go_errors.Is(err, windows.ERROR_NO_MORE_ITEMS) {
|
|
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)
|
|
}
|
|
|
|
func (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) {
|
|
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
|
|
}
|
|
|
|
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))
|
|
|
|
var err1, err2, err3, err4 error
|
|
|
|
switch network {
|
|
case "tcp6", "udp6", "ip6":
|
|
err1 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_UNICAST_IF, iface.Index)
|
|
if network == "udp6" {
|
|
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])))
|
|
}
|
|
default:
|
|
panic(network + " " + address)
|
|
}
|
|
|
|
return errors.Combine(err1, err2, err3, err4)
|
|
}
|
|
|
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
|
if fixedName != "" {
|
|
return net.InterfaceByName(fixedName)
|
|
}
|
|
|
|
r, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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) {
|
|
continue
|
|
}
|
|
ifrow, err := r[i].InterfaceLUID.Interface()
|
|
if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp {
|
|
continue
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
if ifrow.Type == windows.IF_TYPE_IEEE80211 {
|
|
if r[i].Metric+iface.Metric < lowestMetricWifi {
|
|
lowestMetricWifi = r[i].Metric + iface.Metric
|
|
indexWifi = r[i].InterfaceIndex
|
|
}
|
|
continue
|
|
}
|
|
if r[i].Metric+iface.Metric < lowestMetric {
|
|
lowestMetric = r[i].Metric + iface.Metric
|
|
index = r[i].InterfaceIndex
|
|
}
|
|
}
|
|
if indexWifi != 0 {
|
|
index = indexWifi
|
|
}
|
|
return net.InterfaceByIndex(int(index))
|
|
}
|