transport: Reuse force_close helper in xhttp and grpclite

This commit is contained in:
Shtorm
2026-09-20 21:41:03 +03:00
parent 6439d67043
commit 197cf99b73
7 changed files with 42 additions and 71 deletions
@@ -1,29 +1,33 @@
package v2rayhttp
package force_close
import (
"net/http"
"reflect"
"unsafe"
"github.com/sagernet/quic-go/http3"
E "github.com/sagernet/sing/common/exceptions"
"golang.org/x/net/http2"
)
type efaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
func ResetTransport(rawTransport http.RoundTripper) http.RoundTripper {
switch transport := rawTransport.(type) {
case *http.Transport:
transport.CloseIdleConnections()
return transport.Clone()
case *http2.Transport:
closeHTTP2Connections(transport)
CloseHTTP2Connections(transport)
return transport
case *http3.Transport:
transport.Close()
return transport
default:
panic(E.New("unknown transport type: ", reflect.TypeOf(transport)))
}
}
type efaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
@@ -1,6 +1,6 @@
//go:build go1.27 && badlinkname
package v2rayhttp
package force_close
import (
"net/http"
@@ -16,20 +16,7 @@ import (
// -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) {
func CloseHTTP2Connections(transport *http2.Transport) {
h2Transport := transportFromH1Transport(transportInit(transport))
t := (*internalTransport)((*efaceWords)(unsafe.Pointer(&h2Transport)).data)
if t == nil {
@@ -45,6 +32,19 @@ func closeHTTP2Connections(transport *http2.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
}
//go:linkname transportInit golang.org/x/net/http2.(*Transport).init
func transportInit(t *http2.Transport) *http.Transport
@@ -1,9 +1,9 @@
//go:build go1.27 && !badlinkname
package v2rayhttp
package force_close
import "golang.org/x/net/http2"
func closeHTTP2Connections(transport *http2.Transport) {
func CloseHTTP2Connections(transport *http2.Transport) {
transport.CloseIdleConnections()
}
@@ -1,6 +1,6 @@
//go:build !go1.27
package v2rayhttp
package force_close
import (
"sync"
@@ -9,13 +9,7 @@ import (
"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) {
func CloseHTTP2Connections(transport *http2.Transport) {
connPool := transportConnPool(transport)
p := (*clientConnPool)((*efaceWords)(unsafe.Pointer(&connPool)).data)
p.mu.Lock()
@@ -27,5 +21,11 @@ func closeHTTP2Connections(transport *http2.Transport) {
}
}
type clientConnPool struct {
t *http2.Transport
mu sync.Mutex
conns map[string][]*http2.ClientConn // key is host:port
}
//go:linkname transportConnPool golang.org/x/net/http2.(*Transport).connPool
func transportConnPool(t *http2.Transport) http2.ClientConnPool
+2 -2
View File
@@ -9,9 +9,9 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/force_close"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2rayhttp"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -102,6 +102,6 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
}
func (c *Client) Close() error {
v2rayhttp.ResetTransport(c.transport)
force_close.ResetTransport(c.transport)
return nil
}
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/force_close"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
@@ -152,6 +153,6 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
}
func (c *Client) Close() error {
c.transport = ResetTransport(c.transport)
c.transport = force_close.ResetTransport(c.transport)
return nil
}
+2 -36
View File
@@ -9,19 +9,16 @@ import (
"net"
"net/http"
"net/http/httptrace"
"reflect"
"strings"
"sync"
"sync/atomic"
"unsafe"
"github.com/sagernet/quic-go/http3"
"github.com/sagernet/sing-box/common/force_close"
common "github.com/sagernet/sing-box/common/xray"
"github.com/sagernet/sing-box/common/xray/buf"
"github.com/sagernet/sing-box/common/xray/signal/done"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"golang.org/x/net/http2"
)
// interface to abstract between use of browser dialer, vs net/http
@@ -49,20 +46,6 @@ type DefaultDialerClient struct {
mtx sync.RWMutex
}
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
}
//go:linkname transportConnPool golang.org/x/net/http2.(*Transport).connPool
func transportConnPool(t *http2.Transport) http2.ClientConnPool
func (c *DefaultDialerClient) Close() {
c.mtx.Lock()
defer c.mtx.Unlock()
@@ -70,24 +53,7 @@ func (c *DefaultDialerClient) Close() {
return
}
c.closed = true
switch transport := c.client.Transport.(type) {
case *http.Transport:
transport.CloseIdleConnections()
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()
}
}
case *http3.Transport:
transport.Close()
default:
panic(E.New("unknown transport type: ", reflect.TypeOf(transport)))
}
c.client.Transport = force_close.ResetTransport(c.client.Transport)
}
func (c *DefaultDialerClient) IsClosed() bool {