mirror of https://github.com/qwqdanchun/nps.git
新功能+bug修复
This commit is contained in:
parent
bb882f348a
commit
851241a0c7
55
README.md
55
README.md
|
@ -1,7 +1,7 @@
|
|||
# easyProxy
|
||||
![](https://img.shields.io/github/stars/cnlh/easyProxy.svg) ![](https://img.shields.io/github/forks/cnlh/easyProxy.svg) ![](https://img.shields.io/github/license/cnlh/easyProxy.svg)
|
||||
|
||||
easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何tcp、udp上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析等等……),此外还**支持内网http代理、内网socks5代理**,可实现在非内网环境下如同使用vpn一样访问内网资源和设备的效果,同时**支持socks5验证,snnapy压缩(节省带宽和流量)、站点保护、加密传输、多路复用**。
|
||||
easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何tcp、udp上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析等等……),此外还**支持内网http代理、内网socks5代理**,可实现在非内网环境下如同使用vpn一样访问内网资源和设备的效果,同时**支持socks5验证,snnapy压缩(节省带宽和流量)、站点保护、加密传输、多路复用、host修改、自定义header**。
|
||||
|
||||
目前市面上提供类似服务的有花生壳、TeamView、GoToMyCloud等等,但要使用第三方的公网服务器就必须为第三方付费,并且这些服务都有各种各样的限制,此外,由于数据包会流经第三方,因此对数据安全也是一大隐患。
|
||||
|
||||
|
@ -36,6 +36,8 @@ easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透**
|
|||
- [x] 支持TCP多路复用
|
||||
- [x] 支持同时开多条tcp、udp隧道等等,且只需要开一个客户端和服务端
|
||||
- [x] 支持一个服务端,多个客户端模式
|
||||
- [x] host修改支持
|
||||
- [x] 自定义设置header支持
|
||||
|
||||
## 目录
|
||||
|
||||
|
@ -49,7 +51,10 @@ easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透**
|
|||
8. [站点密码保护](#站点保护)
|
||||
9. [加密传输](#加密传输)
|
||||
10. [TCP多路复用](#多路复用)
|
||||
11. [配置文件说明](#配置文件)
|
||||
11. [host修改](#host修改)
|
||||
12. [自定义header](#自定义header)
|
||||
13. [获取用户真实 IP](#获取用户真实 IP)
|
||||
|
||||
|
||||
## 安装
|
||||
|
||||
|
@ -62,8 +67,9 @@ easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透**
|
|||
- 安装源码
|
||||
> go get github.com/cnlh/easyProxy
|
||||
- 编译
|
||||
> go build cmd/proxy_server.go
|
||||
> go build cmd/proxy_client.go
|
||||
> go build cmd/server/proxy_server.go
|
||||
|
||||
> go build cmd/client/proxy_client.go
|
||||
|
||||
## web管理模式
|
||||
|
||||
|
@ -71,11 +77,22 @@ easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透**
|
|||
### 介绍
|
||||
|
||||
可在网页上配置和管理各个tcp、udp隧道、内网站点代理等等,功能极为强大,操作也非常方便。
|
||||
### 服务端配置文件
|
||||
- /conf/app.conf
|
||||
|
||||
名称 | 含义
|
||||
---|---
|
||||
httpport | web管理端口
|
||||
password | web界面管理密码
|
||||
hostPort | 域名代理模式监听端口
|
||||
tcpport | 服务端客户端通信端口
|
||||
|
||||
**提示:使用web模式时,服务端执行文件必须在项目根目录,否则无法正确加载配置文件**
|
||||
|
||||
### 使用
|
||||
|
||||
|
||||
|
||||
**有两种模式:**
|
||||
|
||||
1、单客户端模式,所有的隧道流量均从这个单客户端转发。
|
||||
|
@ -84,13 +101,11 @@ easyProxy是一款轻量级、高性能、功能最为强大的**内网穿透**
|
|||
- 服务端
|
||||
|
||||
```
|
||||
./proxy_server -mode=webServer -tcpport=8284 -vkey=DKibZF5TXvic1g3kY
|
||||
./proxy_server -vkey=DKibZF5TXvic1g3kY
|
||||
```
|
||||
名称 | 含义
|
||||
---|---
|
||||
mode | 运行模式
|
||||
vkey | 验证密钥
|
||||
tcpport | 服务端与客户端通信端口
|
||||
|
||||
|
||||
- 客户端
|
||||
|
@ -100,7 +115,7 @@ tcpport | 服务端与客户端通信端口
|
|||
```
|
||||
- 配置
|
||||
|
||||
进入web界面,公网ip:web界面端口(默认8080),密码为123
|
||||
进入web界面,公网ip:web界面端口(默认8080),密码默认为123
|
||||
|
||||
2、多客户端模式,不同的隧道流量均从不同的客户端转发。
|
||||
|
||||
|
@ -108,12 +123,11 @@ tcpport | 服务端与客户端通信端口
|
|||
- 服务端
|
||||
|
||||
```
|
||||
./proxy_server -mode=webServer -tcpport=8284
|
||||
./proxy_server
|
||||
```
|
||||
名称 | 含义
|
||||
---|---
|
||||
mode | 运行模式
|
||||
tcpport | 服务端与客户端通信端口
|
||||
- 客户端
|
||||
|
||||
进入web管理界面,有详细的命令
|
||||
|
@ -124,7 +138,6 @@ tcpport | 服务端与客户端通信端口
|
|||
|
||||
|
||||
|
||||
|
||||
## tcp隧道模式
|
||||
|
||||
### 场景及原理
|
||||
|
@ -354,16 +367,22 @@ easyProxy支持通过 HTTP Basic Auth 来保护你的 web 服务,使用户需
|
|||
|
||||
web管理中也可配置
|
||||
|
||||
## host修改
|
||||
|
||||
|
||||
## 配置文件
|
||||
- /conf/app.conf
|
||||
通常情况下本代理不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于域名代理模式。
|
||||
|
||||
名称 | 含义
|
||||
---|---
|
||||
httpport | web管理端口
|
||||
password | web界面管理密码
|
||||
hostPort | 域名代理模式监听端口
|
||||
**使用方法:在web管理中设置**
|
||||
|
||||
## 自定义header
|
||||
|
||||
支持对header进行新增或者修改,以配合服务的需要
|
||||
|
||||
## 获取用户真实 IP
|
||||
|
||||
目前只有域名模式的代理支持这一功能,可以通过用户请求的 header 中的 X-Forwarded-For 和 X-Real-IP 来获取用户真实 IP。
|
||||
|
||||
**本代理前会在每一个请求中添加了这两个 header。**
|
||||
|
||||
|
||||
## 操作系统支持
|
||||
|
|
|
@ -92,7 +92,6 @@ func (s *Tunnel) cliProcess(c *utils.Conn) error {
|
|||
s.verifyError(c)
|
||||
return errors.New("验证错误")
|
||||
}
|
||||
log.Println("客户端连接成功: ", c.Conn.RemoteAddr())
|
||||
c.Conn.(*net.TCPConn).SetReadDeadline(time.Time{})
|
||||
//做一个判断 添加到对应的channel里面以供使用
|
||||
if flag, err := c.ReadFlag(); err != nil {
|
||||
|
@ -106,6 +105,7 @@ func (s *Tunnel) cliProcess(c *utils.Conn) error {
|
|||
func (s *Tunnel) typeDeal(typeVal string, c *utils.Conn, cFlag string) error {
|
||||
switch typeVal {
|
||||
case utils.WORK_MAIN:
|
||||
log.Println("客户端连接成功", c.Conn.RemoteAddr())
|
||||
s.addList(s.SignalList, c, cFlag)
|
||||
case utils.WORK_CHAN:
|
||||
s.addList(s.TunnelList, c, cFlag)
|
||||
|
@ -131,14 +131,13 @@ func (s *Tunnel) addList(m map[string]*list, c *utils.Conn, cFlag string) {
|
|||
|
||||
//新建隧道
|
||||
func (s *Tunnel) newChan(cFlag string) error {
|
||||
if err := s.wait(s.SignalList, cFlag); err != nil {
|
||||
var connPass *utils.Conn
|
||||
var err error
|
||||
retry:
|
||||
if connPass, err = s.waitAndPop(s.SignalList, cFlag); err != nil {
|
||||
return err
|
||||
}
|
||||
retry:
|
||||
connPass := s.SignalList[cFlag].Pop()
|
||||
_, err := connPass.Conn.Write([]byte("chan"))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
if _, err = connPass.Conn.Write([]byte("chan")); err != nil {
|
||||
goto retry
|
||||
}
|
||||
s.SignalList[cFlag].Add(connPass)
|
||||
|
@ -152,10 +151,9 @@ func (s *Tunnel) GetTunnel(cFlag string, en, de int, crypt, mux bool) (c *utils.
|
|||
go s.newChan(cFlag)
|
||||
}
|
||||
retry:
|
||||
if err = s.wait(s.TunnelList, cFlag); err != nil {
|
||||
if c, err = s.waitAndPop(s.TunnelList, cFlag); err != nil {
|
||||
return
|
||||
}
|
||||
c = s.TunnelList[cFlag].Pop()
|
||||
if _, err = c.WriteTest(); err != nil {
|
||||
c.Close()
|
||||
goto retry
|
||||
|
@ -212,22 +210,26 @@ func (s *Tunnel) delClient(cFlag string, l map[string]*list) {
|
|||
}
|
||||
|
||||
//等待
|
||||
func (s *Tunnel) wait(m map[string]*list, cFlag string) error {
|
||||
func (s *Tunnel) waitAndPop(m map[string]*list, cFlag string) (c *utils.Conn, err error) {
|
||||
ticker := time.NewTicker(time.Millisecond * 100)
|
||||
stop := time.After(time.Second * 10)
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if _, ok := m[cFlag]; ok {
|
||||
s.lock.Lock()
|
||||
if v, ok := m[cFlag]; ok && v.Len() > 0 {
|
||||
c = v.Pop()
|
||||
ticker.Stop()
|
||||
break loop
|
||||
s.lock.Unlock()
|
||||
return
|
||||
}
|
||||
s.lock.Unlock()
|
||||
case <-stop:
|
||||
return errors.New("client key: " + cFlag + ",err: get client conn timeout")
|
||||
err = errors.New("client key: " + cFlag + ",err: get client conn timeout")
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Tunnel) verify(vKeyMd5 string) bool {
|
||||
|
|
|
@ -122,6 +122,5 @@ func Process(c *utils.Conn, typeStr, host string, en, de int, crypt, mux bool) {
|
|||
return
|
||||
}
|
||||
c.WriteSuccess()
|
||||
go utils.Relay(server, c.Conn, de, crypt, mux)
|
||||
utils.Relay(c.Conn, server, en, crypt, mux)
|
||||
utils.ReplayWaitGroup(c.Conn, server, en, de, crypt, mux)
|
||||
}
|
||||
|
|
|
@ -14,9 +14,6 @@ var (
|
|||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
//go func() {
|
||||
// http.ListenAndServe("0.0.0.0:8899", nil)
|
||||
//}()
|
||||
stop := make(chan int)
|
||||
for _, v := range strings.Split(*verifyKey, ",") {
|
||||
log.Println("客户端启动,连接:", *serverAddr, " 验证令牌:", v)
|
||||
|
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/cnlh/easyProxy/server"
|
||||
"github.com/cnlh/easyProxy/utils"
|
||||
_ "github.com/cnlh/easyProxy/web/routers"
|
||||
|
@ -9,10 +10,9 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
configPath = flag.String("config", "config.json", "配置文件路径")
|
||||
TcpPort = flag.Int("tcpport", 8284, "客户端与服务端通信端口")
|
||||
TcpPort = flag.Int("tcpport", 0, "客户端与服务端通信端口")
|
||||
httpPort = flag.Int("httpport", 8024, "对外监听的端口")
|
||||
rpMode = flag.String("mode", "client", "启动模式")
|
||||
rpMode = flag.String("mode", "webServer", "启动模式")
|
||||
tunnelTarget = flag.String("target", "10.1.50.203:80", "远程目标")
|
||||
VerifyKey = flag.String("vkey", "", "验证密钥")
|
||||
u = flag.String("u", "", "验证用户名(socks5和web)")
|
||||
|
@ -42,6 +42,15 @@ func main() {
|
|||
CompressEncode: 0,
|
||||
CompressDecode: 0,
|
||||
}
|
||||
if *TcpPort == 0 {
|
||||
p, err := beego.AppConfig.Int("tcpport")
|
||||
if err == nil && *rpMode == "webServer" {
|
||||
*TcpPort = p
|
||||
} else {
|
||||
*TcpPort = 8284
|
||||
}
|
||||
}
|
||||
log.SetFlags(log.Lshortfile)
|
||||
cnf.CompressDecode, cnf.CompressEncode = utils.GetCompressType(cnf.Compress)
|
||||
server.StartNewServer(*TcpPort, &cnf)
|
||||
}
|
||||
|
|
|
@ -11,3 +11,6 @@ password=123
|
|||
|
||||
#http监听端口
|
||||
hostPort=80
|
||||
|
||||
##客户端与服务端通信端口
|
||||
tcpport=8284
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
b.proxy.com,127.0.0.1:82,o2430bnq22jgnmcl
|
||||
b.o.com,127.0.0.1:88,ts08z6vk5nc72fs8
|
||||
a.o.com,127.0.0.1:88,7n7bxc2bm1fyjfab
|
||||
a.o.com,127.0.0.1:8082,7hiixust68kbz33a,,www.baidu.com
|
||||
b.o.com,,7hiixust68kbz33a,,ab
|
||||
|
|
|
144
server/file.go
144
server/file.go
|
@ -23,14 +23,16 @@ type ServerConfig struct {
|
|||
ClientStatus int //客s户端状态
|
||||
Crypt bool //是否加密
|
||||
Mux bool //是否加密
|
||||
CompressEncode int
|
||||
CompressDecode int
|
||||
CompressEncode int //加密方式
|
||||
CompressDecode int //解密方式
|
||||
}
|
||||
|
||||
type HostList struct {
|
||||
Vkey string //服务端与客户端通信端口
|
||||
Host string //启动方式
|
||||
Target string //目标
|
||||
Vkey string //服务端与客户端通信端口
|
||||
Host string //启动方式
|
||||
Target string //目标
|
||||
HeaderChange string //host修改
|
||||
HostChange string //host修改
|
||||
}
|
||||
|
||||
func NewCsv(runList map[string]interface{}) *Csv {
|
||||
|
@ -125,64 +127,6 @@ func (s *Csv) LoadTaskFromCsv() {
|
|||
s.Tasks = tasks
|
||||
}
|
||||
|
||||
func (s *Csv) StoreHostToCsv() {
|
||||
// 创建文件
|
||||
csvFile, err := os.Create(beego.AppPath + "/conf/hosts.csv")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer csvFile.Close()
|
||||
// 获取csv的Writer
|
||||
writer := csv.NewWriter(csvFile)
|
||||
// 将map中的Post转换成slice,因为csv的Write需要slice参数
|
||||
// 并写入csv文件
|
||||
for _, host := range s.Hosts {
|
||||
record := []string{
|
||||
host.Host,
|
||||
host.Target,
|
||||
host.Vkey,
|
||||
}
|
||||
err1 := writer.Write(record)
|
||||
if err1 != nil {
|
||||
panic(err1)
|
||||
}
|
||||
}
|
||||
// 确保所有内存数据刷到csv文件
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
func (s *Csv) LoadHostFromCsv() {
|
||||
// 打开文件
|
||||
file, err := os.Open(beego.AppPath + "/conf/hosts.csv")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 获取csv的reader
|
||||
reader := csv.NewReader(file)
|
||||
|
||||
// 设置FieldsPerRecord为-1
|
||||
reader.FieldsPerRecord = -1
|
||||
|
||||
// 读取文件中所有行保存到slice中
|
||||
records, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var hosts []*HostList
|
||||
// 将每一行数据保存到内存slice中
|
||||
for _, item := range records {
|
||||
post := &HostList{
|
||||
Vkey: item[2],
|
||||
Host: item[0],
|
||||
Target: item[1],
|
||||
}
|
||||
hosts = append(hosts, post)
|
||||
}
|
||||
s.Hosts = hosts
|
||||
}
|
||||
|
||||
func (s *Csv) NewTask(t *ServerConfig) {
|
||||
s.Tasks = append(s.Tasks, t)
|
||||
s.StoreTasksToCsv()
|
||||
|
@ -200,6 +144,18 @@ func (s *Csv) UpdateTask(t *ServerConfig) error {
|
|||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) UpdateHost(t *HostList) error {
|
||||
for k, v := range s.Hosts {
|
||||
if v.Host == t.Host {
|
||||
s.Hosts = append(s.Hosts[:k], s.Hosts[k+1:]...)
|
||||
s.Hosts = append(s.Hosts, t)
|
||||
s.StoreHostToCsv()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
func (s *Csv) AddRunList(vKey string, svr interface{}) {
|
||||
s.RunList[vKey] = svr
|
||||
}
|
||||
|
@ -229,6 +185,68 @@ func (s *Csv) GetTask(vKey string) (v *ServerConfig, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (s *Csv) StoreHostToCsv() {
|
||||
// 创建文件
|
||||
csvFile, err := os.Create(beego.AppPath + "/conf/hosts.csv")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer csvFile.Close()
|
||||
// 获取csv的Writer
|
||||
writer := csv.NewWriter(csvFile)
|
||||
// 将map中的Post转换成slice,因为csv的Write需要slice参数
|
||||
// 并写入csv文件
|
||||
for _, host := range s.Hosts {
|
||||
record := []string{
|
||||
host.Host,
|
||||
host.Target,
|
||||
host.Vkey,
|
||||
host.HeaderChange,
|
||||
host.HostChange,
|
||||
}
|
||||
err1 := writer.Write(record)
|
||||
if err1 != nil {
|
||||
panic(err1)
|
||||
}
|
||||
}
|
||||
// 确保所有内存数据刷到csv文件
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
func (s *Csv) LoadHostFromCsv() {
|
||||
// 打开文件
|
||||
file, err := os.Open(beego.AppPath + "/conf/hosts.csv")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 获取csv的reader
|
||||
reader := csv.NewReader(file)
|
||||
|
||||
// 设置FieldsPerRecord为-1
|
||||
reader.FieldsPerRecord = -1
|
||||
|
||||
// 读取文件中所有行保存到slice中
|
||||
records, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var hosts []*HostList
|
||||
// 将每一行数据保存到内存slice中
|
||||
for _, item := range records {
|
||||
post := &HostList{
|
||||
Vkey: item[2],
|
||||
Host: item[0],
|
||||
Target: item[1],
|
||||
HeaderChange: item[3],
|
||||
HostChange: item[4],
|
||||
}
|
||||
hosts = append(hosts, post)
|
||||
}
|
||||
s.Hosts = hosts
|
||||
}
|
||||
|
||||
func (s *Csv) DelHost(host string) error {
|
||||
for k, v := range s.Hosts {
|
||||
if v.Host == host {
|
||||
|
|
|
@ -162,15 +162,14 @@ func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) (proxyConn *utils
|
|||
func (s *Sock5ModeServer) handleConnect(c net.Conn) {
|
||||
proxyConn, err := s.doConnect(c, connectMethod)
|
||||
defer func() {
|
||||
if s.config.Mux {
|
||||
if s.config.Mux && proxyConn != nil {
|
||||
s.bridge.ReturnTunnel(proxyConn, getverifyval(s.config.VerifyKey))
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
} else {
|
||||
go utils.Relay(proxyConn.Conn, c, s.config.CompressEncode, s.config.Crypt, s.config.Mux)
|
||||
utils.Relay(c, proxyConn.Conn, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
|
||||
utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,15 +199,14 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) {
|
|||
|
||||
proxyConn, err := s.doConnect(c, associateMethod)
|
||||
defer func() {
|
||||
if s.config.Mux {
|
||||
if s.config.Mux && proxyConn != nil {
|
||||
s.bridge.ReturnTunnel(proxyConn, getverifyval(s.config.VerifyKey))
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
} else {
|
||||
go utils.Relay(proxyConn.Conn, c, s.config.CompressEncode, s.config.Crypt, s.config.Mux)
|
||||
utils.Relay(c, proxyConn.Conn, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
|
||||
utils.ReplayWaitGroup(proxyConn.Conn, c, s.config.CompressEncode, s.config.CompressDecode, s.config.Crypt, s.config.Mux)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
114
server/tcp.go
114
server/tcp.go
|
@ -1,6 +1,7 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/astaxie/beego"
|
||||
|
@ -9,7 +10,9 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type process func(c *utils.Conn, s *TunnelModeServer) error
|
||||
|
@ -61,28 +64,23 @@ func (s *TunnelModeServer) auth(r *http.Request, c *utils.Conn, u, p string) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *TunnelModeServer) dealClient2(c *utils.Conn, cnf *ServerConfig, addr string, method string, rb []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//与客户端建立通道
|
||||
func (s *TunnelModeServer) dealClient(c *utils.Conn, cnf *ServerConfig, addr string, method string, rb []byte) error {
|
||||
reGet:
|
||||
link, err := s.bridge.GetTunnel(getverifyval(cnf.VerifyKey), cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
|
||||
var link *utils.Conn
|
||||
var err error
|
||||
defer func() {
|
||||
if cnf.Mux {
|
||||
if cnf.Mux && link != nil {
|
||||
s.bridge.ReturnTunnel(link, getverifyval(cnf.VerifyKey))
|
||||
} else {
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
log.Println("conn to client error:", err)
|
||||
c.Close()
|
||||
if link, err = s.GetTunnelAndWriteHost(c, cnf, addr); err != nil {
|
||||
log.Println("get bridge tunnel error: ", err)
|
||||
return err
|
||||
}
|
||||
if _, err := link.WriteHost(utils.CONN_TCP, addr); err != nil {
|
||||
c.Close()
|
||||
link.Close()
|
||||
log.Println(err)
|
||||
goto reGet
|
||||
}
|
||||
if flag, err := link.ReadFlag(); err == nil {
|
||||
if flag == utils.CONN_SUCCESS {
|
||||
if method == "CONNECT" {
|
||||
|
@ -90,8 +88,7 @@ reGet:
|
|||
} else if rb != nil {
|
||||
link.WriteTo(rb, cnf.CompressEncode, cnf.Crypt)
|
||||
}
|
||||
go utils.Relay(link.Conn, c.Conn, cnf.CompressEncode, cnf.Crypt, cnf.Mux)
|
||||
utils.Relay(c.Conn, link.Conn, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
|
||||
utils.ReplayWaitGroup(link.Conn, c.Conn, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -102,6 +99,19 @@ func (s *TunnelModeServer) Close() error {
|
|||
return s.listener.Close()
|
||||
}
|
||||
|
||||
func (s *TunnelModeServer) GetTunnelAndWriteHost(c *utils.Conn, cnf *ServerConfig, addr string) (*utils.Conn, error) {
|
||||
var err error
|
||||
link, err := s.bridge.GetTunnel(getverifyval(cnf.VerifyKey), cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = link.WriteHost(utils.CONN_TCP, addr); err != nil {
|
||||
link.Close()
|
||||
return nil, err
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
|
||||
//tcp隧道模式
|
||||
func ProcessTunnel(c *utils.Conn, s *TunnelModeServer) error {
|
||||
_, _, rb, err, r := c.GetHost()
|
||||
|
@ -124,29 +134,69 @@ func ProcessHttp(c *utils.Conn, s *TunnelModeServer) error {
|
|||
if err := s.auth(r, c, s.config.U, s.config.P); err != nil {
|
||||
return err
|
||||
}
|
||||
//TODO 效率问题
|
||||
return s.dealClient(c, s.config, addr, method, rb)
|
||||
}
|
||||
|
||||
//多客户端域名代理
|
||||
func ProcessHost(c *utils.Conn, s *TunnelModeServer) error {
|
||||
method, addr, rb, err, r := c.GetHost()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return err
|
||||
var (
|
||||
isConn = true
|
||||
link *utils.Conn
|
||||
cnf *ServerConfig
|
||||
host *HostList
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
for {
|
||||
r, err := http.ReadRequest(bufio.NewReader(c))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
//首次获取conn
|
||||
if isConn {
|
||||
isConn = false
|
||||
if host, cnf, err = GetKeyByHost(r.Host); err != nil {
|
||||
log.Printf("the host %s is not found !", r.Host)
|
||||
break
|
||||
}
|
||||
|
||||
if err = s.auth(r, c, cnf.U, cnf.P); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if link, err = s.GetTunnelAndWriteHost(c, 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() {
|
||||
utils.Relay(c.Conn, link.Conn, cnf.CompressDecode, cnf.Crypt, cnf.Mux)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
}
|
||||
utils.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String())
|
||||
b, err := httputil.DumpRequest(r, true)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if _, err := link.WriteTo(b, cnf.CompressEncode, cnf.Crypt); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
host, task, err := GetKeyByHost(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
wg.Wait()
|
||||
if cnf != nil && cnf.Mux && link != nil {
|
||||
link.WriteTo([]byte(utils.IO_EOF), cnf.CompressEncode, cnf.Crypt)
|
||||
s.bridge.ReturnTunnel(link, getverifyval(cnf.VerifyKey))
|
||||
} else if link != nil {
|
||||
link.Close()
|
||||
}
|
||||
if err := s.auth(r, c, task.U, task.P); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return err
|
||||
}
|
||||
return s.dealClient(c, task, host.Target, method, rb)
|
||||
c.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
//web管理方式
|
||||
|
|
|
@ -58,7 +58,7 @@ func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
|
|||
}
|
||||
if flag, err := conn.ReadFlag(); err == nil {
|
||||
defer func() {
|
||||
if s.config.Mux {
|
||||
if conn != nil && s.config.Mux {
|
||||
conn.WriteTo([]byte(utils.IO_EOF), s.config.CompressEncode, s.config.Crypt)
|
||||
s.bridge.ReturnTunnel(conn, getverifyval(s.config.VerifyKey))
|
||||
} else {
|
||||
|
|
|
@ -18,19 +18,17 @@ import (
|
|||
|
||||
const cryptKey = "1234567812345678"
|
||||
const poolSize = 64 * 1024
|
||||
const poolSizeSmall = 10
|
||||
|
||||
type CryptConn struct {
|
||||
conn net.Conn
|
||||
crypt bool
|
||||
rb []byte
|
||||
rn int
|
||||
}
|
||||
|
||||
func NewCryptConn(conn net.Conn, crypt bool) *CryptConn {
|
||||
c := new(CryptConn)
|
||||
c.conn = conn
|
||||
c.crypt = crypt
|
||||
c.rb = make([]byte, poolSize)
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -51,21 +49,6 @@ func (s *CryptConn) Write(b []byte) (n int, err error) {
|
|||
|
||||
//解密读
|
||||
func (s *CryptConn) Read(b []byte) (n int, err error) {
|
||||
read:
|
||||
if len(s.rb) > 0 {
|
||||
if len(b) >= s.rn {
|
||||
n = s.rn
|
||||
copy(b, s.rb[:s.rn])
|
||||
s.rn = 0
|
||||
s.rb = s.rb[:0]
|
||||
} else {
|
||||
n = len(b)
|
||||
copy(b, s.rb[:len(b)])
|
||||
s.rn = n - len(b)
|
||||
s.rb = s.rb[len(b):]
|
||||
}
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err == nil && n == len(IO_EOF) && string(b[:n]) == IO_EOF {
|
||||
err = io.EOF
|
||||
|
@ -74,6 +57,7 @@ read:
|
|||
}()
|
||||
var lens int
|
||||
var buf []byte
|
||||
var rb []byte
|
||||
c := NewConn(s.conn)
|
||||
if lens, err = c.GetLen(); err != nil {
|
||||
return
|
||||
|
@ -82,14 +66,15 @@ read:
|
|||
return
|
||||
}
|
||||
if s.crypt {
|
||||
if s.rb, err = AesDecrypt(buf, []byte(cryptKey)); err != nil {
|
||||
if rb, err = AesDecrypt(buf, []byte(cryptKey)); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
s.rb = buf
|
||||
rb = buf
|
||||
}
|
||||
s.rn = len(s.rb)
|
||||
goto read
|
||||
copy(b, rb)
|
||||
n = len(rb)
|
||||
return
|
||||
}
|
||||
|
||||
type SnappyConn struct {
|
||||
|
@ -164,7 +149,12 @@ func (s *Conn) ReadLen(cLen int) ([]byte, error) {
|
|||
if cLen > poolSize {
|
||||
return nil, errors.New("长度错误" + strconv.Itoa(cLen))
|
||||
}
|
||||
buf := bufPool.Get().([]byte)[:cLen]
|
||||
var buf []byte
|
||||
if cLen <= poolSizeSmall {
|
||||
buf = bufPoolSmall.Get().([]byte)[:cLen]
|
||||
} else {
|
||||
buf = bufPool.Get().([]byte)[:cLen]
|
||||
}
|
||||
if n, err := io.ReadFull(s, buf); err != nil || n != cLen {
|
||||
return buf, errors.New("读取指定长度错误" + err.Error())
|
||||
}
|
||||
|
|
|
@ -40,21 +40,21 @@ WWW-Authenticate: Basic realm="easyProxy"
|
|||
func Relay(in, out net.Conn, compressType int, crypt, mux bool) {
|
||||
switch compressType {
|
||||
case COMPRESS_SNAPY_ENCODE:
|
||||
io.Copy(NewSnappyConn(in, crypt), out)
|
||||
copyBuffer(NewSnappyConn(in, crypt), out)
|
||||
out.Close()
|
||||
NewSnappyConn(in, crypt).Write([]byte(IO_EOF))
|
||||
case COMPRESS_SNAPY_DECODE:
|
||||
io.Copy(in, NewSnappyConn(out, crypt))
|
||||
copyBuffer(in, NewSnappyConn(out, crypt))
|
||||
in.Close()
|
||||
if !mux {
|
||||
out.Close()
|
||||
}
|
||||
case COMPRESS_NONE_ENCODE:
|
||||
io.Copy(NewCryptConn(in, crypt), out)
|
||||
copyBuffer(NewCryptConn(in, crypt), out)
|
||||
out.Close()
|
||||
NewCryptConn(in, crypt).Write([]byte(IO_EOF))
|
||||
case COMPRESS_NONE_DECODE:
|
||||
io.Copy(in, NewCryptConn(out, crypt))
|
||||
copyBuffer(in, NewCryptConn(out, crypt))
|
||||
in.Close()
|
||||
if !mux {
|
||||
out.Close()
|
||||
|
@ -150,11 +150,16 @@ var bufPool = sync.Pool{
|
|||
return make([]byte, poolSize)
|
||||
},
|
||||
}
|
||||
var bufPoolSmall = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, poolSizeSmall)
|
||||
},
|
||||
}
|
||||
// io.copy的优化版,读取buffer长度原为32*1024,与snappy不同,导致读取出的内容存在差异,不利于解密,特此修改
|
||||
//废除
|
||||
func copyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
//TODO 回收问题
|
||||
buf := bufPool.Get().([]byte)
|
||||
buf := bufPool.Get().([]byte)[:32*1024]
|
||||
for {
|
||||
nr, er := src.Read(buf)
|
||||
if nr > 0 {
|
||||
|
@ -197,3 +202,33 @@ func FlushConn(c net.Conn) {
|
|||
func Getverifyval(vkey string) string {
|
||||
return Md5(vkey)
|
||||
}
|
||||
|
||||
//wait replay group
|
||||
func ReplayWaitGroup(conn1 net.Conn, conn2 net.Conn, compressEncode, compressDecode int, crypt, mux bool) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
Relay(conn1, conn2, compressEncode, crypt, mux)
|
||||
wg.Done()
|
||||
}()
|
||||
Relay(conn2, conn1, compressDecode, crypt, mux)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func ChangeHostAndHeader(r *http.Request, host string, header string, addr string) {
|
||||
if host != "" {
|
||||
r.Host = host
|
||||
}
|
||||
if header != "" {
|
||||
h := strings.Split(header, "\n")
|
||||
for _, v := range h {
|
||||
hd := strings.Split(v, ":")
|
||||
if len(hd) == 2 {
|
||||
r.Header.Set(hd[0], hd[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
addr = strings.Split(addr, ":")[0]
|
||||
r.Header.Set("X-Forwarded-For", addr)
|
||||
r.Header.Set("X-Real-IP", addr)
|
||||
}
|
||||
|
|
|
@ -160,11 +160,40 @@ func (s *IndexController) AddHost() {
|
|||
s.display("index/hadd")
|
||||
} else {
|
||||
h := &server.HostList{
|
||||
Vkey: s.GetString("vkey"),
|
||||
Host: s.GetString("host"),
|
||||
Target: s.GetString("target"),
|
||||
Vkey: s.GetString("vkey"),
|
||||
Host: s.GetString("host"),
|
||||
Target: s.GetString("target"),
|
||||
HeaderChange: s.GetString("header"),
|
||||
HostChange: s.GetString("hostchange"),
|
||||
}
|
||||
server.CsvDb.NewHost(h)
|
||||
s.AjaxOk("添加成功")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IndexController) EditHost() {
|
||||
if s.Ctx.Request.Method == "GET" {
|
||||
host := s.GetString("host")
|
||||
if h, t, err := server.GetKeyByHost(host); err != nil {
|
||||
s.error()
|
||||
} else {
|
||||
s.Data["t"] = t
|
||||
s.Data["h"] = h
|
||||
}
|
||||
s.SetInfo("修改")
|
||||
s.display("index/hedit")
|
||||
} else {
|
||||
host := s.GetString("host")
|
||||
if h, _, err := server.GetKeyByHost(host); err != nil {
|
||||
s.error()
|
||||
} else {
|
||||
h.Vkey = s.GetString("vkey")
|
||||
h.Host = s.GetString("host")
|
||||
h.Target = s.GetString("target")
|
||||
h.HeaderChange = s.GetString("header")
|
||||
h.HostChange = s.GetString("hostchange")
|
||||
server.CsvDb.UpdateHost(h)
|
||||
}
|
||||
s.AjaxOk("修改成功")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,15 @@
|
|||
<label class="control-label">内网目标</label>
|
||||
<input class="form-control" type="text" name="target" placeholder="内网隧道目标,例如10.1.50.203:22">
|
||||
</div>
|
||||
<div class="form-group" id="header">
|
||||
<label class="control-label">header头修改(冒号分隔,多个请换行填写)</label>
|
||||
<textarea class="form-control" rows="4" type="text" name="header" placeholder="Cache-Control: no-cache"></textarea>
|
||||
</div>
|
||||
<div class="form-group" id="hostchange">
|
||||
<label class="control-label">host修改</label>
|
||||
<input class="form-control" value="" type="text" name="hostchange"
|
||||
placeholder="host修改">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tile-footer">
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<div class="row tile">
|
||||
<div class="col-md-6 col-md-auto">
|
||||
<div>
|
||||
<h3 class="tile-title">修改</h3>
|
||||
<div class="tile-body">
|
||||
<form>
|
||||
<input type="hidden" name="vkey" value="{{.t.VerifyKey}}">
|
||||
<div class="form-group">
|
||||
<label class="control-label">域名</label>
|
||||
<input class="form-control" value="{{.h.Host}}" type="text" name="host" placeholder="域名">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">内网目标</label>
|
||||
<input class="form-control" value="{{.h.Target}}" type="text" name="target"
|
||||
placeholder="内网隧道目标,例如10.1.50.203:22">
|
||||
</div>
|
||||
<div class="form-group" id="header">
|
||||
<label class="control-label">header头修改(冒号分隔,多个请换行填写)</label>
|
||||
<textarea class="form-control" rows="4" type="text" name="header"
|
||||
placeholder="Cache-Control: no-cache">{{.h.HeaderChange}}</textarea>
|
||||
</div>
|
||||
<div class="form-group" id="hostchange">
|
||||
<label class="control-label">host修改</label>
|
||||
<input class="form-control" value="{{.h.HostChange}}" type="text" name="hostchange"
|
||||
placeholder="host修改">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tile-footer">
|
||||
<button class="btn btn-success" href="#" id="add"><i
|
||||
class="fa fa-fw fa-lg fa-eye"></i>保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
$(function () {
|
||||
$("#add").on("click", function () {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/index/edithost",
|
||||
data: $("form").serializeArray(),
|
||||
success: function (res) {
|
||||
alert(res.msg)
|
||||
if (res.status) {
|
||||
history.back(-1)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
|
@ -42,6 +42,10 @@
|
|||
window.location.href = "/index/addhost?vkey={{.vkey}}"
|
||||
}
|
||||
|
||||
function edit(host) {
|
||||
window.location.href = "/index/edithost?host=" + host
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var table = $('#sampleTable').DataTable({
|
||||
dom: 'Bfrtip',
|
||||
|
@ -67,7 +71,8 @@
|
|||
|
||||
return '<div class="btn-group" role="group" aria-label="..."> ' +
|
||||
'<button onclick="del(\'' + row.Host + '\')" type="button" class="btn btn-danger btn-sm">删除</button>' +
|
||||
' </div>'
|
||||
'<button onclick="edit(\'' + row.Host + '\')" type="button" class="btn btn-primary btn-sm">编辑查看</button> '
|
||||
+ ' </div>'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue