Browse Source

start refactoring

fatedier 8 years ago
parent
commit
88083d21e8
100 changed files with 4488 additions and 3488 deletions
  1. 3 3
      Dockerfile_alpine
  2. 15 7
      Godeps/Godeps.json
  3. 12 7
      Makefile
  4. 20 20
      Makefile.cross-compiles
  5. 1 1
      assets/assets.go
  6. 0 0
      assets/static/css/bootstrap.min.css
  7. 0 0
      assets/static/css/font-awesome.min.css
  8. 0 0
      assets/static/css/iconfont.css
  9. 0 0
      assets/static/favicon.ico
  10. 0 0
      assets/static/font/iconfont.eot
  11. 0 0
      assets/static/font/iconfont.svg
  12. 0 0
      assets/static/font/iconfont.ttf
  13. 0 0
      assets/static/font/iconfont.woff
  14. 0 0
      assets/static/index.html
  15. 0 0
      assets/static/js/angular.min.js
  16. 0 0
      assets/static/js/bootstrap.min.js
  17. 0 0
      assets/static/js/echarts.min.js
  18. 0 0
      assets/static/js/jquery.min.js
  19. 7 0
      assets/statik/statik.go
  20. 345 0
      client/control.go
  21. 0 0
      client/process_udp.go.bak
  22. 141 0
      client/proxy.go
  23. 43 0
      client/service.go
  24. 35 24
      cmd/frpc/main.go
  25. 117 0
      cmd/frps/main.go
  26. 18 30
      conf/frpc.ini
  27. 2 29
      conf/frps.ini
  28. 160 0
      models/config/client_common.go
  29. 446 0
      models/config/proxy.go
  30. 279 0
      models/config/server_common.go
  31. 9 22
      models/consts/consts.go
  32. 21 0
      models/errors/errors.go
  33. 2 8
      models/metric/server.go
  34. 122 0
      models/msg/msg.go
  35. 69 0
      models/msg/pack.go
  36. 86 0
      models/msg/pack_test.go
  37. 88 0
      models/msg/process.go
  38. 97 0
      models/msg/process_test.go
  39. 38 0
      models/proto/tcp/process.go
  40. 129 0
      models/proto/tcp/process_test.go
  41. 89 0
      models/proto/tcp/tcp.go
  42. 100 0
      models/proto/tcp/tcp_test.go
  43. 1 1
      models/proto/udp/udp.go
  44. 1 1
      models/proto/udp/udp_test.go
  45. 332 0
      server/control.go
  46. 6 5
      server/dashboard.go
  47. 2 0
      server/dashboard_api.go
  48. 2 0
      server/dashboard_view.go
  49. 75 0
      server/manager.go
  50. 219 0
      server/proxy.go
  51. 196 0
      server/service.go
  52. 0 7
      src/assets/statik/statik.go
  53. 0 231
      src/cmd/frpc/control.go
  54. 0 365
      src/cmd/frps/control.go
  55. 0 199
      src/cmd/frps/main.go
  56. 0 186
      src/models/client/client.go
  57. 0 302
      src/models/client/config.go
  58. 0 49
      src/models/msg/msg.go
  59. 0 257
      src/models/msg/process.go
  60. 0 456
      src/models/server/config.go
  61. 0 484
      src/models/server/server.go
  62. 0 87
      src/utils/broadcast/broadcast.go
  63. 0 77
      src/utils/broadcast/broadcast_test.go
  64. 0 314
      src/utils/conn/conn.go
  65. 0 139
      src/utils/pcrypto/pcrypto.go
  66. 0 77
      src/utils/pcrypto/pcrypto_test.go
  67. 7 5
      test/echo_server.go
  68. 9 6
      test/func_test.go
  69. 52 0
      utils/crypto/crypto_test.go
  70. 75 0
      utils/crypto/decode.go
  71. 93 0
      utils/crypto/encode.go
  72. 14 8
      utils/errors/errors.go
  73. 55 2
      utils/log/log.go
  74. 16 14
      utils/net/conn.go
  75. 162 0
      utils/net/tcp.go
  76. 243 0
      utils/net/udp.go
  77. 27 0
      utils/net/udp_test.go
  78. 0 0
      utils/pool/pool.go
  79. 51 0
      utils/pool/pool_test.go
  80. 62 0
      utils/shutdown/shutdown.go
  81. 21 0
      utils/shutdown/shutdown_test.go
  82. 47 0
      utils/util/util.go
  83. 22 0
      utils/util/util_test.go
  84. 36 6
      utils/version/version.go
  85. 24 15
      utils/version/version_test.go
  86. 9 10
      utils/vhost/http.go
  87. 5 6
      utils/vhost/https.go
  88. 0 0
      utils/vhost/router.go
  89. 15 16
      utils/vhost/vhost.go
  90. 0 0
      vendor/github.com/fatedier/beego/LICENSE
  91. 0 0
      vendor/github.com/fatedier/beego/logs/README.md
  92. 0 0
      vendor/github.com/fatedier/beego/logs/color.go
  93. 0 0
      vendor/github.com/fatedier/beego/logs/color_windows.go
  94. 0 0
      vendor/github.com/fatedier/beego/logs/conn.go
  95. 0 0
      vendor/github.com/fatedier/beego/logs/console.go
  96. 1 0
      vendor/github.com/fatedier/beego/logs/file.go
  97. 78 0
      vendor/github.com/fatedier/beego/logs/jianliao.go
  98. 36 12
      vendor/github.com/fatedier/beego/logs/log.go
  99. 0 0
      vendor/github.com/fatedier/beego/logs/logger.go
  100. 0 0
      vendor/github.com/fatedier/beego/logs/multifile.go

+ 3 - 3
Dockerfile_alpine

@@ -1,7 +1,7 @@
-FROM alpine:3.4
+FROM alpine:3.5
 
-COPY bin/frpc /frpc
-COPY bin/frps /frps
+COPY tmp/frpc /frpc
+COPY tmp/frps /frps
 COPY conf/frpc_min.ini /frpc.ini
 COPY conf/frps_min.ini /frps.ini
 

+ 15 - 7
Godeps/Godeps.json

@@ -1,16 +1,11 @@
 {
 	"ImportPath": "github.com/fatedier/frp",
-	"GoVersion": "go1.7",
-	"GodepVersion": "v75",
+	"GoVersion": "go1.8",
+	"GodepVersion": "v79",
 	"Packages": [
 		"./..."
 	],
 	"Deps": [
-		{
-			"ImportPath": "github.com/astaxie/beego/logs",
-			"Comment": "v1.7.0-7-gefbde1e",
-			"Rev": "efbde1ee77517486eac03e814e01d724ddad18e6"
-		},
 		{
 			"ImportPath": "github.com/davecgh/go-spew/spew",
 			"Comment": "v1.1.0",
@@ -21,6 +16,15 @@
 			"Comment": "0.6.2",
 			"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
 		},
+		{
+			"ImportPath": "github.com/fatedier/beego/logs",
+			"Comment": "v1.7.2-70-gcd7a213",
+			"Rev": "cd7a213b889b290cf450e1dd51398af12e5a10d4"
+		},
+		{
+			"ImportPath": "github.com/golang/snappy",
+			"Rev": "5979233c5d6225d4a8e438cdd0b411888449ddab"
+		},
 		{
 			"ImportPath": "github.com/pmezard/go-difflib/difflib",
 			"Comment": "v1.0.0",
@@ -39,6 +43,10 @@
 		{
 			"ImportPath": "github.com/vaughan0/go-ini",
 			"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
+		},
+		{
+			"ImportPath": "golang.org/x/crypto/pbkdf2",
+			"Rev": "1f22c0103821b9390939b6776727195525381532"
 		}
 	]
 }

+ 12 - 7
Makefile

@@ -11,21 +11,26 @@ build_test: echo_server http_server
 assets:
 	go get -d github.com/rakyll/statik
 	@go install github.com/rakyll/statik
-	@rm -rf ./src/assets/statik
-	go generate ./src/...
+	@rm -rf ./assets/statik
+	go generate ./assets/...
 
 fmt:
-	go fmt ./src/...
+	@go fmt ./assets/...
+	@go fmt ./cmd/...
+	@go fmt ./client/...
+	@go fmt ./server/...
+	@go fmt ./models/...
+	@go fmt ./utils/...
 	@go fmt ./test/echo_server.go
 	@go fmt ./test/http_server.go
 	@go fmt ./test/func_test.go
 
 frps:
-	go build -o bin/frps ./src/cmd/frps
-	@cp -rf ./src/assets/static ./bin
+	go build -o bin/frps ./cmd/frps
+	@cp -rf ./assets/static ./bin
 
 frpc:
-	go build -o bin/frpc ./src/cmd/frpc
+	go build -o bin/frpc ./cmd/frpc
 
 echo_server:
 	go build -o test/bin/echo_server ./test/echo_server.go
@@ -36,7 +41,7 @@ http_server:
 test: gotest
 
 gotest:
-	go test -v ./src/...
+	go test -v ./...
 
 alltest:
 	cd ./test && ./run_test.sh && cd -

+ 20 - 20
Makefile.cross-compiles

@@ -6,23 +6,23 @@ all: build
 build: app
 
 app:
-    env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ./frpc_darwin_amd64 ./src/cmd/frpc
-    env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ./frps_darwin_amd64 ./src/cmd/frps
-    env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o ./frpc_linux_386 ./src/cmd/frpc
-    env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o ./frps_linux_386 ./src/cmd/frps
-    env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./frpc_linux_amd64 ./src/cmd/frpc
-    env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./frps_linux_amd64 ./src/cmd/frps
-    env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o ./frpc_linux_arm ./src/cmd/frpc
-    env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o ./frps_linux_arm ./src/cmd/frps
-    env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o ./frpc_windows_386.exe ./src/cmd/frpc
-    env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o ./frps_windows_386.exe ./src/cmd/frps
-    env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./frpc_windows_amd64.exe ./src/cmd/frpc
-    env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./frps_windows_amd64.exe ./src/cmd/frps
-    env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -o ./frpc_linux_mips64 ./src/cmd/frpc
-    env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -o ./frps_linux_mips64 ./src/cmd/frps
-    env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -o ./frpc_linux_mips64le ./src/cmd/frpc
-    env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -o ./frps_linux_mips64le ./src/cmd/frps
-    env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o ./frpc_linux_mips ./src/cmd/frpc
-    env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o ./frps_linux_mips ./src/cmd/frps
-    env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o ./frpc_linux_mipsle ./src/cmd/frpc
-    env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o ./frps_linux_mipsle ./src/cmd/frps
+    env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ./frpc_darwin_amd64 ./cmd/frpc
+    env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ./frps_darwin_amd64 ./cmd/frps
+    env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o ./frpc_linux_386 ./cmd/frpc
+    env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o ./frps_linux_386 ./cmd/frps
+    env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./frpc_linux_amd64 ./cmd/frpc
+    env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./frps_linux_amd64 ./cmd/frps
+    env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o ./frpc_linux_arm ./cmd/frpc
+    env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o ./frps_linux_arm ./cmd/frps
+    env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o ./frpc_windows_386.exe ./cmd/frpc
+    env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -o ./frps_windows_386.exe ./cmd/frps
+    env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./frpc_windows_amd64.exe ./cmd/frpc
+    env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o ./frps_windows_amd64.exe ./cmd/frps
+    env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -o ./frpc_linux_mips64 ./cmd/frpc
+    env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -o ./frps_linux_mips64 ./cmd/frps
+    env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -o ./frpc_linux_mips64le ./cmd/frpc
+    env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -o ./frps_linux_mips64le ./cmd/frps
+    env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o ./frpc_linux_mips ./cmd/frpc
+    env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -o ./frps_linux_mips ./cmd/frps
+    env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o ./frpc_linux_mipsle ./cmd/frpc
+    env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -o ./frps_linux_mipsle ./cmd/frps

+ 1 - 1
src/assets/assets.go → assets/assets.go

@@ -25,7 +25,7 @@ import (
 
 	"github.com/rakyll/statik/fs"
 
-	_ "github.com/fatedier/frp/src/assets/statik"
+	_ "github.com/fatedier/frp/assets/statik"
 )
 
 var (

+ 0 - 0
src/assets/static/css/bootstrap.min.css → assets/static/css/bootstrap.min.css


+ 0 - 0
src/assets/static/css/font-awesome.min.css → assets/static/css/font-awesome.min.css


+ 0 - 0
src/assets/static/css/iconfont.css → assets/static/css/iconfont.css


+ 0 - 0
src/assets/static/favicon.ico → assets/static/favicon.ico


+ 0 - 0
src/assets/static/font/iconfont.eot → assets/static/font/iconfont.eot


+ 0 - 0
src/assets/static/font/iconfont.svg → assets/static/font/iconfont.svg


+ 0 - 0
src/assets/static/font/iconfont.ttf → assets/static/font/iconfont.ttf


+ 0 - 0
src/assets/static/font/iconfont.woff → assets/static/font/iconfont.woff


+ 0 - 0
src/assets/static/index.html → assets/static/index.html


+ 0 - 0
src/assets/static/js/angular.min.js → assets/static/js/angular.min.js


+ 0 - 0
src/assets/static/js/bootstrap.min.js → assets/static/js/bootstrap.min.js


+ 0 - 0
src/assets/static/js/echarts.min.js → assets/static/js/echarts.min.js


+ 0 - 0
src/assets/static/js/jquery.min.js → assets/static/js/jquery.min.js


File diff suppressed because it is too large
+ 7 - 0
assets/statik/statik.go


+ 345 - 0
client/control.go

@@ -0,0 +1,345 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package client
+
+import (
+	"fmt"
+	"io"
+	"runtime"
+	"sync"
+	"time"
+
+	"github.com/fatedier/frp/models/config"
+	"github.com/fatedier/frp/models/msg"
+	"github.com/fatedier/frp/utils/log"
+	"github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/util"
+	"github.com/fatedier/frp/utils/version"
+)
+
+type Control struct {
+	// frpc service
+	svr *Service
+
+	// login message to server
+	loginMsg *msg.Login
+
+	// proxy configures
+	pxyCfgs map[string]config.ProxyConf
+
+	// proxies
+	proxies map[string]Proxy
+
+	// control connection
+	conn net.Conn
+
+	// put a message in this channel to send it over control connection to server
+	sendCh chan (msg.Message)
+
+	// read from this channel to get the next message sent by server
+	readCh chan (msg.Message)
+
+	// run id got from server
+	runId string
+
+	// connection or other error happens , control will try to reconnect to server
+	closed int32
+
+	// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
+	closedCh chan int
+
+	// last time got the Pong message
+	lastPong time.Time
+
+	mu sync.RWMutex
+
+	log.Logger
+}
+
+func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf) *Control {
+	loginMsg := &msg.Login{
+		Arch:      runtime.GOARCH,
+		Os:        runtime.GOOS,
+		PoolCount: config.ClientCommonCfg.PoolCount,
+		User:      config.ClientCommonCfg.User,
+		Version:   version.Full(),
+	}
+	return &Control{
+		svr:      svr,
+		loginMsg: loginMsg,
+		pxyCfgs:  pxyCfgs,
+		proxies:  make(map[string]Proxy),
+		sendCh:   make(chan msg.Message, 10),
+		readCh:   make(chan msg.Message, 10),
+		closedCh: make(chan int),
+		Logger:   log.NewPrefixLogger(""),
+	}
+}
+
+// 1. login
+// 2. start reader() writer() manager()
+// 3. connection closed
+// 4. In reader(): close closedCh and exit, controler() get it
+// 5. In controler(): close readCh and sendCh, manager() and writer() will exit
+// 6. In controler(): ini readCh, sendCh, closedCh
+// 7. In controler(): start new reader(), writer(), manager()
+// controler() will keep running
+func (ctl *Control) Run() error {
+	err := ctl.login()
+	if err != nil {
+		return err
+	}
+
+	go ctl.controler()
+	go ctl.manager()
+	go ctl.writer()
+	go ctl.reader()
+
+	// send NewProxy message for all configured proxies
+	for _, cfg := range ctl.pxyCfgs {
+		var newProxyMsg msg.NewProxy
+		cfg.UnMarshalToMsg(&newProxyMsg)
+		ctl.sendCh <- &newProxyMsg
+	}
+	return nil
+}
+
+func (ctl *Control) NewWorkConn() {
+	workConn, err := net.ConnectTcpServerByHttpProxy(config.ClientCommonCfg.HttpProxy,
+		fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
+	if err != nil {
+		ctl.Warn("start new work connection error: %v", err)
+		return
+	}
+
+	m := &msg.NewWorkConn{
+		RunId: ctl.runId,
+	}
+	if err = msg.WriteMsg(workConn, m); err != nil {
+		ctl.Warn("work connection write to server error: %v", err)
+		workConn.Close()
+		return
+	}
+
+	var startMsg msg.StartWorkConn
+	if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
+		ctl.Error("work connection closed and no response from server, %v", err)
+		workConn.Close()
+		return
+	}
+	workConn.AddLogPrefix(startMsg.ProxyName)
+
+	// dispatch this work connection to related proxy
+	if pxy, ok := ctl.proxies[startMsg.ProxyName]; ok {
+		go pxy.InWorkConn(workConn)
+		workConn.Info("start a new work connection")
+	}
+}
+
+func (ctl *Control) init() {
+	ctl.sendCh = make(chan msg.Message, 10)
+	ctl.readCh = make(chan msg.Message, 10)
+	ctl.closedCh = make(chan int)
+}
+
+// login send a login message to server and wait for a loginResp message.
+func (ctl *Control) login() (err error) {
+	if ctl.conn != nil {
+		ctl.conn.Close()
+	}
+	conn, err := net.ConnectTcpServerByHttpProxy(config.ClientCommonCfg.HttpProxy,
+		fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
+	if err != nil {
+		return err
+	}
+
+	now := time.Now().Unix()
+	ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now)
+	ctl.loginMsg.Timestamp = now
+	ctl.loginMsg.RunId = ctl.runId
+
+	if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
+		return err
+	}
+
+	var loginRespMsg msg.LoginResp
+	if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
+		return err
+	}
+
+	if loginRespMsg.Error != "" {
+		err = fmt.Errorf("%s", loginRespMsg.Error)
+		ctl.Error("%s", loginRespMsg.Error)
+		return err
+	}
+
+	ctl.conn = conn
+	// update runId got from server
+	ctl.runId = loginRespMsg.RunId
+	ctl.ClearLogPrefix()
+	ctl.AddLogPrefix(loginRespMsg.RunId)
+	ctl.Info("login to server success, get run id [%s]", loginRespMsg.RunId)
+
+	// login success, so we let closedCh available again
+	ctl.closedCh = make(chan int)
+	ctl.lastPong = time.Now()
+
+	return nil
+}
+
+func (ctl *Control) reader() {
+	defer func() {
+		if err := recover(); err != nil {
+			ctl.Error("panic error: %v", err)
+		}
+	}()
+
+	for {
+		if m, err := msg.ReadMsg(ctl.conn); err != nil {
+			if err == io.EOF {
+				ctl.Debug("read from control connection EOF")
+				close(ctl.closedCh)
+				return
+			} else {
+				ctl.Warn("read error: %v", err)
+				continue
+			}
+		} else {
+			ctl.readCh <- m
+		}
+	}
+}
+
+func (ctl *Control) writer() {
+	for {
+		if m, ok := <-ctl.sendCh; !ok {
+			ctl.Info("control writer is closing")
+			return
+		} else {
+			if err := msg.WriteMsg(ctl.conn, m); err != nil {
+				ctl.Warn("write message to control connection error: %v", err)
+				return
+			}
+		}
+	}
+}
+
+func (ctl *Control) manager() {
+	defer func() {
+		if err := recover(); err != nil {
+			ctl.Error("panic error: %v", err)
+		}
+	}()
+
+	hbSend := time.NewTicker(time.Duration(config.ClientCommonCfg.HeartBeatInterval) * time.Second)
+	defer hbSend.Stop()
+	hbCheck := time.NewTicker(time.Second)
+	defer hbCheck.Stop()
+
+	for {
+		select {
+		case <-hbSend.C:
+			// send heartbeat to server
+			ctl.sendCh <- &msg.Ping{}
+		case <-hbCheck.C:
+			if time.Since(ctl.lastPong) > time.Duration(config.ClientCommonCfg.HeartBeatTimeout)*time.Second {
+				ctl.Warn("heartbeat timeout")
+				return
+			}
+		case rawMsg, ok := <-ctl.readCh:
+			if !ok {
+				return
+			}
+
+			switch m := rawMsg.(type) {
+			case *msg.ReqWorkConn:
+				go ctl.NewWorkConn()
+			case *msg.NewProxyResp:
+				// Server will return NewProxyResp message to each NewProxy message.
+				// Start a new proxy handler if no error got
+				if m.Error != "" {
+					ctl.Warn("[%s] start error: %s", m.ProxyName, m.Error)
+					continue
+				}
+				oldPxy, ok := ctl.proxies[m.ProxyName]
+				if ok {
+					oldPxy.Close()
+				}
+				cfg, ok := ctl.pxyCfgs[m.ProxyName]
+				if !ok {
+					// it will never go to this branch
+					ctl.Warn("[%s] no proxy conf found", m.ProxyName)
+					continue
+				}
+				pxy := NewProxy(ctl, cfg)
+				pxy.Run()
+				ctl.proxies[m.ProxyName] = pxy
+				ctl.Info("[%s] start proxy success", m.ProxyName)
+			case *msg.Pong:
+				ctl.lastPong = time.Now()
+			}
+		}
+	}
+}
+
+// control keep watching closedCh, start a new connection if previous control connection is closed
+func (ctl *Control) controler() {
+	var err error
+	maxDelayTime := 30 * time.Second
+	delayTime := time.Second
+	for {
+		// we won't get any variable from this channel
+		_, ok := <-ctl.closedCh
+		if !ok {
+			// close related channels
+			close(ctl.readCh)
+			close(ctl.sendCh)
+			time.Sleep(time.Second)
+
+			// loop util reconnect to server success
+			for {
+				ctl.Info("try to reconnect to server...")
+				err = ctl.login()
+				if err != nil {
+					ctl.Warn("reconnect to server error: %v", err)
+					time.Sleep(delayTime)
+					delayTime = delayTime * 2
+					if delayTime > maxDelayTime {
+						delayTime = maxDelayTime
+					}
+					continue
+				}
+				// reconnect success, init the delayTime
+				delayTime = time.Second
+				break
+			}
+
+			// init related channels and variables
+			ctl.init()
+
+			// previous work goroutines should be closed and start them here
+			go ctl.manager()
+			go ctl.writer()
+			go ctl.reader()
+
+			// send NewProxy message for all configured proxies
+			for _, cfg := range ctl.pxyCfgs {
+				var newProxyMsg msg.NewProxy
+				cfg.UnMarshalToMsg(&newProxyMsg)
+				ctl.sendCh <- &newProxyMsg
+			}
+		}
+	}
+}

+ 0 - 0
src/models/client/process_udp.go → client/process_udp.go.bak


+ 141 - 0
client/proxy.go

@@ -0,0 +1,141 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package client
+
+import (
+	"fmt"
+	"io"
+
+	"github.com/fatedier/frp/models/config"
+	"github.com/fatedier/frp/models/proto/tcp"
+	"github.com/fatedier/frp/utils/net"
+)
+
+type Proxy interface {
+	Run()
+	InWorkConn(conn net.Conn)
+	Close()
+}
+
+func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
+	switch cfg := pxyConf.(type) {
+	case *config.TcpProxyConf:
+		pxy = &TcpProxy{
+			cfg: cfg,
+			ctl: ctl,
+		}
+	case *config.UdpProxyConf:
+		pxy = &UdpProxy{
+			cfg: cfg,
+			ctl: ctl,
+		}
+	case *config.HttpProxyConf:
+		pxy = &HttpProxy{
+			cfg: cfg,
+			ctl: ctl,
+		}
+	case *config.HttpsProxyConf:
+		pxy = &HttpsProxy{
+			cfg: cfg,
+			ctl: ctl,
+		}
+	}
+	return
+}
+
+// TCP
+type TcpProxy struct {
+	cfg *config.TcpProxyConf
+	ctl *Control
+}
+
+func (pxy *TcpProxy) Run() {
+}
+
+func (pxy *TcpProxy) Close() {
+}
+
+func (pxy *TcpProxy) InWorkConn(conn net.Conn) {
+	defer conn.Close()
+	localConn, err := net.ConnectTcpServer(fmt.Sprintf("%s:%d", pxy.cfg.LocalIp, pxy.cfg.LocalPort))
+	if err != nil {
+		conn.Error("connect to local service [%s:%d] error: %v", pxy.cfg.LocalIp, pxy.cfg.LocalPort, err)
+		return
+	}
+
+	var remote io.ReadWriteCloser
+	remote = conn
+	if pxy.cfg.UseEncryption {
+		remote, err = tcp.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
+		if err != nil {
+			conn.Error("create encryption stream error: %v", err)
+			return
+		}
+	}
+	if pxy.cfg.UseCompression {
+		remote = tcp.WithCompression(remote)
+	}
+	conn.Debug("join connections")
+	tcp.Join(localConn, remote)
+	conn.Debug("join connections closed")
+}
+
+// UDP
+type UdpProxy struct {
+	cfg *config.UdpProxyConf
+	ctl *Control
+}
+
+func (pxy *UdpProxy) Run() {
+}
+
+func (pxy *UdpProxy) Close() {
+}
+
+func (pxy *UdpProxy) InWorkConn(conn net.Conn) {
+	defer conn.Close()
+}
+
+// HTTP
+type HttpProxy struct {
+	cfg *config.HttpProxyConf
+	ctl *Control
+}
+
+func (pxy *HttpProxy) Run() {
+}
+
+func (pxy *HttpProxy) Close() {
+}
+
+func (pxy *HttpProxy) InWorkConn(conn net.Conn) {
+	defer conn.Close()
+}
+
+// HTTPS
+type HttpsProxy struct {
+	cfg *config.HttpsProxyConf
+	ctl *Control
+}
+
+func (pxy *HttpsProxy) Run() {
+}
+
+func (pxy *HttpsProxy) Close() {
+}
+
+func (pxy *HttpsProxy) InWorkConn(conn net.Conn) {
+	defer conn.Close()
+}

+ 43 - 0
client/service.go

@@ -0,0 +1,43 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package client
+
+import "github.com/fatedier/frp/models/config"
+
+type Service struct {
+	// manager control connection with server
+	ctl *Control
+
+	closedCh chan int
+}
+
+func NewService(pxyCfgs map[string]config.ProxyConf) (svr *Service) {
+	svr = &Service{
+		closedCh: make(chan int),
+	}
+	ctl := NewControl(svr, pxyCfgs)
+	svr.ctl = ctl
+	return
+}
+
+func (svr *Service) Run() error {
+	err := svr.ctl.Run()
+	if err != nil {
+		return err
+	}
+
+	<-svr.closedCh
+	return nil
+}

+ 35 - 24
src/cmd/frpc/main.go → cmd/frpc/main.go

@@ -19,13 +19,14 @@ import (
 	"os"
 	"strconv"
 	"strings"
-	"sync"
 
 	docopt "github.com/docopt/docopt-go"
+	ini "github.com/vaughan0/go-ini"
 
-	"github.com/fatedier/frp/src/models/client"
-	"github.com/fatedier/frp/src/utils/log"
-	"github.com/fatedier/frp/src/utils/version"
+	"github.com/fatedier/frp/client"
+	"github.com/fatedier/frp/models/config"
+	"github.com/fatedier/frp/utils/log"
+	"github.com/fatedier/frp/utils/version"
 )
 
 var (
@@ -49,29 +50,38 @@ Options:
 `
 
 func main() {
+	var err error
+	confFile := "./frpc.ini"
 	// the configures parsed from file will be replaced by those from command line if exist
 	args, err := docopt.Parse(usage, nil, true, version.Full(), false)
 
 	if args["-c"] != nil {
-		configFile = args["-c"].(string)
+		confFile = args["-c"].(string)
 	}
-	err = client.LoadConf(configFile)
+
+	conf, err := ini.LoadFile(confFile)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	config.ClientCommonCfg, err = config.LoadClientCommonConf(conf)
 	if err != nil {
 		fmt.Println(err)
-		os.Exit(-1)
+		os.Exit(1)
 	}
 
 	if args["-L"] != nil {
 		if args["-L"].(string) == "console" {
-			client.LogWay = "console"
+			config.ClientCommonCfg.LogWay = "console"
 		} else {
-			client.LogWay = "file"
-			client.LogFile = args["-L"].(string)
+			config.ClientCommonCfg.LogWay = "file"
+			config.ClientCommonCfg.LogFile = args["-L"].(string)
 		}
 	}
 
 	if args["--log-level"] != nil {
-		client.LogLevel = args["--log-level"].(string)
+		config.ClientCommonCfg.LogLevel = args["--log-level"].(string)
 	}
 
 	if args["--server-addr"] != nil {
@@ -85,8 +95,8 @@ func main() {
 			fmt.Println("--server-addr format error, example 0.0.0.0:7000")
 			os.Exit(1)
 		}
-		client.ServerAddr = addr[0]
-		client.ServerPort = serverPort
+		config.ClientCommonCfg.ServerAddr = addr[0]
+		config.ClientCommonCfg.ServerPort = serverPort
 	}
 
 	if args["-v"] != nil {
@@ -96,18 +106,19 @@ func main() {
 		}
 	}
 
-	log.InitLog(client.LogWay, client.LogFile, client.LogLevel, client.LogMaxDays)
-
-	// wait until all control goroutine exit
-	var wait sync.WaitGroup
-	wait.Add(len(client.ProxyClients))
-
-	for _, client := range client.ProxyClients {
-		go ControlProcess(client, &wait)
+	pxyCfgs, err := config.LoadProxyConfFromFile(conf)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
 	}
 
-	log.Info("Start frpc success")
+	log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
+		config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
 
-	wait.Wait()
-	log.Warn("All proxy exit!")
+	svr := client.NewService(pxyCfgs)
+	err = svr.Run()
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
 }

+ 117 - 0
cmd/frps/main.go

@@ -0,0 +1,117 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+
+	docopt "github.com/docopt/docopt-go"
+	ini "github.com/vaughan0/go-ini"
+
+	"github.com/fatedier/frp/models/config"
+	"github.com/fatedier/frp/server"
+	"github.com/fatedier/frp/utils/log"
+	"github.com/fatedier/frp/utils/version"
+)
+
+var usage string = `frps is the server of frp
+
+Usage: 
+    frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
+    frps -h | --help
+    frps -v | --version
+
+Options:
+    -c config_file            set config file
+    -L log_file               set output log file, including console
+    --log-level=<log_level>   set log level: debug, info, warn, error
+    --addr=<bind_addr>        listen addr for client, example: 0.0.0.0:7000
+    -h --help                 show this screen
+    -v --version              show version
+`
+
+func main() {
+	var err error
+	confFile := "./frps.ini"
+	// the configures parsed from file will be replaced by those from command line if exist
+	args, err := docopt.Parse(usage, nil, true, version.Full(), false)
+
+	if args["-c"] != nil {
+		confFile = args["-c"].(string)
+	}
+
+	conf, err := ini.LoadFile(confFile)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+	config.ServerCommonCfg, err = config.LoadServerCommonConf(conf)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	if args["-L"] != nil {
+		if args["-L"].(string) == "console" {
+			config.ServerCommonCfg.LogWay = "console"
+		} else {
+			config.ServerCommonCfg.LogWay = "file"
+			config.ServerCommonCfg.LogFile = args["-L"].(string)
+		}
+	}
+
+	if args["--log-level"] != nil {
+		config.ServerCommonCfg.LogLevel = args["--log-level"].(string)
+	}
+
+	if args["--addr"] != nil {
+		addr := strings.Split(args["--addr"].(string), ":")
+		if len(addr) != 2 {
+			fmt.Println("--addr format error: example 0.0.0.0:7000")
+			os.Exit(1)
+		}
+		bindPort, err := strconv.ParseInt(addr[1], 10, 64)
+		if err != nil {
+			fmt.Println("--addr format error, example 0.0.0.0:7000")
+			os.Exit(1)
+		}
+		config.ServerCommonCfg.BindAddr = addr[0]
+		config.ServerCommonCfg.BindPort = bindPort
+	}
+
+	if args["-v"] != nil {
+		if args["-v"].(bool) {
+			fmt.Println(version.Full())
+			os.Exit(0)
+		}
+	}
+
+	log.InitLog(config.ServerCommonCfg.LogWay, config.ServerCommonCfg.LogFile,
+		config.ServerCommonCfg.LogLevel, config.ServerCommonCfg.LogMaxDays)
+
+	svr, err := server.NewService()
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+	log.Info("Start frps success")
+	if config.ServerCommonCfg.PrivilegeMode == true {
+		log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
+	}
+	svr.Run()
+}

+ 18 - 30
conf/frpc.ini

@@ -17,70 +17,58 @@ log_level = info
 log_max_days = 3
 
 # for authentication
-auth_token = 123
-
-# for privilege mode
 privilege_token = 12345678
 
+# connections will be established in advance, default value is zero
+pool_count = 5
+
+# your proxy name will be changed to {user}.{proxy}
+user = your_name
+
 # heartbeat configure, it's not recommended to modify the default value
 # the default value of heartbeat_interval is 10 and heartbeat_timeout is 30
 # heartbeat_interval = 10
 # heartbeat_timeout = 30
 
 # ssh is the proxy name same as server's configuration
+# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as your_name.ssh
 [ssh]
-# tcp | http, default is tcp
+# tcp | udp | http | https, default is tcp
 type = tcp
 local_ip = 127.0.0.1
 local_port = 22
 # true or false, if true, messages between frps and frpc will be encrypted, default is false
 use_encryption = false
-# default is false
-use_gzip = false
+# if true, message will be compressed
+use_compression = false
+# remote port listen by frps
+remote_port = 6001
 
 [dns]
 type = udp
 local_ip = 114.114.114.114
 local_port = 53
 
-# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02, the domains are set in frps.ini
+# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
 [web01]
 type = http
 local_ip = 127.0.0.1
 local_port = 80
-use_gzip = true
-# connections will be established in advance, default value is zero
-pool_count = 20
+use_compression = true
 # http username and password are safety certification for http protocol
 # if not set, you can access this custom_domains without certification
 http_user = admin
 http_pwd = admin
 # if domain for frps is frps.com, then you can access [web01] proxy by URL http://test.frps.com
-subdomain = test
+subdomain = web01
 
 [web02]
 type = http
 local_ip = 127.0.0.1
 local_port = 8000
-
-[privilege_ssh]
-# if privilege_mode is enabled, this proxy will be created automatically
-privilege_mode = true
-type = tcp
-local_ip = 127.0.0.1
-local_port = 22
-use_encryption = true
-use_gzip = false
-remote_port = 6001
-
-[privilege_web]
-privilege_mode = true
-type = http
-local_ip = 127.0.0.1
-local_port = 80
-use_gzip = true
-custom_domains = web03.yourdomain.com
+use_encryption = false
+use_compression = false 
+custom_domains = web02.yourdomain.com
 # locations is only useful for http type
 locations = /,/pic
 host_header_rewrite = example.com
-subdomain = dev

+ 2 - 29
conf/frps.ini

@@ -26,8 +26,7 @@ log_level = info
 
 log_max_days = 3
 
-# if you enable privilege mode, frpc can create a proxy without pre-configure in frps when privilege_token is correct
-privilege_mode = true
+# privilege mode is the only supported mode since v0.10.0
 privilege_token = 12345678
 
 # heartbeat configure, it's not recommended to modify the default value
@@ -38,7 +37,7 @@ privilege_token = 12345678
 privilege_allow_ports = 2000-3000,3001,3003,4000-50000
 
 # pool_count in each proxy will change to max_pool_count if they exceed the maximum value
-max_pool_count = 100
+max_pool_count = 10
 
 # authentication_timeout means the timeout interval (seconds) when the frpc connects frps
 # if authentication_timeout is zero, the time is not verified, default is 900s
@@ -47,29 +46,3 @@ authentication_timeout = 900
 # if subdomain_host is not empty, you can set subdomain when type is http or https in frpc's configure file
 # when subdomain is test, the host used by routing is test.frps.com
 subdomain_host = frps.com
-
-# ssh is the proxy name, client will use this name and auth_token to connect to server
-[ssh]
-type = tcp
-auth_token = 123
-bind_addr = 0.0.0.0
-listen_port = 6000
-
-[dns]
-type = udp
-auth_token = 123
-bind_addr = 0.0.0.0
-listen_port = 5353
-
-[web01]
-# if type equals http, vhost_http_port must be set
-type = http
-auth_token = 123
-# if proxy type equals http, custom_domains must be set separated by commas
-custom_domains = web01.yourdomain.com,web01.yourdomain2.com
-
-[web02]
-# if type equals https, vhost_https_port must be set
-type = https
-auth_token = 123
-custom_domains = web02.yourdomain.com

+ 160 - 0
models/config/client_common.go

@@ -0,0 +1,160 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+
+	ini "github.com/vaughan0/go-ini"
+)
+
+var ClientCommonCfg *ClientCommonConf
+
+// client common config
+type ClientCommonConf struct {
+	ConfigFile        string
+	ServerAddr        string
+	ServerPort        int64
+	HttpProxy         string
+	LogFile           string
+	LogWay            string
+	LogLevel          string
+	LogMaxDays        int64
+	PrivilegeToken    string
+	PoolCount         int
+	User              string
+	HeartBeatInterval int64
+	HeartBeatTimeout  int64
+}
+
+func GetDeaultClientCommonConf() *ClientCommonConf {
+	return &ClientCommonConf{
+		ConfigFile:        "./frpc.ini",
+		ServerAddr:        "0.0.0.0",
+		ServerPort:        7000,
+		HttpProxy:         "",
+		LogFile:           "console",
+		LogWay:            "console",
+		LogLevel:          "info",
+		LogMaxDays:        3,
+		PrivilegeToken:    "",
+		PoolCount:         1,
+		User:              "",
+		HeartBeatInterval: 10,
+		HeartBeatTimeout:  30,
+	}
+}
+
+func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
+	var (
+		tmpStr string
+		ok     bool
+		v      int64
+	)
+	cfg = GetDeaultClientCommonConf()
+
+	tmpStr, ok = conf.Get("common", "server_addr")
+	if ok {
+		cfg.ServerAddr = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "server_port")
+	if ok {
+		cfg.ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
+	}
+
+	tmpStr, ok = conf.Get("common", "http_proxy")
+	if ok {
+		cfg.HttpProxy = tmpStr
+	} else {
+		// get http_proxy from env
+		cfg.HttpProxy = os.Getenv("http_proxy")
+	}
+
+	tmpStr, ok = conf.Get("common", "log_file")
+	if ok {
+		cfg.LogFile = tmpStr
+		if cfg.LogFile == "console" {
+			cfg.LogWay = "console"
+		} else {
+			cfg.LogWay = "file"
+		}
+	}
+
+	tmpStr, ok = conf.Get("common", "log_level")
+	if ok {
+		cfg.LogLevel = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "log_max_days")
+	if ok {
+		cfg.LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
+	}
+
+	tmpStr, ok = conf.Get("common", "privilege_token")
+	if ok {
+		cfg.PrivilegeToken = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "pool_count")
+	if ok {
+		v, err = strconv.ParseInt(tmpStr, 10, 64)
+		if err != nil {
+			cfg.PoolCount = 1
+		} else {
+			cfg.PoolCount = int(v)
+		}
+	}
+
+	tmpStr, ok = conf.Get("common", "user")
+	if ok {
+		cfg.User = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "heartbeat_timeout")
+	if ok {
+		v, err = strconv.ParseInt(tmpStr, 10, 64)
+		if err != nil {
+			err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
+			return
+		} else {
+			cfg.HeartBeatTimeout = v
+		}
+	}
+
+	tmpStr, ok = conf.Get("common", "heartbeat_interval")
+	if ok {
+		v, err = strconv.ParseInt(tmpStr, 10, 64)
+		if err != nil {
+			err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
+			return
+		} else {
+			cfg.HeartBeatInterval = v
+		}
+	}
+
+	if cfg.HeartBeatInterval <= 0 {
+		err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
+		return
+	}
+
+	if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
+		err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
+		return
+	}
+	return
+}

+ 446 - 0
models/config/proxy.go

@@ -0,0 +1,446 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/fatedier/frp/models/consts"
+	"github.com/fatedier/frp/models/msg"
+
+	ini "github.com/vaughan0/go-ini"
+)
+
+type ProxyConf interface {
+	GetName() string
+	GetBaseInfo() *BaseProxyConf
+	LoadFromMsg(pMsg *msg.NewProxy)
+	LoadFromFile(name string, conf ini.Section) error
+	UnMarshalToMsg(pMsg *msg.NewProxy)
+	Check() error
+}
+
+func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
+	if pMsg.ProxyType == "" {
+		pMsg.ProxyType = consts.TcpProxy
+	}
+	switch pMsg.ProxyType {
+	case consts.TcpProxy:
+		cfg = &TcpProxyConf{}
+	case consts.UdpProxy:
+		cfg = &UdpProxyConf{}
+	case consts.HttpProxy:
+		cfg = &HttpProxyConf{}
+	case consts.HttpsProxy:
+		cfg = &HttpsProxyConf{}
+	default:
+		err = fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType)
+		return
+	}
+	cfg.LoadFromMsg(pMsg)
+	err = cfg.Check()
+	return
+}
+
+func NewProxyConfFromFile(name string, section ini.Section) (cfg ProxyConf, err error) {
+	proxyType := section["type"]
+	if proxyType == "" {
+		proxyType = consts.TcpProxy
+		section["type"] = consts.TcpProxy
+	}
+	switch proxyType {
+	case consts.TcpProxy:
+		cfg = &TcpProxyConf{}
+	case consts.UdpProxy:
+		cfg = &UdpProxyConf{}
+	case consts.HttpProxy:
+		cfg = &HttpProxyConf{}
+	case consts.HttpsProxy:
+		cfg = &HttpsProxyConf{}
+	default:
+		err = fmt.Errorf("proxy [%s] type [%s] error", name, proxyType)
+		return
+	}
+	err = cfg.LoadFromFile(name, section)
+	return
+}
+
+// BaseProxy info
+type BaseProxyConf struct {
+	ProxyName string
+	ProxyType string
+
+	UseEncryption  bool
+	UseCompression bool
+}
+
+func (cfg *BaseProxyConf) GetName() string {
+	return cfg.ProxyName
+}
+
+func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
+	return cfg
+}
+
+func (cfg *BaseProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
+	cfg.ProxyName = pMsg.ProxyName
+	cfg.ProxyType = pMsg.ProxyType
+	cfg.UseEncryption = pMsg.UseEncryption
+	cfg.UseCompression = pMsg.UseCompression
+}
+
+func (cfg *BaseProxyConf) LoadFromFile(name string, section ini.Section) error {
+	var (
+		tmpStr string
+		ok     bool
+	)
+	cfg.ProxyName = ClientCommonCfg.User + "." + name
+	cfg.ProxyType = section["type"]
+
+	tmpStr, ok = section["use_encryption"]
+	if ok && tmpStr == "true" {
+		cfg.UseEncryption = true
+	}
+
+	tmpStr, ok = section["use_compression"]
+	if ok && tmpStr == "true" {
+		cfg.UseCompression = true
+	}
+	return nil
+}
+
+func (cfg *BaseProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
+	pMsg.ProxyName = cfg.ProxyName
+	pMsg.ProxyType = cfg.ProxyType
+	pMsg.UseEncryption = cfg.UseEncryption
+	pMsg.UseCompression = cfg.UseCompression
+}
+
+// Bind info
+type BindInfoConf struct {
+	BindAddr   string
+	RemotePort int64
+}
+
+func (cfg *BindInfoConf) LoadFromMsg(pMsg *msg.NewProxy) {
+	cfg.BindAddr = ServerCommonCfg.BindAddr
+	cfg.RemotePort = pMsg.RemotePort
+}
+
+func (cfg *BindInfoConf) LoadFromFile(name string, section ini.Section) (err error) {
+	var (
+		tmpStr string
+		ok     bool
+	)
+	if tmpStr, ok = section["remote_port"]; ok {
+		if cfg.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
+			return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name)
+		}
+	} else {
+		return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name)
+	}
+	return nil
+}
+
+func (cfg *BindInfoConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
+	pMsg.RemotePort = cfg.RemotePort
+}
+
+func (cfg *BindInfoConf) check() (err error) {
+	if len(ServerCommonCfg.PrivilegeAllowPorts) != 0 {
+		if _, ok := ServerCommonCfg.PrivilegeAllowPorts[cfg.RemotePort]; !ok {
+			return fmt.Errorf("remote port [%d] isn't allowed", cfg.RemotePort)
+		}
+	}
+	return nil
+}
+
+// Domain info
+type DomainConf struct {
+	CustomDomains []string
+	SubDomain     string
+}
+
+func (cfg *DomainConf) LoadFromMsg(pMsg *msg.NewProxy) {
+	cfg.CustomDomains = pMsg.CustomDomains
+	cfg.SubDomain = pMsg.SubDomain
+}
+
+func (cfg *DomainConf) LoadFromFile(name string, section ini.Section) (err error) {
+	var (
+		tmpStr string
+		ok     bool
+	)
+	if tmpStr, ok = section["custom_domains"]; ok {
+		cfg.CustomDomains = strings.Split(tmpStr, ",")
+		for i, domain := range cfg.CustomDomains {
+			cfg.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
+		}
+	}
+
+	if tmpStr, ok = section["subdomain"]; ok {
+		cfg.SubDomain = tmpStr
+	}
+
+	if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
+		return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them", name)
+	}
+	return
+}
+
+func (cfg *DomainConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
+	pMsg.CustomDomains = cfg.CustomDomains
+	pMsg.SubDomain = cfg.SubDomain
+}
+
+func (cfg *DomainConf) check() (err error) {
+	for _, domain := range cfg.CustomDomains {
+		if ServerCommonCfg.SubDomainHost != "" && len(strings.Split(ServerCommonCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
+			if strings.Contains(domain, ServerCommonCfg.SubDomainHost) {
+				return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, ServerCommonCfg.SubDomainHost)
+			}
+		}
+	}
+
+	if cfg.SubDomain != "" {
+		if ServerCommonCfg.SubDomainHost == "" {
+			return fmt.Errorf("subdomain is not supported because this feature is not enabled by frps")
+		}
+		if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
+			return fmt.Errorf("'.' and '*' is not supported in subdomain")
+		}
+		cfg.SubDomain += "." + ServerCommonCfg.SubDomainHost
+	}
+	return nil
+}
+
+type LocalSvrConf struct {
+	LocalIp   string
+	LocalPort int
+}
+
+func (cfg *LocalSvrConf) LoadFromFile(name string, section ini.Section) (err error) {
+	if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" {
+		cfg.LocalIp = "127.0.0.1"
+	}
+
+	if tmpStr, ok := section["local_port"]; ok {
+		if cfg.LocalPort, err = strconv.Atoi(tmpStr); err != nil {
+			return fmt.Errorf("Parse conf error: proxy [%s] local_port error", name)
+		}
+	} else {
+		return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", name)
+	}
+	return nil
+}
+
+// TCP
+type TcpProxyConf struct {
+	BaseProxyConf
+	BindInfoConf
+
+	LocalSvrConf
+}
+
+func (cfg *TcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
+	cfg.BaseProxyConf.LoadFromMsg(pMsg)
+	cfg.BindInfoConf.LoadFromMsg(pMsg)
+}
+
+func (cfg *TcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
+	if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	return
+}
+
+func (cfg *TcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
+	cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
+	cfg.BindInfoConf.UnMarshalToMsg(pMsg)
+}
+
+func (cfg *TcpProxyConf) Check() (err error) {
+	err = cfg.BindInfoConf.check()
+	return
+}
+
+// UDP
+type UdpProxyConf struct {
+	BaseProxyConf
+	BindInfoConf
+
+	LocalSvrConf
+}
+
+func (cfg *UdpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
+	cfg.BaseProxyConf.LoadFromMsg(pMsg)
+	cfg.BindInfoConf.LoadFromMsg(pMsg)
+}
+
+func (cfg *UdpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
+	if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	return
+}
+
+func (cfg *UdpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
+	cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
+	cfg.BindInfoConf.UnMarshalToMsg(pMsg)
+}
+
+func (cfg *UdpProxyConf) Check() (err error) {
+	err = cfg.BindInfoConf.check()
+	return
+}
+
+// HTTP
+type HttpProxyConf struct {
+	BaseProxyConf
+	DomainConf
+
+	LocalSvrConf
+
+	Locations         []string
+	HostHeaderRewrite string
+	HttpUser          string
+	HttpPwd           string
+}
+
+func (cfg *HttpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
+	cfg.BaseProxyConf.LoadFromMsg(pMsg)
+	cfg.DomainConf.LoadFromMsg(pMsg)
+
+	cfg.Locations = pMsg.Locations
+	cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
+	cfg.HttpUser = pMsg.HttpUser
+	cfg.HttpPwd = pMsg.HttpPwd
+}
+
+func (cfg *HttpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
+	if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+
+	var (
+		tmpStr string
+		ok     bool
+	)
+	if tmpStr, ok = section["locations"]; ok {
+		cfg.Locations = strings.Split(tmpStr, ",")
+	} else {
+		cfg.Locations = []string{""}
+	}
+
+	cfg.HostHeaderRewrite = section["host_header_rewrite"]
+	cfg.HttpUser = section["http_user"]
+	cfg.HttpPwd = section["http_pwd"]
+	return
+}
+
+func (cfg *HttpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
+	cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
+	cfg.DomainConf.UnMarshalToMsg(pMsg)
+
+	pMsg.Locations = cfg.Locations
+	pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
+	pMsg.HttpUser = cfg.HttpUser
+	pMsg.HttpPwd = cfg.HttpPwd
+}
+
+func (cfg *HttpProxyConf) Check() (err error) {
+	if ServerCommonCfg.VhostHttpPort == 0 {
+		return fmt.Errorf("type [http] not support when vhost_http_port is not set")
+	}
+	err = cfg.DomainConf.check()
+	return
+}
+
+// HTTPS
+type HttpsProxyConf struct {
+	BaseProxyConf
+	DomainConf
+
+	LocalSvrConf
+}
+
+func (cfg *HttpsProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
+	cfg.BaseProxyConf.LoadFromMsg(pMsg)
+	cfg.DomainConf.LoadFromMsg(pMsg)
+}
+
+func (cfg *HttpsProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
+	if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+	return
+}
+
+func (cfg *HttpsProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
+	cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
+	cfg.DomainConf.UnMarshalToMsg(pMsg)
+}
+
+func (cfg *HttpsProxyConf) Check() (err error) {
+	if ServerCommonCfg.VhostHttpsPort == 0 {
+		return fmt.Errorf("type [https] not support when vhost_https_port is not set")
+	}
+	err = cfg.DomainConf.check()
+	return
+}
+
+func LoadProxyConfFromFile(conf ini.File) (proxyConfs map[string]ProxyConf, err error) {
+	var prefix string
+	if ClientCommonCfg.User != "" {
+		prefix = ClientCommonCfg.User + "."
+	}
+	proxyConfs = make(map[string]ProxyConf)
+	for name, section := range conf {
+		if name != "common" {
+			cfg, err := NewProxyConfFromFile(name, section)
+			if err != nil {
+				return proxyConfs, err
+			}
+			proxyConfs[prefix+name] = cfg
+		}
+	}
+	return
+}

+ 279 - 0
models/config/server_common.go

@@ -0,0 +1,279 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	ini "github.com/vaughan0/go-ini"
+)
+
+var ServerCommonCfg *ServerCommonConf
+
+// common config
+type ServerCommonConf struct {
+	ConfigFile string
+	BindAddr   string
+	BindPort   int64
+
+	// If VhostHttpPort equals 0, don't listen a public port for http protocol.
+	VhostHttpPort int64
+
+	// if VhostHttpsPort equals 0, don't listen a public port for https protocol
+	VhostHttpsPort int64
+
+	// if DashboardPort equals 0, dashboard is not available
+	DashboardPort  int64
+	DashboardUser  string
+	DashboardPwd   string
+	AssetsDir      string
+	LogFile        string
+	LogWay         string // console or file
+	LogLevel       string
+	LogMaxDays     int64
+	PrivilegeMode  bool
+	PrivilegeToken string
+	AuthTimeout    int64
+	SubDomainHost  string
+
+	// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
+	PrivilegeAllowPorts map[int64]struct{}
+	MaxPoolCount        int64
+	HeartBeatTimeout    int64
+	UserConnTimeout     int64
+}
+
+func GetDefaultServerCommonConf() *ServerCommonConf {
+	return &ServerCommonConf{
+		ConfigFile:       "./frps.ini",
+		BindAddr:         "0.0.0.0",
+		BindPort:         7000,
+		VhostHttpPort:    0,
+		VhostHttpsPort:   0,
+		DashboardPort:    0,
+		DashboardUser:    "admin",
+		DashboardPwd:     "admin",
+		AssetsDir:        "",
+		LogFile:          "console",
+		LogWay:           "console",
+		LogLevel:         "info",
+		LogMaxDays:       3,
+		PrivilegeMode:    true,
+		PrivilegeToken:   "",
+		AuthTimeout:      900,
+		SubDomainHost:    "",
+		MaxPoolCount:     10,
+		HeartBeatTimeout: 30,
+		UserConnTimeout:  10,
+	}
+}
+
+// Load server common configure.
+func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
+	var (
+		tmpStr string
+		ok     bool
+		v      int64
+	)
+	cfg = GetDefaultServerCommonConf()
+
+	tmpStr, ok = conf.Get("common", "bind_addr")
+	if ok {
+		cfg.BindAddr = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "bind_port")
+	if ok {
+		v, err = strconv.ParseInt(tmpStr, 10, 64)
+		if err == nil {
+			cfg.BindPort = v
+		}
+	}
+
+	tmpStr, ok = conf.Get("common", "vhost_http_port")
+	if ok {
+		cfg.VhostHttpPort, err = strconv.ParseInt(tmpStr, 10, 64)
+		if err != nil {
+			err = fmt.Errorf("Parse conf error: vhost_http_port is incorrect")
+			return
+		}
+	} else {
+		cfg.VhostHttpPort = 0
+	}
+
+	tmpStr, ok = conf.Get("common", "vhost_https_port")
+	if ok {
+		cfg.VhostHttpsPort, err = strconv.ParseInt(tmpStr, 10, 64)
+		if err != nil {
+			err = fmt.Errorf("Parse conf error: vhost_https_port is incorrect")
+			return
+		}
+	} else {
+		cfg.VhostHttpsPort = 0
+	}
+
+	tmpStr, ok = conf.Get("common", "dashboard_port")
+	if ok {
+		cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
+		if err != nil {
+			err = fmt.Errorf("Parse conf error: dashboard_port is incorrect")
+			return
+		}
+	} else {
+		cfg.DashboardPort = 0
+	}
+
+	tmpStr, ok = conf.Get("common", "dashboard_user")
+	if ok {
+		cfg.DashboardUser = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "dashboard_pwd")
+	if ok {
+		cfg.DashboardPwd = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "assets_dir")
+	if ok {
+		cfg.AssetsDir = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "log_file")
+	if ok {
+		cfg.LogFile = tmpStr
+		if cfg.LogFile == "console" {
+			cfg.LogWay = "console"
+		} else {
+			cfg.LogWay = "file"
+		}
+	}
+
+	tmpStr, ok = conf.Get("common", "log_level")
+	if ok {
+		cfg.LogLevel = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "log_max_days")
+	if ok {
+		v, err = strconv.ParseInt(tmpStr, 10, 64)
+		if err == nil {
+			cfg.LogMaxDays = v
+		}
+	}
+
+	tmpStr, ok = conf.Get("common", "privilege_mode")
+	if ok {
+		if tmpStr == "true" {
+			cfg.PrivilegeMode = true
+		}
+	}
+
+	// PrivilegeMode configure
+	if cfg.PrivilegeMode == true {
+		tmpStr, ok = conf.Get("common", "privilege_token")
+		if ok {
+			if tmpStr == "" {
+				err = fmt.Errorf("Parse conf error: privilege_token can not be empty")
+				return
+			}
+			cfg.PrivilegeToken = tmpStr
+		} else {
+			err = fmt.Errorf("Parse conf error: privilege_token must be set if privilege_mode is enabled")
+			return
+		}
+
+		cfg.PrivilegeAllowPorts = make(map[int64]struct{})
+		tmpStr, ok = conf.Get("common", "privilege_allow_ports")
+		if ok {
+			// e.g. 1000-2000,2001,2002,3000-4000
+			portRanges := strings.Split(tmpStr, ",")
+			for _, portRangeStr := range portRanges {
+				// 1000-2000 or 2001
+				portArray := strings.Split(portRangeStr, "-")
+				// length: only 1 or 2 is correct
+				rangeType := len(portArray)
+				if rangeType == 1 {
+					// single port
+					singlePort, errRet := strconv.ParseInt(portArray[0], 10, 64)
+					if errRet != nil {
+						err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
+						return
+					}
+					ServerCommonCfg.PrivilegeAllowPorts[singlePort] = struct{}{}
+				} else if rangeType == 2 {
+					// range ports
+					min, errRet := strconv.ParseInt(portArray[0], 10, 64)
+					if errRet != nil {
+						err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
+						return
+					}
+					max, errRet := strconv.ParseInt(portArray[1], 10, 64)
+					if errRet != nil {
+						err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
+						return
+					}
+					if max < min {
+						err = fmt.Errorf("Parse conf error: privilege_allow_ports range incorrect")
+						return
+					}
+					for i := min; i <= max; i++ {
+						cfg.PrivilegeAllowPorts[i] = struct{}{}
+					}
+				} else {
+					err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect")
+					return
+				}
+			}
+		}
+	}
+
+	tmpStr, ok = conf.Get("common", "max_pool_count")
+	if ok {
+		v, err = strconv.ParseInt(tmpStr, 10, 64)
+		if err == nil && v >= 0 {
+			cfg.MaxPoolCount = v
+		}
+	}
+
+	tmpStr, ok = conf.Get("common", "authentication_timeout")
+	if ok {
+		v, errRet := strconv.ParseInt(tmpStr, 10, 64)
+		if errRet != nil {
+			err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
+			return
+		} else {
+			cfg.AuthTimeout = v
+		}
+	}
+
+	tmpStr, ok = conf.Get("common", "subdomain_host")
+	if ok {
+		cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
+	}
+
+	tmpStr, ok = conf.Get("common", "heartbeat_timeout")
+	if ok {
+		v, errRet := strconv.ParseInt(tmpStr, 10, 64)
+		if errRet != nil {
+			err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
+			return
+		} else {
+			cfg.HeartBeatTimeout = v
+		}
+	}
+	return
+}

+ 9 - 22
src/models/consts/consts.go → models/consts/consts.go

@@ -14,28 +14,15 @@
 
 package consts
 
-// server status
-const (
-	Idle = iota
-	Working
-	Closed
-)
-
 var (
-	StatusStr = []string{
-		"idle",
-		"working",
-		"closed",
-	}
-)
+	// server status
+	Idle    string = "idle"
+	Working string = "working"
+	Closed  string = "closed"
 
-// msg type
-const (
-	NewCtlConn = iota
-	NewWorkConn
-	NoticeUserConn
-	NewCtlConnRes
-	HeartbeatReq
-	HeartbeatRes
-	NewWorkConnUdp
+	// proxy type
+	TcpProxy   string = "tcp"
+	UdpProxy   string = "udp"
+	HttpProxy  string = "http"
+	HttpsProxy string = "https"
 )

+ 21 - 0
models/errors/errors.go

@@ -0,0 +1,21 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package errors
+
+import "errors"
+
+var (
+	ErrMsgType = errors.New("message type error")
+)

+ 2 - 8
src/models/metric/server.go → models/metric/server.go

@@ -14,14 +14,7 @@
 
 package metric
 
-import (
-	"sort"
-	"sync"
-	"time"
-
-	"github.com/fatedier/frp/src/models/consts"
-)
-
+/*
 var (
 	DailyDataKeepDays   int = 7
 	ServerMetricInfoMap map[string]*ServerMetric
@@ -225,3 +218,4 @@ func AddFlowOut(proxyName string, value int64) {
 		metric.mutex.Unlock()
 	}
 }
+*/

+ 122 - 0
models/msg/msg.go

@@ -0,0 +1,122 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package msg
+
+import "reflect"
+
+const (
+	TypeLogin         = 'o'
+	TypeLoginResp     = '1'
+	TypeNewProxy      = 'p'
+	TypeNewProxyResp  = '2'
+	TypeNewWorkConn   = 'w'
+	TypeReqWorkConn   = 'r'
+	TypeStartWorkConn = 's'
+	TypePing          = 'h'
+	TypePong          = '4'
+)
+
+var (
+	TypeMap       map[byte]reflect.Type
+	TypeStringMap map[reflect.Type]byte
+)
+
+func init() {
+	TypeMap = make(map[byte]reflect.Type)
+	TypeStringMap = make(map[reflect.Type]byte)
+
+	TypeMap[TypeLogin] = getTypeFn((*Login)(nil))
+	TypeMap[TypeLoginResp] = getTypeFn((*LoginResp)(nil))
+	TypeMap[TypeNewProxy] = getTypeFn((*NewProxy)(nil))
+	TypeMap[TypeNewProxyResp] = getTypeFn((*NewProxyResp)(nil))
+	TypeMap[TypeNewWorkConn] = getTypeFn((*NewWorkConn)(nil))
+	TypeMap[TypeReqWorkConn] = getTypeFn((*ReqWorkConn)(nil))
+	TypeMap[TypeStartWorkConn] = getTypeFn((*StartWorkConn)(nil))
+	TypeMap[TypePing] = getTypeFn((*Ping)(nil))
+	TypeMap[TypePong] = getTypeFn((*Pong)(nil))
+
+	for k, v := range TypeMap {
+		TypeStringMap[v] = k
+	}
+}
+
+func getTypeFn(obj interface{}) reflect.Type {
+	return reflect.TypeOf(obj).Elem()
+}
+
+// Message wraps socket packages for communicating between frpc and frps.
+type Message interface{}
+
+// When frpc start, client send this message to login to server.
+type Login struct {
+	Version      string `json:"version"`
+	Hostname     string `json:"hostname"`
+	Os           string `json:"os"`
+	Arch         string `json:"arch"`
+	User         string `json:"user"`
+	PrivilegeKey string `json:"privilege_key"`
+	Timestamp    int64  `json:"timestamp"`
+	RunId        string `json:"run_id"`
+
+	// Some global configures.
+	PoolCount int `json:"pool_count"`
+}
+
+type LoginResp struct {
+	Version string `json:"version"`
+	RunId   string `json:"run_id"`
+	Error   string `json:"error"`
+}
+
+// When frpc login success, send this message to frps for running a new proxy.
+type NewProxy struct {
+	ProxyName      string `json:"proxy_name"`
+	ProxyType      string `json:"proxy_type"`
+	UseEncryption  bool   `json:"use_encryption"`
+	UseCompression bool   `json:"use_compression"`
+
+	// tcp and udp only
+	RemotePort int64 `json:"remote_port"`
+
+	// http and https only
+	CustomDomains     []string `json:"custom_domains"`
+	SubDomain         string   `json:"subdomain"`
+	Locations         []string `json:"locations"`
+	HostHeaderRewrite string   `json:"host_header_rewrite"`
+	HttpUser          string   `json:"http_user"`
+	HttpPwd           string   `json:"http_pwd"`
+}
+
+type NewProxyResp struct {
+	ProxyName string `json:"proxy_name"`
+	Error     string `json:"error"`
+}
+
+type NewWorkConn struct {
+	RunId string `json:"run_id"`
+}
+
+type ReqWorkConn struct {
+}
+
+type StartWorkConn struct {
+	ProxyName string `json:"proxy_name"`
+}
+
+type Ping struct {
+}
+
+type Pong struct {
+}

+ 69 - 0
models/msg/pack.go

@@ -0,0 +1,69 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package msg
+
+import (
+	"bytes"
+	"encoding/binary"
+	"encoding/json"
+	"fmt"
+	"reflect"
+
+	"github.com/fatedier/frp/utils/errors"
+)
+
+func unpack(typeByte byte, buffer []byte, msgIn Message) (msg Message, err error) {
+	if msgIn == nil {
+		t, ok := TypeMap[typeByte]
+		if !ok {
+			err = fmt.Errorf("Unsupported message type %b", typeByte)
+			return
+		}
+
+		msg = reflect.New(t).Interface().(Message)
+	} else {
+		msg = msgIn
+	}
+
+	err = json.Unmarshal(buffer, &msg)
+	return
+}
+
+func UnPackInto(buffer []byte, msg Message) (err error) {
+	_, err = unpack(' ', buffer, msg)
+	return
+}
+
+func UnPack(typeByte byte, buffer []byte) (msg Message, err error) {
+	return unpack(typeByte, buffer, nil)
+}
+
+func Pack(msg Message) ([]byte, error) {
+	typeByte, ok := TypeStringMap[reflect.TypeOf(msg).Elem()]
+	if !ok {
+		return nil, errors.ErrMsgType
+	}
+
+	content, err := json.Marshal(msg)
+	if err != nil {
+		return nil, err
+	}
+
+	buffer := bytes.NewBuffer(nil)
+	buffer.WriteByte(typeByte)
+	binary.Write(buffer, binary.BigEndian, int64(len(content)))
+	buffer.Write(content)
+	return buffer.Bytes(), nil
+}

+ 86 - 0
models/msg/pack_test.go

@@ -0,0 +1,86 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package msg
+
+import (
+	"bytes"
+	"encoding/binary"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/fatedier/frp/utils/errors"
+)
+
+type TestStruct struct{}
+
+func TestPack(t *testing.T) {
+	assert := assert.New(t)
+
+	var (
+		msg    Message
+		buffer []byte
+		err    error
+	)
+
+	// error type
+	msg = &TestStruct{}
+	buffer, err = Pack(msg)
+	assert.Error(err, errors.ErrMsgType.Error())
+
+	// correct
+	msg = &Ping{}
+	buffer, err = Pack(msg)
+	assert.NoError(err)
+	b := bytes.NewBuffer(nil)
+	b.WriteByte(TypePing)
+	binary.Write(b, binary.BigEndian, int64(2))
+	b.WriteString("{}")
+	assert.True(bytes.Equal(b.Bytes(), buffer))
+}
+
+func TestUnPack(t *testing.T) {
+	assert := assert.New(t)
+
+	var (
+		msg Message
+		err error
+	)
+
+	// error message type
+	msg, err = UnPack('-', []byte("{}"))
+	assert.Error(err)
+
+	// correct
+	msg, err = UnPack(TypePong, []byte("{}"))
+	assert.NoError(err)
+	assert.Equal(getTypeFn(msg), getTypeFn((*Pong)(nil)))
+}
+
+func TestUnPackInto(t *testing.T) {
+	assert := assert.New(t)
+
+	var err error
+
+	// correct type
+	pongMsg := &Pong{}
+	err = UnPackInto([]byte("{}"), pongMsg)
+	assert.NoError(err)
+
+	// wrong type
+	loginMsg := &Login{}
+	err = UnPackInto([]byte(`{"version": 123}`), loginMsg)
+	assert.Error(err)
+}

+ 88 - 0
models/msg/process.go

@@ -0,0 +1,88 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package msg
+
+import (
+	"encoding/binary"
+	"fmt"
+	"io"
+)
+
+var (
+	MaxMsgLength int64 = 10240
+)
+
+func readMsg(c io.Reader) (typeByte byte, buffer []byte, err error) {
+	buffer = make([]byte, 1)
+	_, err = c.Read(buffer)
+	if err != nil {
+		return
+	}
+	typeByte = buffer[0]
+	if _, ok := TypeMap[typeByte]; !ok {
+		err = fmt.Errorf("Message type error")
+		return
+	}
+
+	var length int64
+	err = binary.Read(c, binary.BigEndian, &length)
+	if err != nil {
+		return
+	}
+	if length > MaxMsgLength {
+		err = fmt.Errorf("Message length exceed the limit")
+		return
+	}
+
+	buffer = make([]byte, length)
+	n, err := io.ReadFull(c, buffer)
+	if err != nil {
+		return
+	}
+
+	if int64(n) != length {
+		err = fmt.Errorf("Message format error")
+	}
+	return
+}
+
+func ReadMsg(c io.Reader) (msg Message, err error) {
+	typeByte, buffer, err := readMsg(c)
+	if err != nil {
+		return
+	}
+	return UnPack(typeByte, buffer)
+}
+
+func ReadMsgInto(c io.Reader, msg Message) (err error) {
+	_, buffer, err := readMsg(c)
+	if err != nil {
+		return
+	}
+	return UnPackInto(buffer, msg)
+}
+
+func WriteMsg(c io.Writer, msg interface{}) (err error) {
+	buffer, err := Pack(msg)
+	if err != nil {
+		return
+	}
+
+	if _, err = c.Write(buffer); err != nil {
+		return
+	}
+
+	return nil
+}

+ 97 - 0
models/msg/process_test.go

@@ -0,0 +1,97 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package msg
+
+import (
+	"bytes"
+	"encoding/binary"
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestProcess(t *testing.T) {
+	assert := assert.New(t)
+
+	var (
+		msg    Message
+		resMsg Message
+		err    error
+	)
+	// empty struct
+	msg = &Ping{}
+	buffer := bytes.NewBuffer(nil)
+	err = WriteMsg(buffer, msg)
+	assert.NoError(err)
+
+	resMsg, err = ReadMsg(buffer)
+	assert.NoError(err)
+	assert.Equal(reflect.TypeOf(resMsg).Elem(), TypeMap[TypePing])
+
+	// normal message
+	msg = &StartWorkConn{
+		ProxyName: "test",
+	}
+	buffer = bytes.NewBuffer(nil)
+	err = WriteMsg(buffer, msg)
+	assert.NoError(err)
+
+	resMsg, err = ReadMsg(buffer)
+	assert.NoError(err)
+	assert.Equal(reflect.TypeOf(resMsg).Elem(), TypeMap[TypeStartWorkConn])
+
+	startWorkConnMsg, ok := resMsg.(*StartWorkConn)
+	assert.True(ok)
+	assert.Equal("test", startWorkConnMsg.ProxyName)
+
+	// ReadMsgInto correct
+	msg = &Pong{}
+	buffer = bytes.NewBuffer(nil)
+	err = WriteMsg(buffer, msg)
+	assert.NoError(err)
+
+	err = ReadMsgInto(buffer, msg)
+	assert.NoError(err)
+
+	// ReadMsgInto error type
+	content := []byte(`{"run_id": 123}`)
+	buffer = bytes.NewBuffer(nil)
+	buffer.WriteByte(TypeNewWorkConn)
+	binary.Write(buffer, binary.BigEndian, int64(len(content)))
+	buffer.Write(content)
+
+	resMsg = &NewWorkConn{}
+	err = ReadMsgInto(buffer, resMsg)
+	assert.Error(err)
+
+	// message format error
+	buffer = bytes.NewBuffer([]byte("1234"))
+
+	resMsg = &NewProxyResp{}
+	err = ReadMsgInto(buffer, resMsg)
+	assert.Error(err)
+
+	// MaxLength, real message length is 2
+	MaxMsgLength = 1
+	msg = &Ping{}
+	buffer = bytes.NewBuffer(nil)
+	err = WriteMsg(buffer, msg)
+	assert.NoError(err)
+
+	_, err = ReadMsg(buffer)
+	assert.Error(err)
+	return
+}

+ 38 - 0
models/proto/tcp/process.go

@@ -0,0 +1,38 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tcp
+
+import (
+	"io"
+	"sync"
+)
+
+// Join two io.ReadWriteCloser and do some operations.
+func Join(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser) (inCount int64, outCount int64) {
+	var wait sync.WaitGroup
+	pipe := func(to io.ReadWriteCloser, from io.ReadWriteCloser, count *int64) {
+		defer to.Close()
+		defer from.Close()
+		defer wait.Done()
+
+		*count, _ = io.Copy(to, from)
+	}
+
+	wait.Add(2)
+	go pipe(c1, c2, &inCount)
+	go pipe(c2, c1, &outCount)
+	wait.Wait()
+	return
+}

+ 129 - 0
models/proto/tcp/process_test.go

@@ -0,0 +1,129 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tcp
+
+import (
+	"io"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/fatedier/frp/utils/crypto"
+)
+
+func TestJoin(t *testing.T) {
+	assert := assert.New(t)
+
+	var (
+		n   int
+		err error
+	)
+	text1 := "A document that gives tips for writing clear, idiomatic Go code. A must read for any new Go programmer. It augments the tour and the language specification, both of which should be read first."
+	text2 := "A document that specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine."
+
+	// Forward bytes directly.
+	pr, pw := io.Pipe()
+	pr2, pw2 := io.Pipe()
+	pr3, pw3 := io.Pipe()
+	pr4, pw4 := io.Pipe()
+
+	conn1 := WrapReadWriteCloser(pr, pw2)
+	conn2 := WrapReadWriteCloser(pr2, pw)
+	conn3 := WrapReadWriteCloser(pr3, pw4)
+	conn4 := WrapReadWriteCloser(pr4, pw3)
+
+	go func() {
+		Join(conn2, conn3)
+	}()
+
+	buf1 := make([]byte, 1024)
+	buf2 := make([]byte, 1024)
+
+	conn1.Write([]byte(text1))
+	conn4.Write([]byte(text2))
+
+	n, err = conn4.Read(buf1)
+	assert.NoError(err)
+	assert.Equal(text1, string(buf1[:n]))
+
+	n, err = conn1.Read(buf2)
+	assert.NoError(err)
+	assert.Equal(text2, string(buf2[:n]))
+
+	conn1.Close()
+	conn2.Close()
+	conn3.Close()
+	conn4.Close()
+}
+
+func TestJoinEncrypt(t *testing.T) {
+	assert := assert.New(t)
+
+	var (
+		n   int
+		err error
+	)
+	text1 := "1234567890"
+	text2 := "abcdefghij"
+	key := "authkey"
+
+	// Forward enrypted bytes.
+	pr, pw := io.Pipe()
+	pr2, pw2 := io.Pipe()
+	pr3, pw3 := io.Pipe()
+	pr4, pw4 := io.Pipe()
+	pr5, pw5 := io.Pipe()
+	pr6, pw6 := io.Pipe()
+
+	conn1 := WrapReadWriteCloser(pr, pw2)
+	conn2 := WrapReadWriteCloser(pr2, pw)
+	conn3 := WrapReadWriteCloser(pr3, pw4)
+	conn4 := WrapReadWriteCloser(pr4, pw3)
+	conn5 := WrapReadWriteCloser(pr5, pw6)
+	conn6 := WrapReadWriteCloser(pr6, pw5)
+
+	r1, err := crypto.NewReader(conn3, []byte(key))
+	assert.NoError(err)
+	w1, err := crypto.NewWriter(conn3, []byte(key))
+	assert.NoError(err)
+
+	r2, err := crypto.NewReader(conn4, []byte(key))
+	assert.NoError(err)
+	w2, err := crypto.NewWriter(conn4, []byte(key))
+	assert.NoError(err)
+
+	go Join(conn2, WrapReadWriteCloser(r1, w1))
+	go Join(WrapReadWriteCloser(r2, w2), conn5)
+
+	buf := make([]byte, 128)
+
+	conn1.Write([]byte(text1))
+	conn6.Write([]byte(text2))
+
+	n, err = conn6.Read(buf)
+	assert.NoError(err)
+	assert.Equal(text1, string(buf[:n]))
+
+	n, err = conn1.Read(buf)
+	assert.NoError(err)
+	assert.Equal(text2, string(buf[:n]))
+
+	conn1.Close()
+	conn2.Close()
+	conn3.Close()
+	conn4.Close()
+	conn5.Close()
+	conn6.Close()
+}

+ 89 - 0
models/proto/tcp/tcp.go

@@ -0,0 +1,89 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tcp
+
+import (
+	"io"
+
+	"github.com/golang/snappy"
+
+	"github.com/fatedier/frp/utils/crypto"
+)
+
+func WithEncryption(rwc io.ReadWriteCloser, key []byte) (res io.ReadWriteCloser, err error) {
+	var (
+		r io.Reader
+		w io.Writer
+	)
+	r, err = crypto.NewReader(rwc, key)
+	if err != nil {
+		return
+	}
+	w, err = crypto.NewWriter(rwc, key)
+	if err != nil {
+		return
+	}
+	res = WrapReadWriteCloser(r, w)
+	return
+}
+
+func WithCompression(rwc io.ReadWriteCloser) (res io.ReadWriteCloser) {
+	var (
+		r io.Reader
+		w io.Writer
+	)
+	r = snappy.NewReader(rwc)
+	w = snappy.NewWriter(rwc)
+	res = WrapReadWriteCloser(r, w)
+	return
+}
+
+func WrapReadWriteCloser(r io.Reader, w io.Writer) io.ReadWriteCloser {
+	return &ReadWriteCloser{
+		r: r,
+		w: w,
+	}
+}
+
+type ReadWriteCloser struct {
+	r io.Reader
+	w io.Writer
+}
+
+func (rwc *ReadWriteCloser) Read(p []byte) (n int, err error) {
+	return rwc.r.Read(p)
+}
+
+func (rwc *ReadWriteCloser) Write(p []byte) (n int, err error) {
+	return rwc.w.Write(p)
+}
+
+func (rwc *ReadWriteCloser) Close() (errRet error) {
+	var err error
+	if rc, ok := rwc.r.(io.Closer); ok {
+		err = rc.Close()
+		if err != nil {
+			errRet = err
+		}
+	}
+
+	if wc, ok := rwc.w.(io.Closer); ok {
+		err = wc.Close()
+		if err != nil {
+			errRet = err
+		}
+	}
+	return
+}

+ 100 - 0
models/proto/tcp/tcp_test.go

@@ -0,0 +1,100 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tcp
+
+import (
+	"io"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestWithCompression(t *testing.T) {
+	assert := assert.New(t)
+
+	// Forward compression bytes.
+	pr, pw := io.Pipe()
+	pr2, pw2 := io.Pipe()
+
+	conn1 := WrapReadWriteCloser(pr, pw2)
+	conn2 := WrapReadWriteCloser(pr2, pw)
+
+	compressionStream1 := WithCompression(conn1)
+	compressionStream2 := WithCompression(conn2)
+
+	var (
+		n   int
+		err error
+	)
+
+	text := "1234567812345678"
+	buf := make([]byte, 256)
+
+	go compressionStream1.Write([]byte(text))
+	n, err = compressionStream2.Read(buf)
+	assert.NoError(err)
+	assert.Equal(text, string(buf[:n]))
+
+	go compressionStream2.Write([]byte(text))
+	n, err = compressionStream1.Read(buf)
+	assert.NoError(err)
+	assert.Equal(text, string(buf[:n]))
+}
+
+func TestWithEncryption(t *testing.T) {
+	assert := assert.New(t)
+	var (
+		n   int
+		err error
+	)
+	text1 := "Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and networked machines, while its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language."
+	text2 := "An interactive introduction to Go in three sections. The first section covers basic syntax and data structures; the second discusses methods and interfaces; and the third introduces Go's concurrency primitives. Each section concludes with a few exercises so you can practice what you've learned. You can take the tour online or install it locally with"
+	key := "authkey"
+
+	// Forward enrypted bytes.
+	pr, pw := io.Pipe()
+	pr2, pw2 := io.Pipe()
+	pr3, pw3 := io.Pipe()
+	pr4, pw4 := io.Pipe()
+	pr5, pw5 := io.Pipe()
+	pr6, pw6 := io.Pipe()
+
+	conn1 := WrapReadWriteCloser(pr, pw2)
+	conn2 := WrapReadWriteCloser(pr2, pw)
+	conn3 := WrapReadWriteCloser(pr3, pw4)
+	conn4 := WrapReadWriteCloser(pr4, pw3)
+	conn5 := WrapReadWriteCloser(pr5, pw6)
+	conn6 := WrapReadWriteCloser(pr6, pw5)
+
+	encryptStream1, err := WithEncryption(conn3, []byte(key))
+	assert.NoError(err)
+	encryptStream2, err := WithEncryption(conn4, []byte(key))
+	assert.NoError(err)
+
+	go Join(conn2, encryptStream1)
+	go Join(encryptStream2, conn5)
+
+	buf := make([]byte, 1024)
+
+	conn1.Write([]byte(text1))
+	conn6.Write([]byte(text2))
+
+	n, err = conn6.Read(buf)
+	assert.NoError(err)
+	assert.Equal(text1, string(buf[:n]))
+
+	n, err = conn1.Read(buf)
+	assert.NoError(err)
+}

+ 1 - 1
src/models/msg/udp.go → models/proto/udp/udp.go

@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package msg
+package udp
 
 import (
 	"encoding/base64"

+ 1 - 1
src/models/msg/udp_test.go → models/proto/udp/udp_test.go

@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package msg
+package udp
 
 import (
 	"net"

+ 332 - 0
server/control.go

@@ -0,0 +1,332 @@
+package server
+
+import (
+	"fmt"
+	"io"
+	"sync"
+	"time"
+
+	"github.com/fatedier/frp/models/config"
+	"github.com/fatedier/frp/models/consts"
+	"github.com/fatedier/frp/models/msg"
+	"github.com/fatedier/frp/utils/errors"
+	"github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/shutdown"
+	"github.com/fatedier/frp/utils/version"
+)
+
+type Control struct {
+	// frps service
+	svr *Service
+
+	// login message
+	loginMsg *msg.Login
+
+	// control connection
+	conn net.Conn
+
+	// put a message in this channel to send it over control connection to client
+	sendCh chan (msg.Message)
+
+	// read from this channel to get the next message sent by client
+	readCh chan (msg.Message)
+
+	// work connections
+	workConnCh chan net.Conn
+
+	// proxies in one client
+	proxies []Proxy
+
+	// pool count
+	poolCount int
+
+	// last time got the Ping message
+	lastPing time.Time
+
+	// A new run id will be generated when a new client login.
+	// If run id got from login message has same run id, it means it's the same client, so we can
+	// replace old controller instantly.
+	runId string
+
+	// control status
+	status string
+
+	readerShutdown  *shutdown.Shutdown
+	writerShutdown  *shutdown.Shutdown
+	managerShutdown *shutdown.Shutdown
+	allShutdown     *shutdown.Shutdown
+
+	mu sync.RWMutex
+}
+
+func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
+	return &Control{
+		svr:             svr,
+		conn:            ctlConn,
+		loginMsg:        loginMsg,
+		sendCh:          make(chan msg.Message, 10),
+		readCh:          make(chan msg.Message, 10),
+		workConnCh:      make(chan net.Conn, loginMsg.PoolCount+10),
+		proxies:         make([]Proxy, 0),
+		poolCount:       loginMsg.PoolCount,
+		lastPing:        time.Now(),
+		runId:           loginMsg.RunId,
+		status:          consts.Working,
+		readerShutdown:  shutdown.New(),
+		writerShutdown:  shutdown.New(),
+		managerShutdown: shutdown.New(),
+		allShutdown:     shutdown.New(),
+	}
+}
+
+// Start send a login success message to client and start working.
+func (ctl *Control) Start() {
+	go ctl.writer()
+
+	ctl.sendCh <- &msg.LoginResp{
+		Version: version.Full(),
+		RunId:   ctl.runId,
+		Error:   "",
+	}
+
+	for i := 0; i < ctl.poolCount; i++ {
+		ctl.sendCh <- &msg.ReqWorkConn{}
+	}
+
+	go ctl.manager()
+	go ctl.reader()
+	go ctl.stoper()
+}
+
+func (ctl *Control) RegisterWorkConn(conn net.Conn) {
+	defer func() {
+		if err := recover(); err != nil {
+			ctl.conn.Error("panic error: %v", err)
+		}
+	}()
+
+	select {
+	case ctl.workConnCh <- conn:
+		ctl.conn.Debug("new work connection registered.")
+	default:
+		ctl.conn.Debug("work connection pool is full, discarding.")
+		conn.Close()
+	}
+}
+
+// When frps get one user connection, we get one work connection from the pool and return it.
+// If no workConn available in the pool, send message to frpc to get one or more
+// and wait until it is available.
+// return an error if wait timeout
+func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
+	defer func() {
+		if err := recover(); err != nil {
+			ctl.conn.Error("panic error: %v", err)
+		}
+	}()
+
+	var ok bool
+	// get a work connection from the pool
+	select {
+	case workConn, ok = <-ctl.workConnCh:
+		if !ok {
+			err = fmt.Errorf("no work connections available, control is closing")
+			return
+		}
+		ctl.conn.Debug("get work connection from pool")
+	default:
+		// no work connections available in the poll, send message to frpc to get more
+		err = errors.PanicToError(func() {
+			ctl.sendCh <- &msg.ReqWorkConn{}
+		})
+		if err != nil {
+			ctl.conn.Error("%v", err)
+			return
+		}
+
+		select {
+		case workConn, ok = <-ctl.workConnCh:
+			if !ok {
+				err = fmt.Errorf("no work connections available, control is closing")
+				ctl.conn.Warn("%v", err)
+				return
+			}
+
+		case <-time.After(time.Duration(config.ServerCommonCfg.UserConnTimeout) * time.Second):
+			err = fmt.Errorf("timeout trying to get work connection")
+			ctl.conn.Warn("%v", err)
+			return
+		}
+	}
+
+	// When we get a work connection from pool, replace it with a new one.
+	errors.PanicToError(func() {
+		ctl.sendCh <- &msg.ReqWorkConn{}
+	})
+	return
+}
+
+func (ctl *Control) Replaced(newCtl *Control) {
+	ctl.conn.Info("Replaced by client [%s]", newCtl.runId)
+	ctl.runId = ""
+	ctl.allShutdown.Start()
+}
+
+func (ctl *Control) writer() {
+	defer func() {
+		if err := recover(); err != nil {
+			ctl.conn.Error("panic error: %v", err)
+		}
+	}()
+
+	defer ctl.allShutdown.Start()
+	defer ctl.writerShutdown.Done()
+
+	for {
+		if m, ok := <-ctl.sendCh; !ok {
+			ctl.conn.Info("control writer is closing")
+			return
+		} else {
+			if err := msg.WriteMsg(ctl.conn, m); err != nil {
+				ctl.conn.Warn("write message to control connection error: %v", err)
+				return
+			}
+		}
+	}
+}
+
+func (ctl *Control) reader() {
+	defer func() {
+		if err := recover(); err != nil {
+			ctl.conn.Error("panic error: %v", err)
+		}
+	}()
+
+	defer ctl.allShutdown.Start()
+	defer ctl.readerShutdown.Done()
+
+	for {
+		if m, err := msg.ReadMsg(ctl.conn); err != nil {
+			if err == io.EOF {
+				ctl.conn.Debug("control connection closed")
+				return
+			} else {
+				ctl.conn.Warn("read error: %v", err)
+				return
+			}
+		} else {
+			ctl.readCh <- m
+		}
+	}
+}
+
+func (ctl *Control) stoper() {
+	defer func() {
+		if err := recover(); err != nil {
+			ctl.conn.Error("panic error: %v", err)
+		}
+	}()
+
+	ctl.allShutdown.WaitStart()
+
+	close(ctl.readCh)
+	ctl.managerShutdown.WaitDown()
+
+	close(ctl.sendCh)
+	ctl.writerShutdown.WaitDown()
+
+	ctl.conn.Close()
+
+	close(ctl.workConnCh)
+	for workConn := range ctl.workConnCh {
+		workConn.Close()
+	}
+
+	for _, pxy := range ctl.proxies {
+		ctl.svr.DelProxy(pxy.GetName())
+		pxy.Close()
+	}
+
+	ctl.allShutdown.Done()
+	ctl.conn.Info("all shutdown success")
+}
+
+func (ctl *Control) manager() {
+	defer func() {
+		if err := recover(); err != nil {
+			ctl.conn.Error("panic error: %v", err)
+		}
+	}()
+
+	defer ctl.allShutdown.Start()
+	defer ctl.managerShutdown.Done()
+
+	heartbeat := time.NewTicker(time.Second)
+	defer heartbeat.Stop()
+
+	for {
+		select {
+		case <-heartbeat.C:
+			if time.Since(ctl.lastPing) > time.Duration(config.ServerCommonCfg.HeartBeatTimeout)*time.Second {
+				ctl.conn.Warn("heartbeat timeout")
+				ctl.allShutdown.Start()
+			}
+		case rawMsg, ok := <-ctl.readCh:
+			if !ok {
+				return
+			}
+
+			switch m := rawMsg.(type) {
+			case *msg.NewProxy:
+				// register proxy in this control
+				err := ctl.RegisterProxy(m)
+				resp := &msg.NewProxyResp{
+					ProxyName: m.ProxyName,
+				}
+				if err != nil {
+					resp.Error = err.Error()
+					ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err)
+				} else {
+					ctl.conn.Info("new proxy [%s] success", m.ProxyName)
+				}
+				ctl.sendCh <- resp
+			case *msg.Ping:
+				ctl.lastPing = time.Now()
+				ctl.sendCh <- &msg.Pong{}
+			}
+		}
+	}
+}
+
+func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (err error) {
+	var pxyConf config.ProxyConf
+	// Load configures from NewProxy message and check.
+	pxyConf, err = config.NewProxyConf(pxyMsg)
+	if err != nil {
+		return err
+	}
+
+	// NewProxy will return a interface Proxy.
+	// In fact it create different proxies by different proxy type, we just call run() here.
+	pxy, err := NewProxy(ctl, pxyConf)
+	if err != nil {
+		return err
+	}
+
+	err = pxy.Run()
+	if err != nil {
+		return err
+	}
+	defer func() {
+		if err != nil {
+			pxy.Close()
+		}
+	}()
+
+	err = ctl.svr.RegisterProxy(pxyMsg.ProxyName, pxy)
+	if err != nil {
+		return err
+	}
+	ctl.proxies = append(ctl.proxies, pxy)
+	return nil
+}

+ 6 - 5
src/models/server/dashboard.go → server/dashboard.go

@@ -22,7 +22,8 @@ import (
 	"strings"
 	"time"
 
-	"github.com/fatedier/frp/src/assets"
+	"github.com/fatedier/frp/assets"
+	"github.com/fatedier/frp/models/config"
 )
 
 var (
@@ -34,13 +35,13 @@ func RunDashboardServer(addr string, port int64) (err error) {
 	// url router
 	mux := http.NewServeMux()
 	// api, see dashboard_api.go
-	mux.HandleFunc("/api/reload", use(apiReload, basicAuth))
-	mux.HandleFunc("/api/proxies", apiProxies)
+	//mux.HandleFunc("/api/reload", use(apiReload, basicAuth))
+	//mux.HandleFunc("/api/proxies", apiProxies)
 
 	// view, see dashboard_view.go
 	mux.Handle("/favicon.ico", http.FileServer(assets.FileSystem))
 	mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))
-	mux.HandleFunc("/", use(viewDashboard, basicAuth))
+	//mux.HandleFunc("/", use(viewDashboard, basicAuth))
 
 	address := fmt.Sprintf("%s:%d", addr, port)
 	server := &http.Server{
@@ -92,7 +93,7 @@ func basicAuth(h http.HandlerFunc) http.HandlerFunc {
 			return
 		}
 
-		if pair[0] != DashboardUsername || pair[1] != DashboardPassword {
+		if pair[0] != config.ServerCommonCfg.DashboardUser || pair[1] != config.ServerCommonCfg.DashboardPwd {
 			http.Error(w, "Not authorized", 401)
 			return
 		}

+ 2 - 0
src/models/server/dashboard_api.go → server/dashboard_api.go

@@ -14,6 +14,7 @@
 
 package server
 
+/*
 import (
 	"encoding/json"
 	"fmt"
@@ -65,3 +66,4 @@ func apiProxies(w http.ResponseWriter, r *http.Request) {
 	buf, _ = json.Marshal(res)
 	w.Write(buf)
 }
+*/

+ 2 - 0
src/models/server/dashboard_view.go → server/dashboard_view.go

@@ -14,6 +14,7 @@
 
 package server
 
+/*
 import (
 	"html/template"
 	"net/http"
@@ -38,3 +39,4 @@ func viewDashboard(w http.ResponseWriter, r *http.Request) {
 		http.Error(w, "parse template file error", http.StatusInternalServerError)
 	}
 }
+*/

+ 75 - 0
server/manager.go

@@ -0,0 +1,75 @@
+package server
+
+import (
+	"fmt"
+	"sync"
+)
+
+type ControlManager struct {
+	// controls indexed by run id
+	ctlsByRunId map[string]*Control
+
+	mu sync.RWMutex
+}
+
+func NewControlManager() *ControlManager {
+	return &ControlManager{
+		ctlsByRunId: make(map[string]*Control),
+	}
+}
+
+func (cm *ControlManager) Add(runId string, ctl *Control) (oldCtl *Control) {
+	cm.mu.Lock()
+	defer cm.mu.Unlock()
+
+	oldCtl, ok := cm.ctlsByRunId[runId]
+	if ok {
+		oldCtl.Replaced(ctl)
+	}
+	cm.ctlsByRunId[runId] = ctl
+	return
+}
+
+func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
+	cm.mu.RLock()
+	defer cm.mu.RUnlock()
+	ctl, ok = cm.ctlsByRunId[runId]
+	return
+}
+
+type ProxyManager struct {
+	// proxies indexed by proxy name
+	pxys map[string]Proxy
+
+	mu sync.RWMutex
+}
+
+func NewProxyManager() *ProxyManager {
+	return &ProxyManager{
+		pxys: make(map[string]Proxy),
+	}
+}
+
+func (pm *ProxyManager) Add(name string, pxy Proxy) error {
+	pm.mu.Lock()
+	defer pm.mu.Unlock()
+	if _, ok := pm.pxys[name]; ok {
+		return fmt.Errorf("proxy name [%s] is already in use", name)
+	}
+
+	pm.pxys[name] = pxy
+	return nil
+}
+
+func (pm *ProxyManager) Del(name string) {
+	pm.mu.Lock()
+	defer pm.mu.Unlock()
+	delete(pm.pxys, name)
+}
+
+func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
+	pm.mu.RLock()
+	defer pm.mu.RUnlock()
+	pxy, ok = pm.pxys[name]
+	return
+}

+ 219 - 0
server/proxy.go

@@ -0,0 +1,219 @@
+package server
+
+import (
+	"fmt"
+	"io"
+
+	"github.com/fatedier/frp/models/config"
+	"github.com/fatedier/frp/models/msg"
+	"github.com/fatedier/frp/models/proto/tcp"
+	"github.com/fatedier/frp/utils/log"
+	"github.com/fatedier/frp/utils/net"
+)
+
+type Proxy interface {
+	Run() error
+	GetControl() *Control
+	GetName() string
+	GetConf() config.ProxyConf
+	Close()
+	log.Logger
+}
+
+type BaseProxy struct {
+	name      string
+	ctl       *Control
+	listeners []net.Listener
+	log.Logger
+}
+
+func (pxy *BaseProxy) GetName() string {
+	return pxy.name
+}
+
+func (pxy *BaseProxy) GetControl() *Control {
+	return pxy.ctl
+}
+
+func (pxy *BaseProxy) Close() {
+	pxy.Info("proxy closing")
+	for _, l := range pxy.listeners {
+		l.Close()
+	}
+}
+
+func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
+	basePxy := BaseProxy{
+		name:      pxyConf.GetName(),
+		ctl:       ctl,
+		listeners: make([]net.Listener, 0),
+		Logger:    log.NewPrefixLogger(ctl.runId),
+	}
+	switch cfg := pxyConf.(type) {
+	case *config.TcpProxyConf:
+		pxy = &TcpProxy{
+			BaseProxy: basePxy,
+			cfg:       cfg,
+		}
+	case *config.HttpProxyConf:
+		pxy = &HttpProxy{
+			BaseProxy: basePxy,
+			cfg:       cfg,
+		}
+	case *config.HttpsProxyConf:
+		pxy = &HttpsProxy{
+			BaseProxy: basePxy,
+			cfg:       cfg,
+		}
+	case *config.UdpProxyConf:
+		pxy = &UdpProxy{
+			BaseProxy: basePxy,
+			cfg:       cfg,
+		}
+	default:
+		return pxy, fmt.Errorf("proxy type not support")
+	}
+	pxy.AddLogPrefix(pxy.GetName())
+	return
+}
+
+type TcpProxy struct {
+	BaseProxy
+	cfg *config.TcpProxyConf
+}
+
+func (pxy *TcpProxy) Run() error {
+	listener, err := net.ListenTcp(config.ServerCommonCfg.BindAddr, int64(pxy.cfg.RemotePort))
+	if err != nil {
+		return err
+	}
+	pxy.listeners = append(pxy.listeners, listener)
+
+	go func(l net.Listener) {
+		for {
+			// block
+			// if listener is closed, err returned
+			c, err := l.Accept()
+			if err != nil {
+				pxy.Info("listener is closed")
+				return
+			}
+			pxy.Debug("got one user connection [%s]", c.RemoteAddr().String())
+			go HandleUserTcpConnection(pxy, c)
+		}
+	}(listener)
+	return nil
+}
+
+func (pxy *TcpProxy) GetConf() config.ProxyConf {
+	return pxy.cfg
+}
+
+func (pxy *TcpProxy) Close() {
+	pxy.BaseProxy.Close()
+}
+
+type HttpProxy struct {
+	BaseProxy
+	cfg *config.HttpProxyConf
+}
+
+func (pxy *HttpProxy) Run() (err error) {
+	return
+}
+
+func (pxy *HttpProxy) GetConf() config.ProxyConf {
+	return pxy.cfg
+}
+
+func (pxy *HttpProxy) Close() {
+	pxy.BaseProxy.Close()
+}
+
+type HttpsProxy struct {
+	BaseProxy
+	cfg *config.HttpsProxyConf
+}
+
+func (pxy *HttpsProxy) Run() (err error) {
+	return
+}
+
+func (pxy *HttpsProxy) GetConf() config.ProxyConf {
+	return pxy.cfg
+}
+
+func (pxy *HttpsProxy) Close() {
+	pxy.BaseProxy.Close()
+}
+
+type UdpProxy struct {
+	BaseProxy
+	cfg *config.UdpProxyConf
+}
+
+func (pxy *UdpProxy) Run() (err error) {
+	return
+}
+
+func (pxy *UdpProxy) GetConf() config.ProxyConf {
+	return pxy.cfg
+}
+
+func (pxy *UdpProxy) Close() {
+	pxy.BaseProxy.Close()
+}
+
+// HandleUserTcpConnection is used for incoming tcp user connections.
+func HandleUserTcpConnection(pxy Proxy, userConn net.Conn) {
+	defer userConn.Close()
+	ctl := pxy.GetControl()
+	var (
+		workConn net.Conn
+		err      error
+	)
+	// try all connections from the pool
+	for i := 0; i < ctl.poolCount+1; i++ {
+		if workConn, err = ctl.GetWorkConn(); err != nil {
+			pxy.Warn("failed to get work connection: %v", err)
+			return
+		}
+		defer workConn.Close()
+		pxy.Info("get one new work connection: %s", workConn.RemoteAddr().String())
+		workConn.AddLogPrefix(pxy.GetName())
+
+		err := msg.WriteMsg(workConn, &msg.StartWorkConn{
+			ProxyName: pxy.GetName(),
+		})
+		if err != nil {
+			workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
+			workConn.Close()
+		} else {
+			break
+		}
+	}
+
+	if err != nil {
+		pxy.Error("try to get work connection failed in the end")
+		return
+	}
+
+	var (
+		local  io.ReadWriteCloser
+		remote io.ReadWriteCloser
+	)
+	local = workConn
+	remote = userConn
+	cfg := pxy.GetConf().GetBaseInfo()
+	if cfg.UseEncryption {
+		local, err = tcp.WithEncryption(local, []byte(config.ServerCommonCfg.PrivilegeToken))
+		if err != nil {
+			pxy.Error("create encryption stream error: %v", err)
+			return
+		}
+	}
+	if cfg.UseCompression {
+		local = tcp.WithCompression(local)
+	}
+	tcp.Join(local, remote)
+}

+ 196 - 0
server/service.go

@@ -0,0 +1,196 @@
+package server
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/fatedier/frp/assets"
+	"github.com/fatedier/frp/models/config"
+	"github.com/fatedier/frp/models/msg"
+	"github.com/fatedier/frp/utils/log"
+	"github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/util"
+	"github.com/fatedier/frp/utils/version"
+	"github.com/fatedier/frp/utils/vhost"
+)
+
+// Server service.
+type Service struct {
+	// Accept connections from client.
+	listener net.Listener
+
+	// For http proxies, route requests to different clients by hostname and other infomation.
+	VhostHttpMuxer *vhost.HttpMuxer
+
+	// For https proxies, route requests to different clients by hostname and other infomation.
+	VhostHttpsMuxer *vhost.HttpsMuxer
+
+	// Manage all controllers.
+	ctlManager *ControlManager
+
+	// Manage all proxies.
+	pxyManager *ProxyManager
+}
+
+func NewService() (svr *Service, err error) {
+	svr = &Service{
+		ctlManager: NewControlManager(),
+		pxyManager: NewProxyManager(),
+	}
+
+	// Init assets.
+	err = assets.Load(config.ServerCommonCfg.AssetsDir)
+	if err != nil {
+		err = fmt.Errorf("Load assets error: %v", err)
+		return
+	}
+
+	// Listen for accepting connections from client.
+	svr.listener, err = net.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
+	if err != nil {
+		err = fmt.Errorf("Create server listener error, %v", err)
+		return
+	}
+
+	// Create http vhost muxer.
+	if config.ServerCommonCfg.VhostHttpPort != 0 {
+		var l net.Listener
+		l, err = net.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpPort)
+		if err != nil {
+			err = fmt.Errorf("Create vhost http listener error, %v", err)
+			return
+		}
+		svr.VhostHttpMuxer, err = vhost.NewHttpMuxer(l, 30*time.Second)
+		if err != nil {
+			err = fmt.Errorf("Create vhost httpMuxer error, %v", err)
+			return
+		}
+	}
+
+	// Create https vhost muxer.
+	if config.ServerCommonCfg.VhostHttpsPort != 0 {
+		var l net.Listener
+		l, err = net.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpsPort)
+		if err != nil {
+			err = fmt.Errorf("Create vhost https listener error, %v", err)
+			return
+		}
+		svr.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(l, 30*time.Second)
+		if err != nil {
+			err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
+			return
+		}
+	}
+
+	// Create dashboard web server.
+	if config.ServerCommonCfg.DashboardPort != 0 {
+		err = RunDashboardServer(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
+		if err != nil {
+			err = fmt.Errorf("Create dashboard web server error, %v", err)
+			return
+		}
+	}
+	return
+}
+
+func (svr *Service) Run() {
+	// Listen for incoming connections from client.
+	for {
+		c, err := svr.listener.Accept()
+		if err != nil {
+			log.Warn("Listener for incoming connections from client closed")
+			return
+		}
+
+		// Start a new goroutine for dealing connections.
+		go func(frpConn net.Conn) {
+			var rawMsg msg.Message
+			if rawMsg, err = msg.ReadMsg(frpConn); err != nil {
+				log.Warn("Failed to read message: %v", err)
+				frpConn.Close()
+				return
+			}
+
+			switch m := rawMsg.(type) {
+			case *msg.Login:
+				err = svr.RegisterControl(frpConn, m)
+				// If login failed, send error message there.
+				// Otherwise send success message in control's work goroutine.
+				if err != nil {
+					frpConn.Warn("%v", err)
+					msg.WriteMsg(frpConn, &msg.LoginResp{
+						Version: version.Full(),
+						Error:   err.Error(),
+					})
+					frpConn.Close()
+				}
+			case *msg.NewWorkConn:
+				svr.RegisterWorkConn(frpConn, m)
+			default:
+				log.Warn("Error message type for the new connection [%s]", frpConn.RemoteAddr().String())
+				frpConn.Close()
+			}
+		}(c)
+	}
+}
+
+func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err error) {
+	ctlConn.Info("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]",
+		ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
+
+	// Check client version.
+	if ok, msg := version.Compat(loginMsg.Version); !ok {
+		err = fmt.Errorf("%s", msg)
+		return
+	}
+
+	// Check auth.
+	nowTime := time.Now().Unix()
+	if config.ServerCommonCfg.AuthTimeout != 0 && nowTime-loginMsg.Timestamp > config.ServerCommonCfg.AuthTimeout {
+		err = fmt.Errorf("authorization timeout")
+		return
+	}
+	if util.GetAuthKey(config.ServerCommonCfg.PrivilegeToken, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
+		err = fmt.Errorf("authorization failed")
+		return
+	}
+
+	// If client's RunId is empty, it's a new client, we just create a new controller.
+	// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
+	if loginMsg.RunId == "" {
+		loginMsg.RunId, err = util.RandId()
+		if err != nil {
+			return
+		}
+	}
+
+	ctl := NewControl(svr, ctlConn, loginMsg)
+
+	if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
+		oldCtl.allShutdown.WaitDown()
+	}
+
+	ctlConn.AddLogPrefix(loginMsg.RunId)
+	ctl.Start()
+	return
+}
+
+// RegisterWorkConn register a new work connection to control and proxies need it.
+func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) {
+	ctl, exist := svr.ctlManager.GetById(newMsg.RunId)
+	if !exist {
+		workConn.Warn("No client control found for run id [%s]", newMsg.RunId)
+		return
+	}
+	ctl.RegisterWorkConn(workConn)
+	return
+}
+
+func (svr *Service) RegisterProxy(name string, pxy Proxy) error {
+	err := svr.pxyManager.Add(name, pxy)
+	return err
+}
+
+func (svr *Service) DelProxy(name string) {
+	svr.pxyManager.Del(name)
+}

File diff suppressed because it is too large
+ 0 - 7
src/assets/statik/statik.go


+ 0 - 231
src/cmd/frpc/control.go

@@ -1,231 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
-	"encoding/json"
-	"fmt"
-	"io"
-	"sync"
-	"time"
-
-	"github.com/fatedier/frp/src/models/client"
-	"github.com/fatedier/frp/src/models/consts"
-	"github.com/fatedier/frp/src/models/msg"
-	"github.com/fatedier/frp/src/utils/conn"
-	"github.com/fatedier/frp/src/utils/log"
-	"github.com/fatedier/frp/src/utils/pcrypto"
-)
-
-func ControlProcess(cli *client.ProxyClient, wait *sync.WaitGroup) {
-	defer wait.Done()
-
-	msgSendChan := make(chan interface{}, 1024)
-
-	c, err := loginToServer(cli)
-	if err != nil {
-		log.Error("ProxyName [%s], connect to server failed!", cli.Name)
-		return
-	}
-	defer c.Close()
-
-	go heartbeatSender(c, msgSendChan)
-
-	go msgSender(cli, c, msgSendChan)
-	msgReader(cli, c, msgSendChan)
-
-	close(msgSendChan)
-}
-
-// loop for reading messages from frpc after control connection is established
-func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) error {
-	// for heartbeat
-	var heartbeatTimeout bool = false
-	timer := time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, func() {
-		heartbeatTimeout = true
-		if c != nil {
-			c.Close()
-		}
-		if cli != nil {
-			// if it's not udp type, nothing will happen
-			cli.CloseUdpTunnel()
-			cli.SetCloseFlag(true)
-		}
-		log.Error("ProxyName [%s], heartbeatRes from frps timeout", cli.Name)
-	})
-	defer timer.Stop()
-
-	for {
-		buf, err := c.ReadLine()
-		if err == io.EOF || c.IsClosed() {
-			timer.Stop()
-			c.Close()
-			cli.SetCloseFlag(true)
-			log.Warn("ProxyName [%s], frps close this control conn!", cli.Name)
-			var delayTime time.Duration = 1
-
-			// loop until reconnect to frps
-			for {
-				log.Info("ProxyName [%s], try to reconnect to frps [%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort)
-				c, err = loginToServer(cli)
-				if err == nil {
-					close(msgSendChan)
-					msgSendChan = make(chan interface{}, 1024)
-					go heartbeatSender(c, msgSendChan)
-					go msgSender(cli, c, msgSendChan)
-					cli.SetCloseFlag(false)
-					break
-				}
-
-				if delayTime < 30 {
-					delayTime = delayTime * 2
-				} else {
-					delayTime = 30
-				}
-				time.Sleep(delayTime * time.Second)
-			}
-			continue
-		} else if err != nil {
-			log.Warn("ProxyName [%s], read from frps error: %v", cli.Name, err)
-			continue
-		}
-
-		ctlRes := &msg.ControlRes{}
-		if err := json.Unmarshal([]byte(buf), &ctlRes); err != nil {
-			log.Warn("ProxyName [%s], parse msg from frps error: %v : %s", cli.Name, err, buf)
-			continue
-		}
-
-		switch ctlRes.Type {
-		case consts.HeartbeatRes:
-			log.Debug("ProxyName [%s], receive heartbeat response", cli.Name)
-			timer.Reset(time.Duration(client.HeartBeatTimeout) * time.Second)
-		case consts.NoticeUserConn:
-			log.Debug("ProxyName [%s], new user connection", cli.Name)
-			// join local and remote connections, async
-			go cli.StartTunnel(client.ServerAddr, client.ServerPort)
-		default:
-			log.Warn("ProxyName [%s}, unsupport msgType [%d]", cli.Name, ctlRes.Type)
-		}
-	}
-	return nil
-}
-
-// loop for sending messages from channel to frps
-func msgSender(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) {
-	for {
-		msg, ok := <-msgSendChan
-		if !ok {
-			break
-		}
-
-		buf, _ := json.Marshal(msg)
-		err := c.WriteString(string(buf) + "\n")
-		if err != nil {
-			log.Warn("ProxyName [%s], write to server error, proxy exit", cli.Name)
-			c.Close()
-			break
-		}
-	}
-}
-
-func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
-	if client.HttpProxy == "" {
-		c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", client.ServerAddr, client.ServerPort))
-	} else {
-		c, err = conn.ConnectServerByHttpProxy(client.HttpProxy, fmt.Sprintf("%s:%d", client.ServerAddr, client.ServerPort))
-	}
-	if err != nil {
-		log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", cli.Name, client.ServerAddr, client.ServerPort, err)
-		return
-	}
-
-	nowTime := time.Now().Unix()
-	req := &msg.ControlReq{
-		Type:              consts.NewCtlConn,
-		ProxyName:         cli.Name,
-		UseEncryption:     cli.UseEncryption,
-		UseGzip:           cli.UseGzip,
-		PrivilegeMode:     cli.PrivilegeMode,
-		ProxyType:         cli.Type,
-		PoolCount:         cli.PoolCount,
-		HostHeaderRewrite: cli.HostHeaderRewrite,
-		HttpUserName:      cli.HttpUserName,
-		HttpPassWord:      cli.HttpPassWord,
-		SubDomain:         cli.SubDomain,
-		Timestamp:         nowTime,
-	}
-	if cli.PrivilegeMode {
-		privilegeKey := pcrypto.GetAuthKey(cli.Name + client.PrivilegeToken + fmt.Sprintf("%d", nowTime))
-		req.RemotePort = cli.RemotePort
-		req.CustomDomains = cli.CustomDomains
-		req.Locations = cli.Locations
-		req.PrivilegeKey = privilegeKey
-	} else {
-		authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
-		req.AuthKey = authKey
-	}
-
-	buf, _ := json.Marshal(req)
-	err = c.WriteString(string(buf) + "\n")
-	if err != nil {
-		log.Error("ProxyName [%s], write to server error, %v", cli.Name, err)
-		return
-	}
-
-	res, err := c.ReadLine()
-	if err != nil {
-		log.Error("ProxyName [%s], read from server error, %v", cli.Name, err)
-		return
-	}
-	log.Debug("ProxyName [%s], read [%s]", cli.Name, res)
-
-	ctlRes := &msg.ControlRes{}
-	if err = json.Unmarshal([]byte(res), &ctlRes); err != nil {
-		log.Error("ProxyName [%s], format server response error, %v", cli.Name, err)
-		return
-	}
-
-	if ctlRes.Code != 0 {
-		log.Error("ProxyName [%s], start proxy error, %s", cli.Name, ctlRes.Msg)
-		return c, fmt.Errorf("%s", ctlRes.Msg)
-	}
-
-	log.Info("ProxyName [%s], connect to server [%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
-
-	if cli.Type == "udp" {
-		// we only need one udp work connection
-		// all udp messages will be forwarded throngh this connection
-		go cli.StartUdpTunnelOnce(client.ServerAddr, client.ServerPort)
-	}
-	return
-}
-
-func heartbeatSender(c *conn.Conn, msgSendChan chan interface{}) {
-	heartbeatReq := &msg.ControlReq{
-		Type: consts.HeartbeatReq,
-	}
-	log.Info("Start to send heartbeat to frps")
-	for {
-		time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second)
-		if c != nil && !c.IsClosed() {
-			log.Debug("Send heartbeat to server")
-			msgSendChan <- heartbeatReq
-		} else {
-			break
-		}
-	}
-	log.Info("Heartbeat goroutine exit")
-}

+ 0 - 365
src/cmd/frps/control.go

@@ -1,365 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
-	"encoding/json"
-	"fmt"
-	"io"
-	"strings"
-	"time"
-
-	"github.com/fatedier/frp/src/models/consts"
-	"github.com/fatedier/frp/src/models/metric"
-	"github.com/fatedier/frp/src/models/msg"
-	"github.com/fatedier/frp/src/models/server"
-	"github.com/fatedier/frp/src/utils/conn"
-	"github.com/fatedier/frp/src/utils/log"
-	"github.com/fatedier/frp/src/utils/pcrypto"
-)
-
-func ProcessControlConn(l *conn.Listener) {
-	for {
-		c, err := l.Accept()
-		if err != nil {
-			return
-		}
-		log.Debug("Get new connection, %v", c.GetRemoteAddr())
-		go controlWorker(c)
-	}
-}
-
-// connection from every client and server
-func controlWorker(c *conn.Conn) {
-	// if login message type is NewWorkConn, don't close this connection
-	var closeFlag bool = true
-	var s *server.ProxyServer
-	defer func() {
-		if closeFlag {
-			c.Close()
-			if s != nil {
-				s.Close()
-			}
-		}
-	}()
-
-	// get login message
-	buf, err := c.ReadLine()
-	if err != nil {
-		log.Warn("Read error, %v", err)
-		return
-	}
-	log.Debug("Get msg from frpc: %s", buf)
-
-	cliReq := &msg.ControlReq{}
-	if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
-		log.Warn("Parse msg from frpc error: %v : %s", err, buf)
-		return
-	}
-
-	// login when type is NewCtlConn or NewWorkConn
-	ret, info, s := doLogin(cliReq, c)
-	// if login type is NewWorkConn, nothing will be send to frpc
-	if cliReq.Type == consts.NewCtlConn {
-		cliRes := &msg.ControlRes{
-			Type: consts.NewCtlConnRes,
-			Code: ret,
-			Msg:  info,
-		}
-		byteBuf, _ := json.Marshal(cliRes)
-		err = c.WriteString(string(byteBuf) + "\n")
-		if err != nil {
-			log.Warn("ProxyName [%s], write to client error, proxy exit", cliReq.ProxyName)
-			return
-		}
-	} else {
-		if ret == 0 {
-			closeFlag = false
-		}
-		return
-	}
-
-	// if login failed, just return
-	if ret > 0 {
-		return
-	}
-
-	// create a channel for sending messages
-	msgSendChan := make(chan interface{}, 1024)
-	go msgSender(s, c, msgSendChan)
-	go noticeUserConn(s, msgSendChan)
-
-	// loop for reading control messages from frpc and deal with different types
-	msgReader(s, c, msgSendChan)
-
-	close(msgSendChan)
-	log.Info("ProxyName [%s], I'm dead!", s.Name)
-	return
-}
-
-// when frps get one new user connection, send NoticeUserConn message to frpc and accept one new WorkConn later
-func noticeUserConn(s *server.ProxyServer, msgSendChan chan interface{}) {
-	for {
-		closeFlag := s.WaitUserConn()
-		if closeFlag {
-			log.Debug("ProxyName [%s], goroutine for noticing user conn is closed", s.Name)
-			break
-		}
-		notice := &msg.ControlRes{
-			Type: consts.NoticeUserConn,
-		}
-		msgSendChan <- notice
-		log.Debug("ProxyName [%s], notice client to add work conn", s.Name)
-	}
-}
-
-// loop for reading messages from frpc after control connection is established
-func msgReader(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) error {
-	// for heartbeat
-	var heartbeatTimeout bool = false
-	timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, func() {
-		heartbeatTimeout = true
-		s.Close()
-		log.Error("ProxyName [%s], client heartbeat timeout", s.Name)
-	})
-	defer timer.Stop()
-
-	for {
-		buf, err := c.ReadLine()
-		if err != nil {
-			if err == io.EOF {
-				log.Warn("ProxyName [%s], client is dead!", s.Name)
-				s.Close()
-				return err
-			} else if c == nil || c.IsClosed() {
-				log.Warn("ProxyName [%s], client connection is closed", s.Name)
-				s.Close()
-				return err
-			}
-			log.Warn("ProxyName [%s], read error: %v", s.Name, err)
-			continue
-		}
-
-		cliReq := &msg.ControlReq{}
-		if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
-			log.Warn("ProxyName [%s], parse msg from frpc error: %v : %s", s.Name, err, buf)
-			continue
-		}
-
-		switch cliReq.Type {
-		case consts.HeartbeatReq:
-			log.Debug("ProxyName [%s], get heartbeat", s.Name)
-			timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second)
-			heartbeatRes := &msg.ControlRes{
-				Type: consts.HeartbeatRes,
-			}
-			msgSendChan <- heartbeatRes
-		default:
-			log.Warn("ProxyName [%s}, unsupport msgType [%d]", s.Name, cliReq.Type)
-		}
-	}
-	return nil
-}
-
-// loop for sending messages from channel to frpc
-func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) {
-	for {
-		msg, ok := <-msgSendChan
-		if !ok {
-			break
-		}
-
-		buf, _ := json.Marshal(msg)
-		err := c.WriteString(string(buf) + "\n")
-		if err != nil {
-			log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
-			s.Close()
-			break
-		}
-	}
-}
-
-// if success, ret equals 0, otherwise greater than 0
-// NewCtlConn
-// NewWorkConn
-// NewWorkConnUdp
-func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string, s *server.ProxyServer) {
-	ret = 1
-	// check if PrivilegeMode is enabled
-	if req.PrivilegeMode && !server.PrivilegeMode {
-		info = fmt.Sprintf("ProxyName [%s], PrivilegeMode is disabled in frps", req.ProxyName)
-		log.Warn("info")
-		return
-	}
-
-	var ok bool
-	s, ok = server.GetProxyServer(req.ProxyName)
-	if req.PrivilegeMode && req.Type == consts.NewCtlConn {
-		log.Debug("ProxyName [%s], doLogin and privilege mode is enabled", req.ProxyName)
-	} else {
-		if !ok {
-			info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
-			log.Warn(info)
-			return
-		}
-	}
-
-	// check authKey or privilegeKey
-	nowTime := time.Now().Unix()
-	if req.PrivilegeMode {
-		privilegeKey := pcrypto.GetAuthKey(req.ProxyName + server.PrivilegeToken + fmt.Sprintf("%d", req.Timestamp))
-		// privilegeKey unavaiable after server.AuthTimeout minutes
-		if server.AuthTimeout != 0 && nowTime-req.Timestamp > server.AuthTimeout {
-			info = fmt.Sprintf("ProxyName [%s], privilege mode authorization timeout", req.ProxyName)
-			log.Warn(info)
-			return
-		} else if req.PrivilegeKey != privilegeKey {
-			info = fmt.Sprintf("ProxyName [%s], privilege mode authorization failed", req.ProxyName)
-			log.Warn(info)
-			log.Debug("PrivilegeKey [%s] and get [%s]", privilegeKey, req.PrivilegeKey)
-			return
-		}
-	} else {
-		authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
-		if server.AuthTimeout != 0 && nowTime-req.Timestamp > server.AuthTimeout {
-			info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName)
-			log.Warn(info)
-			return
-		} else if req.AuthKey != authKey {
-			info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName)
-			log.Warn(info)
-			log.Debug("AuthKey [%s] and get [%s]", authKey, req.AuthKey)
-			return
-		}
-	}
-
-	// control conn
-	if req.Type == consts.NewCtlConn {
-		if req.PrivilegeMode {
-			s = server.NewProxyServerFromCtlMsg(req)
-			// we check listen_port if privilege_allow_ports are set
-			// and PrivilegeMode is enabled
-			if s.Type == "tcp" {
-				if len(server.PrivilegeAllowPorts) != 0 {
-					_, ok := server.PrivilegeAllowPorts[s.ListenPort]
-					if !ok {
-						info = fmt.Sprintf("ProxyName [%s], remote_port [%d] isn't allowed", req.ProxyName, s.ListenPort)
-						log.Warn(info)
-						return
-					}
-				}
-			} else if s.Type == "http" || s.Type == "https" {
-				for _, domain := range s.CustomDomains {
-					if server.SubDomainHost != "" && strings.Contains(domain, server.SubDomainHost) {
-						info = fmt.Sprintf("ProxyName [%s], custom domain [%s] should not belong to subdomain_host [%s]", req.ProxyName, domain, server.SubDomainHost)
-						log.Warn(info)
-						return
-					}
-				}
-
-				if s.SubDomain != "" {
-					if strings.Contains(s.SubDomain, ".") || strings.Contains(s.SubDomain, "*") {
-						info = fmt.Sprintf("ProxyName [%s], '.' and '*' is not supported in subdomain", req.ProxyName)
-						log.Warn(info)
-						return
-					}
-					if server.SubDomainHost == "" {
-						info = fmt.Sprintf("ProxyName [%s], subdomain is not supported because this feature is not enabled by remote server", req.ProxyName)
-						log.Warn(info)
-						return
-					}
-					s.SubDomain = s.SubDomain + "." + server.SubDomainHost
-				}
-			}
-			err := server.CreateProxy(s)
-			if err != nil {
-				info = fmt.Sprintf("ProxyName [%s], %v", req.ProxyName, err)
-				log.Warn(info)
-				return
-			}
-		}
-
-		// check if vhost_port is set
-		if s.Type == "http" && server.VhostHttpMuxer == nil {
-			info = fmt.Sprintf("ProxyName [%s], type [http] not support when vhost_http_port is not set", req.ProxyName)
-			log.Warn(info)
-			return
-		}
-		if s.Type == "https" && server.VhostHttpsMuxer == nil {
-			info = fmt.Sprintf("ProxyName [%s], type [https] not support when vhost_https_port is not set", req.ProxyName)
-			log.Warn(info)
-			return
-		}
-
-		// set infomations from frpc
-		s.BindAddr = server.BindAddr
-		s.UseEncryption = req.UseEncryption
-		s.UseGzip = req.UseGzip
-		s.HostHeaderRewrite = req.HostHeaderRewrite
-		s.HttpUserName = req.HttpUserName
-		s.HttpPassWord = req.HttpPassWord
-
-		if req.PoolCount > server.MaxPoolCount {
-			s.PoolCount = server.MaxPoolCount
-		} else if req.PoolCount < 0 {
-			s.PoolCount = 0
-		} else {
-			s.PoolCount = req.PoolCount
-		}
-
-		if s.Status == consts.Working {
-			info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
-			log.Warn(info)
-			return
-		}
-
-		// update metric's proxy status
-		metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, s.PrivilegeMode, s.CustomDomains, s.Locations, s.ListenPort)
-
-		// start proxy and listen for user connections, no block
-		err := s.Start(c)
-		if err != nil {
-			info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err)
-			log.Warn(info)
-			return
-		}
-		log.Info("ProxyName [%s], start proxy success", req.ProxyName)
-		if req.PrivilegeMode {
-			log.Info("ProxyName [%s], created by PrivilegeMode", req.ProxyName)
-		}
-	} else if req.Type == consts.NewWorkConn {
-		// work conn
-		if s.Status != consts.Working {
-			log.Warn("ProxyName [%s], is not working when it gets one new work connnection", req.ProxyName)
-			return
-		}
-		// the connection will close after join over
-		s.RegisterNewWorkConn(c)
-	} else if req.Type == consts.NewWorkConnUdp {
-		// work conn for udp
-		if s.Status != consts.Working {
-			log.Warn("ProxyName [%s], is not working when it gets one new work connnection for udp", req.ProxyName)
-			return
-		}
-		s.RegisterNewWorkConnUdp(c)
-	} else {
-		info = fmt.Sprintf("Unsupport login message type [%d]", req.Type)
-		log.Warn("Unsupport login message type [%d]", req.Type)
-		return
-	}
-
-	ret = 0
-	return
-}

+ 0 - 199
src/cmd/frps/main.go

@@ -1,199 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
-	"encoding/base64"
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"os"
-	"strconv"
-	"strings"
-	"time"
-
-	docopt "github.com/docopt/docopt-go"
-
-	"github.com/fatedier/frp/src/assets"
-	"github.com/fatedier/frp/src/models/server"
-	"github.com/fatedier/frp/src/utils/conn"
-	"github.com/fatedier/frp/src/utils/log"
-	"github.com/fatedier/frp/src/utils/version"
-	"github.com/fatedier/frp/src/utils/vhost"
-)
-
-var usage string = `frps is the server of frp
-
-Usage: 
-    frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
-    frps [-c config_file] --reload
-    frps -h | --help
-    frps -v | --version
-
-Options:
-    -c config_file            set config file
-    -L log_file               set output log file, including console
-    --log-level=<log_level>   set log level: debug, info, warn, error
-    --addr=<bind_addr>        listen addr for client, example: 0.0.0.0:7000
-    --reload                  reload ini file and configures in common section won't be changed
-    -h --help                 show this screen
-    -v --version              show version
-`
-
-func main() {
-	// the configures parsed from file will be replaced by those from command line if exist
-	args, err := docopt.Parse(usage, nil, true, version.Full(), false)
-
-	if args["-c"] != nil {
-		server.ConfigFile = args["-c"].(string)
-	}
-	err = server.LoadConf(server.ConfigFile)
-	if err != nil {
-		fmt.Println(err)
-		os.Exit(-1)
-	}
-
-	// reload check
-	if args["--reload"] != nil {
-		if args["--reload"].(bool) {
-			req, err := http.NewRequest("GET", "http://"+server.BindAddr+":"+fmt.Sprintf("%d", server.DashboardPort)+"/api/reload", nil)
-			if err != nil {
-				fmt.Printf("frps reload error: %v\n", err)
-				os.Exit(1)
-			}
-
-			authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(server.DashboardUsername+":"+server.DashboardPassword))
-
-			req.Header.Add("Authorization", authStr)
-			defaultClient := &http.Client{}
-			resp, err := defaultClient.Do(req)
-
-			if err != nil {
-				fmt.Printf("frps reload error: %v\n", err)
-				os.Exit(1)
-			} else {
-				defer resp.Body.Close()
-				body, err := ioutil.ReadAll(resp.Body)
-				if err != nil {
-					fmt.Printf("frps reload error: %v\n", err)
-					os.Exit(1)
-				}
-				res := &server.GeneralResponse{}
-				err = json.Unmarshal(body, &res)
-				if err != nil {
-					fmt.Printf("http response error: %s\n", strings.TrimSpace(string(body)))
-					os.Exit(1)
-				} else if res.Code != 0 {
-					fmt.Printf("reload error: %s\n", res.Msg)
-					os.Exit(1)
-				}
-				fmt.Printf("reload success\n")
-				os.Exit(0)
-			}
-		}
-	}
-
-	if args["-L"] != nil {
-		if args["-L"].(string) == "console" {
-			server.LogWay = "console"
-		} else {
-			server.LogWay = "file"
-			server.LogFile = args["-L"].(string)
-		}
-	}
-
-	if args["--log-level"] != nil {
-		server.LogLevel = args["--log-level"].(string)
-	}
-
-	if args["--addr"] != nil {
-		addr := strings.Split(args["--addr"].(string), ":")
-		if len(addr) != 2 {
-			fmt.Println("--addr format error: example 0.0.0.0:7000")
-			os.Exit(1)
-		}
-		bindPort, err := strconv.ParseInt(addr[1], 10, 64)
-		if err != nil {
-			fmt.Println("--addr format error, example 0.0.0.0:7000")
-			os.Exit(1)
-		}
-		server.BindAddr = addr[0]
-		server.BindPort = bindPort
-	}
-
-	if args["-v"] != nil {
-		if args["-v"].(bool) {
-			fmt.Println(version.Full())
-			os.Exit(0)
-		}
-	}
-
-	log.InitLog(server.LogWay, server.LogFile, server.LogLevel, server.LogMaxDays)
-
-	// init assets
-	err = assets.Load(server.AssetsDir)
-	if err != nil {
-		log.Error("Load assets error: %v", err)
-		os.Exit(1)
-	}
-
-	l, err := conn.Listen(server.BindAddr, server.BindPort)
-	if err != nil {
-		log.Error("Create server listener error, %v", err)
-		os.Exit(1)
-	}
-
-	// create vhost if VhostHttpPort != 0
-	if server.VhostHttpPort != 0 {
-		vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpPort)
-		if err != nil {
-			log.Error("Create vhost http listener error, %v", err)
-			os.Exit(1)
-		}
-		server.VhostHttpMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second)
-		if err != nil {
-			log.Error("Create vhost httpMuxer error, %v", err)
-		}
-	}
-
-	// create vhost if VhostHttpPort != 0
-	if server.VhostHttpsPort != 0 {
-		vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpsPort)
-		if err != nil {
-			log.Error("Create vhost https listener error, %v", err)
-			os.Exit(1)
-		}
-		server.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(vhostListener, 30*time.Second)
-		if err != nil {
-			log.Error("Create vhost httpsMuxer error, %v", err)
-		}
-	}
-
-	// create dashboard web server if DashboardPort is set, so it won't be 0
-	if server.DashboardPort != 0 {
-		err := server.RunDashboardServer(server.BindAddr, server.DashboardPort)
-		if err != nil {
-			log.Error("Create dashboard web server error, %v", err)
-			os.Exit(1)
-		}
-	}
-
-	log.Info("Start frps success")
-	if server.PrivilegeMode == true {
-		log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
-	}
-	ProcessControlConn(l)
-}

+ 0 - 186
src/models/client/client.go

@@ -1,186 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package client
-
-import (
-	"encoding/json"
-	"fmt"
-	"sync"
-	"time"
-
-	"github.com/fatedier/frp/src/models/config"
-	"github.com/fatedier/frp/src/models/consts"
-	"github.com/fatedier/frp/src/models/msg"
-	"github.com/fatedier/frp/src/utils/conn"
-	"github.com/fatedier/frp/src/utils/log"
-	"github.com/fatedier/frp/src/utils/pcrypto"
-)
-
-type ProxyClient struct {
-	config.BaseConf
-	LocalIp   string
-	LocalPort int64
-
-	RemotePort    int64
-	CustomDomains []string
-	Locations     []string
-
-	udpTunnel *conn.Conn
-	once      sync.Once
-	closeFlag bool
-
-	mutex sync.RWMutex
-}
-
-// if proxy type is udp, keep a tcp connection for transferring udp packages
-func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) {
-	pc.once.Do(func() {
-		var err error
-		var c *conn.Conn
-		udpProcessor := NewUdpProcesser(nil, pc.LocalIp, pc.LocalPort)
-		for {
-			if !pc.IsClosed() && (pc.udpTunnel == nil || pc.udpTunnel.IsClosed()) {
-				if HttpProxy == "" {
-					c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", addr, port))
-				} else {
-					c, err = conn.ConnectServerByHttpProxy(HttpProxy, fmt.Sprintf("%s:%d", addr, port))
-				}
-				if err != nil {
-					log.Error("ProxyName [%s], udp tunnel connect to server [%s:%d] error, %v", pc.Name, addr, port, err)
-					time.Sleep(10 * time.Second)
-					continue
-				}
-				log.Info("ProxyName [%s], udp tunnel connect to server [%s:%d] success", pc.Name, addr, port)
-
-				nowTime := time.Now().Unix()
-				req := &msg.ControlReq{
-					Type:          consts.NewWorkConnUdp,
-					ProxyName:     pc.Name,
-					PrivilegeMode: pc.PrivilegeMode,
-					Timestamp:     nowTime,
-				}
-				if pc.PrivilegeMode == true {
-					req.PrivilegeKey = pcrypto.GetAuthKey(pc.Name + PrivilegeToken + fmt.Sprintf("%d", nowTime))
-				} else {
-					req.AuthKey = pcrypto.GetAuthKey(pc.Name + pc.AuthToken + fmt.Sprintf("%d", nowTime))
-				}
-
-				buf, _ := json.Marshal(req)
-				err = c.WriteString(string(buf) + "\n")
-				if err != nil {
-					log.Error("ProxyName [%s], udp tunnel write to server error, %v", pc.Name, err)
-					c.Close()
-					time.Sleep(1 * time.Second)
-					continue
-				}
-				pc.mutex.Lock()
-				pc.udpTunnel = c
-				udpProcessor.UpdateTcpConn(pc.udpTunnel)
-				pc.mutex.Unlock()
-
-				udpProcessor.Run()
-			}
-			time.Sleep(1 * time.Second)
-		}
-	})
-}
-
-func (pc *ProxyClient) CloseUdpTunnel() {
-	pc.mutex.RLock()
-	defer pc.mutex.RUnlock()
-	if pc.udpTunnel != nil {
-		pc.udpTunnel.Close()
-	}
-}
-
-func (pc *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
-	c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", pc.LocalIp, pc.LocalPort))
-	if err != nil {
-		log.Error("ProxyName [%s], connect to local port error, %v", pc.Name, err)
-	}
-	return
-}
-
-func (pc *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err error) {
-	defer func() {
-		if err != nil && c != nil {
-			c.Close()
-		}
-	}()
-
-	if HttpProxy == "" {
-		c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", addr, port))
-	} else {
-		c, err = conn.ConnectServerByHttpProxy(HttpProxy, fmt.Sprintf("%s:%d", addr, port))
-	}
-	if err != nil {
-		log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", pc.Name, addr, port, err)
-		return
-	}
-
-	nowTime := time.Now().Unix()
-	req := &msg.ControlReq{
-		Type:          consts.NewWorkConn,
-		ProxyName:     pc.Name,
-		PrivilegeMode: pc.PrivilegeMode,
-		Timestamp:     nowTime,
-	}
-	if pc.PrivilegeMode == true {
-		req.PrivilegeKey = pcrypto.GetAuthKey(pc.Name + PrivilegeToken + fmt.Sprintf("%d", nowTime))
-	} else {
-		req.AuthKey = pcrypto.GetAuthKey(pc.Name + pc.AuthToken + fmt.Sprintf("%d", nowTime))
-	}
-
-	buf, _ := json.Marshal(req)
-	err = c.WriteString(string(buf) + "\n")
-	if err != nil {
-		log.Error("ProxyName [%s], write to server error, %v", pc.Name, err)
-		return
-	}
-
-	err = nil
-	return
-}
-
-func (pc *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err error) {
-	localConn, err := pc.GetLocalConn()
-	if err != nil {
-		return
-	}
-	remoteConn, err := pc.GetRemoteConn(serverAddr, serverPort)
-	if err != nil {
-		return
-	}
-
-	// l means local, r means remote
-	log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
-		remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr())
-	needRecord := false
-	go msg.JoinMore(localConn, remoteConn, pc.BaseConf, needRecord)
-
-	return nil
-}
-
-func (pc *ProxyClient) SetCloseFlag(closeFlag bool) {
-	pc.mutex.Lock()
-	defer pc.mutex.Unlock()
-	pc.closeFlag = closeFlag
-}
-
-func (pc *ProxyClient) IsClosed() bool {
-	pc.mutex.RLock()
-	defer pc.mutex.RUnlock()
-	return pc.closeFlag
-}

+ 0 - 302
src/models/client/config.go

@@ -1,302 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package client
-
-import (
-	"fmt"
-	"os"
-	"strconv"
-	"strings"
-
-	ini "github.com/vaughan0/go-ini"
-)
-
-// common config
-var (
-	ServerAddr        string = "0.0.0.0"
-	ServerPort        int64  = 7000
-	HttpProxy         string = ""
-	LogFile           string = "console"
-	LogWay            string = "console"
-	LogLevel          string = "info"
-	LogMaxDays        int64  = 3
-	PrivilegeToken    string = ""
-	HeartBeatInterval int64  = 10
-	HeartBeatTimeout  int64  = 30
-)
-
-var ProxyClients map[string]*ProxyClient = make(map[string]*ProxyClient)
-
-func LoadConf(confFile string) (err error) {
-	var tmpStr string
-	var ok bool
-
-	conf, err := ini.LoadFile(confFile)
-	if err != nil {
-		return err
-	}
-
-	// common
-	tmpStr, ok = conf.Get("common", "server_addr")
-	if ok {
-		ServerAddr = tmpStr
-	}
-
-	tmpStr, ok = conf.Get("common", "server_port")
-	if ok {
-		ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
-	}
-
-	tmpStr, ok = conf.Get("common", "http_proxy")
-	if ok {
-		HttpProxy = tmpStr
-	} else {
-		// get http_proxy from env
-		HttpProxy = os.Getenv("http_proxy")
-	}
-
-	tmpStr, ok = conf.Get("common", "log_file")
-	if ok {
-		LogFile = tmpStr
-		if LogFile == "console" {
-			LogWay = "console"
-		} else {
-			LogWay = "file"
-		}
-	}
-
-	tmpStr, ok = conf.Get("common", "log_level")
-	if ok {
-		LogLevel = tmpStr
-	}
-
-	tmpStr, ok = conf.Get("common", "log_max_days")
-	if ok {
-		LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
-	}
-
-	tmpStr, ok = conf.Get("common", "privilege_token")
-	if ok {
-		PrivilegeToken = tmpStr
-	}
-
-	var authToken string
-	tmpStr, ok = conf.Get("common", "auth_token")
-	if ok {
-		authToken = tmpStr
-	}
-
-	tmpStr, ok = conf.Get("common", "heartbeat_timeout")
-	if ok {
-		v, err := strconv.ParseInt(tmpStr, 10, 64)
-		if err != nil {
-			return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
-		} else {
-			HeartBeatTimeout = v
-		}
-	}
-
-	tmpStr, ok = conf.Get("common", "heartbeat_interval")
-	if ok {
-		v, err := strconv.ParseInt(tmpStr, 10, 64)
-		if err != nil {
-			return fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
-		} else {
-			HeartBeatInterval = v
-		}
-	}
-
-	if HeartBeatInterval <= 0 {
-		return fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
-	}
-
-	if HeartBeatTimeout < HeartBeatInterval {
-		return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
-	}
-
-	// proxies
-	for name, section := range conf {
-		if name != "common" {
-			proxyClient := &ProxyClient{}
-			// name
-			proxyClient.Name = name
-
-			// auth_token
-			proxyClient.AuthToken = authToken
-
-			// local_ip
-			proxyClient.LocalIp, ok = section["local_ip"]
-			if !ok {
-				// use 127.0.0.1 as default
-				proxyClient.LocalIp = "127.0.0.1"
-			}
-
-			// local_port
-			tmpStr, ok = section["local_port"]
-			if ok {
-				proxyClient.LocalPort, err = strconv.ParseInt(tmpStr, 10, 64)
-				if err != nil {
-					return fmt.Errorf("Parse conf error: proxy [%s] local_port error", proxyClient.Name)
-				}
-			} else {
-				return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", proxyClient.Name)
-			}
-
-			// type
-			proxyClient.Type = "tcp"
-			tmpStr, ok = section["type"]
-			if ok {
-				if tmpStr != "tcp" && tmpStr != "http" && tmpStr != "https" && tmpStr != "udp" {
-					return fmt.Errorf("Parse conf error: proxy [%s] type error", proxyClient.Name)
-				}
-				proxyClient.Type = tmpStr
-			}
-
-			// use_encryption
-			proxyClient.UseEncryption = false
-			tmpStr, ok = section["use_encryption"]
-			if ok && tmpStr == "true" {
-				proxyClient.UseEncryption = true
-			}
-
-			// use_gzip
-			proxyClient.UseGzip = false
-			tmpStr, ok = section["use_gzip"]
-			if ok && tmpStr == "true" {
-				proxyClient.UseGzip = true
-			}
-
-			if proxyClient.Type == "http" {
-				// host_header_rewrite
-				tmpStr, ok = section["host_header_rewrite"]
-				if ok {
-					proxyClient.HostHeaderRewrite = tmpStr
-				}
-				// http_user
-				tmpStr, ok = section["http_user"]
-				if ok {
-					proxyClient.HttpUserName = tmpStr
-				}
-				// http_pwd
-				tmpStr, ok = section["http_pwd"]
-				if ok {
-					proxyClient.HttpPassWord = tmpStr
-				}
-
-			}
-			if proxyClient.Type == "http" || proxyClient.Type == "https" {
-				// subdomain
-				tmpStr, ok = section["subdomain"]
-				if ok {
-					proxyClient.SubDomain = tmpStr
-				}
-			}
-
-			// privilege_mode
-			proxyClient.PrivilegeMode = false
-			tmpStr, ok = section["privilege_mode"]
-			if ok && tmpStr == "true" {
-				proxyClient.PrivilegeMode = true
-			}
-
-			// pool_count
-			proxyClient.PoolCount = 0
-			tmpStr, ok = section["pool_count"]
-			if ok {
-				tmpInt, err := strconv.ParseInt(tmpStr, 10, 64)
-				if err != nil || tmpInt < 0 {
-					return fmt.Errorf("Parse conf error: proxy [%s] pool_count error", proxyClient.Name)
-				}
-				proxyClient.PoolCount = tmpInt
-			}
-
-			// configures used in privilege mode
-			if proxyClient.PrivilegeMode == true {
-				if PrivilegeToken == "" {
-					return fmt.Errorf("Parse conf error: proxy [%s] privilege_token must be set when privilege_mode = true", proxyClient.Name)
-				} else {
-					proxyClient.PrivilegeToken = PrivilegeToken
-				}
-
-				if proxyClient.Type == "tcp" || proxyClient.Type == "udp" {
-					// remote_port
-					tmpStr, ok = section["remote_port"]
-					if ok {
-						proxyClient.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64)
-						if err != nil {
-							return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", proxyClient.Name)
-						}
-					} else {
-						return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", proxyClient.Name)
-					}
-				} else if proxyClient.Type == "http" {
-					// custom_domains
-					tmpStr, ok = section["custom_domains"]
-					if ok {
-						proxyClient.CustomDomains = strings.Split(tmpStr, ",")
-						for i, domain := range proxyClient.CustomDomains {
-							proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
-						}
-					}
-
-					// subdomain
-					tmpStr, ok = section["subdomain"]
-					if ok {
-						proxyClient.SubDomain = tmpStr
-					}
-
-					if len(proxyClient.CustomDomains) == 0 && proxyClient.SubDomain == "" {
-						return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is http", proxyClient.Name)
-					}
-
-					// locations
-					tmpStr, ok = section["locations"]
-					if ok {
-						proxyClient.Locations = strings.Split(tmpStr, ",")
-					} else {
-						proxyClient.Locations = []string{""}
-					}
-				} else if proxyClient.Type == "https" {
-					// custom_domains
-					tmpStr, ok = section["custom_domains"]
-					if ok {
-						proxyClient.CustomDomains = strings.Split(tmpStr, ",")
-						for i, domain := range proxyClient.CustomDomains {
-							proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
-						}
-					}
-
-					// subdomain
-					tmpStr, ok = section["subdomain"]
-					if ok {
-						proxyClient.SubDomain = tmpStr
-					}
-
-					if len(proxyClient.CustomDomains) == 0 && proxyClient.SubDomain == "" {
-						return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is https", proxyClient.Name)
-					}
-				}
-			}
-
-			ProxyClients[proxyClient.Name] = proxyClient
-		}
-	}
-
-	if len(ProxyClients) == 0 {
-		return fmt.Errorf("Parse conf error: no proxy config found")
-	}
-
-	return nil
-}

+ 0 - 49
src/models/msg/msg.go

@@ -1,49 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package msg
-
-type GeneralRes struct {
-	Code int64  `json:"code"`
-	Msg  string `json:"msg"`
-}
-
-// messages between control connections of frpc and frps
-type ControlReq struct {
-	Type          int64  `json:"type"`
-	ProxyName     string `json:"proxy_name"`
-	AuthKey       string `json:"auth_key"`
-	UseEncryption bool   `json:"use_encryption"`
-	UseGzip       bool   `json:"use_gzip"`
-	PoolCount     int64  `json:"pool_count"`
-
-	// configures used if privilege_mode is enabled
-	PrivilegeMode     bool     `json:"privilege_mode"`
-	PrivilegeKey      string   `json:"privilege_key"`
-	ProxyType         string   `json:"proxy_type"`
-	RemotePort        int64    `json:"remote_port"`
-	CustomDomains     []string `json:"custom_domains, omitempty"`
-	Locations         []string `json:"locations"`
-	HostHeaderRewrite string   `json:"host_header_rewrite"`
-	HttpUserName      string   `json:"http_username"`
-	HttpPassWord      string   `json:"http_password"`
-	SubDomain         string   `json:"subdomain"`
-	Timestamp         int64    `json:"timestamp"`
-}
-
-type ControlRes struct {
-	Type int64  `json:"type"`
-	Code int64  `json:"code"`
-	Msg  string `json:"msg"`
-}

+ 0 - 257
src/models/msg/process.go

@@ -1,257 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package msg
-
-import (
-	"bytes"
-	"encoding/binary"
-	"fmt"
-	"io"
-	"sync"
-
-	"github.com/fatedier/frp/src/models/config"
-	"github.com/fatedier/frp/src/models/metric"
-	"github.com/fatedier/frp/src/utils/conn"
-	"github.com/fatedier/frp/src/utils/log"
-	"github.com/fatedier/frp/src/utils/pcrypto"
-	"github.com/fatedier/frp/src/utils/pool"
-)
-
-// deprecated
-// will block until connection close
-func Join(c1 *conn.Conn, c2 *conn.Conn) {
-	var wait sync.WaitGroup
-	pipe := func(to *conn.Conn, from *conn.Conn) {
-		defer to.Close()
-		defer from.Close()
-		defer wait.Done()
-
-		var err error
-		_, err = io.Copy(to.TcpConn, from.TcpConn)
-		if err != nil {
-			log.Warn("join connections error, %v", err)
-		}
-	}
-
-	wait.Add(2)
-	go pipe(c1, c2)
-	go pipe(c2, c1)
-	wait.Wait()
-	return
-}
-
-// join two connections and do some operations
-func JoinMore(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser, conf config.BaseConf, needRecord bool) {
-	var wait sync.WaitGroup
-	encryptPipe := func(from io.ReadCloser, to io.WriteCloser) {
-		defer from.Close()
-		defer to.Close()
-		defer wait.Done()
-
-		// we don't care about errors here
-		pipeEncrypt(from, to, conf, needRecord)
-	}
-
-	decryptPipe := func(to io.ReadCloser, from io.WriteCloser) {
-		defer from.Close()
-		defer to.Close()
-		defer wait.Done()
-
-		// we don't care about errors here
-		pipeDecrypt(to, from, conf, needRecord)
-	}
-
-	if needRecord {
-		metric.OpenConnection(conf.Name)
-	}
-	wait.Add(2)
-	go encryptPipe(c1, c2)
-	go decryptPipe(c2, c1)
-	wait.Wait()
-	if needRecord {
-		metric.CloseConnection(conf.Name)
-	}
-	log.Debug("ProxyName [%s], One tunnel stopped", conf.Name)
-	return
-}
-
-func pkgMsg(data []byte) []byte {
-	llen := uint32(len(data))
-	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.BigEndian, llen)
-	buf.Write(data)
-	return buf.Bytes()
-}
-
-func unpkgMsg(data []byte) (int, []byte, []byte) {
-	if len(data) < 4 {
-		return -1, nil, data
-	}
-	llen := int(binary.BigEndian.Uint32(data[0:4]))
-	// no complete
-	if len(data) < llen+4 {
-		return -1, nil, data
-	}
-
-	return 0, data[4 : llen+4], data[llen+4:]
-}
-
-// decrypt msg from reader, then write into writer
-func pipeDecrypt(r io.Reader, w io.Writer, conf config.BaseConf, needRecord bool) (err error) {
-	laes := new(pcrypto.Pcrypto)
-	key := conf.AuthToken
-	if conf.PrivilegeMode {
-		key = conf.PrivilegeToken
-	}
-	if err := laes.Init([]byte(key)); err != nil {
-		log.Warn("ProxyName [%s], Pcrypto Init error: %v", conf.Name, err)
-		return fmt.Errorf("Pcrypto Init error: %v", err)
-	}
-
-	// get []byte from buffer pool
-	buf := pool.GetBuf(5*1024 + 4)
-	defer pool.PutBuf(buf)
-
-	var left, res []byte
-	var cnt int = -1
-
-	// record
-	var flowBytes int64 = 0
-	if needRecord {
-		defer func() {
-			metric.AddFlowOut(conf.Name, flowBytes)
-		}()
-	}
-
-	for {
-		// there may be more than 1 package in variable
-		// and we read more bytes if unpkgMsg returns an error
-		var newBuf []byte
-		if cnt < 0 {
-			n, err := r.Read(buf)
-			if err != nil {
-				return err
-			}
-			newBuf = append(left, buf[0:n]...)
-		} else {
-			newBuf = left
-		}
-		cnt, res, left = unpkgMsg(newBuf)
-		if cnt < 0 {
-			// limit one package length, maximum is 1MB
-			if len(res) > 1024*1024 {
-				log.Warn("ProxyName [%s], package length exceeds the limit")
-				return fmt.Errorf("package length error")
-			}
-			continue
-		}
-
-		// aes
-		if conf.UseEncryption {
-			res, err = laes.Decrypt(res)
-			if err != nil {
-				log.Warn("ProxyName [%s], decrypt error, %v", conf.Name, err)
-				return fmt.Errorf("Decrypt error: %v", err)
-			}
-		}
-		// gzip
-		if conf.UseGzip {
-			res, err = laes.Decompression(res)
-			if err != nil {
-				log.Warn("ProxyName [%s], decompression error, %v", conf.Name, err)
-				return fmt.Errorf("Decompression error: %v", err)
-			}
-		}
-
-		_, err = w.Write(res)
-		if err != nil {
-			return err
-		}
-
-		if needRecord {
-			flowBytes += int64(len(res))
-			if flowBytes >= 1024*1024 {
-				metric.AddFlowOut(conf.Name, flowBytes)
-				flowBytes = 0
-			}
-		}
-	}
-	return nil
-}
-
-// recvive msg from reader, then encrypt msg into writer
-func pipeEncrypt(r io.Reader, w io.Writer, conf config.BaseConf, needRecord bool) (err error) {
-	laes := new(pcrypto.Pcrypto)
-	key := conf.AuthToken
-	if conf.PrivilegeMode {
-		key = conf.PrivilegeToken
-	}
-	if err := laes.Init([]byte(key)); err != nil {
-		log.Warn("ProxyName [%s], Pcrypto Init error: %v", conf.Name, err)
-		return fmt.Errorf("Pcrypto Init error: %v", err)
-	}
-
-	// record
-	var flowBytes int64 = 0
-	if needRecord {
-		defer func() {
-			metric.AddFlowIn(conf.Name, flowBytes)
-		}()
-	}
-
-	// get []byte from buffer pool
-	buf := pool.GetBuf(5*1024 + 4)
-	defer pool.PutBuf(buf)
-
-	for {
-		n, err := r.Read(buf)
-		if err != nil {
-			return err
-		}
-		if needRecord {
-			flowBytes += int64(n)
-			if flowBytes >= 1024*1024 {
-				metric.AddFlowIn(conf.Name, flowBytes)
-				flowBytes = 0
-			}
-		}
-
-		res := buf[0:n]
-		// gzip
-		if conf.UseGzip {
-			res, err = laes.Compression(res)
-			if err != nil {
-				log.Warn("ProxyName [%s], compression error: %v", conf.Name, err)
-				return fmt.Errorf("Compression error: %v", err)
-			}
-		}
-		// aes
-		if conf.UseEncryption {
-			res, err = laes.Encrypt(res)
-			if err != nil {
-				log.Warn("ProxyName [%s], encrypt error: %v", conf.Name, err)
-				return fmt.Errorf("Encrypt error: %v", err)
-			}
-		}
-
-		res = pkgMsg(res)
-		_, err = w.Write(res)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}

+ 0 - 456
src/models/server/config.go

@@ -1,456 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package server
-
-import (
-	"fmt"
-	"strconv"
-	"strings"
-	"sync"
-
-	ini "github.com/vaughan0/go-ini"
-
-	"github.com/fatedier/frp/src/models/consts"
-	"github.com/fatedier/frp/src/models/metric"
-	"github.com/fatedier/frp/src/utils/log"
-	"github.com/fatedier/frp/src/utils/vhost"
-)
-
-// common config
-var (
-	ConfigFile        string = "./frps.ini"
-	BindAddr          string = "0.0.0.0"
-	BindPort          int64  = 7000
-	VhostHttpPort     int64  = 0 // if VhostHttpPort equals 0, don't listen a public port for http protocol
-	VhostHttpsPort    int64  = 0 // if VhostHttpsPort equals 0, don't listen a public port for https protocol
-	DashboardPort     int64  = 0 // if DashboardPort equals 0, dashboard is not available
-	DashboardUsername string = "admin"
-	DashboardPassword string = "admin"
-	AssetsDir         string = ""
-	LogFile           string = "console"
-	LogWay            string = "console" // console or file
-	LogLevel          string = "info"
-	LogMaxDays        int64  = 3
-	PrivilegeMode     bool   = false
-	PrivilegeToken    string = ""
-	AuthTimeout       int64  = 900
-	SubDomainHost     string = ""
-
-	// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
-	PrivilegeAllowPorts map[int64]struct{}
-	MaxPoolCount        int64 = 100
-	HeartBeatTimeout    int64 = 30
-	UserConnTimeout     int64 = 10
-
-	VhostHttpMuxer    *vhost.HttpMuxer
-	VhostHttpsMuxer   *vhost.HttpsMuxer
-	ProxyServers      map[string]*ProxyServer = make(map[string]*ProxyServer) // all proxy servers info and resources
-	ProxyServersMutex sync.RWMutex
-)
-
-func LoadConf(confFile string) (err error) {
-	err = loadCommonConf(confFile)
-	if err != nil {
-		return err
-	}
-
-	// load all proxy server's configure and initialize
-	// and set ProxyServers map
-	newProxyServers, err := loadProxyConf(confFile)
-	if err != nil {
-		return err
-	}
-	for _, proxyServer := range newProxyServers {
-		proxyServer.Init()
-	}
-	ProxyServersMutex.Lock()
-	ProxyServers = newProxyServers
-	ProxyServersMutex.Unlock()
-	return nil
-}
-
-func loadCommonConf(confFile string) error {
-	var tmpStr string
-	var ok bool
-	conf, err := ini.LoadFile(confFile)
-	if err != nil {
-		return err
-	}
-	// common
-	tmpStr, ok = conf.Get("common", "bind_addr")
-	if ok {
-		BindAddr = tmpStr
-	}
-
-	tmpStr, ok = conf.Get("common", "bind_port")
-	if ok {
-		v, err := strconv.ParseInt(tmpStr, 10, 64)
-		if err == nil {
-			BindPort = v
-		}
-	}
-
-	tmpStr, ok = conf.Get("common", "vhost_http_port")
-	if ok {
-		VhostHttpPort, _ = strconv.ParseInt(tmpStr, 10, 64)
-	} else {
-		VhostHttpPort = 0
-	}
-
-	tmpStr, ok = conf.Get("common", "vhost_https_port")
-	if ok {
-		VhostHttpsPort, _ = strconv.ParseInt(tmpStr, 10, 64)
-	} else {
-		VhostHttpsPort = 0
-	}
-
-	tmpStr, ok = conf.Get("common", "dashboard_port")
-	if ok {
-		DashboardPort, _ = strconv.ParseInt(tmpStr, 10, 64)
-	} else {
-		DashboardPort = 0
-	}
-
-	tmpStr, ok = conf.Get("common", "dashboard_user")
-	if ok {
-		DashboardUsername = tmpStr
-	}
-
-	tmpStr, ok = conf.Get("common", "dashboard_pwd")
-	if ok {
-		DashboardPassword = tmpStr
-	}
-
-	tmpStr, ok = conf.Get("common", "assets_dir")
-	if ok {
-		AssetsDir = tmpStr
-	}
-
-	tmpStr, ok = conf.Get("common", "log_file")
-	if ok {
-		LogFile = tmpStr
-		if LogFile == "console" {
-			LogWay = "console"
-		} else {
-			LogWay = "file"
-		}
-	}
-
-	tmpStr, ok = conf.Get("common", "log_level")
-	if ok {
-		LogLevel = tmpStr
-	}
-
-	tmpStr, ok = conf.Get("common", "log_max_days")
-	if ok {
-		v, err := strconv.ParseInt(tmpStr, 10, 64)
-		if err == nil {
-			LogMaxDays = v
-		}
-	}
-
-	tmpStr, ok = conf.Get("common", "privilege_mode")
-	if ok {
-		if tmpStr == "true" {
-			PrivilegeMode = true
-		}
-	}
-
-	if PrivilegeMode == true {
-		tmpStr, ok = conf.Get("common", "privilege_token")
-		if ok {
-			if tmpStr == "" {
-				return fmt.Errorf("Parse conf error: privilege_token can not be null")
-			}
-			PrivilegeToken = tmpStr
-		} else {
-			return fmt.Errorf("Parse conf error: privilege_token must be set if privilege_mode is enabled")
-		}
-
-		PrivilegeAllowPorts = make(map[int64]struct{})
-		tmpStr, ok = conf.Get("common", "privilege_allow_ports")
-		if ok {
-			// for example: 1000-2000,2001,2002,3000-4000
-			portRanges := strings.Split(tmpStr, ",")
-			for _, portRangeStr := range portRanges {
-				// 1000-2000 or 2001
-				portArray := strings.Split(portRangeStr, "-")
-				// lenght: only 1 or 2 is correct
-				rangeType := len(portArray)
-				if rangeType == 1 {
-					singlePort, err := strconv.ParseInt(portArray[0], 10, 64)
-					if err != nil {
-						return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
-					}
-					PrivilegeAllowPorts[singlePort] = struct{}{}
-				} else if rangeType == 2 {
-					min, err := strconv.ParseInt(portArray[0], 10, 64)
-					if err != nil {
-						return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
-					}
-					max, err := strconv.ParseInt(portArray[1], 10, 64)
-					if err != nil {
-						return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
-					}
-					if max < min {
-						return fmt.Errorf("Parse conf error: privilege_allow_ports range incorrect")
-					}
-					for i := min; i <= max; i++ {
-						PrivilegeAllowPorts[i] = struct{}{}
-					}
-				} else {
-					return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect")
-				}
-			}
-		}
-	}
-
-	tmpStr, ok = conf.Get("common", "max_pool_count")
-	if ok {
-		v, err := strconv.ParseInt(tmpStr, 10, 64)
-		if err == nil && v >= 0 {
-			MaxPoolCount = v
-		}
-	}
-	tmpStr, ok = conf.Get("common", "authentication_timeout")
-	if ok {
-		v, err := strconv.ParseInt(tmpStr, 10, 64)
-		if err != nil {
-			return fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
-		} else {
-			AuthTimeout = v
-		}
-	}
-	SubDomainHost, ok = conf.Get("common", "subdomain_host")
-	if ok {
-		SubDomainHost = strings.ToLower(strings.TrimSpace(SubDomainHost))
-	}
-
-	tmpStr, ok = conf.Get("common", "heartbeat_timeout")
-	if ok {
-		v, err := strconv.ParseInt(tmpStr, 10, 64)
-		if err != nil {
-			return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
-		} else {
-			HeartBeatTimeout = v
-		}
-	}
-	return nil
-}
-
-func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err error) {
-	var ok bool
-	proxyServers = make(map[string]*ProxyServer)
-	conf, err := ini.LoadFile(confFile)
-	if err != nil {
-		return proxyServers, err
-	}
-	// servers
-	for name, section := range conf {
-		if name != "common" {
-			proxyServer := NewProxyServer()
-			proxyServer.Name = name
-
-			proxyServer.Type, ok = section["type"]
-			if ok {
-				if proxyServer.Type != "tcp" && proxyServer.Type != "http" && proxyServer.Type != "https" && proxyServer.Type != "udp" {
-					return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] type error", proxyServer.Name)
-				}
-			} else {
-				proxyServer.Type = "tcp"
-			}
-
-			proxyServer.AuthToken, ok = section["auth_token"]
-			if !ok {
-				return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] no auth_token found", proxyServer.Name)
-			}
-
-			// for tcp and udp
-			if proxyServer.Type == "tcp" || proxyServer.Type == "udp" {
-				proxyServer.BindAddr, ok = section["bind_addr"]
-				if !ok {
-					proxyServer.BindAddr = "0.0.0.0"
-				}
-
-				portStr, ok := section["listen_port"]
-				if ok {
-					proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64)
-					if err != nil {
-						return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] listen_port error", proxyServer.Name)
-					}
-				} else {
-					return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] listen_port not found", proxyServer.Name)
-				}
-			} else if proxyServer.Type == "http" {
-				// for http
-				proxyServer.ListenPort = VhostHttpPort
-
-				domainStr, ok := section["custom_domains"]
-				if ok {
-					proxyServer.CustomDomains = strings.Split(domainStr, ",")
-					for i, domain := range proxyServer.CustomDomains {
-						domain = strings.ToLower(strings.TrimSpace(domain))
-						// custom domain should not belong to subdomain_host
-						if SubDomainHost != "" && strings.Contains(domain, SubDomainHost) {
-							return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom domain should not belong to subdomain_host", proxyServer.Name)
-						}
-						proxyServer.CustomDomains[i] = domain
-					}
-				}
-
-				// subdomain
-				subdomainStr, ok := section["subdomain"]
-				if ok {
-					if strings.Contains(subdomainStr, ".") || strings.Contains(subdomainStr, "*") {
-						return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] '.' and '*' is not supported in subdomain", proxyServer.Name)
-					}
-
-					if SubDomainHost == "" {
-						return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] subdomain is not supported because subdomain_host is empty", proxyServer.Name)
-					}
-					proxyServer.SubDomain = subdomainStr + "." + SubDomainHost
-				}
-
-				if len(proxyServer.CustomDomains) == 0 && proxyServer.SubDomain == "" {
-					return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is http", proxyServer.Name)
-				}
-
-				// locations
-				locations, ok := section["locations"]
-				if ok {
-					proxyServer.Locations = strings.Split(locations, ",")
-				} else {
-					proxyServer.Locations = []string{""}
-				}
-			} else if proxyServer.Type == "https" {
-				// for https
-				proxyServer.ListenPort = VhostHttpsPort
-
-				domainStr, ok := section["custom_domains"]
-				if ok {
-					proxyServer.CustomDomains = strings.Split(domainStr, ",")
-					for i, domain := range proxyServer.CustomDomains {
-						domain = strings.ToLower(strings.TrimSpace(domain))
-						if SubDomainHost != "" && strings.Contains(domain, SubDomainHost) {
-							return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom domain should not belong to subdomain_host", proxyServer.Name)
-						}
-						proxyServer.CustomDomains[i] = domain
-					}
-				}
-
-				// subdomain
-				subdomainStr, ok := section["subdomain"]
-				if ok {
-					if strings.Contains(subdomainStr, ".") || strings.Contains(subdomainStr, "*") {
-						return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] '.' and '*' is not supported in subdomain", proxyServer.Name)
-					}
-
-					if SubDomainHost == "" {
-						return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] subdomain is not supported because subdomain_host is empty", proxyServer.Name)
-					}
-					proxyServer.SubDomain = subdomainStr + "." + SubDomainHost
-				}
-
-				if len(proxyServer.CustomDomains) == 0 && proxyServer.SubDomain == "" {
-					return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is https", proxyServer.Name)
-				}
-			}
-			proxyServers[proxyServer.Name] = proxyServer
-		}
-	}
-
-	// set metric statistics of all proxies
-	for name, p := range proxyServers {
-		metric.SetProxyInfo(name, p.Type, p.BindAddr, p.UseEncryption, p.UseGzip,
-			p.PrivilegeMode, p.CustomDomains, p.Locations, p.ListenPort)
-	}
-	return proxyServers, nil
-}
-
-// the function can only reload proxy configures
-// common section won't be changed
-func ReloadConf(confFile string) (err error) {
-	loadProxyServers, err := loadProxyConf(confFile)
-	if err != nil {
-		return err
-	}
-
-	ProxyServersMutex.Lock()
-	for name, proxyServer := range loadProxyServers {
-		oldProxyServer, ok := ProxyServers[name]
-		if ok {
-			if !oldProxyServer.Compare(proxyServer) {
-				oldProxyServer.Close()
-				proxyServer.Init()
-				ProxyServers[name] = proxyServer
-				log.Info("ProxyName [%s] configure change, restart", name)
-			}
-		} else {
-			proxyServer.Init()
-			ProxyServers[name] = proxyServer
-			log.Info("ProxyName [%s] is new, init it", name)
-		}
-	}
-
-	// proxies created by PrivilegeMode won't be deleted
-	for name, oldProxyServer := range ProxyServers {
-		_, ok := loadProxyServers[name]
-		if !ok {
-			if !oldProxyServer.PrivilegeMode {
-				oldProxyServer.Close()
-				delete(ProxyServers, name)
-				log.Info("ProxyName [%s] deleted, close it", name)
-			} else {
-				log.Info("ProxyName [%s] created by PrivilegeMode, won't be closed", name)
-			}
-		}
-	}
-	ProxyServersMutex.Unlock()
-	return nil
-}
-
-func CreateProxy(s *ProxyServer) error {
-	ProxyServersMutex.Lock()
-	defer ProxyServersMutex.Unlock()
-	oldServer, ok := ProxyServers[s.Name]
-	if ok {
-		if oldServer.Status == consts.Working {
-			return fmt.Errorf("this proxy is already working now")
-		}
-		oldServer.Lock()
-		oldServer.Release()
-		oldServer.Unlock()
-		if oldServer.PrivilegeMode {
-			delete(ProxyServers, s.Name)
-		}
-	}
-	ProxyServers[s.Name] = s
-	metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip,
-		s.PrivilegeMode, s.CustomDomains, s.Locations, s.ListenPort)
-	return nil
-}
-
-func DeleteProxy(proxyName string) {
-	ProxyServersMutex.Lock()
-	defer ProxyServersMutex.Unlock()
-	delete(ProxyServers, proxyName)
-}
-
-func GetProxyServer(proxyName string) (p *ProxyServer, ok bool) {
-	ProxyServersMutex.RLock()
-	defer ProxyServersMutex.RUnlock()
-	p, ok = ProxyServers[proxyName]
-	return
-}

+ 0 - 484
src/models/server/server.go

@@ -1,484 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package server
-
-import (
-	"fmt"
-	"net"
-	"sync"
-	"time"
-
-	"github.com/fatedier/frp/src/models/config"
-	"github.com/fatedier/frp/src/models/consts"
-	"github.com/fatedier/frp/src/models/metric"
-	"github.com/fatedier/frp/src/models/msg"
-	"github.com/fatedier/frp/src/utils/conn"
-	"github.com/fatedier/frp/src/utils/log"
-	"github.com/fatedier/frp/src/utils/pool"
-)
-
-type Listener interface {
-	Accept() (*conn.Conn, error)
-	Close() error
-}
-
-type ProxyServer struct {
-	config.BaseConf
-	BindAddr      string
-	ListenPort    int64
-	CustomDomains []string
-	Locations     []string
-
-	Status      int64
-	CtlConn     *conn.Conn // control connection with frpc
-	WorkConnUdp *conn.Conn // work connection for udp
-
-	udpConn       *net.UDPConn
-	listeners     []Listener      // accept new connection from remote users
-	ctlMsgChan    chan int64      // every time accept a new user conn, put "1" to the channel
-	workConnChan  chan *conn.Conn // get new work conns from control goroutine
-	udpSenderChan chan *msg.UdpPacket
-	mutex         sync.RWMutex
-	closeChan     chan struct{} // close this channel for notifying other goroutines that the proxy is closed
-}
-
-func NewProxyServer() (p *ProxyServer) {
-	p = &ProxyServer{
-		CustomDomains: make([]string, 0),
-		Locations:     make([]string, 0),
-	}
-	return p
-}
-
-func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) {
-	p = &ProxyServer{}
-	p.Name = req.ProxyName
-	p.Type = req.ProxyType
-	p.UseEncryption = req.UseEncryption
-	p.UseGzip = req.UseGzip
-	p.PrivilegeMode = req.PrivilegeMode
-	p.PrivilegeToken = PrivilegeToken
-	p.BindAddr = BindAddr
-	if p.Type == "tcp" || p.Type == "udp" {
-		p.ListenPort = req.RemotePort
-	} else if p.Type == "http" {
-		p.ListenPort = VhostHttpPort
-	} else if p.Type == "https" {
-		p.ListenPort = VhostHttpsPort
-	}
-	p.CustomDomains = req.CustomDomains
-	p.SubDomain = req.SubDomain
-	p.Locations = req.Locations
-	p.HostHeaderRewrite = req.HostHeaderRewrite
-	p.HttpUserName = req.HttpUserName
-	p.HttpPassWord = req.HttpPassWord
-
-	p.Init()
-	return
-}
-
-func (p *ProxyServer) Init() {
-	p.Lock()
-	p.Status = consts.Idle
-	metric.SetStatus(p.Name, p.Status)
-	p.workConnChan = make(chan *conn.Conn, p.PoolCount+10)
-	p.ctlMsgChan = make(chan int64, p.PoolCount+10)
-	p.udpSenderChan = make(chan *msg.UdpPacket, 1024)
-	p.listeners = make([]Listener, 0)
-	p.closeChan = make(chan struct{})
-	p.Unlock()
-}
-
-func (p *ProxyServer) Compare(p2 *ProxyServer) bool {
-	if p.Name != p2.Name || p.AuthToken != p2.AuthToken || p.Type != p2.Type ||
-		p.BindAddr != p2.BindAddr || p.ListenPort != p2.ListenPort || p.HostHeaderRewrite != p2.HostHeaderRewrite {
-		return false
-	}
-	if len(p.CustomDomains) != len(p2.CustomDomains) {
-		return false
-	}
-	for i, _ := range p.CustomDomains {
-		if p.CustomDomains[i] != p2.CustomDomains[i] {
-			return false
-		}
-	}
-
-	if len(p.Locations) != len(p2.Locations) {
-		return false
-	}
-	for i, _ := range p.Locations {
-		if p.Locations[i] != p2.Locations[i] {
-			return false
-		}
-	}
-	return true
-}
-
-func (p *ProxyServer) Lock() {
-	p.mutex.Lock()
-}
-
-func (p *ProxyServer) Unlock() {
-	p.mutex.Unlock()
-}
-
-// start listening for user conns
-func (p *ProxyServer) Start(c *conn.Conn) (err error) {
-	p.CtlConn = c
-	p.Init()
-	if p.Type == "tcp" {
-		l, err := conn.Listen(p.BindAddr, p.ListenPort)
-		if err != nil {
-			return err
-		}
-		p.listeners = append(p.listeners, l)
-	} else if p.Type == "http" {
-		for _, domain := range p.CustomDomains {
-			if len(p.Locations) == 0 {
-				l, err := VhostHttpMuxer.Listen(domain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
-				if err != nil {
-					return err
-				}
-				log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, domain, "")
-				p.listeners = append(p.listeners, l)
-			} else {
-				for _, location := range p.Locations {
-					l, err := VhostHttpMuxer.Listen(domain, location, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
-					if err != nil {
-						return err
-					}
-					log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, domain, location)
-					p.listeners = append(p.listeners, l)
-				}
-			}
-		}
-		if p.SubDomain != "" {
-			if len(p.Locations) == 0 {
-				l, err := VhostHttpMuxer.Listen(p.SubDomain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
-				if err != nil {
-					return err
-				}
-				log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, p.SubDomain, "")
-				p.listeners = append(p.listeners, l)
-			} else {
-				for _, location := range p.Locations {
-					l, err := VhostHttpMuxer.Listen(p.SubDomain, location, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
-					if err != nil {
-						return err
-					}
-					log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, p.SubDomain, location)
-					p.listeners = append(p.listeners, l)
-				}
-			}
-		}
-	} else if p.Type == "https" {
-		for _, domain := range p.CustomDomains {
-			l, err := VhostHttpsMuxer.Listen(domain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
-			if err != nil {
-				return err
-			}
-			log.Info("ProxyName [%s], type https listen for host [%s]", p.Name, domain)
-			p.listeners = append(p.listeners, l)
-		}
-		if p.SubDomain != "" {
-			l, err := VhostHttpsMuxer.Listen(p.SubDomain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
-			if err != nil {
-				return err
-			}
-			log.Info("ProxyName [%s], type https listen for host [%s]", p.Name, p.SubDomain)
-			p.listeners = append(p.listeners, l)
-		}
-	}
-
-	p.Lock()
-	p.Status = consts.Working
-	p.Unlock()
-	metric.SetStatus(p.Name, p.Status)
-
-	if p.Type == "udp" {
-		// udp is special
-		p.udpConn, err = conn.ListenUDP(p.BindAddr, p.ListenPort)
-		if err != nil {
-			log.Warn("ProxyName [%s], listen udp port error: %v", p.Name, err)
-			return err
-		}
-		go func() {
-			for {
-				buf := pool.GetBuf(2048)
-				n, remoteAddr, err := p.udpConn.ReadFromUDP(buf)
-				if err != nil {
-					log.Info("ProxyName [%s], udp listener is closed", p.Name)
-					return
-				}
-				localAddr, _ := net.ResolveUDPAddr("udp", p.udpConn.LocalAddr().String())
-				udpPacket := msg.NewUdpPacket(buf[0:n], remoteAddr, localAddr)
-				select {
-				case p.udpSenderChan <- udpPacket:
-				default:
-					log.Warn("ProxyName [%s], udp sender channel is full", p.Name)
-				}
-				pool.PutBuf(buf)
-			}
-		}()
-	} else {
-		// create connection pool if needed
-		if p.PoolCount > 0 {
-			go p.connectionPoolManager(p.closeChan)
-		}
-
-		// start a goroutine for every listener to accept user connection
-		for _, listener := range p.listeners {
-			go func(l Listener) {
-				for {
-					// block
-					// if listener is closed, err returned
-					c, err := l.Accept()
-					if err != nil {
-						log.Info("ProxyName [%s], listener is closed", p.Name)
-						return
-					}
-					log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr())
-
-					if p.Status != consts.Working {
-						log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
-						c.Close()
-						return
-					}
-
-					go func(userConn *conn.Conn) {
-						workConn, err := p.getWorkConn()
-						if err != nil {
-							return
-						}
-
-						// message will be transferred to another without modifying
-						// l means local, r means remote
-						log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", workConn.GetLocalAddr(), workConn.GetRemoteAddr(),
-							userConn.GetLocalAddr(), userConn.GetRemoteAddr())
-
-						needRecord := true
-						go msg.JoinMore(userConn, workConn, p.BaseConf, needRecord)
-					}(c)
-				}
-			}(listener)
-		}
-	}
-	return nil
-}
-
-func (p *ProxyServer) Close() {
-	p.Lock()
-	defer p.Unlock()
-
-	oldStatus := p.Status
-	p.Release()
-
-	// if the proxy created by PrivilegeMode, delete it when closed
-	if p.PrivilegeMode && oldStatus == consts.Working {
-		// NOTE: this will take the global ProxyServerMap's lock
-		// if we only want to release resources, use Release() instead
-		DeleteProxy(p.Name)
-	}
-}
-
-func (p *ProxyServer) Release() {
-	if p.Status != consts.Closed {
-		p.Status = consts.Closed
-		for _, l := range p.listeners {
-			if l != nil {
-				l.Close()
-			}
-		}
-		if p.ctlMsgChan != nil {
-			close(p.ctlMsgChan)
-			p.ctlMsgChan = nil
-		}
-		if p.workConnChan != nil {
-			close(p.workConnChan)
-			p.workConnChan = nil
-		}
-		if p.udpSenderChan != nil {
-			close(p.udpSenderChan)
-			p.udpSenderChan = nil
-		}
-		if p.closeChan != nil {
-			close(p.closeChan)
-			p.closeChan = nil
-		}
-		if p.CtlConn != nil {
-			p.CtlConn.Close()
-		}
-		if p.WorkConnUdp != nil {
-			p.WorkConnUdp.Close()
-		}
-		if p.udpConn != nil {
-			p.udpConn.Close()
-			p.udpConn = nil
-		}
-	}
-	metric.SetStatus(p.Name, p.Status)
-}
-
-func (p *ProxyServer) WaitUserConn() (closeFlag bool) {
-	closeFlag = false
-
-	_, ok := <-p.ctlMsgChan
-	if !ok {
-		closeFlag = true
-	}
-	return
-}
-
-func (p *ProxyServer) RegisterNewWorkConn(c *conn.Conn) {
-	select {
-	case p.workConnChan <- c:
-	default:
-		log.Debug("ProxyName [%s], workConnChan is full, so close this work connection", p.Name)
-		c.Close()
-	}
-}
-
-// create a tcp connection for forwarding udp packages
-func (p *ProxyServer) RegisterNewWorkConnUdp(c *conn.Conn) {
-	if p.WorkConnUdp != nil && !p.WorkConnUdp.IsClosed() {
-		p.WorkConnUdp.Close()
-	}
-	p.WorkConnUdp = c
-
-	// read
-	go func() {
-		var (
-			buf string
-			err error
-		)
-		for {
-			buf, err = c.ReadLine()
-			if err != nil {
-				log.Warn("ProxyName [%s], work connection for udp closed", p.Name)
-				return
-			}
-			udpPacket := &msg.UdpPacket{}
-			err = udpPacket.UnPack([]byte(buf))
-			if err != nil {
-				log.Warn("ProxyName [%s], unpack udp packet error: %v", p.Name, err)
-				continue
-			}
-
-			// send to user
-			_, err = p.udpConn.WriteToUDP(udpPacket.Content, udpPacket.Dst)
-			if err != nil {
-				continue
-			}
-		}
-	}()
-
-	// write
-	go func() {
-		for {
-			udpPacket, ok := <-p.udpSenderChan
-			if !ok {
-				return
-			}
-			err := c.WriteString(string(udpPacket.Pack()) + "\n")
-			if err != nil {
-				log.Debug("ProxyName [%s], write to work connection for udp error: %v", p.Name, err)
-				return
-			}
-		}
-	}()
-}
-
-// When frps get one user connection, we get one work connection from the pool and return it.
-// If no workConn available in the pool, send message to frpc to get one or more
-// and wait until it is available.
-// return an error if wait timeout
-func (p *ProxyServer) getWorkConn() (workConn *conn.Conn, err error) {
-	var ok bool
-	// get a work connection from the pool
-	for {
-		select {
-		case workConn, ok = <-p.workConnChan:
-			if !ok {
-				err = fmt.Errorf("ProxyName [%s], no work connections available, control is closing", p.Name)
-				return
-			}
-			log.Debug("ProxyName [%s], get work connection from pool", p.Name)
-		default:
-			// no work connections available in the poll, send message to frpc to get more
-			p.ctlMsgChan <- 1
-
-			select {
-			case workConn, ok = <-p.workConnChan:
-				if !ok {
-					err = fmt.Errorf("ProxyName [%s], no work connections available, control is closing", p.Name)
-					return
-				}
-
-			case <-time.After(time.Duration(UserConnTimeout) * time.Second):
-				log.Warn("ProxyName [%s], timeout trying to get work connection", p.Name)
-				err = fmt.Errorf("ProxyName [%s], timeout trying to get work connection", p.Name)
-				return
-			}
-		}
-
-		// if connection pool is not used, we don't check the status
-		// function CheckClosed will consume at least 1 millisecond if the connection isn't closed
-		if p.PoolCount == 0 || !workConn.CheckClosed() {
-			break
-		} else {
-			log.Warn("ProxyName [%s], connection got from pool, but it's already closed", p.Name)
-		}
-	}
-	return
-}
-
-func (p *ProxyServer) connectionPoolManager(closeCh <-chan struct{}) {
-	defer func() {
-		if r := recover(); r != nil {
-			log.Warn("ProxyName [%s], connectionPoolManager panic %v", p.Name, r)
-		}
-	}()
-
-	for {
-		// check if we need more work connections and send messages to frpc to get more
-		time.Sleep(time.Duration(2) * time.Second)
-		select {
-		// if the channel closed, it means the proxy is closed, so just return
-		case <-closeCh:
-			log.Info("ProxyName [%s], connectionPoolManager exit", p.Name)
-			return
-		default:
-			curWorkConnNum := int64(len(p.workConnChan))
-			diff := p.PoolCount - curWorkConnNum
-			if diff > 0 {
-				if diff < p.PoolCount/5 {
-					diff = p.PoolCount*4/5 + 1
-				} else if diff < p.PoolCount/2 {
-					diff = p.PoolCount/4 + 1
-				} else if diff < p.PoolCount*4/5 {
-					diff = p.PoolCount/5 + 1
-				} else {
-					diff = p.PoolCount/10 + 1
-				}
-				if diff+curWorkConnNum > p.PoolCount {
-					diff = p.PoolCount - curWorkConnNum
-				}
-				for i := 0; i < int(diff); i++ {
-					p.ctlMsgChan <- 1
-				}
-			}
-		}
-	}
-}

+ 0 - 87
src/utils/broadcast/broadcast.go

@@ -1,87 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package broadcast
-
-type Broadcast struct {
-	listeners  []chan interface{}
-	reg        chan (chan interface{})
-	unreg      chan (chan interface{})
-	in         chan interface{}
-	stop       chan int64
-	stopStatus bool
-}
-
-func NewBroadcast() *Broadcast {
-	b := &Broadcast{
-		listeners:  make([]chan interface{}, 0),
-		reg:        make(chan (chan interface{})),
-		unreg:      make(chan (chan interface{})),
-		in:         make(chan interface{}),
-		stop:       make(chan int64),
-		stopStatus: false,
-	}
-
-	go func() {
-		for {
-			select {
-			case l := <-b.unreg:
-				// remove L from b.listeners
-				// this operation is slow: O(n) but not used frequently
-				// unlike iterating over listeners
-				oldListeners := b.listeners
-				b.listeners = make([]chan interface{}, 0, len(oldListeners))
-				for _, oldL := range oldListeners {
-					if l != oldL {
-						b.listeners = append(b.listeners, oldL)
-					}
-				}
-
-			case l := <-b.reg:
-				b.listeners = append(b.listeners, l)
-
-			case item := <-b.in:
-				for _, l := range b.listeners {
-					l <- item
-				}
-
-			case _ = <-b.stop:
-				b.stopStatus = true
-				break
-			}
-		}
-	}()
-
-	return b
-}
-
-func (b *Broadcast) In() chan interface{} {
-	return b.in
-}
-
-func (b *Broadcast) Reg() chan interface{} {
-	listener := make(chan interface{})
-	b.reg <- listener
-	return listener
-}
-
-func (b *Broadcast) UnReg(listener chan interface{}) {
-	b.unreg <- listener
-}
-
-func (b *Broadcast) Close() {
-	if b.stopStatus == false {
-		b.stop <- 1
-	}
-}

+ 0 - 77
src/utils/broadcast/broadcast_test.go

@@ -1,77 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package broadcast
-
-import (
-	"sync"
-	"testing"
-	"time"
-)
-
-var (
-	totalNum int = 5
-	succNum  int = 0
-	mutex    sync.Mutex
-)
-
-func TestBroadcast(t *testing.T) {
-	b := NewBroadcast()
-	if b == nil {
-		t.Fatalf("New Broadcast error, nil return")
-	}
-	defer b.Close()
-
-	var wait sync.WaitGroup
-	wait.Add(totalNum)
-	for i := 0; i < totalNum; i++ {
-		go worker(b, &wait)
-	}
-
-	time.Sleep(1e6 * 20)
-	msg := "test"
-	b.In() <- msg
-
-	wait.Wait()
-	if succNum != totalNum {
-		t.Fatalf("TotalNum %d, FailNum(timeout) %d", totalNum, totalNum-succNum)
-	}
-}
-
-func worker(b *Broadcast, wait *sync.WaitGroup) {
-	defer wait.Done()
-	msgChan := b.Reg()
-
-	// exit if nothing got in 2 seconds
-	timeout := make(chan bool, 1)
-	go func() {
-		time.Sleep(time.Duration(2) * time.Second)
-		timeout <- true
-	}()
-
-	select {
-	case item := <-msgChan:
-		msg := item.(string)
-		if msg == "test" {
-			mutex.Lock()
-			succNum++
-			mutex.Unlock()
-		} else {
-			break
-		}
-
-	case <-timeout:
-		break
-	}
-}

+ 0 - 314
src/utils/conn/conn.go

@@ -1,314 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package conn
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/base64"
-	"fmt"
-	"io"
-	"net"
-	"net/http"
-	"net/url"
-	"strings"
-	"sync"
-	"time"
-
-	"github.com/fatedier/frp/src/utils/pool"
-)
-
-type Listener struct {
-	addr      net.Addr
-	l         *net.TCPListener
-	accept    chan *Conn
-	closeFlag bool
-}
-
-func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
-	tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
-	if err != nil {
-		return l, err
-	}
-	listener, err := net.ListenTCP("tcp", tcpAddr)
-	if err != nil {
-		return l, err
-	}
-
-	l = &Listener{
-		addr:      listener.Addr(),
-		l:         listener,
-		accept:    make(chan *Conn),
-		closeFlag: false,
-	}
-
-	go func() {
-		for {
-			conn, err := l.l.AcceptTCP()
-			if err != nil {
-				if l.closeFlag {
-					return
-				}
-				continue
-			}
-
-			c := NewConn(conn)
-			l.accept <- c
-		}
-	}()
-	return l, err
-}
-
-// wait util get one new connection or listener is closed
-// if listener is closed, err returned
-func (l *Listener) Accept() (*Conn, error) {
-	conn, ok := <-l.accept
-	if !ok {
-		return conn, fmt.Errorf("channel close")
-	}
-	return conn, nil
-}
-
-func (l *Listener) Close() error {
-	if l.l != nil && l.closeFlag == false {
-		l.closeFlag = true
-		l.l.Close()
-		close(l.accept)
-	}
-	return nil
-}
-
-// wrap for TCPConn
-type Conn struct {
-	TcpConn   net.Conn
-	Reader    *bufio.Reader
-	buffer    *bytes.Buffer
-	closeFlag bool
-
-	mutex sync.RWMutex
-}
-
-func NewConn(conn net.Conn) (c *Conn) {
-	c = &Conn{
-		TcpConn:   conn,
-		buffer:    nil,
-		closeFlag: false,
-	}
-	c.Reader = bufio.NewReader(c.TcpConn)
-	return
-}
-
-func ConnectServer(addr string) (c *Conn, err error) {
-	servertAddr, err := net.ResolveTCPAddr("tcp", addr)
-	if err != nil {
-		return
-	}
-	conn, err := net.DialTCP("tcp", nil, servertAddr)
-	if err != nil {
-		return
-	}
-	c = NewConn(conn)
-	return c, nil
-}
-
-func ConnectServerByHttpProxy(httpProxy string, serverAddr string) (c *Conn, err error) {
-	var proxyUrl *url.URL
-	if proxyUrl, err = url.Parse(httpProxy); err != nil {
-		return
-	}
-
-	var proxyAuth string
-	if proxyUrl.User != nil {
-		proxyAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(proxyUrl.User.String()))
-	}
-
-	if proxyUrl.Scheme != "http" {
-		err = fmt.Errorf("Proxy URL scheme must be http, not [%s]", proxyUrl.Scheme)
-		return
-	}
-
-	if c, err = ConnectServer(proxyUrl.Host); err != nil {
-		return
-	}
-
-	req, err := http.NewRequest("CONNECT", "http://"+serverAddr, nil)
-	if err != nil {
-		return
-	}
-	if proxyAuth != "" {
-		req.Header.Set("Proxy-Authorization", proxyAuth)
-	}
-	req.Header.Set("User-Agent", "Mozilla/5.0")
-	req.Write(c.TcpConn)
-
-	resp, err := http.ReadResponse(bufio.NewReader(c), req)
-	if err != nil {
-		return
-	}
-	resp.Body.Close()
-	if resp.StatusCode != 200 {
-		err = fmt.Errorf("ConnectServer using proxy error, StatusCode [%d]", resp.StatusCode)
-		return
-	}
-
-	return
-}
-
-// if the tcpConn is different with c.TcpConn
-// you should call c.Close() first
-func (c *Conn) SetTcpConn(tcpConn net.Conn) {
-	c.mutex.Lock()
-	defer c.mutex.Unlock()
-	c.TcpConn = tcpConn
-	c.closeFlag = false
-	c.Reader = bufio.NewReader(c.TcpConn)
-}
-
-func (c *Conn) GetRemoteAddr() (addr string) {
-	return c.TcpConn.RemoteAddr().String()
-}
-
-func (c *Conn) GetLocalAddr() (addr string) {
-	return c.TcpConn.LocalAddr().String()
-}
-
-func (c *Conn) Read(p []byte) (n int, err error) {
-	c.mutex.RLock()
-	if c.buffer == nil {
-		c.mutex.RUnlock()
-		return c.Reader.Read(p)
-	}
-	c.mutex.RUnlock()
-
-	n, err = c.buffer.Read(p)
-	if err == io.EOF {
-		c.mutex.Lock()
-		c.buffer = nil
-		c.mutex.Unlock()
-		var n2 int
-		n2, err = c.Reader.Read(p[n:])
-
-		n += n2
-	}
-	return
-}
-
-func (c *Conn) ReadLine() (buff string, err error) {
-	buff, err = c.Reader.ReadString('\n')
-	if err != nil {
-		// wsarecv error in windows means connection closed?
-		if err == io.EOF || strings.Contains(err.Error(), "wsarecv") {
-			c.mutex.Lock()
-			c.closeFlag = true
-			c.mutex.Unlock()
-		}
-	}
-	return buff, err
-}
-
-func (c *Conn) Write(content []byte) (n int, err error) {
-	n, err = c.TcpConn.Write(content)
-	return
-}
-
-func (c *Conn) WriteString(content string) (err error) {
-	_, err = c.TcpConn.Write([]byte(content))
-	return err
-}
-
-func (c *Conn) AppendReaderBuffer(content []byte) {
-	c.mutex.Lock()
-	defer c.mutex.Unlock()
-
-	if c.buffer == nil {
-		c.buffer = bytes.NewBuffer(make([]byte, 0, 2048))
-	}
-	c.buffer.Write(content)
-}
-
-func (c *Conn) SetDeadline(t time.Time) error {
-	return c.TcpConn.SetDeadline(t)
-}
-
-func (c *Conn) SetReadDeadline(t time.Time) error {
-	return c.TcpConn.SetReadDeadline(t)
-}
-
-func (c *Conn) Close() error {
-	c.mutex.Lock()
-	defer c.mutex.Unlock()
-	if c.TcpConn != nil && c.closeFlag == false {
-		c.closeFlag = true
-		c.TcpConn.Close()
-	}
-	return nil
-}
-
-func (c *Conn) IsClosed() (closeFlag bool) {
-	c.mutex.RLock()
-	defer c.mutex.RUnlock()
-	closeFlag = c.closeFlag
-	return
-}
-
-// when you call this function, you should make sure that
-// no bytes were read before
-func (c *Conn) CheckClosed() bool {
-	c.mutex.RLock()
-	if c.closeFlag {
-		c.mutex.RUnlock()
-		return true
-	}
-	c.mutex.RUnlock()
-
-	tmp := pool.GetBuf(2048)
-	defer pool.PutBuf(tmp)
-	err := c.TcpConn.SetReadDeadline(time.Now().Add(time.Millisecond))
-	if err != nil {
-		c.Close()
-		return true
-	}
-
-	n, err := c.TcpConn.Read(tmp)
-	if err == io.EOF {
-		return true
-	}
-
-	var tmp2 []byte = make([]byte, 1)
-	err = c.TcpConn.SetReadDeadline(time.Now().Add(time.Millisecond))
-	if err != nil {
-		c.Close()
-		return true
-	}
-
-	n2, err := c.TcpConn.Read(tmp2)
-	if err == io.EOF {
-		return true
-	}
-
-	err = c.TcpConn.SetReadDeadline(time.Time{})
-	if err != nil {
-		c.Close()
-		return true
-	}
-
-	if n > 0 {
-		c.AppendReaderBuffer(tmp[:n])
-	}
-	if n2 > 0 {
-		c.AppendReaderBuffer(tmp2[:n2])
-	}
-	return false
-}

+ 0 - 139
src/utils/pcrypto/pcrypto.go

@@ -1,139 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pcrypto
-
-import (
-	"bytes"
-	"compress/gzip"
-	"crypto/aes"
-	"crypto/cipher"
-	"crypto/md5"
-	"crypto/rand"
-	"encoding/hex"
-	"fmt"
-	"io"
-	"io/ioutil"
-)
-
-type Pcrypto struct {
-	pkey []byte
-	paes cipher.Block
-}
-
-func (pc *Pcrypto) Init(key []byte) error {
-	var err error
-	pc.pkey = pkKeyPadding(key)
-	pc.paes, err = aes.NewCipher(pc.pkey)
-	return err
-}
-
-func (pc *Pcrypto) Encrypt(src []byte) ([]byte, error) {
-	// aes
-	src = pKCS5Padding(src, aes.BlockSize)
-	ciphertext := make([]byte, aes.BlockSize+len(src))
-
-	// The IV needs to be unique, but not secure. Therefore it's common to
-	// include it at the beginning of the ciphertext.
-	iv := ciphertext[:aes.BlockSize]
-	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
-		return nil, err
-	}
-	blockMode := cipher.NewCBCEncrypter(pc.paes, iv)
-	blockMode.CryptBlocks(ciphertext[aes.BlockSize:], src)
-	return ciphertext, nil
-}
-
-func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
-	// aes
-	ciphertext, err := hex.DecodeString(fmt.Sprintf("%x", str))
-	if err != nil {
-		return nil, err
-	}
-
-	if len(ciphertext) < aes.BlockSize {
-		return nil, fmt.Errorf("ciphertext too short")
-	}
-	iv := ciphertext[:aes.BlockSize]
-	ciphertext = ciphertext[aes.BlockSize:]
-
-	if len(ciphertext)%aes.BlockSize != 0 {
-		return nil, fmt.Errorf("crypto/cipher: ciphertext is not a multiple of the block size")
-	}
-
-	blockMode := cipher.NewCBCDecrypter(pc.paes, iv)
-	blockMode.CryptBlocks(ciphertext, ciphertext)
-	return pKCS5UnPadding(ciphertext), nil
-}
-
-func (pc *Pcrypto) Compression(src []byte) ([]byte, error) {
-	var zbuf bytes.Buffer
-	zwr, err := gzip.NewWriterLevel(&zbuf, gzip.DefaultCompression)
-	if err != nil {
-		return nil, err
-	}
-	defer zwr.Close()
-	zwr.Write(src)
-	zwr.Flush()
-	return zbuf.Bytes(), nil
-}
-
-func (pc *Pcrypto) Decompression(src []byte) ([]byte, error) {
-	zbuf := bytes.NewBuffer(src)
-	zrd, err := gzip.NewReader(zbuf)
-	if err != nil {
-		return nil, err
-	}
-	defer zrd.Close()
-	str, _ := ioutil.ReadAll(zrd)
-	return str, nil
-}
-
-func pkKeyPadding(key []byte) []byte {
-	l := len(key)
-	if l == 16 || l == 24 || l == 32 {
-		return key
-	}
-	if l < 16 {
-		return append(key, bytes.Repeat([]byte{byte(0)}, 16-l)...)
-	} else if l < 24 {
-		return append(key, bytes.Repeat([]byte{byte(0)}, 24-l)...)
-	} else if l < 32 {
-		return append(key, bytes.Repeat([]byte{byte(0)}, 32-l)...)
-	} else {
-		md5Ctx := md5.New()
-		md5Ctx.Write(key)
-		md5Str := md5Ctx.Sum(nil)
-		return []byte(hex.EncodeToString(md5Str))
-	}
-}
-
-func pKCS5Padding(ciphertext []byte, blockSize int) []byte {
-	padding := blockSize - len(ciphertext)%blockSize
-	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
-	return append(ciphertext, padtext...)
-}
-
-func pKCS5UnPadding(origData []byte) []byte {
-	length := len(origData)
-	unpadding := int(origData[length-1])
-	return origData[:(length - unpadding)]
-}
-
-func GetAuthKey(str string) (authKey string) {
-	md5Ctx := md5.New()
-	md5Ctx.Write([]byte(str))
-	md5Str := md5Ctx.Sum(nil)
-	return hex.EncodeToString(md5Str)
-}

+ 0 - 77
src/utils/pcrypto/pcrypto_test.go

@@ -1,77 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package pcrypto
-
-import (
-	"testing"
-)
-
-var (
-	pp *Pcrypto
-)
-
-func init() {
-	pp = &Pcrypto{}
-	pp.Init([]byte("12234567890123451223456789012345321:wq"))
-}
-
-func TestEncrypt(t *testing.T) {
-	testStr := "Test Encrypt!"
-	res, err := pp.Encrypt([]byte(testStr))
-	if err != nil {
-		t.Fatalf("encrypt error: %v", err)
-	}
-
-	res, err = pp.Decrypt([]byte(res))
-	if err != nil {
-		t.Fatalf("decrypt error: %v", err)
-	}
-
-	if string(res) != testStr {
-		t.Fatalf("test encrypt error, from [%s] to [%s]", testStr, string(res))
-	}
-}
-
-func TestCompression(t *testing.T) {
-	testStr := "Test Compression!"
-	res, err := pp.Compression([]byte(testStr))
-	if err != nil {
-		t.Fatalf("compression error: %v", err)
-	}
-
-	res, err = pp.Decompression(res)
-	if err != nil {
-		t.Fatalf("decompression error: %v", err)
-	}
-
-	if string(res) != testStr {
-		t.Fatalf("test compression error, from [%s] to [%s]", testStr, string(res))
-	}
-}
-
-func BenchmarkEncrypt(b *testing.B) {
-	testStr := "Test Encrypt!"
-	for i := 0; i < b.N; i++ {
-		pp.Encrypt([]byte(testStr))
-	}
-}
-
-func BenchmarkDecrypt(b *testing.B) {
-	testStr := "Test Encrypt!"
-	res, _ := pp.Encrypt([]byte(testStr))
-	for i := 0; i < b.N; i++ {
-		pp.Decrypt([]byte(res))
-	}
-}

+ 7 - 5
test/echo_server.go

@@ -1,10 +1,11 @@
 package main
 
 import (
+	"bufio"
 	"fmt"
 	"io"
 
-	"github.com/fatedier/frp/src/utils/conn"
+	"github.com/fatedier/frp/utils/net"
 )
 
 var (
@@ -12,7 +13,7 @@ var (
 )
 
 func main() {
-	l, err := conn.Listen("0.0.0.0", PORT)
+	l, err := net.ListenTcp("127.0.0.1", PORT)
 	if err != nil {
 		fmt.Printf("echo server listen error: %v\n", err)
 		return
@@ -29,9 +30,10 @@ func main() {
 	}
 }
 
-func echoWorker(c *conn.Conn) {
+func echoWorker(c net.Conn) {
+	br := bufio.NewReader(c)
 	for {
-		buff, err := c.ReadLine()
+		buf, err := br.ReadString('\n')
 		if err == io.EOF {
 			break
 		}
@@ -40,6 +42,6 @@ func echoWorker(c *conn.Conn) {
 			return
 		}
 
-		c.WriteString(buff)
+		c.Write([]byte(buf + "\n"))
 	}
 }

+ 9 - 6
test/func_test.go

@@ -1,6 +1,7 @@
 package test
 
 import (
+	"bufio"
 	"fmt"
 	"io/ioutil"
 	"net/http"
@@ -8,7 +9,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/fatedier/frp/src/utils/conn"
+	"github.com/fatedier/frp/utils/net"
 )
 
 var (
@@ -19,22 +20,24 @@ var (
 )
 
 func TestEchoServer(t *testing.T) {
-	c, err := conn.ConnectServer(fmt.Sprintf("0.0.0.0:%d", ECHO_PORT))
+	c, err := net.ConnectTcpServer(fmt.Sprintf("127.0.0.1:%d", ECHO_PORT))
 	if err != nil {
 		t.Fatalf("connect to echo server error: %v", err)
 	}
 	timer := time.Now().Add(time.Duration(5) * time.Second)
 	c.SetDeadline(timer)
 
-	c.WriteString(ECHO_TEST_STR)
+	c.Write([]byte(ECHO_TEST_STR))
+	c.Write('\n')
 
-	buff, err := c.ReadLine()
+	br := bufio.NewReader(c)
+	buf, err := br.ReadString('\n')
 	if err != nil {
 		t.Fatalf("read from echo server error: %v", err)
 	}
 
-	if ECHO_TEST_STR != buff {
-		t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buff, "\n"))
+	if ECHO_TEST_STR != buf {
+		t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buf, "\n"))
 	}
 }
 

+ 52 - 0
utils/crypto/crypto_test.go

@@ -0,0 +1,52 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package crypto
+
+import (
+	"bytes"
+	"io"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestWriter(t *testing.T) {
+	// Empty key.
+	assert := assert.New(t)
+	key := ""
+	buffer := bytes.NewBuffer(nil)
+	_, err := NewWriter(buffer, []byte(key))
+	assert.NoError(err)
+}
+
+func TestCrypto(t *testing.T) {
+	assert := assert.New(t)
+
+	text := "1234567890abcdefghigklmnopqrstuvwxyzeeeeeeeeeeeeeeeeeeeeeewwwwwwwwwwwwwwwwwwwwwwwwwwzzzzzzzzzzzzzzzzzzzzzzzzdddddddddddddddddddddddddddddddddddddrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrllllllllllllllllllllllllllllllllllqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeewwwwwwwwwwwwwwwwwwwwww"
+	key := "123456"
+
+	buffer := bytes.NewBuffer(nil)
+	encWriter, err := NewWriter(buffer, []byte(key))
+	assert.NoError(err)
+
+	encWriter.Write([]byte(text))
+
+	decReader, err := NewReader(buffer, []byte(key))
+	assert.NoError(err)
+
+	c := bytes.NewBuffer(nil)
+	io.Copy(c, decReader)
+	assert.Equal(text, string(c.Bytes()))
+}

+ 75 - 0
utils/crypto/decode.go

@@ -0,0 +1,75 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package crypto
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/sha1"
+	"io"
+
+	"golang.org/x/crypto/pbkdf2"
+)
+
+// NewReader returns a new Reader that decrypts bytes from r
+func NewReader(r io.Reader, key []byte) (*Reader, error) {
+	key = pbkdf2.Key(key, []byte(salt), 64, aes.BlockSize, sha1.New)
+
+	return &Reader{
+		r:   r,
+		key: key,
+	}, nil
+}
+
+// Reader is an io.Reader that can read encrypted bytes.
+// Now it only supports aes-128-cfb.
+type Reader struct {
+	r   io.Reader
+	dec *cipher.StreamReader
+	key []byte
+	iv  []byte
+	err error
+}
+
+// Read satisfies the io.Reader interface.
+func (r *Reader) Read(p []byte) (nRet int, errRet error) {
+	if r.err != nil {
+		return 0, r.err
+	}
+
+	if r.dec == nil {
+		iv := make([]byte, aes.BlockSize)
+		if _, errRet = io.ReadFull(r.r, iv); errRet != nil {
+			return
+		}
+		r.iv = iv
+
+		block, err := aes.NewCipher(r.key)
+		if err != nil {
+			errRet = err
+			return
+		}
+		r.dec = &cipher.StreamReader{
+			S: cipher.NewCFBDecrypter(block, iv),
+			R: r.r,
+		}
+	}
+
+	nRet, errRet = r.dec.Read(p)
+	if errRet != nil {
+		r.err = errRet
+	}
+	return
+}

+ 93 - 0
utils/crypto/encode.go

@@ -0,0 +1,93 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package crypto
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"crypto/sha1"
+	"io"
+
+	"golang.org/x/crypto/pbkdf2"
+)
+
+const (
+	salt = "frp"
+)
+
+// NewWriter returns a new Writer that encrypts bytes to w.
+func NewWriter(w io.Writer, key []byte) (*Writer, error) {
+	key = pbkdf2.Key(key, []byte(salt), 64, aes.BlockSize, sha1.New)
+
+	// random iv
+	iv := make([]byte, aes.BlockSize)
+	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+		return nil, err
+	}
+
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Writer{
+		w: w,
+		enc: &cipher.StreamWriter{
+			S: cipher.NewCFBEncrypter(block, iv),
+			W: w,
+		},
+		key: key,
+		iv:  iv,
+	}, nil
+}
+
+// Writer is an io.Writer that can write encrypted bytes.
+// Now it only support aes-128-cfb.
+type Writer struct {
+	w      io.Writer
+	enc    *cipher.StreamWriter
+	key    []byte
+	iv     []byte
+	ivSend bool
+	err    error
+}
+
+// Write satisfies the io.Writer interface.
+func (w *Writer) Write(p []byte) (nRet int, errRet error) {
+	return w.write(p)
+}
+
+func (w *Writer) write(p []byte) (nRet int, errRet error) {
+	if w.err != nil {
+		return 0, w.err
+	}
+
+	// When write is first called, iv will be written to w.w
+	if !w.ivSend {
+		w.ivSend = true
+		_, errRet = w.w.Write(w.iv)
+		if errRet != nil {
+			w.err = errRet
+			return
+		}
+	}
+
+	nRet, errRet = w.enc.Write(p)
+	if errRet != nil {
+		w.err = errRet
+	}
+	return
+}

+ 14 - 8
src/utils/conn/udp_conn.go → utils/errors/errors.go

@@ -12,18 +12,24 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package conn
+package errors
 
 import (
+	"errors"
 	"fmt"
-	"net"
 )
 
-func ListenUDP(bindAddr string, bindPort int64) (conn *net.UDPConn, err error) {
-	udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
-	if err != nil {
-		return conn, err
-	}
-	conn, err = net.ListenUDP("udp", udpAddr)
+var (
+	ErrMsgType = errors.New("message type error")
+)
+
+func PanicToError(fn func()) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = fmt.Errorf("Panic error: %v", r)
+		}
+	}()
+
+	fn()
 	return
 }

+ 55 - 2
src/utils/log/log.go → utils/log/log.go

@@ -16,13 +16,14 @@ package log
 
 import (
 	"fmt"
-	"github.com/astaxie/beego/logs"
+
+	"github.com/fatedier/beego/logs"
 )
 
 var Log *logs.BeeLogger
 
 func init() {
-	Log = logs.NewLogger(1000)
+	Log = logs.NewLogger(100)
 	Log.EnableFuncCallDepth(true)
 	Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
 }
@@ -61,6 +62,7 @@ func SetLogLevel(logLevel string) {
 }
 
 // wrap log
+
 func Error(format string, v ...interface{}) {
 	Log.Error(format, v...)
 }
@@ -76,3 +78,54 @@ func Info(format string, v ...interface{}) {
 func Debug(format string, v ...interface{}) {
 	Log.Debug(format, v...)
 }
+
+// Logger
+type Logger interface {
+	AddLogPrefix(string)
+	ClearLogPrefix()
+	Error(string, ...interface{})
+	Warn(string, ...interface{})
+	Info(string, ...interface{})
+	Debug(string, ...interface{})
+}
+
+type PrefixLogger struct {
+	prefix string
+}
+
+func NewPrefixLogger(prefix string) *PrefixLogger {
+	logger := &PrefixLogger{}
+	logger.AddLogPrefix(prefix)
+	return logger
+}
+
+func (pl *PrefixLogger) AddLogPrefix(prefix string) {
+	if len(prefix) == 0 {
+		return
+	}
+
+	if len(pl.prefix) > 0 {
+		pl.prefix += " "
+	}
+	pl.prefix += "[" + prefix + "] "
+}
+
+func (pl *PrefixLogger) ClearLogPrefix() {
+	pl.prefix = ""
+}
+
+func (pl *PrefixLogger) Error(format string, v ...interface{}) {
+	Log.Error(pl.prefix+format, v...)
+}
+
+func (pl *PrefixLogger) Warn(format string, v ...interface{}) {
+	Log.Warn(pl.prefix+format, v...)
+}
+
+func (pl *PrefixLogger) Info(format string, v ...interface{}) {
+	Log.Info(pl.prefix+format, v...)
+}
+
+func (pl *PrefixLogger) Debug(format string, v ...interface{}) {
+	Log.Debug(pl.prefix+format, v...)
+}

+ 16 - 14
src/models/config/config.go → utils/net/conn.go

@@ -12,19 +12,21 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package config
+package net
 
-type BaseConf struct {
-	Name              string
-	AuthToken         string
-	Type              string
-	UseEncryption     bool
-	UseGzip           bool
-	PrivilegeMode     bool
-	PrivilegeToken    string
-	PoolCount         int64
-	HostHeaderRewrite string
-	HttpUserName      string
-	HttpPassWord      string
-	SubDomain         string
+import (
+	"net"
+
+	"github.com/fatedier/frp/utils/log"
+)
+
+// Conn is the interface of connections used in frp.
+type Conn interface {
+	net.Conn
+	log.Logger
+}
+
+type Listener interface {
+	Accept() (Conn, error)
+	Close() error
 }

+ 162 - 0
utils/net/tcp.go

@@ -0,0 +1,162 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net
+
+import (
+	"bufio"
+	"encoding/base64"
+	"fmt"
+	"net"
+	"net/http"
+	"net/url"
+
+	"github.com/fatedier/frp/utils/log"
+)
+
+type TcpListener struct {
+	net.Addr
+	listener  net.Listener
+	accept    chan Conn
+	closeFlag bool
+}
+
+func ListenTcp(bindAddr string, bindPort int64) (l *TcpListener, err error) {
+	tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
+	if err != nil {
+		return l, err
+	}
+	listener, err := net.ListenTCP("tcp", tcpAddr)
+	if err != nil {
+		return l, err
+	}
+
+	l = &TcpListener{
+		Addr:      listener.Addr(),
+		listener:  listener,
+		accept:    make(chan Conn),
+		closeFlag: false,
+	}
+
+	go func() {
+		for {
+			conn, err := listener.AcceptTCP()
+			if err != nil {
+				if l.closeFlag {
+					close(l.accept)
+					return
+				}
+				continue
+			}
+
+			c := NewTcpConn(conn)
+			l.accept <- c
+		}
+	}()
+	return l, err
+}
+
+// Wait util get one new connection or listener is closed
+// if listener is closed, err returned.
+func (l *TcpListener) Accept() (Conn, error) {
+	conn, ok := <-l.accept
+	if !ok {
+		return conn, fmt.Errorf("channel for tcp listener closed")
+	}
+	return conn, nil
+}
+
+func (l *TcpListener) Close() error {
+	if !l.closeFlag {
+		l.closeFlag = true
+		l.listener.Close()
+	}
+	return nil
+}
+
+// Wrap for TCPConn.
+type TcpConn struct {
+	net.Conn
+	log.Logger
+}
+
+func NewTcpConn(conn *net.TCPConn) (c *TcpConn) {
+	c = &TcpConn{
+		Conn:   conn,
+		Logger: log.NewPrefixLogger(""),
+	}
+	return
+}
+
+func ConnectTcpServer(addr string) (c Conn, err error) {
+	servertAddr, err := net.ResolveTCPAddr("tcp", addr)
+	if err != nil {
+		return
+	}
+	conn, err := net.DialTCP("tcp", nil, servertAddr)
+	if err != nil {
+		return
+	}
+	c = NewTcpConn(conn)
+	return
+}
+
+// ConnectTcpServerByHttpProxy try to connect remote server by http proxy.
+// If httpProxy is empty, it will connect server directly.
+func ConnectTcpServerByHttpProxy(httpProxy string, serverAddr string) (c Conn, err error) {
+	if httpProxy == "" {
+		return ConnectTcpServer(serverAddr)
+	}
+
+	var proxyUrl *url.URL
+	if proxyUrl, err = url.Parse(httpProxy); err != nil {
+		return
+	}
+
+	var proxyAuth string
+	if proxyUrl.User != nil {
+		proxyAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(proxyUrl.User.String()))
+	}
+
+	if proxyUrl.Scheme != "http" {
+		err = fmt.Errorf("Proxy URL scheme must be http, not [%s]", proxyUrl.Scheme)
+		return
+	}
+
+	if c, err = ConnectTcpServer(proxyUrl.Host); err != nil {
+		return
+	}
+
+	req, err := http.NewRequest("CONNECT", "http://"+serverAddr, nil)
+	if err != nil {
+		return
+	}
+	if proxyAuth != "" {
+		req.Header.Set("Proxy-Authorization", proxyAuth)
+	}
+	req.Header.Set("User-Agent", "Mozilla/5.0")
+	req.Write(c)
+
+	resp, err := http.ReadResponse(bufio.NewReader(c), req)
+	if err != nil {
+		return
+	}
+	resp.Body.Close()
+	if resp.StatusCode != 200 {
+		err = fmt.Errorf("ConnectTcpServer using proxy error, StatusCode [%d]", resp.StatusCode)
+		return
+	}
+
+	return
+}

+ 243 - 0
utils/net/udp.go

@@ -0,0 +1,243 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"sync"
+	"time"
+
+	flog "github.com/fatedier/frp/utils/log"
+	"github.com/fatedier/frp/utils/pool"
+)
+
+type UdpPacket struct {
+	Buf        []byte
+	LocalAddr  net.Addr
+	RemoteAddr net.Addr
+}
+
+type FakeUdpConn struct {
+	flog.Logger
+	l *UdpListener
+
+	localAddr  net.Addr
+	remoteAddr net.Addr
+	packets    chan []byte
+	closeFlag  bool
+
+	lastActive time.Time
+	mu         sync.RWMutex
+}
+
+func NewFakeUdpConn(l *UdpListener, laddr, raddr net.Addr) *FakeUdpConn {
+	fc := &FakeUdpConn{
+		Logger:     flog.NewPrefixLogger(""),
+		l:          l,
+		localAddr:  laddr,
+		remoteAddr: raddr,
+		packets:    make(chan []byte, 20),
+	}
+
+	go func() {
+		for {
+			time.Sleep(5 * time.Second)
+			fc.mu.RLock()
+			if time.Now().Sub(fc.lastActive) > 10*time.Second {
+				fc.mu.RUnlock()
+				fc.Close()
+				break
+			}
+			fc.mu.RUnlock()
+		}
+	}()
+	return fc
+}
+
+func (c *FakeUdpConn) putPacket(content []byte) {
+	defer func() {
+		if err := recover(); err != nil {
+		}
+	}()
+
+	select {
+	case c.packets <- content:
+	default:
+	}
+}
+
+func (c *FakeUdpConn) Read(b []byte) (n int, err error) {
+	content, ok := <-c.packets
+	if !ok {
+		return 0, io.EOF
+	}
+	c.mu.Lock()
+	c.lastActive = time.Now()
+	c.mu.Unlock()
+
+	if len(b) < len(content) {
+		n = len(b)
+	} else {
+		n = len(content)
+	}
+	copy(b, content)
+	return n, nil
+}
+
+func (c *FakeUdpConn) Write(b []byte) (n int, err error) {
+	c.mu.RLock()
+	if c.closeFlag {
+		c.mu.RUnlock()
+		return 0, io.ErrClosedPipe
+	}
+	c.mu.RUnlock()
+
+	packet := &UdpPacket{
+		Buf:        b,
+		LocalAddr:  c.localAddr,
+		RemoteAddr: c.remoteAddr,
+	}
+	c.l.writeUdpPacket(packet)
+
+	c.mu.Lock()
+	c.lastActive = time.Now()
+	c.mu.Unlock()
+	return len(b), nil
+}
+
+func (c *FakeUdpConn) Close() error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	if !c.closeFlag {
+		c.closeFlag = true
+		close(c.packets)
+	}
+	return nil
+}
+
+func (c *FakeUdpConn) IsClosed() bool {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	return c.closeFlag
+}
+
+func (c *FakeUdpConn) LocalAddr() net.Addr {
+	return c.localAddr
+}
+
+func (c *FakeUdpConn) RemoteAddr() net.Addr {
+	return c.remoteAddr
+}
+
+func (c *FakeUdpConn) SetDeadline(t time.Time) error {
+	return nil
+}
+
+func (c *FakeUdpConn) SetReadDeadline(t time.Time) error {
+	return nil
+}
+
+func (c *FakeUdpConn) SetWriteDeadline(t time.Time) error {
+	return nil
+}
+
+type UdpListener struct {
+	net.Addr
+	accept    chan Conn
+	writeCh   chan *UdpPacket
+	readConn  net.Conn
+	closeFlag bool
+
+	fakeConns map[string]*FakeUdpConn
+}
+
+func ListenUDP(bindAddr string, bindPort int64) (l *UdpListener, err error) {
+	udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
+	if err != nil {
+		return l, err
+	}
+	readConn, err := net.ListenUDP("udp", udpAddr)
+
+	l = &UdpListener{
+		Addr:      udpAddr,
+		accept:    make(chan Conn),
+		writeCh:   make(chan *UdpPacket, 1000),
+		fakeConns: make(map[string]*FakeUdpConn),
+	}
+
+	// for reading
+	go func() {
+		for {
+			buf := pool.GetBuf(1450)
+			n, remoteAddr, err := readConn.ReadFromUDP(buf)
+			if err != nil {
+				close(l.accept)
+				close(l.writeCh)
+				return
+			}
+
+			fakeConn, exist := l.fakeConns[remoteAddr.String()]
+			if !exist || fakeConn.IsClosed() {
+				fakeConn = NewFakeUdpConn(l, l.Addr, remoteAddr)
+				l.fakeConns[remoteAddr.String()] = fakeConn
+			}
+			fakeConn.putPacket(buf[:n])
+
+			l.accept <- fakeConn
+		}
+	}()
+
+	// for writing
+	go func() {
+		for {
+			packet, ok := <-l.writeCh
+			if !ok {
+				return
+			}
+
+			if addr, ok := packet.RemoteAddr.(*net.UDPAddr); ok {
+				readConn.WriteToUDP(packet.Buf, addr)
+			}
+		}
+	}()
+
+	return
+}
+
+func (l *UdpListener) writeUdpPacket(packet *UdpPacket) {
+	defer func() {
+		if err := recover(); err != nil {
+		}
+	}()
+	l.writeCh <- packet
+}
+
+func (l *UdpListener) Accept() (Conn, error) {
+	conn, ok := <-l.accept
+	if !ok {
+		return conn, fmt.Errorf("channel for udp listener closed")
+	}
+	return conn, nil
+}
+
+func (l *UdpListener) Close() error {
+	if !l.closeFlag {
+		l.closeFlag = true
+		l.readConn.Close()
+	}
+	return nil
+}

+ 27 - 0
utils/net/udp_test.go

@@ -0,0 +1,27 @@
+package net
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestA(t *testing.T) {
+	l, err := ListenUDP("0.0.0.0", 9000)
+	if err != nil {
+		fmt.Println(err)
+	}
+	for {
+		c, _ := l.Accept()
+		go func() {
+			for {
+				buf := make([]byte, 1450)
+				n, err := c.Read(buf)
+				if err != nil {
+					fmt.Println(buf[:n])
+				}
+
+				c.Write(buf[:n])
+			}
+		}()
+	}
+}

+ 0 - 0
src/utils/pool/pool.go → utils/pool/pool.go


+ 51 - 0
utils/pool/pool_test.go

@@ -0,0 +1,51 @@
+// Copyright 2016 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package pool
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPutBuf(t *testing.T) {
+	buf := make([]byte, 512)
+	PutBuf(buf)
+
+	buf = make([]byte, 1025)
+	PutBuf(buf)
+
+	buf = make([]byte, 2*1025)
+	PutBuf(buf)
+
+	buf = make([]byte, 5*1025)
+	PutBuf(buf)
+}
+
+func TestGetBuf(t *testing.T) {
+	assert := assert.New(t)
+
+	buf := GetBuf(200)
+	assert.Len(buf, 200)
+
+	buf = GetBuf(1025)
+	assert.Len(buf, 1025)
+
+	buf = GetBuf(2 * 1024)
+	assert.Len(buf, 2*1024)
+
+	buf = GetBuf(5 * 2000)
+	assert.Len(buf, 5*2000)
+}

+ 62 - 0
utils/shutdown/shutdown.go

@@ -0,0 +1,62 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package shutdown
+
+import (
+	"sync"
+)
+
+type Shutdown struct {
+	doing  bool
+	ending bool
+	start  chan struct{}
+	down   chan struct{}
+	mu     sync.Mutex
+}
+
+func New() *Shutdown {
+	return &Shutdown{
+		doing:  false,
+		ending: false,
+		start:  make(chan struct{}),
+		down:   make(chan struct{}),
+	}
+}
+
+func (s *Shutdown) Start() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if !s.doing {
+		s.doing = true
+		close(s.start)
+	}
+}
+
+func (s *Shutdown) WaitStart() {
+	<-s.start
+}
+
+func (s *Shutdown) Done() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if !s.ending {
+		s.ending = true
+		close(s.down)
+	}
+}
+
+func (s *Shutdown) WaitDown() {
+	<-s.down
+}

+ 21 - 0
utils/shutdown/shutdown_test.go

@@ -0,0 +1,21 @@
+package shutdown
+
+import (
+	"testing"
+	"time"
+)
+
+func TestShutdown(t *testing.T) {
+	s := New()
+	go func() {
+		time.Sleep(time.Millisecond)
+		s.Start()
+	}()
+	s.WaitStart()
+
+	go func() {
+		time.Sleep(time.Millisecond)
+		s.Done()
+	}()
+	s.WaitDown()
+}

+ 47 - 0
utils/util/util.go

@@ -0,0 +1,47 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package util
+
+import (
+	"crypto/md5"
+	"crypto/rand"
+	"encoding/hex"
+	"fmt"
+)
+
+// RandId return a rand string used in frp.
+func RandId() (id string, err error) {
+	return RandIdWithLen(8)
+}
+
+// RandIdWithLen return a rand string with idLen length.
+func RandIdWithLen(idLen int) (id string, err error) {
+	b := make([]byte, idLen)
+	_, err = rand.Read(b)
+	if err != nil {
+		return
+	}
+
+	id = fmt.Sprintf("%x", b)
+	return
+}
+
+func GetAuthKey(token string, timestamp int64) (key string) {
+	token = token + fmt.Sprintf("%d", timestamp)
+	md5Ctx := md5.New()
+	md5Ctx.Write([]byte(token))
+	data := md5Ctx.Sum(nil)
+	return hex.EncodeToString(data)
+}

+ 22 - 0
utils/util/util_test.go

@@ -0,0 +1,22 @@
+package util
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestRandId(t *testing.T) {
+	assert := assert.New(t)
+	id, err := RandId()
+	assert.NoError(err)
+	t.Log(id)
+	assert.Equal(16, len(id))
+}
+
+func TestGetAuthKey(t *testing.T) {
+	assert := assert.New(t)
+	key := GetAuthKey("1234", 1488720000)
+	t.Log(key)
+	assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
+}

+ 36 - 6
src/utils/version/version.go → utils/version/version.go

@@ -19,7 +19,7 @@ import (
 	"strings"
 )
 
-var version string = "0.9.3"
+var version string = "0.10.0"
 
 func Full() string {
 	return version
@@ -27,7 +27,7 @@ func Full() string {
 
 func Proto(v string) int64 {
 	arr := strings.Split(v, ".")
-	if len(arr) < 2 {
+	if len(arr) < 3 {
 		return 0
 	}
 	res, _ := strconv.ParseInt(arr[0], 10, 64)
@@ -36,7 +36,7 @@ func Proto(v string) int64 {
 
 func Major(v string) int64 {
 	arr := strings.Split(v, ".")
-	if len(arr) < 2 {
+	if len(arr) < 3 {
 		return 0
 	}
 	res, _ := strconv.ParseInt(arr[1], 10, 64)
@@ -45,7 +45,7 @@ func Major(v string) int64 {
 
 func Minor(v string) int64 {
 	arr := strings.Split(v, ".")
-	if len(arr) < 2 {
+	if len(arr) < 3 {
 		return 0
 	}
 	res, _ := strconv.ParseInt(arr[2], 10, 64)
@@ -53,6 +53,36 @@ func Minor(v string) int64 {
 }
 
 // add every case there if server will not accept client's protocol and return false
-func Compat(client string, server string) bool {
-	return true
+func Compat(client string) (ok bool, msg string) {
+	if LessThan(client, version) {
+		return false, "Please upgrade your frpc version to 0.10.0"
+	}
+	return true, ""
+}
+
+func LessThan(client string, server string) bool {
+	vc := Proto(client)
+	vs := Proto(server)
+	if vc > vs {
+		return false
+	} else if vc < vs {
+		return true
+	}
+
+	vc = Major(client)
+	vs = Major(server)
+	if vc > vs {
+		return false
+	} else if vc < vs {
+		return true
+	}
+
+	vc = Minor(client)
+	vs = Minor(server)
+	if vc > vs {
+		return false
+	} else if vc < vs {
+		return true
+	}
+	return false
 }

+ 24 - 15
src/utils/version/version_test.go → utils/version/version_test.go

@@ -19,38 +19,47 @@ import (
 	"strconv"
 	"strings"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestFull(t *testing.T) {
+	assert := assert.New(t)
 	version := Full()
 	arr := strings.Split(version, ".")
-	if len(arr) != 3 {
-		t.Fatalf("Version string error: %s", version)
-	}
+	assert.Equal(3, len(arr))
 
 	proto, err := strconv.ParseInt(arr[0], 10, 64)
-	if err != nil || proto < 0 {
-		t.Fatalf("Version proto error")
-	}
+	assert.NoError(err)
+	assert.True(proto >= 0)
 
 	major, err := strconv.ParseInt(arr[1], 10, 64)
-	if err != nil || major < 0 {
-		t.Fatalf("Version major error")
-	}
+	assert.NoError(err)
+	assert.True(major >= 0)
 
 	minor, err := strconv.ParseInt(arr[2], 10, 64)
-	if err != nil || minor < 0 {
-		t.Fatalf("Version minor error")
-	}
+	assert.NoError(err)
+	assert.True(minor >= 0)
 }
 
 func TestVersion(t *testing.T) {
+	assert := assert.New(t)
 	proto := Proto(Full())
 	major := Major(Full())
 	minor := Minor(Full())
 	parseVerion := fmt.Sprintf("%d.%d.%d", proto, major, minor)
 	version := Full()
-	if parseVerion != version {
-		t.Fatalf("Get version incorrect, version [%s], parseVerion [%s]", version, parseVerion)
-	}
+	assert.Equal(parseVerion, version)
+}
+
+func TestCompact(t *testing.T) {
+	assert := assert.New(t)
+	ok, _ := Compat("0.9.0")
+	assert.False(ok)
+
+	ok, _ = Compat("10.0.0")
+	assert.True(ok)
+
+	ok, _ = Compat("0.10.0")
+	assert.False(ok)
 }

+ 9 - 10
src/utils/vhost/http.go → utils/vhost/http.go

@@ -20,23 +20,22 @@ import (
 	"encoding/base64"
 	"fmt"
 	"io"
-	"net"
 	"net/http"
 	"net/url"
 	"strings"
 	"time"
 
-	"github.com/fatedier/frp/src/utils/conn"
-	"github.com/fatedier/frp/src/utils/pool"
+	frpNet "github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/pool"
 )
 
 type HttpMuxer struct {
 	*VhostMuxer
 }
 
-func GetHttpRequestInfo(c *conn.Conn) (_ net.Conn, _ map[string]string, err error) {
+func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
 	reqInfoMap := make(map[string]string, 0)
-	sc, rd := newShareConn(c.TcpConn)
+	sc, rd := newShareConn(c)
 
 	request, err := http.ReadRequest(bufio.NewReader(rd))
 	if err != nil {
@@ -57,13 +56,13 @@ func GetHttpRequestInfo(c *conn.Conn) (_ net.Conn, _ map[string]string, err erro
 	return sc, reqInfoMap, nil
 }
 
-func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, error) {
+func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) {
 	mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, HttpHostNameRewrite, timeout)
 	return &HttpMuxer{mux}, err
 }
 
-func HttpHostNameRewrite(c *conn.Conn, rewriteHost string) (_ net.Conn, err error) {
-	sc, rd := newShareConn(c.TcpConn)
+func HttpHostNameRewrite(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
+	sc, rd := newShareConn(c)
 	var buff []byte
 	if buff, err = hostNameRewrite(rd, rewriteHost); err != nil {
 		return sc, err
@@ -181,11 +180,11 @@ func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error
 	return retBuf.Bytes(), err
 }
 
-func HttpAuthFunc(c *conn.Conn, userName, passWord, authorization string) (bAccess bool, err error) {
+func HttpAuthFunc(c frpNet.Conn, userName, passWord, authorization string) (bAccess bool, err error) {
 	s := strings.SplitN(authorization, " ", 2)
 	if len(s) != 2 {
 		res := noAuthResponse()
-		res.Write(c.TcpConn)
+		res.Write(c)
 		return
 	}
 	b, err := base64.StdEncoding.DecodeString(s[1])

+ 5 - 6
src/utils/vhost/https.go → utils/vhost/https.go

@@ -17,12 +17,11 @@ package vhost
 import (
 	"fmt"
 	"io"
-	"net"
 	"strings"
 	"time"
 
-	"github.com/fatedier/frp/src/utils/conn"
-	"github.com/fatedier/frp/src/utils/pool"
+	frpNet "github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/pool"
 )
 
 const (
@@ -47,7 +46,7 @@ type HttpsMuxer struct {
 	*VhostMuxer
 }
 
-func NewHttpsMuxer(listener *conn.Listener, timeout time.Duration) (*HttpsMuxer, error) {
+func NewHttpsMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpsMuxer, error) {
 	mux, err := NewVhostMuxer(listener, GetHttpsHostname, nil, nil, timeout)
 	return &HttpsMuxer{mux}, err
 }
@@ -178,9 +177,9 @@ func readHandshake(rd io.Reader) (host string, err error) {
 	return
 }
 
-func GetHttpsHostname(c *conn.Conn) (sc net.Conn, _ map[string]string, err error) {
+func GetHttpsHostname(c frpNet.Conn) (sc frpNet.Conn, _ map[string]string, err error) {
 	reqInfoMap := make(map[string]string, 0)
-	sc, rd := newShareConn(c.TcpConn)
+	sc, rd := newShareConn(c)
 	host, err := readHandshake(rd)
 	if err != nil {
 		return sc, reqInfoMap, err

+ 0 - 0
src/utils/vhost/router.go → utils/vhost/router.go


+ 15 - 16
src/utils/vhost/vhost.go → utils/vhost/vhost.go

@@ -16,20 +16,19 @@ import (
 	"bytes"
 	"fmt"
 	"io"
-	"net"
 	"strings"
 	"sync"
 	"time"
 
-	"github.com/fatedier/frp/src/utils/conn"
+	frpNet "github.com/fatedier/frp/utils/net"
 )
 
-type muxFunc func(*conn.Conn) (net.Conn, map[string]string, error)
-type httpAuthFunc func(*conn.Conn, string, string, string) (bool, error)
-type hostRewriteFunc func(*conn.Conn, string) (net.Conn, error)
+type muxFunc func(frpNet.Conn) (frpNet.Conn, map[string]string, error)
+type httpAuthFunc func(frpNet.Conn, string, string, string) (bool, error)
+type hostRewriteFunc func(frpNet.Conn, string) (frpNet.Conn, error)
 
 type VhostMuxer struct {
-	listener       *conn.Listener
+	listener       frpNet.Listener
 	timeout        time.Duration
 	vhostFunc      muxFunc
 	authFunc       httpAuthFunc
@@ -38,7 +37,7 @@ type VhostMuxer struct {
 	mutex          sync.RWMutex
 }
 
-func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
+func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
 	mux = &VhostMuxer{
 		listener:       listener,
 		timeout:        timeout,
@@ -68,7 +67,7 @@ func (v *VhostMuxer) Listen(name, location, rewriteHost, userName, passWord stri
 		userName:    userName,
 		passWord:    passWord,
 		mux:         v,
-		accept:      make(chan *conn.Conn),
+		accept:      make(chan frpNet.Conn),
 	}
 	v.registryRouter.Add(name, location, l)
 	return l, nil
@@ -110,7 +109,7 @@ func (v *VhostMuxer) run() {
 	}
 }
 
-func (v *VhostMuxer) handle(c *conn.Conn) {
+func (v *VhostMuxer) handle(c frpNet.Conn) {
 	if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil {
 		c.Close()
 		return
@@ -137,7 +136,7 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
 		bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])
 		if bAccess == false || err != nil {
 			res := noAuthResponse()
-			res.Write(c.TcpConn)
+			res.Write(c)
 			c.Close()
 			return
 		}
@@ -147,7 +146,7 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
 		c.Close()
 		return
 	}
-	c.SetTcpConn(sConn)
+	c = sConn
 
 	l.accept <- c
 }
@@ -159,10 +158,10 @@ type Listener struct {
 	userName    string
 	passWord    string
 	mux         *VhostMuxer // for closing VhostMuxer
-	accept      chan *conn.Conn
+	accept      chan frpNet.Conn
 }
 
-func (l *Listener) Accept() (*conn.Conn, error) {
+func (l *Listener) Accept() (frpNet.Conn, error) {
 	conn, ok := <-l.accept
 	if !ok {
 		return nil, fmt.Errorf("Listener closed")
@@ -175,7 +174,7 @@ func (l *Listener) Accept() (*conn.Conn, error) {
 		if err != nil {
 			return nil, fmt.Errorf("http host header rewrite failed")
 		}
-		conn.SetTcpConn(sConn)
+		conn = sConn
 	}
 
 	return conn, nil
@@ -192,13 +191,13 @@ func (l *Listener) Name() string {
 }
 
 type sharedConn struct {
-	net.Conn
+	frpNet.Conn
 	sync.Mutex
 	buff *bytes.Buffer
 }
 
 // the bytes you read in io.Reader, will be reserved in sharedConn
-func newShareConn(conn net.Conn) (*sharedConn, io.Reader) {
+func newShareConn(conn frpNet.Conn) (*sharedConn, io.Reader) {
 	sc := &sharedConn{
 		Conn: conn,
 		buff: bytes.NewBuffer(make([]byte, 0, 1024)),

+ 0 - 0
vendor/github.com/astaxie/beego/LICENSE → vendor/github.com/fatedier/beego/LICENSE


+ 0 - 0
vendor/github.com/astaxie/beego/logs/README.md → vendor/github.com/fatedier/beego/logs/README.md


+ 0 - 0
vendor/github.com/astaxie/beego/logs/color.go → vendor/github.com/fatedier/beego/logs/color.go


+ 0 - 0
vendor/github.com/astaxie/beego/logs/color_windows.go → vendor/github.com/fatedier/beego/logs/color_windows.go


+ 0 - 0
vendor/github.com/astaxie/beego/logs/conn.go → vendor/github.com/fatedier/beego/logs/conn.go


+ 0 - 0
vendor/github.com/astaxie/beego/logs/console.go → vendor/github.com/fatedier/beego/logs/console.go


+ 1 - 0
vendor/github.com/astaxie/beego/logs/file.go → vendor/github.com/fatedier/beego/logs/file.go

@@ -270,6 +270,7 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
 	// Rename the file to its new found name
 	// even if occurs error,we MUST guarantee to  restart new logger
 	err = os.Rename(w.Filename, fName)
+	err = os.Chmod(fName, os.FileMode(440))
 	// re-start logger
 RESTART_LOGGER:
 

+ 78 - 0
vendor/github.com/fatedier/beego/logs/jianliao.go

@@ -0,0 +1,78 @@
+package logs
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+	"time"
+)
+
+// JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook
+type JLWriter struct {
+	AuthorName  string `json:"authorname"`
+	Title       string `json:"title"`
+	WebhookURL  string `json:"webhookurl"`
+	RedirectURL string `json:"redirecturl,omitempty"`
+	ImageURL    string `json:"imageurl,omitempty"`
+	Level       int    `json:"level"`
+}
+
+// newJLWriter create jiaoliao writer.
+func newJLWriter() Logger {
+	return &JLWriter{Level: LevelTrace}
+}
+
+// Init JLWriter with json config string
+func (s *JLWriter) Init(jsonconfig string) error {
+	err := json.Unmarshal([]byte(jsonconfig), s)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// WriteMsg write message in smtp writer.
+// it will send an email with subject and only this message.
+func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error {
+	if level > s.Level {
+		return nil
+	}
+
+	text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg)
+
+	form := url.Values{}
+	form.Add("authorName", s.AuthorName)
+	form.Add("title", s.Title)
+	form.Add("text", text)
+	if s.RedirectURL != "" {
+		form.Add("redirectUrl", s.RedirectURL)
+	}
+	if s.ImageURL != "" {
+		form.Add("imageUrl", s.ImageURL)
+	}
+
+	resp, err := http.PostForm(s.WebhookURL, form)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
+	}
+	return nil
+}
+
+// Flush implementing method. empty.
+func (s *JLWriter) Flush() {
+	return
+}
+
+// Destroy implementing method. empty.
+func (s *JLWriter) Destroy() {
+	return
+}
+
+func init() {
+	Register(AdapterJianLiao, newJLWriter)
+}

+ 36 - 12
vendor/github.com/astaxie/beego/logs/log.go → vendor/github.com/fatedier/beego/logs/log.go

@@ -66,9 +66,12 @@ const (
 	AdapterConsole   = "console"
 	AdapterFile      = "file"
 	AdapterMultiFile = "multifile"
-	AdapterMail      = "stmp"
+	AdapterMail      = "smtp"
 	AdapterConn      = "conn"
 	AdapterEs        = "es"
+	AdapterJianLiao  = "jianliao"
+	AdapterSlack     = "slack"
+	AdapterAliLS     = "alils"
 )
 
 // Legacy log level constants to ensure backwards compatibility.
@@ -260,12 +263,7 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
 		bl.setLogger(AdapterConsole)
 		bl.lock.Unlock()
 	}
-	if logLevel == levelLoggerImpl {
-		// set to emergency to ensure all log will be print out correctly
-		logLevel = LevelEmergency
-	} else {
-		msg = levelPrefix[logLevel] + msg
-	}
+
 	if len(v) > 0 {
 		msg = fmt.Sprintf(msg, v...)
 	}
@@ -275,10 +273,27 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
 		if !ok {
 			file = "???"
 			line = 0
+		} else {
+			if strings.Contains(file, "<autogenerated>") {
+				_, file, line, ok = runtime.Caller(bl.loggerFuncCallDepth + 1)
+				if !ok {
+					file = "???"
+					line = 0
+				}
+			}
 		}
 		_, filename := path.Split(file)
 		msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "] " + msg
 	}
+
+	//set level info in front of filename info
+	if logLevel == levelLoggerImpl {
+		// set to emergency to ensure all log will be print out correctly
+		logLevel = LevelEmergency
+	} else {
+		msg = levelPrefix[logLevel] + msg
+	}
+
 	if bl.asynchronous {
 		lm := logMsgPool.Get().(*logMsg)
 		lm.level = logLevel
@@ -374,7 +389,10 @@ func (bl *BeeLogger) Error(format string, v ...interface{}) {
 
 // Warning Log WARNING level message.
 func (bl *BeeLogger) Warning(format string, v ...interface{}) {
-	bl.Warn(format, v...)
+	if LevelWarn > bl.level {
+		return
+	}
+	bl.writeMsg(LevelWarn, format, v...)
 }
 
 // Notice Log NOTICE level message.
@@ -387,7 +405,10 @@ func (bl *BeeLogger) Notice(format string, v ...interface{}) {
 
 // Informational Log INFORMATIONAL level message.
 func (bl *BeeLogger) Informational(format string, v ...interface{}) {
-	bl.Info(format, v...)
+	if LevelInfo > bl.level {
+		return
+	}
+	bl.writeMsg(LevelInfo, format, v...)
 }
 
 // Debug Log DEBUG level message.
@@ -419,7 +440,10 @@ func (bl *BeeLogger) Info(format string, v ...interface{}) {
 // Trace Log TRACE level message.
 // compatibility alias for Debug()
 func (bl *BeeLogger) Trace(format string, v ...interface{}) {
-	bl.Debug(format, v...)
+	if LevelDebug > bl.level {
+		return
+	}
+	bl.writeMsg(LevelDebug, format, v...)
 }
 
 // Flush flush all chan data.
@@ -532,10 +556,10 @@ func EnableFuncCallDepth(b bool) {
 	beeLogger.enableFuncCallDepth = b
 }
 
-// SetLogFuncCall set the CallDepth, default is 3
+// SetLogFuncCall set the CallDepth, default is 4
 func SetLogFuncCall(b bool) {
 	beeLogger.EnableFuncCallDepth(b)
-	beeLogger.SetLogFuncCallDepth(3)
+	beeLogger.SetLogFuncCallDepth(4)
 }
 
 // SetLogFuncCallDepth set log funcCallDepth

+ 0 - 0
vendor/github.com/astaxie/beego/logs/logger.go → vendor/github.com/fatedier/beego/logs/logger.go


+ 0 - 0
vendor/github.com/astaxie/beego/logs/multifile.go → vendor/github.com/fatedier/beego/logs/multifile.go


Some files were not shown because too many files changed in this diff