From f903771282ecd71b0cfee979415fa7b8c1a7d59a Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Tue, 4 Oct 2016 16:41:44 +0800 Subject: [PATCH] update gost library --- chain.go | 93 ++- cmd/gost/http.go | 2 - .../{chain_request.go => gost_client.go} | 12 +- cmd/tools/gost_server.go | 53 ++ conn.go | 46 +- gost.go | 62 +- http.go | 314 +++++++++- node.go | 34 +- server.go | 203 ++++++ socks.go | 580 +++++++++++++++++- ss.go | 134 ++++ ws.go | 99 ++- 12 files changed, 1498 insertions(+), 134 deletions(-) rename cmd/tools/{chain_request.go => gost_client.go} (87%) create mode 100644 cmd/tools/gost_server.go create mode 100644 server.go create mode 100644 ss.go diff --git a/chain.go b/chain.go index 3fdbea5..2b83c56 100644 --- a/chain.go +++ b/chain.go @@ -32,11 +32,26 @@ 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. +func (c *ProxyChain) AddProxyNodeString(snode ...string) error { + for _, sn := range snode { + node, err := ParseProxyNode(sn) + if err != nil { + return err + } + c.AddProxyNode(node) + } + return nil +} + +func (c *ProxyChain) Nodes() []ProxyNode { + return c.nodes +} + +// TryEnableHttp2 initialize HTTP2 if available. +// HTTP2 will be enabled when at least one HTTP2 proxy node (scheme == http2) is present. // -// NOTE: http2 will not be enabled if not called. -func (c *ProxyChain) Init() { +// NOTE: Should be called immediately when proxy nodes are ready, HTTP2 will not be enabled if this function not be called. +func (c *ProxyChain) TryEnableHttp2() { length := len(c.nodes) if length == 0 { return @@ -44,7 +59,7 @@ func (c *ProxyChain) Init() { c.lastNode = &c.nodes[length-1] - // http2 restrict: http2 will be enabled when at least one http2 proxy node present + // HTTP2 restrict: HTTP2 will be enabled when at least one HTTP2 proxy node is present. for i, node := range c.nodes { if node.Transport == "http2" { glog.V(LINFO).Infoln("http2 enabled") @@ -54,11 +69,15 @@ func (c *ProxyChain) Init() { } c.initHttp2Client(node.Addr, cfg, c.nodes[:i]...) c.http2NodeIndex = i - break // shortest chain for http2 + break // shortest chain for HTTP2 } } } +func (c *ProxyChain) Http2Enabled() bool { + return c.http2Enabled +} + func (c *ProxyChain) initHttp2Client(addr string, config *tls.Config, nodes ...ProxyNode) { tr := http2.Transport{ TLSClientConfig: config, @@ -76,10 +95,6 @@ func (c *ProxyChain) initHttp2Client(addr string, config *tls.Config, nodes ...P } -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, ":") { @@ -88,20 +103,35 @@ func (c *ProxyChain) Dial(addr string) (net.Conn, error) { return c.dialWithNodes(addr, c.nodes...) } +// GetConn initializes a proxy chain connection, +// if no proxy nodes on this chain, it will return error +func (c *ProxyChain) GetConn() (net.Conn, error) { + nodes := c.nodes + if len(nodes) == 0 { + return nil, ErrEmptyChain + } + + if c.Http2Enabled() { + nodes = nodes[c.http2NodeIndex+1:] + if len(nodes) == 0 { + return c.getHttp2Conn() + } + } + return c.travelNodes(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) + return c.http2Connect(addr) } } - pc, err = c.travelNodes(nodes...) + pc, err := c.travelNodes(nodes...) if err != nil { return } @@ -109,8 +139,8 @@ func (c *ProxyChain) dialWithNodes(addr string, nodes ...ProxyNode) (conn net.Co pc.Close() return } - - return pc, nil + conn = pc + return } func (c *ProxyChain) travelNodes(nodes ...ProxyNode) (conn *ProxyConn, err error) { @@ -125,7 +155,7 @@ func (c *ProxyChain) travelNodes(nodes ...ProxyNode) (conn *ProxyConn, err error node := nodes[0] if c.Http2Enabled() { - cc, err = c.http2Connect("http", node.Addr) + cc, err = c.http2Connect(node.Addr) } else { cc, err = net.DialTimeout("tcp", node.Addr, DialTimeout) } @@ -152,13 +182,14 @@ func (c *ProxyChain) travelNodes(nodes ...ProxyNode) (conn *ProxyConn, err error return } -func (c *ProxyChain) http2Connect(protocol, addr string) (net.Conn, error) { +// Initialize an HTTP2 transport if HTTP2 is enabled. +func (c *ProxyChain) getHttp2Conn() (net.Conn, error) { if !c.Http2Enabled() { - return nil, errors.New("http2 not enabled") + 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}, @@ -170,11 +201,7 @@ func (c *ProxyChain) http2Connect(protocol, addr string) (net.Conn, error) { Host: http2Node.Addr, ContentLength: -1, } - req.Header.Set("gost-target", addr) - if protocol != "" { - req.Header.Set("gost-protocol", protocol) - } - + req.Header.Set("Proxy-Switch", "gost") // Flag header to indicate server to switch to HTTP2 transport mode if glog.V(LDEBUG) { dump, _ := httputil.DumpRequest(&req, false) glog.Infoln(string(dump)) @@ -187,7 +214,19 @@ func (c *ProxyChain) http2Connect(protocol, addr string) (net.Conn, error) { resp.Body.Close() return nil, errors.New(resp.Status) } - conn := &Http2ClientConn{r: resp.Body, w: pw} - conn.remoteAddr, _ = net.ResolveTCPAddr("tcp", addr) + return &http2Conn{r: resp.Body, w: pw}, nil +} + +// Use HTTP2 as transport to connect target addr +func (c *ProxyChain) http2Connect(addr string) (net.Conn, error) { + conn, err := c.getHttp2Conn() + if err != nil { + return nil, err + } + pc := NewProxyConn(conn, c.nodes[c.http2NodeIndex]) + if err = pc.Connect(addr); err != nil { + pc.Close() + return nil, err + } return conn, nil } diff --git a/cmd/gost/http.go b/cmd/gost/http.go index b2684ea..fb4dbb8 100644 --- a/cmd/gost/http.go +++ b/cmd/gost/http.go @@ -263,8 +263,6 @@ func handlerHttp2Request(w http.ResponseWriter, req *http.Request) { 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) { diff --git a/cmd/tools/chain_request.go b/cmd/tools/gost_client.go similarity index 87% rename from cmd/tools/chain_request.go rename to cmd/tools/gost_client.go index b28f85d..64030b3 100644 --- a/cmd/tools/chain_request.go +++ b/cmd/tools/gost_client.go @@ -25,7 +25,7 @@ func init() { log.Fatal("please specific at least one request URL") } urls = flag.Args() - if glog.V(gost.LVDEBUG) { + if glog.V(5) { http2.VerboseLogs = true } } @@ -42,14 +42,10 @@ func (list *stringlist) Set(value string) error { func main() { chain := gost.NewProxyChain() - for _, s := range proxyNodes { - node, err := gost.ParseProxyNode(s) - if err != nil { - log.Fatal(err) - } - chain.AddProxyNode(*node) + if err := chain.AddProxyNodeString(proxyNodes...); err != nil { + log.Fatal(err) } - chain.Init() + chain.TryEnableHttp2() for _, u := range urls { url, err := url.Parse(u) diff --git a/cmd/tools/gost_server.go b/cmd/tools/gost_server.go new file mode 100644 index 0000000..a830c3b --- /dev/null +++ b/cmd/tools/gost_server.go @@ -0,0 +1,53 @@ +package main + +import ( + "crypto/tls" + "flag" + "fmt" + "github.com/ginuerzh/gost" + "log" + "sync" +) + +var ( + proxyNodes stringlist +) + +func init() { + flag.Var(&proxyNodes, "L", "proxy server node") + flag.Parse() +} + +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() + var wg sync.WaitGroup + for _, ns := range proxyNodes { + serverNode, err := gost.ParseProxyNode(ns) + if err != nil { + log.Println(err) + continue + } + wg.Add(1) + go func(node gost.ProxyNode) { + defer wg.Done() + cert, err := gost.LoadCertificate(node.Get("cert"), node.Get("key")) + if err != nil { + log.Println(err) + return + } + server := gost.NewProxyServer(node, chain, &tls.Config{Certificates: []tls.Certificate{cert}}) + log.Fatal(server.Serve()) + }(serverNode) + } + wg.Wait() +} diff --git a/conn.go b/conn.go index 8923cde..76eacde 100644 --- a/conn.go +++ b/conn.go @@ -20,7 +20,7 @@ import ( type ProxyConn struct { conn net.Conn - node ProxyNode + Node ProxyNode handshaked bool handshakeMutex sync.Mutex handshakeErr error @@ -29,13 +29,13 @@ type ProxyConn struct { func NewProxyConn(conn net.Conn, node ProxyNode) *ProxyConn { return &ProxyConn{ conn: conn, - node: node, + Node: node, } } -// Handshake based on the proxy node info: transport, protocol, authentication, etc. +// Handshake handshake with this proxy node based on the proxy node info: transport, protocol, authentication, etc. // -// NOTE: http2 will be downgrade to http (for protocol) and tls (for transport). +// NOTE: any HTTP2 scheme will be treated as http (for protocol) or tls (for transport). func (c *ProxyConn) Handshake() error { c.handshakeMutex.Lock() defer c.handshakeMutex.Unlock() @@ -53,16 +53,22 @@ func (c *ProxyConn) Handshake() error { func (c *ProxyConn) handshake() error { var tlsUsed bool - switch c.node.Transport { + switch c.Node.Transport { case "ws": // websocket connection - conn, err := wsClient("ws", c.conn, c.node.Addr) + u := url.URL{Scheme: "ws", Host: c.Node.Addr, Path: "/ws"} + conn, err := WebsocketClientConn(u.String(), c.conn, nil) if err != nil { return err } c.conn = conn case "wss": // websocket security tlsUsed = true - conn, err := wsClient("wss", c.conn, c.node.Addr) + u := url.URL{Scheme: "wss", Host: c.Node.Addr, Path: "/ws"} + config := &tls.Config{ + InsecureSkipVerify: c.Node.insecureSkipVerify(), + ServerName: c.Node.serverName, + } + conn, err := WebsocketClientConn(u.String(), c.conn, config) if err != nil { return err } @@ -70,14 +76,14 @@ func (c *ProxyConn) handshake() error { case "tls", "http2": // tls connection tlsUsed = true cfg := &tls.Config{ - InsecureSkipVerify: c.node.insecureSkipVerify(), - ServerName: c.node.serverName, + InsecureSkipVerify: c.Node.insecureSkipVerify(), + ServerName: c.Node.serverName, } c.conn = tls.Client(c.conn, cfg) default: } - switch c.node.Protocol { + switch c.Node.Protocol { case "socks", "socks5": // socks5 handshake with auth and tls supported selector := &clientSelector{ methods: []uint8{ @@ -85,11 +91,15 @@ func (c *ProxyConn) handshake() error { gosocks5.MethodUserPass, //MethodTLS, }, - user: c.node.User, + user: c.Node.User, } if !tlsUsed { // if transport is not security, enable security socks5 selector.methods = append(selector.methods, MethodTLS) + selector.tlsConfig = &tls.Config{ + InsecureSkipVerify: c.Node.insecureSkipVerify(), + ServerName: c.Node.serverName, + } } conn := gosocks5.ClientConn(c.conn, selector) @@ -98,9 +108,9 @@ func (c *ProxyConn) handshake() error { } c.conn = conn case "ss": // shadowsocks - if c.node.User != nil { - method := c.node.User.Username() - password, _ := c.node.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 err @@ -117,9 +127,9 @@ func (c *ProxyConn) handshake() error { return nil } -// Connect to addr through this proxy node +// Connect connect to addr through this proxy node func (c *ProxyConn) Connect(addr string) error { - switch c.node.Protocol { + switch c.Node.Protocol { case "ss": // shadowsocks host, port, err := net.SplitHostPort(addr) if err != nil { @@ -177,9 +187,9 @@ func (c *ProxyConn) Connect(addr string) error { Header: make(http.Header), } req.Header.Set("Proxy-Connection", "keep-alive") - if c.node.User != nil { + if c.Node.User != nil { req.Header.Set("Proxy-Authorization", - "Basic "+base64.StdEncoding.EncodeToString([]byte(c.node.User.String()))) + "Basic "+base64.StdEncoding.EncodeToString([]byte(c.Node.User.String()))) } if err := req.Write(c); err != nil { return err diff --git a/gost.go b/gost.go index c2732d1..664176f 100644 --- a/gost.go +++ b/gost.go @@ -2,24 +2,38 @@ package gost import ( "crypto/tls" + "encoding/base64" "errors" "github.com/golang/glog" "net" + "strings" "time" ) +const ( + Version = "2.2-dev" +) + +// Log level for glog const ( LFATAL = iota LERROR LWARNING LINFO LDEBUG - LVDEBUG // verbose debug for http2 ) var ( - DialTimeout = 30 * time.Second KeepAliveTime = 180 * time.Second + DialTimeout = 30 * time.Second + ReadTimeout = 90 * time.Second + WriteTimeout = 90 * time.Second +) + +var ( + SmallBufferSize = 1 * 1024 // 1KB small buffer + MediumBufferSize = 8 * 1024 // 8KB medium buffer + LargeBufferSize = 16 * 1024 // 16KB large buffer ) var ( @@ -27,7 +41,7 @@ var ( DefaultKeyFile = "key.pem" // This is the default cert and key data for convenience, providing your own cert is recommended. - DefaultRawCert = `-----BEGIN CERTIFICATE----- + defaultRawCert = []byte(`-----BEGIN CERTIFICATE----- MIIC5jCCAdCgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD bzAeFw0xNDAzMTcwNjIwNTFaFw0xNTAzMTcwNjIwNTFaMBIxEDAOBgNVBAoTB0Fj bWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccNO1xmd4lWSf @@ -44,8 +58,8 @@ UBrrrDbKRNibApBHCapPf6gC5sXcjOwx7P2/kiHDgY7YH47jfcRhtAPNsM4gjsEO RmwENY+hRUFHIRfQTyalqND+x6PWhRo3K6hpHs4DQEYPq4P2kFPqUqSBymH+Ny5/ BcQ3wdMNmC6Bm/oiL1QV0M+/InOsAgQk/EDd0kmoU1ZT2lYHQduGmP099bOlHNpS uqO3vXF3q8SPPr/A9TqSs7BKkBQbe0+cdsA= ------END CERTIFICATE-----` - DefaultRawKey = `-----BEGIN RSA PRIVATE KEY----- +-----END CERTIFICATE-----`) + defaultRawKey = []byte(`-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA3HDTtcZneJVkn3f9P0EtxPd3GCFh8PN9YvyCsYoHUQ/1zGaJ y3RB8sFupTol2s2j/Hugqjxkpp5dMeZP93bxgjI9iQlcTOvs+zaS/gIsL15r5I/0 znIgoMKrLAsGEtgolcI32s0Y8dWVCVQwuaoskbH5LUNn1R66ZyRlUkWoJokIMBlg @@ -71,7 +85,11 @@ kE3XzN56Ks+/avHfdYPO+UHMenw5V28nh+hv5pdoZrlmanQTz3pkaOC8o3WNQZEB nh/BAoGBAMY5z2f1pmMhrvtPDSlEVjgjELbaInxFaxPLR4Pdyzn83gtIIU14+R8X 2LPs6PPwrNjWnIgrUSVXncIFL3pa45B+Mx1pYCpOAB1+nCZjIBQmpeo4Y0dwA/XH 85EthKPvoszm+OPbyI16OcePV5ocX7lupRYuAo0pek7bomhmHWHz ------END RSA PRIVATE KEY-----` +-----END RSA PRIVATE KEY-----`) +) + +var ( + ErrEmptyChain = errors.New("empty chain") ) func setKeepAlive(conn net.Conn, d time.Duration) error { @@ -88,11 +106,39 @@ func setKeepAlive(conn net.Conn, d time.Duration) error { return nil } -func loadCertificate(certFile, keyFile string) (tls.Certificate, error) { +// Load the certificate from cert and key files, will use the default certificate if the provided info are invalid. +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)) + return tls.X509KeyPair(defaultRawCert, defaultRawKey) +} + +// Replace the default certificate by your own +func SetDefaultCertificate(rawCert, rawKey []byte) { + defaultRawCert = rawCert + defaultRawKey = rawKey +} + +func basicProxyAuth(proxyAuth string) (username, password string, ok bool) { + if proxyAuth == "" { + return + } + + if !strings.HasPrefix(proxyAuth, "Basic ") { + return + } + c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(proxyAuth, "Basic ")) + if err != nil { + return + } + cs := string(c) + s := strings.IndexByte(cs, ':') + if s < 0 { + return + } + + return cs[:s], cs[s+1:], true } diff --git a/http.go b/http.go index 09fef38..efd71ff 100644 --- a/http.go +++ b/http.go @@ -1,58 +1,318 @@ package gost import ( - //"bufio" - //"crypto/tls" - //"encoding/base64" - //"github.com/golang/glog" - //"golang.org/x/net/http2" + "bufio" + "crypto/tls" + "github.com/golang/glog" + "golang.org/x/net/http2" "io" "net" - //"net/http" - //"net/http/httputil" + "net/http" + "net/http/httputil" + //"encoding/base64" //"strings" + "errors" "time" ) -// http2 client connection, wrapped up just like a net.Conn -type Http2ClientConn struct { - r io.Reader - w io.Writer - localAddr net.Addr - remoteAddr net.Addr +type HttpServer struct { + conn net.Conn + Base *ProxyServer } -func (c *Http2ClientConn) Read(b []byte) (n int, err error) { +func NewHttpServer(conn net.Conn, base *ProxyServer) *HttpServer { + return &HttpServer{ + conn: conn, + Base: base, + } +} + +// Default HTTP server handler +func (s *HttpServer) HandleRequest(req *http.Request) { + glog.V(LINFO).Infof("[http] %s %s - %s %s", req.Method, s.conn.RemoteAddr(), req.Host, req.Proto) + + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(req, false) + glog.Infoln(string(dump)) + } + + if req.Method == "PRI" && req.ProtoMajor == 2 { + glog.V(LWARNING).Infof("[http] %s <- %s : Not an HTTP2 server", s.conn.RemoteAddr(), req.Host) + resp := "HTTP/1.1 400 Bad Request\r\n" + + "Proxy-Agent: gost/" + Version + "\r\n\r\n" + s.conn.Write([]byte(resp)) + return + } + + var username, password string + if s.Base.Node.User != nil { + username = s.Base.Node.User.Username() + password, _ = s.Base.Node.User.Password() + } + + u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) + req.Header.Del("Proxy-Authorization") + if (username != "" && u != username) || (password != "" && p != password) { + glog.V(LWARNING).Infof("[http] %s <- %s : proxy authentication required", s.conn.RemoteAddr(), req.Host) + 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" + s.conn.Write([]byte(resp)) + return + } + + // TODO: forward http request + /* + if len(forwardArgs) > 0 { + last := forwardArgs[len(forwardArgs)-1] + if last.Protocol == "http" || last.Protocol == "" { + forwardHttpRequest(req, conn, arg) + return + } + } + */ + + c, err := s.Base.Chain.Dial(req.Host) + if err != nil { + glog.V(LWARNING).Infof("[http] %s -> %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", s.conn.RemoteAddr(), req.Host, string(b)) + s.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", s.conn.RemoteAddr(), req.Host, string(b)) + s.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", s.conn.RemoteAddr(), req.Host, err) + return + } + } + + glog.V(LINFO).Infof("[http] %s <-> %s", s.conn.RemoteAddr(), req.Host) + s.Base.transport(s.conn, c) + glog.V(LINFO).Infof("[http] %s >-< %s", s.conn.RemoteAddr(), req.Host) +} + +type Http2Server struct { + Base *ProxyServer + Handler http.Handler + TLSConfig *tls.Config +} + +func NewHttp2Server(base *ProxyServer) *Http2Server { + return &Http2Server{Base: base} +} + +func (s *Http2Server) ListenAndServeTLS(config *tls.Config) error { + srv := http.Server{ + Addr: s.Base.Node.Addr, + Handler: s.Handler, + TLSConfig: config, + } + if srv.Handler == nil { + srv.Handler = http.HandlerFunc(s.HandleRequest) + } + http2.ConfigureServer(&srv, nil) + return srv.ListenAndServeTLS("", "") +} + +// Default HTTP2 server handler +func (s *Http2Server) HandleRequest(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)) + } + + w.Header().Set("Proxy-Agent", "gost/"+Version) + + // HTTP2 as transport + if req.Header.Get("Proxy-Switch") == "gost" { + conn, err := s.Upgrade(w, req) + if err != nil { + glog.V(LINFO).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) + return + } + s.Base.handleConn(conn) + return + } + + var username, password string + if s.Base.Node.User != nil { + username = s.Base.Node.User.Username() + password, _ = s.Base.Node.User.Password() + } + + u, p, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization")) + req.Header.Del("Proxy-Authorization") + if (username != "" && u != username) || (password != "" && p != password) { + glog.V(LWARNING).Infof("[http2] %s <- %s : proxy authentication required", req.RemoteAddr, target) + w.WriteHeader(http.StatusProxyAuthRequired) + return + } + + c, err := s.Base.Chain.Dial(target) + if err != nil { + glog.V(LWARNING).Infof("[http2] %s -> %s : %s", req.RemoteAddr, target, err) + w.WriteHeader(http.StatusServiceUnavailable) + return + } + defer c.Close() + + glog.V(LINFO).Infof("[http2] %s <-> %s", req.RemoteAddr, target) + + if req.Method == http.MethodConnect { + w.WriteHeader(http.StatusOK) + if fw, ok := w.(http.Flusher); ok { + fw.Flush() + } + + // compatible with HTTP1.x + if hj, ok := w.(http.Hijacker); ok && 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) + w.WriteHeader(http.StatusInternalServerError) + return + } + defer conn.Close() + + s.Base.transport(conn, c) + return + } + + errc := make(chan error, 2) + + go func() { + _, err := io.Copy(c, req.Body) + errc <- err + }() + go func() { + _, err := io.Copy(flushWriter{w}, c) + errc <- err + }() + + select { + case <-errc: + // glog.V(LWARNING).Infoln("exit", err) + } + glog.V(LINFO).Infof("[http2] %s >-< %s", req.RemoteAddr, target) + return + } + + 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 _, 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) +} + +// Upgrade upgrade an HTTP2 request to a bidirectional connection that preparing for tunneling other protocol, just like a websocket connection. +func (s *Http2Server) Upgrade(w http.ResponseWriter, r *http.Request) (net.Conn, error) { + w.Header().Set("Proxy-Agent", "gost/"+Version) + + if r.Method != http.MethodConnect { + w.WriteHeader(http.StatusMethodNotAllowed) + return nil, errors.New("Method not allowed") + } + + w.WriteHeader(http.StatusOK) + + if fw, ok := w.(http.Flusher); ok { + fw.Flush() + } + + return &http2Conn{r: r.Body, w: flushWriter{w}}, nil +} + +// HTTP2 client connection, wrapped up just like a net.Conn +type http2Conn struct { + r io.Reader + w io.Writer +} + +func (c *http2Conn) Read(b []byte) (n int, err error) { return c.r.Read(b) } -func (c *Http2ClientConn) Write(b []byte) (n int, err error) { +func (c *http2Conn) Write(b []byte) (n int, err error) { return c.w.Write(b) } -func (c *Http2ClientConn) Close() error { +func (c *http2Conn) 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 { +func (c *http2Conn) LocalAddr() net.Addr { return nil } -func (c *Http2ClientConn) SetReadDeadline(t time.Time) error { +func (c *http2Conn) RemoteAddr() net.Addr { return nil } -func (c *Http2ClientConn) SetWriteDeadline(t time.Time) error { +func (c *http2Conn) SetDeadline(t time.Time) error { return nil } + +func (c *http2Conn) SetReadDeadline(t time.Time) error { + return nil +} + +func (c *http2Conn) SetWriteDeadline(t time.Time) error { + return nil +} + +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) + return + } + if f, ok := fw.w.(http.Flusher); ok { + f.Flush() + } + return +} diff --git a/node.go b/node.go index c807214..14c70dd 100644 --- a/node.go +++ b/node.go @@ -9,9 +9,9 @@ import ( // 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 + Addr string // [host]:port + Protocol string // protocol: http/socks5/ss + Transport string // transport: ws/wss/tls/http2/tcp/udp/rtcp/rudp Remote string // remote address, used by tcp/udp port forwarding User *url.Userinfo // authentication for proxy values url.Values @@ -19,8 +19,10 @@ type ProxyNode struct { conn net.Conn } -// the format is [scheme://][user:pass@host]:port -func ParseProxyNode(s string) (node *ProxyNode, err error) { +// The proxy node string pattern is [scheme://][user:pass@host]:port. +// +// Scheme can be devided into two parts by character '+', such as: http+tls. +func ParseProxyNode(s string) (node ProxyNode, err error) { if !strings.Contains(s, "://") { s = "gost://" + s } @@ -29,7 +31,7 @@ func ParseProxyNode(s string) (node *ProxyNode, err error) { return } - node = &ProxyNode{ + node = ProxyNode{ Addr: u.Host, User: u.User, values: u.Query(), @@ -38,6 +40,9 @@ func ParseProxyNode(s string) (node *ProxyNode, err error) { if strings.Contains(u.Host, ":") { node.serverName, _, _ = net.SplitHostPort(u.Host) + if node.serverName == "" { + node.serverName = "localhost" // default server name + } } schemes := strings.Split(u.Scheme, "+") @@ -66,7 +71,7 @@ func ParseProxyNode(s string) (node *ProxyNode, err error) { } switch node.Protocol { - case "http", "http2", "socks", "socks5", "ss": + case "http", "socks", "socks5", "ss", "http2": default: node.Protocol = "" } @@ -74,8 +79,17 @@ func ParseProxyNode(s string) (node *ProxyNode, err error) { return } +// Get get node parameter by key +func (node *ProxyNode) Get(key string) string { + return node.values.Get(key) +} + +func (node *ProxyNode) Set(key, value string) { + node.values.Set(key, value) +} + func (node *ProxyNode) insecureSkipVerify() bool { - s := node.values.Get("secure") + s := node.Get("secure") if secure, _ := strconv.ParseBool(s); secure { return !secure } @@ -86,14 +100,14 @@ func (node *ProxyNode) insecureSkipVerify() bool { } func (node *ProxyNode) certFile() string { - if cert := node.values.Get("cert"); cert != "" { + if cert := node.Get("cert"); cert != "" { return cert } return DefaultCertFile } func (node *ProxyNode) keyFile() string { - if key := node.values.Get("key"); key != "" { + if key := node.Get("key"); key != "" { return key } return DefaultKeyFile diff --git a/server.go b/server.go new file mode 100644 index 0000000..d4e5fa4 --- /dev/null +++ b/server.go @@ -0,0 +1,203 @@ +package gost + +import ( + "bufio" + "crypto/tls" + "github.com/ginuerzh/gosocks5" + "github.com/golang/glog" + "io" + "net" + "net/http" +) + +type ProxyServer struct { + Node ProxyNode + Chain *ProxyChain + TLSConfig *tls.Config + selector *serverSelector +} + +func NewProxyServer(node ProxyNode, chain *ProxyChain, config *tls.Config) *ProxyServer { + if chain == nil { + chain = NewProxyChain() + } + if config == nil { + config = &tls.Config{} + } + return &ProxyServer{ + Node: node, + Chain: chain, + TLSConfig: config, + selector: &serverSelector{ // socks5 server selector + // methods that socks5 server supported + methods: []uint8{ + gosocks5.MethodNoAuth, + gosocks5.MethodUserPass, + MethodTLS, + MethodTLSAuth, + }, + user: node.User, + tlsConfig: config, + }, + } +} + +func (s *ProxyServer) Serve() error { + var ln net.Listener + var err error + node := s.Node + + switch node.Transport { + case "ws": // websocket connection + return NewWebsocketServer(s).ListenAndServe() + case "wss": // websocket security connection + return NewWebsocketServer(s).ListenAndServeTLS(s.TLSConfig) + case "tls": // tls connection + ln, err = tls.Listen("tcp", node.Addr, s.TLSConfig) + case "http2": // Standard HTTP2 proxy server, compatible with HTTP1.x. + server := NewHttp2Server(s) + server.Handler = http.HandlerFunc(server.HandleRequest) + return server.ListenAndServeTLS(s.TLSConfig) + 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", node.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 s.handleConn(conn) + } +} + +func (s *ProxyServer) handleConn(conn net.Conn) { + defer conn.Close() + + switch s.Node.Protocol { + case "ss": // shadowsocks + NewShadowServer(conn, s).Serve() + return + case "http": + req, err := http.ReadRequest(bufio.NewReader(conn)) + if err != nil { + glog.V(LWARNING).Infoln("[http]", err) + return + } + NewHttpServer(conn, s).HandleRequest(req) + return + case "socks", "socks5": + conn = gosocks5.ServerConn(conn, s.selector) + req, err := gosocks5.ReadRequest(conn) + if err != nil { + glog.V(LWARNING).Infoln("[socks5]", err) + return + } + NewSocks5Server(conn, s).HandleRequest(req) + return + } + + glog.V(LINFO).Infof("%s - %s", conn.RemoteAddr(), s.Node.Addr) + // http or socks5 + b := make([]byte, MediumBufferSize) + + n, err := io.ReadAtLeast(conn, b, 2) + if err != nil { + glog.V(LWARNING).Infoln(err) + return + } + + // TODO: use bufio.Reader + 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 + } + } + // TODO: use gosocks5.ServerConn + methods := b[2 : 2+mn] + method := s.selector.Select(methods...) + if _, err := conn.Write([]byte{gosocks5.Ver5, method}); err != nil { + glog.V(LWARNING).Infoln("[socks5] select:", err) + return + } + c, err := s.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 + } + NewSocks5Server(conn, s).HandleRequest(req) + return + } + + req, err := http.ReadRequest(bufio.NewReader(&reqReader{b: b[:n], r: conn})) + if err != nil { + glog.V(LWARNING).Infoln("[http]", err) + return + } + NewHttpServer(conn, s).HandleRequest(req) +} + +func (s *ProxyServer) transport(conn1, conn2 net.Conn) (err error) { + errc := make(chan error, 2) + + go func() { + _, err := io.Copy(conn1, conn2) + errc <- err + }() + + go func() { + _, err := io.Copy(conn2, conn1) + errc <- err + }() + + select { + case err = <-errc: + //glog.V(LWARNING).Infoln("transport exit", err) + } + + return +} + +type reqReader struct { + b []byte + r io.Reader +} + +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 +} diff --git a/socks.go b/socks.go index e89620f..a476f1b 100644 --- a/socks.go +++ b/socks.go @@ -1,9 +1,9 @@ package gost import ( - //"bytes" + "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 ( @@ -26,8 +26,9 @@ const ( ) type clientSelector struct { - methods []uint8 - user *url.Userinfo + methods []uint8 + user *url.Userinfo + tlsConfig *tls.Config } func (selector *clientSelector) Methods() []uint8 { @@ -41,11 +42,11 @@ func (selector *clientSelector) Select(methods ...uint8) (method uint8) { func (selector *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) { switch method { case MethodTLS: - conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + conn = tls.Client(conn, selector.tlsConfig) case gosocks5.MethodUserPass, MethodTLSAuth: if method == MethodTLSAuth { - conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) + conn = tls.Client(conn, selector.tlsConfig) } var username, password string @@ -79,9 +80,9 @@ func (selector *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Con } type serverSelector struct { - methods []uint8 - user *url.Userinfo - cert tls.Certificate + methods []uint8 + user *url.Userinfo + tlsConfig *tls.Config } func (selector *serverSelector) Methods() []uint8 { @@ -117,11 +118,11 @@ func (selector *serverSelector) OnSelected(method uint8, conn net.Conn) (net.Con switch method { case MethodTLS: - conn = tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{selector.cert}}) + conn = tls.Server(conn, selector.tlsConfig) case gosocks5.MethodUserPass, MethodTLSAuth: if method == MethodTLSAuth { - conn = tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{selector.cert}}) + conn = tls.Server(conn, selector.tlsConfig) } req, err := gosocks5.ReadUserPassRequest(conn) @@ -162,3 +163,556 @@ func (selector *serverSelector) OnSelected(method uint8, conn net.Conn) (net.Con return conn, nil } + +type Socks5Server struct { + conn net.Conn + Base *ProxyServer +} + +func NewSocks5Server(conn net.Conn, base *ProxyServer) *Socks5Server { + return &Socks5Server{conn: conn, Base: base} +} + +func (s *Socks5Server) HandleRequest(req *gosocks5.Request) { + glog.V(LDEBUG).Infof("[socks5] %s - %s\n%s", s.conn.RemoteAddr(), req.Addr, req) + + switch req.Cmd { + case gosocks5.CmdConnect: + glog.V(LINFO).Infof("[socks5-connect] %s -> %s", s.conn.RemoteAddr(), req.Addr) + s.handleConnect(req) + + case gosocks5.CmdBind: + glog.V(LINFO).Infof("[socks5-bind] %s - %s", s.conn.RemoteAddr(), req.Addr) + s.handleBind(req) + + case CmdUdpConnect: + glog.V(LINFO).Infof("[udp] %s - %s", s.conn.RemoteAddr(), req.Addr) + s.handleUDPConnect(req) + + case gosocks5.CmdUdp: + glog.V(LINFO).Infof("[socks5-udp] %s - %s", s.conn.RemoteAddr(), req.Addr) + s.handleUDPRelay(req) + + case CmdUdpTun: + glog.V(LINFO).Infof("[socks5-udp] %s - %s", s.conn.RemoteAddr(), req.Addr) + s.handleUDPTunnel(req) + + default: + glog.V(LWARNING).Infoln("[socks5] Unrecognized request:", req.Cmd) + } +} + +func (s *Socks5Server) handleConnect(req *gosocks5.Request) { + cc, err := s.Base.Chain.Dial(req.Addr.String()) + if err != nil { + glog.V(LWARNING).Infof("[socks5-connect] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) + rep := gosocks5.NewReply(gosocks5.HostUnreachable, nil) + rep.Write(s.conn) + glog.V(LDEBUG).Infof("[socks5-connect] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, rep) + return + } + defer cc.Close() + + rep := gosocks5.NewReply(gosocks5.Succeeded, nil) + if err := rep.Write(s.conn); err != nil { + glog.V(LWARNING).Infof("[socks5-connect] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + return + } + glog.V(LDEBUG).Infof("[socks5-connect] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, rep) + + glog.V(LINFO).Infof("[socks5-connect] %s <-> %s", s.conn.RemoteAddr(), req.Addr) + //Transport(conn, cc) + s.Base.transport(s.conn, cc) + glog.V(LINFO).Infof("[socks5-connect] %s >-< %s", s.conn.RemoteAddr(), req.Addr) +} + +func (s *Socks5Server) handleBind(req *gosocks5.Request) { + cc, err := s.Base.Chain.GetConn() + // forward request + if err == nil { + req.Write(cc) + } + // connection error + if err != nil && err != ErrEmptyChain { + glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + reply := gosocks5.NewReply(gosocks5.Failure, nil) + reply.Write(s.conn) + glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) + return + } + // serve socks5 bind + if err == ErrEmptyChain { + cc, err = s.bind(req.Addr.String()) + if err != nil { + glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + reply := gosocks5.NewReply(gosocks5.Failure, nil) + reply.Write(s.conn) + glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) + return + } + } + + defer cc.Close() + + glog.V(LINFO).Infof("[socks5-bind] %s <-> %s", s.conn.RemoteAddr(), cc.RemoteAddr()) + s.Base.transport(s.conn, cc) + glog.V(LINFO).Infof("[socks5-bind] %s >-< %s", s.conn.RemoteAddr(), cc.RemoteAddr()) +} + +func (s *Socks5Server) handleUDPConnect(req *gosocks5.Request) { + cc, err := s.Base.Chain.GetConn() + + // connection error + if err != nil && err != ErrEmptyChain { + glog.V(LWARNING).Infof("[udp] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + reply := gosocks5.NewReply(gosocks5.Failure, nil) + reply.Write(s.conn) + glog.V(LDEBUG).Infof("[udp] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) + return + } + + // serve udp connect + if err == ErrEmptyChain { + s.udpConnect(req.Addr.String()) + return + } + + defer cc.Close() + + // forward request + if err := req.Write(cc); err != nil { + glog.V(LINFO).Infof("[udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) + gosocks5.NewReply(gosocks5.Failure, nil).Write(s.conn) + return + } + + glog.V(LINFO).Infof("[udp] %s <-> %s", s.conn.RemoteAddr(), req.Addr) + s.Base.transport(s.conn, cc) + glog.V(LINFO).Infof("[udp] %s >-< %s", s.conn.RemoteAddr(), req.Addr) +} + +func (s *Socks5Server) handleUDPRelay(req *gosocks5.Request) { + 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", s.conn.RemoteAddr(), req.Addr, err) + reply := gosocks5.NewReply(gosocks5.Failure, nil) + reply.Write(s.conn) + glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) + return + } + defer relay.Close() + + socksAddr := ToSocksAddr(relay.LocalAddr()) + socksAddr.Host, _, _ = net.SplitHostPort(s.conn.LocalAddr().String()) + reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) + if err := reply.Write(s.conn); err != nil { + glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + return + } + glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", s.conn.RemoteAddr(), reply.Addr, reply) + glog.V(LINFO).Infof("[socks5-udp] %s - %s BIND ON %s OK", s.conn.RemoteAddr(), req.Addr, socksAddr) + + cc, err := s.Base.Chain.GetConn() + // connection error + if err != nil && err != ErrEmptyChain { + glog.V(LWARNING).Infof("[udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) + return + } + + // serve as standard socks5 udp relay + if err == ErrEmptyChain { + peer, err := net.ListenUDP("udp", nil) + if err != nil { + glog.V(LWARNING).Infof("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) + return + } + defer peer.Close() + go s.transportUDP(relay, peer) + } + + if err == nil { + defer cc.Close() + + cc.SetWriteDeadline(time.Now().Add(WriteTimeout)) + if err := gosocks5.NewRequest(CmdUdpTun, nil).Write(cc); err != nil { + glog.V(LWARNING).Infoln("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) + return + } + cc.SetWriteDeadline(time.Time{}) + + cc.SetReadDeadline(time.Now().Add(ReadTimeout)) + reply, err = gosocks5.ReadReply(cc) + if err != nil { + glog.V(LWARNING).Infoln("[socks5-udp] %s -> %s : %s", s.conn.RemoteAddr(), req.Addr, err) + return + } + if reply.Rep != gosocks5.Succeeded { + glog.V(LWARNING).Infoln("[socks5-udp] %s -> %s : udp associate error", s.conn.RemoteAddr(), req.Addr) + return + } + cc.SetReadDeadline(time.Time{}) + go s.tunnelUDP(relay, cc, true) + } + + glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", s.conn.RemoteAddr(), req.Addr) + b := make([]byte, SmallBufferSize) + for { + _, err := s.conn.Read(b) // discard any data from tcp connection + if err != nil { + break // client disconnected + } + } + glog.V(LINFO).Infof("[socks5-udp] %s >-< %s", s.conn.RemoteAddr(), req.Addr) +} + +func (s *Socks5Server) handleUDPTunnel(req *gosocks5.Request) { + cc, err := s.Base.Chain.GetConn() + + // connection error + if err != nil && err != ErrEmptyChain { + glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + reply := gosocks5.NewReply(gosocks5.Failure, nil) + reply.Write(s.conn) + glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", s.conn.RemoteAddr(), req.Addr, reply) + return + } + + // serve tunnel udp, tunnel <-> remote, handle tunnel udp request + if err == ErrEmptyChain { + bindAddr, _ := net.ResolveUDPAddr("udp", req.Addr.String()) + uc, err := net.ListenUDP("udp", bindAddr) + if err != nil { + glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + return + } + defer uc.Close() + + socksAddr := ToSocksAddr(uc.LocalAddr()) + socksAddr.Host, _, _ = net.SplitHostPort(s.conn.LocalAddr().String()) + reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) + if err := reply.Write(s.conn); err != nil { + glog.V(LWARNING).Infof("[socks5-udp] %s <- %s : %s", s.conn.RemoteAddr(), req.Addr, err) + return + } + glog.V(LDEBUG).Infof("[socks5-udp] %s <- %s\n%s", s.conn.RemoteAddr(), uc.LocalAddr(), reply) + + glog.V(LINFO).Infof("[socks5-udp] %s <-> %s", s.conn.RemoteAddr(), uc.LocalAddr()) + s.tunnelUDP(uc, s.conn, false) + glog.V(LINFO).Infof("[socks5-udp] %s >-< %s", s.conn.RemoteAddr(), uc.LocalAddr()) + return + } + + defer cc.Close() + + // tunnel <-> tunnel, direct forwarding + req.Write(cc) + + glog.V(LINFO).Infof("[socks5-udp] %s <-> %s[tun]", s.conn.RemoteAddr(), cc.RemoteAddr()) + s.Base.transport(s.conn, cc) + glog.V(LINFO).Infof("[socks5-udp] %s >-< %s[tun]", s.conn.RemoteAddr(), cc.RemoteAddr()) +} + +func (s *Socks5Server) bind(addr string) (net.Conn, error) { + bindAddr, _ := net.ResolveTCPAddr("tcp", addr) + ln, err := net.ListenTCP("tcp", bindAddr) // strict mode: if the port already in use, it will return error + if err != nil { + return nil, err + } + + socksAddr := ToSocksAddr(ln.Addr()) + // Issue: may not reachable when host has multi-interface + socksAddr.Host, _, _ = net.SplitHostPort(s.conn.LocalAddr().String()) + reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) + if err := reply.Write(s.conn); err != nil { + glog.V(LWARNING).Infof("[socks5-bind] %s <- %s : %s", s.conn.RemoteAddr(), addr, err) + ln.Close() + return nil, err + } + glog.V(LDEBUG).Infof("[socks5-bind] %s <- %s\n%s", s.conn.RemoteAddr(), addr, reply) + glog.V(LINFO).Infof("[socks5-bind] %s - %s BIND ON %s OK", s.conn.RemoteAddr(), addr, socksAddr) + + 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 := make([]byte, SmallBufferSize) + _, err := s.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 nil, errors.New("accept error") + } + pconn = c + lnChan = nil + ln = nil + s.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, err + } + goto out + } + } + +out: + s.conn.SetReadDeadline(time.Time{}) + + glog.V(LINFO).Infof("[socks5-bind] %s <- %s PEER %s ACCEPTED", s.conn.RemoteAddr(), socksAddr, pconn.RemoteAddr()) + return pconn, nil +} + +func (s *Socks5Server) udpConnect(addr string) { + raddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + glog.V(LINFO).Infof("[udp] %s -> %s : %s", s.conn.RemoteAddr(), addr, err) + gosocks5.NewReply(gosocks5.Failure, nil).Write(s.conn) + return + } + + if err := gosocks5.NewReply(gosocks5.Succeeded, nil).Write(s.conn); err != nil { + glog.V(LINFO).Infof("[udp] %s <- %s : %s", s.conn.RemoteAddr(), addr, err) + return + } + + glog.V(LINFO).Infof("[udp] %s <-> %s", s.conn.RemoteAddr(), raddr) + defer glog.V(LINFO).Infof("[udp] %s >-< %s", s.conn.RemoteAddr(), raddr) + + for { + dgram, err := gosocks5.ReadUDPDatagram(s.conn) + if err != nil { + glog.V(LWARNING).Infof("[udp] %s -> %s : %s", s.conn.RemoteAddr(), addr, err) + return + } + + go func() { + b := make([]byte, LargeBufferSize) + + relay, err := net.DialUDP("udp", nil, raddr) + if err != nil { + glog.V(LWARNING).Infof("[udp] %s -> %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", s.conn.RemoteAddr(), raddr, err) + return + } + glog.V(LDEBUG).Infof("[udp-tun] %s >>> %s length: %d", s.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", s.conn.RemoteAddr(), raddr, err) + return + } + relay.SetReadDeadline(time.Time{}) + + glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s length: %d", s.conn.RemoteAddr(), raddr, n) + + s.conn.SetWriteDeadline(time.Now().Add(time.Second * 90)) + if err := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(n), 0, dgram.Header.Addr), b[:n]).Write(s.conn); err != nil { + glog.V(LWARNING).Infof("[udp] %s <- %s : %s", s.conn.RemoteAddr(), raddr, err) + return + } + s.conn.SetWriteDeadline(time.Time{}) + }() + } +} + +func (s *Socks5Server) transportUDP(relay, peer *net.UDPConn) (err error) { + errc := make(chan error, 2) + + var clientAddr *net.UDPAddr + + go func() { + b := make([]byte, LargeBufferSize) + + for { + n, laddr, err := relay.ReadFromUDP(b) + if err != nil { + errc <- err + return + } + if clientAddr == nil { + clientAddr = laddr + } + dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n])) + if err != nil { + errc <- err + return + } + + raddr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String()) + if err != nil { + continue // drop silently + } + if _, err := peer.WriteToUDP(dgram.Data, raddr); err != nil { + errc <- err + return + } + glog.V(LDEBUG).Infof("[socks5-udp] %s >>> %s length: %d", relay.LocalAddr(), raddr, len(dgram.Data)) + } + }() + + go func() { + b := make([]byte, LargeBufferSize) + + for { + n, raddr, err := peer.ReadFromUDP(b) + if err != nil { + errc <- err + return + } + if clientAddr == nil { + continue + } + buf := bytes.Buffer{} + dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, ToSocksAddr(raddr)), b[:n]) + dgram.Write(&buf) + if _, err := relay.WriteToUDP(buf.Bytes(), clientAddr); err != nil { + errc <- err + return + } + glog.V(LDEBUG).Infof("[socks5-udp] %s <<< %s length: %d", relay.LocalAddr(), raddr, len(dgram.Data)) + } + }() + + select { + case err = <-errc: + //log.Println("w exit", err) + } + + return +} + +func (s *Socks5Server) tunnelUDP(uc *net.UDPConn, cc net.Conn, client bool) (err error) { + errc := make(chan error, 2) + + var clientAddr *net.UDPAddr + + go func() { + b := make([]byte, LargeBufferSize) + + for { + n, addr, err := uc.ReadFromUDP(b) + if err != nil { + errc <- err + return + } + + var dgram *gosocks5.UDPDatagram + if client { // pipe from relay to tunnel + dgram, err = gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n])) + if err != nil { + errc <- err + return + } + if clientAddr == nil { + clientAddr = addr + } + dgram.Header.Rsv = uint16(len(dgram.Data)) + if err := dgram.Write(cc); err != nil { + errc <- err + return + } + glog.V(LDEBUG).Infof("[udp-tun] %s >>> %s length: %d", uc.LocalAddr(), dgram.Header.Addr, len(dgram.Data)) + } else { // pipe from peer to tunnel + dgram = gosocks5.NewUDPDatagram( + gosocks5.NewUDPHeader(uint16(n), 0, ToSocksAddr(addr)), b[:n]) + if err := dgram.Write(cc); err != nil { + errc <- err + return + } + glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s length: %d", cc.RemoteAddr(), dgram.Header.Addr, len(dgram.Data)) + } + } + }() + + go func() { + for { + dgram, err := gosocks5.ReadUDPDatagram(cc) + if err != nil { + errc <- err + return + } + + if client { // pipe from tunnel to relay + if clientAddr == nil { + continue + } + dgram.Header.Rsv = 0 + + buf := bytes.Buffer{} + dgram.Write(&buf) + if _, err := uc.WriteToUDP(buf.Bytes(), clientAddr); err != nil { + errc <- err + return + } + glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s length: %d", uc.LocalAddr(), dgram.Header.Addr, len(dgram.Data)) + } else { // pipe from tunnel to peer + addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String()) + if err != nil { + continue // drop silently + } + if _, err := uc.WriteToUDP(dgram.Data, addr); err != nil { + errc <- err + return + } + glog.V(LDEBUG).Infof("[udp-tun] %s >>> %s length: %d", cc.RemoteAddr(), addr, len(dgram.Data)) + } + } + }() + + select { + case err = <-errc: + } + + return +} + +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/ss.go new file mode 100644 index 0000000..7c4d786 --- /dev/null +++ b/ss.go @@ -0,0 +1,134 @@ +package gost + +import ( + "encoding/binary" + "fmt" + "github.com/ginuerzh/gosocks5" + "github.com/golang/glog" + "github.com/shadowsocks/shadowsocks-go/shadowsocks" + "io" + "net" +) + +type ShadowServer struct { + conn net.Conn + Base *ProxyServer +} + +func NewShadowServer(conn net.Conn, base *ProxyServer) *ShadowServer { + return &ShadowServer{conn: conn, Base: base} +} + +func (s *ShadowServer) Serve() { + glog.V(LINFO).Infof("[ss] %s -> %s", s.conn.RemoteAddr(), s.conn.LocalAddr()) + + var conn net.Conn + + if s.Base.Node.User != nil { + method := s.Base.Node.User.Username() + password, _ := s.Base.Node.User.Password() + cipher, err := shadowsocks.NewCipher(method, password) + if err != nil { + glog.V(LWARNING).Infof("[ss] %s - %s : %s", s.conn.RemoteAddr(), s.conn.LocalAddr(), err) + return + } + conn = shadowsocks.NewConn(s.conn, cipher) + } + + addr, extra, err := getShadowRequest(conn) + if err != nil { + glog.V(LWARNING).Infof("[ss] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) + return + } + glog.V(LINFO).Infof("[ss] %s -> %s", conn.RemoteAddr(), addr.String()) + + cc, err := s.Base.Chain.Dial(addr.String()) + if err != nil { + glog.V(LWARNING).Infof("[ss] %s -> %s : %s", conn.RemoteAddr(), addr.String(), err) + return + } + defer cc.Close() + + if extra != nil { + if _, err := cc.Write(extra); err != nil { + glog.V(LWARNING).Infof("[ss] %s - %s : %s", conn.RemoteAddr(), addr.String(), err) + return + } + } + + glog.V(LINFO).Infof("[ss] %s <-> %s", conn.RemoteAddr(), addr.String()) + s.Base.transport(conn, cc) + glog.V(LINFO).Infof("[ss] %s >-< %s", conn.RemoteAddr(), addr.String()) +} + +func getShadowRequest(conn net.Conn) (addr *gosocks5.Addr, extra []byte, err error) { + const ( + idType = 0 // address type index + idIP0 = 1 // ip addres start index + idDmLen = 1 // domain address length index + idDm0 = 2 // domain address start index + + typeIPv4 = 1 // type is ipv4 address + typeDm = 3 // type is domain address + typeIPv6 = 4 // type is ipv6 address + + lenIPv4 = 1 + net.IPv4len + 2 // 1addrType + ipv4 + 2port + lenIPv6 = 1 + net.IPv6len + 2 // 1addrType + ipv6 + 2port + lenDmBase = 1 + 1 + 2 // 1addrType + 1addrLen + 2port, plus addrLen + ) + + // buf size should at least have the same size with the largest possible + // request size (when addrType is 3, domain name has at most 256 bytes) + // 1(addrType) + 1(lenByte) + 256(max length address) + 2(port) + buf := make([]byte, SmallBufferSize) + + var n int + // read till we get possible domain length field + //shadowsocks.SetReadTimeout(conn) + if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil { + return + } + + addr = &gosocks5.Addr{ + Type: buf[idType], + } + + reqLen := -1 + switch buf[idType] { + case typeIPv4: + reqLen = lenIPv4 + case typeIPv6: + reqLen = lenIPv6 + case typeDm: + reqLen = int(buf[idDmLen]) + lenDmBase + default: + err = fmt.Errorf("addr type %d not supported", buf[idType]) + return + } + + if n < reqLen { // rare case + //ss.SetReadTimeout(conn) + if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil { + return + } + } else if n > reqLen { + // it's possible to read more than just the request head + extra = buf[reqLen:n] + } + + // Return string for typeIP is not most efficient, but browsers (Chrome, + // Safari, Firefox) all seems using typeDm exclusively. So this is not a + // big problem. + switch buf[idType] { + case typeIPv4: + addr.Host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String() + case typeIPv6: + addr.Host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String() + case typeDm: + addr.Host = string(buf[idDm0 : idDm0+buf[idDmLen]]) + } + // parse port + addr.Port = binary.BigEndian.Uint16(buf[reqLen-2 : reqLen]) + + return +} diff --git a/ws.go b/ws.go index 0665378..1a1a826 100644 --- a/ws.go +++ b/ws.go @@ -3,47 +3,104 @@ 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/url" + "net/http" + "net/http/httputil" + //"net/url" "time" ) -type wsConn struct { +type WebsocketServer struct { + Addr string + Base *ProxyServer + Handler http.Handler + upgrader websocket.Upgrader +} + +func NewWebsocketServer(base *ProxyServer) *WebsocketServer { + return &WebsocketServer{ + Addr: base.Node.Addr, + Base: base, + upgrader: websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { return true }, + }, + } +} + +// Default websocket server handler +func (s *WebsocketServer) HandleRequest(w http.ResponseWriter, r *http.Request) { + glog.V(LINFO).Infof("[ws] %s - %s", r.RemoteAddr, s.Addr) + if glog.V(LDEBUG) { + dump, _ := httputil.DumpRequest(r, false) + glog.V(LDEBUG).Infof("[ws] %s - %s\n%s", r.RemoteAddr, s.Addr, string(dump)) + } + conn, err := s.upgrader.Upgrade(w, r, nil) + if err != nil { + glog.V(LERROR).Infof("[ws] %s - %s : %s", r.RemoteAddr, s.Addr, err) + return + } + s.Base.handleConn(WebsocketServerConn(conn)) +} + +func (s *WebsocketServer) ListenAndServe() error { + mux := http.NewServeMux() + if s.Handler == nil { + s.Handler = http.HandlerFunc(s.HandleRequest) + } + mux.Handle("/ws", s.Handler) + return http.ListenAndServe(s.Addr, mux) +} + +func (s *WebsocketServer) ListenAndServeTLS(config *tls.Config) error { + mux := http.NewServeMux() + if s.Handler == nil { + s.Handler = http.HandlerFunc(s.HandleRequest) + } + mux.Handle("/ws", s.Handler) + server := &http.Server{ + Addr: s.Addr, + Handler: mux, + TLSConfig: config, + } + return server.ListenAndServeTLS("", "") +} + +type WebsocketConn struct { conn *websocket.Conn rb []byte } -func wsClient(scheme string, conn net.Conn, host string) (*wsConn, error) { +func WebsocketClientConn(url string, conn net.Conn, config *tls.Config) (*WebsocketConn, error) { dialer := websocket.Dialer{ ReadBufferSize: 1024, WriteBufferSize: 1024, - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - HandshakeTimeout: time.Second * 90, + TLSClientConfig: config, + HandshakeTimeout: DialTimeout, 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) + + c, resp, err := dialer.Dial(url, nil) if err != nil { return nil, err } resp.Body.Close() - return &wsConn{conn: c}, nil + return &WebsocketConn{conn: c}, nil } -func wsServer(conn *websocket.Conn) *wsConn { - return &wsConn{ +func WebsocketServerConn(conn *websocket.Conn) *WebsocketConn { + return &WebsocketConn{ conn: conn, } } -func (c *wsConn) Read(b []byte) (n int, err error) { +func (c *WebsocketConn) Read(b []byte) (n int, err error) { if len(c.rb) == 0 { _, c.rb, err = c.conn.ReadMessage() } @@ -55,7 +112,7 @@ func (c *wsConn) Read(b []byte) (n int, err error) { return } -func (c *wsConn) Write(b []byte) (n int, err error) { +func (c *WebsocketConn) Write(b []byte) (n int, err error) { err = c.conn.WriteMessage(websocket.BinaryMessage, b) n = len(b) //log.Println("ws w:", n) @@ -63,28 +120,28 @@ func (c *wsConn) Write(b []byte) (n int, err error) { return } -func (c *wsConn) Close() error { +func (c *WebsocketConn) Close() error { return c.conn.Close() } -func (c *wsConn) LocalAddr() net.Addr { +func (c *WebsocketConn) LocalAddr() net.Addr { return c.conn.LocalAddr() } -func (c *wsConn) RemoteAddr() net.Addr { +func (c *WebsocketConn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() } -func (conn *wsConn) SetDeadline(t time.Time) error { +func (conn *WebsocketConn) 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 { +func (c *WebsocketConn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) } -func (c *wsConn) SetWriteDeadline(t time.Time) error { +func (c *WebsocketConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) }