Browse Source

守护进程 负载均衡

刘河 6 năm trước cách đây
mục cha
commit
da899fd3db

+ 49 - 17
README.md

@@ -29,6 +29,11 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
     * [release安装](#release安装)
 * [web管理](#web管理模式)(多隧道时推荐)
     * [启动](#启动)
+       * [服务端测试](#服务端测试)
+       * [服务端启动](#服务端启动)
+       * [web管理](#web管理)
+       * [客户端启动](#客户端启动)
+       * [服务端停止或重启](#服务端停止或重启)
     * [配置文件说明](#服务端配置文件)
     * [详细使用说明](#详细说明)
        * [http|https域名解析](#域名解析)
@@ -54,6 +59,8 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
    * [自定义404页面](#404页面配置)
    * [流量限制](#流量限制)
    * [带宽限制](#带宽限制)
+   * [负载均衡](#负载均衡)
+   * [守护进程](#守护进程)
 * [相关说明](#相关说明)
    * [流量统计](#流量统计)
    * [热更新支持](#热更新支持)
@@ -81,9 +88,9 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
 - 安装源码(另有snappy、beego包)
 > go get github.com/cnlh/easyProxy
 - 编译
-> go build cmd/proxy_server/proxy_server.go
+> go build cmd/server/proxy_server.go
 
-> go build cmd/proxy_client/proxy_client.go
+> go build cmd/client/proxy_client.go
 
 ## web管理模式
 
@@ -98,22 +105,32 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
 ### 启动
 
 
-- 服务端
-
+#### 服务端测试
 ```
- ./proxy_server
+ ./proxy_server test
 ```
-
-- 客户端
+如有错误请及时修改配置文件,无错误可继续进行下去
+#### 服务端启动
 ```
- ./proxy_server -server=ip:port -vkey=web界面中显示的密钥
+ ./proxy_server start
 ```
+如果无需daemon运行,去掉start即可
 
-- 配置
+#### web管理
 
 进入web界面,公网ip:web界面端口(默认8080),密码默认为123
 
-进入web管理界面,有详细的命令
+进入web管理界面,有详细的说明
+
+#### 客户端启动
+```
+ ./proxy_server -server=ip:port -vkey=web界面中显示的密钥
+```
+#### 服务端停止或重启
+如果是daemon启动
+```
+ ./proxy_server stop|restart
+```
 
 ### 服务端配置文件
 - /conf/app.conf
@@ -122,6 +139,7 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
 ---|---
 httpport | web管理端口
 password | web界面管理密码
+hostPort | 域名代理模式监听端口
 tcpport  | 服务端客户端通信端口
 pemPath | ssl certFile绝对路径
 keyPath | ssl keyFile绝对路径
@@ -138,12 +156,13 @@ 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
+- 例如配置文件中tcpport为8284
 
 **使用步骤**
 - 将*.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=客户端的密钥
@@ -158,7 +177,7 @@ httpProxyPort | http代理监听端口
 **适用范围:**  ssh、远程桌面等tcp连接场景
 
 **假设场景:**
- 想通过访问公网服务器1.1.1.1的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接
+ 想通过访问公网服务器1.1.1.1的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接,例如配置文件中tcpport为8284
 
 **使用步骤**
 - 在客户端管理中创建一个客户端,记录下验证密钥
@@ -176,7 +195,7 @@ httpProxyPort | http代理监听端口
 **适用范围:**  内网dns解析等udp连接场景
 
 **假设场景:**
-内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为1.1.1.1
+内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为1.1.1.1,例如配置文件中tcpport为8284
 
 **使用步骤**
 - 在客户端管理中创建一个客户端,记录下验证密钥
@@ -193,7 +212,7 @@ httpProxyPort | http代理监听端口
 **适用范围:**  在外网环境下如同使用vpn一样访问内网设备或者资源
 
 **假设场景:**
-想将公网服务器1.1.1.1的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果
+想将公网服务器1.1.1.1的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果,例如配置文件中tcpport为8284
 
 **使用步骤**
 - 在客户端管理中创建一个客户端,记录下验证密钥
@@ -209,7 +228,7 @@ httpProxyPort | http代理监听端口
 **适用范围:**  在外网环境下使用http代理访问内网站点
 
 **假设场景:**
-想将公网服务器1.1.1.1的8004端口作为http代理,访问内网网站
+想将公网服务器1.1.1.1的8004端口作为http代理,访问内网网站,例如配置文件中tcpport为8284
 
 **使用步骤**
 - 在客户端管理中创建一个客户端,记录下验证密钥
@@ -220,13 +239,14 @@ httpProxyPort | http代理监听端口
 - 在该客户端隧道管理中添加一条http代理,填写监听的端口(8004),选择压缩方式,保存。
 - 在外网环境的本机配置http代理,ip为公网服务器ip(127.0.0.1),端口为填写的监听端口(8004),即可访问了
 
+
 ### 使用https
 
 在配置文件中将httpsProxyPort设置为443或者其他你想配置的端口,和将对应的证书文件路径添加到配置文件中,即可畅销https了
 
 ### 与nginx配合
 
-普通场景下使用本代理已经能满足使用要求,但是有时候我们还需要在云服务器上运行nginx来保证静态文件缓存等,本代理可和nginx配合使用,在配置文件中将httpProxyPort设置为非80端口,并在nginx中配置代理,例
+有时候我们还需要在云服务器上运行https来保证静态文件缓存等,本代理可和nginx配合使用,在配置文件中将httpProxyPort设置为非80端口,并在nginx中配置代理,例
 ```
 server {
     listen 80;
@@ -491,6 +511,18 @@ authip | 免验证ip,适用于web api
 
 支持客户端级带宽限制,带宽计算方式为入口和出口总和,权重均衡
 
+### 负载均衡
+本代理支持域名解析模式的负载均衡,在web域名添加或者编辑中内网目标分行填写多个目标即可实现轮训级别的负载均衡
+
+### 守护进程
+本代理支持守护进程,使用示例如下,服务端客户端所有模式通用,支持linux,darwin,windows。
+```
+./proxy_(client|server) start|stop|restart xxxxxx
+```
+```
+proxy_(client|server).exe start|stop|restart xxxxxx
+```
+
 ## 相关说明
 
 ### 获取用户真实ip
@@ -531,4 +563,4 @@ authip | 免验证ip,适用于web api
 ## webAPI
 
 为方便第三方扩展,在web模式下可利用webAPI进行相关操作,详情见
-[webAPI文档](https://github.com/cnlh/easyProxy/wiki/webAPI%E6%96%87%E6%A1%A3)
+[webAPI文档](https://github.com/cnlh/easyProxy/wiki/webAPI%E6%96%87%E6%A1%A3)

+ 6 - 7
bridge/bridge.go

@@ -3,7 +3,6 @@ package bridge
 import (
 	"errors"
 	"github.com/cnlh/easyProxy/utils"
-	"log"
 	"net"
 	"sync"
 	"time"
@@ -52,7 +51,7 @@ func (s *Bridge) tunnelProcess() error {
 	for {
 		conn, err := s.listener.Accept()
 		if err != nil {
-			log.Println(err)
+			utils.Println(err)
 			continue
 		}
 		go s.cliProcess(utils.NewConn(conn))
@@ -77,7 +76,7 @@ func (s *Bridge) cliProcess(c *utils.Conn) {
 	//验证
 	id, err := utils.GetCsvDb().GetIdByVerifyKey(string(buf), c.Conn.RemoteAddr().String())
 	if err != nil {
-		log.Println("当前客户端连接校验错误,关闭此客户端:", c.Conn.RemoteAddr())
+		utils.Println("当前客户端连接校验错误,关闭此客户端:", c.Conn.RemoteAddr())
 		s.verifyError(c)
 		return
 	}
@@ -116,7 +115,7 @@ func (s *Bridge) typeDeal(typeVal string, c *utils.Conn, id int) {
 			stop:          make(chan bool),
 			linkStatusMap: make(map[int]bool),
 		}
-		log.Printf("客户端%d连接成功,地址为:%s", id, c.Conn.RemoteAddr())
+		utils.Printf("客户端%d连接成功,地址为:%s", id, c.Conn.RemoteAddr())
 		s.Client[id].signal = c
 		s.clientLock.Unlock()
 		go s.GetStatus(id)
@@ -168,7 +167,7 @@ func (s *Bridge) SendLinkInfo(clientId int, link *utils.Link) (tunnel *utils.Con
 		s.clientLock.Unlock()
 		v.signal.SendLinkInfo(link)
 		if err != nil {
-			log.Println("send error:", err, link.Id)
+			utils.Println("send error:", err, link.Id)
 			s.DelClient(clientId)
 			return
 		}
@@ -258,7 +257,7 @@ func (s *Bridge) clientCopy(clientId int) {
 	for {
 		if id, err := client.tunnel.GetLen(); err != nil {
 			s.closeClient(clientId)
-			log.Println("读取msg id 错误", err, id)
+			utils.Println("读取msg id 错误", err, id)
 			break
 		} else {
 			client.Lock()
@@ -267,7 +266,7 @@ func (s *Bridge) clientCopy(clientId int) {
 				if content, err := client.tunnel.GetMsgContent(link); err != nil {
 					utils.PutBufPoolCopy(content)
 					s.closeClient(clientId)
-					log.Println("read msg content error", err, "close client")
+					utils.Println("read msg content error", err, "close client")
 					break
 				} else {
 					if len(content) == len(utils.IO_EOF) && string(content) == utils.IO_EOF {

+ 11 - 12
client/client.go

@@ -2,7 +2,6 @@ package client
 
 import (
 	"github.com/cnlh/easyProxy/utils"
-	"log"
 	"net"
 	"sync"
 	"time"
@@ -40,7 +39,7 @@ func (s *TRPClient) NewConn() {
 retry:
 	conn, err := net.Dial("tcp", s.svrAddr)
 	if err != nil {
-		log.Println("连接服务端失败,五秒后将重连")
+		utils.Println("连接服务端失败,五秒后将重连")
 		time.Sleep(time.Second * 5)
 		goto retry
 		return
@@ -61,12 +60,12 @@ func (s *TRPClient) processor(c *utils.Conn) {
 	for {
 		flags, err := c.ReadFlag()
 		if err != nil {
-			log.Println("服务端断开,正在重新连接")
+			utils.Println("服务端断开,正在重新连接")
 			break
 		}
 		switch flags {
 		case utils.VERIFY_EER:
-			log.Fatalf("vKey:%s不正确,服务端拒绝连接,请检查", s.vKey)
+			utils.Fatalf("vKey:%s不正确,服务端拒绝连接,请检查", s.vKey)
 		case utils.NEW_CONN:
 			if link, err := c.GetLinkInfo(); err != nil {
 				break
@@ -77,12 +76,12 @@ func (s *TRPClient) processor(c *utils.Conn) {
 				go s.linkProcess(link, c)
 			}
 		case utils.RES_CLOSE:
-			log.Fatal("该vkey被另一客户连接")
+			utils.Fatalln("该vkey被另一客户连接")
 		case utils.RES_MSG:
-			log.Println("服务端返回错误,重新连接")
+			utils.Println("服务端返回错误,重新连接")
 			break
 		default:
-			log.Println("无法解析该错误,重新连接")
+			utils.Println("无法解析该错误,重新连接")
 			break
 		}
 	}
@@ -96,7 +95,7 @@ func (s *TRPClient) linkProcess(link *utils.Link, c *utils.Conn) {
 
 	if err != nil {
 		c.WriteFail(link.Id)
-		log.Println("connect to ", link.Host, "error:", err)
+		utils.Println("connect to ", link.Host, "error:", err)
 		return
 	}
 
@@ -135,12 +134,12 @@ func (s *TRPClient) dealChan() {
 	//创建一个tcp连接
 	conn, err := net.Dial("tcp", s.svrAddr)
 	if err != nil {
-		log.Println("connect to ", s.svrAddr, "error:", err)
+		utils.Println("connect to ", s.svrAddr, "error:", err)
 		return
 	}
 	//验证
 	if _, err := conn.Write([]byte(utils.Getverifyval(s.vKey))); err != nil {
-		log.Println("connect to ", s.svrAddr, "error:", err)
+		utils.Println("connect to ", s.svrAddr, "error:", err)
 		return
 	}
 	//默认长连接保持
@@ -152,14 +151,14 @@ func (s *TRPClient) dealChan() {
 	go func() {
 		for {
 			if id, err := s.tunnel.GetLen(); err != nil {
-				log.Println("get msg id error")
+				utils.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)
+						utils.Println("get msg content error:", err, id)
 						break
 					} else {
 						if len(content) == len(utils.IO_EOF) && string(content) == utils.IO_EOF {

+ 12 - 2
cmd/proxy_client/proxy_client.go

@@ -3,20 +3,30 @@ package main
 import (
 	"flag"
 	"github.com/cnlh/easyProxy/client"
-	"log"
+	"github.com/cnlh/easyProxy/utils"
+	_ "github.com/cnlh/easyProxy/utils"
 	"strings"
 )
 
+const VERSION = "v0.0.13"
+
 var (
 	serverAddr = flag.String("server", "", "服务器地址ip:端口")
 	verifyKey  = flag.String("vkey", "", "验证密钥")
+	logType    = flag.String("log", "stdout", "日志输出方式(stdout|file)")
 )
 
 func main() {
 	flag.Parse()
+	utils.InitDaemon("client")
+	if *logType == "stdout" {
+		utils.InitLogFile("client", true)
+	} else {
+		utils.InitLogFile("client", false)
+	}
 	stop := make(chan int)
 	for _, v := range strings.Split(*verifyKey, ",") {
-		log.Println("客户端启动,连接:", *serverAddr, " 验证令牌:", v)
+		utils.Println("客户端启动,连接:", *serverAddr, " 验证令牌:", v)
 		go client.NewRPClient(*serverAddr, v).Start()
 	}
 	<-stop

+ 18 - 5
cmd/proxy_server/proxy_server.go

@@ -6,23 +6,36 @@ import (
 	"github.com/cnlh/easyProxy/server"
 	"github.com/cnlh/easyProxy/utils"
 	_ "github.com/cnlh/easyProxy/web/routers"
-	"log"
+	"os"
 )
 
+const VERSION = "v0.0.13"
+
 var (
 	TcpPort      = flag.Int("tcpport", 0, "客户端与服务端通信端口")
 	httpPort     = flag.Int("httpport", 8024, "对外监听的端口")
 	rpMode       = flag.String("mode", "webServer", "启动模式")
-	tunnelTarget = flag.String("target", "10.1.50.203:80", "远程目标")
+	tunnelTarget = flag.String("target", "127.0.0.1:80", "远程目标")
 	VerifyKey    = flag.String("vkey", "", "验证密钥")
 	u            = flag.String("u", "", "验证用户名(socks5和web)")
 	p            = flag.String("p", "", "验证密码(socks5和web)")
 	compress     = flag.String("compress", "", "数据压缩方式(snappy)")
 	crypt        = flag.String("crypt", "false", "是否加密(true|false)")
+	logType      = flag.String("log", "stdout", "日志输出方式(stdout|file)")
 )
 
 func main() {
 	flag.Parse()
+	var test bool
+	if len(os.Args) > 1 && os.Args[1] == "test" {
+		test = true
+	}
+	utils.InitDaemon("server")
+	if *logType == "stdout" || test {
+		utils.InitLogFile("server", true)
+	} else {
+		utils.InitLogFile("server", false)
+	}
 	task := &utils.Tunnel{
 		TcpPort: *httpPort,
 		Mode:    *rpMode,
@@ -59,10 +72,10 @@ func main() {
 			*TcpPort = 8284
 		}
 	}
-	log.Println("服务端启动,监听tcp服务端端口:", *TcpPort)
+	utils.Println("服务端启动,监听tcp服务端端口:", *TcpPort)
 	task.Config.CompressDecode, task.Config.CompressEncode = utils.GetCompressType(task.Config.Compress)
-	if *rpMode!="webServer" {
+	if *rpMode != "webServer" {
 		server.CsvDb.Tasks[0] = task
 	}
-	server.StartNewServer(*TcpPort, task)
+	server.StartNewServer(*TcpPort, task, test)
 }

+ 2 - 1
conf/hosts.csv

@@ -1 +1,2 @@
-a.o.com,127.0.0.1:8082,1,,www.baidu.com,测试
+a.o.com,"127.0.0.1:8082
+127.0.0.1:8080",1,,www.baidu.com,测试

+ 30 - 7
server/http.go

@@ -6,7 +6,7 @@ import (
 	"github.com/astaxie/beego"
 	"github.com/cnlh/easyProxy/bridge"
 	"github.com/cnlh/easyProxy/utils"
-	"log"
+	"net"
 	"net/http"
 	"net/http/httputil"
 	"strconv"
@@ -49,25 +49,38 @@ func (s *httpServer) Start() error {
 	}
 
 	if s.httpPort > 0 {
+		if !s.TestTcpPort(s.httpPort) {
+			utils.Fatalln("http端口", s.httpPort, "被占用!")
+		}
 		http = s.NewServer(s.httpPort)
 		go func() {
-			log.Println("启动http监听,端口为", s.httpPort)
+			utils.Println("启动http监听,端口为", s.httpPort)
 			err := http.ListenAndServe()
 			if err != nil {
-				log.Fatalln(err)
+				utils.Fatalln(err)
 			}
 		}()
 	}
 	if s.httpsPort > 0 {
+		if !s.TestTcpPort(s.httpsPort) {
+			utils.Fatalln("https端口", s.httpsPort, "被占用!")
+		}
+		if !utils.FileExists(s.pemPath) {
+			utils.Fatalf("ssl certFile文件%s不存在", s.pemPath)
+		}
+		if !utils.FileExists(s.keyPath) {
+			utils.Fatalf("ssl keyFile文件%s不存在", s.keyPath)
+		}
 		https = s.NewServer(s.httpsPort)
 		go func() {
-			log.Println("启动https监听,端口为", s.httpsPort)
+			utils.Println("启动https监听,端口为", s.httpsPort)
 			err := https.ListenAndServeTLS(s.pemPath, s.keyPath)
 			if err != nil {
-				log.Fatalln(err)
+				utils.Fatalln(err)
 			}
 		}()
 	}
+	startFinish <- true
 	select {
 	case <-s.stop:
 		if http != nil {
@@ -77,6 +90,7 @@ func (s *httpServer) Start() error {
 			https.Close()
 		}
 	}
+
 	return nil
 }
 
@@ -110,7 +124,7 @@ func (s *httpServer) process(c *utils.Conn, r *http.Request) {
 		//首次获取conn
 		if isConn {
 			if host, err = GetInfoByHost(r.Host); err != nil {
-				log.Printf("the host %s is not found !", r.Host)
+				utils.Printf("the host %s is not found !", r.Host)
 				break
 			}
 			//流量限制
@@ -122,7 +136,7 @@ func (s *httpServer) process(c *utils.Conn, r *http.Request) {
 			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)
+			link = utils.NewLink(host.Client.GetId(), utils.CONN_TCP, host.GetRandomTarget(), 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
 			}
@@ -166,3 +180,12 @@ func (s *httpServer) NewServer(port int) *http.Server {
 		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
 	}
 }
+
+func (s *httpServer) TestTcpPort(port int) bool {
+	l, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), port, ""})
+	defer l.Close()
+	if err != nil {
+		return false
+	}
+	return true
+}

+ 31 - 17
server/server.go

@@ -5,44 +5,58 @@ import (
 	"github.com/cnlh/easyProxy/bridge"
 	"github.com/cnlh/easyProxy/utils"
 	"log"
+	"os"
 	"reflect"
 	"strings"
 )
 
 var (
-	Bridge  *bridge.Bridge
-	RunList map[int]interface{} //运行中的任务
-	CsvDb   = utils.GetCsvDb()
+	Bridge      *bridge.Bridge
+	RunList     map[int]interface{} //运行中的任务
+	CsvDb       = utils.GetCsvDb()
+	startFinish chan bool
 )
 
 func init() {
 	RunList = make(map[int]interface{})
+	startFinish = make(chan bool)
 }
 
 //从csv文件中恢复任务
 func InitFromCsv() {
 	for _, v := range CsvDb.Tasks {
 		if v.Status {
-			log.Println("启动模式:", v.Mode, "监听端口:", v.TcpPort)
+			utils.Println("启动模式:", v.Mode, "监听端口:", v.TcpPort)
 			AddTask(v)
 		}
 	}
 }
 
 //start a new server
-func StartNewServer(bridgePort int, cnf *utils.Tunnel) {
-	Bridge = bridge.NewTunnel(bridgePort, RunList)
-	if err := Bridge.StartTunnel(); err != nil {
-		log.Fatalln("服务端开启失败", err)
-	}
-	if svr := NewMode(Bridge, cnf); svr != nil {
-		RunList[cnf.Id] = svr
-		err := reflect.ValueOf(svr).MethodByName("Start").Call(nil)[0]
-		if err.Interface() != nil {
-			log.Println(err)
+func StartNewServer(bridgePort int, cnf *utils.Tunnel, test bool) {
+	go func() {
+		Bridge = bridge.NewTunnel(bridgePort, RunList)
+		if err := Bridge.StartTunnel(); err != nil {
+			utils.Fatalln("服务端开启失败", err)
+		}
+		if svr := NewMode(Bridge, cnf); svr != nil {
+			RunList[cnf.Id] = svr
+			err := reflect.ValueOf(svr).MethodByName("Start").Call(nil)[0]
+			if err.Interface() != nil {
+				utils.Fatalln(err)
+			}
+		} else {
+			utils.Fatalln("启动模式不正确")
+		}
+	}()
+	for {
+		select {
+		case <-startFinish:
+			if test {
+				log.Println("测试完成,未发现错误")
+				os.Exit(0)
+			}
 		}
-	} else {
-		log.Fatalln("启动模式不正确")
 	}
 }
 
@@ -98,7 +112,7 @@ func AddTask(t *utils.Tunnel) error {
 		go func() {
 			err := reflect.ValueOf(svr).MethodByName("Start").Call(nil)[0]
 			if err.Interface() != nil {
-				log.Println("客户端", t.Id, "启动失败,错误:", err)
+				utils.Fatalln("服务端", t.Id, "启动失败,错误:", err)
 				delete(RunList, t.Id)
 			}
 		}()

+ 8 - 9
server/socks5.go

@@ -6,7 +6,6 @@ import (
 	"github.com/cnlh/easyProxy/bridge"
 	"github.com/cnlh/easyProxy/utils"
 	"io"
-	"log"
 	"net"
 	"strconv"
 	"strings"
@@ -66,7 +65,7 @@ func (s *Sock5ModeServer) handleRequest(c net.Conn) {
 	_, err := io.ReadFull(c, header)
 
 	if err != nil {
-		log.Println("illegal request", err)
+		utils.Println("illegal request", err)
 		c.Close()
 		return
 	}
@@ -163,7 +162,7 @@ func (s *Sock5ModeServer) handleBind(c net.Conn) {
 
 //udp
 func (s *Sock5ModeServer) handleUDP(c net.Conn) {
-	log.Println("UDP Associate")
+	utils.Println("UDP Associate")
 	/*
 	   +----+------+------+----------+----------+----------+
 	   |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
@@ -176,7 +175,7 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) {
 	// relay udp datagram silently, without any notification to the requesting client
 	if buf[2] != 0 {
 		// does not support fragmentation, drop it
-		log.Println("does not support fragmentation, drop")
+		utils.Println("does not support fragmentation, drop")
 		dummy := make([]byte, maxUDPPacketSize)
 		c.Read(dummy)
 	}
@@ -188,13 +187,13 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) {
 func (s *Sock5ModeServer) handleConn(c net.Conn) {
 	buf := make([]byte, 2)
 	if _, err := io.ReadFull(c, buf); err != nil {
-		log.Println("negotiation err", err)
+		utils.Println("negotiation err", err)
 		c.Close()
 		return
 	}
 
 	if version := buf[0]; version != 5 {
-		log.Println("only support socks5, request from: ", c.RemoteAddr())
+		utils.Println("only support socks5, request from: ", c.RemoteAddr())
 		c.Close()
 		return
 	}
@@ -202,7 +201,7 @@ func (s *Sock5ModeServer) handleConn(c net.Conn) {
 
 	methods := make([]byte, nMethods)
 	if len, err := c.Read(methods); len != int(nMethods) || err != nil {
-		log.Println("wrong method")
+		utils.Println("wrong method")
 		c.Close()
 		return
 	}
@@ -211,7 +210,7 @@ func (s *Sock5ModeServer) handleConn(c net.Conn) {
 		c.Write(buf)
 		if err := s.Auth(c); err != nil {
 			c.Close()
-			log.Println("验证失败:", err)
+			utils.Println("验证失败:", err)
 			return
 		}
 	} else {
@@ -270,7 +269,7 @@ func (s *Sock5ModeServer) Start() error {
 			if strings.Contains(err.Error(), "use of closed network connection") {
 				break
 			}
-			log.Fatal("accept error: ", err)
+			utils.Fatalln("accept error: ", err)
 		}
 		if !s.ResetConfig() {
 			conn.Close()

+ 4 - 4
server/tcp.go

@@ -5,7 +5,6 @@ import (
 	"github.com/astaxie/beego"
 	"github.com/cnlh/easyProxy/bridge"
 	"github.com/cnlh/easyProxy/utils"
-	"log"
 	"net"
 	"strings"
 )
@@ -39,7 +38,7 @@ func (s *TunnelModeServer) Start() error {
 			if strings.Contains(err.Error(), "use of closed network connection") {
 				break
 			}
-			log.Println(err)
+			utils.Println(err)
 			continue
 		}
 		go s.process(utils.NewConn(conn), s)
@@ -71,12 +70,13 @@ type WebServer struct {
 }
 
 //开始
-func (s *WebServer) Start() {
+func (s *WebServer) Start() error {
 	beego.BConfig.WebConfig.Session.SessionOn = true
-	log.Println("web管理启动,访问端口为", beego.AppConfig.String("httpport"))
+	utils.Println("web管理启动,访问端口为", beego.AppConfig.String("httpport"))
 	beego.SetViewsPath(beego.AppPath + "/web/views")
 	beego.SetStaticPath("/static", beego.AppPath+"/web/static")
 	beego.Run()
+	return errors.New("web管理启动失败")
 }
 
 //new

+ 8 - 5
utils/conn.go

@@ -7,7 +7,6 @@ import (
 	"errors"
 	"github.com/golang/snappy"
 	"io"
-	"log"
 	"net"
 	"net/http"
 	"net/url"
@@ -99,7 +98,7 @@ func (s *SnappyConn) Write(b []byte) (n int, err error) {
 	n = len(b)
 	if s.crypt {
 		if b, err = AesEncrypt(b, []byte(cryptKey)); err != nil {
-			log.Println("encode crypt error:", err)
+			Println("encode crypt error:", err)
 			return
 		}
 	}
@@ -125,7 +124,7 @@ func (s *SnappyConn) Read(b []byte) (n int, err error) {
 	var bs []byte
 	if s.crypt {
 		if bs, err = AesDecrypt(buf[:n], []byte(cryptKey)); err != nil {
-			log.Println("decode crypt error:", err)
+			Println("decode crypt error:", err)
 			return
 		}
 	} else {
@@ -170,9 +169,13 @@ func (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http.
 		return
 	}
 	if hostPortURL.Opaque == "443" { //https访问
-		address = r.Host + ":443"
+		if strings.Index(r.Host, ":") == -1 { //host不带端口, 默认80
+			address = r.Host + ":443"
+		} else {
+			address = r.Host
+		}
 	} else { //http访问
-		if strings.Index(hostPortURL.Host, ":") == -1 { //host不带端口, 默认80
+		if strings.Index(r.Host, ":") == -1 { //host不带端口, 默认80
 			address = r.Host + ":80"
 		} else {
 			address = r.Host

+ 69 - 0
utils/daemon.go

@@ -0,0 +1,69 @@
+package utils
+
+import (
+	"github.com/astaxie/beego"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"runtime"
+	"strconv"
+	"strings"
+)
+
+func InitDaemon(f string) {
+	if len(os.Args) < 2 {
+		return
+	}
+	var args []string
+	args = append(args, os.Args[0])
+	if len(os.Args) >= 2 {
+		args = append(args, os.Args[2:]...)
+	}
+	args = append(args, "-log=file")
+	switch os.Args[1] {
+	case "start":
+		start(args, f)
+		os.Exit(0)
+	case "stop":
+		stop(f, args[0])
+		os.Exit(0)
+	case "restart":
+		stop(f, args[0])
+		start(args, f)
+		os.Exit(0)
+	}
+}
+
+func start(osArgs []string, f string) {
+	cmd := exec.Command(osArgs[0], osArgs[1:]...)
+	cmd.Start()
+	log.Println("执行启动成功")
+	if cmd.Process.Pid > 0 {
+		d1 := []byte(strconv.Itoa(cmd.Process.Pid))
+		ioutil.WriteFile(beego.AppPath+"/proxy_"+f+".pid", d1, 0600)
+	}
+}
+
+func stop(f string, p string) {
+	var c *exec.Cmd
+	var err error
+	switch runtime.GOOS {
+	case "windows":
+		p := strings.Split(p, `\`)
+		c = exec.Command("taskkill", "/F", "/IM", p[len(p)-1])
+	case "linux", "darwin":
+		b, err := ioutil.ReadFile(beego.AppPath + "/proxy_" + f + ".pid")
+		if err == nil {
+			c = exec.Command("/bin/bash", "-c", `kill -9 `+string(b))
+		} else {
+			log.Println("停止服务失败,pid文件不存在")
+		}
+	}
+	err = c.Run()
+	if err != nil {
+		log.Println("停止服务失败,", err)
+	} else {
+		log.Println("停止服务成功")
+	}
+}

+ 7 - 8
utils/file.go

@@ -4,7 +4,6 @@ import (
 	"encoding/csv"
 	"errors"
 	"github.com/astaxie/beego"
-	"log"
 	"os"
 	"strconv"
 	"strings"
@@ -40,7 +39,7 @@ func (s *Csv) StoreTasksToCsv() {
 	// 创建文件
 	csvFile, err := os.Create(beego.AppPath + "/conf/tasks.csv")
 	if err != nil {
-		log.Fatalf(err.Error())
+		Fatalf(err.Error())
 	}
 	defer csvFile.Close()
 	writer := csv.NewWriter(csvFile)
@@ -63,7 +62,7 @@ func (s *Csv) StoreTasksToCsv() {
 		}
 		err := writer.Write(record)
 		if err != nil {
-			log.Fatalf(err.Error())
+			Fatalf(err.Error())
 		}
 	}
 	writer.Flush()
@@ -91,7 +90,7 @@ func (s *Csv) LoadTaskFromCsv() {
 	path := beego.AppPath + "/conf/tasks.csv"
 	records, err := s.openFile(path)
 	if err != nil {
-		log.Fatal("配置文件打开错误:", path)
+		Fatalln("配置文件打开错误:", path)
 	}
 	var tasks []*Tunnel
 	// 将每一行数据保存到内存slice中
@@ -218,7 +217,7 @@ func (s *Csv) LoadClientFromCsv() {
 	path := beego.AppPath + "/conf/clients.csv"
 	records, err := s.openFile(path)
 	if err != nil {
-		log.Fatal("配置文件打开错误:", path)
+		Fatalln("配置文件打开错误:", path)
 	}
 	var clients []*Client
 	// 将每一行数据保存到内存slice中
@@ -254,7 +253,7 @@ func (s *Csv) LoadHostFromCsv() {
 	path := beego.AppPath + "/conf/hosts.csv"
 	records, err := s.openFile(path)
 	if err != nil {
-		log.Fatal("配置文件打开错误:", path)
+		Fatalln("配置文件打开错误:", path)
 	}
 	var hosts []*Host
 	// 将每一行数据保存到内存slice中
@@ -392,7 +391,7 @@ func (s *Csv) StoreClientsToCsv() {
 	// 创建文件
 	csvFile, err := os.Create(beego.AppPath + "/conf/clients.csv")
 	if err != nil {
-		log.Fatalf(err.Error())
+		Fatalln(err.Error())
 	}
 	defer csvFile.Close()
 	writer := csv.NewWriter(csvFile)
@@ -411,7 +410,7 @@ func (s *Csv) StoreClientsToCsv() {
 		}
 		err := writer.Write(record)
 		if err != nil {
-			log.Fatalf(err.Error())
+			Fatalln(err.Error())
 		}
 	}
 	writer.Flush()

+ 18 - 0
utils/link.go

@@ -2,6 +2,7 @@ package utils
 
 import (
 	"net"
+	"strings"
 	"sync"
 )
 
@@ -101,6 +102,23 @@ type Host struct {
 	Flow         *Flow
 	Client       *Client
 	Remark       string //备注
+	NowIndex     int
+	TargetArr    []string
+	sync.RWMutex
+}
+
+func (s *Host) GetRandomTarget() string {
+	if s.TargetArr == nil {
+		s.TargetArr = strings.Split(s.Target, "\n")
+	}
+	s.Lock()
+	defer s.Unlock()
+	if s.NowIndex >= len(s.TargetArr)-1 {
+		s.NowIndex = 0
+	} else {
+		s.NowIndex++
+	}
+	return s.TargetArr[s.NowIndex]
 }
 
 //深拷贝Config

+ 45 - 0
utils/log.go

@@ -0,0 +1,45 @@
+package utils
+
+import (
+	"github.com/astaxie/beego"
+	"log"
+	"os"
+	"runtime"
+)
+
+var Log *log.Logger
+
+func InitLogFile(f string, isStdout bool) {
+	var prefix string
+	if !isStdout {
+		logFile, err := os.OpenFile(beego.AppPath+"/proxy_"+f+"_log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
+		if err != nil {
+			log.Fatalln("open file error !")
+		}
+		if runtime.GOOS == "windows" {
+			prefix = "\r\n"
+		}
+		Log = log.New(logFile, prefix, log.Ldate|log.Ltime)
+	} else {
+		Log = log.New(os.Stdout, "", log.Ldate|log.Ltime)
+	}
+}
+
+func Println(v ...interface{}) {
+	Log.Println(v ...)
+}
+
+func Fatalln(v ...interface{}) {
+	Log.SetPrefix("error ")
+	Log.Fatalln(v ...)
+	Log.SetPrefix("")
+}
+func Fatalf(format string, v ...interface{}) {
+	Log.SetPrefix("error ")
+	Log.Fatalf(format, v...)
+	Log.SetPrefix("")
+}
+
+func Printf(format string, v ...interface{}) {
+	Log.Printf(format, v...)
+}

+ 0 - 23
utils/rate_test.go

@@ -1,23 +0,0 @@
-package utils
-
-import (
-	"log"
-	"testing"
-)
-
-var rate = NewRate(100 * 1024)
-
-func TestRate_Get(t *testing.T) {
-	rate.Start()
-	for i := 0; i < 5; i++ {
-		go test(i)
-	}
-	test(5)
-}
-
-func test(i int) {
-	for {
-		rate.Get(64 * 1024)
-		log.Println("get ok", i)
-	}
-}

+ 12 - 4
utils/util.go

@@ -3,7 +3,6 @@ package utils
 import (
 	"encoding/base64"
 	"io/ioutil"
-	"log"
 	"net"
 	"net/http"
 	"os"
@@ -38,7 +37,6 @@ WWW-Authenticate: Basic realm="easyProxy"
 `
 )
 
-
 //判断压缩方式
 func GetCompressType(compress string) (int, int) {
 	switch compress {
@@ -47,7 +45,7 @@ func GetCompressType(compress string) (int, int) {
 	case "snappy":
 		return COMPRESS_SNAPY_DECODE, COMPRESS_SNAPY_ENCODE
 	default:
-		log.Fatalln("数据压缩格式错误")
+		Fatalln("数据压缩格式错误")
 	}
 	return COMPRESS_NONE_DECODE, COMPRESS_NONE_ENCODE
 }
@@ -127,7 +125,6 @@ func Getverifyval(vkey string) string {
 	return Md5(vkey)
 }
 
-
 func ChangeHostAndHeader(r *http.Request, host string, header string, addr string) {
 	if host != "" {
 		r.Host = host
@@ -153,3 +150,14 @@ func ReadAllFromFile(filePath string) ([]byte, error) {
 	}
 	return ioutil.ReadAll(f)
 }
+
+
+// FileExists reports whether the named file or directory exists.
+func FileExists(name string) bool {
+	if _, err := os.Stat(name); err != nil {
+		if os.IsNotExist(err) {
+			return false
+		}
+	}
+	return true
+}

+ 1 - 0
web/controllers/index.go

@@ -250,6 +250,7 @@ func (s *IndexController) EditHost() {
 			h.HeaderChange = s.GetString("header")
 			h.HostChange = s.GetString("hostchange")
 			h.Remark = s.GetString("remark")
+			h.TargetArr = nil
 			server.CsvDb.UpdateHost(h)
 			var err error
 			if h.Client, err = server.CsvDb.GetClient(s.GetIntNoErr("client_id")); err != nil {

+ 4 - 2
web/views/index/hadd.html

@@ -14,11 +14,13 @@
                     </div>
                     <div class="form-group">
                         <label class="control-label">客户端id</label>
-                        <input value="{{.client_id}}" class="form-control" type="text" name="client_id" placeholder="客户端id">
+                        <input value="{{.client_id}}" class="form-control" type="text" name="client_id"
+                               placeholder="客户端id">
                     </div>
                     <div class="form-group">
                         <label class="control-label">内网目标</label>
-                        <input class="form-control" type="text" name="target" placeholder="内网隧道目标,例如10.1.50.203:22">
+                        <textarea class="form-control" rows="4" type="text" name="target"
+                                  placeholder="内网隧道目标,例如10.1.50.203:22,换行分隔"></textarea>
                     </div>
                     <div class="form-group" id="header">
                         <label class="control-label">header头修改(冒号分隔,多个请换行填写)</label>

+ 2 - 2
web/views/index/hedit.html

@@ -20,8 +20,8 @@
                     </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">
+                        <textarea class="form-control" rows="4" type="text" name="target"
+                                  placeholder="内网隧道目标,例如10.1.50.203:22,换行分隔">{{.h.Target}}</textarea>
                     </div>
                     <div class="form-group" id="header">
                         <label class="control-label">header头修改(冒号分隔,多个请换行填写)</label>