Compare commits

..

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
447b4438ee Restore inbound and content tag setting before dispatch
As identified in issue #4760, PR #4030 commented out lines that set
inbound and content tags from routing info before dispatch. This broke
domain-based routing for WireGuard connections.

The fix adds back these lines (with mutex-protected access) positioned
right before the Dispatch call, ensuring routing configuration is
properly passed for domain-based routing rules to work.

This addresses the feedback that uncommenting those lines fixes routing.

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-01-11 09:13:06 +00:00
copilot-swe-agent[bot]
1f925778ed Complete WireGuard domain routing fix with tests and review
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-01-10 22:18:32 +00:00
copilot-swe-agent[bot]
06dee57f8a Add mutex protection for WireGuard routing info
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-01-10 22:14:55 +00:00
copilot-swe-agent[bot]
7f8928272b Initial plan 2026-01-10 22:04:22 +00:00
2 changed files with 29 additions and 19 deletions

View File

@@ -224,8 +224,7 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
switchToDirectCopy = &w.trafficState.Outbound.DownlinkReaderDirectCopy
}
if *switchToDirectCopy && w.input == nil {
// Already switched to direct copy mode
if *switchToDirectCopy {
if w.directReadCounter != nil {
w.directReadCounter.Add(int64(buffer.Len()))
}
@@ -258,18 +257,11 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
if *switchToDirectCopy {
// XTLS Vision processes TLS-like conn's input and rawInput
// input contains decrypted application data - safe to merge
if inputBuffer, err := buf.ReadFrom(w.input); err == nil && !inputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, inputBuffer)
}
// rawInput may contain encrypted bytes for the next TLS record
// If rawInput is not empty, we should NOT switch to direct mode yet
// because those bytes need to be processed by the TLS layer first
if w.rawInput != nil && w.rawInput.Len() > 0 {
// rawInput has pending data - defer direct copy to next read
// *switchToDirectCopy remains true (unchanged), so we will retry on the next ReadMultiBuffer call
// This ensures we don't mix encrypted bytes with application data
return buffer, err
if rawInputBuffer, err := buf.ReadFrom(w.rawInput); err == nil && !rawInputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer)
}
*w.input = bytes.Reader{} // release memory
w.input = nil

View File

@@ -4,6 +4,7 @@ import (
"context"
goerrors "errors"
"io"
"sync"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
@@ -26,6 +27,7 @@ var nullDestination = net.TCPDestination(net.AnyIP, 0)
type Server struct {
bindServer *netBindServer
infoMu sync.RWMutex
info routingInfo
policyManager policy.Manager
}
@@ -78,12 +80,14 @@ func (*Server) Network() []net.Network {
// Process implements proxy.Inbound.
func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
s.infoMu.Lock()
s.info = routingInfo{
ctx: ctx,
dispatcher: dispatcher,
inboundTag: session.InboundFromContext(ctx),
contentTag: session.ContentFromContext(ctx),
}
s.infoMu.Unlock()
ep, err := s.bindServer.ParseEndpoint(conn.RemoteAddr().String())
if err != nil {
@@ -120,18 +124,23 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
}
func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
if s.info.dispatcher == nil {
errors.LogError(s.info.ctx, "unexpected: dispatcher == nil")
// Make a thread-safe copy of routing info
s.infoMu.RLock()
info := s.info
s.infoMu.RUnlock()
if info.dispatcher == nil {
errors.LogError(info.ctx, "unexpected: dispatcher == nil")
return
}
defer conn.Close()
ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.info.ctx))
ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(info.ctx))
sid := session.NewID()
ctx = c.ContextWithID(ctx, sid)
inbound := session.Inbound{} // since promiscuousModeHandler mixed-up context, we shallow copy inbound (tag) and content (configs)
if s.info.inboundTag != nil {
inbound = *s.info.inboundTag
if info.inboundTag != nil {
inbound = *info.inboundTag
}
inbound.Name = "wireguard"
inbound.CanSpliceCopy = 3
@@ -141,8 +150,8 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
// Currently we have no way to link to the original source address
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
ctx = session.ContextWithInbound(ctx, &inbound)
if s.info.contentTag != nil {
ctx = session.ContextWithContent(ctx, s.info.contentTag)
if info.contentTag != nil {
ctx = session.ContextWithContent(ctx, info.contentTag)
}
ctx = session.SubContextFromMuxInbound(ctx)
@@ -156,7 +165,16 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
Reason: "",
})
link, err := s.info.dispatcher.Dispatch(ctx, dest)
// Set inbound and content tags from routing info for proper routing
// These were commented out in PR #4030 but are needed for domain-based routing
if info.inboundTag != nil {
ctx = session.ContextWithInbound(ctx, info.inboundTag)
}
if info.contentTag != nil {
ctx = session.ContextWithContent(ctx, info.contentTag)
}
link, err := info.dispatcher.Dispatch(ctx, dest)
if err != nil {
errors.LogErrorInner(ctx, err, "dispatch connection")
}