diff --git a/README.md b/README.md index 269d8e6..ceec2ba 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透** 目前市面上提供类似服务的有花生壳、TeamView、GoToMyCloud等等,但要使用第三方的公网服务器就必须为第三方付费,并且这些服务都有各种各样的限制,此外,由于数据包会流经第三方,因此对数据安全也是一大隐患。 -支持客户端与服务端连接中断自动重连,多路传输,大大的提高请求处理速度,go语言编写,无第三方依赖,各个平台都已经编译在release中,普通个人场景下,内存使用量在10M以下。 +go语言编写,无第三方依赖,各个平台都已经编译在release中。 ## 背景  @@ -30,6 +30,12 @@ easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透** * [web管理](#web管理模式)(多隧道时推荐) * [启动](#启动) * [配置文件说明](#服务端配置文件) + * [详细使用说明](#详细说明) + * [http|https域名解析](#域名解析) + * [tcp隧道](#tcp隧道) + * [udp隧道](#udp隧道) + * [sock5代理](#sock5代理) + * [http正向代理](#http正向代理) * 单隧道模式及介绍 * [tcp隧道模式](#tcp隧道模式) * [udp隧道模式](#udp隧道模式) @@ -40,7 +46,6 @@ easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透** * [数据压缩支持](#数据压缩支持) * [站点密码保护](#站点保护) * [加密传输](#加密传输) - * [TCP多路复用](#多路复用) * [host修改](#host修改) * [自定义header](#自定义header) * [自定义404页面](#404页面配置) @@ -48,14 +53,18 @@ easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透** * [带宽限制](#带宽限制) * [相关说明](#相关说明) * [流量统计](#流量统计) - * [连接池](#连接池) * [热更新支持](#热更新支持) * [获取用户真实ip](#获取用户真实ip) * [客户端地址显示](#客户端地址显示) +* [简单的性能测试](#简单的性能测试) + * [qps](#qps) + * [速度测试](#速度测试) + * [内存和cpu](#内存和cpu) + * [额外消耗连接数](#额外消耗连接数) * [webAPI](#webAPI) * [客户端](#客户端) - * [域名代理](#域名代理) - * [其他代理](#其他代理) + * [域名代理](#域名代理) + * [其他代理](#其他代理) ## 安装 @@ -78,16 +87,8 @@ easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透**  ### 介绍 -可在网页上配置和管理各个tcp、udp隧道、内网站点代理等等,功能极为强大,操作也非常方便。 -### 服务端配置文件 -- /conf/app.conf +可在网页上配置和管理各个tcp、udp隧道、内网站点代理,http、https解析等,功能极为强大,操作也非常方便。 -名称 | 含义 ----|--- -httpport | web管理端口 -password | web界面管理密码 -hostPort | 域名代理模式监听端口 -tcpport | 服务端客户端通信端口 **提示:使用web模式时,服务端执行文件必须在项目根目录,否则无法正确加载配置文件** @@ -102,15 +103,121 @@ tcpport | 服务端客户端通信端口 - 客户端 ``` - ./proxy_server -server=ip:port -vkey=web界面中显示的 + ./proxy_server -server=ip:port -vkey=web界面中显示的密钥 ``` -进入web管理界面,有详细的命令 - - 配置 进入web界面,公网ip:web界面端口(默认8080),密码默认为123 +进入web管理界面,有详细的命令 + +### 服务端配置文件 +- /conf/app.conf + +名称 | 含义 +---|--- +httpport | web管理端口 +password | web界面管理密码 +hostPort | 域名代理模式监听端口 +tcpport | 服务端客户端通信端口 +pemPath | ssl certFile绝对路径 +keyPath | ssl keyFile绝对路径 +httpsProxyPort | https代理监听端口 +httpProxyPort | http代理监听端口 + +### 详细说明 + +#### 域名解析 + +**适用范围:** 小程序开发、微信公众号开发、产品演示 + +**假设场景:** +- 有一个域名proxy.com,有一台公网机器ip为1.1.1.1 +- 两个内网开发站点127.0.0.1:81,127.0.0.1:82 +- 想通过(http|https://)a.proxy.com访问127.0.0.1:81,通过(http|https://)b.proxy.com访问127.0.0.1:82 + +**使用步骤** +- 将*.proxy.com解析到公网服务器1.1.1.1 +- 在客户端管理中创建一个客户端,记录下验证密钥 +- 点击该客户端的域名管理,添加两条规则规则:1、域名:a.proxy.com,内网目标:127.0.0.1:81,2、域名:b.proxy.com,内网目标:127.0.0.1:82 +-内网客户端运行 + +``` +./proxy_client server=1.1.1.1:8284 -vkey=客户端的密钥 +``` +现在访问(http|https://)a.proxy.com,b.proxy.com即可成功 + +**https:** 如需使用https请在配置文件中将https端口设置为443,和将对应的证书文件路径添加到配置文件中 + +#### tcp隧道 + + +**适用范围:** ssh、远程桌面等tcp连接场景 + +**假设场景:** + 想通过访问公网服务器1.1.1.1的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接 + +**使用步骤** +- 在客户端管理中创建一个客户端,记录下验证密钥 +- -内网客户端运行 +``` +./proxy_client server=1.1.1.1:8284 -vkey=客户端的密钥 +``` +- 在该客户端隧道管理中添加一条tcp隧道,填写监听的端口(8001)、内网目标ip和目标端口(10.1.50.101:22),选择压缩方式,保存。 +- 访问公网服务器ip(127.0.0.1),填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:ssh -p 8001 root@127.0.0.1 + +#### udp隧道 + + + +**适用范围:** 内网dns解析等udp连接场景 + +**假设场景:** +内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为1.1.1.1 + +**使用步骤** +- 在客户端管理中创建一个客户端,记录下验证密钥 +- -内网客户端运行 +``` +./proxy_client server=1.1.1.1:8284 -vkey=客户端的密钥 +``` +- 在该客户端的隧道管理中添加一条udp隧道,填写监听的端口(53)、内网目标ip和目标端口(10.1.50.102:53),选择压缩方式,保存。 +- 修改本机dns为127.0.0.1,则相当于使用10.1.50.202作为dns服务器 + +#### sock5代理 + + +**适用范围:** 在外网环境下如同使用vpn一样访问内网设备或者资源 + +**假设场景:** +想将公网服务器1.1.1.1的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果 + +**使用步骤** +- 在客户端管理中创建一个客户端,记录下验证密钥 +- -内网客户端运行 +``` +./proxy_client server=1.1.1.1:8284 -vkey=客户端的密钥 +``` +- 在该客户端隧道管理中添加一条socks5代理,填写监听的端口(8003),验证用户名和密码自行选择(建议先不填,部分客户端不支持,proxifer支持),选择压缩方式,保存。 +- 在外网环境的本机配置socks5代理,ip为公网服务器ip(127.0.0.1),端口为填写的监听端口(8003),即可畅享内网了 + +#### http正向代理 + +**适用范围:** 在外网环境下使用http代理访问内网站点 + +**假设场景:** +想将公网服务器1.1.1.1的8004端口作为http代理,访问内网网站 + +**使用步骤** +- 在客户端管理中创建一个客户端,记录下验证密钥 +- -内网客户端运行 +``` +./proxy_client server=1.1.1.1:8284 -vkey=客户端的密钥 +``` +- 在该客户端隧道管理中添加一条http代理,填写监听的端口(8004),选择压缩方式,保存。 +- 在外网环境的本机配置http代理,ip为公网服务器ip(127.0.0.1),端口为填写的监听端口(8004),即可访问了 + ## tcp隧道模式 @@ -316,16 +423,6 @@ authip | 免验证ip,适用于web api -crypt=true ``` -### 多路复用 - -客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用。 - - -- 在server端加上参数 -mux=true(或在web管理中设置) - -``` --mux=true -``` ### 站点保护 @@ -373,227 +470,27 @@ authip | 免验证ip,适用于web api ### 流量统计 可统计显示每个代理使用的流量,由于压缩和加密等原因,会和实际环境中的略有差异 -### 连接池 - easyProxy会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接时间。 +## 简单性能测试 + +### qps + +### 速度测试 +**测试环境:** 1M带宽云服务器,理论125kb/s,带宽与代理无关,与服务器关系较大。 + + + +### 内存和cpu + +**1000次性能测试后** + + +**启动时** + + +### 额外消耗连接数 +为了最大化的提升效率和并发,客户端与服务端之间仅两条tcp连接,减少建立连接的时间消耗和多余tcp连接对机器性能的影响。 ## webAPI -### 客户端 - -#### 添加客户端 -``` -POST /client/add/ -``` -参数 | 含义 ----|--- -remark | 备注 -u | 用户名 -p | 密码 -compress | 压缩(snappy或空) -crypt | 是否加密(1或者0) -mux | 是否TCP复用(1或者0) -rate_limit|带宽限制 -flow_limit|流量限制 - -#### 添加客户端 -``` -POST /client/edit/ -``` -参数 | 含义 ----|--- -id | id -remark | 备注 -u | 用户名 -p | 密码 -compress | 压缩(snappy或空) -crypt | 是否加密(1或者0) -mux | 是否TCP复用(1或者0) -rate_limit|带宽限制 -flow_limit|流量限制 - -#### 更改状态 -``` -POST /client/changestatus/ -``` -参数 | 含义 ----|--- -id | id -status|1或0 - -#### 删除客户端 -``` -POST /client/del/ -``` -参数 | 含义 ----|--- -id | id -#### 获取单个客户端 -``` -POST /client/getclient/ -``` -参数 | 含义 ----|--- -id | id -#### 获取客户端列表 -``` -POST /client/list/ -``` - -参数 | 含义 ----|--- -start | 开始 -length | 长度 - -### 域名代理 - -#### 添加域名代理 - -``` -POST /index/addhost/ -``` - -参数 | 含义 ----|--- -host | 域名 -target | 内网目标地址 -header | header修改 -hostchange | host修改 -remark | 备注 -client_id | 客户端id -#### 删除域名代理 -``` -POST /index/delhost/ -``` - -参数 | 含义 ----|--- -host | 域名 - -#### 修改域名代理 -``` -POST /index/edithost/ -``` - -参数 | 含义 ----|--- -nhost | 修改后的域名 -host | 修改之前的域名 -target | 内网目标地址 -header | header修改 -hostchange | host修改 -remark | 备注 -client_id | 客户端id - -#### 获取域名代理列表 -``` -POST /index/hostlist/ -``` - -参数 | 含义 ----|--- -start | 开始 -length | 长度 -client_id | 客户端id(为空获取所有) -#### 获取单个host -``` -POST /index/gethost/ -``` - -参数 | 含义 ----|--- -host|域名 - - -### 其他代理 - -#### 获取隧道列表 - -``` -POST /index/gettunnel/ -``` - -参数 | 含义 ----|--- -client_id|客户端id(为空则忽略客户端限制) -type|类型(udpServer、tunnelServer、socks5Server、httpProxyServer,为空则忽略类型限制) -start|开始 -length|长度 - -#### 添加 - -``` -POST /index/add/ -``` - -参数 | 含义 ----|--- -port|监听端口 -type|类型(udpServer、tunnelServer、socks5Server、httpProxyServer) -start|开始 -u|验证用户名 -p|验证密码 -compress|压缩(空或snappy) -crypt|是否加密(1或0) -mux|是否tcp复用(1或0) -use_client|是否使用客户端配置(1或0) -remark|备注 -client_id|客户端id - -#### 修改 - -``` -POST /index/edit/ -``` - -参数 | 含义 ----|--- -id|id -port|监听端口 -type|类型(udpServer、tunnelServer、socks5Server、httpProxyServer) -start|开始 -u|验证用户名 -p|验证密码 -compress|压缩(空或snappy) -crypt|是否加密(1或0) -mux|是否tcp复用(1或0) -use_client|是否使用客户端配置(1或0) -remark|备注 -client_id|客户端id - -#### 停止隧道 - -``` -POST /index/stop/ -``` - -参数 | 含义 ----|--- -id|id - -#### 删除隧道 - -``` -POST /index/del/ -``` - -参数 | 含义 ----|--- -id|id -#### 开始隧道 - -``` -POST /index/start/ -``` - -参数 | 含义 ----|--- -id|id -#### 获取单条隧道详细 - -``` -POST /index/getonetunnel/ -``` - -参数 | 含义 ----|--- -id|id \ No newline at end of file +为方便第三方扩展,在web模式下可利用webAPI进行相关操作,详情见 +[webAPI文档](https://github.com/cnlh/easyProxy/wiki/webAPI%E6%96%87%E6%A1%A3) \ No newline at end of file diff --git a/bridge/bridge.go b/bridge/bridge.go index fc41fa1..5e541ab 100755 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -5,47 +5,33 @@ import ( "github.com/cnlh/easyProxy/utils" "log" "net" - "strconv" "sync" "time" ) -type list struct { - connList chan *utils.Conn -} - -func (l *list) Add(c *utils.Conn) { - l.connList <- c -} - -func (l *list) Pop() *utils.Conn { - return <-l.connList -} -func (l *list) Len() int { - return len(l.connList) -} - -func newList() *list { - l := new(list) - l.connList = make(chan *utils.Conn, 1000) - return l +type Client struct { + tunnel *utils.Conn + signal *utils.Conn + linkMap map[int]*utils.Link + linkStatusMap map[int]bool + stop chan bool + sync.RWMutex } type Bridge struct { - TunnelPort int //通信隧道端口 - listener *net.TCPListener //server端监听 - SignalList map[int]*list //通信 - TunnelList map[int]*list //隧道 + TunnelPort int //通信隧道端口 + listener *net.TCPListener //server端监听 + Client map[int]*Client RunList map[int]interface{} //运行中的任务 lock sync.Mutex tunnelLock sync.Mutex + clientLock sync.Mutex } func NewTunnel(tunnelPort int, runList map[int]interface{}) *Bridge { t := new(Bridge) t.TunnelPort = tunnelPort - t.SignalList = make(map[int]*list) - t.TunnelList = make(map[int]*list) + t.Client = make(map[int]*Client) t.RunList = runList return t } @@ -103,11 +89,12 @@ func (s *Bridge) cliProcess(c *utils.Conn) { } func (s *Bridge) closeClient(id int) { - if len(s.SignalList) > 0 { - s.SignalList[id].Pop().WriteClose() + s.clientLock.Lock() + defer s.clientLock.Unlock() + if v, ok := s.Client[id]; ok { + v.signal.WriteClose() + delete(s.Client, id) } - s.DelClientSignal(id) - s.DelClientTunnel(id) } //tcp连接类型区分 @@ -115,113 +102,123 @@ func (s *Bridge) typeDeal(typeVal string, c *utils.Conn, id int) { switch typeVal { case utils.WORK_MAIN: //客户端已经存在,下线 - if _, ok := s.SignalList[id]; ok { + s.clientLock.Lock() + if _, ok := s.Client[id]; ok { + s.clientLock.Unlock() s.closeClient(id) + } else { + s.clientLock.Unlock() } - log.Println("客户端连接成功", c.Conn.RemoteAddr()) - s.addList(s.SignalList, c, id) + s.clientLock.Lock() + + s.Client[id] = &Client{ + linkMap: make(map[int]*utils.Link), + stop: make(chan bool), + linkStatusMap: make(map[int]bool), + } + log.Printf("客户端%d连接成功,地址为:%s", id, c.Conn.RemoteAddr()) + s.Client[id].signal = c + s.clientLock.Unlock() + go s.GetStatus(id) case utils.WORK_CHAN: - s.addList(s.TunnelList, c, id) + s.clientLock.Lock() + if v, ok := s.Client[id]; ok { + s.clientLock.Unlock() + v.tunnel = c + } else { + s.clientLock.Unlock() + return + } + go s.clientCopy(id) } c.SetAlive() return } -//加到对应的list中 -func (s *Bridge) addList(m map[int]*list, c *utils.Conn, id int) { - s.lock.Lock() - if v, ok := m[id]; ok { - v.Add(c) - } else { - l := newList() - l.Add(c) - m[id] = l +//等待 +func (s *Bridge) waitStatus(clientId, id int) (bool) { + ticker := time.NewTicker(time.Millisecond * 100) + stop := time.After(time.Second * 10) + for { + select { + case <-ticker.C: + s.clientLock.Lock() + if v, ok := s.Client[clientId]; ok { + s.clientLock.Unlock() + v.Lock() + if vv, ok := v.linkStatusMap[id]; ok { + ticker.Stop() + v.Unlock() + return vv + } + v.Unlock() + } else { + s.clientLock.Unlock() + } + case <-stop: + return false + } } - s.lock.Unlock() + return false +} + +func (s *Bridge) SendLinkInfo(clientId int, link *utils.Link) (tunnel *utils.Conn, err error) { + s.clientLock.Lock() + if v, ok := s.Client[clientId]; ok { + s.clientLock.Unlock() + v.signal.SendLinkInfo(link) + if err != nil { + log.Println("send error:", err, link.Id) + s.DelClient(clientId) + return + } + if v.tunnel == nil { + err = errors.New("tunnel获取错误") + return + } else { + tunnel = v.tunnel + } + v.Lock() + v.linkMap[link.Id] = link + v.Unlock() + if !s.waitStatus(clientId, link.Id) { + err = errors.New("连接失败") + return + } + } else { + s.clientLock.Unlock() + err = errors.New("客户端未连接") + } + return } //得到一个tcp隧道 -func (s *Bridge) GetTunnel(id int, en, de int, crypt, mux bool) (c *utils.Conn, err error) { -retry: - if c, err = s.waitAndPop(s.TunnelList, id); err != nil { - return +func (s *Bridge) GetTunnel(id int, en, de int, crypt, mux bool) (conn *utils.Conn, err error) { + s.clientLock.Lock() + defer s.clientLock.Unlock() + if v, ok := s.Client[id]; !ok { + err = errors.New("客户端未连接") + } else { + conn = v.tunnel } - if _, err = c.WriteTest(); err != nil { - c.Close() - goto retry - } - c.WriteConnInfo(en, de, crypt, mux) return } //得到一个通信通道 -func (s *Bridge) GetSignal(id int) (err error, conn *utils.Conn) { - if v, ok := s.SignalList[id]; !ok || v.Len() == 0 { +func (s *Bridge) GetSignal(id int) (conn *utils.Conn, err error) { + s.clientLock.Lock() + defer s.clientLock.Unlock() + if v, ok := s.Client[id]; !ok { err = errors.New("客户端未连接") - return + } else { + conn = v.signal } - conn = s.SignalList[id].Pop() return } -//重回slice 复用 -func (s *Bridge) ReturnSignal(conn *utils.Conn, id int) { - if v, ok := s.SignalList[id]; ok { - v.Add(conn) - } -} - -//重回slice 复用 -func (s *Bridge) ReturnTunnel(conn *utils.Conn, id int) { - if v, ok := s.TunnelList[id]; ok { - utils.FlushConn(conn.Conn) - v.Add(conn) - } -} - //删除通信通道 -func (s *Bridge) DelClientSignal(id int) { - s.delClient(id, s.SignalList) -} - -//删除隧道 -func (s *Bridge) DelClientTunnel(id int) { - s.delClient(id, s.TunnelList) -} - -func (s *Bridge) delClient(id int, l map[int]*list) { - if t := l[id]; t != nil { - for { - if t.Len() <= 0 { - break - } - t.Pop().Close() - } - delete(l, id) - } -} - -//等待 -func (s *Bridge) waitAndPop(m map[int]*list, id int) (c *utils.Conn, err error) { - ticker := time.NewTicker(time.Millisecond * 100) - stop := time.After(time.Second * 3) - for { - select { - case <-ticker.C: - s.lock.Lock() - if v, ok := m[id]; ok && v.Len() > 0 { - c = v.Pop() - ticker.Stop() - s.lock.Unlock() - return - } - s.lock.Unlock() - case <-stop: - err = errors.New("client id: " + strconv.Itoa(id) + ",err: get client conn timeout") - return - } - } - return +func (s *Bridge) DelClient(id int) { + s.closeClient(id) } func (s *Bridge) verify(id int) bool { @@ -232,3 +229,66 @@ func (s *Bridge) verify(id int) bool { } return false } +func (s *Bridge) GetStatus(clientId int) { + s.clientLock.Lock() + client := s.Client[clientId] + s.clientLock.Unlock() + + if client == nil { + return + } + for { + if id, status, err := client.signal.GetConnStatus(); err != nil { + s.closeClient(clientId) + return + } else { + client.Lock() + client.linkStatusMap[id] = status + client.Unlock() + } + } +} + +func (s *Bridge) clientCopy(clientId int) { + + s.clientLock.Lock() + client := s.Client[clientId] + s.clientLock.Unlock() + + for { + if id, err := client.tunnel.GetLen(); err != nil { + s.closeClient(clientId) + log.Println("读取msg id 错误", err, id) + break + } else { + client.Lock() + if link, ok := client.linkMap[id]; ok { + client.Unlock() + if content, err := client.tunnel.GetMsgContent(link); err != nil { + utils.PutBufPoolCopy(content) + s.closeClient(clientId) + log.Println("read msg content error", err, "close client") + break + } else { + if len(content) == len(utils.IO_EOF) && string(content) == utils.IO_EOF { + if link.Conn != nil { + link.Conn.Close() + } + } else { + if link.UdpListener != nil && link.UdpRemoteAddr != nil { + link.UdpListener.WriteToUDP(content, link.UdpRemoteAddr) + } else { + link.Conn.Write(content) + } + link.Flow.Add(0, len(content)) + } + utils.PutBufPoolCopy(content) + } + } else { + client.Unlock() + continue + } + } + } + +} diff --git a/client/client.go b/client/client.go index adeca64..2bcbe7c 100755 --- a/client/client.go +++ b/client/client.go @@ -5,87 +5,129 @@ import ( "log" "net" "sync" - "sync/atomic" "time" ) type TRPClient struct { - svrAddr string - tcpNum int - connPoolSize int - tunnelNum int64 - tunnel chan bool - serverStatus bool + svrAddr string + linkMap map[int]*utils.Link + stop chan bool + tunnel *utils.Conn sync.Mutex vKey string } //new client -func NewRPClient(svraddr string, tcpNum int, vKey string) *TRPClient { - c := new(TRPClient) - c.svrAddr = svraddr - c.tcpNum = tcpNum - c.vKey = vKey - c.tunnel = make(chan bool) - c.connPoolSize = 5 - return c +func NewRPClient(svraddr string, vKey string) *TRPClient { + return &TRPClient{ + svrAddr: svraddr, + linkMap: make(map[int]*utils.Link), + stop: make(chan bool), + tunnel: nil, + Mutex: sync.Mutex{}, + vKey: vKey, + } } //start func (s *TRPClient) Start() error { - for i := 0; i < s.tcpNum; i++ { - go s.NewConn() - } - for i := 0; i < 5; i++ { - go s.dealChan() - } - go s.session() + s.NewConn() return nil } //新建 -func (s *TRPClient) NewConn() error { - s.Lock() - s.serverStatus = false +func (s *TRPClient) NewConn() { +retry: conn, err := net.Dial("tcp", s.svrAddr) if err != nil { log.Println("连接服务端失败,五秒后将重连") time.Sleep(time.Second * 5) - s.Unlock() - go s.NewConn() - return err + goto retry + return } - s.Unlock() - return s.processor(utils.NewConn(conn)) + s.processor(utils.NewConn(conn)) } + //处理 -func (s *TRPClient) processor(c *utils.Conn) error { - s.serverStatus = true +func (s *TRPClient) processor(c *utils.Conn) { c.SetAlive() if _, err := c.Write([]byte(utils.Getverifyval(s.vKey))); err != nil { - return err + return } c.WriteMain() + + go s.dealChan() + for { flags, err := c.ReadFlag() if err != nil { - log.Println("服务端断开,五秒后将重连", err) - go s.NewConn() + log.Println("服务端断开,正在重新连接") break } switch flags { case utils.VERIFY_EER: - log.Fatalln("vkey:", s.vKey, "不正确,服务端拒绝连接,请检查") - case utils.WORK_CHAN: //隧道模式,每次开启10个,加快连接速度 + log.Fatalf("vKey:%s不正确,服务端拒绝连接,请检查", s.vKey) + case utils.NEW_CONN: + if link, err := c.GetLinkInfo(); err != nil { + break + } else { + log.Println(link) + s.Lock() + s.linkMap[link.Id] = link + s.Unlock() + go s.linkProcess(link, c) + } case utils.RES_CLOSE: log.Fatal("该vkey被另一客户连接") case utils.RES_MSG: - log.Println("服务端返回错误。") + log.Println("服务端返回错误,重新连接") + break default: - log.Println("无法解析该错误。", flags) + log.Println("无法解析该错误,重新连接") + break } } - return nil + s.stop <- true + s.linkMap = make(map[int]*utils.Link) + go s.NewConn() +} +func (s *TRPClient) linkProcess(link *utils.Link, c *utils.Conn) { + //与目标建立连接 + server, err := net.DialTimeout(link.ConnType, link.Host, time.Second*3) + + if err != nil { + c.WriteFail(link.Id) + log.Println("connect to ", link.Host, "error:", err) + return + } + + c.WriteSuccess(link.Id) + + link.Conn = utils.NewConn(server) + + for { + buf := utils.BufPoolCopy.Get().([]byte) + if n, err := server.Read(buf); err != nil { + utils.PutBufPoolCopy(buf) + s.tunnel.SendMsg([]byte(utils.IO_EOF), link) + break + } else { + if _, err := s.tunnel.SendMsg(buf[:n], link); err != nil { + utils.PutBufPoolCopy(buf) + c.Close() + break + } + utils.PutBufPoolCopy(buf) + if link.ConnType == utils.CONN_UDP { + c.Close() + break + } + } + } + + s.Lock() + delete(s.linkMap, link.Id) + s.Unlock() } //隧道模式处理 @@ -103,50 +145,37 @@ func (s *TRPClient) dealChan() { return } //默认长连接保持 - c := utils.NewConn(conn) - c.SetAlive() + s.tunnel = utils.NewConn(conn) + s.tunnel.SetAlive() //写标志 - c.WriteChan() -re: - atomic.AddInt64(&s.tunnelNum, 1) - //获取连接的host type(tcp or udp) - typeStr, host, en, de, crypt, mux, err := c.GetHostFromConn() - s.tunnel <- true - atomic.AddInt64(&s.tunnelNum, -1) - if err != nil { - c.Close() - return - } - s.ConnectAndCopy(c, typeStr, host, en, de, crypt, mux) - if mux { - utils.FlushConn(conn) - goto re - } else { - c.Close() - } -} + s.tunnel.WriteChan() -func (s *TRPClient) session() { - t := time.NewTicker(time.Millisecond * 1000) - for { - select { - case <-s.tunnel: - case <-t.C: - } - if s.serverStatus && s.tunnelNum < 5 { - go s.dealChan() + go func() { + for { + if id, err := s.tunnel.GetLen(); err != nil { + log.Println("get msg id error") + break + } else { + s.Lock() + if v, ok := s.linkMap[id]; ok { + s.Unlock() + if content, err := s.tunnel.GetMsgContent(v); err != nil { + log.Println("get msg content error:", err, id) + break + } else { + if len(content) == len(utils.IO_EOF) && string(content) == utils.IO_EOF { + v.Conn.Close() + } else if v.Conn != nil { + v.Conn.Write(content) + } + } + } else { + s.Unlock() + } + } } + }() + select { + case <-s.stop: } } - -func (s *TRPClient) ConnectAndCopy(c *utils.Conn, typeStr, host string, en, de int, crypt, mux bool) { - //与目标建立连接,超时时间为3 - server, err := net.DialTimeout(typeStr, host, time.Second*3) - if err != nil { - log.Println("connect to ", host, "error:", err, mux) - c.WriteFail() - return - } - c.WriteSuccess() - utils.ReplayWaitGroup(c.Conn, server, en, de, crypt, mux, nil) -} diff --git a/cmd/proxy_client/proxy_client.go b/cmd/proxy_client/proxy_client.go index 56a20d4..88e5e08 100644 --- a/cmd/proxy_client/proxy_client.go +++ b/cmd/proxy_client/proxy_client.go @@ -13,11 +13,12 @@ var ( ) func main() { + log.SetFlags(log.Lshortfile) flag.Parse() stop := make(chan int) for _, v := range strings.Split(*verifyKey, ",") { log.Println("客户端启动,连接:", *serverAddr, " 验证令牌:", v) - go client.NewRPClient(*serverAddr, 1, v).Start() + go client.NewRPClient(*serverAddr, v).Start() } <-stop } diff --git a/cmd/proxy_server/proxy_server.go b/cmd/proxy_server/proxy_server.go index 74d055b..f031c4e 100644 --- a/cmd/proxy_server/proxy_server.go +++ b/cmd/proxy_server/proxy_server.go @@ -19,10 +19,10 @@ var ( p = flag.String("p", "", "验证密码(socks5和web)") compress = flag.String("compress", "", "数据压缩方式(snappy)") crypt = flag.String("crypt", "false", "是否加密(true|false)") - mux = flag.String("mux", "false", "是否TCP多路复用(true|false)") ) func main() { + log.SetFlags(log.Lshortfile) flag.Parse() task := &utils.Tunnel{ TcpPort: *httpPort, @@ -33,7 +33,6 @@ func main() { P: *p, Compress: *compress, Crypt: utils.GetBoolByStr(*crypt), - Mux: utils.GetBoolByStr(*mux), }, Flow: &utils.Flow{}, UseClientCnf: false, diff --git a/conf/app.conf b/conf/app.conf index 1066958..c1d21c0 100755 --- a/conf/app.conf +++ b/conf/app.conf @@ -9,11 +9,20 @@ runmode = dev #web管理密码 password=123 -#http监听端口 -hostPort=80 - ##客户端与服务端通信端口 tcpport=8284 #web api免验证IP地址 authip=127.0.0.1 + +##http代理端口 +httpProxyPort=80 + +##https代理端口 +httpsProxyPort=443 + +##certFile绝对路径 +pemPath=/etc/nginx/certificate.crt + +##keyFile绝对路径 +keyPath=/etc/nginx/private.key diff --git a/conf/clients.csv b/conf/clients.csv index 6b86fdc..d22da32 100644 --- a/conf/clients.csv +++ b/conf/clients.csv @@ -1 +1 @@ -1,wuz1nozs9dhtxic6,,true,,,0,0,,0,1 +1,wajajb6bl086s2aq,111,true,1,2,1,,20000,1000 diff --git a/conf/hosts.csv b/conf/hosts.csv index d45288c..3c0d73a 100644 --- a/conf/hosts.csv +++ b/conf/hosts.csv @@ -1 +1 @@ -a.o.com,127.0.0.1:8082,1,Connection:close,, +a.o.com,127.0.0.1:8082,1,,www.baidu.com,测试 diff --git a/conf/tasks.csv b/conf/tasks.csv index d5d7e38..0d7a03f 100644 --- a/conf/tasks.csv +++ b/conf/tasks.csv @@ -1 +1,4 @@ -9001,tunnelServer,127.0.0.1:8082,,,,1,0,0,0,0,1,1,true, +9002,tunnelServer,127.0.0.1:8082,2,2,,1,1,0,0,1,1,false,123 +53,udpServer,114.114.114.114:53,,,,1,0,0,0,2,1,true,udp测试 +9003,httpProxyServer,,,,,1,0,0,0,3,1,false, +8024,socks5Server,,,,,1,0,0,0,4,1,true, diff --git a/server/base.go b/server/base.go index d1c908a..e5cf236 100644 --- a/server/base.go +++ b/server/base.go @@ -1,29 +1,24 @@ package server import ( + "errors" "github.com/cnlh/easyProxy/bridge" "github.com/cnlh/easyProxy/utils" + "net" + "net/http" "sync" ) //server base struct type server struct { - bridge *bridge.Bridge - task *utils.Tunnel - config *utils.Config + id int + bridge *bridge.Bridge + task *utils.Tunnel + config *utils.Config + errorContent []byte sync.Mutex } -func (s *server) GetTunnelAndWriteHost(connType string, clientId int, cnf *utils.Config, addr string) (link *utils.Conn, err error) { - if link, err = s.bridge.GetTunnel(clientId, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux); err != nil { - return - } - if _, err = link.WriteHost(connType, addr); err != nil { - link.Close() - } - return -} - func (s *server) FlowAdd(in, out int64) { s.Lock() defer s.Unlock() @@ -56,7 +51,6 @@ func (s *server) ResetConfig() bool { s.config.U = client.Cnf.U s.config.P = client.Cnf.P s.config.Compress = client.Cnf.Compress - s.config.Mux = client.Cnf.Mux s.config.Crypt = client.Cnf.Crypt } } else { @@ -64,7 +58,6 @@ func (s *server) ResetConfig() bool { s.config.U = task.Config.U s.config.P = task.Config.P s.config.Compress = task.Config.Compress - s.config.Mux = task.Config.Mux s.config.Crypt = task.Config.Crypt } } @@ -72,3 +65,43 @@ func (s *server) ResetConfig() bool { s.config.CompressDecode, s.config.CompressEncode = utils.GetCompressType(s.config.Compress) return true } + +func (s *server) linkCopy(link *utils.Link, c *utils.Conn, rb []byte, tunnel *utils.Conn, flow *utils.Flow) { + if rb != nil { + if _, err := tunnel.SendMsg(rb, link); err != nil { + c.Close() + return + } + flow.Add(len(rb), 0) + } + for { + buf := utils.BufPoolCopy.Get().([]byte) + if n, err := c.Read(buf); err != nil { + tunnel.SendMsg([]byte(utils.IO_EOF), link) + break + } else { + if _, err := tunnel.SendMsg(buf[:n], link); err != nil { + utils.PutBufPoolCopy(buf) + c.Close() + break + } + utils.PutBufPoolCopy(buf) + flow.Add(n, 0) + } + } +} + +func (s *server) writeConnFail(c net.Conn) { + c.Write([]byte(utils.ConnectionFailBytes)) + c.Write(s.errorContent) +} + +//权限认证 +func (s *server) auth(r *http.Request, c *utils.Conn, u, p string) error { + if u != "" && p != "" && !utils.CheckAuth(r, u, p) { + c.Write([]byte(utils.UnauthorizedBytes)) + c.Close() + return errors.New("401 Unauthorized") + } + return nil +} diff --git a/server/http.go b/server/http.go new file mode 100644 index 0000000..be2ccf9 --- /dev/null +++ b/server/http.go @@ -0,0 +1,169 @@ +package server + +import ( + "bufio" + "crypto/tls" + "github.com/astaxie/beego" + "github.com/cnlh/easyProxy/bridge" + "github.com/cnlh/easyProxy/utils" + "log" + "net/http" + "net/http/httputil" + "strconv" + "sync" +) + +type httpServer struct { + server + httpPort int //http端口 + httpsPort int //https监听端口 + pemPath string + keyPath string + stop chan bool +} + +func NewHttp(bridge *bridge.Bridge, c *utils.Tunnel) *httpServer { + httpPort, _ := beego.AppConfig.Int("httpProxyPort") + httpsPort, _ := beego.AppConfig.Int("httpsProxyPort") + pemPath := beego.AppConfig.String("pemPath") + keyPath := beego.AppConfig.String("keyPath") + return &httpServer{ + server: server{ + task: c, + bridge: bridge, + Mutex: sync.Mutex{}, + }, + httpPort: httpPort, + httpsPort: httpsPort, + pemPath: pemPath, + keyPath: keyPath, + stop: make(chan bool), + } +} + +func (s *httpServer) Start() error { + var err error + var http, https *http.Server + if s.errorContent, err = utils.ReadAllFromFile(beego.AppPath + "/web/static/page/error.html"); err != nil { + s.errorContent = []byte("easyProxy 404") + } + + if s.httpPort > 0 { + http = s.NewServer(s.httpPort) + go func() { + log.Println("启动http监听,端口为", s.httpPort) + err := http.ListenAndServe() + if err != nil { + log.Fatalln(err) + } + }() + } + if s.httpsPort > 0 { + https = s.NewServer(s.httpsPort) + go func() { + log.Println("启动https监听,端口为", s.httpsPort) + err := https.ListenAndServeTLS(s.pemPath, s.keyPath) + if err != nil { + log.Fatalln(err) + } + }() + } + select { + case <-s.stop: + if http != nil { + http.Close() + } + if https != nil { + https.Close() + } + } + return nil +} + +func (s *httpServer) Close() { + s.stop <- true +} + +func (s *httpServer) handleTunneling(w http.ResponseWriter, r *http.Request) { + hijacker, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Hijacking not supported", http.StatusInternalServerError) + return + } + conn, _, err := hijacker.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + } + s.process(utils.NewConn(conn), r) +} + +func (s *httpServer) process(c *utils.Conn, r *http.Request) { + //多客户端域名代理 + var ( + isConn = true + link *utils.Link + host *utils.Host + tunnel *utils.Conn + err error + ) + for { + //首次获取conn + if isConn { + if host, err = GetInfoByHost(r.Host); err != nil { + log.Printf("the host %s is not found !", r.Host) + break + } + //流量限制 + if host.Client.Flow.FlowLimit > 0 && (host.Client.Flow.FlowLimit<<20) < (host.Client.Flow.ExportFlow+host.Client.Flow.InletFlow) { + break + } + host.Client.Cnf.CompressDecode, host.Client.Cnf.CompressEncode = utils.GetCompressType(host.Client.Cnf.Compress) + //权限控制 + if err = s.auth(r, c, host.Client.Cnf.U, host.Client.Cnf.P); err != nil { + break + } + link = utils.NewLink(host.Client.GetId(), utils.CONN_TCP, host.Target, host.Client.Cnf.CompressEncode, host.Client.Cnf.CompressDecode, host.Client.Cnf.Crypt, c, host.Flow, nil, host.Client.Rate, nil) + if tunnel, err = s.bridge.SendLinkInfo(host.Client.Id, link); err != nil { + break + } + isConn = false + } else { + r, err = http.ReadRequest(bufio.NewReader(c)) + if err != nil { + log.Println(err) + break + } + } + //根据设定,修改header和host + utils.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String()) + b, err := httputil.DumpRequest(r, true) + if err != nil { + break + } + host.Flow.Add(len(b), 0) + if _, err := tunnel.SendMsg(b, link); err != nil { + c.Close() + break + } + } + + if isConn { + s.writeConnFail(c.Conn) + } else { + tunnel.SendMsg([]byte(utils.IO_EOF), link) + } + + c.Close() + +} + +func (s *httpServer) NewServer(port int) *http.Server { + return &http.Server{ + Addr: ":" + strconv.Itoa(port), + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + s.handleTunneling(w, r) + }), + // Disable HTTP/2. + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), + } +} diff --git a/server/process.go b/server/process.go deleted file mode 100644 index c7341a1..0000000 --- a/server/process.go +++ /dev/null @@ -1,111 +0,0 @@ -package server - -import ( - "bufio" - "github.com/cnlh/easyProxy/utils" - "github.com/pkg/errors" - "log" - "net/http" - "net/http/httputil" - "sync" -) - -type process func(c *utils.Conn, s *TunnelModeServer) error - -//tcp隧道模式 -func ProcessTunnel(c *utils.Conn, s *TunnelModeServer) error { - if !s.ResetConfig() { - c.Close() - return errors.New("流量超出") - } - return s.dealClient(c, s.config, s.task.Target, "", nil) -} - -//http代理模式 -func ProcessHttp(c *utils.Conn, s *TunnelModeServer) error { - if !s.ResetConfig() { - c.Close() - return errors.New("流量超出") - } - method, addr, rb, err, r := c.GetHost() - if err != nil { - log.Println(err) - c.Close() - return err - } - if err := s.auth(r, c, s.config.U, s.config.P); err != nil { - return err - } - return s.dealClient(c, s.config, addr, method, rb) -} - -//多客户端域名代理 -func ProcessHost(c *utils.Conn, s *TunnelModeServer) error { - var ( - isConn = true - link *utils.Conn - host *utils.Host - wg sync.WaitGroup - ) - for { - r, err := http.ReadRequest(bufio.NewReader(c)) - if err != nil { - break - } - //首次获取conn - if isConn { - if host, err = GetInfoByHost(r.Host); err != nil { - log.Printf("the host %s is not found !", r.Host) - break - } - //流量限制 - if host.Client.Flow.FlowLimit > 0 && (host.Client.Flow.FlowLimit<<20) < (host.Client.Flow.ExportFlow+host.Client.Flow.InletFlow) { - break - } - host.Client.Cnf.CompressDecode, host.Client.Cnf.CompressEncode = utils.GetCompressType(host.Client.Cnf.Compress) - //权限控制 - if err = s.auth(r, c, host.Client.Cnf.U, host.Client.Cnf.P); err != nil { - break - } - if link, err = s.GetTunnelAndWriteHost(utils.CONN_TCP, host.Client.Id, host.Client.Cnf, host.Target); err != nil { - log.Println("get bridge tunnel error: ", err) - break - } - if flag, err := link.ReadFlag(); err != nil || flag == utils.CONN_ERROR { - log.Printf("the host %s connection to %s error", r.Host, host.Target) - break - } else { - wg.Add(1) - go func() { - out, _ := utils.Relay(c.Conn, link.Conn, host.Client.Cnf.CompressDecode, host.Client.Cnf.Crypt, host.Client.Cnf.Mux, host.Client.Rate) - wg.Done() - s.FlowAddHost(host, 0, out) - }() - } - isConn = false - } - //根据设定,修改header和host - utils.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String()) - b, err := httputil.DumpRequest(r, true) - if err != nil { - break - } - s.FlowAddHost(host, int64(len(b)), 0) - if _, err := link.WriteTo(b, host.Client.Cnf.CompressEncode, host.Client.Cnf.Crypt, host.Client.Rate); err != nil { - break - } - } - wg.Wait() - if host != nil && host.Client.Cnf != nil && host.Client.Cnf.Mux && link != nil { - link.WriteTo([]byte(utils.IO_EOF), host.Client.Cnf.CompressEncode, host.Client.Cnf.Crypt, host.Client.Rate) - s.bridge.ReturnTunnel(link, host.Client.Id) - } else if link != nil { - link.Close() - } - - if isConn { - s.writeConnFail(c.Conn) - } - c.Close() - return nil -} diff --git a/server/server.go b/server/server.go index c9808ca..3cdabb6 100644 --- a/server/server.go +++ b/server/server.go @@ -2,27 +2,17 @@ package server import ( "errors" - "github.com/astaxie/beego" "github.com/cnlh/easyProxy/bridge" "github.com/cnlh/easyProxy/utils" "log" "reflect" "strings" - "sync" ) -type RunServer struct { - flag int //标志 - ExportFlow int64 //出口流量 - InletFlow int64 //入口流量 - service interface{} - sync.Mutex -} - var ( - Bridge *bridge.Bridge - RunList map[int]interface{} //运行中的任务 - CsvDb = utils.GetCsvDb() + Bridge *bridge.Bridge + RunList map[int]interface{} //运行中的任务 + CsvDb = utils.GetCsvDb() ) func init() { @@ -69,9 +59,8 @@ func NewMode(Bridge *bridge.Bridge, c *utils.Tunnel) interface{} { return NewUdpModeServer(Bridge, c) case "webServer": InitFromCsv() - p, _ := beego.AppConfig.Int("hostPort") t := &utils.Tunnel{ - TcpPort: p, + TcpPort: 0, Mode: "httpHostServer", Target: "", Config: &utils.Config{}, @@ -82,7 +71,7 @@ func NewMode(Bridge *bridge.Bridge, c *utils.Tunnel) interface{} { case "hostServer": return NewHostServer(c) case "httpHostServer": - return NewTunnelModeServer(ProcessHost, Bridge, c) + return NewHttp(Bridge, c) } return nil } @@ -161,7 +150,7 @@ func GetTunnel(start, length int, typeVal string, clientId int) ([]*utils.Tunnel continue } cnt++ - if _, ok := Bridge.SignalList[v.Client.Id]; ok { + if _, ok := Bridge.Client[v.Client.Id]; ok { v.Client.IsConnect = true } else { v.Client.IsConnect = false @@ -189,7 +178,7 @@ func GetClientList(start, length int) (list []*utils.Client, cnt int) { func dealClientData(list []*utils.Client) { for _, v := range list { - if _, ok := Bridge.SignalList[v.Id]; ok { + if _, ok := Bridge.Client[v.Id]; ok { v.IsConnect = true } else { v.IsConnect = false @@ -228,8 +217,7 @@ func DelTunnelAndHostByClientId(clientId int) { //关闭客户端连接 func DelClientConnect(clientId int) { - Bridge.DelClientTunnel(clientId) - Bridge.DelClientSignal(clientId) + Bridge.DelClient(clientId) } func GetDashboardData() map[string]int { diff --git a/server/socks5.go b/server/socks5.go index 6ce3597..3d92707 100755 --- a/server/socks5.go +++ b/server/socks5.go @@ -106,7 +106,7 @@ func (s *Sock5ModeServer) sendReply(c net.Conn, rep uint8) { } //do conn -func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) (proxyConn *utils.Conn, err error) { +func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) { addrType := make([]byte, 1) c.Read(addrType) var host string @@ -127,8 +127,7 @@ func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) (proxyConn *utils host = string(domain) default: s.sendReply(c, addrTypeNotSupported) - err = errors.New("Address type not supported") - return nil, err + return } var port uint16 @@ -141,34 +140,22 @@ func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) (proxyConn *utils } else { ltype = utils.CONN_TCP } - if proxyConn, err = s.GetTunnelAndWriteHost(ltype, s.task.Client.Id, s.config, addr); err != nil { - log.Println("get bridge tunnel error: ", err) + link := utils.NewLink(s.task.Client.GetId(), ltype, addr, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, utils.NewConn(c), s.task.Flow, nil, s.task.Client.Rate, nil) + + if tunnel, err := s.bridge.SendLinkInfo(s.task.Client.Id, link); err != nil { + log.Println("error", err, link) + c.Close() return - } - s.sendReply(c, succeeded) - var flag string - if flag, err = proxyConn.ReadFlag(); err == nil { - if flag != utils.CONN_SUCCESS { - err = errors.New("conn failed") - } + } else { + s.sendReply(c, succeeded) + s.linkCopy(link, utils.NewConn(c), nil, tunnel, s.task.Flow) } return } //conn func (s *Sock5ModeServer) handleConnect(c net.Conn) { - proxyConn, err := s.doConnect(c, connectMethod) - defer func() { - if s.config.Mux && proxyConn != nil { - s.bridge.ReturnTunnel(proxyConn, s.task.Client.Id) - } - }() - if err != nil { - c.Close() - } else { - out, in := utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux, s.task.Client.Rate) - s.FlowAdd(in, out) - } + s.doConnect(c, connectMethod) } // passive mode @@ -195,18 +182,7 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) { c.Read(dummy) } - proxyConn, err := s.doConnect(c, associateMethod) - defer func() { - if s.config.Mux && proxyConn != nil { - s.bridge.ReturnTunnel(proxyConn, s.task.Client.Id) - } - }() - if err != nil { - c.Close() - } else { - out, in := utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux, s.task.Client.Rate) - s.FlowAdd(in, out) - } + s.doConnect(c, associateMethod) } //new conn diff --git a/server/tcp.go b/server/tcp.go index a1df0e0..5026294 100755 --- a/server/tcp.go +++ b/server/tcp.go @@ -2,21 +2,18 @@ package server import ( "errors" - "fmt" "github.com/astaxie/beego" "github.com/cnlh/easyProxy/bridge" "github.com/cnlh/easyProxy/utils" "log" "net" - "net/http" "strings" ) type TunnelModeServer struct { server - errorContent []byte - process process - listener *net.TCPListener + process process + listener *net.TCPListener } //tcp|http|host @@ -32,9 +29,6 @@ func NewTunnelModeServer(process process, bridge *bridge.Bridge, task *utils.Tun //开始 func (s *TunnelModeServer) Start() error { var err error - if s.errorContent, err = utils.ReadAllFromFile(beego.AppPath + "/web/static/page/error.html"); err != nil { - s.errorContent = []byte("easyProxy 404") - } s.listener, err = net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), s.task.TcpPort, ""}) if err != nil { return err @@ -53,44 +47,15 @@ func (s *TunnelModeServer) Start() error { return nil } -//权限认证 -func (s *TunnelModeServer) auth(r *http.Request, c *utils.Conn, u, p string) error { - if u != "" && p != "" && !utils.CheckAuth(r, u, p) { - c.Write([]byte(utils.UnauthorizedBytes)) - c.Close() - return errors.New("401 Unauthorized") - } - return nil -} - -func (s *TunnelModeServer) writeConnFail(c net.Conn) { - c.Write([]byte(utils.ConnectionFailBytes)) - c.Write(s.errorContent) -} - //与客户端建立通道 func (s *TunnelModeServer) dealClient(c *utils.Conn, cnf *utils.Config, addr string, method string, rb []byte) error { - var link *utils.Conn - var err error - defer func() { - if cnf.Mux && link != nil { - s.bridge.ReturnTunnel(link, s.task.Client.Id) - } - }() - if link, err = s.GetTunnelAndWriteHost(utils.CONN_TCP, s.task.Client.Id, cnf, addr); err != nil { - log.Println("get bridge tunnel error: ", err) + link := utils.NewLink(s.task.Client.GetId(), utils.CONN_TCP, addr, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, c, s.task.Flow, nil, s.task.Client.Rate, nil) + + if tunnel, err := s.bridge.SendLinkInfo(s.task.Client.Id, link); err != nil { + c.Close() return err - } - if flag, err := link.ReadFlag(); err == nil { - if flag == utils.CONN_SUCCESS { - if method == "CONNECT" { - fmt.Fprint(c, "HTTP/1.1 200 Connection established\r\n") - } else if rb != nil { - link.WriteTo(rb, cnf.CompressEncode, cnf.Crypt, s.task.Client.Rate) - } - out, in := utils.ReplayWaitGroup(link.Conn, c.Conn, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux, s.task.Client.Rate) - s.FlowAdd(in, out) - } + } else { + s.linkCopy(link, c, rb, tunnel, s.task.Flow) } return nil } @@ -142,3 +107,32 @@ func NewHostServer(task *utils.Tunnel) *HostServer { func (s *HostServer) Close() error { return nil } + +type process func(c *utils.Conn, s *TunnelModeServer) error + +//tcp隧道模式 +func ProcessTunnel(c *utils.Conn, s *TunnelModeServer) error { + if !s.ResetConfig() { + c.Close() + return errors.New("流量超出") + } + return s.dealClient(c, s.config, s.task.Target, "", nil) +} + +//http代理模式 +func ProcessHttp(c *utils.Conn, s *TunnelModeServer) error { + if !s.ResetConfig() { + c.Close() + return errors.New("流量超出") + } + method, addr, rb, err, r := c.GetHost() + if err != nil { + log.Println(err) + c.Close() + return err + } + if err := s.auth(r, c, s.config.U, s.config.P); err != nil { + return err + } + return s.dealClient(c, s.config, addr, method, rb) +} diff --git a/server/udp.go b/server/udp.go index 7958113..9557130 100755 --- a/server/udp.go +++ b/server/udp.go @@ -3,8 +3,6 @@ package server import ( "github.com/cnlh/easyProxy/bridge" "github.com/cnlh/easyProxy/utils" - "io" - "log" "net" "strings" ) @@ -31,9 +29,9 @@ func (s *UdpModeServer) Start() error { if err != nil { return err } - data := make([]byte, 1472) //udp数据包大小 + buf := utils.BufPoolUdp.Get().([]byte) for { - n, addr, err := s.listener.ReadFromUDP(data) + n, addr, err := s.listener.ReadFromUDP(buf) if err != nil { if strings.Contains(err.Error(), "use of closed network connection") { break @@ -43,42 +41,19 @@ func (s *UdpModeServer) Start() error { if !s.ResetConfig() { continue } - go s.process(addr, data[:n]) + go s.process(addr, buf[:n]) } return nil } -//TODO:效率问题有待解决--->建立稳定通道,重复利用,提高效率,下个版本 func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) { - conn, err := s.bridge.GetTunnel(s.task.Client.Id, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux) - if err != nil { - log.Println(err) + link := utils.NewLink(s.task.Client.GetId(), utils.CONN_UDP, s.task.Target, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, nil, s.task.Flow, s.listener, s.task.Client.Rate, addr) + + if tunnel, err := s.bridge.SendLinkInfo(s.task.Client.Id, link); err != nil { return - } - if _, err := conn.WriteHost(utils.CONN_UDP, s.task.Target); err != nil { - conn.Close() - return - } - if flag, err := conn.ReadFlag(); err == nil { - defer func() { - if conn != nil && s.config.Mux { - conn.WriteTo([]byte(utils.IO_EOF), s.config.CompressEncode, s.config.Crypt, s.task.Client.Rate) - s.bridge.ReturnTunnel(conn, s.task.Client.Id) - } else { - conn.Close() - } - }() - if flag == utils.CONN_SUCCESS { - in, _ := conn.WriteTo(data, s.config.CompressEncode, s.config.Crypt, s.task.Client.Rate) - buf := utils.BufPoolUdp.Get().([]byte) - out, err := conn.ReadFrom(buf, s.config.CompressDecode, s.config.Crypt, s.task.Client.Rate) - if err != nil || err == io.EOF { - return - } - s.listener.WriteToUDP(buf[:out], addr) - s.FlowAdd(int64(in), int64(out)) - utils.BufPoolUdp.Put(buf) - } + } else { + s.task.Flow.Add(len(data), 0) + tunnel.SendMsg(data, link) } } diff --git a/utils/conn.go b/utils/conn.go index 5f539b3..0876f5f 100755 --- a/utils/conn.go +++ b/utils/conn.go @@ -13,6 +13,7 @@ import ( "net/url" "strconv" "strings" + "sync" "time" ) @@ -52,12 +53,6 @@ func (s *CryptConn) Write(b []byte) (n int, err error) { //解密读 func (s *CryptConn) Read(b []byte) (n int, err error) { - defer func() { - if err == nil && n == len(IO_EOF) && string(b[:n]) == IO_EOF { - err = io.EOF - n = 0 - } - }() var lens int var buf []byte var rb []byte @@ -122,14 +117,8 @@ func (s *SnappyConn) Write(b []byte) (n int, err error) { //snappy压缩读 包含解密 func (s *SnappyConn) Read(b []byte) (n int, err error) { - buf := bufPool.Get().([]byte) - defer func() { - if err == nil && n == len(IO_EOF) && string(b[:n]) == IO_EOF { - err = io.EOF - n = 0 - } - bufPool.Put(buf) - }() + buf := BufPool.Get().([]byte) + defer BufPool.Put(buf) if n, err = s.r.Read(buf); err != nil { return } @@ -152,6 +141,7 @@ func (s *SnappyConn) Read(b []byte) (n int, err error) { type Conn struct { Conn net.Conn + sync.Mutex } //new conn @@ -161,101 +151,7 @@ func NewConn(conn net.Conn) *Conn { return c } -//读取指定长度内容 -func (s *Conn) ReadLen(cLen int) ([]byte, error) { - if cLen > poolSize { - return nil, errors.New("长度错误" + strconv.Itoa(cLen)) - } - var buf []byte - if cLen <= poolSizeSmall { - buf = bufPoolSmall.Get().([]byte)[:cLen] - defer bufPoolSmall.Put(buf) - } else { - buf = bufPoolMax.Get().([]byte)[:cLen] - defer bufPoolMax.Put(buf) - } - if n, err := io.ReadFull(s, buf); err != nil || n != cLen { - return buf, errors.New("读取指定长度错误" + err.Error()) - } - return buf, nil -} - -//获取长度 -func (s *Conn) GetLen() (int, error) { - val, err := s.ReadLen(4) - if err != nil { - return 0, err - } - return GetLenByBytes(val) -} - -//写入长度+内容 粘包 -func (s *Conn) WriteLen(buf []byte) (int, error) { - var b []byte - var err error - if b, err = GetLenBytes(buf); err != nil { - return 0, err - } - return s.Write(b) -} - -//读取flag -func (s *Conn) ReadFlag() (string, error) { - val, err := s.ReadLen(4) - if err != nil { - return "", err - } - return string(val), err -} - -//读取host 连接地址 压缩类型 -func (s *Conn) GetHostFromConn() (typeStr string, host string, en, de int, crypt, mux bool, err error) { -retry: - lType, err := s.ReadLen(3) - if err != nil { - return - } - if typeStr = string(lType); typeStr == TEST_FLAG { - en, de, crypt, mux = s.GetConnInfoFromConn() - goto retry - } else if typeStr != CONN_TCP && typeStr != CONN_UDP { - err = errors.New("unknown conn type") - return - } - cLen, err := s.GetLen() - if err != nil || cLen > poolSize { - return - } - hostByte, err := s.ReadLen(cLen) - if err != nil { - return - } - host = string(hostByte) - return -} - -//写连接类型 和 host地址 -func (s *Conn) WriteHost(ltype string, host string) (int, error) { - raw := bytes.NewBuffer([]byte{}) - binary.Write(raw, binary.LittleEndian, []byte(ltype)) - binary.Write(raw, binary.LittleEndian, int32(len([]byte(host)))) - binary.Write(raw, binary.LittleEndian, []byte(host)) - return s.Write(raw.Bytes()) -} - -//设置连接为长连接 -func (s *Conn) SetAlive() { - conn := s.Conn.(*net.TCPConn) - conn.SetReadDeadline(time.Time{}) - conn.SetKeepAlive(true) - conn.SetKeepAlivePeriod(time.Duration(2 * time.Second)) -} - -func (s *Conn) SetReadDeadline(t time.Duration) { - s.Conn.(*net.TCPConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second)) -} - -//从tcp报文中解析出host,连接类型等 TODO 多种情况 +//从tcp报文中解析出host,连接类型等 func (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http.Request) { var b [32 * 1024]byte var n int @@ -285,6 +181,71 @@ func (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http. return } +//读取指定长度内容 +func (s *Conn) ReadLen(cLen int) ([]byte, error) { + if cLen > poolSize { + return nil, errors.New("长度错误" + strconv.Itoa(cLen)) + } + var buf []byte + if cLen <= poolSizeSmall { + buf = BufPoolSmall.Get().([]byte)[:cLen] + defer BufPoolSmall.Put(buf) + } else { + buf = BufPoolMax.Get().([]byte)[:cLen] + defer BufPoolMax.Put(buf) + } + if n, err := io.ReadFull(s, buf); err != nil || n != cLen { + return buf, errors.New("读取指定长度错误" + err.Error()) + } + return buf, nil +} + +//read length or id (content length=4) +func (s *Conn) GetLen() (int, error) { + val, err := s.ReadLen(4) + if err != nil { + return 0, err + } + return GetLenByBytes(val) +} + +//read flag +func (s *Conn) ReadFlag() (string, error) { + val, err := s.ReadLen(4) + if err != nil { + return "", err + } + return string(val), err +} + +//read connect status +func (s *Conn) GetConnStatus() (id int, status bool, err error) { + id, err = s.GetLen() + if err != nil { + return + } + var b []byte + if b, err = s.ReadLen(1); err != nil { + return + } else { + status = GetBoolByStr(string(b[0])) + } + return +} + +//设置连接为长连接 +func (s *Conn) SetAlive() { + conn := s.Conn.(*net.TCPConn) + conn.SetReadDeadline(time.Time{}) + conn.SetKeepAlive(true) + conn.SetKeepAlivePeriod(time.Duration(2 * time.Second)) +} + +//set read dead time +func (s *Conn) SetReadDeadline(t time.Duration) { + s.Conn.(*net.TCPConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second)) +} + //单独读(加密|压缩) func (s *Conn) ReadFrom(b []byte, compress int, crypt bool, rate *Rate) (int, error) { if COMPRESS_SNAPY_DECODE == compress { @@ -301,24 +262,112 @@ func (s *Conn) WriteTo(b []byte, compress int, crypt bool, rate *Rate) (n int, e return NewCryptConn(s.Conn, crypt, rate).Write(b) } -//写压缩方式,加密 -func (s *Conn) WriteConnInfo(en, de int, crypt, mux bool) { - s.Write([]byte(strconv.Itoa(en) + strconv.Itoa(de) + GetStrByBool(crypt) + GetStrByBool(mux))) -} - -//获取压缩方式,是否加密 -func (s *Conn) GetConnInfoFromConn() (en, de int, crypt, mux bool) { - buf, err := s.ReadLen(4) - if err != nil { +//send msg +func (s *Conn) SendMsg(content []byte, link *Link) (n int, err error) { + /* + The msg info is formed as follows: + +----+--------+ + |id | content | + +----+--------+ + | 4 | ... | + +----+--------+ +*/ + s.Lock() + defer s.Unlock() + raw := bytes.NewBuffer([]byte{}) + binary.Write(raw, binary.LittleEndian, int32(link.Id)) + if n, err = s.Write(raw.Bytes()); err != nil { return } - en, _ = strconv.Atoi(string(buf[0])) - de, _ = strconv.Atoi(string(buf[1])) - crypt = GetBoolByStr(string(buf[2])) - mux = GetBoolByStr(string(buf[3])) + raw.Reset() + binary.Write(raw, binary.LittleEndian, content) + n, err = s.WriteTo(raw.Bytes(), link.En, link.Crypt, link.Rate) return } +//get msg content from conn +func (s *Conn) GetMsgContent(link *Link) (content []byte, err error) { + s.Lock() + defer s.Unlock() + buf := BufPoolCopy.Get().([]byte) + if n, err := s.ReadFrom(buf, link.De, link.Crypt, link.Rate); err == nil && n > 4 { + content = buf[:n] + } + return +} + +//send info for link +func (s *Conn) SendLinkInfo(link *Link) (int, error) { + /* + The link info is formed as follows: + +----------+------+----------+------+----------+-----+ + | id | len | type | hostlen | host | en | de |crypt | + +----------+------+----------+------+---------+------+ + | 4 | 4 | 3 | 4 | host | 1 | 1 | 1 | + +----------+------+----------+------+----+----+------+ + */ + raw := bytes.NewBuffer([]byte{}) + binary.Write(raw, binary.LittleEndian, []byte(NEW_CONN)) + binary.Write(raw, binary.LittleEndian, int32(14+len(link.Host))) + binary.Write(raw, binary.LittleEndian, int32(link.Id)) + binary.Write(raw, binary.LittleEndian, []byte(link.ConnType)) + binary.Write(raw, binary.LittleEndian, int32(len(link.Host))) + binary.Write(raw, binary.LittleEndian, []byte(link.Host)) + binary.Write(raw, binary.LittleEndian, []byte(strconv.Itoa(link.En))) + binary.Write(raw, binary.LittleEndian, []byte(strconv.Itoa(link.De))) + binary.Write(raw, binary.LittleEndian, []byte(GetStrByBool(link.Crypt))) + s.Lock() + defer s.Unlock() + return s.Write(raw.Bytes()) +} + +func (s *Conn) GetLinkInfo() (link *Link, err error) { + s.Lock() + defer s.Unlock() + var hostLen, n int + var buf []byte + if n, err = s.GetLen(); err != nil { + return + } + link = new(Link) + if buf, err = s.ReadLen(n); err != nil { + return + } + if link.Id, err = GetLenByBytes(buf[:4]); err != nil { + return + } + link.ConnType = string(buf[4:7]) + if hostLen, err = GetLenByBytes(buf[7:11]); err != nil { + return + } else { + link.Host = string(buf[11 : 11+hostLen]) + link.En = GetIntNoErrByStr(string(buf[11+hostLen])) + link.De = GetIntNoErrByStr(string(buf[12+hostLen])) + link.Crypt = GetBoolByStr(string(buf[13+hostLen])) + } + return +} + +//write connect success +func (s *Conn) WriteSuccess(id int) (int, error) { + raw := bytes.NewBuffer([]byte{}) + binary.Write(raw, binary.LittleEndian, int32(id)) + binary.Write(raw, binary.LittleEndian, []byte("1")) + s.Lock() + defer s.Unlock() + return s.Write(raw.Bytes()) +} + +//write connect fail +func (s *Conn) WriteFail(id int) (int, error) { + raw := bytes.NewBuffer([]byte{}) + binary.Write(raw, binary.LittleEndian, int32(id)) + binary.Write(raw, binary.LittleEndian, []byte("0")) + s.Lock() + defer s.Unlock() + return s.Write(raw.Bytes()) +} + //close func (s *Conn) Close() error { return s.Conn.Close() @@ -351,29 +400,18 @@ func (s *Conn) WriteClose() (int, error) { //write main func (s *Conn) WriteMain() (int, error) { + s.Lock() + defer s.Unlock() return s.Write([]byte(WORK_MAIN)) } //write chan func (s *Conn) WriteChan() (int, error) { + s.Lock() + defer s.Unlock() return s.Write([]byte(WORK_CHAN)) } -//write test -func (s *Conn) WriteTest() (int, error) { - return s.Write([]byte(TEST_FLAG)) -} - -//write test -func (s *Conn) WriteSuccess() (int, error) { - return s.Write([]byte(CONN_SUCCESS)) -} - -//write test -func (s *Conn) WriteFail() (int, error) { - return s.Write([]byte(CONN_ERROR)) -} - //获取长度+内容 func GetLenBytes(buf []byte) (b []byte, err error) { raw := bytes.NewBuffer([]byte{}) diff --git a/utils/file.go b/utils/file.go index 2a89d38..8b2fca6 100644 --- a/utils/file.go +++ b/utils/file.go @@ -17,61 +17,8 @@ var ( once sync.Once ) -type Flow struct { - ExportFlow int64 //出口流量 - InletFlow int64 //入口流量 - FlowLimit int64 //流量限制,出口+入口 /M -} - -type Client struct { - Cnf *Config - Id int //id - VerifyKey string //验证密钥 - Addr string //客户端ip地址 - Remark string //备注 - Status bool //是否开启 - IsConnect bool //是否连接 - RateLimit int //速度限制 /kb - Flow *Flow //流量 - Rate *Rate //速度控制 -} - -type Tunnel struct { - Id int //Id - TcpPort int //服务端与客户端通信端口 - Mode string //启动方式 - Target string //目标 - Status bool //是否开启 - Client *Client //所属客户端id - Flow *Flow - Config *Config - UseClientCnf bool //是否继承客户端配置 - Remark string //备注 -} - -type Config struct { - U string //socks5验证用户名 - P string //socks5验证密码 - Compress string //压缩方式 - Crypt bool //是否加密 - Mux bool //是否加密 - CompressEncode int //加密方式 - CompressDecode int //解密方式 -} - -type Host struct { - Host string //启动方式 - Target string //目标 - HeaderChange string //host修改 - HostChange string //host修改 - Flow *Flow - Client *Client - Remark string //备注 -} - func NewCsv() *Csv { - c := new(Csv) - return c + return new(Csv) } type Csv struct { @@ -108,7 +55,6 @@ func (s *Csv) StoreTasksToCsv() { task.Config.Compress, utils.GetStrByBool(task.Status), GetStrByBool(task.Config.Crypt), - GetStrByBool(task.Config.Mux), strconv.Itoa(task.Config.CompressEncode), strconv.Itoa(task.Config.CompressDecode), strconv.Itoa(task.Id), @@ -160,17 +106,16 @@ func (s *Csv) LoadTaskFromCsv() { P: item[4], Compress: item[5], Crypt: GetBoolByStr(item[7]), - Mux: GetBoolByStr(item[8]), - CompressEncode: GetIntNoErrByStr(item[9]), - CompressDecode: GetIntNoErrByStr(item[10]), + CompressEncode: GetIntNoErrByStr(item[8]), + CompressDecode: GetIntNoErrByStr(item[9]), }, Status: utils.GetBoolByStr(item[6]), - Id: GetIntNoErrByStr(item[11]), - UseClientCnf: GetBoolByStr(item[13]), - Remark: item[14], + Id: GetIntNoErrByStr(item[10]), + UseClientCnf: GetBoolByStr(item[12]), + Remark: item[13], } post.Flow = new(Flow) - if post.Client, err = s.GetClient(GetIntNoErrByStr(item[12])); err != nil { + if post.Client, err = s.GetClient(GetIntNoErrByStr(item[11])); err != nil { continue } tasks = append(tasks, post) @@ -284,13 +229,12 @@ func (s *Csv) LoadClientFromCsv() { VerifyKey: item[1], Remark: item[2], Status: GetBoolByStr(item[3]), - RateLimit: GetIntNoErrByStr(item[9]), + RateLimit: GetIntNoErrByStr(item[8]), Cnf: &Config{ U: item[4], P: item[5], Crypt: GetBoolByStr(item[6]), - Mux: GetBoolByStr(item[7]), - Compress: item[8], + Compress: item[7], }, } if post.Id > s.ClientIncreaseId { @@ -301,7 +245,7 @@ func (s *Csv) LoadClientFromCsv() { post.Rate.Start() } post.Flow = new(Flow) - post.Flow.FlowLimit = int64(utils.GetIntNoerrByStr(item[10])) + post.Flow.FlowLimit = int64(utils.GetIntNoerrByStr(item[9])) clients = append(clients, post) } s.Clients = clients @@ -407,10 +351,14 @@ func (s *Csv) GetClientId() int { func (s *Csv) UpdateClient(t *Client) error { s.Lock() defer s.Unlock() - for k, v := range s.Clients { + for _, v := range s.Clients { if v.Id == t.Id { - s.Clients = append(s.Clients[:k], s.Clients[k+1:]...) - s.Clients = append(s.Clients, t) + v.Cnf = t.Cnf + v.VerifyKey = t.VerifyKey + v.Remark = t.Remark + v.RateLimit = t.RateLimit + v.Flow = t.Flow + v.Rate = t.Rate s.StoreClientsToCsv() return nil } @@ -458,7 +406,6 @@ func (s *Csv) StoreClientsToCsv() { client.Cnf.U, client.Cnf.P, utils.GetStrByBool(client.Cnf.Crypt), - utils.GetStrByBool(client.Cnf.Mux), client.Cnf.Compress, strconv.Itoa(client.RateLimit), strconv.Itoa(int(client.Flow.FlowLimit)), @@ -480,15 +427,3 @@ func GetCsvDb() *Csv { return CsvDb } -//深拷贝Tunnel -func DeepCopyConfig(c *Config) *Config { - return &Config{ - U: c.U, - P: c.P, - Compress: c.Compress, - Crypt: c.Crypt, - Mux: c.Mux, - CompressEncode: c.CompressEncode, - CompressDecode: c.CompressDecode, - } -} diff --git a/utils/link.go b/utils/link.go new file mode 100644 index 0000000..33a468d --- /dev/null +++ b/utils/link.go @@ -0,0 +1,116 @@ +package utils + +import ( + "net" + "sync" +) + +type Link struct { + Id int //id + ConnType string //连接类型 + Host string //目标 + En int //加密 + De int //解密 + Crypt bool //加密 + Conn *Conn + Flow *Flow + UdpListener *net.UDPConn + Rate *Rate + UdpRemoteAddr *net.UDPAddr +} + +func NewLink(id int, connType string, host string, en, de int, crypt bool, conn *Conn, flow *Flow, udpListener *net.UDPConn, rate *Rate, UdpRemoteAddr *net.UDPAddr) *Link { + return &Link{ + Id: id, + ConnType: connType, + Host: host, + En: en, + De: de, + Crypt: crypt, + Conn: conn, + Flow: flow, + UdpListener: udpListener, + Rate: rate, + UdpRemoteAddr: UdpRemoteAddr, + } +} + +type Flow struct { + ExportFlow int64 //出口流量 + InletFlow int64 //入口流量 + FlowLimit int64 //流量限制,出口+入口 /M + sync.RWMutex +} + +func (s *Flow) Add(in, out int) { + s.Lock() + defer s.Unlock() + s.InletFlow += int64(in) + s.ExportFlow += int64(out) +} + +type Client struct { + Cnf *Config + Id int //id + VerifyKey string //验证密钥 + Addr string //客户端ip地址 + Remark string //备注 + Status bool //是否开启 + IsConnect bool //是否连接 + RateLimit int //速度限制 /kb + Flow *Flow //流量 + Rate *Rate //速度控制 + id int + sync.RWMutex +} + +func (s *Client) GetId() int { + s.Lock() + defer s.Unlock() + s.id++ + return s.id +} + +type Tunnel struct { + Id int //Id + TcpPort int //服务端与客户端通信端口 + Mode string //启动方式 + Target string //目标 + Status bool //是否开启 + Client *Client //所属客户端id + Flow *Flow + Config *Config + UseClientCnf bool //是否继承客户端配置 + Remark string //备注 +} + +type Config struct { + U string //socks5验证用户名 + P string //socks5验证密码 + Compress string //压缩方式 + Crypt bool //是否加密 + CompressEncode int //加密方式 + CompressDecode int //解密方式 +} + +type Host struct { + Host string //启动方式 + Target string //目标 + HeaderChange string //host修改 + HostChange string //host修改 + Flow *Flow + Client *Client + Remark string //备注 +} + +//深拷贝Config +func DeepCopyConfig(c *Config) *Config { + return &Config{ + U: c.U, + P: c.P, + Compress: c.Compress, + Crypt: c.Crypt, + CompressEncode: c.CompressEncode, + CompressDecode: c.CompressDecode, + } +} diff --git a/utils/pool.go b/utils/pool.go index 56c2b4d..26ad716 100644 --- a/utils/pool.go +++ b/utils/pool.go @@ -1,13 +1,15 @@ package utils -import "sync" +import ( + "sync" +) const poolSize = 64 * 1024 const poolSizeSmall = 100 const poolSizeUdp = 1472 const poolSizeCopy = 32 * 1024 -var bufPool = sync.Pool{ +var BufPool = sync.Pool{ New: func() interface{} { return make([]byte, poolSize) }, @@ -18,18 +20,24 @@ var BufPoolUdp = sync.Pool{ return make([]byte, poolSizeUdp) }, } -var bufPoolMax = sync.Pool{ +var BufPoolMax = sync.Pool{ New: func() interface{} { return make([]byte, poolSize) }, } -var bufPoolSmall = sync.Pool{ +var BufPoolSmall = sync.Pool{ New: func() interface{} { return make([]byte, poolSizeSmall) }, } -var bufPoolCopy = sync.Pool{ +var BufPoolCopy = sync.Pool{ New: func() interface{} { return make([]byte, poolSizeCopy) }, } + +func PutBufPoolCopy(buf []byte) { + if cap(buf) == poolSizeCopy { + BufPoolCopy.Put(buf[:poolSizeCopy]) + } +} diff --git a/utils/util.go b/utils/util.go index b49d12e..33df543 100755 --- a/utils/util.go +++ b/utils/util.go @@ -2,7 +2,6 @@ package utils import ( "encoding/base64" - "io" "io/ioutil" "log" "net" @@ -11,8 +10,6 @@ import ( "regexp" "strconv" "strings" - "sync" - "time" ) const ( @@ -26,9 +23,8 @@ const ( RES_SIGN = "sign" RES_MSG = "msg0" RES_CLOSE = "clse" + NEW_CONN = "conn" //新连接标志 CONN_SUCCESS = "sucs" - CONN_ERROR = "fail" - TEST_FLAG = "tst" CONN_TCP = "tcp" CONN_UDP = "udp" UnauthorizedBytes = `HTTP/1.1 401 Unauthorized @@ -42,32 +38,6 @@ WWW-Authenticate: Basic realm="easyProxy" ` ) -//copy -func Relay(in, out net.Conn, compressType int, crypt, mux bool, rate *Rate) (n int64, err error) { - switch compressType { - case COMPRESS_SNAPY_ENCODE: - n, err = copyBuffer(NewSnappyConn(in, crypt, rate), out) - out.Close() - NewSnappyConn(in, crypt, rate).Write([]byte(IO_EOF)) - case COMPRESS_SNAPY_DECODE: - n, err = copyBuffer(in, NewSnappyConn(out, crypt, rate)) - in.Close() - if !mux { - out.Close() - } - case COMPRESS_NONE_ENCODE: - n, err = copyBuffer(NewCryptConn(in, crypt, rate), out) - out.Close() - NewCryptConn(in, crypt, rate).Write([]byte(IO_EOF)) - case COMPRESS_NONE_DECODE: - n, err = copyBuffer(in, NewCryptConn(out, crypt, rate)) - in.Close() - if !mux { - out.Close() - } - } - return -} //判断压缩方式 func GetCompressType(compress string) (int, int) { @@ -152,71 +122,11 @@ func GetIntNoErrByStr(str string) int { return i } -// io.copy的优化版,读取buffer长度原为32*1024,与snappy不同,导致读取出的内容存在差异,不利于解密 -//内存优化 用到pool,快速回收 -func copyBuffer(dst io.Writer, src io.Reader) (written int64, err error) { - for { - //放在里面是为了加快回收和重利用 - buf := bufPoolCopy.Get().([]byte) - nr, er := src.Read(buf) - if nr > 0 { - nw, ew := dst.Write(buf[0:nr]) - bufPoolCopy.Put(buf) - if nw > 0 { - written += int64(nw) - } - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - } else { - bufPoolCopy.Put(buf) - } - if er != nil { - if er != io.EOF { - err = er - } - break - } - } - return written, err -} - -//连接重置 清空缓存区 -func FlushConn(c net.Conn) { - c.SetReadDeadline(time.Now().Add(time.Second * 3)) - buf := bufPool.Get().([]byte) - defer bufPool.Put(buf) - for { - if _, err := c.Read(buf); err != nil { - break - } - } - c.SetReadDeadline(time.Time{}) -} - //简单的一个校验值 func Getverifyval(vkey string) string { return Md5(vkey) } -//wait replay group -//conn1 网桥 conn2 -func ReplayWaitGroup(conn1 net.Conn, conn2 net.Conn, compressEncode, compressDecode int, crypt, mux bool, rate *Rate) (out int64, in int64) { - var wg sync.WaitGroup - wg.Add(1) - go func() { - in, _ = Relay(conn1, conn2, compressEncode, crypt, mux, rate) - wg.Done() - }() - out, _ = Relay(conn2, conn1, compressDecode, crypt, mux, rate) - wg.Wait() - return -} func ChangeHostAndHeader(r *http.Request, host string, header string, addr string) { if host != "" { @@ -236,8 +146,8 @@ func ChangeHostAndHeader(r *http.Request, host string, header string, addr strin r.Header.Set("X-Real-IP", addr) } -func ReadAllFromFile(filePth string) ([]byte, error) { - f, err := os.Open(filePth) +func ReadAllFromFile(filePath string) ([]byte, error) { + f, err := os.Open(filePath) if err != nil { return nil, err } diff --git a/web/controllers/base.go b/web/controllers/base.go index f4c5097..017f528 100755 --- a/web/controllers/base.go +++ b/web/controllers/base.go @@ -40,7 +40,8 @@ func (s *BaseController) display(tpl ...string) { } ip := s.Ctx.Request.Host if strings.LastIndex(ip, ":") > 0 { - s.Data["ip"] = utils.GetHostByName(ip[0:]) + arr := strings.Split(utils.GetHostByName(ip), ":") + s.Data["ip"] = arr[0] } s.Data["p"] = server.Bridge.TunnelPort s.Data["proxyPort"] = beego.AppConfig.String("hostPort") diff --git a/web/controllers/client.go b/web/controllers/client.go index 935e126..6f8eb43 100644 --- a/web/controllers/client.go +++ b/web/controllers/client.go @@ -38,7 +38,6 @@ func (s *ClientController) Add() { P: s.GetString("p"), Compress: s.GetString("compress"), Crypt: s.GetBoolNoErr("crypt"), - Mux: s.GetBoolNoErr("mux"), }, RateLimit: s.GetIntNoErr("rate_limit"), Flow: &utils.Flow{ @@ -91,7 +90,6 @@ func (s *ClientController) Edit() { c.Cnf.P = s.GetString("p") c.Cnf.Compress = s.GetString("compress") c.Cnf.Crypt = s.GetBoolNoErr("crypt") - c.Cnf.Mux = s.GetBoolNoErr("mux") c.Flow.FlowLimit = int64(s.GetIntNoErr("flow_limit")) c.RateLimit = s.GetIntNoErr("rate_limit") if c.Rate != nil { diff --git a/web/controllers/index.go b/web/controllers/index.go index ec52003..93da245 100755 --- a/web/controllers/index.go +++ b/web/controllers/index.go @@ -81,7 +81,6 @@ func (s *IndexController) Add() { P: s.GetString("p"), Compress: s.GetString("compress"), Crypt: s.GetBoolNoErr("crypt"), - Mux: s.GetBoolNoErr("mux"), }, Id: server.CsvDb.GetTaskId(), UseClientCnf: s.GetBoolNoErr("use_client"), @@ -136,7 +135,6 @@ func (s *IndexController) Edit() { t.Config.P = s.GetString("p") t.Config.Compress = s.GetString("compress") t.Config.Crypt = s.GetBoolNoErr("crypt") - t.Config.Mux = s.GetBoolNoErr("mux") t.UseClientCnf = s.GetBoolNoErr("use_client") t.Remark = s.GetString("remark") if t.Client, err = server.CsvDb.GetClient(s.GetIntNoErr("client_id")); err != nil { diff --git a/web/views/client/add.html b/web/views/client/add.html index f567d93..d3b651c 100755 --- a/web/views/client/add.html +++ b/web/views/client/add.html @@ -38,13 +38,6 @@ -
使用步骤:
- server {
- listen 80;
- server_name a.proxy.com b.proxy.com;#也可以是泛解析*.proxy.com
- #ssl等配置
- location / {
- proxy_pass http://127.0.0.1:{{.proxyPort}};
- }
- }
-
-
+ ./proxy_client server={{.ip}}:{{.p}} -vkey=客户端的密钥
+
注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动
+注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,如需使用https请在配置文件中将https端口设置为443,和将对应的证书文件路径添加到配置文件中 +
使用步骤:
+ ./proxy_client server={{.ip}}:{{.p}} -vkey=客户端的密钥
+
+ 注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动
@@ -80,9 +73,12 @@使用步骤:
+ ./proxy_client server={{.ip}}:{{.p}} -vkey=客户端的密钥
+
+ 注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动
@@ -101,9 +97,12 @@使用步骤:
+ ./proxy_client server={{.ip}}:{{.p}} -vkey=客户端的密钥
+
+ 注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动
@@ -120,13 +119,23 @@使用步骤:
+ ./proxy_client server={{.ip}}:{{.p}} -vkey=客户端的密钥
+
+ 注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动
+单个客户端可以田间多条隧道或者域名解析
+