mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-09-27 15:14:26 +00:00
630 lines
20 KiB
Go
630 lines
20 KiB
Go
package main
|
|
|
|
import (
|
|
std_bufio "bufio"
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"net/url"
|
|
"strconv"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
sTLS "github.com/sagernet/sing-box/common/tls"
|
|
C "github.com/sagernet/sing-box/constant"
|
|
"github.com/sagernet/sing-box/log"
|
|
"github.com/sagernet/sing-box/option"
|
|
sHTTP "github.com/sagernet/sing-box/transport/http"
|
|
"github.com/sagernet/sing/common"
|
|
"github.com/sagernet/sing/common/auth"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
"github.com/sagernet/sing/common/json/badoption"
|
|
M "github.com/sagernet/sing/common/metadata"
|
|
N "github.com/sagernet/sing/common/network"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/net/http2"
|
|
)
|
|
|
|
const proxyAuthorization = "Basic c2VrYWk6cGFzc3dvcmQ="
|
|
|
|
func startTLSHTTPInbound(t *testing.T, certPem string, keyPem string, versions []int, extraOutbounds []option.Outbound) {
|
|
outbounds := append([]option.Outbound{{Type: C.TypeDirect}}, extraOutbounds...)
|
|
startInstance(t, option.Options{
|
|
Inbounds: []option.Inbound{
|
|
{
|
|
Type: C.TypeHTTP,
|
|
Options: &option.HTTPInboundOptions{
|
|
ListenOptions: option.ListenOptions{
|
|
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
|
|
ListenPort: serverPort,
|
|
},
|
|
Version: versions,
|
|
Users: []auth.User{{Username: "sekai", Password: "password"}},
|
|
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
|
|
TLS: &option.InboundTLSOptions{
|
|
Enabled: true,
|
|
ServerName: "example.org",
|
|
CertificatePath: certPem,
|
|
KeyPath: keyPem,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Outbounds: outbounds,
|
|
})
|
|
}
|
|
|
|
func dialHTTP2Proxy(t *testing.T, port uint16) *http2.ClientConn {
|
|
tlsConn, err := tls.Dial("tcp", "127.0.0.1:"+strconv.Itoa(int(port)), &tls.Config{
|
|
ServerName: "example.org",
|
|
InsecureSkipVerify: true,
|
|
NextProtos: []string{http2.NextProtoTLS},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, http2.NextProtoTLS, tlsConn.ConnectionState().NegotiatedProtocol)
|
|
clientConn, err := (&http2.Transport{}).NewClientConn(tlsConn)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
clientConn.Close()
|
|
})
|
|
return clientConn
|
|
}
|
|
|
|
type http2Tunnel struct {
|
|
writer *io.PipeWriter
|
|
response *http.Response
|
|
}
|
|
|
|
func openHTTP2Tunnel(t *testing.T, clientConn *http2.ClientConn, host string, authorization string) *http2Tunnel {
|
|
pipeReader, pipeWriter := io.Pipe()
|
|
request := &http.Request{
|
|
Method: http.MethodConnect,
|
|
URL: &url.URL{Host: host},
|
|
Host: host,
|
|
Header: make(http.Header),
|
|
Body: pipeReader,
|
|
}
|
|
if authorization != "" {
|
|
request.Header.Set("Proxy-Authorization", authorization)
|
|
}
|
|
response, err := clientConn.RoundTrip(request)
|
|
require.NoError(t, err)
|
|
return &http2Tunnel{writer: pipeWriter, response: response}
|
|
}
|
|
|
|
func (t *http2Tunnel) close() {
|
|
t.writer.Close()
|
|
t.response.Body.Close()
|
|
}
|
|
|
|
func TestHTTPInboundHTTP2(t *testing.T) {
|
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
|
startTLSHTTPInbound(t, certPem, keyPem, nil, nil)
|
|
origin := newForwardOrigin(t)
|
|
clientConn := dialHTTP2Proxy(t, serverPort)
|
|
|
|
rejected := openHTTP2Tunnel(t, clientConn, origin.host(), "")
|
|
require.Equal(t, http.StatusProxyAuthRequired, rejected.response.StatusCode)
|
|
require.Contains(t, rejected.response.Header.Get("Proxy-Authenticate"), "Basic")
|
|
rejected.close()
|
|
|
|
first := openHTTP2Tunnel(t, clientConn, origin.host(), proxyAuthorization)
|
|
require.Equal(t, http.StatusOK, first.response.StatusCode)
|
|
second := openHTTP2Tunnel(t, clientConn, origin.host(), proxyAuthorization)
|
|
require.Equal(t, http.StatusOK, second.response.StatusCode)
|
|
for _, tunnel := range []*http2Tunnel{second, first} {
|
|
_, err := tunnel.writer.Write([]byte("GET /hello HTTP/1.1\r\nHost: " + origin.host() + "\r\n\r\n"))
|
|
require.NoError(t, err)
|
|
response, err := http.ReadResponse(std_bufio.NewReader(tunnel.response.Body), nil)
|
|
require.NoError(t, err)
|
|
body, err := io.ReadAll(response.Body)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "hello", string(body))
|
|
tunnel.close()
|
|
}
|
|
|
|
request, err := http.NewRequest(http.MethodGet, origin.url("/hello"), nil)
|
|
require.NoError(t, err)
|
|
request.Header.Set("User-Agent", "")
|
|
request.Header.Set("Proxy-Authorization", proxyAuthorization)
|
|
response, err := clientConn.RoundTrip(request)
|
|
require.NoError(t, err)
|
|
body, err := io.ReadAll(response.Body)
|
|
response.Body.Close()
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, response.StatusCode)
|
|
require.Equal(t, "hello", string(body))
|
|
require.Equal(t, origin.host(), response.Header.Get("X-Host"))
|
|
|
|
response, err = clientConn.RoundTrip(request)
|
|
require.NoError(t, err)
|
|
body, err = io.ReadAll(response.Body)
|
|
response.Body.Close()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "hello", string(body))
|
|
require.Equal(t, int32(4), origin.connections.Load())
|
|
}
|
|
|
|
type http2ProxyServer struct {
|
|
listener net.Listener
|
|
connections atomic.Int32
|
|
streams atomic.Int32
|
|
}
|
|
|
|
func startHTTP2ProxyServer(t *testing.T, certPem string, keyPem string) *http2ProxyServer {
|
|
certificate, err := tls.LoadX509KeyPair(certPem, keyPem)
|
|
require.NoError(t, err)
|
|
listener, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{
|
|
Certificates: []tls.Certificate{certificate},
|
|
NextProtos: []string{http2.NextProtoTLS},
|
|
})
|
|
require.NoError(t, err)
|
|
server := &http2ProxyServer{listener: listener}
|
|
h2Server := &http2.Server{}
|
|
handler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
server.streams.Add(1)
|
|
if request.Method != http.MethodConnect || request.Header.Get("Proxy-Authorization") != proxyAuthorization {
|
|
writer.WriteHeader(http.StatusProxyAuthRequired)
|
|
return
|
|
}
|
|
conn, err := net.Dial("tcp", request.Host)
|
|
if err != nil {
|
|
writer.WriteHeader(http.StatusBadGateway)
|
|
return
|
|
}
|
|
writer.WriteHeader(http.StatusOK)
|
|
writer.(http.Flusher).Flush()
|
|
go func() {
|
|
io.Copy(conn, request.Body)
|
|
conn.(*net.TCPConn).CloseWrite()
|
|
}()
|
|
buffer := make([]byte, 4096)
|
|
for {
|
|
n, err := conn.Read(buffer)
|
|
if n > 0 {
|
|
_, err = writer.Write(buffer[:n])
|
|
if err != nil {
|
|
break
|
|
}
|
|
writer.(http.Flusher).Flush()
|
|
}
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
conn.Close()
|
|
})
|
|
go func() {
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
server.connections.Add(1)
|
|
go func() {
|
|
err = conn.(*tls.Conn).Handshake()
|
|
if err != nil {
|
|
conn.Close()
|
|
return
|
|
}
|
|
h2Server.ServeConn(conn, &http2.ServeConnOpts{Handler: handler})
|
|
}()
|
|
}
|
|
}()
|
|
t.Cleanup(func() {
|
|
listener.Close()
|
|
})
|
|
return server
|
|
}
|
|
|
|
func (s *http2ProxyServer) port() uint16 {
|
|
return uint16(s.listener.Addr().(*net.TCPAddr).Port)
|
|
}
|
|
|
|
func TestHTTPOutboundHTTP2(t *testing.T) {
|
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
|
proxyServer := startHTTP2ProxyServer(t, certPem, keyPem)
|
|
startInstance(t, option.Options{
|
|
Inbounds: []option.Inbound{
|
|
{
|
|
Type: C.TypeMixed,
|
|
Options: &option.HTTPMixedInboundOptions{
|
|
ListenOptions: option.ListenOptions{
|
|
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
|
|
ListenPort: clientPort,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Outbounds: []option.Outbound{
|
|
{
|
|
Type: C.TypeHTTP,
|
|
Options: &option.HTTPOutboundOptions{
|
|
ServerOptions: option.ServerOptions{
|
|
Server: "127.0.0.1",
|
|
ServerPort: proxyServer.port(),
|
|
},
|
|
Username: "sekai",
|
|
Password: "password",
|
|
OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
|
|
TLS: &option.OutboundTLSOptions{
|
|
Enabled: true,
|
|
ServerName: "example.org",
|
|
CertificatePath: certPem,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
origin := newForwardOrigin(t)
|
|
for i := 0; i < 3; i++ {
|
|
client := proxyClient(t, clientPort)
|
|
request, err := http.NewRequest(http.MethodGet, origin.url("/hello"), nil)
|
|
require.NoError(t, err)
|
|
request.Header.Set("User-Agent", "")
|
|
response, err := client.Do(request)
|
|
require.NoError(t, err)
|
|
body, err := io.ReadAll(response.Body)
|
|
response.Body.Close()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "hello", string(body))
|
|
client.CloseIdleConnections()
|
|
}
|
|
require.Equal(t, int32(1), proxyServer.connections.Load())
|
|
require.Equal(t, int32(3), proxyServer.streams.Load())
|
|
}
|
|
|
|
func TestHTTPSelfTLS(t *testing.T) {
|
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
|
startTLSHTTPInbound(t, certPem, keyPem, nil, nil)
|
|
startInstance(t, option.Options{
|
|
Inbounds: []option.Inbound{
|
|
{
|
|
Type: C.TypeMixed,
|
|
Options: &option.HTTPMixedInboundOptions{
|
|
ListenOptions: option.ListenOptions{
|
|
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
|
|
ListenPort: clientPort,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Outbounds: []option.Outbound{
|
|
{
|
|
Type: C.TypeHTTP,
|
|
Options: &option.HTTPOutboundOptions{
|
|
ServerOptions: option.ServerOptions{
|
|
Server: "127.0.0.1",
|
|
ServerPort: serverPort,
|
|
},
|
|
Username: "sekai",
|
|
Password: "password",
|
|
OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
|
|
TLS: &option.OutboundTLSOptions{
|
|
Enabled: true,
|
|
ServerName: "example.org",
|
|
CertificatePath: certPem,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
origin := newForwardOrigin(t)
|
|
client := proxyClient(t, clientPort)
|
|
for i := 0; i < 3; i++ {
|
|
request, err := http.NewRequest(http.MethodGet, origin.url("/hello"), nil)
|
|
require.NoError(t, err)
|
|
request.Header.Set("User-Agent", "")
|
|
response, err := client.Do(request)
|
|
require.NoError(t, err)
|
|
body, err := io.ReadAll(response.Body)
|
|
response.Body.Close()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "hello", string(body))
|
|
}
|
|
require.Equal(t, int32(1), origin.connections.Load())
|
|
}
|
|
|
|
func TestHTTPForwardEarlyResponse(t *testing.T) {
|
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
|
startTLSHTTPInbound(t, certPem, keyPem, nil, nil)
|
|
origin := startEarlyResponseOrigin(t)
|
|
clientConn := dialHTTP2Proxy(t, serverPort)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
request, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://"+origin.String()+"/upload", io.LimitReader(rand.Reader, 4<<20))
|
|
require.NoError(t, err)
|
|
request.ContentLength = 4 << 20
|
|
request.Header.Set("Proxy-Authorization", proxyAuthorization)
|
|
response, err := clientConn.RoundTrip(request)
|
|
require.NoError(t, err)
|
|
body, err := io.ReadAll(response.Body)
|
|
response.Body.Close()
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, response.StatusCode)
|
|
require.Equal(t, "ok", string(body))
|
|
}
|
|
|
|
func TestHTTPInboundHTTP2Cleartext(t *testing.T) {
|
|
startInstance(t, option.Options{
|
|
Inbounds: []option.Inbound{
|
|
{
|
|
Type: C.TypeHTTP,
|
|
Options: &option.HTTPInboundOptions{
|
|
ListenOptions: option.ListenOptions{
|
|
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
|
|
ListenPort: serverPort,
|
|
},
|
|
Version: []int{2},
|
|
Users: []auth.User{{Username: "sekai", Password: "password"}},
|
|
},
|
|
},
|
|
},
|
|
Outbounds: []option.Outbound{{Type: C.TypeDirect}},
|
|
})
|
|
origin := newForwardOrigin(t)
|
|
http1Conn, err := net.Dial("tcp", "127.0.0.1:"+strconv.Itoa(int(serverPort)))
|
|
require.NoError(t, err)
|
|
defer http1Conn.Close()
|
|
_, err = http1Conn.Write([]byte("CONNECT " + origin.host() + " HTTP/1.1\r\nHost: " + origin.host() + "\r\nProxy-Authorization: " + proxyAuthorization + "\r\n\r\n"))
|
|
require.NoError(t, err)
|
|
http1Response, err := http.ReadResponse(std_bufio.NewReader(http1Conn), nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusHTTPVersionNotSupported, http1Response.StatusCode)
|
|
require.True(t, http1Response.Close)
|
|
|
|
conn, err := net.Dial("tcp", "127.0.0.1:"+strconv.Itoa(int(serverPort)))
|
|
require.NoError(t, err)
|
|
clientConn, err := (&http2.Transport{AllowHTTP: true}).NewClientConn(conn)
|
|
require.NoError(t, err)
|
|
defer clientConn.Close()
|
|
tunnel := openHTTP2Tunnel(t, clientConn, origin.host(), proxyAuthorization)
|
|
require.Equal(t, http.StatusOK, tunnel.response.StatusCode)
|
|
_, err = tunnel.writer.Write([]byte("GET /hello HTTP/1.1\r\nHost: " + origin.host() + "\r\n\r\n"))
|
|
require.NoError(t, err)
|
|
response, err := http.ReadResponse(std_bufio.NewReader(tunnel.response.Body), nil)
|
|
require.NoError(t, err)
|
|
body, err := io.ReadAll(response.Body)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "hello", string(body))
|
|
tunnel.close()
|
|
|
|
forwardRequest, err := http.NewRequest(http.MethodGet, origin.url("/hello"), nil)
|
|
require.NoError(t, err)
|
|
forwardRequest.Header.Set("Proxy-Authorization", proxyAuthorization)
|
|
forwardResponse, err := clientConn.RoundTrip(forwardRequest)
|
|
require.NoError(t, err)
|
|
forwardResponse.Body.Close()
|
|
require.Equal(t, http.StatusBadRequest, forwardResponse.StatusCode)
|
|
}
|
|
|
|
func TestHTTPOutboundHTTP2Deadline(t *testing.T) {
|
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
|
startTLSHTTPInbound(t, certPem, keyPem, nil, nil)
|
|
origin := newForwardOrigin(t)
|
|
detour, err := sTLS.NewDialerFromOptions(globalCtx, log.NewNOPFactory().Logger(), N.SystemDialer, "127.0.0.1", option.OutboundTLSOptions{
|
|
Enabled: true,
|
|
ServerName: "example.org",
|
|
CertificatePath: certPem,
|
|
ALPN: []string{http2.NextProtoTLS},
|
|
})
|
|
require.NoError(t, err)
|
|
client, err := sHTTP.NewClient(sHTTP.ClientOptions{
|
|
Dialer: detour,
|
|
Server: M.ParseSocksaddrHostPort("127.0.0.1", serverPort),
|
|
Username: "sekai",
|
|
Password: "password",
|
|
Version: 2,
|
|
})
|
|
require.NoError(t, err)
|
|
defer client.Close()
|
|
conn, err := client.DialContext(context.Background(), N.NetworkTCP, M.ParseSocksaddr(origin.host()))
|
|
require.NoError(t, err)
|
|
defer conn.Close()
|
|
require.NoError(t, conn.SetDeadline(time.Now().Add(100*time.Millisecond)))
|
|
_, err = conn.Read(make([]byte, 1))
|
|
require.True(t, E.IsTimeout(err))
|
|
require.NoError(t, conn.SetDeadline(time.Now().Add(5*time.Second)))
|
|
_, err = conn.Write([]byte("GET /hello HTTP/1.1\r\nHost: " + origin.host() + "\r\n\r\n"))
|
|
require.NoError(t, err)
|
|
response, err := http.ReadResponse(std_bufio.NewReader(conn), nil)
|
|
require.NoError(t, err)
|
|
body, err := io.ReadAll(response.Body)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "hello", string(body))
|
|
}
|
|
|
|
func TestHTTPOutboundHTTP2NoFallback(t *testing.T) {
|
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
|
startInstance(t, option.Options{
|
|
Inbounds: []option.Inbound{
|
|
{
|
|
Type: C.TypeHTTP,
|
|
Options: &option.HTTPInboundOptions{
|
|
ListenOptions: option.ListenOptions{
|
|
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
|
|
ListenPort: serverPort,
|
|
},
|
|
Version: []int{1},
|
|
Users: []auth.User{{Username: "sekai", Password: "password"}},
|
|
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
|
|
TLS: &option.InboundTLSOptions{
|
|
Enabled: true,
|
|
ServerName: "example.org",
|
|
CertificatePath: certPem,
|
|
KeyPath: keyPem,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Outbounds: []option.Outbound{{Type: C.TypeDirect}},
|
|
})
|
|
startInstance(t, option.Options{
|
|
Inbounds: []option.Inbound{
|
|
{
|
|
Type: C.TypeMixed,
|
|
Options: &option.HTTPMixedInboundOptions{
|
|
ListenOptions: option.ListenOptions{
|
|
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
|
|
ListenPort: clientPort,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Outbounds: []option.Outbound{
|
|
{
|
|
Type: C.TypeHTTP,
|
|
Options: &option.HTTPOutboundOptions{
|
|
ServerOptions: option.ServerOptions{
|
|
Server: "127.0.0.1",
|
|
ServerPort: serverPort,
|
|
},
|
|
Username: "sekai",
|
|
Password: "password",
|
|
Version: 2,
|
|
DisableVersionFallback: true,
|
|
OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
|
|
TLS: &option.OutboundTLSOptions{
|
|
Enabled: true,
|
|
ServerName: "example.org",
|
|
CertificatePath: certPem,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
origin := newForwardOrigin(t)
|
|
client := proxyClient(t, clientPort)
|
|
for i := 0; i < 2; i++ {
|
|
response, err := client.Get(origin.url("/hello"))
|
|
require.NoError(t, err)
|
|
response.Body.Close()
|
|
require.Equal(t, http.StatusBadGateway, response.StatusCode)
|
|
}
|
|
require.Equal(t, int32(0), origin.connections.Load())
|
|
}
|
|
|
|
func startSwitchingProtocolsOrigin(t *testing.T) *net.TCPAddr {
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
listener.Close()
|
|
})
|
|
go func() {
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go func() {
|
|
defer conn.Close()
|
|
_, err := http.ReadRequest(std_bufio.NewReader(conn))
|
|
if err != nil {
|
|
return
|
|
}
|
|
conn.Write([]byte("HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n"))
|
|
}()
|
|
}
|
|
}()
|
|
return listener.Addr().(*net.TCPAddr)
|
|
}
|
|
|
|
func TestHTTPForwardUnexpectedSwitchingProtocols(t *testing.T) {
|
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
|
startTLSHTTPInbound(t, certPem, keyPem, nil, nil)
|
|
origin := startSwitchingProtocolsOrigin(t)
|
|
clientConn := dialHTTP2Proxy(t, serverPort)
|
|
request, err := http.NewRequest(http.MethodGet, "http://"+origin.String()+"/", nil)
|
|
require.NoError(t, err)
|
|
request.Header.Set("Proxy-Authorization", proxyAuthorization)
|
|
response, err := clientConn.RoundTrip(request)
|
|
require.NoError(t, err)
|
|
response.Body.Close()
|
|
require.Equal(t, http.StatusBadGateway, response.StatusCode)
|
|
}
|
|
|
|
func startTruncatedChunkedOrigin(t *testing.T) *net.TCPAddr {
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
listener.Close()
|
|
})
|
|
go func() {
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go func() {
|
|
defer conn.Close()
|
|
_, err := http.ReadRequest(std_bufio.NewReader(conn))
|
|
if err != nil {
|
|
return
|
|
}
|
|
conn.Write([]byte("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n"))
|
|
}()
|
|
}
|
|
}()
|
|
return listener.Addr().(*net.TCPAddr)
|
|
}
|
|
|
|
func TestHTTPForwardHTTP2Responses(t *testing.T) {
|
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
|
startTLSHTTPInbound(t, certPem, keyPem, nil, nil)
|
|
clientConn := dialHTTP2Proxy(t, serverPort)
|
|
|
|
origin := newForwardOrigin(t)
|
|
headRequest, err := http.NewRequest(http.MethodHead, origin.url("/hello"), nil)
|
|
require.NoError(t, err)
|
|
headRequest.Header.Set("User-Agent", "")
|
|
headRequest.Header.Set("Proxy-Authorization", proxyAuthorization)
|
|
headResponse, err := clientConn.RoundTrip(headRequest)
|
|
require.NoError(t, err)
|
|
headResponse.Body.Close()
|
|
require.Equal(t, http.StatusOK, headResponse.StatusCode)
|
|
require.Equal(t, int64(5), headResponse.ContentLength)
|
|
|
|
trailerRequest, err := http.NewRequest(http.MethodGet, origin.url("/trailer"), nil)
|
|
require.NoError(t, err)
|
|
trailerRequest.Header.Set("Proxy-Authorization", proxyAuthorization)
|
|
trailerResponse, err := clientConn.RoundTrip(trailerRequest)
|
|
require.NoError(t, err)
|
|
trailerBody, err := io.ReadAll(trailerResponse.Body)
|
|
require.NoError(t, err)
|
|
trailerResponse.Body.Close()
|
|
require.Equal(t, "hello", string(trailerBody))
|
|
require.Equal(t, "5d41402a", trailerResponse.Trailer.Get("X-Checksum"))
|
|
|
|
streamRequest, err := http.NewRequest(http.MethodGet, origin.url("/stream"), nil)
|
|
require.NoError(t, err)
|
|
streamRequest.Header.Set("Proxy-Authorization", proxyAuthorization)
|
|
startTime := time.Now()
|
|
streamResponse, err := clientConn.RoundTrip(streamRequest)
|
|
require.NoError(t, err)
|
|
require.Less(t, time.Since(startTime), time.Second)
|
|
streamBody, err := io.ReadAll(streamResponse.Body)
|
|
streamResponse.Body.Close()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "late", string(streamBody))
|
|
|
|
truncated := startTruncatedChunkedOrigin(t)
|
|
request, err := http.NewRequest(http.MethodGet, "http://"+truncated.String()+"/", nil)
|
|
require.NoError(t, err)
|
|
request.Header.Set("Proxy-Authorization", proxyAuthorization)
|
|
response, err := clientConn.RoundTrip(request)
|
|
require.NoError(t, err)
|
|
_, err = io.ReadAll(response.Body)
|
|
response.Body.Close()
|
|
require.Error(t, err)
|
|
}
|