From f756d62b19941e5c1fadc5cdabf289b56008563b Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 13 Mar 2018 17:14:25 +0800 Subject: [PATCH 01/19] Signed-off-by: arraykeys@gmail.com --- services/mux_server.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/services/mux_server.go b/services/mux_server.go index 413b216..2bba64a 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -266,14 +266,6 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { } s.sessions.Set(index, session) log.Printf("session[%s] created", index) - } else { - session = _session.(*smux.Session) - } - conn, err = session.OpenStream() - if err != nil { - session.Close() - s.sessions.Remove(index) - } else { go func() { for { if session.IsClosed() { @@ -283,8 +275,14 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { time.Sleep(time.Second * 5) } }() + } else { + session = _session.(*smux.Session) + } + conn, err = session.OpenStream() + if err != nil { + session.Close() + s.sessions.Remove(index) } - return } func (s *MuxServer) getParentConn() (conn net.Conn, err error) { From 0d85c7dd7dfbb7c6290b44036d9a150e13a4302a Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 13 Mar 2018 17:31:51 +0800 Subject: [PATCH 02/19] bridge add timeout Signed-off-by: arraykeys@gmail.com --- services/mux_bridge.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/mux_bridge.go b/services/mux_bridge.go index 6c33246..605a187 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -180,7 +180,9 @@ func (s *MuxBridge) callback(inConn net.Conn, serverID, key string) { index := keys[i] log.Printf("select client : %s-%s", key, index) session, _ := group.Get(index) + session.(*smux.Session).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) stream, err := session.(*smux.Session).OpenStream() + session.(*smux.Session).SetDeadline(time.Time{}) if err != nil { log.Printf("%s client session open stream %s fail, err: %s, retrying...", key, serverID, err) time.Sleep(time.Second * 3) From bab4325414737cafd617f5d85a251bf6833312e9 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 13 Mar 2018 18:36:34 +0800 Subject: [PATCH 03/19] server add safe close user conn Signed-off-by: arraykeys@gmail.com --- README.md | 5 +++-- README_ZH.md | 5 +++-- install_auto.sh | 2 +- main.go | 2 +- release.sh | 2 +- services/mux_server.go | 41 ++++++++++++++++++++++++++++++++--------- 6 files changed, 41 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a949255..349a1e8 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ Proxy is a high performance HTTP, HTTPS, HTTPS, websocket, TCP, UDP, Socks5 prox - ...   -This page is the v4.5 manual, and the other version of the manual can be checked by the following link. +This page is the v4.6 manual, and the other version of the manual can be checked by the following link. +- [v4.5 manual](https://github.com/snail007/goproxy/tree/v4.5) - [v4.4 manual](https://github.com/snail007/goproxy/tree/v4.4) - [v4.3 manual](https://github.com/snail007/goproxy/tree/v4.3) - [v4.2 manual](https://github.com/snail007/goproxy/tree/v4.2) @@ -149,7 +150,7 @@ If the installation fails or your VPS is not a linux64 system, please follow the Download address: https://github.com/snail007/goproxy/releases ```shell cd /root/proxy/ -wget https://github.com/snail007/goproxy/releases/download/v4.5/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.6/proxy-linux-amd64.tar.gz ``` #### **2.Download the automatic installation script** ```shell diff --git a/README_ZH.md b/README_ZH.md index 08f442f..b0b46b4 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -35,7 +35,8 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - ... -本页是v4.5手册,其他版本手册请点击下面链接查看. +本页是v4.6手册,其他版本手册请点击下面链接查看. +- [v4.5手册](https://github.com/snail007/goproxy/tree/v4.5) - [v4.4手册](https://github.com/snail007/goproxy/tree/v4.4) - [v4.3手册](https://github.com/snail007/goproxy/tree/v4.3) - [v4.2手册](https://github.com/snail007/goproxy/tree/v4.2) @@ -147,7 +148,7 @@ curl -L https://raw.githubusercontent.com/snail007/goproxy/master/install_auto.s 下载地址:https://github.com/snail007/goproxy/releases ```shell cd /root/proxy/ -wget https://github.com/snail007/goproxy/releases/download/v4.5/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.6/proxy-linux-amd64.tar.gz ``` #### **2.下载自动安装脚本** ```shell diff --git a/install_auto.sh b/install_auto.sh index e25d8d7..69df125 100755 --- a/install_auto.sh +++ b/install_auto.sh @@ -5,7 +5,7 @@ if [ -e /tmp/proxy ]; then fi mkdir /tmp/proxy cd /tmp/proxy -wget https://github.com/snail007/goproxy/releases/download/v4.5/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.6/proxy-linux-amd64.tar.gz # #install proxy tar zxvf proxy-linux-amd64.tar.gz diff --git a/main.go b/main.go index 1547971..139d970 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,7 @@ import ( "syscall" ) -const APP_VERSION = "4.5" +const APP_VERSION = "4.6" func main() { err := initConfig() diff --git a/release.sh b/release.sh index 5009a87..965d039 100755 --- a/release.sh +++ b/release.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER="4.5" +VER="4.6" RELEASE="release-${VER}" rm -rf .cert mkdir .cert diff --git a/services/mux_server.go b/services/mux_server.go index 2bba64a..bcffc50 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -11,6 +11,7 @@ import ( "snail007/proxy/utils" "strconv" "strings" + "sync" "time" "github.com/golang/snappy" @@ -18,11 +19,13 @@ import ( ) type MuxServer struct { - cfg MuxServerArgs - udpChn chan MuxUDPItem - sc utils.ServerChannel - sessions utils.ConcurrentMap - lockChn chan bool + cfg MuxServerArgs + udpChn chan MuxUDPItem + sc utils.ServerChannel + sessions utils.ConcurrentMap + userConns utils.ConcurrentMap + lockChn chan bool + closeLock *sync.Mutex } type MuxServerManager struct { @@ -116,10 +119,12 @@ func (s *MuxServerManager) InitService() { func NewMuxServer() Service { return &MuxServer{ - cfg: MuxServerArgs{}, - udpChn: make(chan MuxUDPItem, 50000), - lockChn: make(chan bool, 1), - sessions: utils.NewConcurrentMap(), + cfg: MuxServerArgs{}, + udpChn: make(chan MuxUDPItem, 50000), + lockChn: make(chan bool, 1), + sessions: utils.NewConcurrentMap(), + userConns: utils.NewConcurrentMap(), + closeLock: &sync.Mutex{}, } } @@ -164,6 +169,8 @@ func (s *MuxServer) Start(args interface{}) (err error) { log.Printf("connection handler crashed with err : %s \nstack: %s", err, string(debug.Stack())) } }() + inConnRemoteAddr := inConn.RemoteAddr().String() + s.userConns.Set(inConnRemoteAddr, &inConn) var outConn net.Conn var ID string for { @@ -177,6 +184,9 @@ func (s *MuxServer) Start(args interface{}) (err error) { break } } + outConnRemoteAddr := outConn.RemoteAddr().String() + s.userConns.Set(outConnRemoteAddr, &outConn) + log.Printf("%s stream %s created", *s.cfg.Key, ID) if *s.cfg.IsCompress { die1 := make(chan bool, 1) @@ -195,9 +205,13 @@ func (s *MuxServer) Start(args interface{}) (err error) { } outConn.Close() inConn.Close() + s.userConns.Remove(inConnRemoteAddr) + s.userConns.Remove(outConnRemoteAddr) log.Printf("%s stream %s released", *s.cfg.Key, ID) } else { utils.IoBind(inConn, outConn, func(err interface{}) { + s.userConns.Remove(inConnRemoteAddr) + s.userConns.Remove(outConnRemoteAddr) log.Printf("%s stream %s released", *s.cfg.Key, ID) }) } @@ -270,6 +284,15 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { for { if session.IsClosed() { s.sessions.Remove(index) + s.closeLock.Lock() + if len(s.userConns) > 0 { + for _, k := range s.userConns.Keys() { + c, _ := s.userConns.Get(k) + (*(c.(*net.Conn))).Close() + s.userConns.Remove(k) + } + } + s.closeLock.Unlock() break } time.Sleep(time.Second * 5) From 7f983152b7ff4a38a62eee7a5b4408a4740f8ef4 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 14 Mar 2018 09:37:57 +0800 Subject: [PATCH 04/19] Signed-off-by: arraykeys@gmail.com --- services/mux_server.go | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/services/mux_server.go b/services/mux_server.go index bcffc50..f8e370a 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -11,7 +11,6 @@ import ( "snail007/proxy/utils" "strconv" "strings" - "sync" "time" "github.com/golang/snappy" @@ -19,13 +18,11 @@ import ( ) type MuxServer struct { - cfg MuxServerArgs - udpChn chan MuxUDPItem - sc utils.ServerChannel - sessions utils.ConcurrentMap - userConns utils.ConcurrentMap - lockChn chan bool - closeLock *sync.Mutex + cfg MuxServerArgs + udpChn chan MuxUDPItem + sc utils.ServerChannel + sessions utils.ConcurrentMap + lockChn chan bool } type MuxServerManager struct { @@ -119,12 +116,10 @@ func (s *MuxServerManager) InitService() { func NewMuxServer() Service { return &MuxServer{ - cfg: MuxServerArgs{}, - udpChn: make(chan MuxUDPItem, 50000), - lockChn: make(chan bool, 1), - sessions: utils.NewConcurrentMap(), - userConns: utils.NewConcurrentMap(), - closeLock: &sync.Mutex{}, + cfg: MuxServerArgs{}, + udpChn: make(chan MuxUDPItem, 50000), + lockChn: make(chan bool, 1), + sessions: utils.NewConcurrentMap(), } } @@ -170,7 +165,6 @@ func (s *MuxServer) Start(args interface{}) (err error) { } }() inConnRemoteAddr := inConn.RemoteAddr().String() - s.userConns.Set(inConnRemoteAddr, &inConn) var outConn net.Conn var ID string for { @@ -185,7 +179,6 @@ func (s *MuxServer) Start(args interface{}) (err error) { } } outConnRemoteAddr := outConn.RemoteAddr().String() - s.userConns.Set(outConnRemoteAddr, &outConn) log.Printf("%s stream %s created", *s.cfg.Key, ID) if *s.cfg.IsCompress { @@ -205,13 +198,9 @@ func (s *MuxServer) Start(args interface{}) (err error) { } outConn.Close() inConn.Close() - s.userConns.Remove(inConnRemoteAddr) - s.userConns.Remove(outConnRemoteAddr) log.Printf("%s stream %s released", *s.cfg.Key, ID) } else { utils.IoBind(inConn, outConn, func(err interface{}) { - s.userConns.Remove(inConnRemoteAddr) - s.userConns.Remove(outConnRemoteAddr) log.Printf("%s stream %s released", *s.cfg.Key, ID) }) } @@ -284,15 +273,6 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { for { if session.IsClosed() { s.sessions.Remove(index) - s.closeLock.Lock() - if len(s.userConns) > 0 { - for _, k := range s.userConns.Keys() { - c, _ := s.userConns.Get(k) - (*(c.(*net.Conn))).Close() - s.userConns.Remove(k) - } - } - s.closeLock.Unlock() break } time.Sleep(time.Second * 5) From d4c0775b4ac273af4a45a4e4621617fca874fb66 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 16 Mar 2018 16:16:11 +0800 Subject: [PATCH 05/19] Signed-off-by: arraykeys@gmail.com --- services/mux_bridge.go | 3 +++ services/mux_server.go | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/mux_bridge.go b/services/mux_bridge.go index 605a187..151e849 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -128,6 +128,9 @@ func (s *MuxBridge) handler(inConn net.Conn) { } _group, _ := s.clientControlConns.Get(groupKey) group := _group.(*utils.ConcurrentMap) + if v, ok := group.Get(index); ok { + v.(*smux.Session).Close() + } group.Set(index, session) // s.clientControlConns.Set(key, session) go func() { diff --git a/services/mux_server.go b/services/mux_server.go index f8e370a..2bba64a 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -164,7 +164,6 @@ func (s *MuxServer) Start(args interface{}) (err error) { log.Printf("connection handler crashed with err : %s \nstack: %s", err, string(debug.Stack())) } }() - inConnRemoteAddr := inConn.RemoteAddr().String() var outConn net.Conn var ID string for { @@ -178,8 +177,6 @@ func (s *MuxServer) Start(args interface{}) (err error) { break } } - outConnRemoteAddr := outConn.RemoteAddr().String() - log.Printf("%s stream %s created", *s.cfg.Key, ID) if *s.cfg.IsCompress { die1 := make(chan bool, 1) From 40bce3e736e7754bac1cd01d2f6fd52a0fc99df6 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 21 Mar 2018 16:57:46 +0800 Subject: [PATCH 06/19] optimize http basic auth Signed-off-by: arraykeys@gmail.com --- utils/structs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/structs.go b/utils/structs.go index 9528e4f..dc8dcfd 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -411,6 +411,7 @@ func (req *HTTPRequest) BasicAuth() (err error) { // authorization = req.getHeader("Authorization") // code = "401" // } + authorization = strings.Trim(authorization, " \r\n\t") if authorization == "" { fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\n\r\nUnauthorized", code) CloseConn(req.conn) From 59c914887556104243208a94fcbb5d050b4d0f5a Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 21 Mar 2018 18:17:20 +0800 Subject: [PATCH 07/19] optimize timeout of http(s)\socks\nat Signed-off-by: arraykeys@gmail.com --- services/http.go | 8 +++++--- services/mux_bridge.go | 4 ++++ services/mux_client.go | 11 +++++++++++ services/mux_server.go | 8 ++++++++ services/socks.go | 32 +++++++++++++++++++++++++++++++- 5 files changed, 59 insertions(+), 4 deletions(-) diff --git a/services/http.go b/services/http.go index 9587303..37993ab 100644 --- a/services/http.go +++ b/services/http.go @@ -94,7 +94,9 @@ func (s *HTTP) InitService() { for { conn, err := utils.ConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout*2) if err == nil { + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = conn.Write([]byte{0}) + conn.SetDeadline(time.Time{}) } if err != nil { if s.sshClient != nil { @@ -215,7 +217,7 @@ func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *ut if *s.cfg.ParentType == "ssh" { outConn, err = s.getSSHConn(address) } else { - //log.Printf("%v", s.outPool) + // log.Printf("%v", s.outPool) _outConn, err = s.outPool.Pool.Get() if err == nil { outConn = _outConn.(net.Conn) @@ -237,16 +239,16 @@ func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *ut utils.CloseConn(inConn) return } - outAddr := outConn.RemoteAddr().String() //outLocalAddr := outConn.LocalAddr().String() - if req.IsHTTPS() && (!useProxy || *s.cfg.ParentType == "ssh") { //https无上级或者上级非代理,proxy需要响应connect请求,并直连目标 err = req.HTTPSReply() } else { //https或者http,上级是代理,proxy需要转发 + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Write(req.HeadBuf) + outConn.SetDeadline(time.Time{}) if err != nil { log.Printf("write to %s , err:%s", *s.cfg.Parent, err) utils.CloseConn(inConn) diff --git a/services/mux_bridge.go b/services/mux_bridge.go index 151e849..ec57b1f 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -75,7 +75,9 @@ func (s *MuxBridge) handler(inConn net.Conn) { var err error var connType uint8 var key string + inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) err = utils.ReadPacket(reader, &connType, &key) + inConn.SetDeadline(time.Time{}) if err != nil { log.Printf("read error,ERR:%s", err) return @@ -83,7 +85,9 @@ func (s *MuxBridge) handler(inConn net.Conn) { switch connType { case CONN_SERVER: var serverID string + inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) err = utils.ReadPacketData(reader, &serverID) + inConn.SetDeadline(time.Time{}) if err != nil { log.Printf("read error,ERR:%s", err) return diff --git a/services/mux_client.go b/services/mux_client.go index 42cd43d..398744a 100644 --- a/services/mux_client.go +++ b/services/mux_client.go @@ -68,7 +68,9 @@ func (s *MuxClient) Start(args interface{}) (err error) { time.Sleep(time.Second * 3) continue } + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = conn.Write(utils.BuildPacket(CONN_CLIENT, fmt.Sprintf("%s-%d", *s.cfg.Key, i))) + conn.SetDeadline(time.Time{}) if err != nil { conn.Close() log.Printf("connection err: %s, retrying...", err) @@ -98,7 +100,9 @@ func (s *MuxClient) Start(args interface{}) (err error) { } }() var ID, clientLocalAddr, serverID string + stream.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) err = utils.ReadPacketData(stream, &ID, &clientLocalAddr, &serverID) + stream.SetDeadline(time.Time{}) if err != nil { log.Printf("read stream signal err: %s", err) stream.Close() @@ -140,7 +144,9 @@ func (s *MuxClient) getParentConn() (conn net.Conn, err error) { func (s *MuxClient) ServeUDP(inConn *smux.Stream, localAddr, ID string) { for { + inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) srcAddr, body, err := utils.ReadUDPPacket(inConn) + inConn.SetDeadline(time.Time{}) if err != nil { log.Printf("udp packet revecived fail, err: %s", err) log.Printf("connection %s released", ID) @@ -169,13 +175,16 @@ func (s *MuxClient) processUDPPacket(inConn *smux.Stream, srcAddr, localAddr str } conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = conn.Write(body) + conn.SetDeadline(time.Time{}) if err != nil { log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err) return } //log.Printf("send udp packet to %s success", dstAddr.String()) buf := make([]byte, 1024) + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) length, _, err := conn.ReadFromUDP(buf) + conn.SetDeadline(time.Time{}) if err != nil { log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err) return @@ -183,7 +192,9 @@ func (s *MuxClient) processUDPPacket(inConn *smux.Stream, srcAddr, localAddr str respBody := buf[0:length] //log.Printf("revecived udp packet from %s , %v", dstAddr.String(), respBody) bs := utils.UDPPacket(srcAddr, respBody) + (*inConn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = (*inConn).Write(bs) + (*inConn).SetDeadline(time.Time{}) if err != nil { log.Printf("send udp response fail ,ERR:%s", err) inConn.Close() diff --git a/services/mux_server.go b/services/mux_server.go index 2bba64a..9e1fc65 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -227,7 +227,9 @@ func (s *MuxServer) GetOutConn() (outConn net.Conn, ID string, err error) { remoteAddr = "udp:" + *s.cfg.Remote } ID = utils.Uniqueid() + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Write(utils.BuildPacketData(ID, remoteAddr, s.cfg.Mgr.serverID)) + outConn.SetDeadline(time.Time{}) if err != nil { log.Printf("write stream data err: %s ,retrying...", err) utils.CloseConn(&outConn) @@ -253,7 +255,9 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { if err != nil { return } + c.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = c.Write(utils.BuildPacket(CONN_SERVER, *s.cfg.Key, s.cfg.Mgr.serverID)) + c.SetDeadline(time.Time{}) if err != nil { c.Close() return @@ -327,7 +331,9 @@ func (s *MuxServer) UDPConnDeamon() { // outConn.Close() }() for { + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) srcAddrFromConn, body, err := utils.ReadUDPPacket(outConn) + outConn.SetDeadline(time.Time{}) if err != nil { log.Printf("parse revecived udp packet fail, err: %s ,%v", err, body) log.Printf("UDP deamon connection %s exited", ID) @@ -341,7 +347,9 @@ func (s *MuxServer) UDPConnDeamon() { } port, _ := strconv.Atoi(_srcAddr[1]) dstAddr := &net.UDPAddr{IP: net.ParseIP(_srcAddr[0]), Port: port} + s.sc.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = s.sc.UDPListener.WriteToUDP(body, dstAddr) + s.sc.UDPListener.SetDeadline(time.Time{}) if err != nil { log.Printf("udp response to local %s fail,ERR:%s", srcAddrFromConn, err) continue diff --git a/services/socks.go b/services/socks.go index bea8180..9828733 100644 --- a/services/socks.go +++ b/services/socks.go @@ -104,7 +104,9 @@ func (s *Socks) InitService() { for { conn, err := utils.ConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout*2) if err == nil { + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = conn.Write([]byte{0}) + conn.SetDeadline(time.Time{}) } if err != nil { if s.sshClient != nil { @@ -216,6 +218,7 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { } conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*5))) _, err = conn.Write(rawB) + conn.SetDeadline(time.Time{}) log.Printf("udp request:%v", len(rawB)) if err != nil { log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err) @@ -225,7 +228,9 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { //log.Printf("send udp packet to %s success", dstAddr.String()) buf := make([]byte, 10*1024) + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) length, _, err := conn.ReadFromUDP(buf) + conn.SetDeadline(time.Time{}) if err != nil { log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err) conn.Close() @@ -250,10 +255,14 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { conn.Close() return } + s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) s.udpSC.UDPListener.WriteToUDP(d, srcAddr) + s.udpSC.UDPListener.SetDeadline(time.Time{}) log.Printf("udp reply:%v", len(d)) } else { + s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) s.udpSC.UDPListener.WriteToUDP(respBody, srcAddr) + s.udpSC.UDPListener.SetDeadline(time.Time{}) log.Printf("udp reply:%v", len(respBody)) } @@ -272,6 +281,7 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { } conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*3))) _, err = conn.Write(p.Data()) + conn.SetDeadline(time.Time{}) log.Printf("udp send:%v", len(p.Data())) if err != nil { log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err) @@ -280,7 +290,10 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { } //log.Printf("send udp packet to %s success", dstAddr.String()) buf := make([]byte, 10*1024) + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) length, _, err := conn.ReadFromUDP(buf) + conn.SetDeadline(time.Time{}) + if err != nil { log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err) conn.Close() @@ -297,9 +310,13 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { conn.Close() return } + s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) s.udpSC.UDPListener.WriteToUDP(d, srcAddr) + s.udpSC.UDPListener.SetDeadline(time.Time{}) } else { + s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) s.udpSC.UDPListener.WriteToUDP(respPacket, srcAddr) + s.udpSC.UDPListener.SetDeadline(time.Time{}) } log.Printf("udp reply:%v", len(respPacket)) } @@ -371,9 +388,15 @@ func (s *Socks) socksConnCallback(inConn net.Conn) { //auth _addr := strings.Split(inConn.RemoteAddr().String(), ":") if s.basicAuth.CheckUserPass(user, pass, _addr[0], "") { + inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) inConn.Write([]byte{0x01, 0x00}) + inConn.SetDeadline(time.Time{}) + } else { + inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) inConn.Write([]byte{0x01, 0x01}) + inConn.SetDeadline(time.Time{}) + utils.CloseConn(&inConn) return } @@ -496,25 +519,32 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n } var buf = make([]byte, 1024) //var n int + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Write(methodBytes) + outConn.SetDeadline(time.Time{}) if err != nil { err = fmt.Errorf("write method fail,%s", err) return } + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Read(buf) + outConn.SetDeadline(time.Time{}) if err != nil { err = fmt.Errorf("read method reply fail,%s", err) return } //resp := buf[:n] //log.Printf("resp:%v", resp) - + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Write(reqBytes) + outConn.SetDeadline(time.Time{}) if err != nil { err = fmt.Errorf("write req detail fail,%s", err) return } + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Read(buf) + outConn.SetDeadline(time.Time{}) if err != nil { err = fmt.Errorf("read req reply fail,%s", err) return From d81d5ffe06d3c1c6294844d6914f80ddd85f844c Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Mon, 26 Mar 2018 18:42:44 +0800 Subject: [PATCH 08/19] socks5 client server done Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- utils/socks/client.go | 253 +++++++++++++++++++++++++++++++++++++++++ utils/socks/server.go | 225 ++++++++++++++++++++++++++++++++++++ utils/socks/structs.go | 168 +++++++++++++++++++-------- 4 files changed, 599 insertions(+), 49 deletions(-) create mode 100644 utils/socks/client.go create mode 100644 utils/socks/server.go diff --git a/README_ZH.md b/README_ZH.md index b0b46b4..dea5c5e 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -599,7 +599,7 @@ server连接到bridge的时候,如果同时有多个client连接到同一个brid ![5.2](/docs/images/5.2.png) 使用本地端口8090,假设上级SOCKS5代理是`22.22.22.22:8080` `./proxy socks -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" ` -我们还可以指定网站域名的黑白名单文件,一行一个域名,匹配规则是最右匹配,比如:baidu.com,匹配的是*.*.baidu.com,黑名单的域名域名直接走上级代理,白名单的域名不走上级代理. +我们还可以指定网站域名的黑白名单文件,一行一个域名,匹配规则是最右匹配,比如:baidu.com,匹配的是*.*.baidu.com,黑名单的域名域名直接走上级代理,白名单的域名不走上级代理;如果域名即在黑名单又在白名单中,那么黑名单起作用. `./proxy socks -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -b blocked.txt -d direct.txt` #### **5.3.SOCKS二级代理(加密)** diff --git a/utils/socks/client.go b/utils/socks/client.go new file mode 100644 index 0000000..7253d21 --- /dev/null +++ b/utils/socks/client.go @@ -0,0 +1,253 @@ +package socks + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "strconv" + "time" +) + +var socks5Errors = []string{ + "", + "general failure", + "connection forbidden", + "network unreachable", + "host unreachable", + "connection refused", + "TTL expired", + "command not supported", + "address type not supported", +} + +type Auth struct { + User, Password string +} +type ClientConn struct { + user string + password string + conn *net.Conn + header []byte + timeout time.Duration + addr string + network string + udpAddr string +} + +// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address +// with an optional username and password. See RFC 1928 and RFC 1929. +// target must be a canonical address with a host and port. +// network : tcp udp +func NewClientConn(conn *net.Conn, network, target string, timeout time.Duration, auth *Auth, header []byte) *ClientConn { + s := &ClientConn{ + conn: conn, + network: network, + timeout: timeout, + } + if auth != nil { + s.user = auth.User + s.password = auth.Password + } + if header != nil && len(header) > 0 { + s.header = header + } + if network == "udp" && target == "" { + target = "0.0.0.0:1" + } + s.addr = target + return s +} + +// connect takes an existing connection to a socks5 proxy server, +// and commands the server to extend that connection to target, +// which must be a canonical address with a host and port. +func (s *ClientConn) Connect() error { + host, portStr, err := net.SplitHostPort(s.addr) + if err != nil { + return err + } + port, err := strconv.Atoi(portStr) + if err != nil { + return errors.New("proxy: failed to parse port number: " + portStr) + } + if port < 1 || port > 0xffff { + return errors.New("proxy: port number out of range: " + portStr) + } + + if err := s.handshake(host); err != nil { + return err + } + buf := []byte{} + if s.network == "tcp" { + buf = append(buf, VERSION_V5, CMD_CONNECT, 0 /* reserved */) + + } else { + buf = append(buf, VERSION_V5, CMD_ASSOCIATE, 0 /* reserved */) + } + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + buf = append(buf, ATYP_IPV4) + ip = ip4 + } else { + buf = append(buf, ATYP_IPV6) + } + buf = append(buf, ip...) + } else { + if len(host) > 255 { + return errors.New("proxy: destination host name too long: " + host) + } + buf = append(buf, ATYP_DOMAIN) + buf = append(buf, byte(len(host))) + buf = append(buf, host...) + } + buf = append(buf, byte(port>>8), byte(port)) + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := (*s.conn).Write(buf); err != nil { + return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := io.ReadFull((*s.conn), buf[:4]); err != nil { + return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + failure := "unknown error" + if int(buf[1]) < len(socks5Errors) { + failure = socks5Errors[buf[1]] + } + + if len(failure) > 0 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) + } + + bytesToDiscard := 0 + switch buf[3] { + case ATYP_IPV4: + bytesToDiscard = net.IPv4len + case ATYP_IPV6: + bytesToDiscard = net.IPv6len + case ATYP_DOMAIN: + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + _, err := io.ReadFull((*s.conn), buf[:1]) + (*s.conn).SetDeadline(time.Time{}) + if err != nil { + return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + bytesToDiscard = int(buf[0]) + default: + return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr) + } + + if cap(buf) < bytesToDiscard { + buf = make([]byte, bytesToDiscard) + } else { + buf = buf[:bytesToDiscard] + } + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := io.ReadFull((*s.conn), buf); err != nil { + return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + var ip net.IP + ip = buf + ipStr := "" + if bytesToDiscard == net.IPv4len || bytesToDiscard == net.IPv6len { + if ipv4 := ip.To4(); ipv4 != nil { + ipStr = ipv4.String() + } else { + ipStr = ip.To16().String() + } + } + //log.Printf("%v", ipStr) + // Also need to discard the port number + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil { + return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + p := binary.BigEndian.Uint16([]byte{buf[0], buf[1]}) + //log.Printf("%v", p) + s.udpAddr = net.JoinHostPort(ipStr, fmt.Sprintf("%d", p)) + //log.Printf("%v", s.udpAddr) + (*s.conn).SetDeadline(time.Time{}) + return nil +} +func (s *ClientConn) SendUDP(data []byte, addr string) (respData []byte, err error) { + + c, err := net.DialTimeout("udp", s.udpAddr, s.timeout) + if err != nil { + return + } + conn := c.(*net.UDPConn) + + p := NewPacketUDP() + p.Build(addr, data) + conn.SetDeadline(time.Now().Add(s.timeout)) + conn.Write(p.Bytes()) + conn.SetDeadline(time.Time{}) + + buf := make([]byte, 1024) + conn.SetDeadline(time.Now().Add(s.timeout)) + n, _, err := conn.ReadFrom(buf) + conn.SetDeadline(time.Time{}) + if err != nil { + return + } + respData = buf[:n] + return +} +func (s *ClientConn) handshake(host string) error { + + // the size here is just an estimate + buf := make([]byte, 0, 6+len(host)) + + buf = append(buf, VERSION_V5) + if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { + buf = append(buf, 2 /* num auth methods */, Method_NO_AUTH, Method_USER_PASS) + } else { + buf = append(buf, 1 /* num auth methods */, Method_NO_AUTH) + } + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := (*s.conn).Write(buf); err != nil { + return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil { + return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + + if buf[0] != 5 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) + } + if buf[1] == 0xff { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") + } + + // See RFC 1929 + if buf[1] == Method_USER_PASS { + buf = buf[:0] + buf = append(buf, 1 /* password protocol version */) + buf = append(buf, uint8(len(s.user))) + buf = append(buf, s.user...) + buf = append(buf, uint8(len(s.password))) + buf = append(buf, s.password...) + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := (*s.conn).Write(buf); err != nil { + return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil { + return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + if buf[1] != 0 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") + } + } + return nil +} diff --git a/utils/socks/server.go b/utils/socks/server.go new file mode 100644 index 0000000..121798a --- /dev/null +++ b/utils/socks/server.go @@ -0,0 +1,225 @@ +package socks + +import ( + "fmt" + "net" + "snail007/proxy/utils" + "strings" + "time" +) + +const ( + Method_NO_AUTH = uint8(0x00) + Method_GSSAPI = uint8(0x01) + Method_USER_PASS = uint8(0x02) + Method_IANA = uint8(0x7F) + Method_RESVERVE = uint8(0x80) + Method_NONE_ACCEPTABLE = uint8(0xFF) + VERSION_V5 = uint8(0x05) + CMD_CONNECT = uint8(0x01) + CMD_BIND = uint8(0x02) + CMD_ASSOCIATE = uint8(0x03) + ATYP_IPV4 = uint8(0x01) + ATYP_DOMAIN = uint8(0x03) + ATYP_IPV6 = uint8(0x04) + REP_SUCCESS = uint8(0x00) + REP_REQ_FAIL = uint8(0x01) + REP_RULE_FORBIDDEN = uint8(0x02) + REP_NETWOR_UNREACHABLE = uint8(0x03) + REP_HOST_UNREACHABLE = uint8(0x04) + REP_CONNECTION_REFUSED = uint8(0x05) + REP_TTL_TIMEOUT = uint8(0x06) + REP_CMD_UNSUPPORTED = uint8(0x07) + REP_ATYP_UNSUPPORTED = uint8(0x08) + REP_UNKNOWN = uint8(0x09) + RSV = uint8(0x00) +) + +var ( + ZERO_IP = []byte{0x00, 0x00, 0x00, 0x00} + ZERO_PORT = []byte{0x00, 0x00} +) + +type ServerConn struct { + target string + user string + password string + conn *net.Conn + timeout time.Duration + auth *utils.BasicAuth + header []byte + ver uint8 + //method + methodsCount uint8 + methods []uint8 + method uint8 + //request + cmd uint8 + reserve uint8 + addressType uint8 + dstAddr string + dstPort string + dstHost string + udpAddress string +} + +func NewServerConn(conn *net.Conn, timeout time.Duration, auth *utils.BasicAuth, udpAddress string, header []byte) *ServerConn { + if udpAddress == "" { + udpAddress = "0.0.0.0:16666" + } + s := &ServerConn{ + conn: conn, + timeout: timeout, + auth: auth, + header: header, + ver: VERSION_V5, + udpAddress: udpAddress, + } + return s + +} +func (s *ServerConn) Close() { + utils.CloseConn(s.conn) +} +func (s *ServerConn) AuthData() Auth { + return Auth{s.user, s.password} +} +func (s *ServerConn) Method() uint8 { + return s.method +} +func (s *ServerConn) Target() string { + return s.target +} +func (s *ServerConn) Handshake() (err error) { + remoteAddr := (*s.conn).RemoteAddr() + //协商开始 + //method select request + var methodReq MethodsRequest + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + + methodReq, e := NewMethodsRequest((*s.conn), s.header) + (*s.conn).SetReadDeadline(time.Time{}) + if e != nil { + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + methodReq.Reply(Method_NONE_ACCEPTABLE) + (*s.conn).SetReadDeadline(time.Time{}) + err = fmt.Errorf("new methods request fail,ERR: %s", e) + return + } + if s.auth == nil { + if !methodReq.Select(Method_NO_AUTH) { + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + methodReq.Reply(Method_NONE_ACCEPTABLE) + (*s.conn).SetReadDeadline(time.Time{}) + err = fmt.Errorf("none method found : Method_NO_AUTH") + return + } + s.method = Method_NO_AUTH + //method select reply + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + err = methodReq.Reply(Method_NO_AUTH) + (*s.conn).SetReadDeadline(time.Time{}) + if err != nil { + err = fmt.Errorf("reply answer data fail,ERR: %s", err) + return + } + // err = fmt.Errorf("% x", methodReq.Bytes()) + } else { + //auth + if !methodReq.Select(Method_USER_PASS) { + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + methodReq.Reply(Method_NONE_ACCEPTABLE) + (*s.conn).SetReadDeadline(time.Time{}) + err = fmt.Errorf("none method found : Method_USER_PASS") + return + } + s.method = Method_USER_PASS + //method reply need auth + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + err = methodReq.Reply(Method_USER_PASS) + (*s.conn).SetReadDeadline(time.Time{}) + if err != nil { + err = fmt.Errorf("reply answer data fail,ERR: %s", err) + return + } + //read auth + buf := make([]byte, 500) + var n int + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + n, err = (*s.conn).Read(buf) + (*s.conn).SetReadDeadline(time.Time{}) + if err != nil { + err = fmt.Errorf("read auth info fail,ERR: %s", err) + return + } + r := buf[:n] + s.user = string(r[2 : r[1]+2]) + s.password = string(r[2+r[1]+1:]) + //err = fmt.Errorf("user:%s,pass:%s", user, pass) + //auth + _addr := strings.Split(remoteAddr.String(), ":") + if s.auth.CheckUserPass(s.user, s.password, _addr[0], "") { + (*s.conn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(s.timeout))) + _, err = (*s.conn).Write([]byte{0x01, 0x00}) + (*s.conn).SetDeadline(time.Time{}) + if err != nil { + err = fmt.Errorf("answer auth success to %s fail,ERR: %s", remoteAddr, err) + return + } + } else { + (*s.conn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(s.timeout))) + _, err = (*s.conn).Write([]byte{0x01, 0x01}) + (*s.conn).SetDeadline(time.Time{}) + if err != nil { + err = fmt.Errorf("answer auth fail to %s fail,ERR: %s", remoteAddr, err) + return + } + err = fmt.Errorf("auth fail from %s", remoteAddr) + return + } + } + //request detail + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + request, e := NewRequest(*s.conn) + (*s.conn).SetReadDeadline(time.Time{}) + if e != nil { + err = fmt.Errorf("read request data fail,ERR: %s", e) + return + } + //协商结束 + + switch request.CMD() { + case CMD_BIND: + err = request.TCPReply(REP_UNKNOWN) + if err != nil { + err = fmt.Errorf("TCPReply REP_UNKNOWN to %s fail,ERR: %s", remoteAddr, err) + return + } + err = fmt.Errorf("cmd bind not supported, form: %s", remoteAddr) + return + case CMD_CONNECT: + err = request.TCPReply(REP_SUCCESS) + if err != nil { + err = fmt.Errorf("TCPReply REP_SUCCESS to %s fail,ERR: %s", remoteAddr, err) + return + } + case CMD_ASSOCIATE: + err = request.UDPReply(REP_SUCCESS, s.udpAddress) + if err != nil { + err = fmt.Errorf("UDPReply REP_SUCCESS to %s fail,ERR: %s", remoteAddr, err) + return + } + } + + //fill socks info + s.target = request.Addr() + s.methodsCount = methodReq.MethodsCount() + s.methods = methodReq.Methods() + s.cmd = request.CMD() + s.reserve = request.reserve + s.addressType = request.addressType + s.dstAddr = request.dstAddr + s.dstHost = request.dstHost + s.dstPort = request.dstPort + return +} diff --git a/utils/socks/structs.go b/utils/socks/structs.go index ef5b51b..a5ec922 100644 --- a/utils/socks/structs.go +++ b/utils/socks/structs.go @@ -3,44 +3,13 @@ package socks import ( "bytes" "encoding/binary" + "errors" "fmt" "io" "net" "strconv" ) -const ( - Method_NO_AUTH = uint8(0x00) - Method_GSSAPI = uint8(0x01) - Method_USER_PASS = uint8(0x02) - Method_IANA = uint8(0x7F) - Method_RESVERVE = uint8(0x80) - Method_NONE_ACCEPTABLE = uint8(0xFF) - VERSION_V5 = uint8(0x05) - CMD_CONNECT = uint8(0x01) - CMD_BIND = uint8(0x02) - CMD_ASSOCIATE = uint8(0x03) - ATYP_IPV4 = uint8(0x01) - ATYP_DOMAIN = uint8(0x03) - ATYP_IPV6 = uint8(0x04) - REP_SUCCESS = uint8(0x00) - REP_REQ_FAIL = uint8(0x01) - REP_RULE_FORBIDDEN = uint8(0x02) - REP_NETWOR_UNREACHABLE = uint8(0x03) - REP_HOST_UNREACHABLE = uint8(0x04) - REP_CONNECTION_REFUSED = uint8(0x05) - REP_TTL_TIMEOUT = uint8(0x06) - REP_CMD_UNSUPPORTED = uint8(0x07) - REP_ATYP_UNSUPPORTED = uint8(0x08) - REP_UNKNOWN = uint8(0x09) - RSV = uint8(0x00) -) - -var ( - ZERO_IP = []byte{0x00, 0x00, 0x00, 0x00} - ZERO_PORT = []byte{0x00, 0x00} -) - type Request struct { ver uint8 cmd uint8 @@ -57,7 +26,7 @@ func NewRequest(rw io.ReadWriter, header ...[]byte) (req Request, err interface{ var b = make([]byte, 1024) var n int req = Request{rw: rw} - if len(header) == 1 { + if header != nil && len(header) == 1 && len(header[0]) > 1 { b = header[0] n = len(header[0]) } else { @@ -71,7 +40,6 @@ func NewRequest(rw io.ReadWriter, header ...[]byte) (req Request, err interface{ req.cmd = uint8(b[1]) req.reserve = uint8(b[2]) req.addressType = uint8(b[3]) - if b[0] != 0x5 { err = fmt.Errorf("sosck version supported") req.TCPReply(REP_REQ_FAIL) @@ -129,7 +97,7 @@ func (s *Request) NewReply(rep uint8, addr string) []byte { ipv6[4], ipv6[5], ipv6[6], ipv6[7], ipv6[8], ipv6[9], ipv6[10], ipv6[11], ) - if ipv6 != nil && "0000000000255255" != zeroiIPv6 { + if ipb == nil && ipv6 != nil && "0000000000255255" != zeroiIPv6 { atyp = ATYP_IPV6 ipb = ip.To16() } @@ -165,7 +133,7 @@ func NewMethodsRequest(r io.ReadWriter, header ...[]byte) (s MethodsRequest, err s.rw = &r var buf = make([]byte, 300) var n int - if len(header) == 1 { + if header != nil && len(header) == 1 && len(header[0]) > 1 { buf = header[0] n = len(header[0]) } else { @@ -182,7 +150,6 @@ func NewMethodsRequest(r io.ReadWriter, header ...[]byte) (s MethodsRequest, err err = fmt.Errorf("socks methods data length error") return } - s.ver = buf[0] s.methodsCount = buf[1] s.methods = buf[2:n] @@ -195,6 +162,9 @@ func (s *MethodsRequest) Version() uint8 { func (s *MethodsRequest) MethodsCount() uint8 { return s.methodsCount } +func (s *MethodsRequest) Methods() []uint8 { + return s.methods +} func (s *MethodsRequest) Select(method uint8) bool { for _, m := range s.methods { if m == method { @@ -211,17 +181,6 @@ func (s *MethodsRequest) Bytes() []byte { return s.bytes } -type UDPPacket struct { - rsv uint16 - frag uint8 - atype uint8 - dstHost string - dstPort string - data []byte - header []byte - bytes []byte -} - func ParseUDPPacket(b []byte) (p UDPPacket, err error) { p = UDPPacket{} p.frag = uint8(b[2]) @@ -249,6 +208,18 @@ func ParseUDPPacket(b []byte) (p UDPPacket, err error) { p.header = b[:portIndex+2] return } + +type UDPPacket struct { + rsv uint16 + frag uint8 + atype uint8 + dstHost string + dstPort string + data []byte + header []byte + bytes []byte +} + func (s *UDPPacket) Header() []byte { return s.header } @@ -268,3 +239,104 @@ func (s *UDPPacket) Port() string { func (s *UDPPacket) Data() []byte { return s.data } + +type PacketUDP struct { + rsv uint16 + frag uint8 + atype uint8 + dstHost string + dstPort string + data []byte +} + +func NewPacketUDP() (p PacketUDP) { + return PacketUDP{} +} +func (p *PacketUDP) Build(destAddr string, data []byte) (err error) { + host, port, err := net.SplitHostPort(destAddr) + if err != nil { + return + } + p.rsv = 0 + p.frag = 0 + p.dstHost = host + p.dstPort = port + p.atype = ATYP_IPV4 + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + p.atype = ATYP_IPV4 + ip = ip4 + } else { + p.atype = ATYP_IPV6 + } + } else { + if len(host) > 255 { + err = errors.New("proxy: destination host name too long: " + host) + return + } + p.atype = ATYP_DOMAIN + } + p.data = data + + return +} +func (p *PacketUDP) Parse(b []byte) (err error) { + p.frag = uint8(b[2]) + if p.frag != 0 { + err = fmt.Errorf("FRAG only support for 0 , %v ,%v", p.frag, b[:4]) + return + } + portIndex := 0 + p.atype = b[3] + switch p.atype { + case ATYP_IPV4: //IP V4 + p.dstHost = net.IPv4(b[4], b[5], b[6], b[7]).String() + portIndex = 8 + case ATYP_DOMAIN: //域名 + domainLen := uint8(b[4]) + p.dstHost = string(b[5 : 5+domainLen]) //b[4]表示域名的长度 + portIndex = int(5 + domainLen) + case ATYP_IPV6: //IP V6 + p.dstHost = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String() + portIndex = 20 + } + p.dstPort = strconv.Itoa(int(b[portIndex])<<8 | int(b[portIndex+1])) + p.data = b[portIndex+2:] + return +} +func (p *PacketUDP) Header() []byte { + header := new(bytes.Buffer) + header.Write([]byte{0x00, 0x00, p.frag, p.atype}) + if p.atype == ATYP_IPV4 { + ip := net.ParseIP(p.dstHost) + header.Write(ip.To4()) + } else if p.atype == ATYP_IPV6 { + ip := net.ParseIP(p.dstHost) + header.Write(ip.To16()) + } else if p.atype == ATYP_DOMAIN { + hBytes := []byte(p.dstHost) + header.WriteByte(byte(len(hBytes))) + header.Write(hBytes) + } + port, _ := strconv.ParseUint(p.dstPort, 10, 64) + portBytes := new(bytes.Buffer) + binary.Write(portBytes, binary.BigEndian, port) + header.Write(portBytes.Bytes()[portBytes.Len()-2:]) + return header.Bytes() +} +func (p *PacketUDP) Bytes() []byte { + packBytes := new(bytes.Buffer) + packBytes.Write(p.Header()) + packBytes.Write(p.data) + return packBytes.Bytes() +} +func (p *PacketUDP) Host() string { + return p.dstHost +} + +func (p *PacketUDP) Port() string { + return p.dstPort +} +func (p *PacketUDP) Data() []byte { + return p.data +} From 4a34566f087c6ea845ff3c4c33bbf7e7935cb318 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:14:06 +0800 Subject: [PATCH 09/19] Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 6 ++ README_ZH.md | 45 +++++++++- config.go | 9 +- services/args.go | 8 ++ services/sps.go | 204 ++++++++++++++++++++++++------------------ utils/socks/client.go | 2 +- utils/socks/server.go | 19 ++-- utils/structs.go | 23 ++--- 8 files changed, 206 insertions(+), 110 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 020bbda..38e7efa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,10 @@ proxy更新日志 +v4.6 +1.sps,http(s),socks5,内网穿透都做了大量的超时优化处理,更加稳定. +2.sps增加了强大的树形级联认证支持,可以轻松构建你的认证代理网络. +3.手册增加了6.6对sps认证功能的介绍. + + v4.5 1.优化了mux内网穿透连接管理逻辑,增强了稳定性. 2.mux内网穿透增加了tcp和kcp协议支持,之前是tls,现在支持三种协议tcp,tls,kcp. diff --git a/README_ZH.md b/README_ZH.md index dea5c5e..d08694d 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -127,7 +127,8 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - [6.3 SOCKS5转HTTP(S)+SOCKS5](#63-socks5转httpssocks5) - [6.4 链式连接](#64-链式连接) - [6.5 监听多个端口](#65-监听多个端口) - - [6.6 查看帮助](#66-查看帮助) + - [6.6 认证功能](#66-认证功能) + - [6.7 查看帮助](#67-查看帮助) - [7. KCP配置](#7kcp配置) - [7.1 配置介绍](#71-配置介绍) - [7.2 详细配置](#72-详细配置) @@ -736,7 +737,47 @@ vps02:3.3.3.3 一般情况下监听一个端口就可以,不过如果作为反向代理需要同时监听80和443两个端口,那么-p参数是支持的, 格式是:`-p 0.0.0.0:80,0.0.0.0:443`,多个绑定用逗号分隔即可。 -#### **6.6 查看帮助** +#### **6.6 认证功能** +sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: +1:用户发送认证信息`user-auth`。 +2:设置的本地认证信息`local-auth`。 +3:设置的连接上级使用的认证信息`parent-auth`。 +4:最终发送给上级的认证信息`auth-info-to-parent`。 +他们的情况关系如下: +| user-auth | local-auth | parent-auth | auth-info-to-parent +| 有/没有 | 有 | 有 | 来自parent-auth +| 有/没有 | 没有 | 有 | 来自parent-auth +| 有/没有 | 有 | 没有 | 无 +| 没有 | 没有 | 没有 | 无 +| 有 | 没有 | 没有 | 来自user-auth + +对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 +`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` +多个用户,重复-a参数即可. +也可以放在文件中,格式是一行一个"用户名:密码",然后用-F指定. +`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -F auth-file.txt` + +如果上级有认证,下级可以通过-A参数设置认证信息,比如: +上级:`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` +下级:`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -A "user1:pass1" -t tcp -p ":33080" ` + +另外,sps代理,本地认证集成了外部HTTP API认证,我们可以通过--auth-url参数指定一个http url接口地址, +然后有用户连接的时候,proxy会GET方式请求这url,带上下面四个参数,如果返回HTTP状态码204,代表认证成功 +其它情况认为认证失败. +比如: +`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" --auth-url "http://test.com/auth.php"` +用户连接的时候,proxy会GET方式请求这url("http://test.com/auth.php"), +带上user,pass,ip,三个参数: +http://test.com/auth.php?user={USER}&pass={PASS}&ip={IP}&target={TARGET} +user:用户名 +pass:密码 +ip:用户的IP,比如:192.168.1.200 +target:如果客户端是http(s)代理请求,这里代表的是请求的完整url,其它情况为空. + +如果没有-a或-F或--auth-url参数,就是关闭本地认证. +如果没有-A参数,连接上级不使用认证. + +#### **6.7 查看帮助** `./proxy help sps` ### **7.KCP配置** diff --git a/config.go b/config.go index 5d5e671..70f0a4a 100755 --- a/config.go +++ b/config.go @@ -223,7 +223,14 @@ func initConfig() (err error) { spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type ").Short('S').Enum("http", "socks") 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() + spsArgs.Auth = sps.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings() + spsArgs.LocalIPS = sps.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings() + spsArgs.AuthURL = sps.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String() + spsArgs.AuthURLTimeout = sps.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int() + spsArgs.AuthURLOkCode = sps.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int() + spsArgs.AuthURLRetry = sps.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int() + spsArgs.ParentAuth = sps.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String() //parse args serviceName := kingpin.MustParse(app.Parse(os.Args[1:])) diff --git a/services/args.go b/services/args.go index 2786f79..68ccb78 100644 --- a/services/args.go +++ b/services/args.go @@ -215,6 +215,14 @@ type SPSArgs struct { ParentServiceType *string DNSAddress *string DNSTTL *int + AuthFile *string + Auth *[]string + AuthURL *string + AuthURLOkCode *int + AuthURLTimeout *int + AuthURLRetry *int + LocalIPS *[]string + ParentAuth *string } func (a *SPSArgs) Protocol() string { diff --git a/services/sps.go b/services/sps.go index 1b5de03..590e6d9 100644 --- a/services/sps.go +++ b/services/sps.go @@ -2,6 +2,7 @@ package services import ( "bytes" + "encoding/base64" "errors" "fmt" "io/ioutil" @@ -12,18 +13,21 @@ import ( "snail007/proxy/utils/socks" "strconv" "strings" + "time" ) type SPS struct { outPool utils.OutPool cfg SPSArgs domainResolver utils.DomainResolver + basicAuth utils.BasicAuth } func NewSPS() Service { return &SPS{ - outPool: utils.OutPool{}, - cfg: SPSArgs{}, + outPool: utils.OutPool{}, + cfg: SPSArgs{}, + basicAuth: utils.BasicAuth{}, } } func (s *SPS) CheckArgs() { @@ -46,6 +50,10 @@ func (s *SPS) CheckArgs() { } func (s *SPS) InitService() { s.InitOutConnPool() + if *s.cfg.DNSAddress != "" { + (*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL) + } + s.InitBasicAuth() } func (s *SPS) InitOutConnPool() { if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP { @@ -117,7 +125,7 @@ func (s *SPS) callback(inConn net.Conn) { err = fmt.Errorf("unkown parent type %s", *s.cfg.ParentType) } if err != nil { - log.Printf("connect to %s parent %s fail, ERR:%s", *s.cfg.ParentType, *s.cfg.Parent, err) + log.Printf("connect to %s parent %s fail, ERR:%s from %s", *s.cfg.ParentType, *s.cfg.Parent, err, inConn.RemoteAddr()) utils.CloseConn(&inConn) } } @@ -131,51 +139,32 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { return } address := "" + var auth socks.Auth var forwardBytes []byte //fmt.Printf("%v", header) if header[0] == socks.VERSION_V5 { - //socks - methodReq, e := socks.NewMethodsRequest(*inConn, header) - if e != nil { - log.Printf("new method request err:%s", e) - utils.CloseConn(inConn) - err = e.(error) + //socks5 server + var serverConn *socks.ServerConn + if s.IsBasicAuth() { + serverConn = socks.NewServerConn(inConn, time.Millisecond*time.Duration(*s.cfg.Timeout), &s.basicAuth, "", header) + } else { + serverConn = socks.NewServerConn(inConn, time.Millisecond*time.Duration(*s.cfg.Timeout), nil, "", header) + } + if err = serverConn.Handshake(); err != nil { return } - if !methodReq.Select(socks.Method_NO_AUTH) { - methodReq.Reply(socks.Method_NONE_ACCEPTABLE) - utils.CloseConn(inConn) - log.Printf("none method found : Method_NO_AUTH") - return - } - //method select reply - err = methodReq.Reply(socks.Method_NO_AUTH) - if err != nil { - log.Printf("reply answer data fail,ERR: %s", err) - utils.CloseConn(inConn) - return - } - //request detail - request, e := socks.NewRequest(*inConn) - if e != nil { - log.Printf("read request data fail,ERR: %s", e) - utils.CloseConn(inConn) - err = e.(error) - return - } - if request.CMD() != socks.CMD_CONNECT { - //只支持tcp - request.TCPReply(socks.REP_UNKNOWN) - utils.CloseConn(inConn) - err = errors.New("cmd not supported") - return - } - address = request.Addr() - request.TCPReply(socks.REP_SUCCESS) + address = serverConn.Target() + auth = serverConn.AuthData() } else if bytes.IndexByte(header, '\n') != -1 { //http var request utils.HTTPRequest - request, err = utils.NewHTTPRequest(inConn, 1024, false, nil, header) + (*inConn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) + if s.IsBasicAuth() { + request, err = utils.NewHTTPRequest(inConn, 1024, true, &s.basicAuth, header) + } else { + request, err = utils.NewHTTPRequest(inConn, 1024, false, nil, header) + } + (*inConn).SetDeadline(time.Time{}) if err != nil { log.Printf("new http request fail,ERR: %s", err) utils.CloseConn(inConn) @@ -189,6 +178,17 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { forwardBytes = request.HeadBuf } address = request.Host + var userpass string + if s.IsBasicAuth() { + userpass, err = request.GetAuthDataStr() + if err != nil { + return + } + userpassA := strings.Split(userpass, ":") + if len(userpassA) == 2 { + auth = socks.Auth{User: userpassA[0], Password: userpassA[1]} + } + } } else { log.Printf("unknown request from: %s,%s", (*inConn).RemoteAddr(), string(header)) utils.CloseConn(inConn) @@ -207,12 +207,42 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { utils.CloseConn(inConn) return } + //ask parent for connect to target address if *s.cfg.ParentServiceType == "http" { //http parent - fmt.Fprintf(outConn, "CONNECT %s HTTP/1.1\r\n", address) + pb := new(bytes.Buffer) + pb.Write([]byte(fmt.Sprintf("CONNECT %s HTTP/1.1\r\nProxy-Connection: Keep-Alive\r\n", address))) + //Proxy-Authorization:\r\n + u := "" + if *s.cfg.ParentAuth != "" { + a := strings.Split(*s.cfg.ParentAuth, ":") + if len(a) != 2 { + err = fmt.Errorf("parent auth data format error") + return + } + u = fmt.Sprintf("%s:%s", a[0], a[1]) + } else { + if !s.IsBasicAuth() && auth.Password != "" && auth.User != "" { + u = fmt.Sprintf("%s:%s", auth.User, auth.Password) + } + } + if u != "" { + pb.Write([]byte(fmt.Sprintf("Proxy-Authorization:Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(u))))) + } + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) + _, err = outConn.Write(pb.Bytes()) + outConn.SetDeadline(time.Time{}) + if err != nil { + log.Printf("write CONNECT to %s , err:%s", *s.cfg.Parent, err) + utils.CloseConn(inConn) + utils.CloseConn(&outConn) + return + } reply := make([]byte, 100) - n, err = outConn.Read(reply) + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) + _, err = outConn.Read(reply) + outConn.SetDeadline(time.Time{}) if err != nil { log.Printf("read reply from %s , err:%s", *s.cfg.Parent, err) utils.CloseConn(inConn) @@ -222,53 +252,25 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { //log.Printf("reply: %s", string(reply[:n])) } else { log.Printf("connect %s", address) - //socks parent - //send auth type - _, err = outConn.Write([]byte{0x05, 0x01, 0x00}) - if err != nil { - log.Printf("write method to %s fail, err:%s", *s.cfg.Parent, err) - utils.CloseConn(inConn) - utils.CloseConn(&outConn) + //socks client + var clientConn *socks.ClientConn + if *s.cfg.ParentAuth != "" { + a := strings.Split(*s.cfg.ParentAuth, ":") + if len(a) != 2 { + err = fmt.Errorf("parent auth data format error") + return + } + clientConn = socks.NewClientConn(&outConn, "tcp", address, time.Millisecond*time.Duration(*s.cfg.Timeout), &socks.Auth{User: a[0], Password: a[1]}, header) + } else { + if !s.IsBasicAuth() && auth.Password != "" && auth.User != "" { + clientConn = socks.NewClientConn(&outConn, "tcp", address, time.Millisecond*time.Duration(*s.cfg.Timeout), &auth, header) + } else { + clientConn = socks.NewClientConn(&outConn, "tcp", address, time.Millisecond*time.Duration(*s.cfg.Timeout), nil, header) + } + } + if err = clientConn.Handshake(); err != nil { return } - //read reply - reply := make([]byte, 512) - n, err = outConn.Read(reply) - if err != nil { - log.Printf("read reply from %s , err:%s", *s.cfg.Parent, err) - utils.CloseConn(inConn) - utils.CloseConn(&outConn) - return - } - //log.Printf("method reply %v", reply[:n]) - - //build request - buf, err = s.buildRequest(address) - if err != nil { - log.Printf("build request to %s fail , err:%s", *s.cfg.Parent, err) - utils.CloseConn(inConn) - utils.CloseConn(&outConn) - return - } - //send address request - _, err = outConn.Write(buf) - if err != nil { - log.Printf("write request to %s fail, err:%s", *s.cfg.Parent, err) - utils.CloseConn(inConn) - utils.CloseConn(&outConn) - return - } - //read reply - reply = make([]byte, 512) - n, err = outConn.Read(reply) - if err != nil { - log.Printf("read reply from %s , err:%s", *s.cfg.Parent, err) - utils.CloseConn(inConn) - utils.CloseConn(&outConn) - return - } - - //log.Printf("request reply %v", reply[:n]) } //forward client data to target,if necessary. if len(forwardBytes) > 0 { @@ -283,6 +285,34 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { log.Printf("conn %s - %s connected", inAddr, outAddr) return } +func (s *SPS) InitBasicAuth() (err error) { + if *s.cfg.DNSAddress != "" { + s.basicAuth = utils.NewBasicAuth(&(*s).domainResolver) + } else { + s.basicAuth = utils.NewBasicAuth(nil) + } + if *s.cfg.AuthURL != "" { + s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry) + log.Printf("auth from %s", *s.cfg.AuthURL) + } + if *s.cfg.AuthFile != "" { + var n = 0 + n, err = s.basicAuth.AddFromFile(*s.cfg.AuthFile) + if err != nil { + err = fmt.Errorf("auth-file ERR:%s", err) + return + } + log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total()) + } + if len(*s.cfg.Auth) > 0 { + n := s.basicAuth.Add(*s.cfg.Auth) + log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total()) + } + return +} +func (s *SPS) IsBasicAuth() bool { + return *s.cfg.AuthFile != "" || len(*s.cfg.Auth) > 0 || *s.cfg.AuthURL != "" +} func (s *SPS) buildRequest(address string) (buf []byte, err error) { host, portStr, err := net.SplitHostPort(address) if err != nil { diff --git a/utils/socks/client.go b/utils/socks/client.go index 7253d21..619e3c7 100644 --- a/utils/socks/client.go +++ b/utils/socks/client.go @@ -63,7 +63,7 @@ func NewClientConn(conn *net.Conn, network, target string, timeout time.Duration // connect takes an existing connection to a socks5 proxy server, // and commands the server to extend that connection to target, // which must be a canonical address with a host and port. -func (s *ClientConn) Connect() error { +func (s *ClientConn) Handshake() error { host, portStr, err := net.SplitHostPort(s.addr) if err != nil { return err diff --git a/utils/socks/server.go b/utils/socks/server.go index 121798a..3cf00be 100644 --- a/utils/socks/server.go +++ b/utils/socks/server.go @@ -106,14 +106,15 @@ func (s *ServerConn) Handshake() (err error) { err = fmt.Errorf("new methods request fail,ERR: %s", e) return } - if s.auth == nil { - if !methodReq.Select(Method_NO_AUTH) { - (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) - methodReq.Reply(Method_NONE_ACCEPTABLE) - (*s.conn).SetReadDeadline(time.Time{}) - err = fmt.Errorf("none method found : Method_NO_AUTH") - return - } + //log.Printf("%v,s.auth == %v && methodReq.Select(Method_NO_AUTH) %v", methodReq.methods, s.auth, methodReq.Select(Method_NO_AUTH)) + if s.auth == nil && methodReq.Select(Method_NO_AUTH) && !methodReq.Select(Method_USER_PASS) { + // if !methodReq.Select(Method_NO_AUTH) { + // (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + // methodReq.Reply(Method_NONE_ACCEPTABLE) + // (*s.conn).SetReadDeadline(time.Time{}) + // err = fmt.Errorf("none method found : Method_NO_AUTH") + // return + // } s.method = Method_NO_AUTH //method select reply (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) @@ -158,7 +159,7 @@ func (s *ServerConn) Handshake() (err error) { //err = fmt.Errorf("user:%s,pass:%s", user, pass) //auth _addr := strings.Split(remoteAddr.String(), ":") - if s.auth.CheckUserPass(s.user, s.password, _addr[0], "") { + if s.auth == nil || s.auth.CheckUserPass(s.user, s.password, _addr[0], "") { (*s.conn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(s.timeout))) _, err = (*s.conn).Write([]byte{0x01, 0x00}) (*s.conn).SetDeadline(time.Time{}) diff --git a/utils/structs.go b/utils/structs.go index dc8dcfd..764622d 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -317,7 +317,7 @@ func NewHTTPRequest(inConn *net.Conn, bufSize int, isBasicAuth bool, basicAuth * req = HTTPRequest{ conn: inConn, } - if len(header) == 1 { + if header != nil && len(header) == 1 && len(header[0]) > 1 { buf = header[0] n = len(header[0]) } else { @@ -402,18 +402,13 @@ func (req *HTTPRequest) IsHTTPS() bool { return req.Method == "CONNECT" } -func (req *HTTPRequest) BasicAuth() (err error) { - +func (req *HTTPRequest) GetAuthDataStr() (basicInfo string, err error) { // log.Printf("request :%s", string(req.HeadBuf)) - code := "407" authorization := req.getHeader("Proxy-Authorization") - // if authorization == "" { - // authorization = req.getHeader("Authorization") - // code = "401" - // } + authorization = strings.Trim(authorization, " \r\n\t") if authorization == "" { - fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\n\r\nUnauthorized", code) + fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\n\r\nUnauthorized", "407") CloseConn(req.conn) err = errors.New("require auth header data") return @@ -431,6 +426,10 @@ func (req *HTTPRequest) BasicAuth() (err error) { CloseConn(req.conn) return } + basicInfo = string(user) + return +} +func (req *HTTPRequest) BasicAuth() (err error) { addr := strings.Split((*req.conn).RemoteAddr().String(), ":") URL := "" if req.IsHTTPS() { @@ -438,10 +437,14 @@ func (req *HTTPRequest) BasicAuth() (err error) { } else { URL = req.getHTTPURL() } + user, err := req.GetAuthDataStr() + if err != nil { + return + } authOk := (*req.basicAuth).Check(string(user), addr[0], URL) //log.Printf("auth %s,%v", string(user), authOk) if !authOk { - fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\n\r\nUnauthorized", code) + fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\n\r\nUnauthorized", "407") CloseConn(req.conn) err = fmt.Errorf("basic auth fail") return From 59b5ef2df4795708bc830f7a887bd729002abef8 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:21:02 +0800 Subject: [PATCH 10/19] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index d08694d..0ebd5d9 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -743,13 +743,14 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: 2:设置的本地认证信息`local-auth`。 3:设置的连接上级使用的认证信息`parent-auth`。 4:最终发送给上级的认证信息`auth-info-to-parent`。 -他们的情况关系如下: -| user-auth | local-auth | parent-auth | auth-info-to-parent -| 有/没有 | 有 | 有 | 来自parent-auth -| 有/没有 | 没有 | 有 | 来自parent-auth -| 有/没有 | 有 | 没有 | 无 -| 没有 | 没有 | 没有 | 无 -| 有 | 没有 | 没有 | 来自user-auth +他们的情况关系如下: +user-auth | local-auth | parent-auth | auth-info-to-parent +:-:| :-: | :-: |:-: +有/没有 | 有 | 有 | 来自parent-auth +有/没有 | 没有 | 有 | 来自parent-auth +有/没有 | 有 | 没有 | 无 +没有 | 没有 | 没有 | 无 +有 | 没有 | 没有 | 来自user-auth 对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` From dd52ad8a8ade9932b1d5da53cdaee0c994b6317b Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:22:37 +0800 Subject: [PATCH 11/19] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 0ebd5d9..70c9f6c 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -744,13 +744,13 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: 3:设置的连接上级使用的认证信息`parent-auth`。 4:最终发送给上级的认证信息`auth-info-to-parent`。 他们的情况关系如下: -user-auth | local-auth | parent-auth | auth-info-to-parent -:-:| :-: | :-: |:-: -有/没有 | 有 | 有 | 来自parent-auth -有/没有 | 没有 | 有 | 来自parent-auth -有/没有 | 有 | 没有 | 无 -没有 | 没有 | 没有 | 无 -有 | 没有 | 没有 | 来自user-auth +|user-auth | local-auth | parent-auth | auth-info-to-parent| +|:-:| :-: | :-: |:-:| +|有/没有 | 有 | 有 | 来自parent-auth| +|有/没有 | 没有 | 有 | 来自parent-auth| +|有/没有 | 有 | 没有 | 无| +|没有 | 没有 | 没有 | 无| +|有 | 没有 | 没有 | 来自user-auth| 对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` From 69b65a37caab7d3863248b589540a13826d1b7ed Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:25:17 +0800 Subject: [PATCH 12/19] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 70c9f6c..1731022 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -743,14 +743,14 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: 2:设置的本地认证信息`local-auth`。 3:设置的连接上级使用的认证信息`parent-auth`。 4:最终发送给上级的认证信息`auth-info-to-parent`。 -他们的情况关系如下: -|user-auth | local-auth | parent-auth | auth-info-to-parent| -|:-:| :-: | :-: |:-:| -|有/没有 | 有 | 有 | 来自parent-auth| -|有/没有 | 没有 | 有 | 来自parent-auth| -|有/没有 | 有 | 没有 | 无| -|没有 | 没有 | 没有 | 无| -|有 | 没有 | 没有 | 来自user-auth| +他们的情况关系如下: +| user-auth | local-auth | parent-auth | auth-info-to-paren +| ------ | ------ | ------ | ------ +| 有/没有 | 有 | 有 | 来自parent-auth +| 有/没有 | 没有 | 有 | 来自parent-auth +| 有/没有 | 有 | 没有 | 无 +| 没有 | 没有 | 没有 | 无 +| 有 | 没有 | 没有 | 来自user-auth 对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` From bd056d74cca6bdda6a9ea78fb314841854b64a82 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:25:47 +0800 Subject: [PATCH 13/19] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README_ZH.md b/README_ZH.md index 1731022..7946109 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -744,6 +744,7 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: 3:设置的连接上级使用的认证信息`parent-auth`。 4:最终发送给上级的认证信息`auth-info-to-parent`。 他们的情况关系如下: + | user-auth | local-auth | parent-auth | auth-info-to-paren | ------ | ------ | ------ | ------ | 有/没有 | 有 | 有 | 来自parent-auth From d911be7d8053ecb7e2b997761ff396cbe8b7b0ea Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:27:59 +0800 Subject: [PATCH 14/19] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 7946109..522c58b 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -753,17 +753,17 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: | 没有 | 没有 | 没有 | 无 | 有 | 没有 | 没有 | 来自user-auth -对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 +对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` 多个用户,重复-a参数即可. 也可以放在文件中,格式是一行一个"用户名:密码",然后用-F指定. `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -F auth-file.txt` -如果上级有认证,下级可以通过-A参数设置认证信息,比如: +如果上级有认证,下级可以通过-A参数设置认证信息,比如: 上级:`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` 下级:`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -A "user1:pass1" -t tcp -p ":33080" ` -另外,sps代理,本地认证集成了外部HTTP API认证,我们可以通过--auth-url参数指定一个http url接口地址, +另外,sps代理,本地认证集成了外部HTTP API认证,我们可以通过--auth-url参数指定一个http url接口地址, 然后有用户连接的时候,proxy会GET方式请求这url,带上下面四个参数,如果返回HTTP状态码204,代表认证成功 其它情况认为认证失败. 比如: From b11c7e632b402036be9d94167cb9dc7bd11d3922 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:30:09 +0800 Subject: [PATCH 15/19] fix #47 Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_ZH.md b/README_ZH.md index 522c58b..5afc267 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -769,7 +769,7 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: 比如: `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" --auth-url "http://test.com/auth.php"` 用户连接的时候,proxy会GET方式请求这url("http://test.com/auth.php"), -带上user,pass,ip,三个参数: +带上user,pass,ip,target四个参数: http://test.com/auth.php?user={USER}&pass={PASS}&ip={IP}&target={TARGET} user:用户名 pass:密码 From 27ce6e1bd29d27328e89eb5860891143efce3684 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:34:08 +0800 Subject: [PATCH 16/19] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_ZH.md b/README_ZH.md index 5afc267..81fb1b0 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -23,7 +23,7 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - 集成外部API,HTTP(S),SOCKS5代理认证功能可以与外部HTTP API集成,可以方便的通过外部系统控制代理用户. - 反向代理,支持直接把域名解析到proxy监听的ip,然后proxy就会帮你代理访问需要访问的HTTP(S)网站. - 透明HTTP(S)代理,配合iptables,在网关直接把出去的80,443方向的流量转发到proxy,就能实现无感知的智能路由器代理. -- 协议转换,可以把已经存在的HTTP(S)或SOCKS5代理转换为一个端口同时支持HTTP(S)和SOCKS5代理,转换后的SOCKS5代理不支持UDP功能。 +- 协议转换,可以把已经存在的HTTP(S)或SOCKS5代理转换为一个端口同时支持HTTP(S)和SOCKS5代理,转换后的SOCKS5代理不支持UDP功能,同时支持强大的级联认证功能。 ### Why need these? - 当由于某某原因,我们不能访问我们在其它地方的服务,我们可以通过多个相连的proxy节点建立起一个安全的隧道访问我们的服务. From 3eff793ac228790b8b5763b6034f0f4a74ba786b Mon Sep 17 00:00:00 2001 From: yincongcyincong <648588267@qq.com> Date: Wed, 28 Mar 2018 13:53:09 +0800 Subject: [PATCH 17/19] Update README.md --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 66b689b..c8dde3c 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,8 @@ This page is the v4.4 manual, and the other version of the manual can be checked - [6.3 SOCKS5 to HTTP(S) + SOCKS5](#63socks5-to-http-socks5) - [6.4 Chain style connection](#64chain-style-connection) - [6.5 Listening on multiple ports](#65listening-on-multiple-ports) - - [6.6 View Help](#56transfer-through-ssh) + - [6.6 Authentication](#66authentication) + - [6.7 View Help](#67view-help) - [7.KCP Configuration](#7kcp-configuration) - [7.1 Configuration introduction](#71configuration-introduction) - [7.2 Configuration details](#72configuration-details) @@ -730,7 +731,49 @@ finish。 In general, listening one port is enough, but if you need to monitor 80 and 443 ports at the same time as a reverse proxy, the -p parameter can support it. The format is:`-p 0.0.0.0:80,0.0.0.0:443`, Multiple bindings are separated by a comma. -#### **6.6.view help** +#### **6.6.Authentication** +SPS supports HTTP(s)\socks5 proxy authentication, which can concatenate authentication, there are four important information: +1:Users send authentication information`user-auth`。 +2:Local authentication information set up`local-auth`。 +3:Set the authentication information accessing to the father proxy`parent-auth`。 +4:The final authentication information sent to the father proxy`auth-info-to-parent`。 +The relationship between them is as follows: + +| user-auth | local-auth | parent-auth | auth-info-to-paren +| ------ | ------ | ------ | ------ +| yes/no | yes    |     yes   |   come from parent-auth   +| yes/no | no | yes | come from parent-auth +| yes/no | yes | no | no +| no | no | no | no +| yes | no | no | come from user-auth + +For SPS proxy we can have username and password to authenticate, and the authentication username and password can be specified on the command line +`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` +if there are multiple users, repeat the -a parameters. +It can also be placed in a file, which is a line to a username: password, and then specified in -F parameter. +`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -F auth-file.txt` + +If the father proxy is authenticated, the lower level can set the authentication information through the -A parameters, such as: +father proxy:`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` +local proxy:`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -A "user1:pass1" -t tcp -p ":33080" ` + +In addition, SPS proxy, local authentication is integrated with external HTTP API authentication, and we can specify a HTTP URL interface address through the --auth-url parameter, +Then, when there is a user connection, proxy will request this URL by GET way, with the following four parameters, and if the HTTP state code 204 is returned, the authentication is successful. +Other cases consider authentication failure. +for example: +`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" --auth-url "http://test.com/auth.php"` +When the user is connected, proxy will request this URL by GET way("http://test.com/auth.php"), +Four parameters with user, pass, IP, and target: +http://test.com/auth.php?user={USER}&pass={PASS}&ip={IP}&target={TARGET} +user:username +pass:password +ip:user's ip,for example:192.168.1.200 +target: if the client is the HTTP (s) proxy request, this represents the complete URL of the request, and the other cases are empty. + +If there is no -a or -F or --auth-url parameters, local authentication is closed. +If there is no -A parameter, the connection to the father proxy does not use authentication. + +#### **6.7.view help** `./proxy help sps` ### **7.KCP Configuration** From 2d8190873f8a03429df23e4637f625055d279cd2 Mon Sep 17 00:00:00 2001 From: yincongcyincong <648588267@qq.com> Date: Wed, 28 Mar 2018 13:56:02 +0800 Subject: [PATCH 18/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8dde3c..b4f73e6 100644 --- a/README.md +++ b/README.md @@ -741,7 +741,7 @@ The relationship between them is as follows: | user-auth | local-auth | parent-auth | auth-info-to-paren | ------ | ------ | ------ | ------ -| yes/no | yes    |     yes   |   come from parent-auth   +| yes/no | yes    | yes   | come from parent-auth   | yes/no | no | yes | come from parent-auth | yes/no | yes | no | no | no | no | no | no From a5d199fb1c95002bfc1b6aa723a99f36f30570bc Mon Sep 17 00:00:00 2001 From: yincongcyincong <648588267@qq.com> Date: Wed, 28 Mar 2018 14:06:19 +0800 Subject: [PATCH 19/19] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b4f73e6..f244458 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Proxy is a high performance HTTP, HTTPS, HTTPS, websocket, TCP, UDP, Socks5 prox - The integrated external API, HTTP (S): SOCKS5 proxy authentication can be integrated with the external HTTP API, which can easily control the user's access through the external system. - Reverse proxy: goproxy supports directly parsing the domain to proxy monitor IP, and then proxy will help you to access the HTTP (S) site that you need to access. - Transparent proxy: with the iptables, goproxy can directly forward the 80 and 443 port's traffic to proxy in the gateway, and can realize the unaware intelligent router proxy. -- Protocol conversion: The existing HTTP (S) or SOCKS5 proxy can be converted to a proxy which support both HTTP (S) and SOCKS5 by one port, but the converted SOCKS5 proxy does not support the UDP function.   +- Protocol conversion: The existing HTTP (S) or SOCKS5 proxy can be converted to a proxy which support both HTTP (S) and SOCKS5 by one port, but the converted SOCKS5 proxy does not support the UDP function.Also support powerful cascading authentication.   ### Why need these? - Because for some reason, we cannot access our services elsewhere. We can build a secure tunnel to access our services through multiple connected proxy nodes.   @@ -34,7 +34,9 @@ Proxy is a high performance HTTP, HTTPS, HTTPS, websocket, TCP, UDP, Socks5 prox - ...   -This page is the v4.4 manual, and the other version of the manual can be checked by the following link. +This page is the v4.6 manual, and the other version of the manual can be checked by the following link. +- [v4.5 manual](https://github.com/snail007/goproxy/tree/v4.5) +- [v4.4 manual](https://github.com/snail007/goproxy/tree/v4.4) - [v4.3 manual](https://github.com/snail007/goproxy/tree/v4.3) - [v4.2 manual](https://github.com/snail007/goproxy/tree/v4.2) - [v4.0-4.1 manual](https://github.com/snail007/goproxy/tree/v4.1) @@ -149,7 +151,7 @@ If the installation fails or your VPS is not a linux64 system, please follow the Download address: https://github.com/snail007/goproxy/releases ```shell cd /root/proxy/ -wget https://github.com/snail007/goproxy/releases/download/v4.4/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.6/proxy-linux-amd64.tar.gz ``` #### **2.Download the automatic installation script** ```shell