diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index c3a5ca81..0a93e515 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -126,6 +126,12 @@ func (t *HTTP3Transport) newTransport() *http3.Transport { conn.Close() return nil, dialErr } + // quic-go does not take ownership of the packet conn passed to + // DialEarly: when the connection ends it only stops reading. + go func() { + <-quicConn.Context().Done() + conn.Close() + }() return quicConn, nil }, TLSClientConfig: t.tlsConfig, diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go index 3bb93e41..8d45bd82 100644 --- a/dns/transport/quic/quic.go +++ b/dns/transport/quic/quic.go @@ -4,6 +4,7 @@ import ( "context" "errors" "os" + "time" "github.com/sagernet/quic-go" "github.com/sagernet/sing-box/adapter" @@ -117,6 +118,12 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, rawConn.Close() return nil, E.Cause(err, "establish QUIC connection") } + // quic-go does not take ownership of the packet conn passed to + // DialEarly: when the connection ends it only stops reading. + go func() { + <-earlyConnection.Context().Done() + rawConn.Close() + }() return earlyConnection, nil }) if err != nil { @@ -144,6 +151,11 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn *quic. return nil, E.Cause(err, "open stream") } defer stream.CancelRead(0) + stopWatch := context.AfterFunc(ctx, func() { + stream.CancelRead(0) + _ = stream.SetWriteDeadline(time.Now()) + }) + defer stopWatch() err = transport.WriteMessage(stream, 0, message) if err != nil { stream.Close() diff --git a/go.mod b/go.mod index 2af986fb..372c8186 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 github.com/sagernet/sing v0.8.11 github.com/sagernet/sing-mux v0.3.5 - github.com/sagernet/sing-quic v0.6.1 + github.com/sagernet/sing-quic v0.6.3 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 diff --git a/go.sum b/go.sum index 86eca25b..8a0aa268 100644 --- a/go.sum +++ b/go.sum @@ -240,8 +240,8 @@ github.com/sagernet/sing v0.8.11 h1:AKZRvjFPHtAXwGCjOJrzAQPiZxr8mobhuSUqkHf+VQw= github.com/sagernet/sing v0.8.11/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA= github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI= github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.1 h1:lx0tcm99wIA1RkyvILNzRSsMy1k7TTQYIhx71E/WBlw= -github.com/sagernet/sing-quic v0.6.1/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= +github.com/sagernet/sing-quic v0.6.3 h1:0wSPqCNJsC7CadB5GZS1aPkyU27aRwHPd5SlsijbBNw= +github.com/sagernet/sing-quic v0.6.3/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= diff --git a/transport/v2rayquic/client.go b/transport/v2rayquic/client.go index 3e0d8b81..c4e0b102 100644 --- a/transport/v2rayquic/client.go +++ b/transport/v2rayquic/client.go @@ -78,6 +78,12 @@ func (c *Client) offerNew() (*quic.Conn, error) { packetConn.Close() return nil, err } + // quic-go does not take ownership of the packet conn passed to Dial: + // when the connection ends it only stops reading. + go func() { + <-quicConn.Context().Done() + packetConn.Close() + }() c.conn.Store(quicConn) c.rawConn = udpConn return quicConn, nil diff --git a/transport/v2rayquic/stream.go b/transport/v2rayquic/stream.go index aad62afb..411c57a4 100644 --- a/transport/v2rayquic/stream.go +++ b/transport/v2rayquic/stream.go @@ -2,6 +2,7 @@ package v2rayquic import ( "net" + "time" "github.com/sagernet/quic-go" qtls "github.com/sagernet/sing-quic" @@ -37,5 +38,8 @@ func (s *StreamWrapper) Upstream() any { func (s *StreamWrapper) Close() error { s.CancelRead(0) s.Stream.Close() + // quic-go's Stream.Close does not unblock a Write blocked on flow control, + // but a past write deadline does; buffered data and the FIN are unaffected. + s.Stream.SetWriteDeadline(time.Now()) return nil }