Bladeren bron

新功能+bug修复

刘河 6 jaren geleden
bovenliggende
commit
851241a0c7

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

+ 17 - 15
bridge/bridge.go

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

+ 1 - 2
client/client.go

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

+ 0 - 3
cmd/proxy_client/proxy_client.go

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

+ 12 - 3
cmd/proxy_server/proxy_server.go

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

+ 3 - 0
conf/app.conf

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

+ 2 - 3
conf/hosts.csv

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

+ 72 - 54
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,6 +127,64 @@ func (s *Csv) LoadTaskFromCsv() {
 	s.Tasks = tasks
 }
 
+func (s *Csv) NewTask(t *ServerConfig) {
+	s.Tasks = append(s.Tasks, t)
+	s.StoreTasksToCsv()
+}
+
+func (s *Csv) UpdateTask(t *ServerConfig) error {
+	for k, v := range s.Tasks {
+		if v.VerifyKey == t.VerifyKey {
+			s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
+			s.Tasks = append(s.Tasks, t)
+			s.StoreTasksToCsv()
+			return nil
+		}
+	}
+	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
+}
+
+func (s *Csv) DelRunList(vKey string) {
+	delete(s.RunList, vKey)
+}
+
+func (s *Csv) DelTask(vKey string) error {
+	for k, v := range s.Tasks {
+		if v.VerifyKey == vKey {
+			s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
+			s.StoreTasksToCsv()
+			return nil
+		}
+	}
+	return errors.New("不存在")
+}
+
+func (s *Csv) GetTask(vKey string) (v *ServerConfig, err error) {
+	for _, v = range s.Tasks {
+		if v.VerifyKey == vKey {
+			return
+		}
+	}
+	err = errors.New("未找到")
+	return
+}
+
 func (s *Csv) StoreHostToCsv() {
 	// 创建文件
 	csvFile, err := os.Create(beego.AppPath + "/conf/hosts.csv")
@@ -141,6 +201,8 @@ func (s *Csv) StoreHostToCsv() {
 			host.Host,
 			host.Target,
 			host.Vkey,
+			host.HeaderChange,
+			host.HostChange,
 		}
 		err1 := writer.Write(record)
 		if err1 != nil {
@@ -174,61 +236,17 @@ func (s *Csv) LoadHostFromCsv() {
 	// 将每一行数据保存到内存slice中
 	for _, item := range records {
 		post := &HostList{
-			Vkey:   item[2],
-			Host:   item[0],
-			Target: item[1],
+			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) NewTask(t *ServerConfig) {
-	s.Tasks = append(s.Tasks, t)
-	s.StoreTasksToCsv()
-}
-
-func (s *Csv) UpdateTask(t *ServerConfig) error {
-	for k, v := range s.Tasks {
-		if v.VerifyKey == t.VerifyKey {
-			s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
-			s.Tasks = append(s.Tasks, t)
-			s.StoreTasksToCsv()
-			return nil
-		}
-	}
-	return errors.New("不存在")
-}
-
-func (s *Csv) AddRunList(vKey string, svr interface{}) {
-	s.RunList[vKey] = svr
-}
-
-func (s *Csv) DelRunList(vKey string) {
-	delete(s.RunList, vKey)
-}
-
-func (s *Csv) DelTask(vKey string) error {
-	for k, v := range s.Tasks {
-		if v.VerifyKey == vKey {
-			s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
-			s.StoreTasksToCsv()
-			return nil
-		}
-	}
-	return errors.New("不存在")
-}
-
-func (s *Csv) GetTask(vKey string) (v *ServerConfig, err error) {
-	for _, v = range s.Tasks {
-		if v.VerifyKey == vKey {
-			return
-		}
-	}
-	err = errors.New("未找到")
-	return
-}
-
 func (s *Csv) DelHost(host string) error {
 	for k, v := range s.Hosts {
 		if v.Host == host {

+ 4 - 6
server/socks5.go

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

+ 82 - 32
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
-	}
-	host, task, err := GetKeyByHost(addr)
-	if err != nil {
-		return err
-	}
-	if err := s.auth(r, c, task.U, task.P); err != nil {
-		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
+		}
 	}
-	if err != nil {
-		c.Close()
-		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()
 	}
-	return s.dealClient(c, task, host.Target, method, rb)
+	c.Close()
+	return nil
 }
 
 //web管理方式

+ 1 - 1
server/udp.go

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

+ 13 - 23
utils/conn.go

@@ -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 - 5
utils/util.go

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

+ 32 - 3
web/controllers/index.go

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

+ 9 - 0
web/views/index/hadd.html

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

+ 54 - 0
web/views/index/hedit.html

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

+ 6 - 1
web/views/index/hlist.html

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