mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-11-10 21:21:02 +08:00
Fix HTTP/2 extended CONNECT with Go 1.27
This commit is contained in:
12 files changed
+120
-49
No files matched your search
@@ -8,7 +8,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -118,40 +117,6 @@ func TestHTTPInboundConnectUDP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPInboundConnectUDPHTTP2(t *testing.T) {
|
||||
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||
startTLSHTTPInbound(t, certPem, keyPem, nil, nil)
|
||||
echo := startUDPEcho(t)
|
||||
clientConn := dialHTTP2Proxy(t, serverPort)
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
request := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "example.org",
|
||||
Path: connectUDPPath(echo),
|
||||
},
|
||||
Host: "example.org",
|
||||
Header: http.Header{
|
||||
":protocol": []string{"connect-udp"},
|
||||
"Capsule-Protocol": []string{"?1"},
|
||||
"Proxy-Authorization": []string{proxyAuthorization},
|
||||
},
|
||||
Body: pipeReader,
|
||||
}
|
||||
response, err := clientConn.RoundTrip(request)
|
||||
require.NoError(t, err)
|
||||
defer response.Body.Close()
|
||||
defer pipeWriter.Close()
|
||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||
require.Equal(t, "?1", response.Header.Get("Capsule-Protocol"))
|
||||
reader := std_bufio.NewReader(response.Body)
|
||||
for i := 0; i < 3; i++ {
|
||||
writeDatagramCapsule(t, pipeWriter, []byte("ping"))
|
||||
require.Equal(t, "ping", string(readDatagramCapsule(t, reader)))
|
||||
}
|
||||
}
|
||||
|
||||
func testHTTPOutboundUDP(t *testing.T, tlsOptions *option.OutboundTLSOptions) {
|
||||
startInstance(t, option.Options{
|
||||
Inbounds: []option.Inbound{
|
||||
|
||||
@@ -363,6 +363,7 @@ var (
|
||||
ErrHTTP2Unsupported = E.New("server does not support HTTP/2")
|
||||
ErrHTTP3Unavailable = E.New("HTTP/3 unavailable")
|
||||
errExtendedConnectUnsupported = E.New("server does not support HTTP/2 extended CONNECT")
|
||||
errExtendedConnectUnavailable = E.New("HTTP/2 extended CONNECT is unavailable in this build: Go 1.27+ requires the badlinkname build tag")
|
||||
)
|
||||
|
||||
func statusError(response *http.Response) error {
|
||||
|
||||
@@ -16,16 +16,12 @@ import (
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type http2ClientConn struct {
|
||||
*http2.ClientConn
|
||||
}
|
||||
|
||||
func (c *Client) acquireHTTP2(ctx context.Context) (*http2ClientConn, net.Conn, error) {
|
||||
c.http2Access.Lock()
|
||||
defer c.http2Access.Unlock()
|
||||
c.http2Conns = slices.DeleteFunc(c.http2Conns, func(it *http2ClientConn) bool { return it.State().Closed })
|
||||
c.http2Conns = slices.DeleteFunc(c.http2Conns, func(it *http2ClientConn) bool { return it.isClosed() })
|
||||
for _, clientConn := range c.http2Conns {
|
||||
if clientConn.ReserveNewRequest() {
|
||||
if clientConn.reserveNewRequest() {
|
||||
return clientConn, nil, nil
|
||||
}
|
||||
}
|
||||
@@ -39,13 +35,12 @@ func (c *Client) acquireHTTP2(ctx context.Context) (*http2ClientConn, net.Conn,
|
||||
}
|
||||
return nil, conn, nil
|
||||
}
|
||||
rawClientConn, err := c.http2Transport.NewClientConn(conn)
|
||||
clientConn, err := newHTTP2ClientConn(c.http2Transport, conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, nil, E.Cause(err, "create HTTP/2 connection")
|
||||
}
|
||||
clientConn := &http2ClientConn{ClientConn: rawClientConn}
|
||||
clientConn.ReserveNewRequest()
|
||||
clientConn.reserveNewRequest()
|
||||
c.http2Conns = append(c.http2Conns, clientConn)
|
||||
return clientConn, nil, nil
|
||||
}
|
||||
@@ -70,7 +65,7 @@ func (c *Client) roundTripHTTP2(ctx context.Context, clientConn *http2ClientConn
|
||||
request.Body = pipeReader
|
||||
request = request.WithContext(streamCtx)
|
||||
stop := context.AfterFunc(ctx, cancel)
|
||||
response, err := clientConn.RoundTrip(request)
|
||||
response, err := clientConn.roundTrip(request)
|
||||
stopped := stop()
|
||||
if err == nil && !stopped {
|
||||
response.Body.Close()
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//go:build !go1.27 || http2legacy || !badlinkname
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type http2ClientConn struct {
|
||||
*http2.ClientConn
|
||||
}
|
||||
|
||||
func newHTTP2ClientConn(transport *http2.Transport, conn net.Conn) (*http2ClientConn, error) {
|
||||
clientConn, err := transport.NewClientConn(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &http2ClientConn{ClientConn: clientConn}, nil
|
||||
}
|
||||
|
||||
func (c *http2ClientConn) isClosed() bool {
|
||||
return c.State().Closed
|
||||
}
|
||||
|
||||
func (c *http2ClientConn) reserveNewRequest() bool {
|
||||
return c.ReserveNewRequest()
|
||||
}
|
||||
|
||||
func (c *http2ClientConn) roundTrip(request *http.Request) (*http.Response, error) {
|
||||
return c.RoundTrip(request)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//go:build go1.27 && !http2legacy && badlinkname
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type http2ClientConn struct {
|
||||
*http.ClientConn
|
||||
}
|
||||
|
||||
func newHTTP2ClientConn(transport *http2.Transport, conn net.Conn) (*http2ClientConn, error) {
|
||||
clientConn, err := transport.NewClientConn(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &http2ClientConn{ClientConn: *(**http.ClientConn)(unsafe.Pointer(clientConn))}, nil
|
||||
}
|
||||
|
||||
func (c *http2ClientConn) isClosed() bool {
|
||||
return c.Err() != nil
|
||||
}
|
||||
|
||||
func (c *http2ClientConn) reserveNewRequest() bool {
|
||||
return c.Reserve() == nil
|
||||
}
|
||||
|
||||
func (c *http2ClientConn) roundTrip(request *http.Request) (*http.Response, error) {
|
||||
if request.Header.Get(":protocol") == "" {
|
||||
return c.ClientConn.RoundTrip(request)
|
||||
}
|
||||
internalClientConn := (*[2]unsafe.Pointer)(unsafe.Pointer(c.ClientConn))[1]
|
||||
return http2RoundTrip(request, func(clientRequest unsafe.Pointer) (unsafe.Pointer, error) {
|
||||
return netHTTPClientConnRoundTrip(internalClientConn, clientRequest)
|
||||
})
|
||||
}
|
||||
|
||||
//go:linkname http2RoundTrip net/http.http2RoundTrip
|
||||
func http2RoundTrip(request *http.Request, roundTrip func(clientRequest unsafe.Pointer) (unsafe.Pointer, error)) (*http.Response, error)
|
||||
|
||||
//go:linkname netHTTPClientConnRoundTrip net/http/internal/http2.NetHTTPClientConn.RoundTrip
|
||||
func netHTTPClientConnRoundTrip(clientConn unsafe.Pointer, clientRequest unsafe.Pointer) (unsafe.Pointer, error)
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !go1.27 || http2legacy
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
@@ -6,6 +8,8 @@ import (
|
||||
_ "golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
const extendedConnectAvailable = true
|
||||
|
||||
//go:linkname disableExtendedConnectProtocol golang.org/x/net/http2.disableExtendedConnectProtocol
|
||||
var disableExtendedConnectProtocol bool
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//go:build go1.27 && !http2legacy && badlinkname
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
_ "net/http"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
const extendedConnectAvailable = true
|
||||
|
||||
//go:linkname disableExtendedConnectProtocol net/http/internal/http2.disableExtendedConnectProtocol
|
||||
var disableExtendedConnectProtocol bool
|
||||
|
||||
func init() {
|
||||
disableExtendedConnectProtocol = false
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//go:build go1.27 && !http2legacy && !badlinkname
|
||||
|
||||
package http
|
||||
|
||||
const extendedConnectAvailable = false
|
||||
@@ -56,7 +56,10 @@ func (c *Client) openTunnel(ctx context.Context, request tunnelRequest) (net.Con
|
||||
}
|
||||
c.markHTTP3Broken()
|
||||
}
|
||||
if c.tlsDialer != nil && !c.http2Unsupported.Load() && !c.http2ExtendedConnectUnsupported.Load() {
|
||||
if !extendedConnectAvailable && c.tlsDialer != nil && c.disableVersionFallback {
|
||||
return nil, nil, errExtendedConnectUnavailable
|
||||
}
|
||||
if extendedConnectAvailable && c.tlsDialer != nil && !c.http2Unsupported.Load() && !c.http2ExtendedConnectUnsupported.Load() {
|
||||
clientConn, conn, err := c.acquireHTTP2(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build go1.27 && badlinkname
|
||||
//go:build go1.27 && !http2legacy && badlinkname
|
||||
|
||||
package v2rayhttp
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build go1.27 && !badlinkname
|
||||
//go:build go1.27 && !http2legacy && !badlinkname
|
||||
|
||||
package v2rayhttp
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !go1.27
|
||||
//go:build !go1.27 || http2legacy
|
||||
|
||||
package v2rayhttp
|
||||
|
||||
|
||||
Reference in new issue
Block a user