Feature: support VMess HTTP/2 transport (#903)
This commit is contained in:
parent
623d261932
commit
d16ed39e4b
4 changed files with 147 additions and 0 deletions
|
@ -32,6 +32,7 @@ type VmessOption struct {
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
WSPath string `proxy:"ws-path,omitempty"`
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
|
@ -44,6 +45,11 @@ type HTTPOptions struct {
|
||||||
Headers map[string][]string `proxy:"headers,omitempty"`
|
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) {
|
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
switch v.option.Network {
|
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)
|
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:
|
default:
|
||||||
// handle TLS
|
// handle TLS
|
||||||
if v.option.TLS {
|
if v.option.TLS {
|
||||||
|
@ -171,6 +201,9 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if option.Network == "h2" && !option.TLS {
|
||||||
|
return nil, fmt.Errorf("TLS must be true with h2 network")
|
||||||
|
}
|
||||||
|
|
||||||
return &Vmess{
|
return &Vmess{
|
||||||
Base: &Base{
|
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
|
Host string
|
||||||
SkipCertVerify bool
|
SkipCertVerify bool
|
||||||
SessionCache tls.ClientSessionCache
|
SessionCache tls.ClientSessionCache
|
||||||
|
NextProtos []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
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,
|
ServerName: cfg.Host,
|
||||||
InsecureSkipVerify: cfg.SkipCertVerify,
|
InsecureSkipVerify: cfg.SkipCertVerify,
|
||||||
ClientSessionCache: cfg.SessionCache,
|
ClientSessionCache: cfg.SessionCache,
|
||||||
|
NextProtos: cfg.NextProtos,
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
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-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 h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
|
||||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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/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/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=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
Loading…
Reference in a new issue