Files
XTLS_Xray-core/proxy/tun/tun_windows_outbound.go
T
patternihaandClaude Opus 5.5 46d49adc5c TUN inbound: Warn about forwarding on the outbound interface instead of refusing to start
Mobile Hotspot may well be on before the TUN starts, and having it share
the TUN instead of the physical interface then moves forwarding off it, but
the TUN can only be picked to share while it runs. So the TUN starts, with a
warning that says so, and Xray's own connections recover once forwarding
goes off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:12:47 +03:30

129 lines
4.0 KiB
Go

//go:build windows
package tun
import (
"context"
"slices"
"strings"
"sync"
"github.com/xtls/xray-core/common/errors"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)
// outboundGuard keeps Windows to the binding of autoOutboundsInterface, which
// keeps Xray's own connections out of the TUN. With weak host send or
// forwarding on for an IP version on the bound interface, Windows sends them
// where the routes lead, into the TUN, from that interface's address, and
// drops what comes back to that address through the TUN, so they stall.
//
// For the IP versions routed to the TUN, weak host send is turned off on the
// bound interface while the TUN runs, and turned on again when the TUN stops
// or another interface takes over. Forwarding is what Mobile Hotspot and
// Internet Connection Sharing need, so it is only reported.
type outboundGuard struct {
sync.Mutex
families []winipcfg.AddressFamily
luid winipcfg.LUID // of the interface last checked
turnedOff []winipcfg.AddressFamily // where weak host send was turned off on it
reported string // the forwarding problem last seen
stopped bool
}
// check turns weak host send off on the bound interface, and returns what is
// wrong if forwarding is on there.
func (g *outboundGuard) check() string {
g.Lock()
defer g.Unlock()
if g.stopped {
return ""
}
var luid winipcfg.LUID
var name string
if iface := updater.Get(); iface != nil {
luid, _ = winipcfg.LUIDFromIndex(uint32(iface.Index))
name = iface.Name
}
if luid != g.luid {
g.restoreLocked()
g.luid = luid
}
if luid == 0 {
return ""
}
var forwarding []string
for _, family := range g.families {
row, err := luid.IPInterface(family)
if err != nil {
continue // the interface lacks that IP version
}
if row.ForwardingEnabled {
forwarding = append(forwarding, familyName(family))
}
if !row.WeakHostSend {
continue
}
if err := setWeakHostSend(row, false); err != nil {
errors.LogWarningInner(context.Background(), err, "[tun] unable to turn weak host send off for ", familyName(family), " on ", name)
continue
}
if !slices.Contains(g.turnedOff, family) {
g.turnedOff = append(g.turnedOff, family)
errors.LogInfo(context.Background(), "[tun] weak host send turned off for ", familyName(family), " on ", name, " while the TUN runs, as Windows would ignore autoOutboundsInterface")
}
}
if len(forwarding) > 0 {
return "forwarding is on for " + strings.Join(forwarding, " and ") + " on " + name + " (Mobile Hotspot and Internet Connection Sharing turn it on), so Windows ignores autoOutboundsInterface there, and Xray's own connections go into the TUN and stall: turn the hotspot off, or have it share the TUN instead of " + name
}
return ""
}
// recheck runs check, and warns about forwarding when it comes up. (Windows
// may turn forwarding on and off a few times meanwhile.)
func (g *outboundGuard) recheck() {
problem := g.check()
g.Lock()
cameUp := problem != "" && g.reported == ""
g.reported = problem
g.Unlock()
if cameUp {
errors.LogWarning(context.Background(), "[tun] ", problem)
}
}
// restore turns weak host send on again where check turned it off, for good.
func (g *outboundGuard) restore() {
g.Lock()
defer g.Unlock()
g.restoreLocked()
g.stopped = true
}
func (g *outboundGuard) restoreLocked() {
for _, family := range g.turnedOff {
if row, err := g.luid.IPInterface(family); err == nil {
if err := setWeakHostSend(row, true); err != nil {
errors.LogWarningInner(context.Background(), err, "[tun] unable to turn weak host send on again for ", familyName(family))
}
}
}
g.turnedOff = nil
}
func setWeakHostSend(row *winipcfg.MibIPInterfaceRow, on bool) error {
row.WeakHostSend = on
if row.Family == windows.AF_INET {
row.SitePrefixLength = 0 // as SetIpInterfaceEntry requires for IPv4
}
return row.Set()
}
func familyName(family winipcfg.AddressFamily) string {
if family == windows.AF_INET {
return "IPv4"
}
return "IPv6"
}