Compare commits

...
Author SHA1 Message Date
patternihaandClaude Opus 5.5 14f4bcaf29 TUN inbound: Warn about forwarding on a new outbound interface too; Log failed restores of weak host send
As asked in review: switching between two interfaces with forwarding on now
warns about the new one, and an IPInterface failure while restoring weak
host send is logged, with the interface's name, instead of ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:20:31 +03:30
patternihaandClaude Opus 5.5 30a78f1563 TUN inbound: Merge the outbound guard's check and recheck
Since the TUN starts with forwarding on the outbound interface too, check
is only called through recheck, so they are one function now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:52:50 +03:30
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
patternihaandClaude Opus 5.5 b71975abed TUN inbound: Log the outbound interface's forwarding once; Tweak messages
Windows turns forwarding on and off a few times while Mobile Hotspot starts,
so the error is logged when forwarding comes up, not on every change, and
turning weak host send off is logged once per IP version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:28:24 +03:30
patternihaandClaude Opus 5.5 6b0f06fb2b TUN inbound: Handle weak host send and forwarding on the outbound interface on Windows
Windows ignores the binding of autoOutboundsInterface (IP_UNICAST_IF) when
the outbound interface has weak host send or forwarding on for an IP version
routed to the TUN: Xray's own connections then go into the TUN, from that
interface's address, and stall.

While the TUN runs, weak host send is turned off on that interface for those
IP versions, and turned on again when it stops or another interface takes
over. Forwarding, which Mobile Hotspot and Internet Connection Sharing need,
cannot be turned off without breaking them, so it is reported instead: the
TUN does not start while it is on, and an error is logged when it comes on
later.

Fixes https://github.com/XTLS/Xray-core/issues/6776
Reports the cause of https://github.com/XTLS/Xray-core/issues/6872

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:14:52 +03:30
SakuraBurstand风扇滑翔翼 7da5dae650 Mux client: Fix a race condition between Dispatch and SessionManager.Close (#6871)
Fixes https://github.com/XTLS/Xray-core/issues/6870

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-10-05 09:52:18 +00:00
LjhAUMEM a3c6cc6aa8 XDNS finalmask: Default values for types; name->names; type+addr->addrs (#7090)
https://github.com/XTLS/Xray-core/pull/7090#issuecomment-5989883544
https://github.com/XTLS/Xray-core/pull/7090#issuecomment-5988755551
https://github.com/XTLS/Xray-core/pull/7089#issuecomment-5987266008
2026-10-05 08:07:19 +00:00
nSQUICK 1e08d48d88 TUN inbound: Start on Windows when IPv6 is disabled system-wide (#6874)
https://github.com/XTLS/Xray-core/pull/6874#issuecomment-5989986544
2026-10-05 07:26:00 +00:00
Filipp Balakinand风扇滑翔翼 5121c28b85 QUIC sniffer: Handle zero-filled datagram tails (#6882)
https://github.com/XTLS/Xray-core/pull/6882#pullrequestreview-5395639793

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-10-05 03:53:22 +00:00
PotatoChips87and风扇滑翔翼 92f7e72490 WireGuard kernelTun: Fix IPv6 table index allocation when the first table is occupied (#6881)
https://github.com/XTLS/Xray-core/pull/6881#issuecomment-5927334896

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-10-05 03:50:22 +00:00
Жора Змейкин eddad37c69 XDNS finalmask: Fix resolver close deadlock and socket leaks (#7089)
https://github.com/XTLS/Xray-core/pull/7089#issuecomment-5987521972
2026-10-05 03:31:46 +00:00
SVLAVR 00cde72a9b Test: Fix process leaks when retrying scenario tests (#6863)
https://github.com/XTLS/Xray-core/pull/6863#pullrequestreview-5362117967
2026-10-05 02:13:03 +00:00
Denis 08775afd65 WireGuard inbound: Reduce memory held by idle UDP flows (#6856)
https://github.com/XTLS/Xray-core/pull/6856#issuecomment-5870639238
2026-10-05 01:56:36 +00:00
Denis e0bae21201 WireGuard inbound: Reply fake pong to ICMP ping (#6855)
https://github.com/XTLS/Xray-core/pull/6015#issuecomment-4321525342
2026-10-05 01:36:54 +00:00
LevL-maxandClaude Opus 5.5 9d9a7a1c00 gRPC client: Fix redial with the first request's canceled context (#6877)
https://github.com/XTLS/Xray-core/pull/6877#issuecomment-5986476726

Fixes https://github.com/XTLS/Xray-core/issues/6876

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:26:12 +00:00
27 changed files with 860 additions and 240 deletions
+1 -3
View File
@@ -314,12 +314,10 @@ func (m *ClientWorker) Dispatch(ctx context.Context, link *transport.Link) bool
} }
sm := m.sessionManager sm := m.sessionManager
s := sm.Allocate(&m.strategy) s := sm.Allocate(&m.strategy, link.Reader, link.Writer)
if s == nil { if s == nil {
return false return false
} }
s.input = link.Reader
s.output = link.Writer
go fetchInput(ctx, s, m.link.Writer) go fetchInput(ctx, s, m.link.Writer)
if _, ok := link.Reader.(*pipe.Reader); !ok { if _, ok := link.Reader.(*pipe.Reader); !ok {
select { select {
+3 -1
View File
@@ -51,7 +51,7 @@ func (m *SessionManager) Count() int {
return int(m.count) return int(m.count)
} }
func (m *SessionManager) Allocate(Strategy *ClientStrategy) *Session { func (m *SessionManager) Allocate(Strategy *ClientStrategy, input buf.Reader, output buf.Writer) *Session {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
@@ -64,6 +64,8 @@ func (m *SessionManager) Allocate(Strategy *ClientStrategy) *Session {
m.count++ m.count++
s := &Session{ s := &Session{
input: input,
output: output,
ID: m.count, ID: m.count,
parent: m, parent: m,
done: done.New(), done: done.New(),
+3 -3
View File
@@ -9,7 +9,7 @@ import (
func TestSessionManagerAdd(t *testing.T) { func TestSessionManagerAdd(t *testing.T) {
m := NewSessionManager() m := NewSessionManager()
s := m.Allocate(&ClientStrategy{}) s := m.Allocate(&ClientStrategy{}, nil, nil)
if s.ID != 1 { if s.ID != 1 {
t.Error("id: ", s.ID) t.Error("id: ", s.ID)
} }
@@ -17,7 +17,7 @@ func TestSessionManagerAdd(t *testing.T) {
t.Error("size: ", m.Size()) t.Error("size: ", m.Size())
} }
s = m.Allocate(&ClientStrategy{}) s = m.Allocate(&ClientStrategy{}, nil, nil)
if s.ID != 2 { if s.ID != 2 {
t.Error("id: ", s.ID) t.Error("id: ", s.ID)
} }
@@ -39,7 +39,7 @@ func TestSessionManagerAdd(t *testing.T) {
func TestSessionManagerClose(t *testing.T) { func TestSessionManagerClose(t *testing.T) {
m := NewSessionManager() m := NewSessionManager()
s := m.Allocate(&ClientStrategy{}) s := m.Allocate(&ClientStrategy{}, nil, nil)
if m.CloseIfNoSessionAndIdle(m.Size(), m.Count()) { if m.CloseIfNoSessionAndIdle(m.Size(), m.Count()) {
t.Error("able to close") t.Error("able to close")
+4
View File
@@ -146,6 +146,10 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
} }
restPayload := b[hdrLen+int(packetLen):] restPayload := b[hdrLen+int(packetLen):]
// cachedReader can concatenate zero-padded UDP datagrams.
for len(restPayload) > 0 && restPayload[0] == 0 {
restPayload = restPayload[1:]
}
if !isQUICInitial { // Skip this packet if it's not initial packet if !isQUICInitial { // Skip this packet if it's not initial packet
b = restPayload b = restPayload
continue continue
File diff suppressed because one or more lines are too long
+43 -53
View File
@@ -15,7 +15,6 @@ import (
googleuuid "github.com/google/uuid" googleuuid "github.com/google/uuid"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet/finalmask/fragment" "github.com/xtls/xray-core/transport/internet/finalmask/fragment"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
@@ -792,37 +791,15 @@ func (c *Sudoku) Build() (proto.Message, error) {
} }
type XDNSDomain struct { type XDNSDomain struct {
Name string `json:"name"` Names []string `json:"names"`
LenLimit int32 `json:"lenLimit"` LenLimit int32 `json:"lenLimit"`
LabelLimit int32 `json:"labelLimit"` LabelLimit int32 `json:"labelLimit"`
Types []int32 `json:"types"` Types []int32 `json:"types"`
Edns0 int32 `json:"edns0"` Edns0 int32 `json:"edns0"`
} }
type XDNSResolverTCP struct {
Addr string `json:"addr"`
}
func (c *XDNSResolverTCP) Build() (proto.Message, error) {
return &xdns.TCPResolverProto{Addr: c.Addr}, nil
}
type XDNSResolverUDP struct {
Addr string `json:"addr"`
}
func (c *XDNSResolverUDP) Build() (proto.Message, error) {
return &xdns.UDPResolverProto{Addr: c.Addr}, nil
}
var xdnsLoader = NewJSONConfigLoader(ConfigCreatorCache{
"tcp": func() interface{} { return new(XDNSResolverTCP) },
"udp": func() interface{} { return new(XDNSResolverUDP) },
}, "type", "settings")
type XDNSResolver struct { type XDNSResolver struct {
Type string `json:"type"` Addrs []string `json:"addrs"`
Settings json.RawMessage `json:"settings"`
} }
type XDNS struct { type XDNS struct {
@@ -833,7 +810,7 @@ type XDNS struct {
func (c *XDNS) Build() (proto.Message, error) { func (c *XDNS) Build() (proto.Message, error) {
var domains []*xdns.DomainProto var domains []*xdns.DomainProto
var resolvers []*serial.TypedMessage var resolvers []*xdns.ResolverProto
for i := range c.Domains { for i := range c.Domains {
if c.Domains[i].LenLimit == 0 { if c.Domains[i].LenLimit == 0 {
c.Domains[i].LenLimit = 255 c.Domains[i].LenLimit = 255
@@ -841,33 +818,46 @@ func (c *XDNS) Build() (proto.Message, error) {
if c.Domains[i].LabelLimit == 0 { if c.Domains[i].LabelLimit == 0 {
c.Domains[i].LabelLimit = 63 c.Domains[i].LabelLimit = 63
} }
types := make([]uint16, 0, len(c.Domains[i].Types)) for j := range c.Domains[i].Names {
for j := range c.Domains[i].Types { domain, err := xdns.NewDomain(c.Domains[i].Names[j], int(c.Domains[i].LenLimit), int(c.Domains[i].LabelLimit), []uint16{1, 5, 16, 28}, uint16(c.Domains[i].Edns0))
types = append(types, uint16(c.Domains[i].Types[j])) if err != nil {
return nil, err
}
errors.LogInfo(context.Background(), domain.Show())
domains = append(domains, &xdns.DomainProto{
Name: c.Domains[i].Names[j],
LenLimit: c.Domains[i].LenLimit,
LabelLimit: c.Domains[i].LabelLimit,
Types: c.Domains[i].Types,
Edns0: c.Domains[i].Edns0,
})
} }
domain, err := xdns.NewDomain(c.Domains[i].Name, int(c.Domains[i].LenLimit), int(c.Domains[i].LabelLimit), types, uint16(c.Domains[i].Edns0))
if err != nil {
return nil, err
}
errors.LogInfo(context.Background(), domain.Show())
domains = append(domains, &xdns.DomainProto{
Name: c.Domains[i].Name,
LenLimit: c.Domains[i].LenLimit,
LabelLimit: c.Domains[i].LabelLimit,
Types: c.Domains[i].Types,
Edns0: c.Domains[i].Edns0,
})
} }
for i := range c.Resolvers { for i := range c.Resolvers {
config, err := xdnsLoader.LoadWithID(c.Resolvers[i].Settings, c.Resolvers[i].Type) for j := range c.Resolvers[i].Addrs {
if err != nil { var u *url.URL
return nil, err var e error
if !strings.Contains(c.Resolvers[i].Addrs[j], "://") {
u, e = url.Parse("udp://" + c.Resolvers[i].Addrs[j])
} else {
u, e = url.Parse(c.Resolvers[i].Addrs[j])
}
if e != nil {
return nil, e
}
switch u.Scheme {
case "tcp", "udp":
default:
return nil, errors.New("invalid protocol")
}
var host, port string
host = u.Hostname()
port = u.Port()
if port == "" {
port = "53"
}
resolvers = append(resolvers, &xdns.ResolverProto{Type: u.Scheme, Addr: net.JoinHostPort(host, port)})
} }
pm, err := config.(interface{ Build() (proto.Message, error) }).Build()
if err != nil {
return nil, err
}
resolvers = append(resolvers, serial.ToTypedMessage(pm))
} }
if c.ExtraPoll < 0 || c.ExtraPoll > 3 { if c.ExtraPoll < 0 || c.ExtraPoll > 3 {
return nil, errors.New("c.ExtraPoll < 0 || c.ExtraPoll > 3") return nil, errors.New("c.ExtraPoll < 0 || c.ExtraPoll > 3")
+2
View File
@@ -213,6 +213,8 @@ If the filters cannot be added, Xray does not start. They are removed when Xray
`autoSystemWfpBlockLeak` (Windows only) is empty by default, as the filters break some setups: with `"dns"`, a local DNS resolver other programs use (e.g. on `127.0.0.1:53`), the DNS of another VPN on its own interface, virtual machines whose NAT resolves names on the host, or signing in to a captive portal; with `"misconfigtun"`, IPv4 or IPv6 on the local network while no route of that version leads to the TUN. Without the filters, DNS may leak as described above. To keep an IP version out of the TUN on purpose while still blocking DNS leaks, use only `["dns"]`. `autoSystemWfpBlockLeak` (Windows only) is empty by default, as the filters break some setups: with `"dns"`, a local DNS resolver other programs use (e.g. on `127.0.0.1:53`), the DNS of another VPN on its own interface, virtual machines whose NAT resolves names on the host, or signing in to a captive portal; with `"misconfigtun"`, IPv4 or IPv6 on the local network while no route of that version leads to the TUN. Without the filters, DNS may leak as described above. To keep an IP version out of the TUN on purpose while still blocking DNS leaks, use only `["dns"]`.
`autoOutboundsInterface` (the default with `autoSystemRoutingTable`) keeps Xray's own connections out of the TUN by binding them to another interface, which Windows only honors while that interface has weak host send and forwarding off for the IP versions routed to the TUN. Otherwise, Windows sends them into the TUN, from that interface's address, and they stall. While the TUN runs, Xray therefore turns weak host send off on that interface, and on again when it stops or another interface takes over. Forwarding cannot be turned off this way, as Mobile Hotspot and Internet Connection Sharing need it, so a warning is logged while it is on. Having the hotspot share the TUN instead of that interface (Settings, Mobile hotspot, Share my internet connection from) moves forwarding to the TUN, where it does no harm, and sends the hotspot's devices through Xray as well.
You can give the adapter ip address manually, you can live Windows to give it autogenerated ip address (which take few seconds), it doesn't matter, the traffic going _through_ the interface will be forwarded into the app for proxying. \ You can give the adapter ip address manually, you can live Windows to give it autogenerated ip address (which take few seconds), it doesn't matter, the traffic going _through_ the interface will be forwarded into the app for proxying. \
Minimal configuration that will work for local machine is routing passing the traffic on-link through the interface. Minimal configuration that will work for local machine is routing passing the traffic on-link through the interface.
You will need the interface id for that, unfortunately it is going to change with every Xray start due to implementation ambiguity between Xray and wintun driver. You will need the interface id for that, unfortunately it is going to change with every Xray start due to implementation ambiguity between Xray and wintun driver.
+1 -1
View File
@@ -101,7 +101,7 @@ func (t *stackGVisor) Start() error {
// Use custom UDP packet handler, instead of strict gVisor forwarder, for FullCone NAT support // Use custom UDP packet handler, instead of strict gVisor forwarder, for FullCone NAT support
udpForwarder := newUdpConnectionHandler(t.handler.HandleConnection, t.writeRawUDPPacket) udpForwarder := newUdpConnectionHandler(t.handler.HandleConnection, t.writeRawUDPPacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool { ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
data := pkt.Clone().Data().AsRange().ToSlice() data := pkt.Data().AsRange().ToSlice()
// if len(data) == 0 { // if len(data) == 0 {
// return false // return false
// } // }
+19
View File
@@ -46,6 +46,7 @@ type WindowsTun struct {
luid winipcfg.LUID luid winipcfg.LUID
cbr winipcfg.ChangeCallback cbr winipcfg.ChangeCallback
cbi winipcfg.ChangeCallback cbi winipcfg.ChangeCallback
guard outboundGuard
wfp windows.Handle wfp windows.Handle
resolver *savedResolver resolver *savedResolver
skipStop chan struct{} skipStop chan struct{}
@@ -178,6 +179,11 @@ startOver:
} }
ipif, err := t.luid.IPInterface(family) ipif, err := t.luid.IPInterface(family)
if err != nil { if err != nil {
// With IPv6 disabled system-wide (DisabledComponents), the adapter has no
// IPv6 interface at all. Skip the family unless the config asks for it.
if err == windows.ERROR_NOT_FOUND && family == windows.AF_INET6 && !address6 && !route6 {
continue
}
return err return err
} }
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
@@ -292,10 +298,21 @@ startOver:
} }
if updater != nil { if updater != nil {
// Xray's own connections have to stay out of the IP versions routed
// to the TUN, which needs Windows to honor the binding to updater's
// interface.
if route4 {
t.guard.families = append(t.guard.families, windows.AF_INET)
}
if route6 {
t.guard.families = append(t.guard.families, windows.AF_INET6)
}
t.guard.check()
// Only a registered callback goes into the fields: a nil pointer in // Only a registered callback goes into the fields: a nil pointer in
// them would not compare equal to nil in Close. // them would not compare equal to nil in Close.
cbr, err := winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) { cbr, err := winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) {
updater.Update() updater.Update()
t.guard.check()
}) })
if err != nil { if err != nil {
return err return err
@@ -303,6 +320,7 @@ startOver:
t.cbr = cbr t.cbr = cbr
cbi, err := winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) { cbi, err := winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
updater.Update() updater.Update()
t.guard.check()
}) })
if err != nil { if err != nil {
return err return err
@@ -326,6 +344,7 @@ func (t *WindowsTun) Close() error {
if t.cbi != nil { if t.cbi != nil {
t.cbi.Unregister() t.cbi.Unregister()
} }
t.guard.restore()
if t.luid != 0 { if t.luid != 0 {
t.luid.FlushRoutes(windows.AF_INET) t.luid.FlushRoutes(windows.AF_INET)
t.luid.FlushIPAddresses(windows.AF_INET) t.luid.FlushIPAddresses(windows.AF_INET)
+120
View File
@@ -0,0 +1,120 @@
//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
name string // of that interface
turnedOff []winipcfg.AddressFamily // where weak host send was turned off on it
forwarding bool // whether forwarding was on there
stopped bool
}
// check turns weak host send off on the bound interface, and warns when
// forwarding comes on there, but not again while it stays on.
func (g *outboundGuard) check() {
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, g.name = luid, name
g.forwarding = false // to warn about the new interface as well
}
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")
}
}
wasOn := g.forwarding
g.forwarding = len(forwarding) > 0
if g.forwarding && !wasOn {
errors.LogWarning(context.Background(), "[tun] 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)
}
}
// 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 {
row, err := g.luid.IPInterface(family)
if err == nil {
err = setWeakHostSend(row, true)
}
if err != nil {
errors.LogWarningInner(context.Background(), err, "[tun] unable to turn weak host send on again for ", familyName(family), " on ", g.name)
}
}
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"
}
+91
View File
@@ -0,0 +1,91 @@
package wireguard
import (
"context"
"github.com/xtls/xray-core/common/errors"
tunicmp "github.com/xtls/xray-core/proxy/tun/icmp"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
)
// CreateICMPEchoResponder answers ICMP echo requests from peers locally, the way
// the TUN inbound does: ICMP is not proxied, but ping and connectivity checks
// through the tunnel get a reply instead of timing out.
//
// In promiscuous mode gVisor skips its own IPv4 echo reply for addresses that are
// not assigned to the NIC and leaves it to a custom handler; IPv6 is registered
// too so both families behave the same.
func CreateICMPEchoResponder(gstack *stack.Stack) {
gstack.SetTransportProtocolHandler(icmp.ProtocolNumber4, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
return handleICMPEcho(gstack, header.IPv4ProtocolNumber, id, pkt)
})
gstack.SetTransportProtocolHandler(icmp.ProtocolNumber6, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
return handleICMPEcho(gstack, header.IPv6ProtocolNumber, id, pkt)
})
}
func handleICMPEcho(gstack *stack.Stack, netProto tcpip.NetworkProtocolNumber, id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
srcIP := id.RemoteAddress
dstIP := id.LocalAddress
if srcIP.Len() == 0 || dstIP.Len() == 0 {
return true
}
headerBytes := pkt.TransportHeader().Slice()
payloadBytes := pkt.Data().AsRange().ToSlice()
message := make([]byte, len(headerBytes)+len(payloadBytes))
copy(message, headerBytes)
copy(message[len(headerBytes):], payloadBytes)
if _, _, ok := tunicmp.ParseEchoRequest(netProto, message); !ok {
return true
}
reply, err := tunicmp.BuildLocalEchoReply(netProto, message, dstIP, srcIP)
if err != nil {
errors.LogInfoInner(context.Background(), err, "failed to build local icmp echo reply")
return true
}
if err := writeRawICMPPacket(gstack, netProto, reply, dstIP, srcIP); err != nil {
errors.LogInfoInner(context.Background(), err, "failed to write local icmp echo reply")
}
return true
}
func writeRawICMPPacket(gstack *stack.Stack, netProto tcpip.NetworkProtocolNumber, message []byte, srcIP, dstIP tcpip.Address) error {
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: header.IPv6MinimumSize,
Payload: buffer.MakeWithData(message),
})
defer pkt.DecRef()
if netProto == header.IPv4ProtocolNumber {
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
ipHdr.Encode(&header.IPv4Fields{
TotalLength: uint16(header.IPv4MinimumSize + len(message)),
TTL: 64,
Protocol: uint8(header.ICMPv4ProtocolNumber),
SrcAddr: srcIP,
DstAddr: dstIP,
})
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
} else {
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
ipHdr.Encode(&header.IPv6Fields{
PayloadLength: uint16(len(message)),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: 64,
SrcAddr: srcIP,
DstAddr: dstIP,
})
}
if err := gstack.WriteRawPacket(1, netProto, buffer.MakeWithView(pkt.ToView())); err != nil {
return errors.New("failed to write raw icmp packet back to stack ", err)
}
return nil
}
+176
View File
@@ -0,0 +1,176 @@
package wireguard
import (
"bytes"
"net/netip"
"testing"
"time"
"github.com/xtls/xray-core/common/net"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
func newICMPTestStack(t *testing.T) *netTun {
t.Helper()
dev, _, gstack, err := CreateNetTUN([]netip.Addr{
netip.MustParseAddr("10.66.0.1"),
netip.MustParseAddr("fd00::1"),
}, nil, 1420, false)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { dev.Close() })
CreateForwarder(gstack, func(conn net.Conn, dest net.Destination) { conn.Close() })
CreateICMPEchoResponder(gstack)
return dev.(*netTun)
}
// startReader must run before the request is written: the stack may answer
// synchronously inside Write, and netTun hands packets over an unbuffered channel.
func startReader(dev *netTun) <-chan []byte {
got := make(chan []byte, 1)
go func() {
buf := make([]byte, 2048)
sizes := make([]int, 1)
if _, err := dev.Read([][]byte{buf}, sizes, 0); err == nil {
got <- buf[:sizes[0]]
}
}()
return got
}
func awaitPacket(t *testing.T, got <-chan []byte) []byte {
t.Helper()
select {
case p := <-got:
return p
case <-time.After(2 * time.Second):
t.Fatal("no echo reply from the stack")
return nil
}
}
func TestICMPv4EchoReply(t *testing.T) {
dev := newICMPTestStack(t)
src := tcpip.AddrFrom4([4]byte{10, 66, 0, 2})
dst := tcpip.AddrFrom4([4]byte{1, 1, 1, 1})
payload := []byte("xray wireguard ping")
icmpMsg := make([]byte, header.ICMPv4MinimumSize+len(payload))
req := header.ICMPv4(icmpMsg)
req.SetType(header.ICMPv4Echo)
req.SetIdent(0x1234)
req.SetSequence(7)
copy(req.Payload(), payload)
req.SetChecksum(header.ICMPv4Checksum(req[:header.ICMPv4MinimumSize], checksum.Checksum(payload, 0)))
pkt := make([]byte, header.IPv4MinimumSize+len(icmpMsg))
ip := header.IPv4(pkt)
ip.Encode(&header.IPv4Fields{
TotalLength: uint16(len(pkt)),
TTL: 64,
Protocol: uint8(header.ICMPv4ProtocolNumber),
SrcAddr: src,
DstAddr: dst,
})
ip.SetChecksum(^ip.CalculateChecksum())
copy(pkt[header.IPv4MinimumSize:], icmpMsg)
got := startReader(dev)
if _, err := dev.Write([][]byte{pkt}, 0); err != nil {
t.Fatal(err)
}
reply := header.IPv4(awaitPacket(t, got))
if !reply.IsValid(len(reply)) {
t.Fatal("invalid ipv4 reply")
}
if reply.SourceAddress() != dst || reply.DestinationAddress() != src {
t.Fatalf("reply addresses %v -> %v, want %v -> %v", reply.SourceAddress(), reply.DestinationAddress(), dst, src)
}
if reply.TransportProtocol() != header.ICMPv4ProtocolNumber {
t.Fatalf("reply protocol %v, want icmpv4", reply.TransportProtocol())
}
echo := header.ICMPv4(reply.Payload())
if echo.Type() != header.ICMPv4EchoReply {
t.Fatalf("reply type %v, want echo reply", echo.Type())
}
if echo.Ident() != 0x1234 || echo.Sequence() != 7 {
t.Fatalf("reply ident/seq %#x/%d, want 0x1234/7", echo.Ident(), echo.Sequence())
}
if !bytes.Equal(echo.Payload(), payload) {
t.Fatalf("reply payload %q, want %q", echo.Payload(), payload)
}
if checksum.Checksum(echo, 0) != 0xffff {
t.Fatal("bad icmpv4 checksum")
}
}
func TestICMPv6EchoReply(t *testing.T) {
dev := newICMPTestStack(t)
src := tcpip.AddrFrom16([16]byte{0xfd, 15: 2})
dst := tcpip.AddrFrom16([16]byte{0x26, 0x06, 0x47, 0x00, 0x47, 0x00, 15: 0x11})
payload := []byte("xray wireguard ping6")
icmpMsg := make([]byte, header.ICMPv6MinimumSize+len(payload))
req := header.ICMPv6(icmpMsg)
req.SetType(header.ICMPv6EchoRequest)
req.SetIdent(0x4321)
req.SetSequence(9)
copy(req.Payload(), payload)
req.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: req[:header.ICMPv6MinimumSize],
Src: src,
Dst: dst,
PayloadCsum: checksum.Checksum(payload, 0),
PayloadLen: len(payload),
}))
pkt := make([]byte, header.IPv6MinimumSize+len(icmpMsg))
ip := header.IPv6(pkt)
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(len(icmpMsg)),
TransportProtocol: header.ICMPv6ProtocolNumber,
HopLimit: 64,
SrcAddr: src,
DstAddr: dst,
})
copy(pkt[header.IPv6MinimumSize:], icmpMsg)
got := startReader(dev)
if _, err := dev.Write([][]byte{pkt}, 0); err != nil {
t.Fatal(err)
}
reply := header.IPv6(awaitPacket(t, got))
if !reply.IsValid(len(reply)) {
t.Fatal("invalid ipv6 reply")
}
if reply.SourceAddress() != dst || reply.DestinationAddress() != src {
t.Fatalf("reply addresses %v -> %v, want %v -> %v", reply.SourceAddress(), reply.DestinationAddress(), dst, src)
}
echo := header.ICMPv6(reply.Payload())
if echo.Type() != header.ICMPv6EchoReply {
t.Fatalf("reply type %v, want echo reply", echo.Type())
}
if echo.Ident() != 0x4321 || echo.Sequence() != 9 {
t.Fatalf("reply ident/seq %#x/%d, want 0x4321/9", echo.Ident(), echo.Sequence())
}
if !bytes.Equal(echo.Payload(), payload) {
t.Fatalf("reply payload %q, want %q", echo.Payload(), payload)
}
zeroed := header.ICMPv6(append([]byte(nil), echo[:header.ICMPv6MinimumSize]...))
zeroed.SetChecksum(0)
want := header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: zeroed,
Src: dst,
Dst: src,
PayloadCsum: checksum.Checksum(echo.Payload(), 0),
PayloadLen: len(echo.Payload()),
})
if echo.Checksum() != want {
t.Fatalf("icmpv6 checksum %#x, want %#x", echo.Checksum(), want)
}
}
+80
View File
@@ -0,0 +1,80 @@
package wireguard
import (
"runtime"
"testing"
)
const benchBatch = 64
// Raw cost of queueing and draining a small burst, as one flow's reader does.
func BenchmarkQueueBurstChan(b *testing.B) {
ch := make(chan *packet, udpQueueLimit)
p := &packet{}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j := 0; j < benchBatch; j++ {
ch <- p
}
for j := 0; j < benchBatch; j++ {
<-ch
}
}
}
func BenchmarkQueueBurstPacketQueue(b *testing.B) {
q := newPacketQueue(udpQueueLimit)
p := &packet{}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j := 0; j < benchBatch; j++ {
q.push(p)
}
for j := 0; j < benchBatch; j++ {
q.pop()
}
}
}
// Producer and consumer on different goroutines; the producer yields when the
// queue is full instead of spinning, like a blocking channel send would.
func BenchmarkQueueStreamChan(b *testing.B) {
ch := make(chan *packet, udpQueueLimit)
p := &packet{}
done := make(chan struct{})
go func() {
for range ch {
}
close(done)
}()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ch <- p
}
close(ch)
<-done
}
func BenchmarkQueueStreamPacketQueue(b *testing.B) {
q := newPacketQueue(udpQueueLimit)
p := &packet{}
done := make(chan struct{})
go func() {
for {
if _, ok := q.pop(); !ok {
break
}
}
close(done)
}()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for !q.push(p) {
runtime.Gosched()
}
}
q.close()
<-done
}
+1
View File
@@ -134,6 +134,7 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
} }
// Install the stack's protocol handlers before the device can deliver packets to it (Start -> dev.Up). // Install the stack's protocol handlers before the device can deliver packets to it (Start -> dev.Up).
CreateForwarder(stack, s.HandleConnection) CreateForwarder(stack, s.HandleConnection)
CreateICMPEchoResponder(stack)
return s, nil return s, nil
} }
+73 -18
View File
@@ -85,7 +85,7 @@ func CreateForwarder(gstack *stack.Stack, handler func(conn net.Conn, dest net.D
} }
gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool { gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
data := pkt.Clone().Data().AsRange().ToSlice() data := pkt.Data().AsRange().ToSlice()
// if len(data) == 0 { // if len(data) == 0 {
// return false // return false
// } // }
@@ -112,12 +112,7 @@ func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte)
m.mutex.RLock() m.mutex.RLock()
uc, ok := m.m[src.NetAddr()] uc, ok := m.m[src.NetAddr()]
if ok { if ok {
select { if !uc.queue.push(&packet{p: data, dest: &dst}) {
case uc.queue <- &packet{
p: data,
dest: &dst,
}:
default:
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full") errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full")
} }
m.mutex.RUnlock() m.mutex.RUnlock()
@@ -131,7 +126,7 @@ func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte)
uc, ok = m.m[src.NetAddr()] uc, ok = m.m[src.NetAddr()]
if !ok { if !ok {
uc = &udpConn{ uc = &udpConn{
queue: make(chan *packet, 1024), queue: newPacketQueue(udpQueueLimit),
src: src, src: src,
dst: dst, dst: dst,
} }
@@ -145,12 +140,7 @@ func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte)
go m.handler(uc, dst) go m.handler(uc, dst)
} }
select { if !uc.queue.push(&packet{p: data, dest: &dst}) {
case uc.queue <- &packet{
p: data,
dest: &dst,
}:
default:
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full 2") errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full 2")
} }
} }
@@ -158,7 +148,7 @@ func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte)
func (m *udpManager) close(uc *udpConn) { func (m *udpManager) close(uc *udpConn) {
if !uc.closed { if !uc.closed {
uc.closed = true uc.closed = true
close(uc.queue) uc.queue.close()
delete(m.m, uc.src.NetAddr()) delete(m.m, uc.src.NetAddr())
} }
} }
@@ -232,7 +222,7 @@ type packet struct {
} }
type udpConn struct { type udpConn struct {
queue chan *packet queue *packetQueue
src net.Destination src net.Destination
dst net.Destination dst net.Destination
writeFunc func(payload []byte, src net.Destination, dst net.Destination) error writeFunc func(payload []byte, src net.Destination, dst net.Destination) error
@@ -242,7 +232,7 @@ type udpConn struct {
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) { func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
for { for {
q, ok := <-c.queue q, ok := c.queue.pop()
if !ok { if !ok {
return nil, io.EOF return nil, io.EOF
} }
@@ -261,7 +251,7 @@ func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
} }
func (c *udpConn) Read(p []byte) (int, error) { func (c *udpConn) Read(p []byte) (int, error) {
q, ok := <-c.queue q, ok := c.queue.pop()
if !ok { if !ok {
return 0, io.EOF return 0, io.EOF
} }
@@ -324,3 +314,68 @@ func (c *udpConn) SetReadDeadline(t time.Time) error {
func (c *udpConn) SetWriteDeadline(t time.Time) error { func (c *udpConn) SetWriteDeadline(t time.Time) error {
return nil return nil
} }
// udpQueueLimit bounds the packets waiting for one UDP flow; more are dropped.
const udpQueueLimit = 1024
// packetQueue holds the packets waiting for one UDP flow. Unlike a buffered
// channel of the same bound it only allocates for packets actually queued, so
// the many idle flows kept until the idle timeout cost next to nothing.
type packetQueue struct {
mu sync.Mutex
items []*packet
limit int
notify chan struct{}
closed bool
}
func newPacketQueue(limit int) *packetQueue {
return &packetQueue{limit: limit, notify: make(chan struct{}, 1)}
}
// push queues p and reports whether it was accepted.
func (q *packetQueue) push(p *packet) bool {
q.mu.Lock()
defer q.mu.Unlock()
if q.closed || len(q.items) >= q.limit {
return false
}
q.items = append(q.items, p)
select {
case q.notify <- struct{}{}:
default:
}
return true
}
// pop blocks until a packet is queued or the queue is closed and drained.
func (q *packetQueue) pop() (*packet, bool) {
for {
q.mu.Lock()
if len(q.items) > 0 {
p := q.items[0]
q.items[0] = nil
q.items = q.items[1:]
if len(q.items) == 0 {
q.items = nil
}
q.mu.Unlock()
return p, true
}
if q.closed {
q.mu.Unlock()
return nil, false
}
q.mu.Unlock()
<-q.notify
}
}
func (q *packetQueue) close() {
q.mu.Lock()
defer q.mu.Unlock()
if !q.closed {
q.closed = true
close(q.notify)
}
}
+16 -21
View File
@@ -9,7 +9,7 @@ import (
"net" "net"
"net/netip" "net/netip"
"os" "os"
"sync" "sync/atomic"
"syscall" "syscall"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@@ -20,21 +20,10 @@ import (
"golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/tun"
) )
var ( var tableIndex atomic.Uint32
tableIndex int = 10230
mu sync.Mutex
)
func allocateIPv6TableIndex() int { func init() {
mu.Lock() tableIndex.Store(10230)
defer mu.Unlock()
if tableIndex > 10230 {
errors.LogInfo(context.Background(), "allocate new ipv6 table index: ", tableIndex)
}
currentIndex := tableIndex
tableIndex++
return currentIndex
} }
type kernelTun struct { type kernelTun struct {
@@ -111,17 +100,23 @@ func createKernelTun(localAddresses, dnsServers []netip.Addr, mtu int) (tdev tun
} }
} }
ipv6TableIndex := allocateIPv6TableIndex() var ipv6TableIndex int
if v6 != nil { if v6 != nil {
r := &netlink.Route{Table: ipv6TableIndex} r := &netlink.Route{}
for { for {
ipv6TableIndex = int(tableIndex.Add(1)) - 1
r.Table = ipv6TableIndex
routeList, fErr := netlink.RouteListFiltered(netlink.FAMILY_V6, r, netlink.RT_FILTER_TABLE) routeList, fErr := netlink.RouteListFiltered(netlink.FAMILY_V6, r, netlink.RT_FILTER_TABLE)
if len(routeList) == 0 || fErr != nil { if fErr != nil {
return nil, nil, errors.New("failed to pre check routes for table: ", ipv6TableIndex).Base(fErr)
}
if len(routeList) == 0 {
errors.LogInfo(context.Background(), "allocate new ipv6 table index: ", ipv6TableIndex)
break break
} }
ipv6TableIndex-- // to prevent infinite loop
if ipv6TableIndex < 0 { if ipv6TableIndex > 65535 {
return nil, nil, fmt.Errorf("failed to find available ipv6 table index") return nil, nil, errors.New("failed to find available ipv6 table index")
} }
} }
} }
+91
View File
@@ -0,0 +1,91 @@
package wireguard
import (
"testing"
"time"
"github.com/xtls/xray-core/common/net"
)
// BenchmarkUDPManagerNewSession measures what one new UDP flow costs the
// inbound while it stays open: QUIC and DNS open many short flows, and each
// one lives until the connection idle timeout.
func BenchmarkUDPManagerNewSession(b *testing.B) {
m := &udpManager{
handler: func(conn net.Conn, dest net.Destination) {},
m: make(map[string]*udpConn),
}
dst := net.UDPDestination(net.ParseAddress("1.1.1.1"), 443)
payload := make([]byte, 1200)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
src := net.UDPDestination(net.IPAddress([]byte{10, byte(i >> 16), byte(i >> 8), byte(i)}), net.Port(1024+i%60000))
m.feed(src, dst, payload)
}
}
func TestPacketQueueOrderAndClose(t *testing.T) {
q := newPacketQueue(udpQueueLimit)
for i := 0; i < 3; i++ {
if !q.push(&packet{p: []byte{byte(i)}}) {
t.Fatalf("push %d rejected", i)
}
}
for i := 0; i < 3; i++ {
p, ok := q.pop()
if !ok || p.p[0] != byte(i) {
t.Fatalf("pop %d: got %v, %v", i, p, ok)
}
}
q.close()
if _, ok := q.pop(); ok {
t.Fatal("pop after close returned a packet")
}
if q.push(&packet{}) {
t.Fatal("push after close accepted")
}
}
func TestPacketQueueLimit(t *testing.T) {
q := newPacketQueue(udpQueueLimit)
for i := 0; i < udpQueueLimit; i++ {
if !q.push(&packet{}) {
t.Fatalf("push %d rejected below the limit", i)
}
}
if q.push(&packet{}) {
t.Fatal("push above the limit accepted")
}
}
func TestPacketQueueCloseUnblocksReader(t *testing.T) {
q := newPacketQueue(udpQueueLimit)
done := make(chan bool)
go func() {
_, ok := q.pop()
done <- ok
}()
q.close()
select {
case ok := <-done:
if ok {
t.Fatal("blocked pop returned a packet after close")
}
case <-time.After(time.Second):
t.Fatal("close did not wake the reader")
}
}
func TestPacketQueueDropsDrainedStorage(t *testing.T) {
q := newPacketQueue(udpQueueLimit)
for i := 0; i < 100; i++ {
q.push(&packet{})
}
for i := 0; i < 100; i++ {
q.pop()
}
if q.items != nil {
t.Fatalf("drained queue still holds %d slots", cap(q.items))
}
}
+6
View File
@@ -115,6 +115,9 @@ func TestDokodemoTCP(t *testing.T) {
defer CloseServer(server) defer CloseServer(server)
break break
} }
if server != nil {
CloseServer(server)
}
retry++ retry++
if retry > 5 { if retry > 5 {
t.Fatal("All attempts failed to start client") t.Fatal("All attempts failed to start client")
@@ -209,6 +212,9 @@ func TestDokodemoUDP(t *testing.T) {
defer CloseServer(server) defer CloseServer(server)
break break
} }
if server != nil {
CloseServer(server)
}
retry++ retry++
if retry > 5 { if retry > 5 {
t.Fatal("All attempts failed to start client") t.Fatal("All attempts failed to start client")
+6
View File
@@ -227,6 +227,9 @@ func TestSocksBridageUDP(t *testing.T) {
defer CloseServer(server) defer CloseServer(server)
break break
} }
if server != nil {
CloseServer(server)
}
retry++ retry++
if retry > 5 { if retry > 5 {
t.Fatal("All attempts failed to start server") t.Fatal("All attempts failed to start server")
@@ -342,6 +345,9 @@ func TestSocksBridageUDPWithRouting(t *testing.T) {
defer CloseServer(server) defer CloseServer(server)
break break
} }
if server != nil {
CloseServer(server)
}
retry++ retry++
if retry > 5 { if retry > 5 {
t.Fatal("All attempts failed to start server") t.Fatal("All attempts failed to start server")
@@ -70,6 +70,9 @@ func NewClient(c *Config, dialer *finalmask.Dialer) (net.PacketConn, error) {
for j := range c.Domains[i].Types { for j := range c.Domains[i].Types {
types = append(types, uint16(c.Domains[i].Types[j])) types = append(types, uint16(c.Domains[i].Types[j]))
} }
if len(types) == 0 {
types = []uint16{16}
}
domain, err := NewDomain(c.Domains[i].Name, int(c.Domains[i].LenLimit), int(c.Domains[i].LabelLimit), types, uint16(c.Domains[i].Edns0)) domain, err := NewDomain(c.Domains[i].Name, int(c.Domains[i].LenLimit), int(c.Domains[i].LabelLimit), types, uint16(c.Domains[i].Edns0))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -80,6 +83,9 @@ func NewClient(c *Config, dialer *finalmask.Dialer) (net.PacketConn, error) {
for i := range c.Resolvers { for i := range c.Resolvers {
resolver, err := NewResolver(c.Resolvers[i], dialer) resolver, err := NewResolver(c.Resolvers[i], dialer)
if err != nil { if err != nil {
for _, resolver := range resolvers {
resolver.Close()
}
return nil, err return nil, err
} }
resolvers = append(resolvers, resolver) resolvers = append(resolvers, resolver)
+71 -110
View File
@@ -7,7 +7,7 @@
package xdns package xdns
import ( import (
serial "github.com/xtls/xray-core/common/serial" _ "github.com/xtls/xray-core/common/serial"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
@@ -98,10 +98,62 @@ func (x *DomainProto) GetEdns0() int32 {
return 0 return 0
} }
type ResolverProto struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResolverProto) Reset() {
*x = ResolverProto{}
mi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResolverProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResolverProto) ProtoMessage() {}
func (x *ResolverProto) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResolverProto.ProtoReflect.Descriptor instead.
func (*ResolverProto) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_xdns_config_proto_rawDescGZIP(), []int{1}
}
func (x *ResolverProto) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *ResolverProto) GetAddr() string {
if x != nil {
return x.Addr
}
return ""
}
type Config struct { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Domains []*DomainProto `protobuf:"bytes,1,rep,name=domains,proto3" json:"domains,omitempty"` Domains []*DomainProto `protobuf:"bytes,1,rep,name=domains,proto3" json:"domains,omitempty"`
Resolvers []*serial.TypedMessage `protobuf:"bytes,2,rep,name=resolvers,proto3" json:"resolvers,omitempty"` Resolvers []*ResolverProto `protobuf:"bytes,2,rep,name=resolvers,proto3" json:"resolvers,omitempty"`
ExtraPoll int32 `protobuf:"varint,3,opt,name=extra_poll,json=extraPoll,proto3" json:"extra_poll,omitempty"` ExtraPoll int32 `protobuf:"varint,3,opt,name=extra_poll,json=extraPoll,proto3" json:"extra_poll,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -109,7 +161,7 @@ type Config struct {
func (x *Config) Reset() { func (x *Config) Reset() {
*x = Config{} *x = Config{}
mi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[1] mi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -121,7 +173,7 @@ func (x *Config) String() string {
func (*Config) ProtoMessage() {} func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message { func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[1] mi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[2]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -134,7 +186,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
// Deprecated: Use Config.ProtoReflect.Descriptor instead. // Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) { func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_xdns_config_proto_rawDescGZIP(), []int{1} return file_transport_internet_finalmask_xdns_config_proto_rawDescGZIP(), []int{2}
} }
func (x *Config) GetDomains() []*DomainProto { func (x *Config) GetDomains() []*DomainProto {
@@ -144,7 +196,7 @@ func (x *Config) GetDomains() []*DomainProto {
return nil return nil
} }
func (x *Config) GetResolvers() []*serial.TypedMessage { func (x *Config) GetResolvers() []*ResolverProto {
if x != nil { if x != nil {
return x.Resolvers return x.Resolvers
} }
@@ -158,94 +210,6 @@ func (x *Config) GetExtraPoll() int32 {
return 0 return 0
} }
type TCPResolverProto struct {
state protoimpl.MessageState `protogen:"open.v1"`
Addr string `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TCPResolverProto) Reset() {
*x = TCPResolverProto{}
mi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TCPResolverProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TCPResolverProto) ProtoMessage() {}
func (x *TCPResolverProto) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TCPResolverProto.ProtoReflect.Descriptor instead.
func (*TCPResolverProto) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_xdns_config_proto_rawDescGZIP(), []int{2}
}
func (x *TCPResolverProto) GetAddr() string {
if x != nil {
return x.Addr
}
return ""
}
type UDPResolverProto struct {
state protoimpl.MessageState `protogen:"open.v1"`
Addr string `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UDPResolverProto) Reset() {
*x = UDPResolverProto{}
mi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UDPResolverProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UDPResolverProto) ProtoMessage() {}
func (x *UDPResolverProto) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_xdns_config_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UDPResolverProto.ProtoReflect.Descriptor instead.
func (*UDPResolverProto) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_xdns_config_proto_rawDescGZIP(), []int{3}
}
func (x *UDPResolverProto) GetAddr() string {
if x != nil {
return x.Addr
}
return ""
}
var File_transport_internet_finalmask_xdns_config_proto protoreflect.FileDescriptor var File_transport_internet_finalmask_xdns_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_xdns_config_proto_rawDesc = "" + const file_transport_internet_finalmask_xdns_config_proto_rawDesc = "" +
@@ -257,16 +221,15 @@ const file_transport_internet_finalmask_xdns_config_proto_rawDesc = "" +
"\vlabel_limit\x18\x03 \x01(\x05R\n" + "\vlabel_limit\x18\x03 \x01(\x05R\n" +
"labelLimit\x12\x14\n" + "labelLimit\x12\x14\n" +
"\x05types\x18\x04 \x03(\x05R\x05types\x12\x14\n" + "\x05types\x18\x04 \x03(\x05R\x05types\x12\x14\n" +
"\x05edns0\x18\x05 \x01(\x05R\x05edns0\"\xb6\x01\n" + "\x05edns0\x18\x05 \x01(\x05R\x05edns0\"7\n" +
"\rResolverProto\x12\x12\n" +
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
"\x04addr\x18\x02 \x01(\tR\x04addr\"\xcb\x01\n" +
"\x06Config\x12M\n" + "\x06Config\x12M\n" +
"\adomains\x18\x01 \x03(\v23.xray.transport.internet.finalmask.xdns.DomainProtoR\adomains\x12>\n" + "\adomains\x18\x01 \x03(\v23.xray.transport.internet.finalmask.xdns.DomainProtoR\adomains\x12S\n" +
"\tresolvers\x18\x02 \x03(\v2 .xray.common.serial.TypedMessageR\tresolvers\x12\x1d\n" + "\tresolvers\x18\x02 \x03(\v25.xray.transport.internet.finalmask.xdns.ResolverProtoR\tresolvers\x12\x1d\n" +
"\n" + "\n" +
"extra_poll\x18\x03 \x01(\x05R\textraPoll\"&\n" + "extra_poll\x18\x03 \x01(\x05R\textraPollB\x94\x01\n" +
"\x10TCPResolverProto\x12\x12\n" +
"\x04addr\x18\x01 \x01(\tR\x04addr\"&\n" +
"\x10UDPResolverProto\x12\x12\n" +
"\x04addr\x18\x01 \x01(\tR\x04addrB\x94\x01\n" +
"*com.xray.transport.internet.finalmask.xdnsP\x01Z;github.com/xtls/xray-core/transport/internet/finalmask/xdns\xaa\x02&Xray.Transport.Internet.Finalmask.Xdnsb\x06proto3" "*com.xray.transport.internet.finalmask.xdnsP\x01Z;github.com/xtls/xray-core/transport/internet/finalmask/xdns\xaa\x02&Xray.Transport.Internet.Finalmask.Xdnsb\x06proto3"
var ( var (
@@ -281,17 +244,15 @@ func file_transport_internet_finalmask_xdns_config_proto_rawDescGZIP() []byte {
return file_transport_internet_finalmask_xdns_config_proto_rawDescData return file_transport_internet_finalmask_xdns_config_proto_rawDescData
} }
var file_transport_internet_finalmask_xdns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_transport_internet_finalmask_xdns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_transport_internet_finalmask_xdns_config_proto_goTypes = []any{ var file_transport_internet_finalmask_xdns_config_proto_goTypes = []any{
(*DomainProto)(nil), // 0: xray.transport.internet.finalmask.xdns.DomainProto (*DomainProto)(nil), // 0: xray.transport.internet.finalmask.xdns.DomainProto
(*Config)(nil), // 1: xray.transport.internet.finalmask.xdns.Config (*ResolverProto)(nil), // 1: xray.transport.internet.finalmask.xdns.ResolverProto
(*TCPResolverProto)(nil), // 2: xray.transport.internet.finalmask.xdns.TCPResolverProto (*Config)(nil), // 2: xray.transport.internet.finalmask.xdns.Config
(*UDPResolverProto)(nil), // 3: xray.transport.internet.finalmask.xdns.UDPResolverProto
(*serial.TypedMessage)(nil), // 4: xray.common.serial.TypedMessage
} }
var file_transport_internet_finalmask_xdns_config_proto_depIdxs = []int32{ var file_transport_internet_finalmask_xdns_config_proto_depIdxs = []int32{
0, // 0: xray.transport.internet.finalmask.xdns.Config.domains:type_name -> xray.transport.internet.finalmask.xdns.DomainProto 0, // 0: xray.transport.internet.finalmask.xdns.Config.domains:type_name -> xray.transport.internet.finalmask.xdns.DomainProto
4, // 1: xray.transport.internet.finalmask.xdns.Config.resolvers:type_name -> xray.common.serial.TypedMessage 1, // 1: xray.transport.internet.finalmask.xdns.Config.resolvers:type_name -> xray.transport.internet.finalmask.xdns.ResolverProto
2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension type_name
@@ -310,7 +271,7 @@ func file_transport_internet_finalmask_xdns_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_xdns_config_proto_rawDesc), len(file_transport_internet_finalmask_xdns_config_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_xdns_config_proto_rawDesc), len(file_transport_internet_finalmask_xdns_config_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 4, NumMessages: 3,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
@@ -16,16 +16,13 @@ message DomainProto {
int32 edns0 = 5; int32 edns0 = 5;
} }
message ResolverProto {
string type = 1;
string addr = 2;
}
message Config { message Config {
repeated DomainProto domains = 1; repeated DomainProto domains = 1;
repeated xray.common.serial.TypedMessage resolvers = 2; repeated ResolverProto resolvers = 2;
int32 extra_poll = 3; int32 extra_poll = 3;
}
message TCPResolverProto {
string addr = 1;
}
message UDPResolverProto {
string addr = 1;
} }
+7 -12
View File
@@ -4,7 +4,6 @@ import (
"errors" "errors"
"net" "net"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet/finalmask" "github.com/xtls/xray-core/transport/internet/finalmask"
) )
@@ -15,17 +14,13 @@ type Resolver interface {
Close() Close()
} }
func NewResolver(proto *serial.TypedMessage, dialer *finalmask.Dialer) (Resolver, error) { func NewResolver(config *ResolverProto, dialer *finalmask.Dialer) (Resolver, error) {
config, err := proto.GetInstance() switch config.Type {
if err != nil { case "tcp":
return nil, err return NewTCPResolver(config, dialer)
} case "udp":
switch v := config.(type) { return NewUDPResolver(config, dialer)
case *TCPResolverProto:
return NewTCPResolver(v, dialer)
case *UDPResolverProto:
return NewUDPResolver(v, dialer)
default: default:
return nil, errors.New("unknown proto") return nil, errors.New("unknown type")
} }
} }
@@ -24,7 +24,7 @@ type TCPResolver struct {
mu sync.Mutex mu sync.Mutex
} }
func NewTCPResolver(config *TCPResolverProto, dialer *finalmask.Dialer) (Resolver, error) { func NewTCPResolver(config *ResolverProto, dialer *finalmask.Dialer) (Resolver, error) {
dest, err := net.ParseDestination("tcp:" + config.Addr) dest, err := net.ParseDestination("tcp:" + config.Addr)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -130,13 +130,15 @@ func (r *TCPResolver) Send(p []byte) {
func (r *TCPResolver) Close() { func (r *TCPResolver) Close() {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock()
if r.closed() { if r.closed() {
r.mu.Unlock()
return return
} }
close(r.closeCh) close(r.closeCh)
if r.conn != nil { conn := r.conn
_ = r.conn.Close() r.mu.Unlock()
if conn != nil {
_ = conn.Close()
} }
r.wg.Wait() r.wg.Wait()
close(r.readCh) close(r.readCh)
@@ -22,7 +22,7 @@ type UDPResolver struct {
mu sync.Mutex mu sync.Mutex
} }
func NewUDPResolver(config *UDPResolverProto, dialer *finalmask.Dialer) (Resolver, error) { func NewUDPResolver(config *ResolverProto, dialer *finalmask.Dialer) (Resolver, error) {
dest, err := net.ParseDestination("udp:" + config.Addr) dest, err := net.ParseDestination("udp:" + config.Addr)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -117,13 +117,15 @@ func (r *UDPResolver) Send(p []byte) {
func (r *UDPResolver) Close() { func (r *UDPResolver) Close() {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock()
if r.closed() { if r.closed() {
r.mu.Unlock()
return return
} }
close(r.closeCh) close(r.closeCh)
if r.conn != nil { conn := r.conn
_ = r.conn.Close() r.mu.Unlock()
if conn != nil {
_ = conn.Close()
} }
r.wg.Wait() r.wg.Wait()
close(r.readCh) close(r.readCh)
@@ -52,6 +52,9 @@ func NewServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
for j := range c.Domains[i].Types { for j := range c.Domains[i].Types {
types = append(types, uint16(c.Domains[i].Types[j])) types = append(types, uint16(c.Domains[i].Types[j]))
} }
if len(types) == 0 {
types = []uint16{1, 5, 16, 28}
}
domain, err := NewDomain(c.Domains[i].Name, int(c.Domains[i].LenLimit), int(c.Domains[i].LabelLimit), types, uint16(c.Domains[i].Edns0)) domain, err := NewDomain(c.Domains[i].Name, int(c.Domains[i].LenLimit), int(c.Domains[i].LabelLimit), types, uint16(c.Domains[i].Edns0))
if err != nil { if err != nil {
return nil, err return nil, err
+1 -1
View File
@@ -127,7 +127,7 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in
if streamSettings.FinalMask != nil { if streamSettings.FinalMask != nil {
c, err = streamSettings.FinalMask.DialTCP(gctx, net.TCPDestination(address, port)) c, err = streamSettings.FinalMask.DialTCP(gctx, net.TCPDestination(address, port))
} else { } else {
c, err = internet.DialSystem(ctx, dest, streamSettings.SocketSettings) c, err = internet.DialSystem(gctx, net.TCPDestination(address, port), streamSettings.SocketSettings)
} }
if err == nil { if err == nil {
if tlsConfig != nil { if tlsConfig != nil {