Feature: support VMess HTTP/2 transport (#903)
This commit is contained in:
parent
8766287e72
commit
5bd189f2d0
4 changed files with 147 additions and 0 deletions
|
@ -32,6 +32,7 @@ type VmessOption struct {
|
|||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
WSPath string `proxy:"ws-path,omitempty"`
|
||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
|
@ -44,6 +45,11 @@ type HTTPOptions struct {
|
|||
Headers map[string][]string `proxy:"headers,omitempty"`
|
||||
}
|
||||
|
||||
type HTTP2Options struct {
|
||||
Host []string `proxy:"host,omitempty"`
|
||||
Path string `proxy:"path,omitempty"`
|
||||
}
|
||||
|
||||
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
switch v.option.Network {
|
||||
|
@ -99,6 +105,30 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||
}
|
||||
|
||||
c = vmess.StreamHTTPConn(c, httpOpts)
|
||||
case "h2":
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsOpts := vmess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
SessionCache: getClientSessionCache(),
|
||||
NextProtos: []string{"h2"},
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
c, err = vmess.StreamTLSConn(c, &tlsOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h2Opts := &vmess.H2Config{
|
||||
Hosts: v.option.HTTP2Opts.Host,
|
||||
Path: v.option.HTTP2Opts.Path,
|
||||
}
|
||||
|
||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||
default:
|
||||
// handle TLS
|
||||
if v.option.TLS {
|
||||
|
@ -171,6 +201,9 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if option.Network == "h2" && !option.TLS {
|
||||
return nil, fmt.Errorf("TLS must be true with h2 network")
|
||||
}
|
||||
|
||||
return &Vmess{
|
||||
Base: &Base{
|
||||
|
|
111
component/vmess/h2.go
Normal file
111
component/vmess/h2.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package vmess
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type h2Conn struct {
|
||||
net.Conn
|
||||
*http2.ClientConn
|
||||
pwriter *io.PipeWriter
|
||||
res *http.Response
|
||||
cfg *H2Config
|
||||
}
|
||||
|
||||
type H2Config struct {
|
||||
Hosts []string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (hc *h2Conn) establishConn() error {
|
||||
preader, pwriter := io.Pipe()
|
||||
|
||||
host := hc.cfg.Hosts[rand.Intn(len(hc.cfg.Hosts))]
|
||||
path := hc.cfg.Path
|
||||
// TODO: connect use VMess Host instead of H2 Host
|
||||
req := http.Request{
|
||||
Method: "PUT",
|
||||
Host: host,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: host,
|
||||
Path: path,
|
||||
},
|
||||
Proto: "HTTP/2",
|
||||
ProtoMajor: 2,
|
||||
ProtoMinor: 0,
|
||||
Body: preader,
|
||||
Header: map[string][]string{
|
||||
"Accept-Encoding": {"identity"},
|
||||
},
|
||||
}
|
||||
|
||||
res, err := hc.ClientConn.RoundTrip(&req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hc.pwriter = pwriter
|
||||
hc.res = res
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read implements net.Conn.Read()
|
||||
func (hc *h2Conn) Read(b []byte) (int, error) {
|
||||
if hc.res != nil && !hc.res.Close {
|
||||
n, err := hc.res.Body.Read(b)
|
||||
return n, err
|
||||
}
|
||||
|
||||
if err := hc.establishConn(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return hc.res.Body.Read(b)
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (hc *h2Conn) Write(b []byte) (int, error) {
|
||||
if hc.pwriter != nil {
|
||||
return hc.pwriter.Write(b)
|
||||
}
|
||||
|
||||
if err := hc.establishConn(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return hc.pwriter.Write(b)
|
||||
}
|
||||
|
||||
func (hc *h2Conn) Close() error {
|
||||
if err := hc.pwriter.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hc.ClientConn.Shutdown(hc.res.Request.Context()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hc.Conn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StreamH2Conn(conn net.Conn, cfg *H2Config) (net.Conn, error) {
|
||||
transport := &http2.Transport{}
|
||||
|
||||
cconn, err := transport.NewClientConn(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &h2Conn{
|
||||
Conn: conn,
|
||||
ClientConn: cconn,
|
||||
cfg: cfg,
|
||||
}, nil
|
||||
}
|
|
@ -9,6 +9,7 @@ type TLSConfig struct {
|
|||
Host string
|
||||
SkipCertVerify bool
|
||||
SessionCache tls.ClientSessionCache
|
||||
NextProtos []string
|
||||
}
|
||||
|
||||
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||
|
@ -16,6 +17,7 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
|||
ServerName: cfg.Host,
|
||||
InsecureSkipVerify: cfg.SkipCertVerify,
|
||||
ClientSessionCache: cfg.SessionCache,
|
||||
NextProtos: cfg.NextProtos,
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(conn, tlsConfig)
|
||||
|
|
1
go.sum
1
go.sum
|
@ -55,6 +55,7 @@ golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
Loading…
Reference in a new issue