mirror of https://github.com/ginuerzh/gost
10 changed files with 581 additions and 73 deletions
@ -0,0 +1,444 @@ |
|||||
|
package gost |
||||
|
|
||||
|
import ( |
||||
|
"bufio" |
||||
|
"context" |
||||
|
"crypto/tls" |
||||
|
"encoding/base64" |
||||
|
"errors" |
||||
|
"io" |
||||
|
"io/ioutil" |
||||
|
"net" |
||||
|
"net/http" |
||||
|
"net/http/httputil" |
||||
|
"net/url" |
||||
|
"sync" |
||||
|
"time" |
||||
|
|
||||
|
"github.com/go-log/log" |
||||
|
"golang.org/x/net/http2" |
||||
|
) |
||||
|
|
||||
|
type http2Connector struct { |
||||
|
User *url.Userinfo |
||||
|
} |
||||
|
|
||||
|
// HTTP2Connector creates a Connector for HTTP2 proxy client.
|
||||
|
// It accepts an optional auth info for HTTP Basic Authentication.
|
||||
|
func HTTP2Connector(user *url.Userinfo) Connector { |
||||
|
return &http2Connector{User: user} |
||||
|
} |
||||
|
|
||||
|
func (c *http2Connector) Connect(conn net.Conn, addr string) (net.Conn, error) { |
||||
|
cc, ok := conn.(*http2DummyConn) |
||||
|
if !ok { |
||||
|
return nil, errors.New("conn must be a conn wrapper") |
||||
|
} |
||||
|
|
||||
|
pr, pw := io.Pipe() |
||||
|
u := &url.URL{ |
||||
|
Host: addr, |
||||
|
} |
||||
|
req, err := http.NewRequest("CONNECT", u.String(), ioutil.NopCloser(pr)) |
||||
|
if err != nil { |
||||
|
log.Logf("[http2] %s - %s : %s", cc.raddr, addr, err) |
||||
|
return nil, err |
||||
|
} |
||||
|
if c.User != nil { |
||||
|
req.Header.Set("Proxy-Authorization", |
||||
|
"Basic "+base64.StdEncoding.EncodeToString([]byte(c.User.String()))) |
||||
|
} |
||||
|
req.ProtoMajor = 2 |
||||
|
req.ProtoMinor = 0 |
||||
|
|
||||
|
if Debug { |
||||
|
dump, _ := httputil.DumpRequest(req, false) |
||||
|
log.Log("[http2]", string(dump)) |
||||
|
} |
||||
|
resp, err := cc.conn.RoundTrip(req) |
||||
|
if err != nil { |
||||
|
log.Logf("[http2] %s - %s : %s", cc.raddr, addr, err) |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
if Debug { |
||||
|
dump, _ := httputil.DumpResponse(resp, false) |
||||
|
log.Log("[http2]", string(dump)) |
||||
|
} |
||||
|
|
||||
|
if resp.StatusCode != http.StatusOK { |
||||
|
resp.Body.Close() |
||||
|
return nil, errors.New(resp.Status) |
||||
|
} |
||||
|
hc := &http2Conn{r: resp.Body, w: pw} |
||||
|
hc.remoteAddr, _ = net.ResolveTCPAddr("tcp", cc.raddr) |
||||
|
return hc, nil |
||||
|
} |
||||
|
|
||||
|
type http2Transporter struct { |
||||
|
tlsConfig *tls.Config |
||||
|
tr *http2.Transport |
||||
|
chain *Chain |
||||
|
conns map[string]*http2.ClientConn |
||||
|
connMutex sync.Mutex |
||||
|
pingInterval time.Duration |
||||
|
} |
||||
|
|
||||
|
// HTTP2Transporter creates a Transporter that is used by HTTP2 proxy client.
|
||||
|
//
|
||||
|
// Optional chain is a proxy chain that can be used to establish a connection with the HTTP2 server.
|
||||
|
//
|
||||
|
// 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{ |
||||
|
tlsConfig: config, |
||||
|
tr: new(http2.Transport), |
||||
|
chain: chain, |
||||
|
pingInterval: ping, |
||||
|
conns: make(map[string]*http2.ClientConn), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (tr *http2Transporter) Dial(addr string) (net.Conn, error) { |
||||
|
tr.connMutex.Lock() |
||||
|
conn, ok := tr.conns[addr] |
||||
|
|
||||
|
if !ok { |
||||
|
cc, err := tr.chain.Dial(addr) |
||||
|
if err != nil { |
||||
|
tr.connMutex.Unlock() |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
if tr.tlsConfig != nil { |
||||
|
tc := tls.Client(cc, tr.tlsConfig) |
||||
|
if err := tc.Handshake(); err != nil { |
||||
|
tr.connMutex.Unlock() |
||||
|
return nil, err |
||||
|
} |
||||
|
cc = tc |
||||
|
} |
||||
|
conn, err = tr.tr.NewClientConn(cc) |
||||
|
if err != nil { |
||||
|
tr.connMutex.Unlock() |
||||
|
return nil, err |
||||
|
} |
||||
|
tr.conns[addr] = conn |
||||
|
go tr.ping(tr.pingInterval, addr, conn) |
||||
|
} |
||||
|
tr.connMutex.Unlock() |
||||
|
|
||||
|
if !conn.CanTakeNewRequest() { |
||||
|
tr.connMutex.Lock() |
||||
|
delete(tr.conns, addr) // TODO: we could re-connect to the addr automatically.
|
||||
|
tr.connMutex.Unlock() |
||||
|
return nil, errors.New("connection is dead") |
||||
|
} |
||||
|
|
||||
|
return &http2DummyConn{ |
||||
|
raddr: addr, |
||||
|
conn: conn, |
||||
|
}, nil |
||||
|
} |
||||
|
|
||||
|
func (tr *http2Transporter) ping(interval time.Duration, addr string, conn *http2.ClientConn) { |
||||
|
if interval <= 0 { |
||||
|
return |
||||
|
} |
||||
|
log.Log("[http2] ping is enabled, interval:", interval) |
||||
|
|
||||
|
baseCtx := context.Background() |
||||
|
t := time.NewTicker(interval) |
||||
|
retries := PingRetries |
||||
|
for { |
||||
|
select { |
||||
|
case <-t.C: |
||||
|
if !conn.CanTakeNewRequest() { |
||||
|
return |
||||
|
} |
||||
|
ctx, cancel := context.WithTimeout(baseCtx, PingTimeout) |
||||
|
if err := conn.Ping(ctx); err != nil { |
||||
|
log.Logf("[http2] ping: %s", err) |
||||
|
if retries > 0 { |
||||
|
retries-- |
||||
|
log.Log("[http2] retry ping") |
||||
|
cancel() |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
// connection is dead, remove it.
|
||||
|
tr.connMutex.Lock() |
||||
|
delete(tr.conns, addr) |
||||
|
tr.connMutex.Unlock() |
||||
|
|
||||
|
cancel() |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
cancel() |
||||
|
retries = PingRetries |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (tr *http2Transporter) Handshake(conn net.Conn) (net.Conn, error) { |
||||
|
return conn, nil |
||||
|
} |
||||
|
|
||||
|
func (tr *http2Transporter) Multiplex() bool { |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
type http2Handler struct { |
||||
|
server *http2.Server |
||||
|
options *HandlerOptions |
||||
|
} |
||||
|
|
||||
|
// HTTP2Handler creates a server Handler for HTTP2 proxy server.
|
||||
|
func HTTP2Handler(opts ...HandlerOption) Handler { |
||||
|
h := &http2Handler{ |
||||
|
server: new(http2.Server), |
||||
|
options: &HandlerOptions{ |
||||
|
Chain: new(Chain), |
||||
|
}, |
||||
|
} |
||||
|
for _, opt := range opts { |
||||
|
opt(h.options) |
||||
|
} |
||||
|
return h |
||||
|
} |
||||
|
|
||||
|
func (h *http2Handler) Handle(conn net.Conn) { |
||||
|
defer conn.Close() |
||||
|
|
||||
|
if tc, ok := conn.(*tls.Conn); ok { |
||||
|
// NOTE: HTTP2 server will check the TLS version,
|
||||
|
// so we must ensure that the TLS connection is handshake completed.
|
||||
|
if err := tc.Handshake(); err != nil { |
||||
|
log.Logf("[http2] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
opt := http2.ServeConnOpts{ |
||||
|
Handler: http.HandlerFunc(h.handleFunc), |
||||
|
} |
||||
|
h.server.ServeConn(conn, &opt) |
||||
|
} |
||||
|
|
||||
|
func (h *http2Handler) handleFunc(w http.ResponseWriter, r *http.Request) { |
||||
|
target := r.Header.Get("Gost-Target") // compitable with old version
|
||||
|
if target == "" { |
||||
|
target = r.Host |
||||
|
} |
||||
|
|
||||
|
log.Logf("[http2] %s %s - %s %s", r.Method, r.RemoteAddr, target, r.Proto) |
||||
|
if Debug { |
||||
|
dump, _ := httputil.DumpRequest(r, false) |
||||
|
log.Log("[http2]", string(dump)) |
||||
|
} |
||||
|
|
||||
|
w.Header().Set("Proxy-Agent", "gost/"+Version) |
||||
|
|
||||
|
//! if !s.Base.Node.Can("tcp", target) {
|
||||
|
//! glog.Errorf("Unauthorized to tcp connect to %s", target)
|
||||
|
//! return
|
||||
|
//! }
|
||||
|
|
||||
|
u, p, _ := basicProxyAuth(r.Header.Get("Proxy-Authorization")) |
||||
|
if !authenticate(u, p, h.options.Users...) { |
||||
|
log.Logf("[http2] %s <- %s : proxy authentication required", r.RemoteAddr, target) |
||||
|
w.Header().Set("Proxy-Authenticate", "Basic realm=\"gost\"") |
||||
|
w.WriteHeader(http.StatusProxyAuthRequired) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
r.Header.Del("Proxy-Authorization") |
||||
|
r.Header.Del("Proxy-Connection") |
||||
|
|
||||
|
cc, err := h.options.Chain.Dial(target) |
||||
|
if err != nil { |
||||
|
log.Logf("[http2] %s -> %s : %s", r.RemoteAddr, target, err) |
||||
|
w.WriteHeader(http.StatusServiceUnavailable) |
||||
|
return |
||||
|
} |
||||
|
defer cc.Close() |
||||
|
|
||||
|
log.Logf("[http2] %s <-> %s", r.RemoteAddr, target) |
||||
|
|
||||
|
if r.Method == http.MethodConnect { |
||||
|
w.WriteHeader(http.StatusOK) |
||||
|
if fw, ok := w.(http.Flusher); ok { |
||||
|
fw.Flush() |
||||
|
} |
||||
|
|
||||
|
// compatible with HTTP1.x
|
||||
|
if hj, ok := w.(http.Hijacker); ok && r.ProtoMajor == 1 { |
||||
|
// we take over the underly connection
|
||||
|
conn, _, err := hj.Hijack() |
||||
|
if err != nil { |
||||
|
log.Logf("[http2] %s -> %s : %s", r.RemoteAddr, target, err) |
||||
|
w.WriteHeader(http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
defer conn.Close() |
||||
|
|
||||
|
log.Logf("[http2] %s -> %s : downgrade to HTTP/1.1", r.RemoteAddr, target) |
||||
|
transport(conn, cc) |
||||
|
log.Logf("[http2] %s >-< %s", r.RemoteAddr, target) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
errc := make(chan error, 2) |
||||
|
go func() { |
||||
|
_, err := io.Copy(cc, r.Body) |
||||
|
errc <- err |
||||
|
}() |
||||
|
go func() { |
||||
|
_, err := io.Copy(flushWriter{w}, cc) |
||||
|
errc <- err |
||||
|
}() |
||||
|
|
||||
|
select { |
||||
|
case <-errc: |
||||
|
// glog.V(LWARNING).Infoln("exit", err)
|
||||
|
} |
||||
|
log.Logf("[http2] %s >-< %s", r.RemoteAddr, target) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if err = r.Write(cc); err != nil { |
||||
|
log.Logf("[http2] %s -> %s : %s", r.RemoteAddr, target, err) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
resp, err := http.ReadResponse(bufio.NewReader(cc), r) |
||||
|
if err != nil { |
||||
|
log.Logf("[http2] %s -> %s : %s", r.RemoteAddr, target, err) |
||||
|
return |
||||
|
} |
||||
|
defer resp.Body.Close() |
||||
|
|
||||
|
for k, v := range resp.Header { |
||||
|
for _, vv := range v { |
||||
|
w.Header().Add(k, vv) |
||||
|
} |
||||
|
} |
||||
|
w.WriteHeader(resp.StatusCode) |
||||
|
if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil { |
||||
|
log.Logf("[http2] %s <- %s : %s", r.RemoteAddr, target, err) |
||||
|
} |
||||
|
log.Logf("[http2] %s >-< %s", r.RemoteAddr, target) |
||||
|
} |
||||
|
|
||||
|
// HTTP2 connection, wrapped up just like a net.Conn
|
||||
|
type http2Conn struct { |
||||
|
r io.Reader |
||||
|
w io.Writer |
||||
|
remoteAddr net.Addr |
||||
|
localAddr net.Addr |
||||
|
} |
||||
|
|
||||
|
func (c *http2Conn) Read(b []byte) (n int, err error) { |
||||
|
return c.r.Read(b) |
||||
|
} |
||||
|
|
||||
|
func (c *http2Conn) Write(b []byte) (n int, err error) { |
||||
|
return c.w.Write(b) |
||||
|
} |
||||
|
|
||||
|
func (c *http2Conn) Close() (err error) { |
||||
|
if rc, ok := c.r.(io.Closer); ok { |
||||
|
err = rc.Close() |
||||
|
} |
||||
|
if w, ok := c.w.(io.Closer); ok { |
||||
|
err = w.Close() |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
func (c *http2Conn) LocalAddr() net.Addr { |
||||
|
return c.localAddr |
||||
|
} |
||||
|
|
||||
|
func (c *http2Conn) RemoteAddr() net.Addr { |
||||
|
return c.remoteAddr |
||||
|
} |
||||
|
|
||||
|
func (c *http2Conn) SetDeadline(t time.Time) error { |
||||
|
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
||||
|
} |
||||
|
|
||||
|
func (c *http2Conn) SetReadDeadline(t time.Time) error { |
||||
|
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
||||
|
} |
||||
|
|
||||
|
func (c *http2Conn) SetWriteDeadline(t time.Time) error { |
||||
|
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
||||
|
} |
||||
|
|
||||
|
// Dummy HTTP2 connection.
|
||||
|
type http2DummyConn struct { |
||||
|
raddr string |
||||
|
conn *http2.ClientConn |
||||
|
} |
||||
|
|
||||
|
func (c *http2DummyConn) 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")} |
||||
|
} |
||||
|
|
||||
|
func (c *http2DummyConn) 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")} |
||||
|
} |
||||
|
|
||||
|
func (c *http2DummyConn) Close() error { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (c *http2DummyConn) LocalAddr() net.Addr { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (c *http2DummyConn) RemoteAddr() net.Addr { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (c *http2DummyConn) SetDeadline(t time.Time) error { |
||||
|
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 { |
||||
|
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 { |
||||
|
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} |
||||
|
} |
||||
|
|
||||
|
type flushWriter struct { |
||||
|
w io.Writer |
||||
|
} |
||||
|
|
||||
|
func (fw flushWriter) Write(p []byte) (n int, err error) { |
||||
|
defer func() { |
||||
|
if r := recover(); r != nil { |
||||
|
if s, ok := r.(string); ok { |
||||
|
err = errors.New(s) |
||||
|
return |
||||
|
} |
||||
|
err = r.(error) |
||||
|
} |
||||
|
}() |
||||
|
|
||||
|
n, err = fw.w.Write(p) |
||||
|
if err != nil { |
||||
|
// log.Log("flush writer:", err)
|
||||
|
return |
||||
|
} |
||||
|
if f, ok := fw.w.(http.Flusher); ok { |
||||
|
f.Flush() |
||||
|
} |
||||
|
return |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
package gost |
||||
Loading…
Reference in new issue