add HTTP2 proxy client support

This commit is contained in:
rui.zheng 2017-08-03 13:54:20 +08:00
parent 36b61e0580
commit c02bc32cb6
4 changed files with 88 additions and 179 deletions

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/ginuerzh/gost/gost" "github.com/ginuerzh/gost/gost"
"golang.org/x/net/http2"
) )
var ( var (
@ -25,6 +26,7 @@ func init() {
flag.IntVar(&requests, "n", 1, "Number of requests to perform") flag.IntVar(&requests, "n", 1, "Number of requests to perform")
flag.IntVar(&concurrency, "c", 1, "Number of multiple requests to make at a time") flag.IntVar(&concurrency, "c", 1, "Number of multiple requests to make at a time")
flag.BoolVar(&quiet, "q", false, "quiet mode") flag.BoolVar(&quiet, "q", false, "quiet mode")
flag.BoolVar(&http2.VerboseLogs, "v", false, "HTTP2 verbose logs")
flag.BoolVar(&gost.Debug, "d", false, "debug mode") flag.BoolVar(&gost.Debug, "d", false, "debug mode")
flag.Parse() flag.Parse()
@ -103,17 +105,13 @@ func main() {
*/ */
/* /*
// http2+tls, http2+tcp // http2
gost.Node{ gost.Node{
Addr: "127.0.0.1:1443", Addr: "127.0.0.1:1443",
Client: gost.NewClient( Client: &gost.Client{
gost.HTTP2Connector(url.UserPassword("admin", "123456")), Connector: gost.HTTP2Connector(url.UserPassword("admin", "123456")),
gost.HTTP2Transporter( Transporter: gost.HTTP2Transporter(nil),
nil, },
&tls.Config{InsecureSkipVerify: true}, // or nil, will use h2c mode (http2+tcp).
time.Second*1,
),
),
}, },
*/ */
@ -149,13 +147,14 @@ func main() {
}, },
}, },
*/ */
// socks5+h2
gost.Node{ gost.Node{
Addr: "localhost:8443", Addr: "localhost:8443",
Client: &gost.Client{ Client: &gost.Client{
// Connector: gost.HTTPConnector(url.UserPassword("admin", "123456")), // Connector: gost.HTTPConnector(url.UserPassword("admin", "123456")),
Connector: gost.SOCKS5Connector(url.UserPassword("admin", "123456")), Connector: gost.SOCKS5Connector(url.UserPassword("admin", "123456")),
// Transporter: gost.H2CTransporter(), // HTTP2 h2c mode // Transporter: gost.H2CTransporter(), // HTTP2 h2c mode
Transporter: gost.H2Transporter(), // HTTP2 h2 Transporter: gost.H2Transporter(nil), // HTTP2 h2
}, },
}, },
) )

View File

@ -222,8 +222,7 @@ func http2Server() {
// http2.VerboseLogs = true // http2.VerboseLogs = true
s := &gost.Server{} s := &gost.Server{}
ln, err := gost.TLSListener(":1443", tlsConfig()) // HTTP2 h2 mode ln, err := gost.HTTP2Listener(":1443", tlsConfig())
// ln, err := gost.TCPListener(":1443") // HTTP2 h2c mode
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -2,12 +2,10 @@ package gost
import ( import (
"bufio" "bufio"
"context"
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"errors" "errors"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
@ -31,37 +29,36 @@ func HTTP2Connector(user *url.Userinfo) Connector {
} }
func (c *http2Connector) Connect(conn net.Conn, addr string) (net.Conn, error) { func (c *http2Connector) Connect(conn net.Conn, addr string) (net.Conn, error) {
cc, ok := conn.(*http2DummyConn) cc, ok := conn.(*http2ClientConn)
if !ok { if !ok {
return nil, errors.New("wrong connection type") return nil, errors.New("wrong connection type")
} }
pr, pw := io.Pipe() pr, pw := io.Pipe()
u := &url.URL{ req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Scheme: "https", Host: addr},
Header: make(http.Header),
Proto: "HTTP/2.0",
ProtoMajor: 2,
ProtoMinor: 0,
Body: pr,
Host: addr, Host: addr,
ContentLength: -1,
} }
req, err := http.NewRequest(http.MethodConnect, u.String(), ioutil.NopCloser(pr)) // req.Header.Set("Gost-Target", addr) // Flag header to indicate the address that server connected to
if err != nil {
log.Logf("[http2] %s - %s : %s", cc.raddr, addr, err)
return nil, err
}
if c.User != nil { if c.User != nil {
req.Header.Set("Proxy-Authorization", req.Header.Set("Proxy-Authorization",
"Basic "+base64.StdEncoding.EncodeToString([]byte(c.User.String()))) "Basic "+base64.StdEncoding.EncodeToString([]byte(c.User.String())))
} }
req.ProtoMajor = 2
req.ProtoMinor = 0
if Debug { if Debug {
dump, _ := httputil.DumpRequest(req, false) dump, _ := httputil.DumpRequest(req, false)
log.Log("[http2]", string(dump)) log.Log("[http2]", string(dump))
} }
resp, err := cc.conn.RoundTrip(req) resp, err := cc.client.Do(req)
if err != nil { if err != nil {
log.Logf("[http2] %s - %s : %s", cc.raddr, addr, err)
return nil, err return nil, err
} }
if Debug { if Debug {
dump, _ := httputil.DumpResponse(resp, false) dump, _ := httputil.DumpResponse(resp, false)
log.Log("[http2]", string(dump)) log.Log("[http2]", string(dump))
@ -71,72 +68,64 @@ func (c *http2Connector) Connect(conn net.Conn, addr string) (net.Conn, error) {
resp.Body.Close() resp.Body.Close()
return nil, errors.New(resp.Status) return nil, errors.New(resp.Status)
} }
hc := &http2Conn{r: resp.Body, w: pw} hc := &http2Conn{
hc.remoteAddr, _ = net.ResolveTCPAddr("tcp", cc.raddr) r: resp.Body,
w: pw,
closed: make(chan struct{}),
}
hc.remoteAddr, _ = net.ResolveTCPAddr("tcp", addr)
hc.localAddr, _ = net.ResolveTCPAddr("tcp", cc.addr)
return hc, nil return hc, nil
} }
type http2Transporter struct { type http2Transporter struct {
clients map[string]*http.Client
clientMutex sync.Mutex
tlsConfig *tls.Config tlsConfig *tls.Config
tr *http2.Transport
chain *Chain
sessions map[string]*http2Session
sessionMutex sync.Mutex
pingInterval time.Duration
} }
// HTTP2Transporter creates a Transporter that is used by HTTP2 proxy client. // HTTP2Transporter creates a Transporter that is used by HTTP2 h2 proxy client.
// func HTTP2Transporter(config *tls.Config) Transporter {
// Optional chain is a proxy chain that can be used to establish a connection with the HTTP2 server. if config == nil {
// config = &tls.Config{InsecureSkipVerify: true}
// Optional config is a TLS config for TLS handshake, if is nil, will use h2c mode. }
//
// Optional ping is the ping interval, if is zero, ping will not be enabled.
func HTTP2Transporter(chain *Chain, config *tls.Config, ping time.Duration) Transporter {
return &http2Transporter{ return &http2Transporter{
clients: make(map[string]*http.Client),
tlsConfig: config, tlsConfig: config,
tr: new(http2.Transport),
chain: chain,
pingInterval: ping,
sessions: make(map[string]*http2Session),
} }
} }
func (tr *http2Transporter) Dial(addr string, options ...DialOption) (net.Conn, error) { func (tr *http2Transporter) Dial(addr string, options ...DialOption) (net.Conn, error) {
tr.sessionMutex.Lock() opts := &DialOptions{}
defer tr.sessionMutex.Unlock() for _, option := range options {
option(opts)
}
session, ok := tr.sessions[addr] tr.clientMutex.Lock()
client, ok := tr.clients[addr]
if !ok { if !ok {
conn, err := tr.chain.Dial(addr) transport := http2.Transport{
TLSClientConfig: tr.tlsConfig,
DialTLS: func(network, adr string, cfg *tls.Config) (net.Conn, error) {
conn, err := opts.Chain.Dial(addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return wrapTLSClient(conn, cfg)
},
}
client = &http.Client{
Transport: &transport,
Timeout: opts.Timeout,
}
tr.clients[addr] = client
}
tr.clientMutex.Unlock()
if tr.tlsConfig != nil { return &http2ClientConn{
tc := tls.Client(conn, tr.tlsConfig) addr: addr,
if err := tc.Handshake(); err != nil { client: client,
return nil, err
}
conn = tc
}
cc, err := tr.tr.NewClientConn(conn)
if err != nil {
return nil, err
}
session = newHTTP2Session(conn, cc, tr.pingInterval)
tr.sessions[addr] = session
}
if !session.Healthy() {
session.Close()
delete(tr.sessions, addr) // TODO: we could re-connect to the addr automatically.
return nil, errors.New("connection is dead")
}
return &http2DummyConn{
raddr: addr,
conn: session.clientConn,
}, nil }, nil
} }
@ -154,13 +143,18 @@ type h2Transporter struct {
tlsConfig *tls.Config tlsConfig *tls.Config
} }
func H2Transporter() Transporter { // H2Transporter creates a Transporter that is used by HTTP2 h2 tunnel client.
func H2Transporter(config *tls.Config) Transporter {
if config == nil {
config = &tls.Config{InsecureSkipVerify: true}
}
return &h2Transporter{ return &h2Transporter{
clients: make(map[string]*http.Client), clients: make(map[string]*http.Client),
tlsConfig: &tls.Config{InsecureSkipVerify: true}, tlsConfig: config,
} }
} }
// H2CTransporter creates a Transporter that is used by HTTP2 h2c tunnel client.
func H2CTransporter() Transporter { func H2CTransporter() Transporter {
return &h2Transporter{ return &h2Transporter{
clients: make(map[string]*http.Client), clients: make(map[string]*http.Client),
@ -186,7 +180,7 @@ func (tr *h2Transporter) Dial(addr string, options ...DialOption) (net.Conn, err
if tr.tlsConfig == nil { if tr.tlsConfig == nil {
return conn, nil return conn, nil
} }
return wrapTLSClient(conn, tr.tlsConfig) return wrapTLSClient(conn, cfg)
}, },
} }
client = &http.Client{ client = &http.Client{
@ -277,11 +271,10 @@ func (h *http2Handler) Handle(conn net.Conn) {
} }
func (h *http2Handler) roundTrip(w http.ResponseWriter, r *http.Request) { func (h *http2Handler) roundTrip(w http.ResponseWriter, r *http.Request) {
target := r.Header.Get("Gost-Target") // compitable with old version target := r.Header.Get("Gost-Target")
if target == "" { if target == "" {
target = r.Host target = r.Host
} }
// target := r.Host
if !strings.Contains(target, ":") { if !strings.Contains(target, ":") {
target += ":80" target += ":80"
} }
@ -391,6 +384,7 @@ type http2Listener struct {
errChan chan error errChan chan error
} }
// HTTP2Listener creates a Listener for HTTP2 proxy server.
func HTTP2Listener(addr string, config *tls.Config) (Listener, error) { func HTTP2Listener(addr string, config *tls.Config) (Listener, error) {
l := &http2Listener{ l := &http2Listener{
connChan: make(chan *http2ServerConn, 1024), connChan: make(chan *http2ServerConn, 1024),
@ -596,91 +590,6 @@ func (l *h2Listener) Accept() (conn net.Conn, err error) {
return return
} }
type http2Session struct {
conn net.Conn
clientConn *http2.ClientConn
closeChan chan struct{}
pingChan chan struct{}
}
func newHTTP2Session(conn net.Conn, clientConn *http2.ClientConn, interval time.Duration) *http2Session {
session := &http2Session{
conn: conn,
clientConn: clientConn,
closeChan: make(chan struct{}),
}
if interval > 0 {
session.pingChan = make(chan struct{})
go session.Ping(interval)
}
return session
}
func (s *http2Session) Ping(interval time.Duration) {
if interval <= 0 {
return
}
defer close(s.pingChan)
log.Log("[http2] ping is enabled, interval:", interval)
baseCtx := context.Background()
t := time.NewTicker(interval)
retries := PingRetries
for {
select {
case <-t.C:
if Debug {
log.Log("[http2] sending ping")
}
if !s.clientConn.CanTakeNewRequest() {
log.Logf("[http2] connection is dead")
return
}
ctx, cancel := context.WithTimeout(baseCtx, PingTimeout)
if err := s.clientConn.Ping(ctx); err != nil {
log.Logf("[http2] ping: %s", err)
if retries > 0 {
retries--
log.Log("[http2] retry ping")
cancel()
continue
}
cancel()
return
}
if Debug {
log.Log("[http2] ping OK")
}
cancel()
retries = PingRetries
case <-s.closeChan:
return
}
}
}
func (s *http2Session) Healthy() bool {
select {
case <-s.pingChan:
return false
default:
}
return s.clientConn.CanTakeNewRequest()
}
func (s *http2Session) Close() error {
select {
case <-s.closeChan:
default:
close(s.closeChan)
}
return nil
}
// HTTP2 connection, wrapped up just like a net.Conn // HTTP2 connection, wrapped up just like a net.Conn
type http2Conn struct { type http2Conn struct {
r io.Reader r io.Reader
@ -780,41 +689,41 @@ func (c *http2ServerConn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
} }
// Dummy HTTP2 connection. // a dummy HTTP2 client conn used by HTTP2 client connector
type http2DummyConn struct { type http2ClientConn struct {
raddr string addr string
conn *http2.ClientConn client *http.Client
} }
func (c *http2DummyConn) Read(b []byte) (n int, err error) { func (c *http2ClientConn) Read(b []byte) (n int, err error) {
return 0, &net.OpError{Op: "read", Net: "http2", Source: nil, Addr: nil, Err: errors.New("read not supported")} return 0, &net.OpError{Op: "read", Net: "http2", Source: nil, Addr: nil, Err: errors.New("read not supported")}
} }
func (c *http2DummyConn) Write(b []byte) (n int, err error) { func (c *http2ClientConn) Write(b []byte) (n int, err error) {
return 0, &net.OpError{Op: "write", Net: "http2", Source: nil, Addr: nil, Err: errors.New("write not supported")} return 0, &net.OpError{Op: "write", Net: "http2", Source: nil, Addr: nil, Err: errors.New("write not supported")}
} }
func (c *http2DummyConn) Close() error { func (c *http2ClientConn) Close() error {
return nil return nil
} }
func (c *http2DummyConn) LocalAddr() net.Addr { func (c *http2ClientConn) LocalAddr() net.Addr {
return nil return nil
} }
func (c *http2DummyConn) RemoteAddr() net.Addr { func (c *http2ClientConn) RemoteAddr() net.Addr {
return nil return nil
} }
func (c *http2DummyConn) SetDeadline(t time.Time) error { func (c *http2ClientConn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
} }
func (c *http2DummyConn) SetReadDeadline(t time.Time) error { func (c *http2ClientConn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
} }
func (c *http2DummyConn) SetWriteDeadline(t time.Time) error { func (c *http2ClientConn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
} }

View File

@ -9,6 +9,7 @@ func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile) log.SetFlags(log.LstdFlags | log.Lshortfile)
} }
// LogLogger uses the standard log package as the logger
type LogLogger struct { type LogLogger struct {
} }
@ -20,6 +21,7 @@ func (l *LogLogger) Logf(format string, v ...interface{}) {
log.Output(3, fmt.Sprintf(format, v...)) log.Output(3, fmt.Sprintf(format, v...))
} }
// NopLogger is a null logger that discards the log outputs
type NopLogger struct { type NopLogger struct {
} }