From ea7f8ef0e7561d9d9bfe28633a4e3cf4b6ac3a0d Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Mon, 4 Aug 2025 23:57:23 -0400 Subject: [PATCH 1/4] Try fix Wireguard inbound context sharing problem --- proxy/wireguard/server.go | 50 +++++++++++++++------------------------ 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/proxy/wireguard/server.go b/proxy/wireguard/server.go index bdf2756854fd..ecf355414c68 100644 --- a/proxy/wireguard/server.go +++ b/proxy/wireguard/server.go @@ -7,6 +7,7 @@ import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" + c "github.com/xtls/xray-core/common/ctx" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/net" @@ -30,10 +31,8 @@ type Server struct { } type routingInfo struct { - ctx context.Context dispatcher routing.Dispatcher inboundTag *session.Inbound - outboundTag *session.Outbound contentTag *session.Content } @@ -78,18 +77,9 @@ 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 { - inbound := session.InboundFromContext(ctx) - inbound.Name = "wireguard" - inbound.CanSpliceCopy = 3 - outbounds := session.OutboundsFromContext(ctx) - ob := outbounds[len(outbounds)-1] - s.info = routingInfo{ - ctx: core.ToBackgroundDetachedContext(ctx), dispatcher: dispatcher, inboundTag: session.InboundFromContext(ctx), - outboundTag: ob, - contentTag: session.ContentFromContext(ctx), } ep, err := s.bindServer.ParseEndpoint(conn.RemoteAddr().String()) @@ -128,12 +118,26 @@ 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") + errors.LogError(context.Background(), "unexpected: dispatcher == nil") return } defer conn.Close() - ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.info.ctx)) + ctx, cancel := context.WithCancel(context.Background()) + sid := session.NewID() + ctx = c.ContextWithID(ctx, sid) + if s.info.inboundTag != nil { + ctx = session.ContextWithInbound(ctx, s.info.inboundTag) + } + if s.info.contentTag != nil { + ctx = session.ContextWithContent(ctx, s.info.contentTag) + } + inbound := session.InboundFromContext(ctx) + inbound.Name = "wireguard" + inbound.CanSpliceCopy = 3 + outbounds := []*session.Outbound{{}} + ctx = session.ContextWithOutbounds(ctx, outbounds) // since promiscuousModeHandler mixed-up context, we need to define new Outbounds here + plcy := s.policyManager.ForLevel(0) timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) @@ -144,25 +148,9 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) { Reason: "", }) - if s.info.inboundTag != nil { - ctx = session.ContextWithInbound(ctx, s.info.inboundTag) - } - - // what's this? - // Session information should not be shared between different connections - // why reuse them in server level? This will cause incorrect destoverride and unexpected routing behavior. - // Disable it temporarily. Maybe s.info should be removed. - - // if s.info.outboundTag != nil { - // ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{s.info.outboundTag}) - // } - // if s.info.contentTag != nil { - // ctx = session.ContextWithContent(ctx, s.info.contentTag) - // } - link, err := s.info.dispatcher.Dispatch(ctx, dest) if err != nil { - errors.LogErrorInner(s.info.ctx, err, "dispatch connection") + errors.LogErrorInner(ctx, err, "dispatch connection") } defer cancel() @@ -188,7 +176,7 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) { if err := task.Run(ctx, requestDonePost, responseDone); err != nil { common.Interrupt(link.Reader) common.Interrupt(link.Writer) - errors.LogDebugInner(s.info.ctx, err, "connection ends") + errors.LogDebugInner(ctx, err, "connection ends") return } } From cca0c446d041c6a8d6ee03731fabfca18c90f68e Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 10 Aug 2025 18:07:22 -0400 Subject: [PATCH 2/4] Shallow copy inbound and content --- common/mux/server.go | 4 +--- common/session/context.go | 32 +++++++++++--------------------- common/session/session.go | 16 +++++++++------- proxy/wireguard/server.go | 14 ++++++++------ 4 files changed, 29 insertions(+), 37 deletions(-) diff --git a/common/mux/server.go b/common/mux/server.go index 30dcf06e4fce..99a144a51e61 100644 --- a/common/mux/server.go +++ b/common/mux/server.go @@ -118,9 +118,7 @@ func (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.Bu } func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error { - // deep-clone outbounds because it is going to be mutated concurrently - // (Target and OriginalTarget) - ctx = session.ContextCloneOutboundsAndContent(ctx) + ctx = session.SubContextFromMuxInbound(ctx) errors.LogInfo(ctx, "received request for ", meta.Target) { msg := &log.AccessMessage{ diff --git a/common/session/context.go b/common/session/context.go index 0e2558c2c26f..ba3530b5bb79 100644 --- a/common/session/context.go +++ b/common/session/context.go @@ -16,15 +16,15 @@ const ( inboundSessionKey ctx.SessionKey = 1 outboundSessionKey ctx.SessionKey = 2 contentSessionKey ctx.SessionKey = 3 - muxPreferredSessionKey ctx.SessionKey = 4 - sockoptSessionKey ctx.SessionKey = 5 - trackedConnectionErrorKey ctx.SessionKey = 6 - dispatcherKey ctx.SessionKey = 7 - timeoutOnlyKey ctx.SessionKey = 8 - allowedNetworkKey ctx.SessionKey = 9 - handlerSessionKey ctx.SessionKey = 10 - mitmAlpn11Key ctx.SessionKey = 11 - mitmServerNameKey ctx.SessionKey = 12 + muxPreferredSessionKey ctx.SessionKey = 4 // unused + sockoptSessionKey ctx.SessionKey = 5 // used by dokodemo to only receive sockopt.Mark + trackedConnectionErrorKey ctx.SessionKey = 6 // used by observer to get outbound error + dispatcherKey ctx.SessionKey = 7 // used by ss2022 inbounds to get dispatcher + timeoutOnlyKey ctx.SessionKey = 8 // mux context's child contexts to only cancel when its own traffic times out + allowedNetworkKey ctx.SessionKey = 9 // muxcool server control incoming request tcp/udp + handlerSessionKey ctx.SessionKey = 10 // unused + mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer + mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer ) func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context { @@ -42,18 +42,8 @@ func ContextWithOutbounds(ctx context.Context, outbounds []*Outbound) context.Co return context.WithValue(ctx, outboundSessionKey, outbounds) } -func ContextCloneOutboundsAndContent(ctx context.Context) context.Context { - outbounds := OutboundsFromContext(ctx) - newOutbounds := make([]*Outbound, len(outbounds)) - for i, ob := range outbounds { - if ob == nil { - continue - } - - // copy outbound by value - v := *ob - newOutbounds[i] = &v - } +func SubContextFromMuxInbound(ctx context.Context) context.Context { + newOutbounds := []*Outbound{{}} content := ContentFromContext(ctx) newContent := Content{} diff --git a/common/session/session.go b/common/session/session.go index 272aa5fbc9c2..aad31cdfeba1 100644 --- a/common/session/session.go +++ b/common/session/session.go @@ -48,9 +48,9 @@ type Inbound struct { User *protocol.MemoryUser // VlessRoute is the user-sent VLESS UUID's last byte. VlessRoute net.Port - // Conn is actually internet.Connection. May be nil. + // Used by splice copy. Conn is actually internet.Connection. May be nil. Conn net.Conn - // Timer of the inbound buf copier. May be nil. + // Used by splice copy. Timer of the inbound buf copier. May be nil. Timer *signal.ActivityTimer // CanSpliceCopy is a property for this connection // 1 = can, 2 = after processing protocol info should be able to, 3 = cannot @@ -69,31 +69,33 @@ type Outbound struct { Tag string // Name of the outbound proxy that handles the connection. Name string - // Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings + // Unused. Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings Conn net.Conn // CanSpliceCopy is a property for this connection // 1 = can, 2 = after processing protocol info should be able to, 3 = cannot CanSpliceCopy int } -// SniffingRequest controls the behavior of content sniffing. +// SniffingRequest controls the behavior of content sniffing. They are from inbound config. Read-only type SniffingRequest struct { - ExcludeForDomain []string // read-only once set - OverrideDestinationForProtocol []string // read-only once set + ExcludeForDomain []string + OverrideDestinationForProtocol []string Enabled bool MetadataOnly bool RouteOnly bool } -// Content is the metadata of the connection content. +// Content is the metadata of the connection content. Mainly used for routing. type Content struct { // Protocol of current content. Protocol string SniffingRequest SniffingRequest + // HTTP traffic sniffed headers Attributes map[string]string + // SkipDNSResolve is set from DNS module. the DOH remote server maybe a domain name, this prevents cycle resolving dead loop SkipDNSResolve bool } diff --git a/proxy/wireguard/server.go b/proxy/wireguard/server.go index ecf355414c68..97442397fc35 100644 --- a/proxy/wireguard/server.go +++ b/proxy/wireguard/server.go @@ -126,17 +126,19 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) { ctx, cancel := context.WithCancel(context.Background()) 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 { - ctx = session.ContextWithInbound(ctx, s.info.inboundTag) + inbound = *s.info.inboundTag } + inbound.Name = "wireguard" + inbound.CanSpliceCopy = 3 + inbound.Source = net.DestinationFromAddr(conn.RemoteAddr()) + inbound.Gateway = net.DestinationFromAddr(conn.LocalAddr()) + ctx = session.ContextWithInbound(ctx, &inbound) if s.info.contentTag != nil { ctx = session.ContextWithContent(ctx, s.info.contentTag) } - inbound := session.InboundFromContext(ctx) - inbound.Name = "wireguard" - inbound.CanSpliceCopy = 3 - outbounds := []*session.Outbound{{}} - ctx = session.ContextWithOutbounds(ctx, outbounds) // since promiscuousModeHandler mixed-up context, we need to define new Outbounds here + ctx = session.SubContextFromMuxInbound(ctx) plcy := s.policyManager.ForLevel(0) timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) From 0271f2fbbc632e17e3248f6f5343a88d332dc15e Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 10 Aug 2025 20:42:13 -0400 Subject: [PATCH 3/4] Fix context passing --- proxy/wireguard/server.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/proxy/wireguard/server.go b/proxy/wireguard/server.go index 97442397fc35..cf3512016f56 100644 --- a/proxy/wireguard/server.go +++ b/proxy/wireguard/server.go @@ -31,6 +31,7 @@ type Server struct { } type routingInfo struct { + ctx context.Context dispatcher routing.Dispatcher inboundTag *session.Inbound contentTag *session.Content @@ -78,8 +79,10 @@ 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.info = routingInfo{ - dispatcher: dispatcher, - inboundTag: session.InboundFromContext(ctx), + ctx: ctx, + dispatcher: dispatcher, + inboundTag: session.InboundFromContext(ctx), + contentTag: session.ContentFromContext(ctx), } ep, err := s.bindServer.ParseEndpoint(conn.RemoteAddr().String()) @@ -118,12 +121,12 @@ 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(context.Background(), "unexpected: dispatcher == nil") + errors.LogError(s.info.ctx, "unexpected: dispatcher == nil") return } defer conn.Close() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.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) From 8f653eeea9c53f576bc291a73848ef1ceffb94ca Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 17 Aug 2025 10:35:55 -0400 Subject: [PATCH 4/4] Add notes for source address --- proxy/wireguard/server.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/proxy/wireguard/server.go b/proxy/wireguard/server.go index cf3512016f56..6144f5c75144 100644 --- a/proxy/wireguard/server.go +++ b/proxy/wireguard/server.go @@ -135,8 +135,11 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) { } inbound.Name = "wireguard" inbound.CanSpliceCopy = 3 - inbound.Source = net.DestinationFromAddr(conn.RemoteAddr()) - inbound.Gateway = net.DestinationFromAddr(conn.LocalAddr()) + + // overwrite the source to use the tun address for each sub context. + // Since gvisor.ForwarderRequest doesn't provide any info to associate the sub-context with the Parent context + // 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)