From 037a79a00dacf3857f60e289ef5dd41ac7e56445 Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Fri, 25 Sep 2015 17:00:06 +0800 Subject: [PATCH] gost v2.0 init --- LICENSE | 22 --- README.md | 172 ---------------- cert2.pem | 18 ++ client.go | 563 ----------------------------------------------------- conn.go | 176 +++++++++++++++++ http.go | 331 ++++++------------------------- key.pem | 27 +++ main.go | 92 ++++----- pool.go | 108 ---------- server.go | 6 - socks.go | 400 +++++++++++++++++++++++++++++++++++++ socks5.go | 296 ---------------------------- tls.go | 51 ----- util.go | 315 +++++++++--------------------- version.go | 2 +- ws.go | 117 +++++++---- 16 files changed, 882 insertions(+), 1814 deletions(-) delete mode 100644 LICENSE delete mode 100644 README.md create mode 100644 cert2.pem delete mode 100644 client.go create mode 100644 conn.go create mode 100644 key.pem delete mode 100644 pool.go delete mode 100644 server.go create mode 100644 socks.go delete mode 100644 socks5.go delete mode 100644 tls.go diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 4df246f..0000000 --- a/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 郑锐 - -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/README.md b/README.md deleted file mode 100644 index da62127..0000000 --- a/README.md +++ /dev/null @@ -1,172 +0,0 @@ -gost - GO Simple Tunnel -==== - -### GO语言实现的安全隧道 - -#### 特性 -1. 支持设置上层代理(客户端,服务器端均可),支持上层代理认证。 -2. 客户端可用作http(s), socks5代理。 -3. 服务器端兼容标准的socks5协议, 可直接用作socks5代理, 并额外增加协商加密功能。 -4. Tunnel UDP over TCP, UDP数据包使用TCP通道传输,以解决防火墙的限制。 -5. 多种加密方式(tls,aes-256-cfb,des-cfb,rc4-md5等)。 -6. 客户端兼容shadowsocks协议,可作为shadowsocks服务器。 - -二进制文件下载:https://github.com/ginuerzh/gost/releases - -Google讨论组: https://groups.google.com/d/forum/go-gost - -#### 版本更新 - -##### v1.8 -* 支持tls tunnel(-tls参数),直接使用tls进行加密传输 - -##### v1.7 -* 支持认证功能,当作为http(s)代理时使用Basic Auth认证方式,当作为标准socks5代理时使用Username/Password认证方式 - -###### Bug fix: -* 修正当作为http代理时,POST请求出错问题 - -##### v1.6 -* 增加tls-auth加密方式,此方式必须设置认证密码(-p参数),原tls加密方式与v1.3版以前兼容 - -###### Bug fix: -* 修正当不设置上层代理时,连接出错问题 - -##### v1.5 -* 支持设置上层socks5代理(注: http tunnel不支持) -* 支持上层代理认证 - -##### V1.4 -* 支持http tunnel(-http参数),使用http协议来传输数据(注: 效率低,非特殊情况下,不推荐使用)。 - -##### v1.3 -* tls加密方式增加密码认证功能(与旧版本不兼容) -* 增加版本查看(-v参数) -* -p参数的默认值修改为空 - -##### v1.2 -* websocket tunnel增加加密功能。 - -##### v1.1 -* 支持websocket tunnel(-ws参数),使用websocket协议来传输数据。 - -#### 参数说明 -> -L=":8080": listen address - -> -P="": proxy for forward - -> -S="": the server that connect to - -> -cert="": tls cert file - -> -key="": tls key file - -> -m="": tunnel cipher method - -> -p="": tunnel cipher password - -> -sm="rc4-md5": shadowsocks cipher method - -> -sp="ginuerzh@gmail.com": shadowsocks cipher password - -> -ss=false: run as shadowsocks server - -> -tls=false: use ssl/tls tunnel - -> -ws=false: use websocket tunnel - -> -http=false: use http tunnel - -> -v=false: print version - - -#### 使用方法 -##### 基本用法 -* 客户端: `gost -L=:8899 -S=server_ip:8080` -* 服务器: `gost -L=:8080` - -##### 设置认证信息 -* 客户端: `gost -L=admin:123456@:8899 -S=server_ip:8080` -* 服务器: `gost -L=admin:123456@:8080` - -注:当服务器端设置了认证,默认的无加密模式(-m为空)不可用, -即客户端或者使用认证方式(标准socks5模式),或者设置加密方式(gost兼容模式)。 - -##### 设置加密 -* 客户端: `gost -L=:8899 -S=server_ip:8080 -m=rc4-md5 -p=123456` -* 服务器: `gost -L=:8080 -m=rc4-md5 -p=123456` - -##### 设置上层代理 -* http代理: `gost -L=:8899 -P=http://127.0.0.1:8080` -* http代理(需认证): `gost -L=:8899 -P=http://admin:123456@127.0.0.1:8080` -* socks5代理: `gost -L=:8899 -P=socks://127.0.0.1:1080` -* socks5代理(需认证): `gost -L=:8899 -P=socks://admin:123456@127.0.0.1:1080` - -##### 使用tls tunnel (推荐) -* 客户端: `gost -L=:8899 -S=server_ip:8080 -tls` -* 服务器: `gost -L=:8080 -tls` - -注: 可通过-key, -cert参数手动指定自己的公钥与私钥文件。 - -##### 使用websocket tunnel -* 客户端: `gost -L=:8899 -S=server_ip:8080 -ws` -* 服务器: `gost -L=:8080 -ws` - -##### 使用http tunnel -* 客户端: `gost -L=:8899 -S=server_ip:8080 -http` -* 服务器: `gost -L=:8080 -http` - -注:websocket方式优先级高于http方式,即当-ws与-http参数同时存在时,-http参数无效。 - -##### 作为shadowsocks服务器 -gost支持作为shadowsocks服务器运行(-ss参数),这样就可以让android手机通过shadowsocks客户端(影梭)使用代理了。 - -###### 相关参数 -> -ss 开启shadowsocks模式 - -> -sm 设置shadowsocks加密方式(默认为rc4-md5) - -> -sp 设置shadowsocks加密密码(默认为ginuerzh@gmail.com) - -当无-ss参数时,-sm, -sp参数无效。以上三个参数对服务端无效。 - -###### 相关命令 -* 客户端: `gost -L :8899 -S server_ip:port -sm=rc4-md5 -sp=ginuerzh@gmail.com -ss` -* 服务器: 无需特殊设置,shadowsocks模式只与客户端有关,与服务端无关。 - -在手机的shadowsocks软件中设置好服务器IP(运行gost客户端电脑的IP),端口(8899),加密方法和密码就可以使用了。 - -注:shadowsocks模式与正常模式是不兼容的,当作为shadowsocks模式使用时(有-ss参数),浏览器不能使用。 - - -#### tunnel加密说明 -##### 目前支持的加密方法 -tls, tls-auth, aes-128-cfb, aes-192-cfb, aes-256-cfb, des-cfb, bf-cfb, cast5-cfb, rc4-md5, rc4, table - -##### Client - -Client端通过-m参数设置加密方式,默认为不加密(-m参数为空)。 - -如果设置的加密方式不被支持,则默认为不加密。 - -当设置的加密方式为tls时,-p参数无效。 - -当设置的加密方式为非tls时,通过-p参数设置加密密码,且不能为空;-p参数必须与Server端的-p参数相同。 - -##### Server - -Server端通过-m参数设置加密方式,默认为不加密(-m参数为空)。 - -如果设置的加密方式不被支持,默认为不处理。 - -如果没有设置加密方式(-m参数为空),则由client端控制加密方式,即client端可通过-m参数指定Server端使用哪种加密方式。 - -如果设置了加密方式(-m参数不为空),client端必须使用与Server端相同的加密方式。 - -当设置的加密方式为tls,tls-auth时,-key参数可手动指定公钥文件,-cert参数可手动指定私钥文件,如果未指定,则使用默认的公钥与私钥。 - -当设置的加密方式为tls时,-p参数无效;为tls-auth时,通过-p参数设置认证密码,且不能为空。 - -当设置的加密方式为非tls,tls-auth时,-key,-cert参数无效;通过-p参数设置加密密码,且不能为空。 - - diff --git a/cert2.pem b/cert2.pem new file mode 100644 index 0000000..5650a1e --- /dev/null +++ b/cert2.pem @@ -0,0 +1,18 @@ +-----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----- diff --git a/client.go b/client.go deleted file mode 100644 index a2143ff..0000000 --- a/client.go +++ /dev/null @@ -1,563 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "crypto/tls" - "encoding/base64" - "encoding/binary" - //"encoding/hex" - "errors" - "fmt" - "github.com/ginuerzh/gosocks5" - "github.com/gorilla/websocket" - "github.com/shadowsocks/shadowsocks-go/shadowsocks" - "io" - "io/ioutil" - "log" - "net" - "net/http" - "net/url" - "strconv" - "strings" - //"sync/atomic" -) - -var ( - sessionCount int64 - clientConfig = &gosocks5.Config{ - MethodSelected: clientMethodSelected, - } -) - -func listenAndServe(addr string, handler func(net.Conn)) error { - laddr, err := net.ResolveTCPAddr("tcp", addr) - if err != nil { - return err - } - - ln, err := net.ListenTCP("tcp", laddr) - if err != nil { - return err - } - defer ln.Close() - - for m, v := range Methods { - if Method == v { - clientConfig.Methods = []uint8{m} - } - } - - for { - conn, err := ln.AcceptTCP() - if err != nil { - log.Println("accept:", err) - continue - } - //log.Println("accept", conn.RemoteAddr()) - go handler(conn) - } -} - -func clientMethodSelected(method uint8, conn net.Conn) (net.Conn, error) { - switch method { - case gosocks5.MethodUserPass: - user, pass := parseUserPass(Password) - if err := clientSocksAuth(conn, user, pass); err != nil { - return nil, err - } - case MethodTLS, MethodTLSAuth: - conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) - if method == MethodTLSAuth { - if len(Password) == 0 { - return nil, ErrEmptyAuth - } - if err := clientSocksAuth(conn, "", Password); err != nil { - return nil, err - } - } - case MethodAES128, MethodAES192, MethodAES256, - MethodDES, MethodBF, MethodCAST5, MethodRC4MD5, MethodRC4, MethodTable: - cipher, err := shadowsocks.NewCipher(Methods[method], Password) - if err != nil { - log.Println(err) - return nil, err - } - conn = shadowsocks.NewConn(conn, cipher) - case gosocks5.MethodNoAcceptable: - return nil, gosocks5.ErrBadMethod - } - - return conn, nil -} - -func makeTunnel() (c net.Conn, err error) { - if UseTLS || UseWebsocket || !UseHttp { - c, err = connect(Saddr) - } else { - addr := Saddr - if proxyURL != nil { - addr = proxyURL.Host - } - c, err = dial(addr) - } - if err != nil { - return - } - - if UseTLS { - config := &tls.Config{InsecureSkipVerify: true} - c = tls.Client(c, config) - } else if UseWebsocket { - ws, resp, err := websocket.NewClient(c, &url.URL{Host: Saddr}, nil, 8192, 8192) - if err != nil { - c.Close() - return nil, err - } - resp.Body.Close() - - c = NewWSConn(ws) - } else if UseHttp { - httpcli := NewHttpClientConn(c) - if err = httpcli.Handshake(); err != nil { - c.Close() - return nil, err - } - c = httpcli - //defer httpcli.Close() - } - - sc := gosocks5.ClientConn(c, clientConfig) - if err = sc.Handleshake(); err != nil { - c.Close() - return nil, err - } - c = sc - - return -} - -func cliHandle(conn net.Conn) { - defer conn.Close() - - if Shadows { - cipher, _ := shadowsocks.NewCipher(SMethod, SPassword) - conn = shadowsocks.NewConn(conn, cipher) - handleShadow(conn) - return - } - - b := mpool.Take() - defer mpool.put(b) - - n, err := io.ReadAtLeast(conn, b, 2) - if err != nil { - 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 { - return - } - } - - methods := b[2 : 2+mn] - handleSocks5(conn, methods) - return - } - - req, err := http.ReadRequest(bufio.NewReader(newReqReader(b[:n], conn))) - if err != nil { - //log.Println(hex.Dump(b[:n])) - log.Println(err) - return - } - handleHttp(req, conn) -} - -func selectMethod(conn net.Conn, methods ...uint8) error { - m := gosocks5.MethodNoAuth - - if listenUrl.User != nil { - for _, method := range methods { - if method == gosocks5.MethodUserPass { - m = method - break - } - } - if m != gosocks5.MethodUserPass { - m = gosocks5.MethodNoAcceptable - } - } - if err := gosocks5.WriteMethod(m, conn); err != nil { - return err - } - - //log.Println(m) - - switch m { - case gosocks5.MethodUserPass: - var username, password string - - if listenUrl != nil && listenUrl.User != nil { - username = listenUrl.User.Username() - password, _ = listenUrl.User.Password() - } - - if err := serverSocksAuth(conn, username, password); err != nil { - return err - } - case gosocks5.MethodNoAcceptable: - return gosocks5.ErrBadMethod - } - - return nil -} - -func handleSocks5(conn net.Conn, methods []uint8) { - if err := selectMethod(conn, methods...); err != nil { - log.Println(err) - return - } - - req, err := gosocks5.ReadRequest(conn) - if err != nil { - return - } - - //log.Println(req) - sconn, err := makeTunnel() - if err != nil { - gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) - log.Println(err) - return - } - defer sconn.Close() - - switch req.Cmd { - case gosocks5.CmdConnect, gosocks5.CmdBind: - if err := req.Write(sconn); err != nil { - return - } - Transport(conn, sconn) - case gosocks5.CmdUdp: - if err := req.Write(sconn); err != nil { - return - } - rep, err := gosocks5.ReadReply(sconn) - if err != nil || rep.Rep != gosocks5.Succeeded { - return - } - - uconn, err := net.ListenUDP("udp", nil) - if err != nil { - log.Println(err) - gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) - return - } - defer uconn.Close() - - addr := ToSocksAddr(uconn.LocalAddr()) - addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) - log.Println("udp:", addr) - - rep = gosocks5.NewReply(gosocks5.Succeeded, addr) - if err := rep.Write(conn); err != nil { - log.Println(err) - return - } - - go cliTunnelUDP(uconn, sconn) - - // block, waiting for client exit - ioutil.ReadAll(conn) - } -} - -func cliTunnelUDP(uconn *net.UDPConn, sconn net.Conn) { - var raddr *net.UDPAddr - - go func() { - b := lpool.Take() - defer lpool.put(b) - - for { - n, addr, err := uconn.ReadFromUDP(b) - if err != nil { - log.Println(err) - return - } - raddr = addr - r := bytes.NewBuffer(b[:n]) - udp, err := gosocks5.ReadUDPDatagram(r) - if err != nil { - return - } - udp.Header.Rsv = uint16(len(udp.Data)) - //log.Println("r", raddr.String(), udp.Header) - - if err := udp.Write(sconn); err != nil { - log.Println(err) - return - } - } - }() - - for { - b := lpool.Take() - defer lpool.put(b) - - udp, err := gosocks5.ReadUDPDatagram(sconn) - if err != nil { - log.Println(err) - return - } - //log.Println("w", udp.Header) - udp.Header.Rsv = 0 - buf := bytes.NewBuffer(b[0:0]) - udp.Write(buf) - if _, err := uconn.WriteTo(buf.Bytes(), raddr); err != nil { - log.Println(err) - return - } - } -} - -func clientHttpAuth(req *http.Request, conn net.Conn, username, password string) error { - u, p, ok := proxyBasicAuth(req.Header.Get("Proxy-Authorization")) - req.Header.Del("Proxy-Authorization") - if !ok || - (len(username) > 0 && u != username) || - (len(password) > 0 && p != password) { - conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n" + - "Proxy-Authenticate: Basic realm=\"gost\"\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n")) - - return errors.New("Proxy Authentication Required") - } - - return nil -} - -func proxyBasicAuth(auth string) (username, password string, ok bool) { - if auth == "" { - return - } - - if !strings.HasPrefix(auth, "Basic ") { - return - } - c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic ")) - if err != nil { - return - } - cs := string(c) - s := strings.IndexByte(cs, ':') - if s < 0 { - return - } - - return cs[:s], cs[s+1:], true -} - -func handleHttp(req *http.Request, conn net.Conn) { - var host string - var port uint16 - - if listenUrl != nil && listenUrl.User != nil { - username := listenUrl.User.Username() - password, _ := listenUrl.User.Password() - - if err := clientHttpAuth(req, conn, username, password); err != nil { - log.Println(err) - return - } - } - - s := strings.Split(req.Host, ":") - host = s[0] - port = 80 - if len(s) == 2 { - n, _ := strconv.ParseUint(s[1], 10, 16) - port = uint16(n) - } - - addr := &gosocks5.Addr{ - Type: gosocks5.AddrDomain, - Host: host, - Port: port, - } - r := gosocks5.NewRequest(gosocks5.CmdConnect, addr) - - sconn, err := makeTunnel() - if err != nil { - conn.Write([]byte("HTTP/1.1 503 Service unavailable\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n")) - log.Println(err) - return - } - defer sconn.Close() - - if err := r.Write(sconn); err != nil { - return - } - rep, err := gosocks5.ReadReply(sconn) - if err != nil || rep.Rep != gosocks5.Succeeded { - conn.Write([]byte("HTTP/1.1 503 Service unavailable\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n")) - return - } - - if req.Method == "CONNECT" { - if _, err = conn.Write( - []byte("HTTP/1.1 200 Connection established\r\n" + - "Proxy-Agent: gost/" + Version + "\r\n\r\n")); err != nil { - return - } - } else { - if err := req.Write(sconn); err != nil { - return - } - } - - if err := Transport(conn, sconn); err != nil { - //log.Println(err) - } -} - -func handleShadow(conn net.Conn) { - addr, extra, err := getShadowRequest(conn) - if err != nil { - log.Println(err) - return - } - - sconn, err := makeTunnel() - if err != nil { - log.Println(err) - return - } - defer sconn.Close() - - req := gosocks5.NewRequest(gosocks5.CmdConnect, addr) - if err := req.Write(sconn); err != nil { - log.Println(err) - return - } - rep, err := gosocks5.ReadReply(sconn) - if err != nil || rep.Rep != gosocks5.Succeeded { - log.Println(err) - return - } - - if extra != nil { - if _, err := sconn.Write(extra); err != nil { - log.Println(err) - return - } - } - - if err := Transport(conn, sconn); err != nil { - //log.Println(err) - } -} - -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 := spool.Take() - defer spool.put(buf) - - 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 { - log.Println(err) - 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 { - log.Println(err) - 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 -} - -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 -} diff --git a/conn.go b/conn.go new file mode 100644 index 0000000..43620b8 --- /dev/null +++ b/conn.go @@ -0,0 +1,176 @@ +package main + +import ( + "bufio" + "crypto/tls" + "github.com/ginuerzh/gosocks5" + "github.com/golang/glog" + "io" + "net" + "net/http" +) + +func listenAndServe(arg Args) error { + var ln net.Listener + var err error + + switch arg.Transport { + case "ws": // websocket connection + err = NewWs(arg).ListenAndServe() + if err != nil { + if glog.V(LFATAL) { + glog.Errorln(err) + } + } + return err + case "tls": // tls connection + ln, err = tls.Listen("tcp", arg.Addr, + &tls.Config{Certificates: []tls.Certificate{arg.Cert}}) + default: + ln, err = net.Listen("tcp", arg.Addr) + } + + if err != nil { + if glog.V(LFATAL) { + glog.Errorln(err) + } + return err + } + defer ln.Close() + + for { + conn, err := ln.Accept() + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + continue + } + if glog.V(LINFO) { + glog.Infoln("accept", conn.RemoteAddr()) + } + go handleConn(conn, arg) + } + + return nil +} + +func handleConn(conn net.Conn, arg Args) { + defer conn.Close() + + selector := &serverSelector{ + methods: []uint8{ + gosocks5.MethodNoAuth, gosocks5.MethodUserPass, + MethodTLS, MethodTLSAuth, + }, + arg: arg, + } + + switch arg.Protocol { + case "ss": // shadowsocks + return + case "http": + req, err := http.ReadRequest(bufio.NewReader(conn)) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return + } + handleHttpRequest(req, conn, arg) + return + case "socks", "socks5": + conn = gosocks5.ServerConn(conn, selector) + req, err := gosocks5.ReadRequest(conn) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5:", err) + } + return + } + handleSocks5Request(req, conn, arg) + return + } + + // http + socks5 + + b := make([]byte, 16*1024) + + n, err := io.ReadAtLeast(conn, b, 2) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(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 { + if glog.V(LWARNING) { + glog.Warningln("socks5:", err) + } + return + } + } + methods := b[2 : 2+mn] + method := selector.Select(methods...) + if _, err := conn.Write([]byte{gosocks5.Ver5, method}); err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5:", err) + } + return + } + c, err := selector.OnSelected(method, conn) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5:", err) + } + return + } + conn = c + + req, err := gosocks5.ReadRequest(conn) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5:", err) + } + return + } + handleSocks5Request(req, conn, arg) + return + } + + req, err := http.ReadRequest(bufio.NewReader(newReqReader(b[:n], conn))) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(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 +} diff --git a/http.go b/http.go index 2f33035..4a09290 100644 --- a/http.go +++ b/http.go @@ -1,285 +1,70 @@ package main import ( - "bufio" - "bytes" - "code.google.com/p/go-uuid/uuid" - "errors" - "github.com/ginuerzh/gosocks5" - "io" - "io/ioutil" - "log" + "encoding/base64" + "github.com/golang/glog" "net" "net/http" - "net/url" - "time" + "net/http/httputil" + "strings" ) -const ( - s2cUri = "/s2c" - c2sUri = "/c2s" -) - -type HttpClientConn struct { - c net.Conn - token string - r io.ReadCloser -} - -func NewHttpClientConn(conn net.Conn) *HttpClientConn { - return &HttpClientConn{ - c: conn, - } -} - -func (conn *HttpClientConn) Handshake() (err error) { - //log.Println("remote", conn.c.RemoteAddr().String()) - req := &http.Request{ - Method: "GET", - URL: &url.URL{ - Host: Saddr, - Scheme: "http", - Path: s2cUri, - }, - Header: make(http.Header), - } - if proxyURL == nil { - err = req.Write(conn.c) - } else { - setBasicAuth(req) - err = req.WriteProxy(conn.c) - } - if err != nil { - return err - } - - resp, err := http.ReadResponse(bufio.NewReader(conn.c), req) - if err != nil { - return err - } - if resp.StatusCode != http.StatusOK { - return errors.New(resp.Status) - } - - b := make([]byte, 36) - if _, err = io.ReadFull(resp.Body, b); err != nil { - return err - } - if uuid.Parse(string(b)) == nil { - return errors.New("Handshake: wrong token") - } - conn.token = string(b) - conn.r = resp.Body - //log.Println(conn.token, "connected") - - return nil -} - -func (conn *HttpClientConn) Read(b []byte) (n int, err error) { - n, err = conn.r.Read(b) - //log.Println("http r:", n) - return -} - -func (conn *HttpClientConn) Write(b []byte) (n int, err error) { - q := url.Values{} - q.Set("token", conn.token) - req := &http.Request{ - Method: "POST", - Body: ioutil.NopCloser(bytes.NewReader(b)), - ContentLength: int64(len(b)), - URL: &url.URL{ - Host: Saddr, - Scheme: "http", - Path: c2sUri, - RawQuery: q.Encode(), - }, - Header: make(http.Header), - } - resp, err := doRequest(req) - if err != nil { - log.Println(err) - return - } - resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - //log.Println(resp.Status) - return 0, errors.New(resp.Status) - } - //log.Println("http w:", len(b)) - return len(b), nil -} - -func (conn *HttpClientConn) Close() error { - conn.Write(nil) - return conn.r.Close() -} - -func (conn *HttpClientConn) LocalAddr() net.Addr { - return conn.c.LocalAddr() -} - -func (conn *HttpClientConn) RemoteAddr() net.Addr { - return conn.c.RemoteAddr() -} - -func (conn *HttpClientConn) SetDeadline(t time.Time) error { - return conn.c.SetDeadline(t) -} - -func (conn *HttpClientConn) SetReadDeadline(t time.Time) error { - return conn.c.SetReadDeadline(t) -} - -func (conn *HttpClientConn) SetWriteDeadline(t time.Time) error { - return conn.c.SetWriteDeadline(t) -} - -type HttpServerConn struct { - w http.ResponseWriter - c chan []byte - closed bool - rb []byte -} - -func NewHttpServerConn(w http.ResponseWriter, c chan []byte) *HttpServerConn { - return &HttpServerConn{ - w: w, - c: c, - } -} - -func (conn *HttpServerConn) Read(b []byte) (n int, err error) { - if len(conn.rb) == 0 { - var ok bool - if conn.rb, ok = <-conn.c; !ok { - return 0, io.EOF - } - } - n = copy(b, conn.rb) - conn.rb = conn.rb[n:] - - //log.Println("http r:", n) - - return -} - -func (conn *HttpServerConn) Write(b []byte) (n int, err error) { - n, err = conn.w.Write(b) - if f, ok := conn.w.(http.Flusher); ok { - f.Flush() - } - //log.Println("http w:", n) - return -} - -func (conn *HttpServerConn) Close() error { - if !conn.closed { - close(conn.c) - conn.closed = true - } - return nil -} - -func (conn *HttpServerConn) LocalAddr() net.Addr { - return nil -} - -func (conn *HttpServerConn) RemoteAddr() net.Addr { - return nil -} - -func (conn *HttpServerConn) SetDeadline(t time.Time) error { - return nil -} - -func (conn *HttpServerConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (conn *HttpServerConn) SetWriteDeadline(t time.Time) error { - return nil -} - -type HttpServer struct { - Addr string - conns map[string]*HttpServerConn -} - -func (s *HttpServer) s2c(w http.ResponseWriter, r *http.Request) { - token := uuid.New() - ch := make(chan []byte, 8) - - conn := NewHttpServerConn(w, ch) - if _, err := conn.Write([]byte(token)); err != nil { - return - } - - s.conns[token] = conn - defer delete(s.conns, token) - - serveSocks5(gosocks5.ServerConn(conn, serverConfig)) -} - -func (s *HttpServer) c2s(w http.ResponseWriter, r *http.Request) { - defer func() { - if err := recover(); err != nil { - log.Println(err) - } - }() - - if r.Method != "POST" { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - token := r.FormValue("token") - conn := s.conns[token] - if conn == nil { - w.WriteHeader(http.StatusBadRequest) - return - } - b, err := ioutil.ReadAll(r.Body) - if err != nil || len(b) == 0 { - conn.Close() - delete(s.conns, token) - //log.Println(token, "disconnected") - return - } - conn.c <- b -} - -func (s *HttpServer) ListenAndServe() error { - s.conns = make(map[string]*HttpServerConn) - http.HandleFunc(s2cUri, s.s2c) - http.HandleFunc(c2sUri, s.c2s) - return http.ListenAndServe(s.Addr, nil) -} - -func doRequest(req *http.Request) (*http.Response, error) { - if proxyURL != nil { - c, err := dial(proxyURL.Host) +func handleHttpRequest(req *http.Request, conn net.Conn, arg Args) { + if glog.V(LDEBUG) { + dump, err := httputil.DumpRequest(req, false) if err != nil { - log.Println(err) - return nil, err + glog.Infoln(err) + } else { + glog.Infoln(string(dump)) } - defer c.Close() - - setBasicAuth(req) - if err := req.WriteProxy(c); err != nil { - log.Println(err) - return nil, err - } - /* - b, err := ioutil.ReadAll(c) - if err != nil { - log.Println(err) - return nil, err - } - */ - return http.ReadResponse(bufio.NewReader(c), req) } - return http.DefaultClient.Do(req) + var username, password string + if arg.User != nil { + username = arg.User.Username() + password, _ = arg.User.Password() + } + + u, p, _ := proxyBasicAuth(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 { + if glog.V(LWARNING) { + glog.Warningln(err) + } + } + if glog.V(LDEBUG) { + glog.Infoln(resp) + } + if glog.V(LWARNING) { + glog.Warningln("http: proxy authentication required") + } + return + } +} + +func proxyBasicAuth(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 } diff --git a/key.pem b/key.pem new file mode 100644 index 0000000..3683350 --- /dev/null +++ b/key.pem @@ -0,0 +1,27 @@ +-----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----- diff --git a/main.go b/main.go index 78c84aa..9bfa51f 100644 --- a/main.go +++ b/main.go @@ -3,75 +3,57 @@ package main import ( "flag" - "log" - "net/url" - "time" + "github.com/golang/glog" + "sync" +) + +const ( + LFATAL = iota + LERROR + LWARNING + LINFO + LDEBUG ) var ( - Laddr, Saddr, Proxy string - UseWebsocket, UseHttp, UseTLS bool - Shadows bool - SMethod, SPassword string - Method, Password string - CertFile, KeyFile string - PrintVersion bool + listenUrl, proxyUrl, forwardUrl string - proxyURL *url.URL - listenUrl *url.URL + listenArgs []Args + proxyArgs []Args + forwardArgs []Args ) func init() { - flag.StringVar(&Proxy, "P", "", "proxy for forward") - flag.StringVar(&Saddr, "S", "", "the server that connect to") - flag.StringVar(&Laddr, "L", ":8080", "listen address") - flag.StringVar(&Method, "m", "", "tunnel cipher method") - flag.StringVar(&Password, "p", "", "tunnel cipher password") - flag.StringVar(&CertFile, "cert", "", "tls cert file") - flag.StringVar(&KeyFile, "key", "", "tls key file") - flag.BoolVar(&Shadows, "ss", false, "run as shadowsocks server") - flag.BoolVar(&UseTLS, "tls", false, "use ssl/tls tunnel") - flag.BoolVar(&UseWebsocket, "ws", false, "use websocket tunnel") - flag.BoolVar(&UseHttp, "http", false, "use http tunnel") - flag.StringVar(&SMethod, "sm", "rc4-md5", "shadowsocks cipher method") - flag.StringVar(&SPassword, "sp", "ginuerzh@gmail.com", "shadowsocks cipher password") - flag.BoolVar(&PrintVersion, "v", false, "print version") + flag.StringVar(&listenUrl, "L", ":http", "local address") + flag.StringVar(&forwardUrl, "S", "", "remote address") + flag.StringVar(&proxyUrl, "P", "", "proxy address") + flag.Parse() - log.SetFlags(log.LstdFlags | log.Lshortfile) - - proxyURL, _ = parseURL(Proxy) - listenUrl, _ = parseURL(Laddr) + listenArgs = parseArgs(listenUrl) + proxyArgs = parseArgs(proxyUrl) + forwardArgs = parseArgs(forwardUrl) } -var ( - spool = NewMemPool(1024, 120*time.Minute, 1024) // 1k size buffer pool - mpool = NewMemPool(16*1024, 60*time.Minute, 512) // 16k size buffer pool - lpool = NewMemPool(32*1024, 30*time.Minute, 256) // 32k size buffer pool -) - func main() { - if PrintVersion { - printVersion() - return + defer glog.Flush() + + if len(listenArgs) == 0 { + glog.Fatalln("no listen addr") } - laddr := listenUrl.Host + var wg sync.WaitGroup - if len(Saddr) == 0 { - var server Server - if UseTLS { - server = &TlsServer{Addr: laddr, CertFile: CertFile, KeyFile: KeyFile} - } else if UseWebsocket { - server = &WSServer{Addr: laddr} - } else if UseHttp { - server = &HttpServer{Addr: laddr} - } else { - server = &Socks5Server{Addr: laddr} - } - log.Fatal(server.ListenAndServe()) - return + for _, arg := range listenArgs { + wg.Add(1) + go func() { + defer wg.Done() + if err := listenAndServe(arg); err != nil { + if glog.V(LFATAL) { + glog.Errorln(err) + } + } + }() } - - log.Fatal(listenAndServe(laddr, cliHandle)) + wg.Wait() } diff --git a/pool.go b/pool.go deleted file mode 100644 index 58493ec..0000000 --- a/pool.go +++ /dev/null @@ -1,108 +0,0 @@ -// pool for buffer -package main - -import ( - "container/list" - //"log" - "time" -) - -type poolItem struct { - when time.Time - item interface{} -} - -type pool struct { - quque *list.List - takeChan, putChan chan interface{} - age time.Duration - max int -} - -func (p *pool) run() { - for { - if p.size() == 0 { - select { - case b := <-p.putChan: - p.put(b) - } - continue - } - - i := p.quque.Front() - timeout := time.NewTimer(p.age) - - select { - case b := <-p.putChan: - timeout.Stop() - p.put(b) - case p.takeChan <- i.Value.(*poolItem).item: - timeout.Stop() - p.quque.Remove(i) - case <-timeout.C: - i = p.quque.Back() - for i != nil { - if time.Since(i.Value.(*poolItem).when) < p.age { - break - } - e := i.Prev() - p.quque.Remove(i) - i = e - } - } - } -} - -func (p *pool) size() int { - return p.quque.Len() -} - -func (p *pool) put(v interface{}) { - if p.size() < p.max { - p.quque.PushFront(&poolItem{when: time.Now(), item: v}) - return - } -} - -type MemPool struct { - pool - bs int -} - -func NewMemPool(bs int, age time.Duration, max int) *MemPool { - if bs <= 0 { - bs = 8192 - } - - if age == 0 { - age = 1 * time.Minute - } - - p := &MemPool{ - pool: pool{ - quque: list.New(), - takeChan: make(chan interface{}), - putChan: make(chan interface{}), - age: age, - max: max, - }, - bs: bs, - } - - go p.run() - - return p -} - -func (p *MemPool) Take() []byte { - select { - case v := <-p.takeChan: - return v.([]byte) - default: - return make([]byte, p.bs) - } -} - -func (p *MemPool) Put(b []byte) { - p.putChan <- b -} diff --git a/server.go b/server.go deleted file mode 100644 index c999d17..0000000 --- a/server.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -type Server interface { - ListenAndServe() error -} - diff --git a/socks.go b/socks.go new file mode 100644 index 0000000..ef7094c --- /dev/null +++ b/socks.go @@ -0,0 +1,400 @@ +package main + +import ( + "crypto/tls" + "github.com/ginuerzh/gosocks5" + "github.com/golang/glog" + "net" + "strconv" +) + +const ( + MethodTLS uint8 = 0x80 // extended method for tls + MethodTLSAuth uint8 = 0x82 // extended method for tls+auth +) + +type clientSelector struct { + arg Args +} + +func (selector *clientSelector) Methods() []uint8 { + return nil +} + +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.arg.User != nil { + username = selector.arg.User.Username() + password, _ = selector.arg.User.Password() + } + + req := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, username, password) + if err := req.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return nil, err + } + if glog.V(LDEBUG) { + glog.Infoln(req) + } + + res, err := gosocks5.ReadUserPassResponse(conn) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return nil, err + } + if glog.V(LDEBUG) { + glog.Infoln(res) + } + + if res.Status != gosocks5.Succeeded { + return nil, gosocks5.ErrAuthFailure + } + case gosocks5.MethodNoAcceptable: + return nil, gosocks5.ErrBadMethod + } + + return conn, nil +} + +type serverSelector struct { + methods []uint8 + arg Args +} + +func (selector *serverSelector) Methods() []uint8 { + return selector.methods +} + +func (selector *serverSelector) Select(methods ...uint8) (method uint8) { + if glog.V(LDEBUG) { + glog.Infof("%x %x % x", gosocks5.Ver5, len(methods), methods) + } + + method = gosocks5.MethodNoAcceptable + + for _, m := range methods { + for _, mm := range selector.methods { + if m == mm { + method = m + break + } + } + } + + if method == gosocks5.MethodNoAcceptable { + return + } + // when user/pass is set, auth is mandatory + if selector.arg.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) { + if glog.V(LDEBUG) { + glog.Infof("%x %x", gosocks5.Ver5, method) + } + + switch method { + case MethodTLS: + conn = tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{selector.arg.Cert}}) + + case gosocks5.MethodUserPass, MethodTLSAuth: + if method == MethodTLSAuth { + conn = tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{selector.arg.Cert}}) + } + + req, err := gosocks5.ReadUserPassRequest(conn) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return nil, err + } + if glog.V(LDEBUG) { + glog.Infoln(req) + } + + var username, password string + if selector.arg.User != nil { + username = selector.arg.User.Username() + password, _ = selector.arg.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 { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return nil, err + } + if glog.V(LDEBUG) { + glog.Infoln(resp) + } + return nil, gosocks5.ErrAuthFailure + } + + resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Succeeded) + if err := resp.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return nil, err + } + if glog.V(LDEBUG) { + glog.Infoln(resp) + } + + case gosocks5.MethodNoAcceptable: + return nil, gosocks5.ErrBadMethod + } + + return conn, nil +} + +func handleSocks5Request(req *gosocks5.Request, conn net.Conn, arg Args) { + if glog.V(LDEBUG) { + glog.Infoln(req) + } + + switch req.Cmd { + case gosocks5.CmdConnect: + if glog.V(LINFO) { + glog.Infoln("socks5 connect:", req.Addr.String()) + } + tconn, err := connect(req.Addr.String()) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 connect:", err) + } + rep := gosocks5.NewReply(gosocks5.HostUnreachable, nil) + if err := rep.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 connect:", err) + } + } else { + if glog.V(LDEBUG) { + glog.Infoln(rep) + } + } + return + } + defer tconn.Close() + + rep := gosocks5.NewReply(gosocks5.Succeeded, nil) + if err := rep.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 connect:", err) + } + return + } else { + if glog.V(LDEBUG) { + glog.Infoln(rep) + } + } + + if err := Transport(conn, tconn); err != nil { + //log.Println(err) + } + case gosocks5.CmdBind: + l, err := net.ListenTCP("tcp", nil) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 bind listen:", err) + } + rep := gosocks5.NewReply(gosocks5.Failure, nil) + if err := rep.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 bind listen:", err) + } + } else { + if glog.V(LDEBUG) { + glog.Infoln(rep) + } + } + return + } + + addr := ToSocksAddr(l.Addr()) + addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) + if glog.V(LINFO) { + glog.Infoln("socks5 bind:", addr) + } + rep := gosocks5.NewReply(gosocks5.Succeeded, addr) + if err := rep.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 bind:", err) + } + l.Close() + return + } else { + if glog.V(LDEBUG) { + glog.Infoln(rep) + } + } + + tconn, err := l.AcceptTCP() + l.Close() // only accept one peer + if err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 bind accept:", err) + } + rep = gosocks5.NewReply(gosocks5.Failure, nil) + if err := rep.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 bind accept:", err) + } + } else { + if glog.V(LDEBUG) { + glog.Infoln(rep) + } + } + return + } + defer tconn.Close() + + addr = ToSocksAddr(tconn.RemoteAddr()) + if glog.V(LINFO) { + glog.Infoln("socks5 bind accept:", addr.String()) + } + rep = gosocks5.NewReply(gosocks5.Succeeded, addr) + if err := rep.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 bind accept:", err) + } + return + } else { + if glog.V(LDEBUG) { + glog.Infoln(rep) + } + } + + if err := Transport(conn, tconn); err != nil { + //log.Println(err) + } + case gosocks5.CmdUdp: + uconn, err := net.ListenUDP("udp", nil) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 udp listen:", err) + } + rep := gosocks5.NewReply(gosocks5.Failure, nil) + if err := rep.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 udp listen:", err) + } + } else { + if glog.V(LDEBUG) { + glog.Infoln(rep) + } + } + return + } + defer uconn.Close() + + addr := ToSocksAddr(uconn.LocalAddr()) + addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) + if glog.V(LINFO) { + glog.Infoln("socks5 udp:", addr) + } + rep := gosocks5.NewReply(gosocks5.Succeeded, addr) + if err := rep.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln("socks5 udp:", err) + } + return + } else { + if glog.V(LDEBUG) { + glog.Infoln(rep) + } + } + srvTunnelUDP(conn, uconn) + } +} + +func srvTunnelUDP(conn net.Conn, uconn *net.UDPConn) { + go func() { + b := make([]byte, 16*1024) + + for { + n, addr, err := uconn.ReadFromUDP(b) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return + } + + udp := gosocks5.NewUDPDatagram( + gosocks5.NewUDPHeader(uint16(n), 0, ToSocksAddr(addr)), b[:n]) + //log.Println("r", udp.Header) + if err := udp.Write(conn); err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return + } + } + }() + + for { + udp, err := gosocks5.ReadUDPDatagram(conn) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return + } + //log.Println("w", udp.Header) + addr, err := net.ResolveUDPAddr("udp", udp.Header.Addr.String()) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + continue // drop silently + } + + if _, err := uconn.WriteToUDP(udp.Data, addr); err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + return + } + } +} + +func ToSocksAddr(addr net.Addr) *gosocks5.Addr { + host, port, _ := net.SplitHostPort(addr.String()) + p, _ := strconv.Atoi(port) + + return &gosocks5.Addr{ + Type: gosocks5.AddrIPv4, + Host: host, + Port: uint16(p), + } +} diff --git a/socks5.go b/socks5.go deleted file mode 100644 index 37d1094..0000000 --- a/socks5.go +++ /dev/null @@ -1,296 +0,0 @@ -package main - -import ( - "github.com/ginuerzh/gosocks5" - "github.com/shadowsocks/shadowsocks-go/shadowsocks" - "net" - //"strconv" - "crypto/tls" - "log" -) - -const ( - rawCert = `-----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-----` - - rawKey = `-----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-----` -) - -var ( - serverConfig = &gosocks5.Config{ - SelectMethod: serverSelectMethod, - MethodSelected: serverMethodSelected, - } -) - -type Socks5Server struct { - Addr string // TCP address to listen on -} - -func (s *Socks5Server) ListenAndServe() error { - addr, err := net.ResolveTCPAddr("tcp", s.Addr) - if err != nil { - return err - } - - ln, err := net.ListenTCP("tcp", addr) - if err != nil { - return err - } - defer ln.Close() - - for { - conn, err := ln.AcceptTCP() - if err != nil { - log.Println("accept:", err) - continue - } - //log.Println("accept", conn.RemoteAddr()) - - go serveSocks5(gosocks5.ServerConn(conn, serverConfig)) - } -} - -func serverSelectMethod(methods ...uint8) uint8 { - //log.Println(methods) - m := gosocks5.MethodNoAuth - - for _, method := range methods { - if _, ok := Methods[method]; ok { - m = method - } - } - - // when user/pass is set for proxy auth, the NoAuth method is disabled - if len(Method) == 0 && m == gosocks5.MethodNoAuth && listenUrl.User != nil { - return gosocks5.MethodNoAcceptable - } - - if len(Method) == 0 || Methods[m] == Method { - return m - } - - return gosocks5.MethodNoAcceptable -} - -func serverMethodSelected(method uint8, conn net.Conn) (net.Conn, error) { - //log.Println(method) - switch method { - case gosocks5.MethodUserPass: - var username, password string - - if listenUrl != nil && listenUrl.User != nil { - username = listenUrl.User.Username() - password, _ = listenUrl.User.Password() - } - - if err := serverSocksAuth(conn, username, password); err != nil { - return nil, err - } - case MethodTLS, MethodTLSAuth: - var cert tls.Certificate - var err error - - if len(CertFile) == 0 || len(KeyFile) == 0 { - cert, err = tls.X509KeyPair([]byte(rawCert), []byte(rawKey)) - } else { - cert, err = tls.LoadX509KeyPair(CertFile, KeyFile) - } - - if err != nil { - return nil, err - } - conn = tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{cert}}) - if method == MethodTLSAuth { - // password is mandatory - if len(Password) == 0 { - return nil, ErrEmptyAuth - } - if err := serverSocksAuth(conn, "", Password); err != nil { - return nil, err - } - } - case MethodAES128, MethodAES192, MethodAES256, - MethodDES, MethodBF, MethodCAST5, MethodRC4MD5, MethodRC4, MethodTable: - cipher, err := shadowsocks.NewCipher(Methods[method], Password) - if err != nil { - return nil, err - } - conn = shadowsocks.NewConn(conn, cipher) - case gosocks5.MethodNoAcceptable: - return nil, gosocks5.ErrBadMethod - } - - return conn, nil -} - -func serveSocks5(conn net.Conn) { - defer conn.Close() - - req, err := gosocks5.ReadRequest(conn) - if err != nil { - log.Println(err) - return - } - - switch req.Cmd { - case gosocks5.CmdConnect: - //log.Println("connect", req.Addr.String()) - tconn, err := connect(req.Addr.String()) - if err != nil { - log.Println("connect", req.Addr.String(), err) - gosocks5.NewReply(gosocks5.HostUnreachable, nil).Write(conn) - return - } - defer tconn.Close() - - rep := gosocks5.NewReply(gosocks5.Succeeded, nil) - if err := rep.Write(conn); err != nil { - return - } - - if err := Transport(conn, tconn); err != nil { - //log.Println(err) - } - case gosocks5.CmdBind: - l, err := net.ListenTCP("tcp", nil) - if err != nil { - gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) - log.Println("bind listen", err) - return - } - - addr := ToSocksAddr(l.Addr()) - addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) - log.Println("bind:", addr) - rep := gosocks5.NewReply(gosocks5.Succeeded, addr) - if err := rep.Write(conn); err != nil { - log.Println(err) - l.Close() - return - } - - tconn, err := l.AcceptTCP() - l.Close() // only accept one peer - if err != nil { - log.Println("accept:", err) - gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) - return - } - defer tconn.Close() - - addr = ToSocksAddr(tconn.RemoteAddr()) - log.Println("accept peer:", addr.String()) - rep = gosocks5.NewReply(gosocks5.Succeeded, addr) - if err := rep.Write(conn); err != nil { - log.Println(err) - return - } - - if err := Transport(conn, tconn); err != nil { - //log.Println(err) - } - case gosocks5.CmdUdp: - uconn, err := net.ListenUDP("udp", nil) - if err != nil { - log.Println("udp listen", err) - gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) - return - } - defer uconn.Close() - - addr := ToSocksAddr(uconn.LocalAddr()) - addr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) - log.Println("udp:", addr) - rep := gosocks5.NewReply(gosocks5.Succeeded, addr) - if err := rep.Write(conn); err != nil { - log.Println(err) - return - } - srvTunnelUDP(conn, uconn) - } -} - -func srvTunnelUDP(conn net.Conn, uconn *net.UDPConn) { - go func() { - b := lpool.Take() - defer lpool.put(b) - - for { - n, addr, err := uconn.ReadFromUDP(b) - if err != nil { - log.Println(err) - return - } - - udp := gosocks5.NewUDPDatagram( - gosocks5.NewUDPHeader(uint16(n), 0, ToSocksAddr(addr)), b[:n]) - //log.Println("r", udp.Header) - if err := udp.Write(conn); err != nil { - log.Println(err) - return - } - } - }() - - for { - udp, err := gosocks5.ReadUDPDatagram(conn) - if err != nil { - log.Println(err) - return - } - //log.Println("w", udp.Header) - addr, err := net.ResolveUDPAddr("udp", udp.Header.Addr.String()) - if err != nil { - log.Println(err) - continue // drop silently - } - - if _, err := uconn.WriteToUDP(udp.Data, addr); err != nil { - log.Println(err) - return - } - } -} diff --git a/tls.go b/tls.go deleted file mode 100644 index 6864633..0000000 --- a/tls.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "crypto/tls" - "github.com/ginuerzh/gosocks5" - "net" -) - -type TlsServer struct { - Addr string - CertFile, KeyFile string -} - -func (s *TlsServer) ListenAndServe() error { - return s.listenAndServeTLS() -} - -func (s *TlsServer) listenAndServeTLS() error { - var cert tls.Certificate - var err error - - if len(s.CertFile) == 0 || len(s.KeyFile) == 0 { - cert, err = tls.X509KeyPair([]byte(rawCert), []byte(rawKey)) - } else { - cert, err = tls.LoadX509KeyPair(s.CertFile, s.KeyFile) - } - if err != nil { - return err - } - - config := &tls.Config{Certificates: []tls.Certificate{cert}} - l, err := tls.Listen("tcp", s.Addr, config) - if err != nil { - return err - } - defer l.Close() - - for { - conn, err := l.Accept() - if err != nil { - return err - } - - go func(c net.Conn) { - c = gosocks5.ServerConn(c, serverConfig) - serveSocks5(c) - }(conn) - } - - return nil -} diff --git a/util.go b/util.go index 0cf5125..c388ed6 100644 --- a/util.go +++ b/util.go @@ -1,262 +1,123 @@ package main import ( - "bufio" - //"bytes" - "encoding/base64" + "crypto/tls" "errors" - "github.com/ginuerzh/gosocks5" + "fmt" + "github.com/golang/glog" "io" - //"log" "net" - "net/http" "net/url" - "strconv" "strings" ) -const ( - MethodTLS uint8 = 0x80 + iota - MethodAES128 - MethodAES192 - MethodAES256 - MethodDES - MethodBF - MethodCAST5 - MethodRC4MD5 - MethodRC4 - MethodTable - MethodTLSAuth -) - -var ErrEmptyAuth = errors.New("empty auth") - -var Methods = map[uint8]string{ - //gosocks5.MethodNoAuth: "", // 0x00 - gosocks5.MethodUserPass: "userpass", // 0x02 - MethodTLS: "tls", // 0x80 - MethodAES128: "aes-128-cfb", // 0x81 - MethodAES192: "aes-192-cfb", // 0x82 - MethodAES256: "aes-256-cfb", // 0x83 - MethodDES: "des-cfb", // 0x84 - MethodBF: "bf-cfb", // 0x85 - MethodCAST5: "cast5-cfb", // 0x86 - MethodRC4MD5: "rc4-md5", // 8x87 - MethodRC4: "rc4", // 0x88 - MethodTable: "table", // 0x89 - MethodTLSAuth: "tls-auth", // 0x90 +// socks://admin:123456@localhost:8080 +type Args struct { + Addr string // host:port + Protocol string // protocol: hs/http/socks/socks5/ss, default is hs(http+socks5) + Transport string // transport: tcp/ws/tls, default is tcp(raw tcp) + User *url.Userinfo + EncMeth string // data encryption method + EncPass string // data encryption password + Cert tls.Certificate // tls certificate } -func parseURL(rawurl string) (*url.URL, error) { - if len(rawurl) == 0 { - return nil, nil +func (args Args) String() string { + var authUser, authPass string + if args.User != nil { + authUser = args.User.Username() + authPass, _ = args.User.Password() } - if !strings.HasPrefix(rawurl, "http://") && - !strings.HasPrefix(rawurl, "socks://") { - rawurl = "http://" + rawurl - } - return url.Parse(rawurl) + return fmt.Sprintf("host: %s, proto: %s, trans: %s, auth: %s:%s, enc: %s:%s", + args.Addr, args.Protocol, args.Transport, authUser, authPass, + args.EncMeth, args.EncPass) } -func parseUserPass(key string) (username string, password string) { - sep := ":" - i := strings.Index(key, sep) - if i < 0 { - return key, "" +func parseArgs(rawurl string) (args []Args) { + ss := strings.Split(rawurl, ",") + if rawurl == "" || len(ss) == 0 { + return nil } - return key[0:i], key[i+len(sep):] -} -func ToSocksAddr(addr net.Addr) *gosocks5.Addr { - host, port, _ := net.SplitHostPort(addr.String()) - p, _ := strconv.Atoi(port) + for _, s := range ss { + if !strings.Contains(s, "://") { + s = "hs://" + s + } + u, err := url.Parse(s) + if err != nil { + if glog.V(LWARNING) { + glog.Warningln(err) + } + continue + } - return &gosocks5.Addr{ - Type: gosocks5.AddrIPv4, - Host: host, - Port: uint16(p), + arg := Args{ + Addr: u.Host, + User: u.User, + } + + schemes := strings.Split(u.Scheme, "+") + if len(schemes) == 1 { + switch schemes[0] { + case "http", "socks", "socks5", "ss": + arg.Protocol = schemes[0] + case "ws", "tls", "tcp": + arg.Transport = schemes[0] + } + } + if len(schemes) == 2 { + arg.Protocol = schemes[0] + arg.Transport = schemes[1] + } + + arg.Cert, err = tls.LoadX509KeyPair("cert.pem", "key.pem") + if err != nil { + if glog.V(LFATAL) { + glog.Errorln(err, ", tls will not be supported") + } + } + + mp := strings.Split(strings.Trim(u.Path, "/"), ":") + if len(mp) == 1 { + arg.EncMeth = mp[0] + } + if len(mp) == 2 { + arg.EncMeth = mp[0] + arg.EncPass = mp[1] + } + if glog.V(LINFO) { + glog.Infoln(arg) + } + args = append(args, arg) } -} -func dial(addr string) (net.Conn, error) { - taddr, err := net.ResolveTCPAddr("tcp", addr) - if err != nil { - return nil, err - } - return net.DialTCP("tcp", nil, taddr) + return } func connect(addr string) (net.Conn, error) { if !strings.Contains(addr, ":") { addr += ":80" } - if proxyURL == nil { - return dial(addr) - } - - switch proxyURL.Scheme { - case "socks": // socks5 proxy - return connectSocks5Proxy(addr) - case "http": // http proxy - fallthrough - default: - return connectHTTPProxy(addr) - } - -} - -func connectHTTPProxy(addr string) (conn net.Conn, err error) { - conn, err = dial(proxyURL.Host) - if err != nil { - return - } - - req := &http.Request{ - Method: "CONNECT", - URL: &url.URL{Host: addr}, - Host: addr, - Header: make(http.Header), - } - req.Header.Set("Proxy-Connection", "keep-alive") - setBasicAuth(req) - - if err = req.Write(conn); err != nil { - conn.Close() - return - } - - resp, err := http.ReadResponse(bufio.NewReader(conn), req) - if err != nil { - conn.Close() - return - } - if resp.StatusCode != http.StatusOK { - conn.Close() - //log.Println(resp.Status) - return nil, errors.New(resp.Status) - } - return -} - -func connectSocks5Proxy(addr string) (conn net.Conn, err error) { - conn, err = dial(proxyURL.Host) - if err != nil { - return - } - - conf := &gosocks5.Config{ - // Methods: []uint8{gosocks5.MethodNoAuth, gosocks5.MethodUserPass}, - MethodSelected: proxyMethodSelected, - } - if proxyURL.User != nil { - conf.Methods = []uint8{gosocks5.MethodUserPass} - } - - c := gosocks5.ClientConn(conn, conf) - if err := c.Handleshake(); err != nil { - conn.Close() - return nil, err - } - conn = c - - s := strings.Split(addr, ":") - host := s[0] - port := 80 - if len(s) == 2 { - n, _ := strconv.ParseUint(s[1], 10, 16) - port = int(n) - } - a := &gosocks5.Addr{ - Type: gosocks5.AddrDomain, - Host: host, - Port: uint16(port), - } - if err := gosocks5.NewRequest(gosocks5.CmdConnect, a).Write(conn); err != nil { - conn.Close() - return nil, err - } - rep, err := gosocks5.ReadReply(conn) - if err != nil { - conn.Close() - return nil, err - } - if rep.Rep != gosocks5.Succeeded { - conn.Close() - return nil, errors.New("Socks Failture") - } - - return conn, nil -} - -func proxyMethodSelected(method uint8, conn net.Conn) (net.Conn, error) { - switch method { - case gosocks5.MethodUserPass: - var user, pass string - - if proxyURL != nil && proxyURL.User != nil { - user = proxyURL.User.Username() - pass, _ = proxyURL.User.Password() + /* + if proxyURL == nil { + return dial(addr) } - if err := clientSocksAuth(conn, user, pass); err != nil { - return nil, err + + switch proxyURL.Scheme { + case "socks": // socks5 proxy + return connectSocks5Proxy(addr) + case "http": // http proxy + fallthrough + default: + return connectHTTPProxy(addr) } - case gosocks5.MethodNoAcceptable: - return nil, gosocks5.ErrBadMethod - } - - return conn, nil -} - -func clientSocksAuth(conn net.Conn, username, password string) error { - if err := gosocks5.NewUserPassRequest( - gosocks5.UserPassVer, username, password).Write(conn); err != nil { - return err - } - res, err := gosocks5.ReadUserPassResponse(conn) - if err != nil { - return err - } - if res.Status != gosocks5.Succeeded { - return gosocks5.ErrAuthFailure - } - - return nil -} - -func serverSocksAuth(conn net.Conn, username, password string) error { - req, err := gosocks5.ReadUserPassRequest(conn) - if err != nil { - return err - } - - if (len(username) > 0 && req.Username != username) || - (len(password) > 0 && req.Password != password) { - if err := gosocks5.NewUserPassResponse( - gosocks5.UserPassVer, gosocks5.Failure).Write(conn); err != nil { - return err - } - return gosocks5.ErrAuthFailure - } - - if err := gosocks5.NewUserPassResponse( - gosocks5.UserPassVer, gosocks5.Succeeded).Write(conn); err != nil { - return err - } - - return nil -} - -func setBasicAuth(r *http.Request) { - if proxyURL != nil && proxyURL.User != nil { - r.Header.Set("Proxy-Authorization", - "Basic "+base64.StdEncoding.EncodeToString([]byte(proxyURL.User.String()))) - } + */ + return nil, errors.New("not implemented") } // based on io.Copy func Copy(dst io.Writer, src io.Reader) (written int64, err error) { - buf := lpool.Take() - defer lpool.put(buf) + buf := make([]byte, 32*1024) for { nr, er := src.Read(buf) diff --git a/version.go b/version.go index bb98490..f16a175 100644 --- a/version.go +++ b/version.go @@ -5,7 +5,7 @@ import ( ) const ( - Version = "1.8" + Version = "2.0" ) func printVersion() { diff --git a/ws.go b/ws.go index c2db6ec..cffd357 100644 --- a/ws.go +++ b/ws.go @@ -1,82 +1,119 @@ package main import ( - "github.com/ginuerzh/gosocks5" + //"github.com/ginuerzh/gosocks5" + "github.com/golang/glog" "github.com/gorilla/websocket" - "log" + "net" "net/http" + "net/http/httputil" + "net/url" "time" ) -type WSConn struct { - *websocket.Conn - rb []byte +type wsConn struct { + conn *websocket.Conn + rb []byte } -func NewWSConn(conn *websocket.Conn) *WSConn { - c := &WSConn{ - Conn: conn, +func wsClient(conn net.Conn, host string) (*wsConn, error) { + c, resp, err := websocket.NewClient(conn, &url.URL{Host: host, Path: "/ws"}, nil, 1024, 1024) + if err != nil { + return nil, err } + resp.Body.Close() - return c + return &wsConn{conn: c}, nil } -func (conn *WSConn) Read(b []byte) (n int, err error) { - if len(conn.rb) == 0 { - _, conn.rb, err = conn.ReadMessage() +func wsServer(conn *websocket.Conn) *wsConn { + return &wsConn{ + conn: conn, } - n = copy(b, conn.rb) - conn.rb = conn.rb[n:] +} + +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 (conn *WSConn) Write(b []byte) (n int, err error) { - err = conn.WriteMessage(websocket.BinaryMessage, b) +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 (conn *WSConn) SetDeadline(t time.Time) error { +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) } - -type WSServer struct { - Addr string +func (c *wsConn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) } -var upgrader = websocket.Upgrader{ - ReadBufferSize: 8192, - WriteBufferSize: 8192, - CheckOrigin: func(r *http.Request) bool { return true }, +func (c *wsConn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) } -func (s *WSServer) handle(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) +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) { + if glog.V(LDEBUG) { + dump, err := httputil.DumpRequest(r, false) + if err != nil { + glog.Infoln(err) + } else { + glog.Infoln(string(dump)) + } + } + conn, err := s.upgrader.Upgrade(w, r, nil) if err != nil { - log.Println(err) + if glog.V(LERROR) { + glog.Errorln(err) + } return } - //defer conn.Close() - - c := gosocks5.ServerConn(NewWSConn(conn), serverConfig) - /* - if err := c.Handleshake(); err != nil { - log.Println(err) - return - } - */ - serveSocks5(c) + handleConn(wsServer(conn), s.arg) } -func (s *WSServer) ListenAndServe() error { - http.HandleFunc("/", s.handle) - return http.ListenAndServe(s.Addr, nil) +func (s *ws) ListenAndServe() error { + http.HandleFunc("/ws", s.handle) + return http.ListenAndServe(s.arg.Addr, nil) }