diff --git a/transport/v2rayhttp/force_close.go b/transport/v2rayhttp/force_close.go index d574a510..ef89990c 100644 --- a/transport/v2rayhttp/force_close.go +++ b/transport/v2rayhttp/force_close.go @@ -3,7 +3,6 @@ package v2rayhttp import ( "net/http" "reflect" - "sync" "unsafe" E "github.com/sagernet/sing/common/exceptions" @@ -11,12 +10,6 @@ import ( "golang.org/x/net/http2" ) -type clientConnPool struct { - t *http2.Transport - mu sync.Mutex - conns map[string][]*http2.ClientConn // key is host:port -} - type efaceWords struct { typ unsafe.Pointer data unsafe.Pointer @@ -28,20 +21,9 @@ func ResetTransport(rawTransport http.RoundTripper) http.RoundTripper { transport.CloseIdleConnections() return transport.Clone() case *http2.Transport: - connPool := transportConnPool(transport) - p := (*clientConnPool)((*efaceWords)(unsafe.Pointer(&connPool)).data) - p.mu.Lock() - defer p.mu.Unlock() - for _, vv := range p.conns { - for _, cc := range vv { - cc.Close() - } - } + closeHTTP2Connections(transport) return transport default: panic(E.New("unknown transport type: ", reflect.TypeOf(transport))) } } - -//go:linkname transportConnPool golang.org/x/net/http2.(*Transport).connPool -func transportConnPool(t *http2.Transport) http2.ClientConnPool diff --git a/transport/v2rayhttp/force_close_go127.go b/transport/v2rayhttp/force_close_go127.go new file mode 100644 index 00000000..76ffc70e --- /dev/null +++ b/transport/v2rayhttp/force_close_go127.go @@ -0,0 +1,55 @@ +//go:build go1.27 && badlinkname + +package v2rayhttp + +import ( + "net/http" + "sync" + "unsafe" + + "golang.org/x/net/http2" +) + +// cmd/compile creates the method symbols reachable from *http.Transport's field types with +// their package recorded when the type is first used by a declaration; a linkname pull +// processed afterwards reuses that symbol as a package-indexed reference, which the linker's +// -checklinkname does not inspect. This declaration must precede the linkname declarations. +var _ *http.Transport + +// net/http/internal/http2.Transport +type internalTransport struct { + t1 [2]uintptr // TransportConfig + connPool *clientConnPool +} + +// net/http/internal/http2.clientConnPool +type clientConnPool struct { + t *internalTransport + mu sync.Mutex + conns map[string][]unsafe.Pointer // key is host:port, value is []*ClientConn +} + +func closeHTTP2Connections(transport *http2.Transport) { + h2Transport := transportFromH1Transport(transportInit(transport)) + t := (*internalTransport)((*efaceWords)(unsafe.Pointer(&h2Transport)).data) + if t == nil { + return + } + p := t.connPool + p.mu.Lock() + defer p.mu.Unlock() + for _, vv := range p.conns { + for _, cc := range vv { + clientConnClose(cc) + } + } +} + +//go:linkname transportInit golang.org/x/net/http2.(*Transport).init +func transportInit(t *http2.Transport) *http.Transport + +//go:linkname transportFromH1Transport net/http/internal/http2_test.transportFromH1Transport +func transportFromH1Transport(t *http.Transport) any + +//go:linkname clientConnClose net/http/internal/http2.(*ClientConn).Close +func clientConnClose(cc unsafe.Pointer) error diff --git a/transport/v2rayhttp/force_close_go127_stub.go b/transport/v2rayhttp/force_close_go127_stub.go new file mode 100644 index 00000000..dc675636 --- /dev/null +++ b/transport/v2rayhttp/force_close_go127_stub.go @@ -0,0 +1,9 @@ +//go:build go1.27 && !badlinkname + +package v2rayhttp + +import "golang.org/x/net/http2" + +func closeHTTP2Connections(transport *http2.Transport) { + transport.CloseIdleConnections() +} diff --git a/transport/v2rayhttp/force_close_legacy.go b/transport/v2rayhttp/force_close_legacy.go new file mode 100644 index 00000000..d8807d7e --- /dev/null +++ b/transport/v2rayhttp/force_close_legacy.go @@ -0,0 +1,31 @@ +//go:build !go1.27 + +package v2rayhttp + +import ( + "sync" + "unsafe" + + "golang.org/x/net/http2" +) + +type clientConnPool struct { + t *http2.Transport + mu sync.Mutex + conns map[string][]*http2.ClientConn // key is host:port +} + +func closeHTTP2Connections(transport *http2.Transport) { + connPool := transportConnPool(transport) + p := (*clientConnPool)((*efaceWords)(unsafe.Pointer(&connPool)).data) + p.mu.Lock() + defer p.mu.Unlock() + for _, vv := range p.conns { + for _, cc := range vv { + cc.Close() + } + } +} + +//go:linkname transportConnPool golang.org/x/net/http2.(*Transport).connPool +func transportConnPool(t *http2.Transport) http2.ClientConnPool