From 53df3b5578c742ae3959ab1ab941fe0480e4e52d Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 15 May 2018 10:56:57 +0800 Subject: [PATCH] add docker support Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 2 +- README.md | 33 ++++++++++++++++++- README_ZH.md | 34 ++++++++++++++++++-- config.go | 4 ++- services/args.go | 2 ++ services/http.go | 7 +++- services/sps.go | 80 +++++++++++++++++++++++++++++----------------- utils/functions.go | 27 ++++++++++++++++ utils/ss/conn.go | 15 ++++++--- utils/structs.go | 8 ++--- 10 files changed, 168 insertions(+), 44 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 17d8c4a9..266a232b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,7 @@ v4.8 1.优化了SPS连接HTTP上级的指令,避免了某些代理不响应的问题. 2.SPS功能增加了参数: --disable-http:禁用http(s)代理 ---disable-socks:禁用socks代理. +--disable-socks:禁用socks代理 默认都是false(开启). 3.重构了部分代码的日志部分,保证了日志按着预期输出. diff --git a/README.md b/README.md index 6f84180f..cac9ea64 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ This page is the v4.7 manual, and the other version of the manual can be checked ### Installation - [Quick installation](#quick-installation) - [Manual installation](#manual-installation) +- [Docker installation](#docker-installation) ### First use must read - [Environmental Science](#environmental-science) @@ -169,6 +170,36 @@ wget https://raw.githubusercontent.com/snail007/goproxy/master/install.sh chmod +x install.sh ./install.sh ``` + +#### Docker installation + +Dockerfile root of project uses multistage build and alpine project to comply with best practices. Uses golang 1.8.5 for building as noted in the project README.md and will be pretty small image. total extracted size will be 17.3MB for goproxy version 4.7. + +The default build process builds the master branch (latest commits/ cutting edge), and it can be configured to build specific version, just edit Dockerfile before build, following builds release version 4.7: + +``` +ARG GOPROXY_VERSION=v4.7 +``` + +To Run: +1. Clone the repository and cd into it. +``` +sudo docker build . +``` +2. Tag the image: +``` +sudo docker tag goproxy/goproxy:latest +``` +3. Run! +Just put your arguments to proxy binary in the OPTS environmental variable (this is just a sample http proxy): +``` +sudo docker run -d --restart=always --name goproxy -e OPTS="http -p :33080" -p 33080:33080 goproxy/goproxy:latest +``` +4. View logs: +``` +sudo docker logs -f goproxy +``` + ## **First use must be read**   @@ -967,7 +998,7 @@ If you want to get a more detailed configuration and explanation of the KCP para - HTTP (s) proxy support PAC? - Welcome joining group feedback... -### How to contribute to the code? +### How to contribute to the code (Pull Request)? First, you need to clone the project to your account, and then modify the code on the dev branch. Finally, Pull Request to dev branch of goproxy project, and contribute code for efficiency. PR needs to explain what changes have been made and why you change them. diff --git a/README_ZH.md b/README_ZH.md index 3ecfe940..f49b91ca 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -61,6 +61,7 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 ### 安装 1. [快速安装](#自动安装) 1. [手动安装](#手动安装) +1. [Docker安装](#docker安装) ### 首次使用必看 - [环境](#首次使用必看-1) @@ -168,7 +169,36 @@ wget https://raw.githubusercontent.com/snail007/goproxy/master/install.sh chmod +x install.sh ./install.sh ``` - + +#### Docker安装 +项目根目录的Dockerfile文件用来构建,使用golang 1.8.5,构建基于goproxy v4.7, +全部大小17.3MB,默认情况下使用master分支,不过可以通过修改配置文件Dockerfile,指定构建的goproxy版本. + +``` +ARG GOPROXY_VERSION=v4.7 +``` + +步骤: +1. 克隆仓库,然后cd进入仓库文件夹,执行: +``` +sudo docker build . +``` +2. 镜像打标签: +``` +sudo docker tag <上一步的结果ID> goproxy/goproxy:latest +``` +3. 运行 +参数OPTS的值就是传递给proxy的所有参数 +比如下面的例子启动了一个http服务: + +``` +sudo docker run -d --restart=always --name goproxy -e OPTS="http -p :33080" -p 33080:33080 goproxy/goproxy:latest +``` +4. 查看日志: +``` +sudo docker logs -f goproxy +``` + ## **首次使用必看** ### **环境** @@ -1012,7 +1042,7 @@ fast3:`--nodelay=1 --interval=10 --resend=2 --nc=1` - http(s)代理增加pac支持? - 欢迎加群反馈... -### 如何贡献代码? +### 如何贡献代码(Pull Request)? 首先需要clone本项目到自己的帐号下面,然后在dev分支上面修改代码, 最后发Pull Request到goproxy项目的dev分支即可,为了高效贡献代码, pr的时候需要说明做了什么变更,原因是什么. diff --git a/config.go b/config.go index 5c2e80a8..6553e274 100755 --- a/config.go +++ b/config.go @@ -227,7 +227,7 @@ func initConfig() (err error) { spsArgs.ParentType = sps.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "kcp") spsArgs.LocalType = sps.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp", "kcp") spsArgs.Local = sps.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String() - spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type ").Short('S').Enum("http", "socks") + spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type ").Short('S').Enum("http", "socks", "ss") spsArgs.DNSAddress = sps.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String() spsArgs.DNSTTL = sps.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int() spsArgs.AuthFile = sps.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String() @@ -244,6 +244,8 @@ func initConfig() (err error) { spsArgs.ParentCompress = sps.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool() spsArgs.SSMethod = sps.Flag("ss-method", "").Hidden().Short('h').Default("aes-256-cfb").String() spsArgs.SSKey = sps.Flag("ss-key", "").Hidden().Short('j').Default("sspassword").String() + spsArgs.ParentSSMethod = sps.Flag("parent-ss-method", "").Hidden().Short('H').Default("aes-256-cfb").String() + spsArgs.ParentSSKey = sps.Flag("parent-ss-key", "").Hidden().Short('J').Default("sspassword").String() spsArgs.DisableHTTP = sps.Flag("disable-http", "disable http(s) proxy").Default("false").Bool() spsArgs.DisableSocks5 = sps.Flag("disable-socks", "disable socks proxy").Default("false").Bool() spsArgs.DisableSS = sps.Flag("disable-ss", "").Hidden().Default("false").Bool() diff --git a/services/args.go b/services/args.go index c54c5c8e..e2b5c7b6 100644 --- a/services/args.go +++ b/services/args.go @@ -234,6 +234,8 @@ type SPSArgs struct { ParentCompress *bool SSMethod *string SSKey *string + ParentSSMethod *string + ParentSSKey *string DisableHTTP *bool DisableSocks5 *bool DisableSS *bool diff --git a/services/http.go b/services/http.go index abb8f860..85e18624 100644 --- a/services/http.go +++ b/services/http.go @@ -309,7 +309,12 @@ func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *ut } else { //https或者http,上级是代理,proxy需要转发 outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) - _, err = outConn.Write(req.HeadBuf) + //直连目标或上级非代理,清理HTTP头部的代理头信息 + if !useProxy || *s.cfg.ParentType == "ssh" { + _, err = outConn.Write(utils.RemoveProxyHeaders(req.HeadBuf)) + } else { + _, err = outConn.Write(req.HeadBuf) + } outConn.SetDeadline(time.Time{}) if err != nil { s.log.Printf("write to %s , err:%s", *s.cfg.Parent, err) diff --git a/services/sps.go b/services/sps.go index ea0193b9..d2bfd116 100644 --- a/services/sps.go +++ b/services/sps.go @@ -27,7 +27,8 @@ type SPS struct { serverChannels []*utils.ServerChannel userConns utils.ConcurrentMap log *logger.Logger - cipher *ss.Cipher + localCipher *ss.Cipher + parentCipher *ss.Cipher } func NewSPS() Service { @@ -48,6 +49,10 @@ func (s *SPS) CheckArgs() (err error) { err = fmt.Errorf("parent type unkown,use -T ") return } + if *s.cfg.ParentType == "ss" && (*s.cfg.ParentSSKey == "" || *s.cfg.ParentSSMethod == "") { + err = fmt.Errorf("ss parent need a ss key, set it by : -J ") + return + } if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS { s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) if err != nil { @@ -70,7 +75,14 @@ func (s *SPS) InitService() (err error) { } err = s.InitBasicAuth() if *s.cfg.SSMethod != "" && *s.cfg.SSKey != "" { - s.cipher, err = ss.NewCipher(*s.cfg.SSMethod, *s.cfg.SSKey) + s.localCipher, err = ss.NewCipher(*s.cfg.SSMethod, *s.cfg.SSKey) + if err != nil { + s.log.Printf("error generating cipher : %s", err) + return + } + } + if *s.cfg.ParentServiceType == "ss" { + s.parentCipher, err = ss.NewCipher(*s.cfg.ParentSSMethod, *s.cfg.ParentSSKey) if err != nil { s.log.Printf("error generating cipher : %s", err) return @@ -111,10 +123,10 @@ func (s *SPS) StopService() { } } for _, c := range s.userConns.Items() { - if _,ok:=c.(*net.Conn);ok{ + if _, ok := c.(*net.Conn); ok { (*c.(*net.Conn)).Close() } - if _,ok:=c.(**net.Conn);ok{ + if _, ok := c.(**net.Conn); ok { (*(*c.(**net.Conn))).Close() } } @@ -252,7 +264,7 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { //s.log.Printf("https reply: %s", request.Host) } else { //forwardBytes = bytes.TrimRight(request.HeadBuf,"\r\n") - forwardBytes = bytes.TrimRight(request.HeadBuf,"\r\n") + forwardBytes = request.HeadBuf } address = request.Host var userpass string @@ -273,7 +285,7 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { return } (*inConn).SetDeadline(time.Now().Add(time.Second * 5)) - ssConn := ss.NewConn(*inConn, s.cipher.Copy()) + ssConn := ss.NewConn(*inConn, s.localCipher.Copy()) address, err = ss.GetRequest(ssConn) (*inConn).SetDeadline(time.Time{}) if err != nil { @@ -309,20 +321,23 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { Password: *s.cfg.ParentKey, }) } + + if *s.cfg.ParentAuth != "" || *s.cfg.ParentSSKey != "" || s.IsBasicAuth() { + forwardBytes = utils.RemoveProxyHeaders(forwardBytes) + } + //ask parent for connect to target address if *s.cfg.ParentServiceType == "http" { //http parent - isHTTPS:=false + isHTTPS := false + pb := new(bytes.Buffer) if len(forwardBytes) == 0 { - isHTTPS=true - pb.Write([]byte(fmt.Sprintf("CONNECT %s HTTP/1.1", address))) + isHTTPS = true + pb.Write([]byte(fmt.Sprintf("CONNECT %s HTTP/1.1\r\n", address))) } - pb.WriteString("\r\nProxy-Connection: Keep-Alive\r\n") + pb.WriteString("Proxy-Connection: Keep-Alive\r\n") - s.log.Printf("before bytes:%s",string(forwardBytes)) - - //Proxy-Authorization:\r\n u := "" if *s.cfg.ParentAuth != "" { a := strings.Split(*s.cfg.ParentAuth, ":") @@ -337,24 +352,18 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { } } if u != "" { - pb.Write([]byte(fmt.Sprintf("Proxy-Authorization:Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(u))))) + pb.Write([]byte(fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(u))))) } - if isHTTPS{ + if isHTTPS { pb.Write([]byte("\r\n")) - }else{ - //do remove some headers with forwardBytes - //forwardBytes - - forwardBytes=bytes.Replace(forwardBytes,[]byte("\r\n"),[]byte(pb.Bytes()),1) + } else { + forwardBytes = utils.InsertProxyHeaders(forwardBytes, string(pb.Bytes())) pb.Reset() pb.Write(forwardBytes) - if !bytes.Contains(forwardBytes,[]byte("\r\n\r\n\r\n")){ - pb.Write([]byte("\r\n\r\n")) - } - forwardBytes=nil + forwardBytes = nil } - + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Write(pb.Bytes()) outConn.SetDeadline(time.Time{}) @@ -365,10 +374,7 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { return } - s.log.Printf("ishttps:%v",isHTTPS) - s.log.Printf("bytes:%s",string(pb.Bytes())) - - if isHTTPS{ + if isHTTPS { reply := make([]byte, 1024) outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Read(reply) @@ -381,7 +387,7 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { } //s.log.Printf("reply: %s", string(reply[:n])) } - } else { + } else if *s.cfg.ParentServiceType == "socks" { s.log.Printf("connect %s", address) //socks client var clientConn *socks.ClientConn @@ -402,11 +408,25 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { if err = clientConn.Handshake(); err != nil { return } + } else if *s.cfg.ParentServiceType == "ss" { + ra, e := ss.RawAddr(address) + if e != nil { + err = fmt.Errorf("build ss raw addr fail, err: %s", e) + return + } + + outConn, err = ss.DialWithRawAddr(&outConn, ra, "", s.parentCipher.Copy()) + if err != nil { + err = fmt.Errorf("dial ss parent fail, err : %s", err) + return + } } + //forward client data to target,if necessary. if len(forwardBytes) > 0 { outConn.Write(forwardBytes) } + //bind inAddr := (*inConn).RemoteAddr().String() outAddr := outConn.RemoteAddr().String() diff --git a/utils/functions.go b/utils/functions.go index 21ca84fc..ddb588f9 100755 --- a/utils/functions.go +++ b/utils/functions.go @@ -621,6 +621,33 @@ func IsSocks5(head []byte) bool { } return false } +func RemoveProxyHeaders(head []byte) []byte { + newLines := [][]byte{} + var keys = map[string]bool{} + lines := bytes.Split(head, []byte("\r\n")) + IsBody := false + for _, line := range lines { + if len(line) == 0 || IsBody { + newLines = append(newLines, line) + IsBody = true + } else { + hline := bytes.SplitN(line, []byte(":"), 2) + if len(hline) != 2 { + continue + } + k := strings.ToUpper(string(hline[0])) + if _, ok := keys[k]; ok || strings.HasPrefix(k, "PROXY-") { + continue + } + keys[k] = true + newLines = append(newLines, line) + } + } + return bytes.Join(newLines, []byte("\r\n")) +} +func InsertProxyHeaders(head []byte, headers string) []byte { + return bytes.Replace(head, []byte("\r\n"), []byte("\r\n"+headers), 1) +} // type sockaddr struct { // family uint16 diff --git a/utils/ss/conn.go b/utils/ss/conn.go index 46513f29..83f06814 100644 --- a/utils/ss/conn.go +++ b/utils/ss/conn.go @@ -58,12 +58,19 @@ func RawAddr(addr string) (buf []byte, err error) { // This is intended for use by users implementing a local socks proxy. // rawaddr shoud contain part of the data in socks request, starting from the // ATYP field. (Refer to rfc1928 for more information.) -func DialWithRawAddr(rawaddr []byte, server string, cipher *Cipher) (c *Conn, err error) { - conn, err := net.Dial("tcp", server) +func DialWithRawAddr(rawConn *net.Conn, rawaddr []byte, server string, cipher *Cipher) (c *Conn, err error) { + var conn net.Conn + if rawConn == nil { + conn, err = net.Dial("tcp", server) + } if err != nil { return } - c = NewConn(conn, cipher) + if rawConn != nil { + c = NewConn(*rawConn, cipher) + } else { + c = NewConn(conn, cipher) + } if cipher.ota { if c.enc == nil { if _, err = c.initEncrypt(); err != nil { @@ -88,7 +95,7 @@ func Dial(addr, server string, cipher *Cipher) (c *Conn, err error) { if err != nil { return } - return DialWithRawAddr(ra, server, cipher) + return DialWithRawAddr(nil, ra, server, cipher) } func (c *Conn) GetIv() (iv []byte) { diff --git a/utils/structs.go b/utils/structs.go index 69f415cb..86353671 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -486,10 +486,10 @@ func (req *HTTPRequest) getHeader(key string) (val string) { lines := strings.Split(string(req.HeadBuf), "\r\n") //log.Println(lines) for _, line := range lines { - line := strings.SplitN(strings.Trim(line, "\r\n "), ":", 2) - if len(line) == 2 { - k := strings.ToUpper(strings.Trim(line[0], " ")) - v := strings.Trim(line[1], " ") + hline := strings.SplitN(strings.Trim(line, "\r\n "), ":", 2) + if len(hline) == 2 { + k := strings.ToUpper(strings.Trim(hline[0], " ")) + v := strings.Trim(hline[1], " ") if key == k { val = v return