diff --git a/README_ZH.md b/README_ZH.md index 4a9316f..3e4f5b4 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -1,5 +1,5 @@ -Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务器,支持正向代理、反向代理、透明代理、内网穿透、TCP/UDP端口映射、SSH中转,TLS加密传输。 +Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务器,支持正向代理、反向代理、透明代理、内网穿透、TCP/UDP端口映射、SSH中转,TLS加密传输,协议转换。 [点击下载](https://github.com/snail007/goproxy/releases) 官方QQ交流群:189618940 @@ -23,6 +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功能。 ### Why need these? - 当由于某某原因,我们不能访问我们在其它地方的服务,我们可以通过多个相连的proxy节点建立起一个安全的隧道访问我们的服务. @@ -34,7 +35,8 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - ... -本页是v4.3手册,其他版本手册请点击下面链接查看. +本页是v4.4手册,其他版本手册请点击下面链接查看. +- [v4.3手册](https://github.com/snail007/goproxy/tree/v4.3) - [v4.2手册](https://github.com/snail007/goproxy/tree/v4.2) - [v4.0-v4.1手册](https://github.com/snail007/goproxy/tree/v4.1) - [v3.9手册](https://github.com/snail007/goproxy/tree/v3.9) @@ -117,6 +119,12 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - [5.8 KCP协议传输](#58kcp协议传输) - [5.9 自定义DNS](#59自定义dns) - [5.10 查看帮助](#510查看帮助) +- [6. 代理协议转换](#6代理协议转换) + - [6.1 功能介绍](#61-功能介绍) + - [6.2 HTTP(S)转HTTP(S)+SOCKS5](#62-https转https+socks5) + - [6.3 SOCKS5转HTTP(S)+SOCKS5](#63-socks5转https+socks5) + - [6.4 链式连接](#64-链式连接) + - [6.5 查看帮助](#65-查看帮助) ### Fast Start 提示:所有操作需要root权限. @@ -667,6 +675,60 @@ KCP协议需要-B参数设置一个密码用于加密解密数据 #### **5.10.查看帮助** `./proxy help socks` +### **6.代理协议转换** + +#### **6.1 功能介绍** +代理协议转换使用的是sps子命令(socks+https的缩写),sps本身不提供代理功能,只是接受代理请求**转换并转发**给已经存在的http(s)代理或者socks5代理;sps可以把已经存在的http(s)代理或者socks5代理转换为一个端口同时支持http(s)和socks5代理,而且http(s)代理主持正向代理和反向代理(SNI);另外对于已经存在的http(s)代理或者socks5代理,支持tls、tcp、kcp三种模式,支持链式连接,也就是可以多个sps结点层级连接构建加密通道。 + +#### **6.2 HTTP(S)转HTTP(S)+SOCKS5** +假设已经存在一个普通的http(s)代理:127.0.0.1:8080,现在我们把它转为同时支持http(s)和socks5的普通代理,转换后的本地端口为18080。 +命令如下: +`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p :18080` + +假设已经存在一个tls的http(s)代理:127.0.0.1:8080,现在我们把它转为同时支持http(s)和socks5的普通代理,转换后的本地端口为18080,tls需要证书文件。 +命令如下: +`./proxy sps -S http -T tls -P 127.0.0.1:8080 -t tcp -p :18080 -C proxy.crt -K proxy.key` + +假设已经存在一个kcp的http(s)代理(密码是:demo123):127.0.0.1:8080,现在我们把它转为同时支持http(s)和socks5的普通代理,转换后的本地端口为18080。 +命令如下: +`./proxy sps -S http -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 -B demo123` + +#### **6.3 SOCKS5转HTTP(S)+SOCKS5** +假设已经存在一个普通的socks5代理:127.0.0.1:8080,现在我们把它转为同时支持http(s)和socks5的普通代理,转换后的本地端口为18080。 +命令如下: +`./proxy sps -S socks -T tcp -P 127.0.0.1:8080 -t tcp -p :18080` + +假设已经存在一个tls的socks5代理:127.0.0.1:8080,现在我们把它转为同时支持http(s)和socks5的普通代理,转换后的本地端口为18080,tls需要证书文件。 +命令如下: +`./proxy sps -S socks -T tls -P 127.0.0.1:8080 -t tcp -p :18080 -C proxy.crt -K proxy.key` + +假设已经存在一个kcp的socks5代理(密码是:demo123):127.0.0.1:8080,现在我们把它转为同时支持http(s)和socks5的普通代理,转换后的本地端口为18080。 +命令如下: +`./proxy sps -S socks -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 -B demo123` + +#### **6.4 链式连接** +上面提过多个sps结点可以层级连接构建加密通道,假设有如下vps和家里的pc电脑。 +vps01:2.2.2.2 +vps02:3.3.3.3 +现在我们想利用pc和vps01和vps02构建一个加密通道,本例子用tls加密也可以用kcp,在pc上访问本地18080端口就是访问vps02的本地8080端口。 +首先在vps01(2.2.2.2)上我们运行一个只有本地可以访问的http(s)代理,执行: +`./proxy -t tcp -p 127.0.0.1:8080` + +然后在vps01(2.2.2.2)上运行一个sps结点,执行: +`./proxy -S http -T tcp -P 127.0.0.1:8080 -t tls -p :8081 -C proxy.crt -K proxy.key` + +然后在vps02(3.3.3.3)上运行一个sps结点,执行: +`./proxy -S http -T tls -P 2.2.2.2:8081 -t tls -p :8082 -C proxy.crt -K proxy.key` + +然后在pc上运行一个sps结点,执行: +`./proxy -S http -T tls -P 3.3.3.3:8082 -t tcp -p :18080 -C proxy.crt -K proxy.key` + +完成。 + +#### **6.5 查看帮助** +`./proxy help sps` + + ### TODO - http,socks代理多个上级负载均衡? - http(s)代理增加pac支持? diff --git a/config.go b/config.go index 1da4c3e..bf3a593 100755 --- a/config.go +++ b/config.go @@ -192,11 +192,15 @@ func initConfig() (err error) { spsArgs.Parent = sps.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() spsArgs.CertFile = sps.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() spsArgs.KeyFile = sps.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() - spsArgs.Timeout = sps.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('e').Default("2000").Int() - spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type ").Short('S').Enum("http", "socks") - spsArgs.ParentType = sps.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp") - spsArgs.LocalType = sps.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp") + spsArgs.Timeout = sps.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('i').Default("2000").Int() + 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").Short('p').Default(":33080").String() + spsArgs.KCPKey = sps.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String() + spsArgs.KCPMethod = sps.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String() + 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() //parse args serviceName := kingpin.MustParse(app.Parse(os.Args[1:])) diff --git a/services/args.go b/services/args.go index ceec143..7dfa671 100644 --- a/services/args.go +++ b/services/args.go @@ -197,7 +197,11 @@ type SPSArgs struct { ParentType *string LocalType *string Timeout *int + KCPMethod *string + KCPKey *string ParentServiceType *string + DNSAddress *string + DNSTTL *int } func (a *SPSArgs) Protocol() string { @@ -206,6 +210,8 @@ func (a *SPSArgs) Protocol() string { return TYPE_TLS case TYPE_TCP: return TYPE_TCP + case TYPE_KCP: + return TYPE_KCP } return "unknown" } diff --git a/services/sps.go b/services/sps.go index 3cf51ad..d1d61b0 100644 --- a/services/sps.go +++ b/services/sps.go @@ -1,6 +1,8 @@ package services import ( + "bytes" + "errors" "fmt" "log" "net" @@ -8,11 +10,13 @@ import ( "snail007/proxy/utils" "snail007/proxy/utils/socks" "strconv" + "strings" ) type SPS struct { - outPool utils.OutPool - cfg SPSArgs + outPool utils.OutPool + cfg SPSArgs + domainResolver utils.DomainResolver } func NewSPS() Service { @@ -26,15 +30,33 @@ func (s *SPS) CheckArgs() { log.Fatalf("parent required for %s %s", s.cfg.Protocol(), *s.cfg.Local) } if *s.cfg.ParentType == "" { - log.Fatalf("parent type unkown,use -T ") + log.Fatalf("parent type unkown,use -T ") } if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS { s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) } } func (s *SPS) InitService() { - + s.InitOutConnPool() } +func (s *SPS) InitOutConnPool() { + if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP { + //dur int, isTLS bool, certBytes, keyBytes []byte, + //parent string, timeout int, InitialCap int, MaxCap int + s.outPool = utils.NewOutPool( + 0, + *s.cfg.ParentType, + *s.cfg.KCPMethod, + *s.cfg.KCPKey, + s.cfg.CertBytes, s.cfg.KeyBytes, + *s.cfg.Parent, + *s.cfg.Timeout, + 0, + 0, + ) + } +} + func (s *SPS) StopService() { if s.outPool.Pool != nil { s.outPool.Pool.ReleaseAll() @@ -49,11 +71,12 @@ func (s *SPS) Start(args interface{}) (err error) { host, port, _ := net.SplitHostPort(*s.cfg.Local) p, _ := strconv.Atoi(port) sc := utils.NewServerChannel(host, p) - if *s.cfg.LocalType == TYPE_TCP { err = sc.ListenTCP(s.callback) } else if *s.cfg.LocalType == TYPE_TLS { err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.callback) + } else if *s.cfg.LocalType == TYPE_KCP { + err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.callback) } if err != nil { return @@ -73,6 +96,8 @@ func (s *SPS) callback(inConn net.Conn) { }() var err error switch *s.cfg.ParentType { + case TYPE_KCP: + fallthrough case TYPE_TCP: fallthrough case TYPE_TLS: @@ -94,17 +119,204 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { utils.CloseConn(inConn) return } - fmt.Printf("%v", header[0]) - + address := "" + var forwardBytes []byte + //fmt.Printf("%v", header) if header[0] == socks.VERSION_V5 { - req, e := socks.NewMethodsRequest(*inConn, header) + //socks + methodReq, e := socks.NewMethodsRequest(*inConn, header) if e != nil { - log.Printf("ERR:%s", e) + log.Printf("new method request err:%s", e) utils.CloseConn(inConn) err = e.(error) return } - fmt.Printf("address:%v", req.Version()) + 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) + } else if bytes.IndexByte(header, '\n') != -1 { + //http + var request utils.HTTPRequest + request, err = utils.NewHTTPRequest(inConn, 1024, false, nil, header) + if err != nil { + log.Printf("new http request fail,ERR: %s", err) + utils.CloseConn(inConn) + return + } + if len(header) >= 7 && strings.ToLower(string(header[:7])) == "connect" { + //https + request.HTTPSReply() + //log.Printf("https reply: %s", request.Host) + } else { + forwardBytes = request.HeadBuf + } + address = request.Host + } else { + log.Printf("unknown request from: %s,%s", (*inConn).RemoteAddr(), string(header)) + utils.CloseConn(inConn) + err = errors.New("unknown request") + return } + //connect to parent + var outConn net.Conn + var _outConn interface{} + _outConn, err = s.outPool.Pool.Get() + if err == nil { + outConn = _outConn.(net.Conn) + } + if err != nil { + log.Printf("connect to %s , err:%s", *s.cfg.Parent, err) + utils.CloseConn(inConn) + return + } + //ask parent for connect to target address + if *s.cfg.ParentServiceType == "http" { + //http parent + fmt.Fprintf(outConn, "CONNECT %s\r\n", address) + reply := make([]byte, 100) + 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("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) + 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 { + outConn.Write(forwardBytes) + } + //bind + inAddr := (*inConn).RemoteAddr().String() + outAddr := outConn.RemoteAddr().String() + utils.IoBind((*inConn), outConn, func(err interface{}) { + log.Printf("conn %s - %s released", inAddr, outAddr) + }) + log.Printf("conn %s - %s connected", inAddr, outAddr) return } +func (s *SPS) buildRequest(address string) (buf []byte, err error) { + host, portStr, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + port, err := strconv.Atoi(portStr) + if err != nil { + err = errors.New("proxy: failed to parse port number: " + portStr) + return + } + if port < 1 || port > 0xffff { + err = errors.New("proxy: port number out of range: " + portStr) + return + } + buf = buf[:0] + buf = append(buf, 0x05, 0x01, 0 /* reserved */) + + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + buf = append(buf, 0x01) + ip = ip4 + } else { + buf = append(buf, 0x04) + } + buf = append(buf, ip...) + } else { + if len(host) > 255 { + err = errors.New("proxy: destination host name too long: " + host) + return + } + buf = append(buf, 0x03) + buf = append(buf, byte(len(host))) + buf = append(buf, host...) + } + buf = append(buf, byte(port>>8), byte(port)) + return +} +func (s *SPS) Resolve(address string) string { + if *s.cfg.DNSAddress == "" { + return address + } + ip, err := s.domainResolver.Resolve(address) + if err != nil { + log.Printf("dns error %s , ERR:%s", address, err) + } + return ip +}