Allow http1 in v2ray HTTP transport

This commit is contained in:
世界 2022-08-22 22:51:08 +08:00
parent 4005452772
commit a24a2b475a
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
4 changed files with 130 additions and 27 deletions

View file

@ -211,3 +211,73 @@ func TestVMessQUICSelf(t *testing.T) {
}) })
testSuitQUIC(t, clientPort, testPort) 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)
}

View file

@ -44,9 +44,6 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks
} }
switch options.Type { switch options.Type {
case C.V2RayTransportTypeHTTP: case C.V2RayTransportTypeHTTP:
if tlsConfig == nil {
return nil, C.ErrTLSRequired
}
return v2rayhttp.NewClient(ctx, dialer, serverAddr, options.HTTPOptions, tlsConfig), nil return v2rayhttp.NewClient(ctx, dialer, serverAddr, options.HTTPOptions, tlsConfig), nil
case C.V2RayTransportTypeGRPC: case C.V2RayTransportTypeGRPC:
if tlsConfig == nil { if tlsConfig == nil {

View file

@ -1,6 +1,7 @@
package v2rayhttp package v2rayhttp
import ( import (
"bufio"
"context" "context"
"crypto/tls" "crypto/tls"
"io" "io"
@ -21,7 +22,10 @@ var _ adapter.V2RayClientTransport = (*Client)(nil)
type Client struct { type Client struct {
ctx context.Context ctx context.Context
dialer N.Dialer
serverAddr M.Socksaddr
client *http.Client client *http.Client
http2 bool
url *url.URL url *url.URL
host []string host []string
method string method string
@ -31,6 +35,8 @@ type Client struct {
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig *tls.Config) adapter.V2RayClientTransport { func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig *tls.Config) adapter.V2RayClientTransport {
client := &Client{ client := &Client{
ctx: ctx, ctx: ctx,
dialer: dialer,
serverAddr: serverAddr,
host: options.Host, host: options.Host,
method: options.Method, method: options.Method,
headers: make(http.Header), headers: make(http.Header),
@ -43,6 +49,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
}, },
}, },
http2: tlsConfig != nil,
} }
if client.method == "" { if client.method == "" {
client.method = "PUT" 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) { 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() pipeInReader, pipeInWriter := io.Pipe()
request := &http.Request{ request := &http.Request{
Method: c.method, Method: c.method,
Body: pipeInReader, Body: pipeInReader,
URL: c.url, URL: c.url,
ProtoMajor: 2, ProtoMajor: 2,
ProtoMinor: 0,
Proto: "HTTP/2", Proto: "HTTP/2",
Header: c.headers.Clone(), Header: c.headers.Clone(),
} }

View file

@ -12,8 +12,6 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" 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 { if h, ok := writer.(http.Hijacker); ok {
conn, reader, err := h.Hijack() conn, _, err := h.Hijack()
if err != nil { if err != nil {
writer.WriteHeader(http.StatusInternalServerError) writer.WriteHeader(http.StatusInternalServerError)
s.badRequest(request, E.Cause(err, "hijack conn")) s.badRequest(request, E.Cause(err, "hijack conn"))
return 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{}) s.handler.NewConnection(request.Context(), conn, M.Metadata{})
} else { } else {
conn := &ServerHTTPConn{ conn := &ServerHTTPConn{