diff --git a/README.md b/README.md index c12a59c..12efa48 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,40 @@ gost - GO Simple Tunnel ==== -###GO语言实现的安全隧道 +### GO语言实现的安全隧道 -####特性 -1. 支持设置上层http代理。 +#### 特性 +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://bintray.com/ginuerzh/gost/gost/v1.3/view +二进制文件下载:https://bintray.com/ginuerzh/gost/gost/view -Google讨论组: https://groups.google.com/forum/#!forum/go-gost +Google讨论组: https://groups.google.com/d/forum/go-gost -####版本更新 -#####V1.4 -* 支持http tunnel (-http参数),使用http协议来传输数据(注:效率低,非特殊情况下,不推荐使用)。 +#### 版本更新 +##### v1.5 +* 支持设置上层socks5代理(注: http tunnel不支持) +* 支持上层代理用户名密码验证 -#####v1.3 -* tls加密方式增加密码认证功能(与旧版本不兼容) -* 增加版本查看(-v参数) -* -p参数的默认值修改为空 +##### V1.4 +* 支持http tunnel(-http参数),使用http协议来传输数据(注: 效率低,非特殊情况下,不推荐使用)。 -#####v1.2 -* websocket tunnel增加加密功能。 +##### v1.3 +* tls加密方式增加密码认证功能(与旧版本不兼容) +* 增加版本查看(-v参数) +* -p参数的默认值修改为空 -#####v1.1 -* 支持websocket tunnel (-ws参数),使用websocket协议来传输数据。 +##### v1.2 +* websocket tunnel增加加密功能。 -####参数说明 +##### v1.1 +* 支持websocket tunnel(-ws参数),使用websocket协议来传输数据。 + +#### 参数说明 > -L=":8080": listen address > -P="": proxy for forward @@ -58,65 +62,57 @@ Google讨论组: https://groups.google.com/forum/#!forum/go-gost > -v=false: print version -####使用方法 -#####服务器端: -`gost -L=:8080` +#### 使用方法 +##### 基本用法 +* 客户端: `gost -L=:8899 -S=server_ip:8080` +* 服务器: `gost -L=:8080` -#####服务器端设置加密: -`gost -L=:8080 -m=rc4-md5 -p=123456` +##### 设置加密 +* 客户端: `gost -L=:8899 -S=server_ip:8080 -m=rc4-md5 -p=123456` +* 服务器: `gost -L=:8080 -m=rc4-md5 -p=123456` -#####服务器端有上层http代理: -`gost -L=:8080 -m=rc4-md5 -p=123456 -P=proxy_ip:port` +##### 设置上层代理 +* 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` -#####客户端: -`gost -L=:8899 -S=your_server_ip:8080` +##### 使用websocket tunnel +* 客户端: `gost -L=:8899 -S=server_ip:8080 -ws` +* 服务器: `gost -L=:8080 -ws` -#####客户端设置加密: -`gost -L=:8899 -S=your_server_ip:8080 -m=rc4-md5 -p=123456` +##### 使用http tunnel +* 客户端: `gost -L=:8899 -S=server_ip:8080 -http` +* 服务器: `gost -L=:8080 -http` -#####客户端有上层http代理: -`gost -L=:8899 -S=your_server_ip:8080 -m=rc4-md5 -p=123456 -P=proxy_ip:port` +注:websocket方式优先级高于http方式,即当-ws与-http参数同时存在时,-http参数无效。 -#####使用websocket tunnel -* 服务器端 -`gost -L=:8080 -m=aes-256-cfb -p=123456 -ws` -* 客户端 -`gost -L=:8899 -S=your_server_ip:8080 -m=rc4-md5 -p=123456 -ws` - -#####使用http tunnel -* 服务器端 -`gost -L=:8080 -m=rc4-md5 -p=123456 -http` -* 客户端 -`gost -L=:8899 -S=your_server_ip:8080 -m=rc4-md5 -p=123456 -http` - -注:websocket方式优先级高于http方式,即当-ws与-http参数同时存在时,-http参数默认无效。 - -#####作为shadowsocks服务器: +##### 作为shadowsocks服务器 gost支持作为shadowsocks服务器运行(-ss参数),这样就可以让android手机通过shadowsocks客户端(影梭)使用代理了。 -######相关参数: -> -ss 开启shadowsocks模式 +###### 相关参数 +> -ss 开启shadowsocks模式 -> -sm 设置shadowsocks加密方式(默认为rc4-md5) +> -sm 设置shadowsocks加密方式(默认为rc4-md5) -> -sp 设置shadowsocks加密密码(默认为ginuerzh@gmail.com) +> -sp 设置shadowsocks加密密码(默认为ginuerzh@gmail.com) 当无-ss参数时,-sm, -sp参数无效。以上三个参数对服务端无效。 -######相关命令: -* 服务端:无需特殊设置,shadowsocks模式只与客户端有关,与服务端无关。 -* 客户端:`gost -L :8899 -S demo-project-gostwebsocket.c9.io -sm=rc4-md5 -sp=ginuerzh@gmail.com -ss` +###### 相关命令 +* 客户端: `gost -L :8899 -S server_ip:port -sm=rc4-md5 -sp=ginuerzh@gmail.com -ss` +* 服务器: 无需特殊设置,shadowsocks模式只与客户端有关,与服务端无关。 -在手机的shadowsocks软件中设置好服务器(运行gost电脑的IP),端口(8899),加密方法和密码就可以使用了。 +在手机的shadowsocks软件中设置好服务器IP(运行gost客户端电脑的IP),端口(8899),加密方法和密码就可以使用了。 注:shadowsocks模式与正常模式是不兼容的,当作为shadowsocks模式使用时(有-ss参数),浏览器不能使用。 -####tunnel加密说明 -#####目前支持的加密方法 +#### tunnel加密说明 +##### 目前支持的加密方法 tls, aes-128-cfb, aes-192-cfb, aes-256-cfb, des-cfb, bf-cfb, cast5-cfb, rc4-md5, rc4, table -#####Client +##### Client Client端通过-m参数设置加密方式,默认为不加密(-m参数为空)。 @@ -126,7 +122,7 @@ Client端通过-m参数设置加密方式,默认为不加密(-m参数为空) 当设置的加密方式为非tls时,通过-p参数设置加密密码,且不能为空;-p参数必须与Server端的-p参数相同。 -#####Server +##### Server Server端通过-m参数设置加密方式,默认为不加密(-m参数为空)。 diff --git a/client.go b/client.go index a38a6b1..98d7e02 100644 --- a/client.go +++ b/client.go @@ -108,13 +108,13 @@ func cliHandle(conn net.Conn) { var err error if UseWebsocket || !UseHttp { - c, err = ConnectProxy(Saddr, Proxy) + c, err = connect(Saddr) } else { addr := Saddr - if len(Proxy) > 0 { - addr = Proxy + if proxyURL != nil { + addr = proxyURL.Host } - c, err = Connect(addr) + c, err = dial(addr) } if err != nil { log.Println(err) diff --git a/http.go b/http.go index 1ab26c8..8e41c51 100644 --- a/http.go +++ b/http.go @@ -41,10 +41,12 @@ func (conn *HttpClientConn) Handshake() (err error) { Scheme: "http", Path: s2cUri, }, + Header: make(http.Header), } - if len(Proxy) == 0 { + if proxyURL == nil { err = req.Write(conn.c) } else { + setBasicAuth(req) err = req.WriteProxy(conn.c) } if err != nil { @@ -55,6 +57,9 @@ func (conn *HttpClientConn) Handshake() (err error) { 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 { @@ -89,8 +94,9 @@ func (conn *HttpClientConn) Write(b []byte) (n int, err error) { Path: c2sUri, RawQuery: q.Encode(), }, + Header: make(http.Header), } - resp, err := doRequest(req, Proxy) + resp, err := doRequest(req) if err != nil { log.Println(err) return @@ -251,15 +257,16 @@ func (s *HttpServer) ListenAndServe() error { return http.ListenAndServe(s.Addr, nil) } -func doRequest(req *http.Request, proxy string) (*http.Response, error) { - if len(proxy) > 0 { - c, err := Connect(proxy) +func doRequest(req *http.Request) (*http.Response, error) { + if proxyURL != nil { + c, err := dial(proxyURL.Host) if err != nil { log.Println(err) return nil, err } defer c.Close() + setBasicAuth(req) if err := req.WriteProxy(c); err != nil { log.Println(err) return nil, err diff --git a/main.go b/main.go index d96a53a..658a871 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,8 @@ import ( "flag" //"github.com/ginuerzh/gosocks5" "log" + "net/url" + "strings" "time" ) @@ -16,6 +18,8 @@ var ( Method, Password string CertFile, KeyFile string PrintVersion bool + + proxyURL *url.URL ) func init() { @@ -35,6 +39,8 @@ func init() { flag.Parse() log.SetFlags(log.LstdFlags | log.Lshortfile) + + proxyURL, _ = parseURL(Proxy) } var ( @@ -64,3 +70,11 @@ func main() { log.Fatal(listenAndServe(Laddr, cliHandle)) } + +func parseURL(rawurl string) (*url.URL, error) { + if !strings.HasPrefix(rawurl, "http://") && + !strings.HasPrefix(rawurl, "socks://") { + rawurl = "http://" + rawurl + } + return url.Parse(rawurl) +} diff --git a/socks5.go b/socks5.go index 91e1d95..6249aa0 100644 --- a/socks5.go +++ b/socks5.go @@ -176,8 +176,9 @@ func socks5Handle(conn net.Conn) { switch req.Cmd { case gosocks5.CmdConnect: //log.Println("connect", req.Addr.String()) - tconn, err := ConnectProxy(req.Addr.String(), Proxy) + tconn, err := connect(req.Addr.String()) if err != nil { + log.Println(err) gosocks5.NewReply(gosocks5.HostUnreachable, nil).Write(conn) return } diff --git a/util.go b/util.go index c541a0c..2023a27 100644 --- a/util.go +++ b/util.go @@ -3,10 +3,11 @@ package main import ( "bufio" //"bytes" + "encoding/base64" "errors" "github.com/ginuerzh/gosocks5" "io" - "log" + //"log" "net" "net/http" "net/url" @@ -52,59 +53,145 @@ func ToSocksAddr(addr net.Addr) *gosocks5.Addr { } } -func Connect(addr string) (net.Conn, error) { +func dial(addr string) (net.Conn, error) { taddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { - log.Println(err) return nil, err } return net.DialTCP("tcp", nil, taddr) } -func ConnectProxy(addr, proxy string) (net.Conn, error) { +func connect(addr string) (net.Conn, error) { if !strings.Contains(addr, ":") { addr += ":80" } - if len(proxy) == 0 { - return Connect(addr) + if proxyURL == nil { + return dial(addr) } - paddr, err := net.ResolveTCPAddr("tcp", proxy) - if err != nil { - return nil, err - } - pconn, err := net.DialTCP("tcp", nil, paddr) - if err != nil { - log.Println(err) - return nil, err + 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 } - header := http.Header{} - header.Set("Proxy-Connection", "keep-alive") req := &http.Request{ Method: "CONNECT", URL: &url.URL{Host: addr}, Host: addr, - Header: header, + Header: make(http.Header), } - if err := req.Write(pconn); err != nil { - log.Println(err) - pconn.Close() - return nil, err + 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(pconn), req) + resp, err := http.ReadResponse(bufio.NewReader(conn), req) if err != nil { - log.Println(err) - pconn.Close() - return nil, err + conn.Close() + return } if resp.StatusCode != http.StatusOK { - pconn.Close() + conn.Close() + //log.Println(resp.Status) return nil, errors.New(resp.Status) } + return +} - return pconn, nil +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, + } + + 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: + if proxyURL == nil || proxyURL.User == nil { + return nil, gosocks5.ErrAuthFailure + } + pwd, _ := proxyURL.User.Password() + if err := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, + proxyURL.User.Username(), pwd).Write(conn); err != nil { + return nil, err + } + resp, err := gosocks5.ReadUserPassResponse(conn) + if err != nil { + return nil, err + } + if resp.Status != gosocks5.Succeeded { + return nil, gosocks5.ErrAuthFailure + } + case gosocks5.MethodNoAcceptable: + return nil, gosocks5.ErrBadMethod + + //case gosocks5.MethodNoAuth: + } + + return conn, 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()))) + } } // based on io.Copy diff --git a/version.go b/version.go index ab60f86..1f1157b 100644 --- a/version.go +++ b/version.go @@ -5,7 +5,7 @@ import ( ) const ( - Version = "1.4" + Version = "1.5" ) func printVersion() {