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) }