From 4dd45c84175324af614fe57b80617b2a0f02fec1 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:54:58 +0800 Subject: [PATCH 1/7] DNS outbound: Fix own-link detection for XUDP sessions --- proxy/dns/dns.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/dns/dns.go b/proxy/dns/dns.go index efeb56cda572..079ac750d7ca 100644 --- a/proxy/dns/dns.go +++ b/proxy/dns/dns.go @@ -215,7 +215,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet. } if session.TimeoutOnlyFromContext(ctx) { - ctx = context.Background() + ctx = context.WithoutCancel(ctx) } ctx, cancel := context.WithCancel(ctx) From f30526a60fb94970c5afa27a854a3b098bcead0f Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Sat, 4 Jul 2026 01:20:27 +0800 Subject: [PATCH 2/7] more outbounds --- proxy/freedom/freedom.go | 15 ++------------- proxy/http/client.go | 15 ++------------- proxy/hysteria/client.go | 15 ++------------- proxy/shadowsocks/client.go | 15 ++------------- proxy/shadowsocks_2022/outbound.go | 2 +- proxy/socks/client.go | 15 ++------------- proxy/trojan/client.go | 15 ++------------- proxy/vless/outbound/outbound.go | 15 ++------------- proxy/vmess/outbound/outbound.go | 15 ++------------- proxy/wireguard/client.go | 15 ++------------- 10 files changed, 19 insertions(+), 118 deletions(-) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 9cdce38a3406..4b94af5a02b0 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -388,20 +388,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte defer conn.Close() errors.LogInfo(ctx, "connection opened to ", destination, ", local endpoint ", conn.LocalAddr(), ", remote endpoint ", conn.RemoteAddr()) - var newCtx context.Context - var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - newCtx, newCancel = context.WithCancel(context.Background()) + ctx = context.WithoutCancel(ctx) } plcy := h.policy() ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, func() { - cancel() - if newCancel != nil { - newCancel() - } - }, plcy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) requestDone := func() error { defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly) @@ -462,10 +455,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return nil } - if newCtx != nil { - ctx = newCtx - } - if err := task.Run(ctx, requestDone, task.OnSuccess(responseDone, task.Close(output))); err != nil { return errors.New("connection ends").Base(err) } diff --git a/proxy/http/client.go b/proxy/http/client.go index 0e50edba3b61..540286e1766b 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -127,19 +127,12 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter p = c.policyManager.ForLevel(user.Level) } - var newCtx context.Context - var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - newCtx, newCancel = context.WithCancel(context.Background()) + ctx = context.WithoutCancel(ctx) } ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, func() { - cancel() - if newCancel != nil { - newCancel() - } - }, p.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) requestFunc := func() error { defer timer.SetTimeout(p.Timeouts.DownlinkOnly) @@ -151,10 +144,6 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) } - if newCtx != nil { - ctx = newCtx - } - responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer)) if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { return errors.New("connection ends").Base(err) diff --git a/proxy/hysteria/client.go b/proxy/hysteria/client.go index 5b602c342de8..8d5634bc12ba 100644 --- a/proxy/hysteria/client.go +++ b/proxy/hysteria/client.go @@ -67,24 +67,13 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter defer conn.Close() errors.LogInfo(ctx, "tunneling request to ", target, " via ", target.Network, ":", c.server.Destination.NetAddr()) - var newCtx context.Context - var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - newCtx, newCancel = context.WithCancel(context.Background()) + ctx = context.WithoutCancel(ctx) } sessionPolicy := c.policyManager.ForLevel(0) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, func() { - cancel() - if newCancel != nil { - newCancel() - } - }, sessionPolicy.Timeouts.ConnectionIdle) - - if newCtx != nil { - ctx = newCtx - } + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) if target.Network == net.Network_TCP { requestDone := func() error { diff --git a/proxy/shadowsocks/client.go b/proxy/shadowsocks/client.go index 29cb04566dd2..075b5682457a 100644 --- a/proxy/shadowsocks/client.go +++ b/proxy/shadowsocks/client.go @@ -95,24 +95,13 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } request.User = user - var newCtx context.Context - var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - newCtx, newCancel = context.WithCancel(context.Background()) + ctx = context.WithoutCancel(ctx) } sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, func() { - cancel() - if newCancel != nil { - newCancel() - } - }, sessionPolicy.Timeouts.ConnectionIdle) - - if newCtx != nil { - ctx = newCtx - } + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) if request.Command == protocol.RequestCommandTCP { requestDone := func() error { diff --git a/proxy/shadowsocks_2022/outbound.go b/proxy/shadowsocks_2022/outbound.go index f1195950dfd9..dd4efce55a88 100644 --- a/proxy/shadowsocks_2022/outbound.go +++ b/proxy/shadowsocks_2022/outbound.go @@ -84,7 +84,7 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int } if session.TimeoutOnlyFromContext(ctx) { - ctx, _ = context.WithCancel(context.Background()) + ctx = context.WithoutCancel(ctx) } if network == net.Network_TCP { diff --git a/proxy/socks/client.go b/proxy/socks/client.go index eac3f35b8214..e74776508883 100644 --- a/proxy/socks/client.go +++ b/proxy/socks/client.go @@ -117,19 +117,12 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter errors.LogInfoInner(ctx, err, "failed to clear deadline after handshake") } - var newCtx context.Context - var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - newCtx, newCancel = context.WithCancel(context.Background()) + ctx = context.WithoutCancel(ctx) } ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, func() { - cancel() - if newCancel != nil { - newCancel() - } - }, p.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) var requestFunc func() error var responseFunc func() error @@ -162,10 +155,6 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } } - if newCtx != nil { - ctx = newCtx - } - responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer)) if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { return errors.New("connection ends").Base(err) diff --git a/proxy/trojan/client.go b/proxy/trojan/client.go index 4af8c019d0f7..0cd450ad580c 100644 --- a/proxy/trojan/client.go +++ b/proxy/trojan/client.go @@ -81,20 +81,13 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return errors.New("user account is not valid") } - var newCtx context.Context - var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - newCtx, newCancel = context.WithCancel(context.Background()) + ctx = context.WithoutCancel(ctx) } sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, func() { - cancel() - if newCancel != nil { - newCancel() - } - }, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) postRequest := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) @@ -150,10 +143,6 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) } - if newCtx != nil { - ctx = newCtx - } - responseDoneAndCloseWriter := task.OnSuccess(getResponse, task.Close(link.Writer)) if err := task.Run(ctx, postRequest, responseDoneAndCloseWriter); err != nil { return errors.New("connection ends").Base(err) diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go index ac087c6372b1..3103b2eb675f 100644 --- a/proxy/vless/outbound/outbound.go +++ b/proxy/vless/outbound/outbound.go @@ -294,20 +294,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte ob.CanSpliceCopy = 3 } - var newCtx context.Context - var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - newCtx, newCancel = context.WithCancel(context.Background()) + ctx = context.WithoutCancel(ctx) } sessionPolicy := h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, func() { - cancel() - if newCancel != nil { - newCancel() - } - }, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) clientReader := link.Reader // .(*pipe.Reader) clientWriter := link.Writer // .(*pipe.Writer) @@ -413,10 +406,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return nil } - if newCtx != nil { - ctx = newCtx - } - if err := task.Run(ctx, postRequest, task.OnSuccess(getResponse, task.Close(clientWriter))); err != nil { return errors.New("connection ends").Base(err).AtInfo() } diff --git a/proxy/vmess/outbound/outbound.go b/proxy/vmess/outbound/outbound.go index 903e73ce37b1..65ec65ac6fd7 100644 --- a/proxy/vmess/outbound/outbound.go +++ b/proxy/vmess/outbound/outbound.go @@ -131,22 +131,15 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte behaviorSeed := crc64.Checksum(hashkdf.Sum(nil), crc64.MakeTable(crc64.ISO)) - var newCtx context.Context - var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - newCtx, newCancel = context.WithCancel(context.Background()) + ctx = context.WithoutCancel(ctx) } session := encoding.NewClientSession(ctx, int64(behaviorSeed)) sessionPolicy := h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, func() { - cancel() - if newCancel != nil { - newCancel() - } - }, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) if request.Command == protocol.RequestCommandUDP && h.cone && request.Port != 53 && request.Port != 443 { request.Command = protocol.RequestCommandMux @@ -212,10 +205,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return buf.Copy(bodyReader, output, buf.UpdateActivity(timer)) } - if newCtx != nil { - ctx = newCtx - } - responseDonePost := task.OnSuccess(responseDone, task.Close(output)) if err := task.Run(ctx, requestDone, responseDonePost); err != nil { return errors.New("connection ends").Base(err) diff --git a/proxy/wireguard/client.go b/proxy/wireguard/client.go index 4491886abd4d..efcb4a9dfb04 100644 --- a/proxy/wireguard/client.go +++ b/proxy/wireguard/client.go @@ -165,24 +165,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return errors.New("invalid target ", ob.Target) } - var newCtx context.Context - var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - newCtx, newCancel = context.WithCancel(context.Background()) + ctx = context.WithoutCancel(ctx) } sessionPolicy := h.policyManager.ForLevel(0) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, func() { - cancel() - if newCancel != nil { - newCancel() - } - }, sessionPolicy.Timeouts.ConnectionIdle) - - if newCtx != nil { - ctx = newCtx - } + timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) var reader buf.Reader var writer buf.Writer From b53f8b56a47258773f70267b7e2f4bd1962dde5b Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Wed, 8 Jul 2026 20:56:21 +0800 Subject: [PATCH 3/7] remove IndependentCancelCtx --- common/session/context.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/session/context.go b/common/session/context.go index abe4219ca935..df445ab55b64 100644 --- a/common/session/context.go +++ b/common/session/context.go @@ -10,9 +10,6 @@ import ( "github.com/xtls/xray-core/features/routing" ) -//go:linkname IndependentCancelCtx context.newCancelCtx -func IndependentCancelCtx(parent context.Context) context.Context - const ( inboundSessionKey ctx.SessionKey = 1 outboundSessionKey ctx.SessionKey = 2 From 723ef2b8e69ce120acadf3d3881bcfe51f629509 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:44:22 +0800 Subject: [PATCH 4/7] refactor (remove TimeoutOnly) --- common/mux/server.go | 2 +- common/session/context.go | 37 ++++++++++-------------------- proxy/dns/dns.go | 4 ---- proxy/freedom/freedom.go | 4 ---- proxy/http/client.go | 4 ---- proxy/hysteria/client.go | 4 ---- proxy/shadowsocks/client.go | 4 ---- proxy/shadowsocks_2022/outbound.go | 4 ---- proxy/socks/client.go | 4 ---- proxy/trojan/client.go | 4 ---- proxy/vless/outbound/outbound.go | 4 ---- proxy/vmess/outbound/outbound.go | 4 ---- proxy/wireguard/client.go | 4 ---- transport/internet/grpc/dial.go | 2 +- 14 files changed, 14 insertions(+), 71 deletions(-) diff --git a/common/mux/server.go b/common/mux/server.go index d1cdac113e44..0cf5851cf351 100644 --- a/common/mux/server.go +++ b/common/mux/server.go @@ -227,7 +227,7 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, errors.LogInfoInner(ctx, err, "XUDP hit ", meta.GlobalID) } if mb != nil { - ctx = session.ContextWithTimeoutOnly(ctx, true) + ctx = context.WithoutCancel(ctx) // Actually, it won't return an error in Xray-core's implementations. link, err := w.dispatcher.Dispatch(ctx, meta.Target) if err != nil { diff --git a/common/session/context.go b/common/session/context.go index df445ab55b64..5605980d79a8 100644 --- a/common/session/context.go +++ b/common/session/context.go @@ -11,20 +11,18 @@ import ( ) const ( - inboundSessionKey ctx.SessionKey = 1 - outboundSessionKey ctx.SessionKey = 2 - contentSessionKey ctx.SessionKey = 3 - isReverseMuxKey ctx.SessionKey = 4 // is reverse mux - 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 - fullHandlerKey ctx.SessionKey = 10 // outbound gets full handler - mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer - mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer - - streamSettingsKey ctx.SessionKey = 13 + inboundSessionKey ctx.SessionKey = iota + outboundSessionKey + contentSessionKey + isReverseMuxKey // is reverse mux + sockoptSessionKey // used by dokodemo to only receive sockopt.Mark + trackedConnectionErrorKey // used by observer to get outbound error + dispatcherKey // used by ss2022 inbounds to get dispatcher + allowedNetworkKey // muxcool server control incoming request tcp/udp + fullHandlerKey // outbound gets full handler + mitmAlpn11Key // used by TLS dialer + mitmServerNameKey // used by TLS dialer + streamSettingsKey ) func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context { @@ -137,17 +135,6 @@ func DispatcherFromContext(ctx context.Context) routing.Dispatcher { return nil } -func ContextWithTimeoutOnly(ctx context.Context, only bool) context.Context { - return context.WithValue(ctx, timeoutOnlyKey, only) -} - -func TimeoutOnlyFromContext(ctx context.Context) bool { - if val, ok := ctx.Value(timeoutOnlyKey).(bool); ok { - return val - } - return false -} - func ContextWithAllowedNetwork(ctx context.Context, network net.Network) context.Context { return context.WithValue(ctx, allowedNetworkKey, network) } diff --git a/proxy/dns/dns.go b/proxy/dns/dns.go index 079ac750d7ca..852af3d802bf 100644 --- a/proxy/dns/dns.go +++ b/proxy/dns/dns.go @@ -214,10 +214,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet. } } - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - ctx, cancel := context.WithCancel(ctx) terminate := func() { cancel() diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 4b94af5a02b0..e5f09aa7e01a 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -388,10 +388,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte defer conn.Close() errors.LogInfo(ctx, "connection opened to ", destination, ", local endpoint ", conn.LocalAddr(), ", remote endpoint ", conn.RemoteAddr()) - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - plcy := h.policy() ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) diff --git a/proxy/http/client.go b/proxy/http/client.go index 540286e1766b..7de166e93f3a 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -127,10 +127,6 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter p = c.policyManager.ForLevel(user.Level) } - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) diff --git a/proxy/hysteria/client.go b/proxy/hysteria/client.go index 8d5634bc12ba..efe5e2277a44 100644 --- a/proxy/hysteria/client.go +++ b/proxy/hysteria/client.go @@ -67,10 +67,6 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter defer conn.Close() errors.LogInfo(ctx, "tunneling request to ", target, " via ", target.Network, ":", c.server.Destination.NetAddr()) - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - sessionPolicy := c.policyManager.ForLevel(0) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) diff --git a/proxy/shadowsocks/client.go b/proxy/shadowsocks/client.go index 075b5682457a..672fbfb7213a 100644 --- a/proxy/shadowsocks/client.go +++ b/proxy/shadowsocks/client.go @@ -95,10 +95,6 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } request.User = user - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) diff --git a/proxy/shadowsocks_2022/outbound.go b/proxy/shadowsocks_2022/outbound.go index dd4efce55a88..af1c3c367c98 100644 --- a/proxy/shadowsocks_2022/outbound.go +++ b/proxy/shadowsocks_2022/outbound.go @@ -83,10 +83,6 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int return errors.New("failed to connect to server").Base(err) } - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - if network == net.Network_TCP { serverConn := o.method.DialEarlyConn(connection, singbridge.ToSocksaddr(destination)) var handshake bool diff --git a/proxy/socks/client.go b/proxy/socks/client.go index e74776508883..c60d10d8ae11 100644 --- a/proxy/socks/client.go +++ b/proxy/socks/client.go @@ -117,10 +117,6 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter errors.LogInfoInner(ctx, err, "failed to clear deadline after handshake") } - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) diff --git a/proxy/trojan/client.go b/proxy/trojan/client.go index 0cd450ad580c..6176441fc1b5 100644 --- a/proxy/trojan/client.go +++ b/proxy/trojan/client.go @@ -81,10 +81,6 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return errors.New("user account is not valid") } - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go index 3103b2eb675f..30c9576d1310 100644 --- a/proxy/vless/outbound/outbound.go +++ b/proxy/vless/outbound/outbound.go @@ -294,10 +294,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte ob.CanSpliceCopy = 3 } - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - sessionPolicy := h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) diff --git a/proxy/vmess/outbound/outbound.go b/proxy/vmess/outbound/outbound.go index 65ec65ac6fd7..7681dce4dd0c 100644 --- a/proxy/vmess/outbound/outbound.go +++ b/proxy/vmess/outbound/outbound.go @@ -131,10 +131,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte behaviorSeed := crc64.Checksum(hashkdf.Sum(nil), crc64.MakeTable(crc64.ISO)) - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - session := encoding.NewClientSession(ctx, int64(behaviorSeed)) sessionPolicy := h.policyManager.ForLevel(request.User.Level) diff --git a/proxy/wireguard/client.go b/proxy/wireguard/client.go index efcb4a9dfb04..60dc97102200 100644 --- a/proxy/wireguard/client.go +++ b/proxy/wireguard/client.go @@ -165,10 +165,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return errors.New("invalid target ", ob.Target) } - if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) - } - sessionPolicy := h.policyManager.ForLevel(0) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 4883f30aba2c..da8b64996d79 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -122,7 +122,7 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in gctx = c.ContextWithID(gctx, c.IDFromContext(ctx)) gctx = session.ContextWithOutbounds(gctx, session.OutboundsFromContext(ctx)) - gctx = session.ContextWithTimeoutOnly(gctx, true) + gctx = context.WithoutCancel(gctx) c, err := internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt) if err == nil { From 95c960977b7d72797fa811727e84e0e67347cb88 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Wed, 8 Jul 2026 22:44:16 +0800 Subject: [PATCH 5/7] Revert "refactor (remove TimeoutOnly)" This reverts commit 723ef2b8e69ce120acadf3d3881bcfe51f629509. --- common/mux/server.go | 2 +- common/session/context.go | 37 ++++++++++++++++++++---------- proxy/dns/dns.go | 4 ++++ proxy/freedom/freedom.go | 4 ++++ proxy/http/client.go | 4 ++++ proxy/hysteria/client.go | 4 ++++ proxy/shadowsocks/client.go | 4 ++++ proxy/shadowsocks_2022/outbound.go | 4 ++++ proxy/socks/client.go | 4 ++++ proxy/trojan/client.go | 4 ++++ proxy/vless/outbound/outbound.go | 4 ++++ proxy/vmess/outbound/outbound.go | 4 ++++ proxy/wireguard/client.go | 4 ++++ transport/internet/grpc/dial.go | 2 +- 14 files changed, 71 insertions(+), 14 deletions(-) diff --git a/common/mux/server.go b/common/mux/server.go index 0cf5851cf351..d1cdac113e44 100644 --- a/common/mux/server.go +++ b/common/mux/server.go @@ -227,7 +227,7 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, errors.LogInfoInner(ctx, err, "XUDP hit ", meta.GlobalID) } if mb != nil { - ctx = context.WithoutCancel(ctx) + ctx = session.ContextWithTimeoutOnly(ctx, true) // Actually, it won't return an error in Xray-core's implementations. link, err := w.dispatcher.Dispatch(ctx, meta.Target) if err != nil { diff --git a/common/session/context.go b/common/session/context.go index 5605980d79a8..df445ab55b64 100644 --- a/common/session/context.go +++ b/common/session/context.go @@ -11,18 +11,20 @@ import ( ) const ( - inboundSessionKey ctx.SessionKey = iota - outboundSessionKey - contentSessionKey - isReverseMuxKey // is reverse mux - sockoptSessionKey // used by dokodemo to only receive sockopt.Mark - trackedConnectionErrorKey // used by observer to get outbound error - dispatcherKey // used by ss2022 inbounds to get dispatcher - allowedNetworkKey // muxcool server control incoming request tcp/udp - fullHandlerKey // outbound gets full handler - mitmAlpn11Key // used by TLS dialer - mitmServerNameKey // used by TLS dialer - streamSettingsKey + inboundSessionKey ctx.SessionKey = 1 + outboundSessionKey ctx.SessionKey = 2 + contentSessionKey ctx.SessionKey = 3 + isReverseMuxKey ctx.SessionKey = 4 // is reverse mux + 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 + fullHandlerKey ctx.SessionKey = 10 // outbound gets full handler + mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer + mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer + + streamSettingsKey ctx.SessionKey = 13 ) func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context { @@ -135,6 +137,17 @@ func DispatcherFromContext(ctx context.Context) routing.Dispatcher { return nil } +func ContextWithTimeoutOnly(ctx context.Context, only bool) context.Context { + return context.WithValue(ctx, timeoutOnlyKey, only) +} + +func TimeoutOnlyFromContext(ctx context.Context) bool { + if val, ok := ctx.Value(timeoutOnlyKey).(bool); ok { + return val + } + return false +} + func ContextWithAllowedNetwork(ctx context.Context, network net.Network) context.Context { return context.WithValue(ctx, allowedNetworkKey, network) } diff --git a/proxy/dns/dns.go b/proxy/dns/dns.go index 852af3d802bf..079ac750d7ca 100644 --- a/proxy/dns/dns.go +++ b/proxy/dns/dns.go @@ -214,6 +214,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet. } } + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + ctx, cancel := context.WithCancel(ctx) terminate := func() { cancel() diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index e5f09aa7e01a..4b94af5a02b0 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -388,6 +388,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte defer conn.Close() errors.LogInfo(ctx, "connection opened to ", destination, ", local endpoint ", conn.LocalAddr(), ", remote endpoint ", conn.RemoteAddr()) + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + plcy := h.policy() ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) diff --git a/proxy/http/client.go b/proxy/http/client.go index 7de166e93f3a..540286e1766b 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -127,6 +127,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter p = c.policyManager.ForLevel(user.Level) } + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) diff --git a/proxy/hysteria/client.go b/proxy/hysteria/client.go index efe5e2277a44..8d5634bc12ba 100644 --- a/proxy/hysteria/client.go +++ b/proxy/hysteria/client.go @@ -67,6 +67,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter defer conn.Close() errors.LogInfo(ctx, "tunneling request to ", target, " via ", target.Network, ":", c.server.Destination.NetAddr()) + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + sessionPolicy := c.policyManager.ForLevel(0) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) diff --git a/proxy/shadowsocks/client.go b/proxy/shadowsocks/client.go index 672fbfb7213a..075b5682457a 100644 --- a/proxy/shadowsocks/client.go +++ b/proxy/shadowsocks/client.go @@ -95,6 +95,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } request.User = user + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) diff --git a/proxy/shadowsocks_2022/outbound.go b/proxy/shadowsocks_2022/outbound.go index af1c3c367c98..dd4efce55a88 100644 --- a/proxy/shadowsocks_2022/outbound.go +++ b/proxy/shadowsocks_2022/outbound.go @@ -83,6 +83,10 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int return errors.New("failed to connect to server").Base(err) } + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + if network == net.Network_TCP { serverConn := o.method.DialEarlyConn(connection, singbridge.ToSocksaddr(destination)) var handshake bool diff --git a/proxy/socks/client.go b/proxy/socks/client.go index c60d10d8ae11..e74776508883 100644 --- a/proxy/socks/client.go +++ b/proxy/socks/client.go @@ -117,6 +117,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter errors.LogInfoInner(ctx, err, "failed to clear deadline after handshake") } + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) diff --git a/proxy/trojan/client.go b/proxy/trojan/client.go index 6176441fc1b5..0cd450ad580c 100644 --- a/proxy/trojan/client.go +++ b/proxy/trojan/client.go @@ -81,6 +81,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return errors.New("user account is not valid") } + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go index 30c9576d1310..3103b2eb675f 100644 --- a/proxy/vless/outbound/outbound.go +++ b/proxy/vless/outbound/outbound.go @@ -294,6 +294,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte ob.CanSpliceCopy = 3 } + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + sessionPolicy := h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) diff --git a/proxy/vmess/outbound/outbound.go b/proxy/vmess/outbound/outbound.go index 7681dce4dd0c..65ec65ac6fd7 100644 --- a/proxy/vmess/outbound/outbound.go +++ b/proxy/vmess/outbound/outbound.go @@ -131,6 +131,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte behaviorSeed := crc64.Checksum(hashkdf.Sum(nil), crc64.MakeTable(crc64.ISO)) + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + session := encoding.NewClientSession(ctx, int64(behaviorSeed)) sessionPolicy := h.policyManager.ForLevel(request.User.Level) diff --git a/proxy/wireguard/client.go b/proxy/wireguard/client.go index 60dc97102200..efcb4a9dfb04 100644 --- a/proxy/wireguard/client.go +++ b/proxy/wireguard/client.go @@ -165,6 +165,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return errors.New("invalid target ", ob.Target) } + if session.TimeoutOnlyFromContext(ctx) { + ctx = context.WithoutCancel(ctx) + } + sessionPolicy := h.policyManager.ForLevel(0) ctx, cancel := context.WithCancel(ctx) timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index da8b64996d79..4883f30aba2c 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -122,7 +122,7 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in gctx = c.ContextWithID(gctx, c.IDFromContext(ctx)) gctx = session.ContextWithOutbounds(gctx, session.OutboundsFromContext(ctx)) - gctx = context.WithoutCancel(gctx) + gctx = session.ContextWithTimeoutOnly(gctx, true) c, err := internet.DialSystem(gctx, net.TCPDestination(address, port), sockopt) if err == nil { From dc7ae49fab28753b9843cc58d0a594f1b95ce983 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Wed, 8 Jul 2026 22:44:21 +0800 Subject: [PATCH 6/7] Revert "remove IndependentCancelCtx" This reverts commit b53f8b56a47258773f70267b7e2f4bd1962dde5b. --- common/session/context.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/session/context.go b/common/session/context.go index df445ab55b64..abe4219ca935 100644 --- a/common/session/context.go +++ b/common/session/context.go @@ -10,6 +10,9 @@ import ( "github.com/xtls/xray-core/features/routing" ) +//go:linkname IndependentCancelCtx context.newCancelCtx +func IndependentCancelCtx(parent context.Context) context.Context + const ( inboundSessionKey ctx.SessionKey = 1 outboundSessionKey ctx.SessionKey = 2 From 93ca624bb504b4b3359666d8f7f9443ecca2fc25 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Wed, 8 Jul 2026 22:44:25 +0800 Subject: [PATCH 7/7] Revert "more outbounds" This reverts commit f30526a60fb94970c5afa27a854a3b098bcead0f. --- proxy/freedom/freedom.go | 15 +++++++++++++-- proxy/http/client.go | 15 +++++++++++++-- proxy/hysteria/client.go | 15 +++++++++++++-- proxy/shadowsocks/client.go | 15 +++++++++++++-- proxy/shadowsocks_2022/outbound.go | 2 +- proxy/socks/client.go | 15 +++++++++++++-- proxy/trojan/client.go | 15 +++++++++++++-- proxy/vless/outbound/outbound.go | 15 +++++++++++++-- proxy/vmess/outbound/outbound.go | 15 +++++++++++++-- proxy/wireguard/client.go | 15 +++++++++++++-- 10 files changed, 118 insertions(+), 19 deletions(-) diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index 4b94af5a02b0..9cdce38a3406 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -388,13 +388,20 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte defer conn.Close() errors.LogInfo(ctx, "connection opened to ", destination, ", local endpoint ", conn.LocalAddr(), ", remote endpoint ", conn.RemoteAddr()) + var newCtx context.Context + var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) + newCtx, newCancel = context.WithCancel(context.Background()) } plcy := h.policy() ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, plcy.Timeouts.ConnectionIdle) requestDone := func() error { defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly) @@ -455,6 +462,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return nil } + if newCtx != nil { + ctx = newCtx + } + if err := task.Run(ctx, requestDone, task.OnSuccess(responseDone, task.Close(output))); err != nil { return errors.New("connection ends").Base(err) } diff --git a/proxy/http/client.go b/proxy/http/client.go index 540286e1766b..0e50edba3b61 100644 --- a/proxy/http/client.go +++ b/proxy/http/client.go @@ -127,12 +127,19 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter p = c.policyManager.ForLevel(user.Level) } + var newCtx context.Context + var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) + newCtx, newCancel = context.WithCancel(context.Background()) } ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, p.Timeouts.ConnectionIdle) requestFunc := func() error { defer timer.SetTimeout(p.Timeouts.DownlinkOnly) @@ -144,6 +151,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) } + if newCtx != nil { + ctx = newCtx + } + responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer)) if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { return errors.New("connection ends").Base(err) diff --git a/proxy/hysteria/client.go b/proxy/hysteria/client.go index 8d5634bc12ba..5b602c342de8 100644 --- a/proxy/hysteria/client.go +++ b/proxy/hysteria/client.go @@ -67,13 +67,24 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter defer conn.Close() errors.LogInfo(ctx, "tunneling request to ", target, " via ", target.Network, ":", c.server.Destination.NetAddr()) + var newCtx context.Context + var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) + newCtx, newCancel = context.WithCancel(context.Background()) } sessionPolicy := c.policyManager.ForLevel(0) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, sessionPolicy.Timeouts.ConnectionIdle) + + if newCtx != nil { + ctx = newCtx + } if target.Network == net.Network_TCP { requestDone := func() error { diff --git a/proxy/shadowsocks/client.go b/proxy/shadowsocks/client.go index 075b5682457a..29cb04566dd2 100644 --- a/proxy/shadowsocks/client.go +++ b/proxy/shadowsocks/client.go @@ -95,13 +95,24 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } request.User = user + var newCtx context.Context + var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) + newCtx, newCancel = context.WithCancel(context.Background()) } sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, sessionPolicy.Timeouts.ConnectionIdle) + + if newCtx != nil { + ctx = newCtx + } if request.Command == protocol.RequestCommandTCP { requestDone := func() error { diff --git a/proxy/shadowsocks_2022/outbound.go b/proxy/shadowsocks_2022/outbound.go index dd4efce55a88..f1195950dfd9 100644 --- a/proxy/shadowsocks_2022/outbound.go +++ b/proxy/shadowsocks_2022/outbound.go @@ -84,7 +84,7 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int } if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) + ctx, _ = context.WithCancel(context.Background()) } if network == net.Network_TCP { diff --git a/proxy/socks/client.go b/proxy/socks/client.go index e74776508883..eac3f35b8214 100644 --- a/proxy/socks/client.go +++ b/proxy/socks/client.go @@ -117,12 +117,19 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter errors.LogInfoInner(ctx, err, "failed to clear deadline after handshake") } + var newCtx context.Context + var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) + newCtx, newCancel = context.WithCancel(context.Background()) } ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, p.Timeouts.ConnectionIdle) var requestFunc func() error var responseFunc func() error @@ -155,6 +162,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter } } + if newCtx != nil { + ctx = newCtx + } + responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer)) if err := task.Run(ctx, requestFunc, responseDonePost); err != nil { return errors.New("connection ends").Base(err) diff --git a/proxy/trojan/client.go b/proxy/trojan/client.go index 0cd450ad580c..4af8c019d0f7 100644 --- a/proxy/trojan/client.go +++ b/proxy/trojan/client.go @@ -81,13 +81,20 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return errors.New("user account is not valid") } + var newCtx context.Context + var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) + newCtx, newCancel = context.WithCancel(context.Background()) } sessionPolicy := c.policyManager.ForLevel(user.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, sessionPolicy.Timeouts.ConnectionIdle) postRequest := func() error { defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly) @@ -143,6 +150,10 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) } + if newCtx != nil { + ctx = newCtx + } + responseDoneAndCloseWriter := task.OnSuccess(getResponse, task.Close(link.Writer)) if err := task.Run(ctx, postRequest, responseDoneAndCloseWriter); err != nil { return errors.New("connection ends").Base(err) diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go index 3103b2eb675f..ac087c6372b1 100644 --- a/proxy/vless/outbound/outbound.go +++ b/proxy/vless/outbound/outbound.go @@ -294,13 +294,20 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte ob.CanSpliceCopy = 3 } + var newCtx context.Context + var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) + newCtx, newCancel = context.WithCancel(context.Background()) } sessionPolicy := h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, sessionPolicy.Timeouts.ConnectionIdle) clientReader := link.Reader // .(*pipe.Reader) clientWriter := link.Writer // .(*pipe.Writer) @@ -406,6 +413,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return nil } + if newCtx != nil { + ctx = newCtx + } + if err := task.Run(ctx, postRequest, task.OnSuccess(getResponse, task.Close(clientWriter))); err != nil { return errors.New("connection ends").Base(err).AtInfo() } diff --git a/proxy/vmess/outbound/outbound.go b/proxy/vmess/outbound/outbound.go index 65ec65ac6fd7..903e73ce37b1 100644 --- a/proxy/vmess/outbound/outbound.go +++ b/proxy/vmess/outbound/outbound.go @@ -131,15 +131,22 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte behaviorSeed := crc64.Checksum(hashkdf.Sum(nil), crc64.MakeTable(crc64.ISO)) + var newCtx context.Context + var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) + newCtx, newCancel = context.WithCancel(context.Background()) } session := encoding.NewClientSession(ctx, int64(behaviorSeed)) sessionPolicy := h.policyManager.ForLevel(request.User.Level) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, sessionPolicy.Timeouts.ConnectionIdle) if request.Command == protocol.RequestCommandUDP && h.cone && request.Port != 53 && request.Port != 443 { request.Command = protocol.RequestCommandMux @@ -205,6 +212,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return buf.Copy(bodyReader, output, buf.UpdateActivity(timer)) } + if newCtx != nil { + ctx = newCtx + } + responseDonePost := task.OnSuccess(responseDone, task.Close(output)) if err := task.Run(ctx, requestDone, responseDonePost); err != nil { return errors.New("connection ends").Base(err) diff --git a/proxy/wireguard/client.go b/proxy/wireguard/client.go index efcb4a9dfb04..4491886abd4d 100644 --- a/proxy/wireguard/client.go +++ b/proxy/wireguard/client.go @@ -165,13 +165,24 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte return errors.New("invalid target ", ob.Target) } + var newCtx context.Context + var newCancel context.CancelFunc if session.TimeoutOnlyFromContext(ctx) { - ctx = context.WithoutCancel(ctx) + newCtx, newCancel = context.WithCancel(context.Background()) } sessionPolicy := h.policyManager.ForLevel(0) ctx, cancel := context.WithCancel(ctx) - timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle) + timer := signal.CancelAfterInactivity(ctx, func() { + cancel() + if newCancel != nil { + newCancel() + } + }, sessionPolicy.Timeouts.ConnectionIdle) + + if newCtx != nil { + ctx = newCtx + } var reader buf.Reader var writer buf.Writer