新功能+bug修复

This commit is contained in:
刘河 2019-01-13 00:09:12 +08:00
parent bb882f348a
commit 851241a0c7
17 changed files with 394 additions and 178 deletions

View File

@ -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。**
## 操作系统支持

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}

View File

@ -11,3 +11,6 @@ password=123
#http监听端口
hostPort=80
##客户端与服务端通信端口
tcpport=8284

View File

@ -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

1 b.proxy.com a.o.com 127.0.0.1:82 127.0.0.1:8082 o2430bnq22jgnmcl 7hiixust68kbz33a www.baidu.com
2 b.o.com b.o.com 127.0.0.1:88 ts08z6vk5nc72fs8 7hiixust68kbz33a ab
a.o.com 127.0.0.1:88 7n7bxc2bm1fyjfab

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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管理方式

View File

@ -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 {

View File

@ -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())
}

View File

@ -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)
}

View File

@ -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("修改成功")
}
}

View File

@ -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">

View File

@ -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">
&nbsp;&nbsp;<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>

View File

@ -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>'
}
}
],