diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2033b3a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 ginuerzh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/chain.go b/chain.go new file mode 100644 index 0000000..3fdbea5 --- /dev/null +++ b/chain.go @@ -0,0 +1,193 @@ +package gost + +import ( + "crypto/tls" + "errors" + "github.com/golang/glog" + "golang.org/x/net/http2" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httputil" + "net/url" + "strings" +) + +// Proxy chain holds a list of proxy nodes +type ProxyChain struct { + nodes []ProxyNode + lastNode *ProxyNode + http2NodeIndex int + http2Enabled bool + http2Client *http.Client +} + +func NewProxyChain(nodes ...ProxyNode) *ProxyChain { + chain := &ProxyChain{nodes: nodes, http2NodeIndex: -1} + return chain +} + +func (c *ProxyChain) AddProxyNode(node ...ProxyNode) { + c.nodes = append(c.nodes, node...) +} + +// Initialize proxy nodes, mainly check for http2 feature. +// Should be called immediately when proxy nodes are ready. +// +// NOTE: http2 will not be enabled if not called. +func (c *ProxyChain) Init() { + length := len(c.nodes) + if length == 0 { + return + } + + c.lastNode = &c.nodes[length-1] + + // http2 restrict: http2 will be enabled when at least one http2 proxy node present + for i, node := range c.nodes { + if node.Transport == "http2" { + glog.V(LINFO).Infoln("http2 enabled") + cfg := &tls.Config{ + InsecureSkipVerify: node.insecureSkipVerify(), + ServerName: node.serverName, + } + c.initHttp2Client(node.Addr, cfg, c.nodes[:i]...) + c.http2NodeIndex = i + break // shortest chain for http2 + } + } +} + +func (c *ProxyChain) initHttp2Client(addr string, config *tls.Config, nodes ...ProxyNode) { + tr := http2.Transport{ + TLSClientConfig: config, + DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { + // replace the default dialer with our proxy chain. + conn, err := c.dialWithNodes(addr, nodes...) + if err != nil { + return conn, err + } + return tls.Client(conn, cfg), nil + }, + } + c.http2Client = &http.Client{Transport: &tr} + c.http2Enabled = true + +} + +func (c *ProxyChain) Http2Enabled() bool { + return c.http2Enabled +} + +// Connect to addr through proxy chain +func (c *ProxyChain) Dial(addr string) (net.Conn, error) { + if !strings.Contains(addr, ":") { + addr += ":80" + } + return c.dialWithNodes(addr, c.nodes...) +} + +func (c *ProxyChain) dialWithNodes(addr string, nodes ...ProxyNode) (conn net.Conn, err error) { + if len(nodes) == 0 { + return net.DialTimeout("tcp", addr, DialTimeout) + } + + var pc *ProxyConn + + if c.Http2Enabled() { + nodes = nodes[c.http2NodeIndex+1:] + if len(nodes) == 0 { + return c.http2Connect("http", addr) + } + } + pc, err = c.travelNodes(nodes...) + if err != nil { + return + } + if err = pc.Connect(addr); err != nil { + pc.Close() + return + } + + return pc, nil +} + +func (c *ProxyChain) travelNodes(nodes ...ProxyNode) (conn *ProxyConn, err error) { + defer func() { + if err != nil && conn != nil { + conn.Close() + conn = nil + } + }() + + var cc net.Conn + node := nodes[0] + + if c.Http2Enabled() { + cc, err = c.http2Connect("http", node.Addr) + } else { + cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout) + } + if err != nil { + return + } + setKeepAlive(cc, KeepAliveTime) + + pc := NewProxyConn(cc, node) + if err = pc.Handshake(); err != nil { + return + } + conn = pc + for _, node := range nodes[1:] { + if err = conn.Connect(node.Addr); err != nil { + return + } + pc := NewProxyConn(conn, node) + if err = pc.Handshake(); err != nil { + return + } + conn = pc + } + return +} + +func (c *ProxyChain) http2Connect(protocol, addr string) (net.Conn, error) { + if !c.Http2Enabled() { + return nil, errors.New("http2 not enabled") + } + http2Node := c.nodes[c.http2NodeIndex] + + pr, pw := io.Pipe() + req := http.Request{ + Method: http.MethodConnect, + URL: &url.URL{Scheme: "https", Host: http2Node.Addr}, + Header: make(http.Header), + Proto: "HTTP/2.0", + ProtoMajor: 2, + ProtoMinor: 0, + Body: ioutil.NopCloser(pr), + Host: http2Node.Addr, + ContentLength: -1, + } + req.Header.Set("gost-target", addr) + if protocol != "" { + req.Header.Set("gost-protocol", protocol) + } + + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(&req, false) + glog.Infoln(string(dump)) + } + resp, err := c.http2Client.Do(&req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + return nil, errors.New(resp.Status) + } + conn := &Http2ClientConn{r: resp.Body, w: pw} + conn.remoteAddr, _ = net.ResolveTCPAddr("tcp", addr) + return conn, nil +} diff --git a/conn.go b/conn.go index 94bd755..8923cde 100644 --- a/conn.go +++ b/conn.go @@ -1,4 +1,4 @@ -package main +package gost import ( "bufio" @@ -9,557 +9,117 @@ import ( "github.com/ginuerzh/gosocks5" "github.com/golang/glog" "github.com/shadowsocks/shadowsocks-go/shadowsocks" - "io" - "io/ioutil" "net" "net/http" "net/http/httputil" "net/url" "strconv" - "strings" "sync" - //"sync/atomic" - "golang.org/x/net/http2" "time" ) -var ( - connCounter int32 -) +type ProxyConn struct { + conn net.Conn + node ProxyNode + handshaked bool + handshakeMutex sync.Mutex + handshakeErr error +} -var ( - // tcp buffer pool - tcpPool = sync.Pool{ - New: func() interface{} { - return make([]byte, 32*1024) - }, +func NewProxyConn(conn net.Conn, node ProxyNode) *ProxyConn { + return &ProxyConn{ + conn: conn, + node: node, } - // udp buffer pool - udpPool = sync.Pool{ - New: func() interface{} { - return make([]byte, 32*1024) - }, - } -) +} -func listenAndServe(arg Args) error { - var ln net.Listener - var err error +// Handshake based on the proxy node info: transport, protocol, authentication, etc. +// +// NOTE: http2 will be downgrade to http (for protocol) and tls (for transport). +func (c *ProxyConn) Handshake() error { + c.handshakeMutex.Lock() + defer c.handshakeMutex.Unlock() - switch arg.Transport { - case "ws": // websocket connection - return NewWs(arg).ListenAndServe() - case "wss": // websocket security connection - return NewWs(arg).listenAndServeTLS() - case "tls": // tls connection - ln, err = tls.Listen("tcp", arg.Addr, - &tls.Config{Certificates: []tls.Certificate{arg.Cert}}) - case "http2": // http2 connetction - return listenAndServeHttp2(arg, http.HandlerFunc(handlerHttp2Request)) - case "tcp": // Local TCP port forwarding - return listenAndServeTcpForward(arg) - case "udp": // Local UDP port forwarding - return listenAndServeUdpForward(arg) - case "rtcp": // Remote TCP port forwarding - return serveRTcpForward(arg) - case "rudp": // Remote UDP port forwarding - return serveRUdpForward(arg) - default: - ln, err = net.Listen("tcp", arg.Addr) - } - - if err != nil { + if err := c.handshakeErr; err != nil { return err } - - defer ln.Close() - - for { - conn, err := ln.Accept() - if err != nil { - glog.V(LWARNING).Infoln(err) - continue - } - - setKeepAlive(conn, keepAliveTime) - - go handleConn(conn, arg) + if c.handshaked { + return nil } + c.handshakeErr = c.handshake() + return c.handshakeErr } -func listenAndServeHttp2(arg Args, handler http.Handler) error { - srv := http.Server{ - Addr: arg.Addr, - Handler: handler, - TLSConfig: &tls.Config{ - Certificates: []tls.Certificate{arg.Cert}, - }, - } - http2.ConfigureServer(&srv, nil) - return srv.ListenAndServeTLS("", "") -} - -func listenAndServeTcpForward(arg Args) error { - raddr, err := net.ResolveTCPAddr("tcp", arg.Remote) - if err != nil { - return err - } - - ln, err := net.Listen("tcp", arg.Addr) - if err != nil { - return err - } - defer ln.Close() - - for { - conn, err := ln.Accept() - if err != nil { - glog.V(LWARNING).Infoln(err) - continue - } - setKeepAlive(conn, keepAliveTime) - - go handleTcpForward(conn, raddr) - } -} - -func listenAndServeUdpForward(arg Args) error { - laddr, err := net.ResolveUDPAddr("udp", arg.Addr) - if err != nil { - return err - } - - raddr, err := net.ResolveUDPAddr("udp", arg.Remote) - if err != nil { - return err - } - - conn, err := net.ListenUDP("udp", laddr) - if err != nil { - glog.V(LWARNING).Infof("[udp] %s -> %s : %s", laddr, raddr, err) - return err - } - defer conn.Close() - - if len(forwardArgs) == 0 { - for { - b := udpPool.Get().([]byte) - - n, addr, err := conn.ReadFromUDP(b) - if err != nil { - glog.V(LWARNING).Infof("[udp] %s -> %s : %s", laddr, raddr, err) - continue - } - go func() { - handleUdpForwardLocal(conn, addr, raddr, b[:n]) - udpPool.Put(b) - }() - } - } - - rChan, wChan := make(chan *gosocks5.UDPDatagram, 32), make(chan *gosocks5.UDPDatagram, 32) - - go func() { - for { - b := make([]byte, 32*1024) - n, addr, err := conn.ReadFromUDP(b) - if err != nil { - glog.V(LWARNING).Infof("[udp] %s -> %s : %s", laddr, raddr, err) - return - } - - select { - case rChan <- gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(n), 0, ToSocksAddr(addr)), b[:n]): - default: - // glog.V(LWARNING).Infof("[udp-connect] %s -> %s : rbuf is full", laddr, raddr) - } - } - }() - - go func() { - for { - dgram := <-wChan - addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String()) - if err != nil { - glog.V(LWARNING).Infof("[udp] %s <- %s : %s", laddr, raddr, err) - continue // drop silently - } - if _, err = conn.WriteToUDP(dgram.Data, addr); err != nil { - glog.V(LWARNING).Infof("[udp] %s <- %s : %s", laddr, raddr, err) - return - } - } - }() - - for { - handleUdpForwardTunnel(laddr, raddr, rChan, wChan) - } -} - -func serveRTcpForward(arg Args) error { - if len(forwardArgs) == 0 { - return errors.New("rtcp: at least one -F must be assigned") - } - - laddr, err := net.ResolveTCPAddr("tcp", arg.Addr) - if err != nil { - return err - } - raddr, err := net.ResolveTCPAddr("tcp", arg.Remote) - if err != nil { - return err - } - - retry := 0 - for { - conn, _, err := forwardChain(forwardArgs...) - if err != nil { - glog.V(LWARNING).Infof("[rtcp] %s - %s : %s", arg.Addr, arg.Remote, err) - time.Sleep((1 << uint(retry)) * time.Second) - if retry < 5 { - retry++ - } - continue - } - retry = 0 - - if err := connectRTcpForward(conn, laddr, raddr); err != nil { - conn.Close() - time.Sleep(6 * time.Second) - } - } -} - -func serveRUdpForward(arg Args) error { - if len(forwardArgs) == 0 { - return errors.New("rudp: at least one -F must be assigned") - } - - laddr, err := net.ResolveUDPAddr("udp", arg.Addr) - if err != nil { - return err - } - raddr, err := net.ResolveUDPAddr("udp", arg.Remote) - if err != nil { - return err - } - - retry := 0 - for { - conn, _, err := forwardChain(forwardArgs...) - if err != nil { - glog.V(LWARNING).Infof("[rudp] %s - %s : %s", arg.Addr, arg.Remote, err) - time.Sleep((1 << uint(retry)) * time.Second) - if retry < 5 { - retry++ - } - continue - } - retry = 0 - - if err := connectRUdpForward(conn, laddr, raddr); err != nil { - conn.Close() - time.Sleep(6 * time.Second) - } - } -} - -func handleConn(conn net.Conn, arg Args) { - defer conn.Close() - - // socks5 server supported methods - selector := &serverSelector{ - methods: []uint8{ - gosocks5.MethodNoAuth, - gosocks5.MethodUserPass, - MethodTLS, - MethodTLSAuth, - }, - user: arg.User, - cert: arg.Cert, - } - - switch arg.Protocol { - case "ss": // shadowsocks - handleShadow(conn, arg) - return - case "http": - req, err := http.ReadRequest(bufio.NewReader(conn)) - if err != nil { - glog.V(LWARNING).Infoln("[http]", err) - return - } - handleHttpRequest(req, conn, arg) - return - case "socks", "socks5": - conn = gosocks5.ServerConn(conn, selector) - req, err := gosocks5.ReadRequest(conn) - if err != nil { - glog.V(LWARNING).Infoln("[socks5]", err) - return - } - handleSocks5Request(req, conn) - return - } - - // http or socks5 - - //b := make([]byte, 16*1024) - b := tcpPool.Get().([]byte) - defer tcpPool.Put(b) - - n, err := io.ReadAtLeast(conn, b, 2) - if err != nil { - glog.V(LWARNING).Infoln("[client]", err) - return - } - - if b[0] == gosocks5.Ver5 { - mn := int(b[1]) // methods count - length := 2 + mn - if n < length { - if _, err := io.ReadFull(conn, b[n:length]); err != nil { - glog.V(LWARNING).Infoln("[socks5]", err) - return - } - } - methods := b[2 : 2+mn] - method := selector.Select(methods...) - if _, err := conn.Write([]byte{gosocks5.Ver5, method}); err != nil { - glog.V(LWARNING).Infoln("[socks5] select:", err) - return - } - c, err := selector.OnSelected(method, conn) - if err != nil { - glog.V(LWARNING).Infoln("[socks5] onselected:", err) - return - } - conn = c - - req, err := gosocks5.ReadRequest(conn) - if err != nil { - glog.V(LWARNING).Infoln("[socks5] request:", err) - return - } - handleSocks5Request(req, conn) - return - } - - req, err := http.ReadRequest(bufio.NewReader(newReqReader(b[:n], conn))) - if err != nil { - glog.V(LWARNING).Infoln("[http]", err) - return - } - handleHttpRequest(req, conn, arg) -} - -type reqReader struct { - b []byte - r io.Reader -} - -func newReqReader(b []byte, r io.Reader) *reqReader { - return &reqReader{ - b: b, - r: r, - } -} - -func (r *reqReader) Read(p []byte) (n int, err error) { - if len(r.b) == 0 { - return r.r.Read(p) - } - n = copy(p, r.b) - r.b = r.b[n:] - - return -} - -func connect(addr string, prot string, chain ...Args) (conn net.Conn, err error) { - if !strings.Contains(addr, ":") { - addr += ":80" - } - - if enabled, h2host := http2Enabled(); enabled { - return connectHttp2(http2Client, h2host, addr, prot) - } - - if len(chain) == 0 { - return net.DialTimeout("tcp", addr, time.Second*90) - } - - var end Args - conn, end, err = forwardChain(chain...) - if err != nil { - return nil, err - } - if err := establish(conn, addr, end); err != nil { - conn.Close() - return nil, err - } - return conn, nil -} - -func http2Enabled() (enabled bool, host string) { - length := len(forwardArgs) - if http2Client == nil || length == 0 || forwardArgs[length-1].Transport != "http2" { - return - } - return true, forwardArgs[length-1].Addr -} - -func connectHttp2(client *http.Client, host, target string, prot string) (net.Conn, error) { - pr, pw := io.Pipe() - req := http.Request{ - Method: http.MethodConnect, - URL: &url.URL{Scheme: "https", Host: host}, - Header: make(http.Header), - Proto: "HTTP/2.0", - ProtoMajor: 2, - ProtoMinor: 0, - Body: ioutil.NopCloser(pr), - Host: host, - ContentLength: -1, - } - req.Header.Set("gost-target", target) - if prot != "" { - req.Header.Set("gost-protocol", prot) - } - - if glog.V(LDEBUG) { - dump, _ := httputil.DumpRequest(&req, false) - glog.Infoln(string(dump)) - } - resp, err := client.Do(&req) - if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - resp.Body.Close() - return nil, errors.New(resp.Status) - } - conn := &Http2ClientConn{r: resp.Body, w: pw} - conn.remoteAddr, _ = net.ResolveTCPAddr("tcp", target) - return conn, nil -} - -// establish connection throughout the forward chain -func forwardChain(chain ...Args) (conn net.Conn, end Args, err error) { - defer func() { - if err != nil && conn != nil { - conn.Close() - conn = nil - } - }() - - end = chain[0] - if conn, err = net.DialTimeout("tcp", end.Addr, time.Second*90); err != nil { - return - } - - setKeepAlive(conn, keepAliveTime) - - c, err := forward(conn, end) - if err != nil { - return - } - conn = c - - chain = chain[1:] - for _, arg := range chain { - if err = establish(conn, arg.Addr, end); err != nil { - goto exit - } - - c, err = forward(conn, arg) - if err != nil { - goto exit - } - conn = c - end = arg - } - -exit: - return -} - -func forward(conn net.Conn, arg Args) (net.Conn, error) { - var err error - if glog.V(LINFO) { - proto := arg.Protocol - trans := arg.Transport - if proto == "" { - proto = "http" // default is http - } - if trans == "" { // default is tcp - trans = "tcp" - } - glog.V(LDEBUG).Infof("forward: %s/%s %s", proto, trans, arg.Addr) - } - +func (c *ProxyConn) handshake() error { var tlsUsed bool - switch arg.Transport { + switch c.node.Transport { case "ws": // websocket connection - conn, err = wsClient("ws", conn, arg.Addr) + conn, err := wsClient("ws", c.conn, c.node.Addr) if err != nil { - return nil, err + return err } + c.conn = conn case "wss": // websocket security tlsUsed = true - conn, err = wsClient("wss", conn, arg.Addr) + conn, err := wsClient("wss", c.conn, c.node.Addr) if err != nil { - return nil, err + return err } - case "tls": // tls connection + c.conn = conn + case "tls", "http2": // tls connection tlsUsed = true - conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) - // conn = tls.Client(conn, &tls.Config{ServerName: "ice139.com"}) - case "tcp": - fallthrough + cfg := &tls.Config{ + InsecureSkipVerify: c.node.insecureSkipVerify(), + ServerName: c.node.serverName, + } + c.conn = tls.Client(c.conn, cfg) default: } - switch arg.Protocol { - case "socks", "socks5": + switch c.node.Protocol { + case "socks", "socks5": // socks5 handshake with auth and tls supported selector := &clientSelector{ methods: []uint8{ gosocks5.MethodNoAuth, gosocks5.MethodUserPass, //MethodTLS, }, - user: arg.User, + user: c.node.User, } if !tlsUsed { // if transport is not security, enable security socks5 selector.methods = append(selector.methods, MethodTLS) } - c := gosocks5.ClientConn(conn, selector) - if err := c.Handleshake(); err != nil { - return nil, err + conn := gosocks5.ClientConn(c.conn, selector) + if err := conn.Handleshake(); err != nil { + return err } - conn = c + c.conn = conn case "ss": // shadowsocks - if arg.User != nil { - method := arg.User.Username() - password, _ := arg.User.Password() + if c.node.User != nil { + method := c.node.User.Username() + password, _ := c.node.User.Password() cipher, err := shadowsocks.NewCipher(method, password) if err != nil { - return nil, err + return err } - conn = shadowsocks.NewConn(conn, cipher) + c.conn = shadowsocks.NewConn(c.conn, cipher) } - case "http": + case "http", "http2": fallthrough default: } - return conn, nil + c.handshaked = true + + return nil } -func establish(conn net.Conn, addr string, arg Args) error { - switch arg.Protocol { +// Connect to addr through this proxy node +func (c *ProxyConn) Connect(addr string) error { + switch c.node.Protocol { case "ss": // shadowsocks host, port, err := net.SplitHostPort(addr) if err != nil { @@ -576,9 +136,10 @@ func establish(conn net.Conn, addr string, arg Args) error { return err } b := buf.Bytes() - if _, err := conn.Write(b[3:]); err != nil { + if _, err := c.Write(b[3:]); err != nil { return err } + glog.V(LDEBUG).Infoln(req) case "socks", "socks5": host, port, err := net.SplitHostPort(addr) @@ -591,12 +152,12 @@ func establish(conn net.Conn, addr string, arg Args) error { Host: host, Port: uint16(p), }) - if err := req.Write(conn); err != nil { + if err := req.Write(c); err != nil { return err } glog.V(LDEBUG).Infoln(req) - rep, err := gosocks5.ReadReply(conn) + rep, err := gosocks5.ReadReply(c) if err != nil { return err } @@ -604,11 +165,11 @@ func establish(conn net.Conn, addr string, arg Args) error { if rep.Rep != gosocks5.Succeeded { return errors.New("Service unavailable") } - case "http": + case "http", "http2": fallthrough default: req := &http.Request{ - Method: "CONNECT", + Method: http.MethodConnect, URL: &url.URL{Host: addr}, Host: addr, ProtoMajor: 1, @@ -616,11 +177,11 @@ func establish(conn net.Conn, addr string, arg Args) error { Header: make(http.Header), } req.Header.Set("Proxy-Connection", "keep-alive") - if arg.User != nil { + if c.node.User != nil { req.Header.Set("Proxy-Authorization", - "Basic "+base64.StdEncoding.EncodeToString([]byte(arg.User.String()))) + "Basic "+base64.StdEncoding.EncodeToString([]byte(c.node.User.String()))) } - if err := req.Write(conn); err != nil { + if err := req.Write(c); err != nil { return err } if glog.V(LDEBUG) { @@ -628,7 +189,7 @@ func establish(conn net.Conn, addr string, arg Args) error { glog.Infoln(string(dump)) } - resp, err := http.ReadResponse(bufio.NewReader(conn), req) + resp, err := http.ReadResponse(bufio.NewReader(c), req) if err != nil { return err } @@ -643,3 +204,35 @@ func establish(conn net.Conn, addr string, arg Args) error { return nil } + +func (c *ProxyConn) Read(b []byte) (n int, err error) { + return c.conn.Read(b) +} + +func (c *ProxyConn) Write(b []byte) (n int, err error) { + return c.conn.Write(b) +} + +func (c *ProxyConn) Close() error { + return c.conn.Close() +} + +func (c *ProxyConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +func (c *ProxyConn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +func (c *ProxyConn) SetDeadline(t time.Time) error { + return c.conn.SetDeadline(t) +} + +func (c *ProxyConn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +func (c *ProxyConn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} diff --git a/gost.go b/gost.go new file mode 100644 index 0000000..c2732d1 --- /dev/null +++ b/gost.go @@ -0,0 +1,98 @@ +package gost + +import ( + "crypto/tls" + "errors" + "github.com/golang/glog" + "net" + "time" +) + +const ( + LFATAL = iota + LERROR + LWARNING + LINFO + LDEBUG + LVDEBUG // verbose debug for http2 +) + +var ( + DialTimeout = 30 * time.Second + KeepAliveTime = 180 * time.Second +) + +var ( + DefaultCertFile = "cert.pem" + DefaultKeyFile = "key.pem" + + // This is the default cert and key data for convenience, providing your own cert is recommended. + DefaultRawCert = `-----BEGIN CERTIFICATE----- +MIIC5jCCAdCgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD +bzAeFw0xNDAzMTcwNjIwNTFaFw0xNTAzMTcwNjIwNTFaMBIxEDAOBgNVBAoTB0Fj +bWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccNO1xmd4lWSf +d/0/QS3E93cYIWHw831i/IKxigdRD/XMZonLdEHywW6lOiXazaP8e6CqPGSmnl0x +5k/3dvGCMj2JCVxM6+z7NpL+AiwvXmvkj/TOciCgwqssCwYS2CiVwjfazRjx1ZUJ +VDC5qiyRsfktQ2fVHrpnJGVSRagmiQgwGWBilVG9B8QvRtpQKN/GQGq17oIQm8aK +kOdPt93g93ojMIg7YJpgDgOirvVz/hDn7YD4ryrtPos9CMafFkJprymKpRHyvz7P +8a3+OkuPjFjPnwOHQ5u1U3+8vC44vfb1ExWzDLoT8Xp8Gndx39k0f7MVOol3GnYu +MN/dvNUdAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIAoDATBgNVHSUEDDAKBggrBgEF +BQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDALBgkqhkiG +9w0BAQUDggEBAIG8CJqvTIgJnNOK+i5/IUc/3yF/mSCWuG8qP+Fmo2t6T0PVOtc0 +8wiWH5iWtCAhjn0MRY9l/hIjWm6gUZGHCGuEgsOPpJDYGoNLjH9Xwokm4y3LFNRK +UBrrrDbKRNibApBHCapPf6gC5sXcjOwx7P2/kiHDgY7YH47jfcRhtAPNsM4gjsEO +RmwENY+hRUFHIRfQTyalqND+x6PWhRo3K6hpHs4DQEYPq4P2kFPqUqSBymH+Ny5/ +BcQ3wdMNmC6Bm/oiL1QV0M+/InOsAgQk/EDd0kmoU1ZT2lYHQduGmP099bOlHNpS +uqO3vXF3q8SPPr/A9TqSs7BKkBQbe0+cdsA= +-----END CERTIFICATE-----` + DefaultRawKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA3HDTtcZneJVkn3f9P0EtxPd3GCFh8PN9YvyCsYoHUQ/1zGaJ +y3RB8sFupTol2s2j/Hugqjxkpp5dMeZP93bxgjI9iQlcTOvs+zaS/gIsL15r5I/0 +znIgoMKrLAsGEtgolcI32s0Y8dWVCVQwuaoskbH5LUNn1R66ZyRlUkWoJokIMBlg +YpVRvQfEL0baUCjfxkBqte6CEJvGipDnT7fd4Pd6IzCIO2CaYA4Doq71c/4Q5+2A ++K8q7T6LPQjGnxZCaa8piqUR8r8+z/Gt/jpLj4xYz58Dh0ObtVN/vLwuOL329RMV +swy6E/F6fBp3cd/ZNH+zFTqJdxp2LjDf3bzVHQIDAQABAoIBAHal26147nQ+pHwY +jxwers3XDCjWvup7g79lfcqlKi79UiUEA6KYHm7UogMYewt7p4nb2KwH+XycvDiB +aAUf5flXpTs+6IkWauUDiLZi4PlV7uiEexUq5FjirlL0U/6MjbudX4bK4WQ4uxDc +WaV07Kw2iJFOOHLDKT0en9JaX5jtJNc4ZnE9efFoQ5jfypPWtRw65G1rULEg6nvc +GDh+1ce+4foCkpLRC9c24xAwJONZG6x3UqrSS9qfAsb73nWRQrTfUcO3nhoN8VvL +kL9skn1+S06NyUN0KoEtyRBp+RcpXSsBWAo6qZmo/WqhB/gjzWrxVwn20+yJSm35 +ZsMc6QECgYEA8GS+Mp9xfB2szWHz6YTOO1Uu4lHM1ccZMwS1G+dL0KO3uGAiPdvp +woVot6v6w88t7onXsLo5pgz7SYug0CpkF3K/MRd1Ar4lH7PK7IBQ6rFr9ppVxDbx +AEWRswUoPbKCr7W6HU8LbQHDavsDlEIwc6+DiwnL4BzlKjb7RpgQEz0CgYEA6sB5 +uHvx3Y5FDcGk1n73leQSAcq14l3ZLNpjrs8msoREDil/j5WmuSN58/7PGMiMgHEi +1vLm3H796JmvGr9OBvspOjHyk07ui2/We/j9Hoxm1VWhyi8HkLNDj70HKalTTFMz +RHO4O+0xCva+h9mKZrRMVktXr2jjdFn/0MYIZ2ECgYAIIsC1IeRLWQ3CHbCNlKsO +IwHlMvOFwKk/qsceXKOaOhA7szU1dr3gkXdL0Aw6mEZrrkqYdpUA46uVf54/rU+Z +445I8QxKvXiwK/uQKX+TkdGflPWWIG3jnnch4ejMvb/ihnn4B/bRB6A/fKNQXzUY +lTYUfI5j1VaEKTwz1W2l2QKBgByFCcSp+jZqhGUpc3dDsZyaOr3Q/Mvlju7uEVI5 +hIAHpaT60a6GBd1UPAqymEJwivFHzW3D0NxU6VAK68UaHMaoWNfjHY9b9YsnKS2i +kE3XzN56Ks+/avHfdYPO+UHMenw5V28nh+hv5pdoZrlmanQTz3pkaOC8o3WNQZEB +nh/BAoGBAMY5z2f1pmMhrvtPDSlEVjgjELbaInxFaxPLR4Pdyzn83gtIIU14+R8X +2LPs6PPwrNjWnIgrUSVXncIFL3pa45B+Mx1pYCpOAB1+nCZjIBQmpeo4Y0dwA/XH +85EthKPvoszm+OPbyI16OcePV5ocX7lupRYuAo0pek7bomhmHWHz +-----END RSA PRIVATE KEY-----` +) + +func setKeepAlive(conn net.Conn, d time.Duration) error { + c, ok := conn.(*net.TCPConn) + if !ok { + return errors.New("Not a TCP connection") + } + if err := c.SetKeepAlive(true); err != nil { + return err + } + if err := c.SetKeepAlivePeriod(d); err != nil { + return err + } + return nil +} + +func loadCertificate(certFile, keyFile string) (tls.Certificate, error) { + tlsCert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err == nil { + return tlsCert, nil + } + glog.V(LWARNING).Infoln(err) + return tls.X509KeyPair([]byte(DefaultRawCert), []byte(DefaultRawKey)) +} diff --git a/gost/conn.go b/gost/conn.go new file mode 100644 index 0000000..94bd755 --- /dev/null +++ b/gost/conn.go @@ -0,0 +1,645 @@ +package main + +import ( + "bufio" + "bytes" + "crypto/tls" + "encoding/base64" + "errors" + "github.com/ginuerzh/gosocks5" + "github.com/golang/glog" + "github.com/shadowsocks/shadowsocks-go/shadowsocks" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httputil" + "net/url" + "strconv" + "strings" + "sync" + //"sync/atomic" + "golang.org/x/net/http2" + "time" +) + +var ( + connCounter int32 +) + +var ( + // tcp buffer pool + tcpPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 32*1024) + }, + } + // udp buffer pool + udpPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 32*1024) + }, + } +) + +func listenAndServe(arg Args) error { + var ln net.Listener + var err error + + switch arg.Transport { + case "ws": // websocket connection + return NewWs(arg).ListenAndServe() + case "wss": // websocket security connection + return NewWs(arg).listenAndServeTLS() + case "tls": // tls connection + ln, err = tls.Listen("tcp", arg.Addr, + &tls.Config{Certificates: []tls.Certificate{arg.Cert}}) + case "http2": // http2 connetction + return listenAndServeHttp2(arg, http.HandlerFunc(handlerHttp2Request)) + case "tcp": // Local TCP port forwarding + return listenAndServeTcpForward(arg) + case "udp": // Local UDP port forwarding + return listenAndServeUdpForward(arg) + case "rtcp": // Remote TCP port forwarding + return serveRTcpForward(arg) + case "rudp": // Remote UDP port forwarding + return serveRUdpForward(arg) + default: + ln, err = net.Listen("tcp", arg.Addr) + } + + if err != nil { + return err + } + + defer ln.Close() + + for { + conn, err := ln.Accept() + if err != nil { + glog.V(LWARNING).Infoln(err) + continue + } + + setKeepAlive(conn, keepAliveTime) + + go handleConn(conn, arg) + } +} + +func listenAndServeHttp2(arg Args, handler http.Handler) error { + srv := http.Server{ + Addr: arg.Addr, + Handler: handler, + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{arg.Cert}, + }, + } + http2.ConfigureServer(&srv, nil) + return srv.ListenAndServeTLS("", "") +} + +func listenAndServeTcpForward(arg Args) error { + raddr, err := net.ResolveTCPAddr("tcp", arg.Remote) + if err != nil { + return err + } + + ln, err := net.Listen("tcp", arg.Addr) + if err != nil { + return err + } + defer ln.Close() + + for { + conn, err := ln.Accept() + if err != nil { + glog.V(LWARNING).Infoln(err) + continue + } + setKeepAlive(conn, keepAliveTime) + + go handleTcpForward(conn, raddr) + } +} + +func listenAndServeUdpForward(arg Args) error { + laddr, err := net.ResolveUDPAddr("udp", arg.Addr) + if err != nil { + return err + } + + raddr, err := net.ResolveUDPAddr("udp", arg.Remote) + if err != nil { + return err + } + + conn, err := net.ListenUDP("udp", laddr) + if err != nil { + glog.V(LWARNING).Infof("[udp] %s -> %s : %s", laddr, raddr, err) + return err + } + defer conn.Close() + + if len(forwardArgs) == 0 { + for { + b := udpPool.Get().([]byte) + + n, addr, err := conn.ReadFromUDP(b) + if err != nil { + glog.V(LWARNING).Infof("[udp] %s -> %s : %s", laddr, raddr, err) + continue + } + go func() { + handleUdpForwardLocal(conn, addr, raddr, b[:n]) + udpPool.Put(b) + }() + } + } + + rChan, wChan := make(chan *gosocks5.UDPDatagram, 32), make(chan *gosocks5.UDPDatagram, 32) + + go func() { + for { + b := make([]byte, 32*1024) + n, addr, err := conn.ReadFromUDP(b) + if err != nil { + glog.V(LWARNING).Infof("[udp] %s -> %s : %s", laddr, raddr, err) + return + } + + select { + case rChan <- gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(n), 0, ToSocksAddr(addr)), b[:n]): + default: + // glog.V(LWARNING).Infof("[udp-connect] %s -> %s : rbuf is full", laddr, raddr) + } + } + }() + + go func() { + for { + dgram := <-wChan + addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String()) + if err != nil { + glog.V(LWARNING).Infof("[udp] %s <- %s : %s", laddr, raddr, err) + continue // drop silently + } + if _, err = conn.WriteToUDP(dgram.Data, addr); err != nil { + glog.V(LWARNING).Infof("[udp] %s <- %s : %s", laddr, raddr, err) + return + } + } + }() + + for { + handleUdpForwardTunnel(laddr, raddr, rChan, wChan) + } +} + +func serveRTcpForward(arg Args) error { + if len(forwardArgs) == 0 { + return errors.New("rtcp: at least one -F must be assigned") + } + + laddr, err := net.ResolveTCPAddr("tcp", arg.Addr) + if err != nil { + return err + } + raddr, err := net.ResolveTCPAddr("tcp", arg.Remote) + if err != nil { + return err + } + + retry := 0 + for { + conn, _, err := forwardChain(forwardArgs...) + if err != nil { + glog.V(LWARNING).Infof("[rtcp] %s - %s : %s", arg.Addr, arg.Remote, err) + time.Sleep((1 << uint(retry)) * time.Second) + if retry < 5 { + retry++ + } + continue + } + retry = 0 + + if err := connectRTcpForward(conn, laddr, raddr); err != nil { + conn.Close() + time.Sleep(6 * time.Second) + } + } +} + +func serveRUdpForward(arg Args) error { + if len(forwardArgs) == 0 { + return errors.New("rudp: at least one -F must be assigned") + } + + laddr, err := net.ResolveUDPAddr("udp", arg.Addr) + if err != nil { + return err + } + raddr, err := net.ResolveUDPAddr("udp", arg.Remote) + if err != nil { + return err + } + + retry := 0 + for { + conn, _, err := forwardChain(forwardArgs...) + if err != nil { + glog.V(LWARNING).Infof("[rudp] %s - %s : %s", arg.Addr, arg.Remote, err) + time.Sleep((1 << uint(retry)) * time.Second) + if retry < 5 { + retry++ + } + continue + } + retry = 0 + + if err := connectRUdpForward(conn, laddr, raddr); err != nil { + conn.Close() + time.Sleep(6 * time.Second) + } + } +} + +func handleConn(conn net.Conn, arg Args) { + defer conn.Close() + + // socks5 server supported methods + selector := &serverSelector{ + methods: []uint8{ + gosocks5.MethodNoAuth, + gosocks5.MethodUserPass, + MethodTLS, + MethodTLSAuth, + }, + user: arg.User, + cert: arg.Cert, + } + + switch arg.Protocol { + case "ss": // shadowsocks + handleShadow(conn, arg) + return + case "http": + req, err := http.ReadRequest(bufio.NewReader(conn)) + if err != nil { + glog.V(LWARNING).Infoln("[http]", err) + return + } + handleHttpRequest(req, conn, arg) + return + case "socks", "socks5": + conn = gosocks5.ServerConn(conn, selector) + req, err := gosocks5.ReadRequest(conn) + if err != nil { + glog.V(LWARNING).Infoln("[socks5]", err) + return + } + handleSocks5Request(req, conn) + return + } + + // http or socks5 + + //b := make([]byte, 16*1024) + b := tcpPool.Get().([]byte) + defer tcpPool.Put(b) + + n, err := io.ReadAtLeast(conn, b, 2) + if err != nil { + glog.V(LWARNING).Infoln("[client]", err) + return + } + + if b[0] == gosocks5.Ver5 { + mn := int(b[1]) // methods count + length := 2 + mn + if n < length { + if _, err := io.ReadFull(conn, b[n:length]); err != nil { + glog.V(LWARNING).Infoln("[socks5]", err) + return + } + } + methods := b[2 : 2+mn] + method := selector.Select(methods...) + if _, err := conn.Write([]byte{gosocks5.Ver5, method}); err != nil { + glog.V(LWARNING).Infoln("[socks5] select:", err) + return + } + c, err := selector.OnSelected(method, conn) + if err != nil { + glog.V(LWARNING).Infoln("[socks5] onselected:", err) + return + } + conn = c + + req, err := gosocks5.ReadRequest(conn) + if err != nil { + glog.V(LWARNING).Infoln("[socks5] request:", err) + return + } + handleSocks5Request(req, conn) + return + } + + req, err := http.ReadRequest(bufio.NewReader(newReqReader(b[:n], conn))) + if err != nil { + glog.V(LWARNING).Infoln("[http]", err) + return + } + handleHttpRequest(req, conn, arg) +} + +type reqReader struct { + b []byte + r io.Reader +} + +func newReqReader(b []byte, r io.Reader) *reqReader { + return &reqReader{ + b: b, + r: r, + } +} + +func (r *reqReader) Read(p []byte) (n int, err error) { + if len(r.b) == 0 { + return r.r.Read(p) + } + n = copy(p, r.b) + r.b = r.b[n:] + + return +} + +func connect(addr string, prot string, chain ...Args) (conn net.Conn, err error) { + if !strings.Contains(addr, ":") { + addr += ":80" + } + + if enabled, h2host := http2Enabled(); enabled { + return connectHttp2(http2Client, h2host, addr, prot) + } + + if len(chain) == 0 { + return net.DialTimeout("tcp", addr, time.Second*90) + } + + var end Args + conn, end, err = forwardChain(chain...) + if err != nil { + return nil, err + } + if err := establish(conn, addr, end); err != nil { + conn.Close() + return nil, err + } + return conn, nil +} + +func http2Enabled() (enabled bool, host string) { + length := len(forwardArgs) + if http2Client == nil || length == 0 || forwardArgs[length-1].Transport != "http2" { + return + } + return true, forwardArgs[length-1].Addr +} + +func connectHttp2(client *http.Client, host, target string, prot string) (net.Conn, error) { + pr, pw := io.Pipe() + req := http.Request{ + Method: http.MethodConnect, + URL: &url.URL{Scheme: "https", Host: host}, + Header: make(http.Header), + Proto: "HTTP/2.0", + ProtoMajor: 2, + ProtoMinor: 0, + Body: ioutil.NopCloser(pr), + Host: host, + ContentLength: -1, + } + req.Header.Set("gost-target", target) + if prot != "" { + req.Header.Set("gost-protocol", prot) + } + + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(&req, false) + glog.Infoln(string(dump)) + } + resp, err := client.Do(&req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + return nil, errors.New(resp.Status) + } + conn := &Http2ClientConn{r: resp.Body, w: pw} + conn.remoteAddr, _ = net.ResolveTCPAddr("tcp", target) + return conn, nil +} + +// establish connection throughout the forward chain +func forwardChain(chain ...Args) (conn net.Conn, end Args, err error) { + defer func() { + if err != nil && conn != nil { + conn.Close() + conn = nil + } + }() + + end = chain[0] + if conn, err = net.DialTimeout("tcp", end.Addr, time.Second*90); err != nil { + return + } + + setKeepAlive(conn, keepAliveTime) + + c, err := forward(conn, end) + if err != nil { + return + } + conn = c + + chain = chain[1:] + for _, arg := range chain { + if err = establish(conn, arg.Addr, end); err != nil { + goto exit + } + + c, err = forward(conn, arg) + if err != nil { + goto exit + } + conn = c + end = arg + } + +exit: + return +} + +func forward(conn net.Conn, arg Args) (net.Conn, error) { + var err error + if glog.V(LINFO) { + proto := arg.Protocol + trans := arg.Transport + if proto == "" { + proto = "http" // default is http + } + if trans == "" { // default is tcp + trans = "tcp" + } + glog.V(LDEBUG).Infof("forward: %s/%s %s", proto, trans, arg.Addr) + } + + var tlsUsed bool + + switch arg.Transport { + case "ws": // websocket connection + conn, err = wsClient("ws", conn, arg.Addr) + if err != nil { + return nil, err + } + case "wss": // websocket security + tlsUsed = true + conn, err = wsClient("wss", conn, arg.Addr) + if err != nil { + return nil, err + } + case "tls": // tls connection + tlsUsed = true + conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + // conn = tls.Client(conn, &tls.Config{ServerName: "ice139.com"}) + case "tcp": + fallthrough + default: + } + + switch arg.Protocol { + case "socks", "socks5": + selector := &clientSelector{ + methods: []uint8{ + gosocks5.MethodNoAuth, + gosocks5.MethodUserPass, + //MethodTLS, + }, + user: arg.User, + } + + if !tlsUsed { // if transport is not security, enable security socks5 + selector.methods = append(selector.methods, MethodTLS) + } + + c := gosocks5.ClientConn(conn, selector) + if err := c.Handleshake(); err != nil { + return nil, err + } + conn = c + case "ss": // shadowsocks + if arg.User != nil { + method := arg.User.Username() + password, _ := arg.User.Password() + cipher, err := shadowsocks.NewCipher(method, password) + if err != nil { + return nil, err + } + conn = shadowsocks.NewConn(conn, cipher) + } + case "http": + fallthrough + default: + } + + return conn, nil +} + +func establish(conn net.Conn, addr string, arg Args) error { + switch arg.Protocol { + case "ss": // shadowsocks + host, port, err := net.SplitHostPort(addr) + if err != nil { + return err + } + p, _ := strconv.Atoi(port) + req := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{ + Type: gosocks5.AddrDomain, + Host: host, + Port: uint16(p), + }) + buf := bytes.Buffer{} + if err := req.Write(&buf); err != nil { + return err + } + b := buf.Bytes() + if _, err := conn.Write(b[3:]); err != nil { + return err + } + glog.V(LDEBUG).Infoln(req) + case "socks", "socks5": + host, port, err := net.SplitHostPort(addr) + if err != nil { + return err + } + p, _ := strconv.Atoi(port) + req := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{ + Type: gosocks5.AddrDomain, + Host: host, + Port: uint16(p), + }) + if err := req.Write(conn); err != nil { + return err + } + glog.V(LDEBUG).Infoln(req) + + rep, err := gosocks5.ReadReply(conn) + if err != nil { + return err + } + glog.V(LDEBUG).Infoln(rep) + if rep.Rep != gosocks5.Succeeded { + return errors.New("Service unavailable") + } + case "http": + fallthrough + default: + req := &http.Request{ + Method: "CONNECT", + URL: &url.URL{Host: addr}, + Host: addr, + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(http.Header), + } + req.Header.Set("Proxy-Connection", "keep-alive") + if arg.User != nil { + req.Header.Set("Proxy-Authorization", + "Basic "+base64.StdEncoding.EncodeToString([]byte(arg.User.String()))) + } + if err := req.Write(conn); err != nil { + return err + } + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(req, false) + glog.Infoln(string(dump)) + } + + resp, err := http.ReadResponse(bufio.NewReader(conn), req) + if err != nil { + return err + } + if glog.V(LDEBUG) { + dump, _ := httputil.DumpResponse(resp, false) + glog.Infoln(string(dump)) + } + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + } + + return nil +} diff --git a/forward.go b/gost/forward.go similarity index 100% rename from forward.go rename to gost/forward.go diff --git a/gost/http.go b/gost/http.go new file mode 100644 index 0000000..b2684ea --- /dev/null +++ b/gost/http.go @@ -0,0 +1,310 @@ +package main + +import ( + "bufio" + "crypto/tls" + "encoding/base64" + "github.com/golang/glog" + "golang.org/x/net/http2" + "io" + "net" + "net/http" + "net/http/httputil" + "strings" + "time" +) + +var ( + http2Client *http.Client +) + +func handleHttpRequest(req *http.Request, conn net.Conn, arg Args) { + glog.V(LINFO).Infof("[http] %s %s - %s %s", req.Method, conn.RemoteAddr(), req.Host, req.Proto) + + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(req, false) + glog.Infoln(string(dump)) + } + + var username, password string + if arg.User != nil { + username = arg.User.Username() + password, _ = arg.User.Password() + } + + u, p, _ := basicAuth(req.Header.Get("Proxy-Authorization")) + req.Header.Del("Proxy-Authorization") + if (username != "" && u != username) || (password != "" && p != password) { + resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" + + "Proxy-Authenticate: Basic realm=\"gost\"\r\n" + + "Proxy-Agent: gost/" + Version + "\r\n\r\n" + + if _, err := conn.Write([]byte(resp)); err != nil { + glog.V(LWARNING).Infof("[http] %s <- %s : %s", conn.RemoteAddr(), req.Host, err) + } + glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, resp) + + glog.V(LWARNING).Infof("[http] %s <- %s : proxy authentication required", conn.RemoteAddr(), req.Host) + return + } + + if len(forwardArgs) > 0 { + last := forwardArgs[len(forwardArgs)-1] + if last.Protocol == "http" || last.Protocol == "" { + forwardHttpRequest(req, conn, arg) + return + } + } + + c, err := connect(req.Host, "http", forwardArgs...) + if err != nil { + glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err) + + b := []byte("HTTP/1.1 503 Service unavailable\r\n" + + "Proxy-Agent: gost/" + Version + "\r\n\r\n") + glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b)) + conn.Write(b) + return + } + defer c.Close() + + if req.Method == http.MethodConnect { + b := []byte("HTTP/1.1 200 Connection established\r\n" + + "Proxy-Agent: gost/" + Version + "\r\n\r\n") + glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b)) + conn.Write(b) + } else { + req.Header.Del("Proxy-Connection") + req.Header.Set("Connection", "Keep-Alive") + + if err = req.Write(c); err != nil { + glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err) + return + } + } + + glog.V(LINFO).Infof("[http] %s <-> %s", conn.RemoteAddr(), req.Host) + Transport(conn, c) + glog.V(LINFO).Infof("[http] %s >-< %s", conn.RemoteAddr(), req.Host) +} + +func forwardHttpRequest(req *http.Request, conn net.Conn, arg Args) { + last := forwardArgs[len(forwardArgs)-1] + c, _, err := forwardChain(forwardArgs...) + if err != nil { + glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), last.Addr, err) + + b := []byte("HTTP/1.1 503 Service unavailable\r\n" + + "Proxy-Agent: gost/" + Version + "\r\n\r\n") + glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), last.Addr, string(b)) + conn.Write(b) + return + } + defer c.Close() + + if last.User != nil { + req.Header.Set("Proxy-Authorization", + "Basic "+base64.StdEncoding.EncodeToString([]byte(last.User.String()))) + } + + if err = req.Write(c); err != nil { + glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err) + return + } + glog.V(LINFO).Infof("[http] %s <-> %s", conn.RemoteAddr(), req.Host) + Transport(conn, c) + glog.V(LINFO).Infof("[http] %s >-< %s", conn.RemoteAddr(), req.Host) + return +} + +type Http2ClientConn struct { + r io.Reader + w io.Writer + localAddr net.Addr + remoteAddr net.Addr +} + +func (c *Http2ClientConn) Read(b []byte) (n int, err error) { + return c.r.Read(b) +} + +func (c *Http2ClientConn) Write(b []byte) (n int, err error) { + return c.w.Write(b) +} + +func (c *Http2ClientConn) Close() error { + if rc, ok := c.r.(io.ReadCloser); ok { + return rc.Close() + } + return nil +} + +func (c *Http2ClientConn) LocalAddr() net.Addr { + return c.localAddr +} + +func (c *Http2ClientConn) RemoteAddr() net.Addr { + return c.remoteAddr +} + +func (c *Http2ClientConn) SetDeadline(t time.Time) error { + return nil +} + +func (c *Http2ClientConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (c *Http2ClientConn) SetWriteDeadline(t time.Time) error { + return nil +} + +// init http2 client with target http2 proxy server addr, and forward chain chain +func initHttp2Client(host string, chain ...Args) { + tr := http2.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { + // replace the default dialer with our forward chain. + conn, err := connect(host, "http2", chain...) + if err != nil { + return conn, err + } + return tls.Client(conn, cfg), nil + }, + } + http2Client = &http.Client{Transport: &tr} +} + +func handlerHttp2Request(w http.ResponseWriter, req *http.Request) { + target := req.Header.Get("gost-target") + if target == "" { + target = req.Host + } + + glog.V(LINFO).Infof("[http2] %s %s - %s %s", req.Method, req.RemoteAddr, target, req.Proto) + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(req, false) + glog.Infoln(string(dump)) + } + + c, err := connect(target, req.Header.Get("gost-protocol"), forwardArgs...) + if err != nil { + glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) + w.Header().Set("Proxy-Agent", "gost/"+Version) + w.WriteHeader(http.StatusServiceUnavailable) + if fw, ok := w.(http.Flusher); ok { + fw.Flush() + } + return + } + defer c.Close() + + glog.V(LINFO).Infof("[http2] %s <-> %s", req.RemoteAddr, target) + errc := make(chan error, 2) + + if req.Method == http.MethodConnect { + w.Header().Set("Proxy-Agent", "gost/"+Version) + w.WriteHeader(http.StatusOK) + if fw, ok := w.(http.Flusher); ok { + fw.Flush() + } + + // compatible with HTTP 1.x + if hj, ok := w.(http.Hijacker); ok && req.ProtoMajor == 1 { + // we take over the underly connection + conn, _, err := hj.Hijack() + if err != nil { + glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) + return + } + defer conn.Close() + + go Pipe(conn, c, errc) + go Pipe(c, conn, errc) + } else { + go Pipe(req.Body, c, errc) + go Pipe(c, flushWriter{w}, errc) + } + + select { + case <-errc: + // glog.V(LWARNING).Infoln("exit", err) + } + } else { + req.Header.Set("Connection", "Keep-Alive") + if err = req.Write(c); err != nil { + glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) + return + } + + resp, err := http.ReadResponse(bufio.NewReader(c), req) + if err != nil { + glog.V(LWARNING).Infoln(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 fw, ok := w.(http.Flusher); ok { + fw.Flush() + } + if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil { + glog.V(LWARNING).Infof("[http2] %s <- %s : %s", req.RemoteAddr, target, err) + } + } + + glog.V(LINFO).Infof("[http2] %s >-< %s", req.RemoteAddr, target) +} + +//func processSocks5OverHttp2() + +func handleHttp2Transport(w http.ResponseWriter, req *http.Request) { + glog.V(LINFO).Infof("[http2] %s - %s", req.RemoteAddr, req.Host) + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(req, false) + glog.Infoln(string(dump)) + } +} + +func basicAuth(authInfo string) (username, password string, ok bool) { + if authInfo == "" { + return + } + + if !strings.HasPrefix(authInfo, "Basic ") { + return + } + c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authInfo, "Basic ")) + if err != nil { + return + } + cs := string(c) + s := strings.IndexByte(cs, ':') + if s < 0 { + return + } + + return cs[:s], cs[s+1:], true +} + +type flushWriter struct { + w io.Writer +} + +func (fw flushWriter) Write(p []byte) (n int, err error) { + n, err = fw.w.Write(p) + if err != nil { + glog.V(LWARNING).Infoln("flush writer:", err) + } + if f, ok := fw.w.(http.Flusher); ok { + f.Flush() + } + return +} diff --git a/main.go b/gost/main.go similarity index 100% rename from main.go rename to gost/main.go diff --git a/gost/socks.go b/gost/socks.go new file mode 100644 index 0000000..d304930 --- /dev/null +++ b/gost/socks.go @@ -0,0 +1,553 @@ +package main + +import ( + //"bytes" + "crypto/tls" + "errors" + "github.com/ginuerzh/gosocks5" + "github.com/golang/glog" + //"os/exec" + //"io" + //"io/ioutil" + "net" + "net/url" + "strconv" + "time" +) + +const ( + MethodTLS uint8 = 0x80 // extended method for tls + MethodTLSAuth uint8 = 0x82 // extended method for tls+auth +) + +const ( + CmdUdpConnect uint8 = 0xF1 // extended method for udp local port forwarding + CmdUdpTun uint8 = 0xF3 // extended method for udp over tcp +) + +type clientSelector struct { + methods []uint8 + user *url.Userinfo +} + +func (selector *clientSelector) Methods() []uint8 { + return selector.methods +} + +func (selector *clientSelector) Select(methods ...uint8) (method uint8) { + return +} + +func (selector *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) { + switch method { + case MethodTLS: + conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + + case gosocks5.MethodUserPass, MethodTLSAuth: + if method == MethodTLSAuth { + conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + } + + var username, password string + if selector.user != nil { + username = selector.user.Username() + password, _ = selector.user.Password() + } + + req := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, username, password) + if err := req.Write(conn); err != nil { + glog.V(LWARNING).Infoln("socks5 auth:", err) + return nil, err + } + glog.V(LDEBUG).Infoln(req) + + resp, err := gosocks5.ReadUserPassResponse(conn) + if err != nil { + glog.V(LWARNING).Infoln("socks5 auth:", err) + return nil, err + } + glog.V(LDEBUG).Infoln(resp) + + if resp.Status != gosocks5.Succeeded { + return nil, gosocks5.ErrAuthFailure + } + case gosocks5.MethodNoAcceptable: + return nil, gosocks5.ErrBadMethod + } + + return conn, nil +} + +type serverSelector struct { + methods []uint8 + user *url.Userinfo + cert tls.Certificate +} + +func (selector *serverSelector) Methods() []uint8 { + return selector.methods +} + +func (selector *serverSelector) Select(methods ...uint8) (method uint8) { + glog.V(LDEBUG).Infof("%d %d %v", gosocks5.Ver5, len(methods), methods) + + method = gosocks5.MethodNoAuth + for _, m := range methods { + if m == MethodTLS { + method = m + break + } + } + + // when user/pass is set, auth is mandatory + if selector.user != nil { + if method == gosocks5.MethodNoAuth { + method = gosocks5.MethodUserPass + } + if method == MethodTLS { + method = MethodTLSAuth + } + } + + return +} + +func (selector *serverSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) { + glog.V(LDEBUG).Infof("%d %d", gosocks5.Ver5, method) + + switch method { + case MethodTLS: + conn = tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{selector.cert}}) + + case gosocks5.MethodUserPass, MethodTLSAuth: + if method == MethodTLSAuth { + conn = tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{selector.cert}}) + } + + req, err := gosocks5.ReadUserPassRequest(conn) + if err != nil { + glog.V(LWARNING).Infoln("[socks5-auth]", err) + return nil, err + } + glog.V(LDEBUG).Infoln("[socks5]", req.String()) + + var username, password string + if selector.user != nil { + username = selector.user.Username() + password, _ = selector.user.Password() + } + + if (username != "" && req.Username != username) || (password != "" && req.Password != password) { + resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure) + if err := resp.Write(conn); err != nil { + glog.V(LWARNING).Infoln("[socks5-auth]", err) + return nil, err + } + glog.V(LDEBUG).Infoln("[socks5]", resp) + glog.V(LWARNING).Infoln("[socks5-auth] proxy authentication required") + + return nil, gosocks5.ErrAuthFailure + } + + resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Succeeded) + if err := resp.Write(conn); err != nil { + glog.V(LWARNING).Infoln("[socks5-auth]", err) + return nil, err + } + glog.V(LDEBUG).Infoln(resp) + + case gosocks5.MethodNoAcceptable: + return nil, gosocks5.ErrBadMethod + } + + return conn, nil +} + +func handleSocks5Request(req *gosocks5.Request, conn net.Conn) { + glog.V(LDEBUG).Infof("[socks5] %s -> %s\n%s", conn.RemoteAddr(), req.Addr, req) + + switch req.Cmd { + case gosocks5.CmdConnect: + glog.V(LINFO).Infof("[socks5-connect] %s - %s", conn.RemoteAddr(), req.Addr) + + tconn, err := connect(req.Addr.String(), "socks5", forwardArgs...) + if err != nil { + glog.V(LWARNING).Infof("[socks5-connect] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + rep := gosocks5.NewReply(gosocks5.HostUnreachable, nil) + if err := rep.Write(conn); err != nil { + glog.V(LWARNING).Infof("[socks5-connect] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) + } else { + glog.V(LDEBUG).Infof("[socks5-connect] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) + } + return + } + defer tconn.Close() + + rep := gosocks5.NewReply(gosocks5.Succeeded, nil) + if err := rep.Write(conn); err != nil { + glog.V(LWARNING).Infof("[socks5-connect] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) + return + } + glog.V(LDEBUG).Infof("[socks5-connect] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) + + glog.V(LINFO).Infof("[socks5-connect] %s <-> %s", conn.RemoteAddr(), req.Addr) + Transport(conn, tconn) + glog.V(LINFO).Infof("[socks5-connect] %s >-< %s", conn.RemoteAddr(), req.Addr) + + case gosocks5.CmdBind: + glog.V(LINFO).Infof("[socks5-bind] %s - %s", conn.RemoteAddr(), req.Addr) + + reply, fconn, err := socks5Bind(req, conn) + if reply != nil { + if err := reply.Write(conn); err != nil { + glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) + if fconn != nil { + fconn.Close() + } + return + } + glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, reply) + } + + if err != nil { + glog.V(LWARNING).Infof("[socks5-bind] %s - %s : %s", conn.RemoteAddr(), req.Addr, err) + return + } + defer fconn.Close() + + glog.V(LINFO).Infof("[socks5-bind] %s <-> %s", conn.RemoteAddr(), fconn.RemoteAddr()) + Transport(conn, fconn) + glog.V(LINFO).Infof("[socks5-bind] %s >-< %s", conn.RemoteAddr(), fconn.RemoteAddr()) + + case CmdUdpConnect: + glog.V(LINFO).Infof("[udp] %s - %s", conn.RemoteAddr(), req.Addr) + udpConnect(req, conn) + + case gosocks5.CmdUdp: + glog.V(LINFO).Infof("[socks5-udp] %s - %s", conn.RemoteAddr(), req.Addr) + socks5UDP(req, conn) + + case CmdUdpTun: + glog.V(LINFO).Infof("[socks5-udp] %s - %s", conn.RemoteAddr(), req.Addr) + if err := socks5TunnelUDP(req, conn); err != nil { + glog.V(LWARNING).Infof("[socks5-udp] %s - %s : %s", conn.RemoteAddr(), req.Addr, err) + rep := gosocks5.NewReply(gosocks5.Failure, nil) + if err := rep.Write(conn); err != nil { + glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) + } else { + glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) + } + return + } + + default: + glog.V(LWARNING).Infoln("[socks5] Unrecognized request:", req.Cmd) + } +} + +func udpConnect(req *gosocks5.Request, conn net.Conn) error { + if len(forwardArgs) > 0 { // direct forwarding + fconn, _, err := forwardChain(forwardArgs...) + if err != nil { + glog.V(LINFO).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) + return err + } + defer fconn.Close() + + if err := req.Write(fconn); err != nil { + glog.V(LINFO).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) + return err + } + + glog.V(LINFO).Infof("[udp] %s <-> %s", conn.RemoteAddr(), req.Addr) + err = Transport(conn, fconn) + glog.V(LINFO).Infof("[udp] %s >-< %s", conn.RemoteAddr(), req.Addr) + return err + } + + raddr, err := net.ResolveUDPAddr("udp", req.Addr.String()) + if err != nil { + glog.V(LINFO).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) + return err + } + + if err := gosocks5.NewReply(gosocks5.Succeeded, nil).Write(conn); err != nil { + glog.V(LINFO).Infof("[udp] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) + return err + } + + glog.V(LINFO).Infof("[udp] %s <-> %s", conn.RemoteAddr(), raddr) + defer glog.V(LINFO).Infof("[udp] %s >-< %s", conn.RemoteAddr(), raddr) + + for { + dgram, err := gosocks5.ReadUDPDatagram(conn) + if err != nil { + glog.V(LWARNING).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + return err + } + + go func() { + b := udpPool.Get().([]byte) + defer udpPool.Put(b) + + relay, err := net.DialUDP("udp", nil, raddr) + if err != nil { + glog.V(LWARNING).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), raddr, err) + return + } + defer relay.Close() + + if _, err := relay.Write(dgram.Data); err != nil { + glog.V(LWARNING).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), raddr, err) + return + } + glog.V(LDEBUG).Infof("[udp-tun] %s >>> %s length: %d", conn.RemoteAddr(), raddr, len(dgram.Data)) + + relay.SetReadDeadline(time.Now().Add(time.Second * 60)) + n, err := relay.Read(b) + if err != nil { + glog.V(LWARNING).Infof("[udp] %s <- %s : %s", conn.RemoteAddr(), raddr, err) + return + } + relay.SetReadDeadline(time.Time{}) + + glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s length: %d", conn.RemoteAddr(), raddr, n) + + conn.SetWriteDeadline(time.Now().Add(time.Second * 90)) + if err := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(n), 0, dgram.Header.Addr), b[:n]).Write(conn); err != nil { + glog.V(LWARNING).Infof("[udp] %s <- %s : %s", conn.RemoteAddr(), raddr, err) + return + } + conn.SetWriteDeadline(time.Time{}) + }() + } +} + +func socks5UDP(req *gosocks5.Request, conn net.Conn) error { + bindAddr, _ := net.ResolveUDPAddr("udp", req.Addr.String()) + relay, err := net.ListenUDP("udp", bindAddr) // udp associate, strict mode: if the port already in use, it will return error + if err != nil { + glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + + rep := gosocks5.NewReply(gosocks5.Failure, nil) + if err := rep.Write(conn); err != nil { + glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) + } else { + glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) + } + return err + } + defer relay.Close() + + addr := ToSocksAddr(relay.LocalAddr()) + addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) + rep := gosocks5.NewReply(gosocks5.Succeeded, addr) + if err := rep.Write(conn); err != nil { + return err + } + glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) + + glog.V(LINFO).Infof("[socks5-udp] %s - %s BIND ON %s OK", conn.RemoteAddr(), req.Addr, addr) + + if len(forwardArgs) > 0 { // client -> tunnel, tunnel udp over tcp + tun, _, err := forwardChain(forwardArgs...) + if err != nil { + glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + return err + } + defer tun.Close() + + tun.SetWriteDeadline(time.Now().Add(time.Second * 90)) + if err := gosocks5.NewRequest(CmdUdpTun, nil).Write(tun); err != nil { + glog.V(LWARNING).Infoln("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + return err + } + tun.SetWriteDeadline(time.Time{}) + + tun.SetReadDeadline(time.Now().Add(time.Second * 90)) + rep, err := gosocks5.ReadReply(tun) + if err != nil { + glog.V(LWARNING).Infoln("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + return err + } + if rep.Rep != gosocks5.Succeeded { + return errors.New("udp associate error") + } + tun.SetReadDeadline(time.Time{}) + + glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", conn.RemoteAddr(), req.Addr) + go tunnelUDP(relay, tun, true) + } else { // standard socks5 udp relay + peer, err := net.ListenUDP("udp", nil) + if err != nil { + glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + return err + } + defer peer.Close() + + glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", conn.RemoteAddr(), req.Addr) + go transportUDP(relay, peer) + } + + b := tcpPool.Get().([]byte) + defer tcpPool.Put(b) + for { + _, err := conn.Read(b) // discard any data from tcp connection + if err != nil { + break // client disconnected + } + } + glog.V(LINFO).Infof("[socks5-udp] %s >-< %s", conn.RemoteAddr(), req.Addr) + return nil +} + +func socks5TunnelUDP(req *gosocks5.Request, conn net.Conn) error { + if len(forwardArgs) > 0 { // tunnel -> tunnel, direct forwarding + tun, _, err := forwardChain(forwardArgs...) + if err != nil { + return err + } + defer tun.Close() + + if err := req.Write(tun); err != nil { + return err + } + + glog.V(LINFO).Infof("[socks5-udp] %s <-> %s[tun]", conn.RemoteAddr(), tun.RemoteAddr()) + Transport(conn, tun) + glog.V(LINFO).Infof("[socks5-udp] %s >-< %s[tun]", conn.RemoteAddr(), tun.RemoteAddr()) + } else { // tunnel -> remote, handle tunnel udp request + bindAddr, _ := net.ResolveUDPAddr("udp", req.Addr.String()) + uconn, err := net.ListenUDP("udp", bindAddr) + if err != nil { + return err + } + defer uconn.Close() + + addr := ToSocksAddr(uconn.LocalAddr()) + addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) + rep := gosocks5.NewReply(gosocks5.Succeeded, addr) + if err := rep.Write(conn); err != nil { + return nil + } + glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), uconn.LocalAddr(), rep) + + glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", conn.RemoteAddr(), uconn.LocalAddr()) + tunnelUDP(uconn, conn, false) + glog.V(LINFO).Infof("[socks5-udp] %s >-< %s", conn.RemoteAddr(), uconn.LocalAddr()) + } + return nil +} + +func socks5Bind(req *gosocks5.Request, conn net.Conn) (*gosocks5.Reply, net.Conn, error) { + if len(forwardArgs) > 0 { + fconn, _, err := forwardChain(forwardArgs...) + if err != nil { + return gosocks5.NewReply(gosocks5.Failure, nil), nil, err + } + + if err := req.Write(fconn); err != nil { + fconn.Close() + return gosocks5.NewReply(gosocks5.Failure, nil), nil, err + } + + return nil, fconn, nil + } + + bindAddr, _ := net.ResolveTCPAddr("tcp", req.Addr.String()) + ln, err := net.ListenTCP("tcp", bindAddr) // strict mode: if the port already in use, it will return error + if err != nil { + return gosocks5.NewReply(gosocks5.Failure, nil), nil, err + } + + addr := ToSocksAddr(ln.Addr()) + // Issue: may not reachable when host has multi-interface + addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) + rep := gosocks5.NewReply(gosocks5.Succeeded, addr) + if err := rep.Write(conn); err != nil { + glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) + ln.Close() + return nil, nil, err + } + glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) + glog.V(LINFO).Infof("[socks5-bind] %s - %s BIND ON %s OK", conn.RemoteAddr(), req.Addr, addr) + + lnChan := make(chan net.Conn, 1) + go func() { + defer close(lnChan) + c, err := ln.AcceptTCP() + if err != nil { + return + } + lnChan <- c + }() + + peerChan := make(chan error, 1) + go func() { + defer close(peerChan) + b := tcpPool.Get().([]byte) + defer tcpPool.Put(b) + _, err := conn.Read(b) + if err != nil { + if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { + return + } + peerChan <- err + } + }() + + var pconn net.Conn + + for { + select { + case c := <-lnChan: + ln.Close() // only accept one peer + if c == nil { + return gosocks5.NewReply(gosocks5.Failure, nil), nil, errors.New("[socks5-bind] accept error") + } + pconn = c + lnChan = nil + ln = nil + conn.SetReadDeadline(time.Now()) // timeout right now ,so we can break out of blocking + case err := <-peerChan: + if err != nil || pconn == nil { + if ln != nil { + ln.Close() + } + if pconn != nil { + pconn.Close() + } + if err == nil { + err = errors.New("Oops, some mysterious error!") + } + return nil, nil, err + } + goto out + } + } + +out: + conn.SetReadDeadline(time.Time{}) + + glog.V(LINFO).Infof("[socks5-bind] %s <- %s PEER %s ACCEPTED", conn.RemoteAddr(), addr, pconn.RemoteAddr()) + rep = gosocks5.NewReply(gosocks5.Succeeded, ToSocksAddr(pconn.RemoteAddr())) + return rep, pconn, nil +} + +func ToSocksAddr(addr net.Addr) *gosocks5.Addr { + host := "0.0.0.0" + port := 0 + if addr != nil { + h, p, _ := net.SplitHostPort(addr.String()) + host = h + port, _ = strconv.Atoi(p) + } + return &gosocks5.Addr{ + Type: gosocks5.AddrIPv4, + Host: host, + Port: uint16(port), + } +} diff --git a/ss.go b/gost/ss.go similarity index 100% rename from ss.go rename to gost/ss.go diff --git a/tls.go b/gost/tls.go similarity index 100% rename from tls.go rename to gost/tls.go diff --git a/udp.go b/gost/udp.go similarity index 100% rename from udp.go rename to gost/udp.go diff --git a/util.go b/gost/util.go similarity index 100% rename from util.go rename to gost/util.go diff --git a/gost/ws.go b/gost/ws.go new file mode 100644 index 0000000..e549723 --- /dev/null +++ b/gost/ws.go @@ -0,0 +1,141 @@ +package main + +import ( + //"github.com/ginuerzh/gosocks5" + "crypto/tls" + "github.com/golang/glog" + "github.com/gorilla/websocket" + "net" + "net/http" + "net/http/httputil" + "net/url" + "time" +) + +type wsConn struct { + conn *websocket.Conn + rb []byte +} + +func wsClient(scheme string, conn net.Conn, host string) (*wsConn, error) { + dialer := websocket.Dialer{ + ReadBufferSize: 4096, + WriteBufferSize: 4096, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + HandshakeTimeout: time.Second * 90, + NetDial: func(net, addr string) (net.Conn, error) { + return conn, nil + }, + } + u := url.URL{Scheme: scheme, Host: host, Path: "/ws"} + c, resp, err := dialer.Dial(u.String(), nil) + if err != nil { + return nil, err + } + resp.Body.Close() + + return &wsConn{conn: c}, nil +} + +func wsServer(conn *websocket.Conn) *wsConn { + return &wsConn{ + conn: conn, + } +} + +func (c *wsConn) Read(b []byte) (n int, err error) { + if len(c.rb) == 0 { + _, c.rb, err = c.conn.ReadMessage() + } + n = copy(b, c.rb) + c.rb = c.rb[n:] + + //log.Println("ws r:", n) + + return +} + +func (c *wsConn) Write(b []byte) (n int, err error) { + err = c.conn.WriteMessage(websocket.BinaryMessage, b) + n = len(b) + //log.Println("ws w:", n) + + return +} + +func (c *wsConn) Close() error { + return c.conn.Close() +} + +func (c *wsConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +func (c *wsConn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +func (conn *wsConn) SetDeadline(t time.Time) error { + if err := conn.SetReadDeadline(t); err != nil { + return err + } + return conn.SetWriteDeadline(t) +} +func (c *wsConn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +func (c *wsConn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} + +type ws struct { + upgrader websocket.Upgrader + arg Args +} + +func NewWs(arg Args) *ws { + return &ws{ + arg: arg, + upgrader: websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { return true }, + }, + } +} + +func (s *ws) handle(w http.ResponseWriter, r *http.Request) { + glog.V(LINFO).Infof("[ws] %s - %s", r.RemoteAddr, s.arg.Addr) + if glog.V(LDEBUG) { + dump, err := httputil.DumpRequest(r, false) + if err != nil { + glog.V(LWARNING).Infof("[ws] %s - %s : %s", r.RemoteAddr, s.arg.Addr, err) + } else { + glog.V(LDEBUG).Infof("[ws] %s - %s\n%s", r.RemoteAddr, s.arg.Addr, string(dump)) + } + } + conn, err := s.upgrader.Upgrade(w, r, nil) + if err != nil { + glog.V(LERROR).Infoln(err) + return + } + handleConn(wsServer(conn), s.arg) +} + +func (s *ws) ListenAndServe() error { + sm := http.NewServeMux() + sm.HandleFunc("/ws", s.handle) + return http.ListenAndServe(s.arg.Addr, sm) +} + +func (s *ws) listenAndServeTLS() error { + sm := http.NewServeMux() + sm.HandleFunc("/ws", s.handle) + server := &http.Server{ + Addr: s.arg.Addr, + TLSConfig: &tls.Config{Certificates: []tls.Certificate{s.arg.Cert}}, + Handler: sm, + } + return server.ListenAndServeTLS("", "") +} diff --git a/http.go b/http.go index b2684ea..09fef38 100644 --- a/http.go +++ b/http.go @@ -1,122 +1,20 @@ -package main +package gost import ( - "bufio" - "crypto/tls" - "encoding/base64" - "github.com/golang/glog" - "golang.org/x/net/http2" + //"bufio" + //"crypto/tls" + //"encoding/base64" + //"github.com/golang/glog" + //"golang.org/x/net/http2" "io" "net" - "net/http" - "net/http/httputil" - "strings" + //"net/http" + //"net/http/httputil" + //"strings" "time" ) -var ( - http2Client *http.Client -) - -func handleHttpRequest(req *http.Request, conn net.Conn, arg Args) { - glog.V(LINFO).Infof("[http] %s %s - %s %s", req.Method, conn.RemoteAddr(), req.Host, req.Proto) - - if glog.V(LDEBUG) { - dump, _ := httputil.DumpRequest(req, false) - glog.Infoln(string(dump)) - } - - var username, password string - if arg.User != nil { - username = arg.User.Username() - password, _ = arg.User.Password() - } - - u, p, _ := basicAuth(req.Header.Get("Proxy-Authorization")) - req.Header.Del("Proxy-Authorization") - if (username != "" && u != username) || (password != "" && p != password) { - resp := "HTTP/1.1 407 Proxy Authentication Required\r\n" + - "Proxy-Authenticate: Basic realm=\"gost\"\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n" - - if _, err := conn.Write([]byte(resp)); err != nil { - glog.V(LWARNING).Infof("[http] %s <- %s : %s", conn.RemoteAddr(), req.Host, err) - } - glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, resp) - - glog.V(LWARNING).Infof("[http] %s <- %s : proxy authentication required", conn.RemoteAddr(), req.Host) - return - } - - if len(forwardArgs) > 0 { - last := forwardArgs[len(forwardArgs)-1] - if last.Protocol == "http" || last.Protocol == "" { - forwardHttpRequest(req, conn, arg) - return - } - } - - c, err := connect(req.Host, "http", forwardArgs...) - if err != nil { - glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err) - - b := []byte("HTTP/1.1 503 Service unavailable\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n") - glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b)) - conn.Write(b) - return - } - defer c.Close() - - if req.Method == http.MethodConnect { - b := []byte("HTTP/1.1 200 Connection established\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n") - glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), req.Host, string(b)) - conn.Write(b) - } else { - req.Header.Del("Proxy-Connection") - req.Header.Set("Connection", "Keep-Alive") - - if err = req.Write(c); err != nil { - glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err) - return - } - } - - glog.V(LINFO).Infof("[http] %s <-> %s", conn.RemoteAddr(), req.Host) - Transport(conn, c) - glog.V(LINFO).Infof("[http] %s >-< %s", conn.RemoteAddr(), req.Host) -} - -func forwardHttpRequest(req *http.Request, conn net.Conn, arg Args) { - last := forwardArgs[len(forwardArgs)-1] - c, _, err := forwardChain(forwardArgs...) - if err != nil { - glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), last.Addr, err) - - b := []byte("HTTP/1.1 503 Service unavailable\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n") - glog.V(LDEBUG).Infof("[http] %s <- %s\n%s", conn.RemoteAddr(), last.Addr, string(b)) - conn.Write(b) - return - } - defer c.Close() - - if last.User != nil { - req.Header.Set("Proxy-Authorization", - "Basic "+base64.StdEncoding.EncodeToString([]byte(last.User.String()))) - } - - if err = req.Write(c); err != nil { - glog.V(LWARNING).Infof("[http] %s -> %s : %s", conn.RemoteAddr(), req.Host, err) - return - } - glog.V(LINFO).Infof("[http] %s <-> %s", conn.RemoteAddr(), req.Host) - Transport(conn, c) - glog.V(LINFO).Infof("[http] %s >-< %s", conn.RemoteAddr(), req.Host) - return -} - +// http2 client connection, wrapped up just like a net.Conn type Http2ClientConn struct { r io.Reader w io.Writer @@ -158,153 +56,3 @@ func (c *Http2ClientConn) SetReadDeadline(t time.Time) error { func (c *Http2ClientConn) SetWriteDeadline(t time.Time) error { return nil } - -// init http2 client with target http2 proxy server addr, and forward chain chain -func initHttp2Client(host string, chain ...Args) { - tr := http2.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { - // replace the default dialer with our forward chain. - conn, err := connect(host, "http2", chain...) - if err != nil { - return conn, err - } - return tls.Client(conn, cfg), nil - }, - } - http2Client = &http.Client{Transport: &tr} -} - -func handlerHttp2Request(w http.ResponseWriter, req *http.Request) { - target := req.Header.Get("gost-target") - if target == "" { - target = req.Host - } - - glog.V(LINFO).Infof("[http2] %s %s - %s %s", req.Method, req.RemoteAddr, target, req.Proto) - if glog.V(LDEBUG) { - dump, _ := httputil.DumpRequest(req, false) - glog.Infoln(string(dump)) - } - - c, err := connect(target, req.Header.Get("gost-protocol"), forwardArgs...) - if err != nil { - glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) - w.Header().Set("Proxy-Agent", "gost/"+Version) - w.WriteHeader(http.StatusServiceUnavailable) - if fw, ok := w.(http.Flusher); ok { - fw.Flush() - } - return - } - defer c.Close() - - glog.V(LINFO).Infof("[http2] %s <-> %s", req.RemoteAddr, target) - errc := make(chan error, 2) - - if req.Method == http.MethodConnect { - w.Header().Set("Proxy-Agent", "gost/"+Version) - w.WriteHeader(http.StatusOK) - if fw, ok := w.(http.Flusher); ok { - fw.Flush() - } - - // compatible with HTTP 1.x - if hj, ok := w.(http.Hijacker); ok && req.ProtoMajor == 1 { - // we take over the underly connection - conn, _, err := hj.Hijack() - if err != nil { - glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) - return - } - defer conn.Close() - - go Pipe(conn, c, errc) - go Pipe(c, conn, errc) - } else { - go Pipe(req.Body, c, errc) - go Pipe(c, flushWriter{w}, errc) - } - - select { - case <-errc: - // glog.V(LWARNING).Infoln("exit", err) - } - } else { - req.Header.Set("Connection", "Keep-Alive") - if err = req.Write(c); err != nil { - glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) - return - } - - resp, err := http.ReadResponse(bufio.NewReader(c), req) - if err != nil { - glog.V(LWARNING).Infoln(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 fw, ok := w.(http.Flusher); ok { - fw.Flush() - } - if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil { - glog.V(LWARNING).Infof("[http2] %s <- %s : %s", req.RemoteAddr, target, err) - } - } - - glog.V(LINFO).Infof("[http2] %s >-< %s", req.RemoteAddr, target) -} - -//func processSocks5OverHttp2() - -func handleHttp2Transport(w http.ResponseWriter, req *http.Request) { - glog.V(LINFO).Infof("[http2] %s - %s", req.RemoteAddr, req.Host) - if glog.V(LDEBUG) { - dump, _ := httputil.DumpRequest(req, false) - glog.Infoln(string(dump)) - } -} - -func basicAuth(authInfo string) (username, password string, ok bool) { - if authInfo == "" { - return - } - - if !strings.HasPrefix(authInfo, "Basic ") { - return - } - c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authInfo, "Basic ")) - if err != nil { - return - } - cs := string(c) - s := strings.IndexByte(cs, ':') - if s < 0 { - return - } - - return cs[:s], cs[s+1:], true -} - -type flushWriter struct { - w io.Writer -} - -func (fw flushWriter) Write(p []byte) (n int, err error) { - n, err = fw.w.Write(p) - if err != nil { - glog.V(LWARNING).Infoln("flush writer:", err) - } - if f, ok := fw.w.(http.Flusher); ok { - f.Flush() - } - return -} diff --git a/node.go b/node.go new file mode 100644 index 0000000..c807214 --- /dev/null +++ b/node.go @@ -0,0 +1,100 @@ +package gost + +import ( + "net" + "net/url" + "strconv" + "strings" +) + +// Proxy node represent a proxy +type ProxyNode struct { + Addr string // host:port + Protocol string // protocol: http/http2/socks5/ss + Transport string // transport: ws/wss/tls/tcp/udp/rtcp/rudp + Remote string // remote address, used by tcp/udp port forwarding + User *url.Userinfo // authentication for proxy + values url.Values + serverName string + conn net.Conn +} + +// the format is [scheme://][user:pass@host]:port +func ParseProxyNode(s string) (node *ProxyNode, err error) { + if !strings.Contains(s, "://") { + s = "gost://" + s + } + u, err := url.Parse(s) + if err != nil { + return + } + + node = &ProxyNode{ + Addr: u.Host, + User: u.User, + values: u.Query(), + serverName: u.Host, + } + + if strings.Contains(u.Host, ":") { + node.serverName, _, _ = net.SplitHostPort(u.Host) + } + + schemes := strings.Split(u.Scheme, "+") + if len(schemes) == 1 { + node.Protocol = schemes[0] + node.Transport = schemes[0] + } + if len(schemes) == 2 { + node.Protocol = schemes[0] + node.Transport = schemes[1] + } + + switch node.Transport { + case "ws", "wss", "tls": + case "https": + node.Protocol = "http" + node.Transport = "tls" + case "http2": + node.Protocol = "http2" + case "tcp", "udp": // started from v2.1, tcp and udp are for local port forwarding + node.Remote = strings.Trim(u.EscapedPath(), "/") + case "rtcp", "rudp": // started from v2.1, rtcp and rudp are for remote port forwarding + node.Remote = strings.Trim(u.EscapedPath(), "/") + default: + node.Transport = "" + } + + switch node.Protocol { + case "http", "http2", "socks", "socks5", "ss": + default: + node.Protocol = "" + } + + return +} + +func (node *ProxyNode) insecureSkipVerify() bool { + s := node.values.Get("secure") + if secure, _ := strconv.ParseBool(s); secure { + return !secure + } + if n, _ := strconv.Atoi(s); n > 0 { + return false + } + return true +} + +func (node *ProxyNode) certFile() string { + if cert := node.values.Get("cert"); cert != "" { + return cert + } + return DefaultCertFile +} + +func (node *ProxyNode) keyFile() string { + if key := node.values.Get("key"); key != "" { + return key + } + return DefaultKeyFile +} diff --git a/socks.go b/socks.go index d304930..e89620f 100644 --- a/socks.go +++ b/socks.go @@ -1,9 +1,9 @@ -package main +package gost import ( //"bytes" "crypto/tls" - "errors" + //"errors" "github.com/ginuerzh/gosocks5" "github.com/golang/glog" //"os/exec" @@ -11,8 +11,8 @@ import ( //"io/ioutil" "net" "net/url" - "strconv" - "time" + //"strconv" + //"time" ) const ( @@ -162,392 +162,3 @@ func (selector *serverSelector) OnSelected(method uint8, conn net.Conn) (net.Con return conn, nil } - -func handleSocks5Request(req *gosocks5.Request, conn net.Conn) { - glog.V(LDEBUG).Infof("[socks5] %s -> %s\n%s", conn.RemoteAddr(), req.Addr, req) - - switch req.Cmd { - case gosocks5.CmdConnect: - glog.V(LINFO).Infof("[socks5-connect] %s - %s", conn.RemoteAddr(), req.Addr) - - tconn, err := connect(req.Addr.String(), "socks5", forwardArgs...) - if err != nil { - glog.V(LWARNING).Infof("[socks5-connect] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) - rep := gosocks5.NewReply(gosocks5.HostUnreachable, nil) - if err := rep.Write(conn); err != nil { - glog.V(LWARNING).Infof("[socks5-connect] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) - } else { - glog.V(LDEBUG).Infof("[socks5-connect] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) - } - return - } - defer tconn.Close() - - rep := gosocks5.NewReply(gosocks5.Succeeded, nil) - if err := rep.Write(conn); err != nil { - glog.V(LWARNING).Infof("[socks5-connect] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) - return - } - glog.V(LDEBUG).Infof("[socks5-connect] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) - - glog.V(LINFO).Infof("[socks5-connect] %s <-> %s", conn.RemoteAddr(), req.Addr) - Transport(conn, tconn) - glog.V(LINFO).Infof("[socks5-connect] %s >-< %s", conn.RemoteAddr(), req.Addr) - - case gosocks5.CmdBind: - glog.V(LINFO).Infof("[socks5-bind] %s - %s", conn.RemoteAddr(), req.Addr) - - reply, fconn, err := socks5Bind(req, conn) - if reply != nil { - if err := reply.Write(conn); err != nil { - glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) - if fconn != nil { - fconn.Close() - } - return - } - glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, reply) - } - - if err != nil { - glog.V(LWARNING).Infof("[socks5-bind] %s - %s : %s", conn.RemoteAddr(), req.Addr, err) - return - } - defer fconn.Close() - - glog.V(LINFO).Infof("[socks5-bind] %s <-> %s", conn.RemoteAddr(), fconn.RemoteAddr()) - Transport(conn, fconn) - glog.V(LINFO).Infof("[socks5-bind] %s >-< %s", conn.RemoteAddr(), fconn.RemoteAddr()) - - case CmdUdpConnect: - glog.V(LINFO).Infof("[udp] %s - %s", conn.RemoteAddr(), req.Addr) - udpConnect(req, conn) - - case gosocks5.CmdUdp: - glog.V(LINFO).Infof("[socks5-udp] %s - %s", conn.RemoteAddr(), req.Addr) - socks5UDP(req, conn) - - case CmdUdpTun: - glog.V(LINFO).Infof("[socks5-udp] %s - %s", conn.RemoteAddr(), req.Addr) - if err := socks5TunnelUDP(req, conn); err != nil { - glog.V(LWARNING).Infof("[socks5-udp] %s - %s : %s", conn.RemoteAddr(), req.Addr, err) - rep := gosocks5.NewReply(gosocks5.Failure, nil) - if err := rep.Write(conn); err != nil { - glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) - } else { - glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) - } - return - } - - default: - glog.V(LWARNING).Infoln("[socks5] Unrecognized request:", req.Cmd) - } -} - -func udpConnect(req *gosocks5.Request, conn net.Conn) error { - if len(forwardArgs) > 0 { // direct forwarding - fconn, _, err := forwardChain(forwardArgs...) - if err != nil { - glog.V(LINFO).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) - gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) - return err - } - defer fconn.Close() - - if err := req.Write(fconn); err != nil { - glog.V(LINFO).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) - gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) - return err - } - - glog.V(LINFO).Infof("[udp] %s <-> %s", conn.RemoteAddr(), req.Addr) - err = Transport(conn, fconn) - glog.V(LINFO).Infof("[udp] %s >-< %s", conn.RemoteAddr(), req.Addr) - return err - } - - raddr, err := net.ResolveUDPAddr("udp", req.Addr.String()) - if err != nil { - glog.V(LINFO).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) - gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) - return err - } - - if err := gosocks5.NewReply(gosocks5.Succeeded, nil).Write(conn); err != nil { - glog.V(LINFO).Infof("[udp] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) - return err - } - - glog.V(LINFO).Infof("[udp] %s <-> %s", conn.RemoteAddr(), raddr) - defer glog.V(LINFO).Infof("[udp] %s >-< %s", conn.RemoteAddr(), raddr) - - for { - dgram, err := gosocks5.ReadUDPDatagram(conn) - if err != nil { - glog.V(LWARNING).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) - return err - } - - go func() { - b := udpPool.Get().([]byte) - defer udpPool.Put(b) - - relay, err := net.DialUDP("udp", nil, raddr) - if err != nil { - glog.V(LWARNING).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), raddr, err) - return - } - defer relay.Close() - - if _, err := relay.Write(dgram.Data); err != nil { - glog.V(LWARNING).Infof("[udp] %s -> %s : %s", conn.RemoteAddr(), raddr, err) - return - } - glog.V(LDEBUG).Infof("[udp-tun] %s >>> %s length: %d", conn.RemoteAddr(), raddr, len(dgram.Data)) - - relay.SetReadDeadline(time.Now().Add(time.Second * 60)) - n, err := relay.Read(b) - if err != nil { - glog.V(LWARNING).Infof("[udp] %s <- %s : %s", conn.RemoteAddr(), raddr, err) - return - } - relay.SetReadDeadline(time.Time{}) - - glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s length: %d", conn.RemoteAddr(), raddr, n) - - conn.SetWriteDeadline(time.Now().Add(time.Second * 90)) - if err := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(n), 0, dgram.Header.Addr), b[:n]).Write(conn); err != nil { - glog.V(LWARNING).Infof("[udp] %s <- %s : %s", conn.RemoteAddr(), raddr, err) - return - } - conn.SetWriteDeadline(time.Time{}) - }() - } -} - -func socks5UDP(req *gosocks5.Request, conn net.Conn) error { - bindAddr, _ := net.ResolveUDPAddr("udp", req.Addr.String()) - relay, err := net.ListenUDP("udp", bindAddr) // udp associate, strict mode: if the port already in use, it will return error - if err != nil { - glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) - - rep := gosocks5.NewReply(gosocks5.Failure, nil) - if err := rep.Write(conn); err != nil { - glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) - } else { - glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) - } - return err - } - defer relay.Close() - - addr := ToSocksAddr(relay.LocalAddr()) - addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) - rep := gosocks5.NewReply(gosocks5.Succeeded, addr) - if err := rep.Write(conn); err != nil { - return err - } - glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) - - glog.V(LINFO).Infof("[socks5-udp] %s - %s BIND ON %s OK", conn.RemoteAddr(), req.Addr, addr) - - if len(forwardArgs) > 0 { // client -> tunnel, tunnel udp over tcp - tun, _, err := forwardChain(forwardArgs...) - if err != nil { - glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) - return err - } - defer tun.Close() - - tun.SetWriteDeadline(time.Now().Add(time.Second * 90)) - if err := gosocks5.NewRequest(CmdUdpTun, nil).Write(tun); err != nil { - glog.V(LWARNING).Infoln("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) - return err - } - tun.SetWriteDeadline(time.Time{}) - - tun.SetReadDeadline(time.Now().Add(time.Second * 90)) - rep, err := gosocks5.ReadReply(tun) - if err != nil { - glog.V(LWARNING).Infoln("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) - return err - } - if rep.Rep != gosocks5.Succeeded { - return errors.New("udp associate error") - } - tun.SetReadDeadline(time.Time{}) - - glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", conn.RemoteAddr(), req.Addr) - go tunnelUDP(relay, tun, true) - } else { // standard socks5 udp relay - peer, err := net.ListenUDP("udp", nil) - if err != nil { - glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) - return err - } - defer peer.Close() - - glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", conn.RemoteAddr(), req.Addr) - go transportUDP(relay, peer) - } - - b := tcpPool.Get().([]byte) - defer tcpPool.Put(b) - for { - _, err := conn.Read(b) // discard any data from tcp connection - if err != nil { - break // client disconnected - } - } - glog.V(LINFO).Infof("[socks5-udp] %s >-< %s", conn.RemoteAddr(), req.Addr) - return nil -} - -func socks5TunnelUDP(req *gosocks5.Request, conn net.Conn) error { - if len(forwardArgs) > 0 { // tunnel -> tunnel, direct forwarding - tun, _, err := forwardChain(forwardArgs...) - if err != nil { - return err - } - defer tun.Close() - - if err := req.Write(tun); err != nil { - return err - } - - glog.V(LINFO).Infof("[socks5-udp] %s <-> %s[tun]", conn.RemoteAddr(), tun.RemoteAddr()) - Transport(conn, tun) - glog.V(LINFO).Infof("[socks5-udp] %s >-< %s[tun]", conn.RemoteAddr(), tun.RemoteAddr()) - } else { // tunnel -> remote, handle tunnel udp request - bindAddr, _ := net.ResolveUDPAddr("udp", req.Addr.String()) - uconn, err := net.ListenUDP("udp", bindAddr) - if err != nil { - return err - } - defer uconn.Close() - - addr := ToSocksAddr(uconn.LocalAddr()) - addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) - rep := gosocks5.NewReply(gosocks5.Succeeded, addr) - if err := rep.Write(conn); err != nil { - return nil - } - glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), uconn.LocalAddr(), rep) - - glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", conn.RemoteAddr(), uconn.LocalAddr()) - tunnelUDP(uconn, conn, false) - glog.V(LINFO).Infof("[socks5-udp] %s >-< %s", conn.RemoteAddr(), uconn.LocalAddr()) - } - return nil -} - -func socks5Bind(req *gosocks5.Request, conn net.Conn) (*gosocks5.Reply, net.Conn, error) { - if len(forwardArgs) > 0 { - fconn, _, err := forwardChain(forwardArgs...) - if err != nil { - return gosocks5.NewReply(gosocks5.Failure, nil), nil, err - } - - if err := req.Write(fconn); err != nil { - fconn.Close() - return gosocks5.NewReply(gosocks5.Failure, nil), nil, err - } - - return nil, fconn, nil - } - - bindAddr, _ := net.ResolveTCPAddr("tcp", req.Addr.String()) - ln, err := net.ListenTCP("tcp", bindAddr) // strict mode: if the port already in use, it will return error - if err != nil { - return gosocks5.NewReply(gosocks5.Failure, nil), nil, err - } - - addr := ToSocksAddr(ln.Addr()) - // Issue: may not reachable when host has multi-interface - addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) - rep := gosocks5.NewReply(gosocks5.Succeeded, addr) - if err := rep.Write(conn); err != nil { - glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) - ln.Close() - return nil, nil, err - } - glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) - glog.V(LINFO).Infof("[socks5-bind] %s - %s BIND ON %s OK", conn.RemoteAddr(), req.Addr, addr) - - lnChan := make(chan net.Conn, 1) - go func() { - defer close(lnChan) - c, err := ln.AcceptTCP() - if err != nil { - return - } - lnChan <- c - }() - - peerChan := make(chan error, 1) - go func() { - defer close(peerChan) - b := tcpPool.Get().([]byte) - defer tcpPool.Put(b) - _, err := conn.Read(b) - if err != nil { - if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { - return - } - peerChan <- err - } - }() - - var pconn net.Conn - - for { - select { - case c := <-lnChan: - ln.Close() // only accept one peer - if c == nil { - return gosocks5.NewReply(gosocks5.Failure, nil), nil, errors.New("[socks5-bind] accept error") - } - pconn = c - lnChan = nil - ln = nil - conn.SetReadDeadline(time.Now()) // timeout right now ,so we can break out of blocking - case err := <-peerChan: - if err != nil || pconn == nil { - if ln != nil { - ln.Close() - } - if pconn != nil { - pconn.Close() - } - if err == nil { - err = errors.New("Oops, some mysterious error!") - } - return nil, nil, err - } - goto out - } - } - -out: - conn.SetReadDeadline(time.Time{}) - - glog.V(LINFO).Infof("[socks5-bind] %s <- %s PEER %s ACCEPTED", conn.RemoteAddr(), addr, pconn.RemoteAddr()) - rep = gosocks5.NewReply(gosocks5.Succeeded, ToSocksAddr(pconn.RemoteAddr())) - return rep, pconn, nil -} - -func ToSocksAddr(addr net.Addr) *gosocks5.Addr { - host := "0.0.0.0" - port := 0 - if addr != nil { - h, p, _ := net.SplitHostPort(addr.String()) - host = h - port, _ = strconv.Atoi(p) - } - return &gosocks5.Addr{ - Type: gosocks5.AddrIPv4, - Host: host, - Port: uint16(port), - } -} diff --git a/tools/chain_request.go b/tools/chain_request.go new file mode 100644 index 0000000..b28f85d --- /dev/null +++ b/tools/chain_request.go @@ -0,0 +1,84 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "github.com/ginuerzh/gost" + "github.com/golang/glog" + "golang.org/x/net/http2" + "log" + "net/http" + "net/http/httputil" + "net/url" +) + +var ( + proxyNodes stringlist + urls []string +) + +func init() { + flag.Var(&proxyNodes, "F", "forward address, can make a forward chain") + flag.Parse() + if flag.NArg() == 0 { + log.Fatal("please specific at least one request URL") + } + urls = flag.Args() + if glog.V(gost.LVDEBUG) { + http2.VerboseLogs = true + } +} + +type stringlist []string + +func (list *stringlist) String() string { + return fmt.Sprintf("%s", *list) +} +func (list *stringlist) Set(value string) error { + *list = append(*list, value) + return nil +} + +func main() { + chain := gost.NewProxyChain() + for _, s := range proxyNodes { + node, err := gost.ParseProxyNode(s) + if err != nil { + log.Fatal(err) + } + chain.AddProxyNode(*node) + } + chain.Init() + + for _, u := range urls { + url, err := url.Parse(u) + if err != nil { + log.Println("Invalid url:", u) + continue + } + + log.Println("GET", u) + conn, err := chain.Dial(url.Host) + if err != nil { + log.Fatal(err) + } + req, err := http.NewRequest("GET", u, nil) + if err != nil { + log.Fatal(err) + } + + if err := req.Write(conn); err != nil { + log.Fatal(err) + } + resp, err := http.ReadResponse(bufio.NewReader(conn), req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + header, _ := httputil.DumpResponse(resp, false) + log.Println(string(header)) + } + +} diff --git a/utils/http2_client.go b/utils/http2_client.go deleted file mode 100644 index cadf486..0000000 --- a/utils/http2_client.go +++ /dev/null @@ -1,63 +0,0 @@ -// +build http2client - -package main - -import ( - "crypto/tls" - "golang.org/x/net/http2" - "io" - "io/ioutil" - "log" - "net" - "net/http" - //"net/http/httputil" - "os" - "time" -) - -func init() { - log.SetFlags(log.LstdFlags | log.Lshortfile) -} - -func main() { - tr := http2.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { - return tls.DialWithDialer(&net.Dialer{Timeout: 30 * time.Second}, "tcp", "localhost:8080", cfg) - }, - } - client := http.Client{Transport: &tr} - - pr, pw := io.Pipe() - - req, err := http.NewRequest("CONNECT", "https://www.baidu.com", ioutil.NopCloser(pr)) - req.ContentLength = -1 - if err != nil { - log.Fatal(err) - } - /* - req := &http.Request{ - Method: "CONNECT", - URL: &url.URL{Scheme: "https"}, - Host: "www.baidu.com:443", - Header: make(http.Header), - } - */ - - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - - r, err := http.NewRequest("GET", "https://www.baidu.com", nil) - if err != nil { - log.Fatal(err) - } - r.Write(pw) - - n, err := io.Copy(os.Stdout, resp.Body) - log.Fatalf("copied %d, %v", n, err) -} diff --git a/ws.go b/ws.go index e549723..0665378 100644 --- a/ws.go +++ b/ws.go @@ -1,13 +1,13 @@ -package main +package gost import ( //"github.com/ginuerzh/gosocks5" "crypto/tls" - "github.com/golang/glog" + //"github.com/golang/glog" "github.com/gorilla/websocket" "net" - "net/http" - "net/http/httputil" + //"net/http" + //"net/http/httputil" "net/url" "time" ) @@ -19,8 +19,8 @@ type wsConn struct { func wsClient(scheme string, conn net.Conn, host string) (*wsConn, error) { dialer := websocket.Dialer{ - ReadBufferSize: 4096, - WriteBufferSize: 4096, + ReadBufferSize: 1024, + WriteBufferSize: 1024, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, HandshakeTimeout: time.Second * 90, NetDial: func(net, addr string) (net.Conn, error) { @@ -88,54 +88,3 @@ func (c *wsConn) SetReadDeadline(t time.Time) error { func (c *wsConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } - -type ws struct { - upgrader websocket.Upgrader - arg Args -} - -func NewWs(arg Args) *ws { - return &ws{ - arg: arg, - upgrader: websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { return true }, - }, - } -} - -func (s *ws) handle(w http.ResponseWriter, r *http.Request) { - glog.V(LINFO).Infof("[ws] %s - %s", r.RemoteAddr, s.arg.Addr) - if glog.V(LDEBUG) { - dump, err := httputil.DumpRequest(r, false) - if err != nil { - glog.V(LWARNING).Infof("[ws] %s - %s : %s", r.RemoteAddr, s.arg.Addr, err) - } else { - glog.V(LDEBUG).Infof("[ws] %s - %s\n%s", r.RemoteAddr, s.arg.Addr, string(dump)) - } - } - conn, err := s.upgrader.Upgrade(w, r, nil) - if err != nil { - glog.V(LERROR).Infoln(err) - return - } - handleConn(wsServer(conn), s.arg) -} - -func (s *ws) ListenAndServe() error { - sm := http.NewServeMux() - sm.HandleFunc("/ws", s.handle) - return http.ListenAndServe(s.arg.Addr, sm) -} - -func (s *ws) listenAndServeTLS() error { - sm := http.NewServeMux() - sm.HandleFunc("/ws", s.handle) - server := &http.Server{ - Addr: s.arg.Addr, - TLSConfig: &tls.Config{Certificates: []tls.Certificate{s.arg.Cert}}, - Handler: sm, - } - return server.ListenAndServeTLS("", "") -}