From a24a2b475a55fb06dcb61bda904d2b5df3a16718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 22 Aug 2022 22:51:08 +0800 Subject: [PATCH] Allow http1 in v2ray HTTP transport --- test/vmess_transport_test.go | 70 +++++++++++++++++++++++++++++++++++ transport/v2ray/transport.go | 3 -- transport/v2rayhttp/client.go | 70 +++++++++++++++++++++++++++++------ transport/v2rayhttp/server.go | 14 +------ 4 files changed, 130 insertions(+), 27 deletions(-) diff --git a/test/vmess_transport_test.go b/test/vmess_transport_test.go index b5df62ce..62b7ba59 100644 --- a/test/vmess_transport_test.go +++ b/test/vmess_transport_test.go @@ -211,3 +211,73 @@ func TestVMessQUICSelf(t *testing.T) { }) testSuitQUIC(t, clientPort, testPort) } + +func TestVMessHTTPNoTLSSelf(t *testing.T) { + transport := &option.V2RayTransportOptions{ + Type: C.V2RayTransportTypeHTTP, + } + user, err := uuid.DefaultGenerator.NewV4() + require.NoError(t, err) + startInstance(t, option.Options{ + Log: &option.LogOptions{ + Level: "trace", + }, + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeVMess, + VMessOptions: option.VMessInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: serverPort, + }, + Users: []option.VMessUser{ + { + Name: "sekai", + UUID: user.String(), + }, + }, + Transport: transport, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeVMess, + Tag: "vmess-out", + VMessOptions: option.VMessOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + UUID: user.String(), + Security: "zero", + Transport: transport, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + DefaultOptions: option.DefaultRule{ + Inbound: []string{"mixed-in"}, + Outbound: "vmess-out", + }, + }, + }, + }, + }) + testSuitQUIC(t, clientPort, testPort) +} diff --git a/transport/v2ray/transport.go b/transport/v2ray/transport.go index 9c726354..503fa54c 100644 --- a/transport/v2ray/transport.go +++ b/transport/v2ray/transport.go @@ -44,9 +44,6 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks } switch options.Type { case C.V2RayTransportTypeHTTP: - if tlsConfig == nil { - return nil, C.ErrTLSRequired - } return v2rayhttp.NewClient(ctx, dialer, serverAddr, options.HTTPOptions, tlsConfig), nil case C.V2RayTransportTypeGRPC: if tlsConfig == nil { diff --git a/transport/v2rayhttp/client.go b/transport/v2rayhttp/client.go index 465a7adb..b0acde33 100644 --- a/transport/v2rayhttp/client.go +++ b/transport/v2rayhttp/client.go @@ -1,6 +1,7 @@ package v2rayhttp import ( + "bufio" "context" "crypto/tls" "io" @@ -20,20 +21,25 @@ import ( var _ adapter.V2RayClientTransport = (*Client)(nil) type Client struct { - ctx context.Context - client *http.Client - url *url.URL - host []string - method string - headers http.Header + ctx context.Context + dialer N.Dialer + serverAddr M.Socksaddr + client *http.Client + http2 bool + url *url.URL + host []string + method string + headers http.Header } func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig *tls.Config) adapter.V2RayClientTransport { client := &Client{ - ctx: ctx, - host: options.Host, - method: options.Method, - headers: make(http.Header), + ctx: ctx, + dialer: dialer, + serverAddr: serverAddr, + host: options.Host, + method: options.Method, + headers: make(http.Header), client: &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { @@ -43,6 +49,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt TLSClientConfig: tlsConfig, }, }, + http2: tlsConfig != nil, } if client.method == "" { client.method = "PUT" @@ -66,13 +73,54 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt } func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { + if !c.http2 { + return c.dialHTTP() + } else { + return c.dialHTTP2() + } +} + +func (c *Client) dialHTTP() (net.Conn, error) { + conn, err := c.dialer.DialContext(c.ctx, N.NetworkTCP, c.serverAddr) + if err != nil { + return nil, err + } + request := &http.Request{ + Method: c.method, + URL: c.url, + ProtoMajor: 1, + Proto: "HTTP/1.1", + Header: c.headers.Clone(), + } + switch hostLen := len(c.host); hostLen { + case 0: + case 1: + request.Host = c.host[0] + default: + request.Host = c.host[rand.Intn(hostLen)] + } + err = request.Write(conn) + if err != nil { + return nil, err + } + reader := bufio.NewReader(conn) + response, err := http.ReadResponse(reader, request) + if err != nil { + return nil, err + } + if response.StatusCode != 200 { + return nil, E.New("unexpected status: ", response.Status) + } + return conn, nil +} + +func (c *Client) dialHTTP2() (net.Conn, error) { pipeInReader, pipeInWriter := io.Pipe() request := &http.Request{ Method: c.method, Body: pipeInReader, URL: c.url, ProtoMajor: 2, - ProtoMinor: 0, Proto: "HTTP/2", Header: c.headers.Clone(), } diff --git a/transport/v2rayhttp/server.go b/transport/v2rayhttp/server.go index a42c5d49..637f9065 100644 --- a/transport/v2rayhttp/server.go +++ b/transport/v2rayhttp/server.go @@ -12,8 +12,6 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -96,22 +94,12 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { } if h, ok := writer.(http.Hijacker); ok { - conn, reader, err := h.Hijack() + conn, _, err := h.Hijack() if err != nil { writer.WriteHeader(http.StatusInternalServerError) s.badRequest(request, E.Cause(err, "hijack conn")) return } - if reader.Available() > 0 { - buffer := buf.NewSize(reader.Available()) - _, err = buffer.ReadFullFrom(reader, buffer.FreeLen()) - if err != nil { - writer.WriteHeader(http.StatusInternalServerError) - s.badRequest(request, E.Cause(err, "read cached data")) - return - } - conn = bufio.NewCachedConn(conn, buffer) - } s.handler.NewConnection(request.Context(), conn, M.Metadata{}) } else { conn := &ServerHTTPConn{