ソースを参照

Merge pull request #1415 from fatedier/dev

bump version to v0.29.0
fatedier 6 年 前
コミット
e62d9a5242

+ 2 - 1
README.md

@@ -66,7 +66,7 @@ Now it also try to support p2p connect.
 
 
 frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
 frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
 
 
-**We may change any protocol and can't promise backward compatible. Please check the release log when upgrading.**
+**We may change any protocol and can't promise backward compatibility. Please check the release log when upgrading.**
 
 
 ## Architecture
 ## Architecture
 
 
@@ -265,6 +265,7 @@ Configure frps same as above.
   plugin_crt_path = ./server.crt
   plugin_crt_path = ./server.crt
   plugin_key_path = ./server.key
   plugin_key_path = ./server.key
   plugin_host_header_rewrite = 127.0.0.1
   plugin_host_header_rewrite = 127.0.0.1
+  plugin_header_X-From-Where = frp
   ```
   ```
 
 
 2. Visit `https://test.yourdomain.com`.
 2. Visit `https://test.yourdomain.com`.

+ 2 - 1
README_zh.md

@@ -129,7 +129,7 @@ master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试
   vhost_http_port = 8080
   vhost_http_port = 8080
   ```
   ```
 
 
-2. 启动 frps
+2. 启动 frps
 
 
   `./frps -c ./frps.ini`
   `./frps -c ./frps.ini`
 
 
@@ -270,6 +270,7 @@ frps 的部署步骤同上。
   plugin_crt_path = ./server.crt
   plugin_crt_path = ./server.crt
   plugin_key_path = ./server.key
   plugin_key_path = ./server.key
   plugin_host_header_rewrite = 127.0.0.1
   plugin_host_header_rewrite = 127.0.0.1
+  plugin_header_X-From-Where = frp
   ```
   ```
 
 
 2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
 2. 通过浏览器访问 `https://test.yourdomain.com` 即可。

+ 1 - 2
client/admin.go

@@ -21,7 +21,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/fatedier/frp/assets"
 	"github.com/fatedier/frp/assets"
-	"github.com/fatedier/frp/g"
 	frpNet "github.com/fatedier/frp/utils/net"
 	frpNet "github.com/fatedier/frp/utils/net"
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
@@ -36,7 +35,7 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
 	// url router
 	// url router
 	router := mux.NewRouter()
 	router := mux.NewRouter()
 
 
-	user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd
+	user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
 	router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
 	router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
 
 
 	// api, see dashboard_api.go
 	// api, see dashboard_api.go

+ 18 - 19
client/admin_api.go

@@ -23,7 +23,6 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/fatedier/frp/client/proxy"
 	"github.com/fatedier/frp/client/proxy"
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/utils/log"
 	"github.com/fatedier/frp/utils/log"
 )
 )
@@ -47,7 +46,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
 		}
 		}
 	}()
 	}()
 
 
-	content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
+	content, err := config.GetRenderedConfFromFile(svr.cfgFile)
 	if err != nil {
 	if err != nil {
 		res.Code = 400
 		res.Code = 400
 		res.Msg = err.Error()
 		res.Msg = err.Error()
@@ -55,7 +54,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
-	newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
+	newCommonCfg, err := config.UnmarshalClientConfFromIni(content)
 	if err != nil {
 	if err != nil {
 		res.Code = 400
 		res.Code = 400
 		res.Msg = err.Error()
 		res.Msg = err.Error()
@@ -63,7 +62,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
-	pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
+	pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(svr.cfg.User, content, newCommonCfg.Start)
 	if err != nil {
 	if err != nil {
 		res.Code = 400
 		res.Code = 400
 		res.Msg = err.Error()
 		res.Msg = err.Error()
@@ -107,7 +106,7 @@ func (a ByProxyStatusResp) Len() int           { return len(a) }
 func (a ByProxyStatusResp) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 func (a ByProxyStatusResp) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
 func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
 
 
-func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
+func NewProxyStatusResp(status *proxy.ProxyStatus, serverAddr string) ProxyStatusResp {
 	psr := ProxyStatusResp{
 	psr := ProxyStatusResp{
 		Name:   status.Name,
 		Name:   status.Name,
 		Type:   status.Type,
 		Type:   status.Type,
@@ -121,18 +120,18 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
 		}
 		}
 		psr.Plugin = cfg.Plugin
 		psr.Plugin = cfg.Plugin
 		if status.Err != "" {
 		if status.Err != "" {
-			psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
+			psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
 		} else {
 		} else {
-			psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
+			psr.RemoteAddr = serverAddr + status.RemoteAddr
 		}
 		}
 	case *config.UdpProxyConf:
 	case *config.UdpProxyConf:
 		if cfg.LocalPort != 0 {
 		if cfg.LocalPort != 0 {
 			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
 			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
 		}
 		}
 		if status.Err != "" {
 		if status.Err != "" {
-			psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
+			psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
 		} else {
 		} else {
-			psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
+			psr.RemoteAddr = serverAddr + status.RemoteAddr
 		}
 		}
 	case *config.HttpProxyConf:
 	case *config.HttpProxyConf:
 		if cfg.LocalPort != 0 {
 		if cfg.LocalPort != 0 {
@@ -184,17 +183,17 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
 	for _, status := range ps {
 	for _, status := range ps {
 		switch status.Type {
 		switch status.Type {
 		case "tcp":
 		case "tcp":
-			res.Tcp = append(res.Tcp, NewProxyStatusResp(status))
+			res.Tcp = append(res.Tcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		case "udp":
 		case "udp":
-			res.Udp = append(res.Udp, NewProxyStatusResp(status))
+			res.Udp = append(res.Udp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		case "http":
 		case "http":
-			res.Http = append(res.Http, NewProxyStatusResp(status))
+			res.Http = append(res.Http, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		case "https":
 		case "https":
-			res.Https = append(res.Https, NewProxyStatusResp(status))
+			res.Https = append(res.Https, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		case "stcp":
 		case "stcp":
-			res.Stcp = append(res.Stcp, NewProxyStatusResp(status))
+			res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		case "xtcp":
 		case "xtcp":
-			res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status))
+			res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		}
 		}
 	}
 	}
 	sort.Sort(ByProxyStatusResp(res.Tcp))
 	sort.Sort(ByProxyStatusResp(res.Tcp))
@@ -219,14 +218,14 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
 		}
 		}
 	}()
 	}()
 
 
-	if g.GlbClientCfg.CfgFile == "" {
+	if svr.cfgFile == "" {
 		res.Code = 400
 		res.Code = 400
 		res.Msg = "frpc has no config file path"
 		res.Msg = "frpc has no config file path"
 		log.Warn("%s", res.Msg)
 		log.Warn("%s", res.Msg)
 		return
 		return
 	}
 	}
 
 
-	content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
+	content, err := config.GetRenderedConfFromFile(svr.cfgFile)
 	if err != nil {
 	if err != nil {
 		res.Code = 400
 		res.Code = 400
 		res.Msg = err.Error()
 		res.Msg = err.Error()
@@ -277,7 +276,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
 
 
 	// get token from origin content
 	// get token from origin content
 	token := ""
 	token := ""
-	b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
+	b, err := ioutil.ReadFile(svr.cfgFile)
 	if err != nil {
 	if err != nil {
 		res.Code = 400
 		res.Code = 400
 		res.Msg = err.Error()
 		res.Msg = err.Error()
@@ -316,7 +315,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	content = strings.Join(newRows, "\n")
 	content = strings.Join(newRows, "\n")
 
 
-	err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
+	err = ioutil.WriteFile(svr.cfgFile, []byte(content), 0644)
 	if err != nil {
 	if err != nil {
 		res.Code = 500
 		res.Code = 500
 		res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
 		res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)

+ 20 - 11
client/control.go

@@ -23,7 +23,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/fatedier/frp/client/proxy"
 	"github.com/fatedier/frp/client/proxy"
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/utils/log"
 	"github.com/fatedier/frp/utils/log"
@@ -65,16 +64,24 @@ type Control struct {
 	// last time got the Pong message
 	// last time got the Pong message
 	lastPong time.Time
 	lastPong time.Time
 
 
+	// The client configuration
+	clientCfg config.ClientCommonConf
+
 	readerShutdown     *shutdown.Shutdown
 	readerShutdown     *shutdown.Shutdown
 	writerShutdown     *shutdown.Shutdown
 	writerShutdown     *shutdown.Shutdown
 	msgHandlerShutdown *shutdown.Shutdown
 	msgHandlerShutdown *shutdown.Shutdown
 
 
+	// The UDP port that the server is listening on
+	serverUDPPort int
+
 	mu sync.RWMutex
 	mu sync.RWMutex
 
 
 	log.Logger
 	log.Logger
 }
 }
 
 
-func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control {
+func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, clientCfg config.ClientCommonConf,
+	pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, serverUDPPort int) *Control {
+
 	ctl := &Control{
 	ctl := &Control{
 		runId:              runId,
 		runId:              runId,
 		conn:               conn,
 		conn:               conn,
@@ -84,12 +91,14 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs m
 		readCh:             make(chan msg.Message, 100),
 		readCh:             make(chan msg.Message, 100),
 		closedCh:           make(chan struct{}),
 		closedCh:           make(chan struct{}),
 		closedDoneCh:       make(chan struct{}),
 		closedDoneCh:       make(chan struct{}),
+		clientCfg:          clientCfg,
 		readerShutdown:     shutdown.New(),
 		readerShutdown:     shutdown.New(),
 		writerShutdown:     shutdown.New(),
 		writerShutdown:     shutdown.New(),
 		msgHandlerShutdown: shutdown.New(),
 		msgHandlerShutdown: shutdown.New(),
+		serverUDPPort:      serverUDPPort,
 		Logger:             log.NewPrefixLogger(""),
 		Logger:             log.NewPrefixLogger(""),
 	}
 	}
-	ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId)
+	ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId, clientCfg, serverUDPPort)
 
 
 	ctl.vm = NewVisitorManager(ctl)
 	ctl.vm = NewVisitorManager(ctl)
 	ctl.vm.Reload(visitorCfgs)
 	ctl.vm.Reload(visitorCfgs)
@@ -161,7 +170,7 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} {
 
 
 // connectServer return a new connection to frps
 // connectServer return a new connection to frps
 func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
 func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
-	if g.GlbClientCfg.TcpMux {
+	if ctl.clientCfg.TcpMux {
 		stream, errRet := ctl.session.OpenStream()
 		stream, errRet := ctl.session.OpenStream()
 		if errRet != nil {
 		if errRet != nil {
 			err = errRet
 			err = errRet
@@ -171,13 +180,13 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
 		conn = frpNet.WrapConn(stream)
 		conn = frpNet.WrapConn(stream)
 	} else {
 	} else {
 		var tlsConfig *tls.Config
 		var tlsConfig *tls.Config
-		if g.GlbClientCfg.TLSEnable {
+		if ctl.clientCfg.TLSEnable {
 			tlsConfig = &tls.Config{
 			tlsConfig = &tls.Config{
 				InsecureSkipVerify: true,
 				InsecureSkipVerify: true,
 			}
 			}
 		}
 		}
-		conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
-			fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
+		conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HttpProxy, ctl.clientCfg.Protocol,
+			fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig)
 		if err != nil {
 		if err != nil {
 			ctl.Warn("start new connection to server error: %v", err)
 			ctl.Warn("start new connection to server error: %v", err)
 			return
 			return
@@ -197,7 +206,7 @@ func (ctl *Control) reader() {
 	defer ctl.readerShutdown.Done()
 	defer ctl.readerShutdown.Done()
 	defer close(ctl.closedCh)
 	defer close(ctl.closedCh)
 
 
-	encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token))
+	encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
 	for {
 	for {
 		if m, err := msg.ReadMsg(encReader); err != nil {
 		if m, err := msg.ReadMsg(encReader); err != nil {
 			if err == io.EOF {
 			if err == io.EOF {
@@ -217,7 +226,7 @@ func (ctl *Control) reader() {
 // writer writes messages got from sendCh to frps
 // writer writes messages got from sendCh to frps
 func (ctl *Control) writer() {
 func (ctl *Control) writer() {
 	defer ctl.writerShutdown.Done()
 	defer ctl.writerShutdown.Done()
-	encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token))
+	encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
 	if err != nil {
 	if err != nil {
 		ctl.conn.Error("crypto new writer error: %v", err)
 		ctl.conn.Error("crypto new writer error: %v", err)
 		ctl.conn.Close()
 		ctl.conn.Close()
@@ -246,7 +255,7 @@ func (ctl *Control) msgHandler() {
 	}()
 	}()
 	defer ctl.msgHandlerShutdown.Done()
 	defer ctl.msgHandlerShutdown.Done()
 
 
-	hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second)
+	hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartBeatInterval) * time.Second)
 	defer hbSend.Stop()
 	defer hbSend.Stop()
 	hbCheck := time.NewTicker(time.Second)
 	hbCheck := time.NewTicker(time.Second)
 	defer hbCheck.Stop()
 	defer hbCheck.Stop()
@@ -260,7 +269,7 @@ func (ctl *Control) msgHandler() {
 			ctl.Debug("send heartbeat to server")
 			ctl.Debug("send heartbeat to server")
 			ctl.sendCh <- &msg.Ping{}
 			ctl.sendCh <- &msg.Ping{}
 		case <-hbCheck.C:
 		case <-hbCheck.C:
-			if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second {
+			if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second {
 				ctl.Warn("heartbeat timeout")
 				ctl.Warn("heartbeat timeout")
 				// let reader() stop
 				// let reader() stop
 				ctl.conn.Close()
 				ctl.conn.Close()

+ 14 - 11
client/proxy/proxy.go

@@ -25,7 +25,6 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/plugin"
 	"github.com/fatedier/frp/models/plugin"
@@ -51,9 +50,11 @@ type Proxy interface {
 	log.Logger
 	log.Logger
 }
 }
 
 
-func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
+func NewProxy(pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
 	baseProxy := BaseProxy{
 	baseProxy := BaseProxy{
-		Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
+		Logger:        log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
+		clientCfg:     clientCfg,
+		serverUDPPort: serverUDPPort,
 	}
 	}
 	switch cfg := pxyConf.(type) {
 	switch cfg := pxyConf.(type) {
 	case *config.TcpProxyConf:
 	case *config.TcpProxyConf:
@@ -91,8 +92,10 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
 }
 }
 
 
 type BaseProxy struct {
 type BaseProxy struct {
-	closed bool
-	mu     sync.RWMutex
+	closed        bool
+	mu            sync.RWMutex
+	clientCfg     config.ClientCommonConf
+	serverUDPPort int
 	log.Logger
 	log.Logger
 }
 }
 
 
@@ -122,7 +125,7 @@ func (pxy *TcpProxy) Close() {
 
 
 func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
-		[]byte(g.GlbClientCfg.Token), m)
+		[]byte(pxy.clientCfg.Token), m)
 }
 }
 
 
 // HTTP
 // HTTP
@@ -151,7 +154,7 @@ func (pxy *HttpProxy) Close() {
 
 
 func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
-		[]byte(g.GlbClientCfg.Token), m)
+		[]byte(pxy.clientCfg.Token), m)
 }
 }
 
 
 // HTTPS
 // HTTPS
@@ -180,7 +183,7 @@ func (pxy *HttpsProxy) Close() {
 
 
 func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
-		[]byte(g.GlbClientCfg.Token), m)
+		[]byte(pxy.clientCfg.Token), m)
 }
 }
 
 
 // STCP
 // STCP
@@ -209,7 +212,7 @@ func (pxy *StcpProxy) Close() {
 
 
 func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
-		[]byte(g.GlbClientCfg.Token), m)
+		[]byte(pxy.clientCfg.Token), m)
 }
 }
 
 
 // XTCP
 // XTCP
@@ -250,7 +253,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 		Sid:       natHoleSidMsg.Sid,
 		Sid:       natHoleSidMsg.Sid,
 	}
 	}
 	raddr, _ := net.ResolveUDPAddr("udp",
 	raddr, _ := net.ResolveUDPAddr("udp",
-		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
+		fmt.Sprintf("%s:%d", pxy.clientCfg.ServerAddr, pxy.serverUDPPort))
 	clientConn, err := net.DialUDP("udp", nil, raddr)
 	clientConn, err := net.DialUDP("udp", nil, raddr)
 	defer clientConn.Close()
 	defer clientConn.Close()
 
 
@@ -518,7 +521,7 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
 				DestinationPort:    m.DstPort,
 				DestinationPort:    m.DstPort,
 			}
 			}
 
 
-			if h.SourceAddress.To16() == nil {
+			if strings.Contains(m.SrcAddr, ".") {
 				h.TransportProtocol = pp.TCPv4
 				h.TransportProtocol = pp.TCPv4
 			} else {
 			} else {
 				h.TransportProtocol = pp.TCPv6
 				h.TransportProtocol = pp.TCPv6

+ 14 - 7
client/proxy/proxy_manager.go

@@ -20,17 +20,24 @@ type ProxyManager struct {
 	closed bool
 	closed bool
 	mu     sync.RWMutex
 	mu     sync.RWMutex
 
 
+	clientCfg config.ClientCommonConf
+
+	// The UDP port that the server is listening on
+	serverUDPPort int
+
 	logPrefix string
 	logPrefix string
 	log.Logger
 	log.Logger
 }
 }
 
 
-func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
+func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string, clientCfg config.ClientCommonConf, serverUDPPort int) *ProxyManager {
 	return &ProxyManager{
 	return &ProxyManager{
-		proxies:   make(map[string]*ProxyWrapper),
-		sendCh:    msgSendCh,
-		closed:    false,
-		logPrefix: logPrefix,
-		Logger:    log.NewPrefixLogger(logPrefix),
+		proxies:       make(map[string]*ProxyWrapper),
+		sendCh:        msgSendCh,
+		closed:        false,
+		clientCfg:     clientCfg,
+		serverUDPPort: serverUDPPort,
+		logPrefix:     logPrefix,
+		Logger:        log.NewPrefixLogger(logPrefix),
 	}
 	}
 }
 }
 
 
@@ -126,7 +133,7 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
 	addPxyNames := make([]string, 0)
 	addPxyNames := make([]string, 0)
 	for name, cfg := range pxyCfgs {
 	for name, cfg := range pxyCfgs {
 		if _, ok := pm.proxies[name]; !ok {
 		if _, ok := pm.proxies[name]; !ok {
-			pxy := NewProxyWrapper(cfg, pm.HandleEvent, pm.logPrefix)
+			pxy := NewProxyWrapper(cfg, pm.clientCfg, pm.HandleEvent, pm.logPrefix, pm.serverUDPPort)
 			pm.proxies[name] = pxy
 			pm.proxies[name] = pxy
 			addPxyNames = append(addPxyNames, name)
 			addPxyNames = append(addPxyNames, name)
 
 

+ 2 - 2
client/proxy/proxy_wrapper.go

@@ -65,7 +65,7 @@ type ProxyWrapper struct {
 	log.Logger
 	log.Logger
 }
 }
 
 
-func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper {
+func NewProxyWrapper(cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, logPrefix string, serverUDPPort int) *ProxyWrapper {
 	baseInfo := cfg.GetBaseInfo()
 	baseInfo := cfg.GetBaseInfo()
 	pw := &ProxyWrapper{
 	pw := &ProxyWrapper{
 		ProxyStatus: ProxyStatus{
 		ProxyStatus: ProxyStatus{
@@ -90,7 +90,7 @@ func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logP
 		pw.Trace("enable health check monitor")
 		pw.Trace("enable health check monitor")
 	}
 	}
 
 
-	pw.pxy = NewProxy(pw.Cfg)
+	pw.pxy = NewProxy(pw.Cfg, clientCfg, serverUDPPort)
 	return pw
 	return pw
 }
 }
 
 

+ 33 - 23
client/service.go

@@ -24,7 +24,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/fatedier/frp/assets"
 	"github.com/fatedier/frp/assets"
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/utils/log"
 	"github.com/fatedier/frp/utils/log"
@@ -35,6 +34,7 @@ import (
 	fmux "github.com/hashicorp/yamux"
 	fmux "github.com/hashicorp/yamux"
 )
 )
 
 
+// Service is a client service.
 type Service struct {
 type Service struct {
 	// uniq id got from frps, attach it in loginMsg
 	// uniq id got from frps, attach it in loginMsg
 	runId string
 	runId string
@@ -43,23 +43,27 @@ type Service struct {
 	ctl   *Control
 	ctl   *Control
 	ctlMu sync.RWMutex
 	ctlMu sync.RWMutex
 
 
+	cfg         config.ClientCommonConf
 	pxyCfgs     map[string]config.ProxyConf
 	pxyCfgs     map[string]config.ProxyConf
 	visitorCfgs map[string]config.VisitorConf
 	visitorCfgs map[string]config.VisitorConf
 	cfgMu       sync.RWMutex
 	cfgMu       sync.RWMutex
 
 
+	// The configuration file used to initialize this client, or an empty
+	// string if no configuration file was used.
+	cfgFile string
+
+	// This is configured by the login response from frps
+	serverUDPPort int
+
 	exit     uint32 // 0 means not exit
 	exit     uint32 // 0 means not exit
 	closedCh chan int
 	closedCh chan int
 }
 }
 
 
-func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service, err error) {
-	// Init assets
-	err = assets.Load("")
-	if err != nil {
-		err = fmt.Errorf("Load assets error: %v", err)
-		return
-	}
-
+// NewService creates a new client service with the given configuration.
+func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {
 	svr = &Service{
 	svr = &Service{
+		cfg:         cfg,
+		cfgFile:     cfgFile,
 		pxyCfgs:     pxyCfgs,
 		pxyCfgs:     pxyCfgs,
 		visitorCfgs: visitorCfgs,
 		visitorCfgs: visitorCfgs,
 		exit:        0,
 		exit:        0,
@@ -83,14 +87,14 @@ func (svr *Service) Run() error {
 
 
 			// if login_fail_exit is true, just exit this program
 			// if login_fail_exit is true, just exit this program
 			// otherwise sleep a while and try again to connect to server
 			// otherwise sleep a while and try again to connect to server
-			if g.GlbClientCfg.LoginFailExit {
+			if svr.cfg.LoginFailExit {
 				return err
 				return err
 			} else {
 			} else {
 				time.Sleep(10 * time.Second)
 				time.Sleep(10 * time.Second)
 			}
 			}
 		} else {
 		} else {
 			// login success
 			// login success
-			ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
+			ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
 			ctl.Run()
 			ctl.Run()
 			svr.ctlMu.Lock()
 			svr.ctlMu.Lock()
 			svr.ctl = ctl
 			svr.ctl = ctl
@@ -101,12 +105,18 @@ func (svr *Service) Run() error {
 
 
 	go svr.keepControllerWorking()
 	go svr.keepControllerWorking()
 
 
-	if g.GlbClientCfg.AdminPort != 0 {
-		err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
+	if svr.cfg.AdminPort != 0 {
+		// Init admin server assets
+		err := assets.Load(svr.cfg.AssetsDir)
+		if err != nil {
+			return fmt.Errorf("Load assets error: %v", err)
+		}
+
+		err = svr.RunAdminServer(svr.cfg.AdminAddr, svr.cfg.AdminPort)
 		if err != nil {
 		if err != nil {
 			log.Warn("run admin server error: %v", err)
 			log.Warn("run admin server error: %v", err)
 		}
 		}
-		log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
+		log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
 	}
 	}
 
 
 	<-svr.closedCh
 	<-svr.closedCh
@@ -138,7 +148,7 @@ func (svr *Service) keepControllerWorking() {
 			// reconnect success, init delayTime
 			// reconnect success, init delayTime
 			delayTime = time.Second
 			delayTime = time.Second
 
 
-			ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
+			ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
 			ctl.Run()
 			ctl.Run()
 			svr.ctlMu.Lock()
 			svr.ctlMu.Lock()
 			svr.ctl = ctl
 			svr.ctl = ctl
@@ -153,13 +163,13 @@ func (svr *Service) keepControllerWorking() {
 // session: if it's not nil, using tcp mux
 // session: if it's not nil, using tcp mux
 func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
 func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
 	var tlsConfig *tls.Config
 	var tlsConfig *tls.Config
-	if g.GlbClientCfg.TLSEnable {
+	if svr.cfg.TLSEnable {
 		tlsConfig = &tls.Config{
 		tlsConfig = &tls.Config{
 			InsecureSkipVerify: true,
 			InsecureSkipVerify: true,
 		}
 		}
 	}
 	}
-	conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
-		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
+	conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HttpProxy, svr.cfg.Protocol,
+		fmt.Sprintf("%s:%d", svr.cfg.ServerAddr, svr.cfg.ServerPort), tlsConfig)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
@@ -173,7 +183,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
 		}
 		}
 	}()
 	}()
 
 
-	if g.GlbClientCfg.TcpMux {
+	if svr.cfg.TcpMux {
 		fmuxCfg := fmux.DefaultConfig()
 		fmuxCfg := fmux.DefaultConfig()
 		fmuxCfg.KeepAliveInterval = 20 * time.Second
 		fmuxCfg.KeepAliveInterval = 20 * time.Second
 		fmuxCfg.LogOutput = ioutil.Discard
 		fmuxCfg.LogOutput = ioutil.Discard
@@ -194,10 +204,10 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
 	loginMsg := &msg.Login{
 	loginMsg := &msg.Login{
 		Arch:         runtime.GOARCH,
 		Arch:         runtime.GOARCH,
 		Os:           runtime.GOOS,
 		Os:           runtime.GOOS,
-		PoolCount:    g.GlbClientCfg.PoolCount,
-		User:         g.GlbClientCfg.User,
+		PoolCount:    svr.cfg.PoolCount,
+		User:         svr.cfg.User,
 		Version:      version.Full(),
 		Version:      version.Full(),
-		PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now),
+		PrivilegeKey: util.GetAuthKey(svr.cfg.Token, now),
 		Timestamp:    now,
 		Timestamp:    now,
 		RunId:        svr.runId,
 		RunId:        svr.runId,
 	}
 	}
@@ -220,7 +230,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
 	}
 	}
 
 
 	svr.runId = loginRespMsg.RunId
 	svr.runId = loginRespMsg.RunId
-	g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
+	svr.serverUDPPort = loginRespMsg.ServerUdpPort
 	log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
 	log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
 	return
 	return
 }
 }

+ 2 - 3
client/visitor.go

@@ -23,7 +23,6 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/utils/log"
 	"github.com/fatedier/frp/utils/log"
@@ -193,13 +192,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 	defer userConn.Close()
 	defer userConn.Close()
 
 
 	sv.Debug("get a new xtcp user connection")
 	sv.Debug("get a new xtcp user connection")
-	if g.GlbClientCfg.ServerUdpPort == 0 {
+	if sv.ctl.serverUDPPort == 0 {
 		sv.Error("xtcp is not supported by server")
 		sv.Error("xtcp is not supported by server")
 		return
 		return
 	}
 	}
 
 
 	raddr, err := net.ResolveUDPAddr("udp",
 	raddr, err := net.ResolveUDPAddr("udp",
-		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
+		fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort))
 	if err != nil {
 	if err != nil {
 		sv.Error("resolve server UDP addr error")
 		sv.Error("resolve server UDP addr error")
 		return
 		return

+ 4 - 0
cmd/frpc/main.go

@@ -15,6 +15,9 @@
 package main
 package main
 
 
 import (
 import (
+	"math/rand"
+	"time"
+
 	_ "github.com/fatedier/frp/assets/frpc/statik"
 	_ "github.com/fatedier/frp/assets/frpc/statik"
 	"github.com/fatedier/frp/cmd/frpc/sub"
 	"github.com/fatedier/frp/cmd/frpc/sub"
 
 
@@ -23,6 +26,7 @@ import (
 
 
 func main() {
 func main() {
 	crypto.DefaultSalt = "frp"
 	crypto.DefaultSalt = "frp"
+	rand.Seed(time.Now().UnixNano())
 
 
 	sub.Execute()
 	sub.Execute()
 }
 }

+ 3 - 2
cmd/frpc/sub/http.go

@@ -33,6 +33,7 @@ func init() {
 	httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
 	httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	httpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 
 	httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
 	httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -53,7 +54,7 @@ var httpCmd = &cobra.Command{
 	Use:   "http",
 	Use:   "http",
 	Short: "Run frpc with a single http proxy",
 	Short: "Run frpc with a single http proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)
@@ -86,7 +87,7 @@ var httpCmd = &cobra.Command{
 		proxyConfs := map[string]config.ProxyConf{
 		proxyConfs := map[string]config.ProxyConf{
 			cfg.ProxyName: cfg,
 			cfg.ProxyName: cfg,
 		}
 		}
-		err = startService(proxyConfs, nil)
+		err = startService(clientCfg, proxyConfs, nil, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)

+ 3 - 2
cmd/frpc/sub/https.go

@@ -33,6 +33,7 @@ func init() {
 	httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
 	httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	httpsCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 
 	httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
 	httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -49,7 +50,7 @@ var httpsCmd = &cobra.Command{
 	Use:   "https",
 	Use:   "https",
 	Short: "Run frpc with a single https proxy",
 	Short: "Run frpc with a single https proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)
@@ -78,7 +79,7 @@ var httpsCmd = &cobra.Command{
 		proxyConfs := map[string]config.ProxyConf{
 		proxyConfs := map[string]config.ProxyConf{
 			cfg.ProxyName: cfg,
 			cfg.ProxyName: cfg,
 		}
 		}
-		err = startService(proxyConfs, nil)
+		err = startService(clientCfg, proxyConfs, nil, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)

+ 7 - 8
cmd/frpc/sub/reload.go

@@ -24,7 +24,6 @@ import (
 
 
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 )
 )
 
 
@@ -42,13 +41,13 @@ var reloadCmd = &cobra.Command{
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
 
 
-		err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
 
 
-		err = reload()
+		err = reload(clientCfg)
 		if err != nil {
 		if err != nil {
 			fmt.Printf("frpc reload error: %v\n", err)
 			fmt.Printf("frpc reload error: %v\n", err)
 			os.Exit(1)
 			os.Exit(1)
@@ -58,19 +57,19 @@ var reloadCmd = &cobra.Command{
 	},
 	},
 }
 }
 
 
-func reload() error {
-	if g.GlbClientCfg.AdminPort == 0 {
+func reload(clientCfg config.ClientCommonConf) error {
+	if clientCfg.AdminPort == 0 {
 		return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
 		return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
 	}
 	}
 
 
 	req, err := http.NewRequest("GET", "http://"+
 	req, err := http.NewRequest("GET", "http://"+
-		g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil)
+		clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
-		g.GlbClientCfg.AdminPwd))
+	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
+		clientCfg.AdminPwd))
 
 
 	req.Header.Add("Authorization", authStr)
 	req.Header.Add("Authorization", authStr)
 	resp, err := http.DefaultClient.Do(req)
 	resp, err := http.DefaultClient.Do(req)

+ 44 - 40
cmd/frpc/sub/root.go

@@ -28,7 +28,6 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 
 
 	"github.com/fatedier/frp/client"
 	"github.com/fatedier/frp/client"
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/utils/log"
 	"github.com/fatedier/frp/utils/log"
 	"github.com/fatedier/frp/utils/version"
 	"github.com/fatedier/frp/utils/version"
@@ -43,13 +42,14 @@ var (
 	cfgFile     string
 	cfgFile     string
 	showVersion bool
 	showVersion bool
 
 
-	serverAddr string
-	user       string
-	protocol   string
-	token      string
-	logLevel   string
-	logFile    string
-	logMaxDays int
+	serverAddr      string
+	user            string
+	protocol        string
+	token           string
+	logLevel        string
+	logFile         string
+	logMaxDays      int
+	disableLogColor bool
 
 
 	proxyName         string
 	proxyName         string
 	localIp           string
 	localIp           string
@@ -113,59 +113,62 @@ func handleSignal(svr *client.Service) {
 	close(kcpDoneCh)
 	close(kcpDoneCh)
 }
 }
 
 
-func parseClientCommonCfg(fileType int, content string) (err error) {
+func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommonConf, err error) {
 	if fileType == CfgFileTypeIni {
 	if fileType == CfgFileTypeIni {
-		err = parseClientCommonCfgFromIni(content)
+		cfg, err = parseClientCommonCfgFromIni(content)
 	} else if fileType == CfgFileTypeCmd {
 	} else if fileType == CfgFileTypeCmd {
-		err = parseClientCommonCfgFromCmd()
+		cfg, err = parseClientCommonCfgFromCmd()
 	}
 	}
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
 
 
-	err = g.GlbClientCfg.ClientCommonConf.Check()
+	err = cfg.Check()
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
 	return
 	return
 }
 }
 
 
-func parseClientCommonCfgFromIni(content string) (err error) {
-	cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
+func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error) {
+	cfg, err := config.UnmarshalClientConfFromIni(content)
 	if err != nil {
 	if err != nil {
-		return err
+		return config.ClientCommonConf{}, err
 	}
 	}
-	g.GlbClientCfg.ClientCommonConf = *cfg
-	return
+	return cfg, err
 }
 }
 
 
-func parseClientCommonCfgFromCmd() (err error) {
+func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
+	cfg = config.GetDefaultClientConf()
+
 	strs := strings.Split(serverAddr, ":")
 	strs := strings.Split(serverAddr, ":")
 	if len(strs) < 2 {
 	if len(strs) < 2 {
 		err = fmt.Errorf("invalid server_addr")
 		err = fmt.Errorf("invalid server_addr")
 		return
 		return
 	}
 	}
 	if strs[0] != "" {
 	if strs[0] != "" {
-		g.GlbClientCfg.ServerAddr = strs[0]
+		cfg.ServerAddr = strs[0]
 	}
 	}
-	g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1])
+	cfg.ServerPort, err = strconv.Atoi(strs[1])
 	if err != nil {
 	if err != nil {
 		err = fmt.Errorf("invalid server_addr")
 		err = fmt.Errorf("invalid server_addr")
 		return
 		return
 	}
 	}
 
 
-	g.GlbClientCfg.User = user
-	g.GlbClientCfg.Protocol = protocol
-	g.GlbClientCfg.Token = token
-	g.GlbClientCfg.LogLevel = logLevel
-	g.GlbClientCfg.LogFile = logFile
-	g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
+	cfg.User = user
+	cfg.Protocol = protocol
+	cfg.Token = token
+	cfg.LogLevel = logLevel
+	cfg.LogFile = logFile
+	cfg.LogMaxDays = int64(logMaxDays)
 	if logFile == "console" {
 	if logFile == "console" {
-		g.GlbClientCfg.LogWay = "console"
+		cfg.LogWay = "console"
 	} else {
 	} else {
-		g.GlbClientCfg.LogWay = "file"
+		cfg.LogWay = "file"
 	}
 	}
-	return nil
+	cfg.DisableLogColor = disableLogColor
+
+	return
 }
 }
 
 
 func runClient(cfgFilePath string) (err error) {
 func runClient(cfgFilePath string) (err error) {
@@ -174,26 +177,27 @@ func runClient(cfgFilePath string) (err error) {
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
-	g.GlbClientCfg.CfgFile = cfgFilePath
 
 
-	err = parseClientCommonCfg(CfgFileTypeIni, content)
+	cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
 
 
-	pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start)
+	pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	err = startService(pxyCfgs, visitorCfgs)
+	err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
 	return
 	return
 }
 }
 
 
-func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) {
-	log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
-	if g.GlbClientCfg.DnsServer != "" {
-		s := g.GlbClientCfg.DnsServer
+func startService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (err error) {
+	log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
+		cfg.LogMaxDays, cfg.DisableLogColor)
+
+	if cfg.DnsServer != "" {
+		s := cfg.DnsServer
 		if !strings.Contains(s, ":") {
 		if !strings.Contains(s, ":") {
 			s += ":53"
 			s += ":53"
 		}
 		}
@@ -205,19 +209,19 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
 			},
 			},
 		}
 		}
 	}
 	}
-	svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
+	svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
 	if errRet != nil {
 	if errRet != nil {
 		err = errRet
 		err = errRet
 		return
 		return
 	}
 	}
 
 
 	// Capture the exit signal if we use kcp.
 	// Capture the exit signal if we use kcp.
-	if g.GlbClientCfg.Protocol == "kcp" {
+	if cfg.Protocol == "kcp" {
 		go handleSignal(svr)
 		go handleSignal(svr)
 	}
 	}
 
 
 	err = svr.Run()
 	err = svr.Run()
-	if g.GlbClientCfg.Protocol == "kcp" {
+	if cfg.Protocol == "kcp" {
 		<-kcpDoneCh
 		<-kcpDoneCh
 	}
 	}
 	return
 	return

+ 7 - 8
cmd/frpc/sub/status.go

@@ -27,7 +27,6 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 
 
 	"github.com/fatedier/frp/client"
 	"github.com/fatedier/frp/client"
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 )
 )
 
 
@@ -45,13 +44,13 @@ var statusCmd = &cobra.Command{
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
 
 
-		err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
 
 
-		err = status()
+		err = status(clientCfg)
 		if err != nil {
 		if err != nil {
 			fmt.Printf("frpc get status error: %v\n", err)
 			fmt.Printf("frpc get status error: %v\n", err)
 			os.Exit(1)
 			os.Exit(1)
@@ -60,19 +59,19 @@ var statusCmd = &cobra.Command{
 	},
 	},
 }
 }
 
 
-func status() error {
-	if g.GlbClientCfg.AdminPort == 0 {
+func status(clientCfg config.ClientCommonConf) error {
+	if clientCfg.AdminPort == 0 {
 		return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
 		return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
 	}
 	}
 
 
 	req, err := http.NewRequest("GET", "http://"+
 	req, err := http.NewRequest("GET", "http://"+
-		g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil)
+		clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
-		g.GlbClientCfg.AdminPwd))
+	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
+		clientCfg.AdminPwd))
 
 
 	req.Header.Add("Authorization", authStr)
 	req.Header.Add("Authorization", authStr)
 	resp, err := http.DefaultClient.Do(req)
 	resp, err := http.DefaultClient.Do(req)

+ 3 - 2
cmd/frpc/sub/stcp.go

@@ -32,6 +32,7 @@ func init() {
 	stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
 	stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	stcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 
 	stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
 	stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
@@ -51,7 +52,7 @@ var stcpCmd = &cobra.Command{
 	Use:   "stcp",
 	Use:   "stcp",
 	Short: "Run frpc with a single stcp proxy",
 	Short: "Run frpc with a single stcp proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)
@@ -103,7 +104,7 @@ var stcpCmd = &cobra.Command{
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
 
 
-		err = startService(proxyConfs, visitorConfs)
+		err = startService(clientCfg, proxyConfs, visitorConfs, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)

+ 3 - 2
cmd/frpc/sub/tcp.go

@@ -32,6 +32,7 @@ func init() {
 	tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
 	tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	tcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 
 	tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
 	tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -47,7 +48,7 @@ var tcpCmd = &cobra.Command{
 	Use:   "tcp",
 	Use:   "tcp",
 	Short: "Run frpc with a single tcp proxy",
 	Short: "Run frpc with a single tcp proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)
@@ -75,7 +76,7 @@ var tcpCmd = &cobra.Command{
 		proxyConfs := map[string]config.ProxyConf{
 		proxyConfs := map[string]config.ProxyConf{
 			cfg.ProxyName: cfg,
 			cfg.ProxyName: cfg,
 		}
 		}
-		err = startService(proxyConfs, nil)
+		err = startService(clientCfg, proxyConfs, nil, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)

+ 3 - 2
cmd/frpc/sub/udp.go

@@ -32,6 +32,7 @@ func init() {
 	udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
 	udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	udpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 
 	udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
 	udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -47,7 +48,7 @@ var udpCmd = &cobra.Command{
 	Use:   "udp",
 	Use:   "udp",
 	Short: "Run frpc with a single udp proxy",
 	Short: "Run frpc with a single udp proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)
@@ -75,7 +76,7 @@ var udpCmd = &cobra.Command{
 		proxyConfs := map[string]config.ProxyConf{
 		proxyConfs := map[string]config.ProxyConf{
 			cfg.ProxyName: cfg,
 			cfg.ProxyName: cfg,
 		}
 		}
-		err = startService(proxyConfs, nil)
+		err = startService(clientCfg, proxyConfs, nil, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)

+ 3 - 2
cmd/frpc/sub/xtcp.go

@@ -32,6 +32,7 @@ func init() {
 	xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
 	xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	xtcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 
 	xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
 	xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
@@ -51,7 +52,7 @@ var xtcpCmd = &cobra.Command{
 	Use:   "xtcp",
 	Use:   "xtcp",
 	Short: "Run frpc with a single xtcp proxy",
 	Short: "Run frpc with a single xtcp proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)
@@ -103,7 +104,7 @@ var xtcpCmd = &cobra.Command{
 			os.Exit(1)
 			os.Exit(1)
 		}
 		}
 
 
-		err = startService(proxyConfs, visitorConfs)
+		err = startService(clientCfg, proxyConfs, visitorConfs, "")
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)

+ 4 - 0
cmd/frps/main.go

@@ -15,6 +15,9 @@
 package main
 package main
 
 
 import (
 import (
+	"math/rand"
+	"time"
+
 	"github.com/fatedier/golib/crypto"
 	"github.com/fatedier/golib/crypto"
 
 
 	_ "github.com/fatedier/frp/assets/frps/statik"
 	_ "github.com/fatedier/frp/assets/frps/statik"
@@ -22,6 +25,7 @@ import (
 
 
 func main() {
 func main() {
 	crypto.DefaultSalt = "frp"
 	crypto.DefaultSalt = "frp"
+	rand.Seed(time.Now().UnixNano())
 
 
 	Execute()
 	Execute()
 }
 }

+ 43 - 43
cmd/frps/root.go

@@ -20,7 +20,6 @@ import (
 
 
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/server"
 	"github.com/fatedier/frp/server"
 	"github.com/fatedier/frp/utils/log"
 	"github.com/fatedier/frp/utils/log"
@@ -53,6 +52,7 @@ var (
 	logFile           string
 	logFile           string
 	logLevel          string
 	logLevel          string
 	logMaxDays        int64
 	logMaxDays        int64
+	disableLogColor   bool
 	token             string
 	token             string
 	subDomainHost     string
 	subDomainHost     string
 	tcpMux            bool
 	tcpMux            bool
@@ -80,6 +80,8 @@ func init() {
 	rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
 	rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
 	rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
 	rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
+	rootCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
+
 	rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
 	rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
 	rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
 	rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
 	rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
 	rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
@@ -95,6 +97,7 @@ var rootCmd = &cobra.Command{
 			return nil
 			return nil
 		}
 		}
 
 
+		var cfg config.ServerCommonConf
 		var err error
 		var err error
 		if cfgFile != "" {
 		if cfgFile != "" {
 			var content string
 			var content string
@@ -102,16 +105,15 @@ var rootCmd = &cobra.Command{
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
-			g.GlbServerCfg.CfgFile = cfgFile
-			err = parseServerCommonCfg(CfgFileTypeIni, content)
+			cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
 		} else {
 		} else {
-			err = parseServerCommonCfg(CfgFileTypeCmd, "")
+			cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
 		}
 		}
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 
 
-		err = runServer()
+		err = runServer(cfg)
 		if err != nil {
 		if err != nil {
 			fmt.Println(err)
 			fmt.Println(err)
 			os.Exit(1)
 			os.Exit(1)
@@ -126,52 +128,51 @@ func Execute() {
 	}
 	}
 }
 }
 
 
-func parseServerCommonCfg(fileType int, content string) (err error) {
+func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommonConf, err error) {
 	if fileType == CfgFileTypeIni {
 	if fileType == CfgFileTypeIni {
-		err = parseServerCommonCfgFromIni(content)
+		cfg, err = parseServerCommonCfgFromIni(content)
 	} else if fileType == CfgFileTypeCmd {
 	} else if fileType == CfgFileTypeCmd {
-		err = parseServerCommonCfgFromCmd()
+		cfg, err = parseServerCommonCfgFromCmd()
 	}
 	}
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
 
 
-	err = g.GlbServerCfg.ServerCommonConf.Check()
+	err = cfg.Check()
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
-
-	config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
 	return
 	return
 }
 }
 
 
-func parseServerCommonCfgFromIni(content string) (err error) {
-	cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
+func parseServerCommonCfgFromIni(content string) (config.ServerCommonConf, error) {
+	cfg, err := config.UnmarshalServerConfFromIni(content)
 	if err != nil {
 	if err != nil {
-		return err
+		return config.ServerCommonConf{}, err
 	}
 	}
-	g.GlbServerCfg.ServerCommonConf = *cfg
-	return
+	return cfg, nil
 }
 }
 
 
-func parseServerCommonCfgFromCmd() (err error) {
-	g.GlbServerCfg.BindAddr = bindAddr
-	g.GlbServerCfg.BindPort = bindPort
-	g.GlbServerCfg.BindUdpPort = bindUdpPort
-	g.GlbServerCfg.KcpBindPort = kcpBindPort
-	g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
-	g.GlbServerCfg.VhostHttpPort = vhostHttpPort
-	g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
-	g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout
-	g.GlbServerCfg.DashboardAddr = dashboardAddr
-	g.GlbServerCfg.DashboardPort = dashboardPort
-	g.GlbServerCfg.DashboardUser = dashboardUser
-	g.GlbServerCfg.DashboardPwd = dashboardPwd
-	g.GlbServerCfg.LogFile = logFile
-	g.GlbServerCfg.LogLevel = logLevel
-	g.GlbServerCfg.LogMaxDays = logMaxDays
-	g.GlbServerCfg.Token = token
-	g.GlbServerCfg.SubDomainHost = subDomainHost
+func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
+	cfg = config.GetDefaultServerConf()
+
+	cfg.BindAddr = bindAddr
+	cfg.BindPort = bindPort
+	cfg.BindUdpPort = bindUdpPort
+	cfg.KcpBindPort = kcpBindPort
+	cfg.ProxyBindAddr = proxyBindAddr
+	cfg.VhostHttpPort = vhostHttpPort
+	cfg.VhostHttpsPort = vhostHttpsPort
+	cfg.VhostHttpTimeout = vhostHttpTimeout
+	cfg.DashboardAddr = dashboardAddr
+	cfg.DashboardPort = dashboardPort
+	cfg.DashboardUser = dashboardUser
+	cfg.DashboardPwd = dashboardPwd
+	cfg.LogFile = logFile
+	cfg.LogLevel = logLevel
+	cfg.LogMaxDays = logMaxDays
+	cfg.Token = token
+	cfg.SubDomainHost = subDomainHost
 	if len(allowPorts) > 0 {
 	if len(allowPorts) > 0 {
 		// e.g. 1000-2000,2001,2002,3000-4000
 		// e.g. 1000-2000,2001,2002,3000-4000
 		ports, errRet := util.ParseRangeNumbers(allowPorts)
 		ports, errRet := util.ParseRangeNumbers(allowPorts)
@@ -181,28 +182,27 @@ func parseServerCommonCfgFromCmd() (err error) {
 		}
 		}
 
 
 		for _, port := range ports {
 		for _, port := range ports {
-			g.GlbServerCfg.AllowPorts[int(port)] = struct{}{}
+			cfg.AllowPorts[int(port)] = struct{}{}
 		}
 		}
 	}
 	}
-	g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
+	cfg.MaxPortsPerClient = maxPortsPerClient
 
 
 	if logFile == "console" {
 	if logFile == "console" {
-		g.GlbServerCfg.LogWay = "console"
+		cfg.LogWay = "console"
 	} else {
 	} else {
-		g.GlbServerCfg.LogWay = "file"
+		cfg.LogWay = "file"
 	}
 	}
+	cfg.DisableLogColor = disableLogColor
 	return
 	return
 }
 }
 
 
-func runServer() (err error) {
-	log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel,
-		g.GlbServerCfg.LogMaxDays)
-	svr, err := server.NewService()
+func runServer(cfg config.ServerCommonConf) (err error) {
+	log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
+	svr, err := server.NewService(cfg)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	log.Info("Start frps success")
 	log.Info("Start frps success")
-	server.ServerService = svr
 	svr.Run()
 	svr.Run()
 	return
 	return
 }
 }

+ 6 - 0
conf/frpc_full.ini

@@ -18,6 +18,9 @@ log_level = info
 
 
 log_max_days = 3
 log_max_days = 3
 
 
+# disable log colors when log_file is console, default is false
+disable_log_color = false
+
 # for authentication
 # for authentication
 token = 12345678
 token = 12345678
 
 
@@ -26,6 +29,8 @@ admin_addr = 127.0.0.1
 admin_port = 7400
 admin_port = 7400
 admin_user = admin
 admin_user = admin
 admin_pwd = admin
 admin_pwd = admin
+# Admin assets directory. By default, these assets are bundled with frpc.
+# assets_dir = ./static
 
 
 # connections will be established in advance, default value is zero
 # connections will be established in advance, default value is zero
 pool_count = 5
 pool_count = 5
@@ -198,6 +203,7 @@ plugin_local_addr = 127.0.0.1:80
 plugin_crt_path = ./server.crt
 plugin_crt_path = ./server.crt
 plugin_key_path = ./server.key
 plugin_key_path = ./server.key
 plugin_host_header_rewrite = 127.0.0.1
 plugin_host_header_rewrite = 127.0.0.1
+plugin_header_X-From-Where = frp
 
 
 [secret_tcp]
 [secret_tcp]
 # If the type is secret tcp, remote_port is useless
 # If the type is secret tcp, remote_port is useless

+ 3 - 0
conf/frps_full.ini

@@ -43,6 +43,9 @@ log_level = info
 
 
 log_max_days = 3
 log_max_days = 3
 
 
+# disable log colors when log_file is console, default is false
+disable_log_color = false
+
 # auth token
 # auth token
 token = 12345678
 token = 12345678
 
 

+ 0 - 32
g/g.go

@@ -1,32 +0,0 @@
-package g
-
-import (
-	"github.com/fatedier/frp/models/config"
-)
-
-var (
-	GlbClientCfg *ClientCfg
-	GlbServerCfg *ServerCfg
-)
-
-func init() {
-	GlbClientCfg = &ClientCfg{
-		ClientCommonConf: *config.GetDefaultClientConf(),
-	}
-	GlbServerCfg = &ServerCfg{
-		ServerCommonConf: *config.GetDefaultServerConf(),
-	}
-}
-
-type ClientCfg struct {
-	config.ClientCommonConf
-
-	CfgFile       string
-	ServerUdpPort int // this is configured by login response from frps
-}
-
-type ServerCfg struct {
-	config.ServerCommonConf
-
-	CfgFile string
-}

+ 2 - 0
go.sum

@@ -1,5 +1,6 @@
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
 github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
 github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
 github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
@@ -27,6 +28,7 @@ github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHX
 github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
 github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
 github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
 github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
 github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
 github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
 github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=

+ 107 - 32
models/config/client_common.go

@@ -23,34 +23,103 @@ import (
 	ini "github.com/vaughan0/go-ini"
 	ini "github.com/vaughan0/go-ini"
 )
 )
 
 
-// client common config
+// ClientCommonConf contains information for a client service. It is
+// recommended to use GetDefaultClientConf instead of creating this object
+// directly, so that all unspecified fields have reasonable default values.
 type ClientCommonConf struct {
 type ClientCommonConf struct {
-	ServerAddr        string              `json:"server_addr"`
-	ServerPort        int                 `json:"server_port"`
-	HttpProxy         string              `json:"http_proxy"`
-	LogFile           string              `json:"log_file"`
-	LogWay            string              `json:"log_way"`
-	LogLevel          string              `json:"log_level"`
-	LogMaxDays        int64               `json:"log_max_days"`
-	Token             string              `json:"token"`
-	AdminAddr         string              `json:"admin_addr"`
-	AdminPort         int                 `json:"admin_port"`
-	AdminUser         string              `json:"admin_user"`
-	AdminPwd          string              `json:"admin_pwd"`
-	PoolCount         int                 `json:"pool_count"`
-	TcpMux            bool                `json:"tcp_mux"`
-	User              string              `json:"user"`
-	DnsServer         string              `json:"dns_server"`
-	LoginFailExit     bool                `json:"login_fail_exit"`
-	Start             map[string]struct{} `json:"start"`
-	Protocol          string              `json:"protocol"`
-	TLSEnable         bool                `json:"tls_enable"`
-	HeartBeatInterval int64               `json:"heartbeat_interval"`
-	HeartBeatTimeout  int64               `json:"heartbeat_timeout"`
+	// ServerAddr specifies the address of the server to connect to. By
+	// default, this value is "0.0.0.0".
+	ServerAddr string `json:"server_addr"`
+	// ServerPort specifies the port to connect to the server on. By default,
+	// this value is 7000.
+	ServerPort int `json:"server_port"`
+	// HttpProxy specifies a proxy address to connect to the server through. If
+	// this value is "", the server will be connected to directly. By default,
+	// this value is read from the "http_proxy" environment variable.
+	HttpProxy string `json:"http_proxy"`
+	// LogFile specifies a file where logs will be written to. This value will
+	// only be used if LogWay is set appropriately. By default, this value is
+	// "console".
+	LogFile string `json:"log_file"`
+	// LogWay specifies the way logging is managed. Valid values are "console"
+	// or "file". If "console" is used, logs will be printed to stdout. If
+	// "file" is used, logs will be printed to LogFile. By default, this value
+	// is "console".
+	LogWay string `json:"log_way"`
+	// LogLevel specifies the minimum log level. Valid values are "trace",
+	// "debug", "info", "warn", and "error". By default, this value is "info".
+	LogLevel string `json:"log_level"`
+	// LogMaxDays specifies the maximum number of days to store log information
+	// before deletion. This is only used if LogWay == "file". By default, this
+	// value is 0.
+	LogMaxDays int64 `json:"log_max_days"`
+	// DisableLogColor disables log colors when LogWay == "console" when set to
+	// true. By default, this value is false.
+	DisableLogColor bool `json:"disable_log_color"`
+	// Token specifies the authorization token used to create keys to be sent
+	// to the server. The server must have a matching token for authorization
+	// to succeed.  By default, this value is "".
+	Token string `json:"token"`
+	// AdminAddr specifies the address that the admin server binds to. By
+	// default, this value is "127.0.0.1".
+	AdminAddr string `json:"admin_addr"`
+	// AdminPort specifies the port for the admin server to listen on. If this
+	// value is 0, the admin server will not be started. By default, this value
+	// is 0.
+	AdminPort int `json:"admin_port"`
+	// AdminUser specifies the username that the admin server will use for
+	// login. By default, this value is "admin".
+	AdminUser string `json:"admin_user"`
+	// AdminPwd specifies the password that the admin server will use for
+	// login. By default, this value is "admin".
+	AdminPwd string `json:"admin_pwd"`
+	// AssetsDir specifies the local directory that the admin server will load
+	// resources from. If this value is "", assets will be loaded from the
+	// bundled executable using statik. By default, this value is "".
+	AssetsDir string `json:"assets_dir"`
+	// PoolCount specifies the number of connections the client will make to
+	// the server in advance. By default, this value is 0.
+	PoolCount int `json:"pool_count"`
+	// TcpMux toggles TCP stream multiplexing. This allows multiple requests
+	// from a client to share a single TCP connection. If this value is true,
+	// the server must have TCP multiplexing enabled as well. By default, this
+	// value is true.
+	TcpMux bool `json:"tcp_mux"`
+	// User specifies a prefix for proxy names to distinguish them from other
+	// clients. If this value is not "", proxy names will automatically be
+	// changed to "{user}.{proxy_name}". By default, this value is "".
+	User string `json:"user"`
+	// DnsServer specifies a DNS server address for FRPC to use. If this value
+	// is "", the default DNS will be used. By default, this value is "".
+	DnsServer string `json:"dns_server"`
+	// LoginFailExit controls whether or not the client should exit after a
+	// failed login attempt. If false, the client will retry until a login
+	// attempt succeeds. By default, this value is true.
+	LoginFailExit bool `json:"login_fail_exit"`
+	// Start specifies a set of enabled proxies by name. If this set is empty,
+	// all supplied proxies are enabled. By default, this value is an empty
+	// set.
+	Start map[string]struct{} `json:"start"`
+	// Protocol specifies the protocol to use when interacting with the server.
+	// Valid values are "tcp", "kcp", and "websocket". By default, this value
+	// is "tcp".
+	Protocol string `json:"protocol"`
+	// TLSEnable specifies whether or not TLS should be used when communicating
+	// with the server.
+	TLSEnable bool `json:"tls_enable"`
+	// HeartBeatInterval specifies at what interval heartbeats are sent to the
+	// server, in seconds. It is not recommended to change this value. By
+	// default, this value is 30.
+	HeartBeatInterval int64 `json:"heartbeat_interval"`
+	// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
+	// before the connection is terminated, in seconds. It is not recommended
+	// to change this value. By default, this value is 90.
+	HeartBeatTimeout int64 `json:"heartbeat_timeout"`
 }
 }
 
 
-func GetDefaultClientConf() *ClientCommonConf {
-	return &ClientCommonConf{
+// GetDefaultClientConf returns a client configuration with default values.
+func GetDefaultClientConf() ClientCommonConf {
+	return ClientCommonConf{
 		ServerAddr:        "0.0.0.0",
 		ServerAddr:        "0.0.0.0",
 		ServerPort:        7000,
 		ServerPort:        7000,
 		HttpProxy:         os.Getenv("http_proxy"),
 		HttpProxy:         os.Getenv("http_proxy"),
@@ -58,11 +127,13 @@ func GetDefaultClientConf() *ClientCommonConf {
 		LogWay:            "console",
 		LogWay:            "console",
 		LogLevel:          "info",
 		LogLevel:          "info",
 		LogMaxDays:        3,
 		LogMaxDays:        3,
+		DisableLogColor:   false,
 		Token:             "",
 		Token:             "",
 		AdminAddr:         "127.0.0.1",
 		AdminAddr:         "127.0.0.1",
 		AdminPort:         0,
 		AdminPort:         0,
 		AdminUser:         "",
 		AdminUser:         "",
 		AdminPwd:          "",
 		AdminPwd:          "",
+		AssetsDir:         "",
 		PoolCount:         1,
 		PoolCount:         1,
 		TcpMux:            true,
 		TcpMux:            true,
 		User:              "",
 		User:              "",
@@ -76,16 +147,12 @@ func GetDefaultClientConf() *ClientCommonConf {
 	}
 	}
 }
 }
 
 
-func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) {
-	cfg = defaultCfg
-	if cfg == nil {
-		cfg = GetDefaultClientConf()
-	}
+func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error) {
+	cfg = GetDefaultClientConf()
 
 
 	conf, err := ini.Load(strings.NewReader(content))
 	conf, err := ini.Load(strings.NewReader(content))
 	if err != nil {
 	if err != nil {
-		err = fmt.Errorf("parse ini conf file error: %v", err)
-		return nil, err
+		return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err)
 	}
 	}
 
 
 	var (
 	var (
@@ -106,6 +173,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
 		cfg.ServerPort = int(v)
 		cfg.ServerPort = int(v)
 	}
 	}
 
 
+	if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
+		cfg.DisableLogColor = true
+	}
+
 	if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
 	if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
 		cfg.HttpProxy = tmpStr
 		cfg.HttpProxy = tmpStr
 	}
 	}
@@ -154,6 +225,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
 		cfg.AdminPwd = tmpStr
 		cfg.AdminPwd = tmpStr
 	}
 	}
 
 
+	if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
+		cfg.AssetsDir = tmpStr
+	}
+
 	if tmpStr, ok = conf.Get("common", "pool_count"); ok {
 	if tmpStr, ok = conf.Get("common", "pool_count"); ok {
 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
 			cfg.PoolCount = int(v)
 			cfg.PoolCount = int(v)

+ 82 - 38
models/config/proxy.go

@@ -58,11 +58,11 @@ type ProxyConf interface {
 	UnmarshalFromIni(prefix string, name string, conf ini.Section) error
 	UnmarshalFromIni(prefix string, name string, conf ini.Section) error
 	MarshalToMsg(pMsg *msg.NewProxy)
 	MarshalToMsg(pMsg *msg.NewProxy)
 	CheckForCli() error
 	CheckForCli() error
-	CheckForSvr() error
+	CheckForSvr(serverCfg ServerCommonConf) error
 	Compare(conf ProxyConf) bool
 	Compare(conf ProxyConf) bool
 }
 }
 
 
-func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
+func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (cfg ProxyConf, err error) {
 	if pMsg.ProxyType == "" {
 	if pMsg.ProxyType == "" {
 		pMsg.ProxyType = consts.TcpProxy
 		pMsg.ProxyType = consts.TcpProxy
 	}
 	}
@@ -73,7 +73,7 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
 		return
 		return
 	}
 	}
 	cfg.UnmarshalFromMsg(pMsg)
 	cfg.UnmarshalFromMsg(pMsg)
-	err = cfg.CheckForSvr()
+	err = cfg.CheckForSvr(serverCfg)
 	return
 	return
 }
 }
 
 
@@ -97,17 +97,33 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P
 	return
 	return
 }
 }
 
 
-// BaseProxy info
+// BaseProxyConf provides configuration info that is common to all proxy types.
 type BaseProxyConf struct {
 type BaseProxyConf struct {
+	// ProxyName is the name of this proxy.
 	ProxyName string `json:"proxy_name"`
 	ProxyName string `json:"proxy_name"`
+	// ProxyType specifies the type of this proxy. Valid values include "tcp",
+	// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
+	// "tcp".
 	ProxyType string `json:"proxy_type"`
 	ProxyType string `json:"proxy_type"`
 
 
-	UseEncryption  bool   `json:"use_encryption"`
-	UseCompression bool   `json:"use_compression"`
-	Group          string `json:"group"`
-	GroupKey       string `json:"group_key"`
-
-	// only used for client
+	// UseEncryption controls whether or not communication with the server will
+	// be encrypted. Encryption is done using the tokens supplied in the server
+	// and client configuration. By default, this value is false.
+	UseEncryption bool `json:"use_encryption"`
+	// UseCompression controls whether or not communication with the server
+	// will be compressed. By default, this value is false.
+	UseCompression bool `json:"use_compression"`
+	// Group specifies which group the proxy is a part of. The server will use
+	// this information to load balance proxies in the same group. If the value
+	// is "", this proxy will not be in a group. By default, this value is "".
+	Group string `json:"group"`
+	// GroupKey specifies a group key, which should be the same among proxies
+	// of the same group. By default, this value is "".
+	GroupKey string `json:"group_key"`
+
+	// ProxyProtocolVersion specifies which protocol version to use. Valid
+	// values include "v1", "v2", and "". If the value is "", a protocol
+	// version will be automatically selected. By default, this value is "".
 	ProxyProtocolVersion string `json:"proxy_protocol_version"`
 	ProxyProtocolVersion string `json:"proxy_protocol_version"`
 	LocalSvrConf
 	LocalSvrConf
 	HealthCheckConf
 	HealthCheckConf
@@ -308,21 +324,21 @@ func (cfg *DomainConf) checkForCli() (err error) {
 	return
 	return
 }
 }
 
 
-func (cfg *DomainConf) checkForSvr() (err error) {
+func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) {
 	if err = cfg.check(); err != nil {
 	if err = cfg.check(); err != nil {
 		return
 		return
 	}
 	}
 
 
 	for _, domain := range cfg.CustomDomains {
 	for _, domain := range cfg.CustomDomains {
-		if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) {
-			if strings.Contains(domain, subDomainHost) {
-				return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost)
+		if serverCfg.SubDomainHost != "" && len(strings.Split(serverCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
+			if strings.Contains(domain, serverCfg.SubDomainHost) {
+				return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, serverCfg.SubDomainHost)
 			}
 			}
 		}
 		}
 	}
 	}
 
 
 	if cfg.SubDomain != "" {
 	if cfg.SubDomain != "" {
-		if subDomainHost == "" {
+		if serverCfg.SubDomainHost == "" {
 			return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
 			return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
 		}
 		}
 		if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
 		if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
@@ -332,12 +348,20 @@ func (cfg *DomainConf) checkForSvr() (err error) {
 	return
 	return
 }
 }
 
 
-// Local service info
+// LocalSvrConf configures what location the client will proxy to, or what
+// plugin will be used.
 type LocalSvrConf struct {
 type LocalSvrConf struct {
-	LocalIp   string `json:"local_ip"`
-	LocalPort int    `json:"local_port"`
-
-	Plugin       string            `json:"plugin"`
+	// LocalIp specifies the IP address or host name to proxy to.
+	LocalIp string `json:"local_ip"`
+	// LocalPort specifies the port to proxy to.
+	LocalPort int `json:"local_port"`
+
+	// Plugin specifies what plugin should be used for proxying. If this value
+	// is set, the LocalIp and LocalPort values will be ignored. By default,
+	// this value is "".
+	Plugin string `json:"plugin"`
+	// PluginParams specify parameters to be passed to the plugin, if one is
+	// being used. By default, this value is an empty map.
 	PluginParams map[string]string `json:"plugin_params"`
 	PluginParams map[string]string `json:"plugin_params"`
 }
 }
 
 
@@ -399,15 +423,35 @@ func (cfg *LocalSvrConf) checkForCli() (err error) {
 	return
 	return
 }
 }
 
 
-// Health check info
+// HealthCheckConf configures health checking. This can be useful for load
+// balancing purposes to detect and remove proxies to failing services.
 type HealthCheckConf struct {
 type HealthCheckConf struct {
-	HealthCheckType      string `json:"health_check_type"` // tcp | http
-	HealthCheckTimeoutS  int    `json:"health_check_timeout_s"`
-	HealthCheckMaxFailed int    `json:"health_check_max_failed"`
-	HealthCheckIntervalS int    `json:"health_check_interval_s"`
-	HealthCheckUrl       string `json:"health_check_url"`
-
-	// local_ip + local_port
+	// HealthCheckType specifies what protocol to use for health checking.
+	// Valid values include "tcp", "http", and "". If this value is "", health
+	// checking will not be performed. By default, this value is "".
+	//
+	// If the type is "tcp", a connection will be attempted to the target
+	// server. If a connection cannot be established, the health check fails.
+	//
+	// If the type is "http", a GET request will be made to the endpoint
+	// specified by HealthCheckUrl. If the response is not a 200, the health
+	// check fails.
+	HealthCheckType string `json:"health_check_type"` // tcp | http
+	// HealthCheckTimeoutS specifies the number of seconds to wait for a health
+	// check attempt to connect. If the timeout is reached, this counts as a
+	// health check failure. By default, this value is 3.
+	HealthCheckTimeoutS int `json:"health_check_timeout_s"`
+	// HealthCheckMaxFailed specifies the number of allowed failures before the
+	// proxy is stopped. By default, this value is 1.
+	HealthCheckMaxFailed int `json:"health_check_max_failed"`
+	// HealthCheckIntervalS specifies the time in seconds between health
+	// checks. By default, this value is 10.
+	HealthCheckIntervalS int `json:"health_check_interval_s"`
+	// HealthCheckUrl specifies the address to send health checks to if the
+	// health check type is "http".
+	HealthCheckUrl string `json:"health_check_url"`
+	// HealthCheckAddr specifies the address to connect to if the health check
+	// type is "tcp".
 	HealthCheckAddr string `json:"-"`
 	HealthCheckAddr string `json:"-"`
 }
 }
 
 
@@ -504,7 +548,7 @@ func (cfg *TcpProxyConf) CheckForCli() (err error) {
 	return
 	return
 }
 }
 
 
-func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
+func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
 
 
 // UDP
 // UDP
 type UdpProxyConf struct {
 type UdpProxyConf struct {
@@ -552,7 +596,7 @@ func (cfg *UdpProxyConf) CheckForCli() (err error) {
 	return
 	return
 }
 }
 
 
-func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
+func (cfg *UdpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
 
 
 // HTTP
 // HTTP
 type HttpProxyConf struct {
 type HttpProxyConf struct {
@@ -657,11 +701,11 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) {
 	return
 	return
 }
 }
 
 
-func (cfg *HttpProxyConf) CheckForSvr() (err error) {
-	if vhostHttpPort == 0 {
+func (cfg *HttpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
+	if serverCfg.VhostHttpPort == 0 {
 		return fmt.Errorf("type [http] not support when vhost_http_port is not set")
 		return fmt.Errorf("type [http] not support when vhost_http_port is not set")
 	}
 	}
-	if err = cfg.DomainConf.checkForSvr(); err != nil {
+	if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
 		err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
 		err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
 		return
 		return
 	}
 	}
@@ -717,11 +761,11 @@ func (cfg *HttpsProxyConf) CheckForCli() (err error) {
 	return
 	return
 }
 }
 
 
-func (cfg *HttpsProxyConf) CheckForSvr() (err error) {
-	if vhostHttpsPort == 0 {
+func (cfg *HttpsProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
+	if serverCfg.VhostHttpsPort == 0 {
 		return fmt.Errorf("type [https] not support when vhost_https_port is not set")
 		return fmt.Errorf("type [https] not support when vhost_https_port is not set")
 	}
 	}
-	if err = cfg.DomainConf.checkForSvr(); err != nil {
+	if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
 		err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
 		err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
 		return
 		return
 	}
 	}
@@ -790,7 +834,7 @@ func (cfg *StcpProxyConf) CheckForCli() (err error) {
 	return
 	return
 }
 }
 
 
-func (cfg *StcpProxyConf) CheckForSvr() (err error) {
+func (cfg *StcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
 	return
 	return
 }
 }
 
 
@@ -857,7 +901,7 @@ func (cfg *XtcpProxyConf) CheckForCli() (err error) {
 	return
 	return
 }
 }
 
 
-func (cfg *XtcpProxyConf) CheckForSvr() (err error) {
+func (cfg *XtcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
 	return
 	return
 }
 }
 
 

+ 108 - 44
models/config/server_common.go

@@ -24,62 +24,122 @@ import (
 	"github.com/fatedier/frp/utils/util"
 	"github.com/fatedier/frp/utils/util"
 )
 )
 
 
-var (
-	// server global configure used for generate proxy conf used in frps
-	proxyBindAddr  string
-	subDomainHost  string
-	vhostHttpPort  int
-	vhostHttpsPort int
-)
-
-func InitServerCfg(cfg *ServerCommonConf) {
-	proxyBindAddr = cfg.ProxyBindAddr
-	subDomainHost = cfg.SubDomainHost
-	vhostHttpPort = cfg.VhostHttpPort
-	vhostHttpsPort = cfg.VhostHttpsPort
-}
-
-// common config
+// ServerCommonConf contains information for a server service. It is
+// recommended to use GetDefaultServerConf instead of creating this object
+// directly, so that all unspecified fields have reasonable default values.
 type ServerCommonConf struct {
 type ServerCommonConf struct {
-	BindAddr      string `json:"bind_addr"`
-	BindPort      int    `json:"bind_port"`
-	BindUdpPort   int    `json:"bind_udp_port"`
-	KcpBindPort   int    `json:"kcp_bind_port"`
+	// BindAddr specifies the address that the server binds to. By default,
+	// this value is "0.0.0.0".
+	BindAddr string `json:"bind_addr"`
+	// BindPort specifies the port that the server listens on. By default, this
+	// value is 7000.
+	BindPort int `json:"bind_port"`
+	// BindUdpPort specifies the UDP port that the server listens on. If this
+	// value is 0, the server will not listen for UDP connections. By default,
+	// this value is 0
+	BindUdpPort int `json:"bind_udp_port"`
+	// BindKcpPort specifies the KCP port that the server listens on. If this
+	// value is 0, the server will not listen for KCP connections. By default,
+	// this value is 0.
+	KcpBindPort int `json:"kcp_bind_port"`
+	// ProxyBindAddr specifies the address that the proxy binds to. This value
+	// may be the same as BindAddr. By default, this value is "0.0.0.0".
 	ProxyBindAddr string `json:"proxy_bind_addr"`
 	ProxyBindAddr string `json:"proxy_bind_addr"`
 
 
-	// If VhostHttpPort equals 0, don't listen a public port for http protocol.
+	// VhostHttpPort specifies the port that the server listens for HTTP Vhost
+	// requests. If this value is 0, the server will not listen for HTTP
+	// requests. By default, this value is 0.
 	VhostHttpPort int `json:"vhost_http_port"`
 	VhostHttpPort int `json:"vhost_http_port"`
 
 
-	// if VhostHttpsPort equals 0, don't listen a public port for https protocol
+	// VhostHttpsPort specifies the port that the server listens for HTTPS
+	// Vhost requests. If this value is 0, the server will not listen for HTTPS
+	// requests. By default, this value is 0.
 	VhostHttpsPort int `json:"vhost_https_port"`
 	VhostHttpsPort int `json:"vhost_https_port"`
 
 
+	// VhostHttpTimeout specifies the response header timeout for the Vhost
+	// HTTP server, in seconds. By default, this value is 60.
 	VhostHttpTimeout int64 `json:"vhost_http_timeout"`
 	VhostHttpTimeout int64 `json:"vhost_http_timeout"`
 
 
+	// DashboardAddr specifies the address that the dashboard binds to. By
+	// default, this value is "0.0.0.0".
 	DashboardAddr string `json:"dashboard_addr"`
 	DashboardAddr string `json:"dashboard_addr"`
 
 
-	// if DashboardPort equals 0, dashboard is not available
-	DashboardPort int    `json:"dashboard_port"`
+	// DashboardPort specifies the port that the dashboard listens on. If this
+	// value is 0, the dashboard will not be started. By default, this value is
+	// 0.
+	DashboardPort int `json:"dashboard_port"`
+	// DashboardUser specifies the username that the dashboard will use for
+	// login. By default, this value is "admin".
 	DashboardUser string `json:"dashboard_user"`
 	DashboardUser string `json:"dashboard_user"`
-	DashboardPwd  string `json:"dashboard_pwd"`
-	AssetsDir     string `json:"asserts_dir"`
-	LogFile       string `json:"log_file"`
-	LogWay        string `json:"log_way"` // console or file
-	LogLevel      string `json:"log_level"`
-	LogMaxDays    int64  `json:"log_max_days"`
-	Token         string `json:"token"`
+	// DashboardUser specifies the password that the dashboard will use for
+	// login. By default, this value is "admin".
+	DashboardPwd string `json:"dashboard_pwd"`
+	// AssetsDir specifies the local directory that the dashboard will load
+	// resources from. If this value is "", assets will be loaded from the
+	// bundled executable using statik. By default, this value is "".
+	AssetsDir string `json:"asserts_dir"`
+	// LogFile specifies a file where logs will be written to. This value will
+	// only be used if LogWay is set appropriately. By default, this value is
+	// "console".
+	LogFile string `json:"log_file"`
+	// LogWay specifies the way logging is managed. Valid values are "console"
+	// or "file". If "console" is used, logs will be printed to stdout. If
+	// "file" is used, logs will be printed to LogFile. By default, this value
+	// is "console".
+	LogWay string `json:"log_way"`
+	// LogLevel specifies the minimum log level. Valid values are "trace",
+	// "debug", "info", "warn", and "error". By default, this value is "info".
+	LogLevel string `json:"log_level"`
+	// LogMaxDays specifies the maximum number of days to store log information
+	// before deletion. This is only used if LogWay == "file". By default, this
+	// value is 0.
+	LogMaxDays int64 `json:"log_max_days"`
+	// DisableLogColor disables log colors when LogWay == "console" when set to
+	// true. By default, this value is false.
+	DisableLogColor bool `json:"disable_log_color"`
+	// Token specifies the authorization token used to authenticate keys
+	// received from clients. Clients must have a matching token to be
+	// authorized to use the server. By default, this value is "".
+	Token string `json:"token"`
+	// SubDomainHost specifies the domain that will be attached to sub-domains
+	// requested by the client when using Vhost proxying. For example, if this
+	// value is set to "frps.com" and the client requested the subdomain
+	// "test", the resulting URL would be "test.frps.com". By default, this
+	// value is "".
 	SubDomainHost string `json:"subdomain_host"`
 	SubDomainHost string `json:"subdomain_host"`
-	TcpMux        bool   `json:"tcp_mux"`
+	// TcpMux toggles TCP stream multiplexing. This allows multiple requests
+	// from a client to share a single TCP connection. By default, this value
+	// is true.
+	TcpMux bool `json:"tcp_mux"`
+	// Custom404Page specifies a path to a custom 404 page to display. If this
+	// value is "", a default page will be displayed. By default, this value is
+	// "".
 	Custom404Page string `json:"custom_404_page"`
 	Custom404Page string `json:"custom_404_page"`
 
 
-	AllowPorts        map[int]struct{}
-	MaxPoolCount      int64 `json:"max_pool_count"`
+	// AllowPorts specifies a set of ports that clients are able to proxy to.
+	// If the length of this value is 0, all ports are allowed. By default,
+	// this value is an empty set.
+	AllowPorts map[int]struct{}
+	// MaxPoolCount specifies the maximum pool size for each proxy. By default,
+	// this value is 5.
+	MaxPoolCount int64 `json:"max_pool_count"`
+	// MaxPortsPerClient specifies the maximum number of ports a single client
+	// may proxy to. If this value is 0, no limit will be applied. By default,
+	// this value is 0.
 	MaxPortsPerClient int64 `json:"max_ports_per_client"`
 	MaxPortsPerClient int64 `json:"max_ports_per_client"`
-	HeartBeatTimeout  int64 `json:"heart_beat_timeout"`
-	UserConnTimeout   int64 `json:"user_conn_timeout"`
+	// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
+	// before terminating the connection. It is not recommended to change this
+	// value. By default, this value is 90.
+	HeartBeatTimeout int64 `json:"heart_beat_timeout"`
+	// UserConnTimeout specifies the maximum time to wait for a work
+	// connection. By default, this value is 10.
+	UserConnTimeout int64 `json:"user_conn_timeout"`
 }
 }
 
 
-func GetDefaultServerConf() *ServerCommonConf {
-	return &ServerCommonConf{
+// GetDefaultServerConf returns a server configuration with reasonable
+// defaults.
+func GetDefaultServerConf() ServerCommonConf {
+	return ServerCommonConf{
 		BindAddr:          "0.0.0.0",
 		BindAddr:          "0.0.0.0",
 		BindPort:          7000,
 		BindPort:          7000,
 		BindUdpPort:       0,
 		BindUdpPort:       0,
@@ -97,6 +157,7 @@ func GetDefaultServerConf() *ServerCommonConf {
 		LogWay:            "console",
 		LogWay:            "console",
 		LogLevel:          "info",
 		LogLevel:          "info",
 		LogMaxDays:        3,
 		LogMaxDays:        3,
+		DisableLogColor:   false,
 		Token:             "",
 		Token:             "",
 		SubDomainHost:     "",
 		SubDomainHost:     "",
 		TcpMux:            true,
 		TcpMux:            true,
@@ -109,16 +170,15 @@ func GetDefaultServerConf() *ServerCommonConf {
 	}
 	}
 }
 }
 
 
-func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) {
-	cfg = defaultCfg
-	if cfg == nil {
-		cfg = GetDefaultServerConf()
-	}
+// UnmarshalServerConfFromIni parses the contents of a server configuration ini
+// file and returns the resulting server configuration.
+func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error) {
+	cfg = GetDefaultServerConf()
 
 
 	conf, err := ini.Load(strings.NewReader(content))
 	conf, err := ini.Load(strings.NewReader(content))
 	if err != nil {
 	if err != nil {
 		err = fmt.Errorf("parse ini conf file error: %v", err)
 		err = fmt.Errorf("parse ini conf file error: %v", err)
-		return nil, err
+		return ServerCommonConf{}, err
 	}
 	}
 
 
 	var (
 	var (
@@ -244,6 +304,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
 		}
 		}
 	}
 	}
 
 
+	if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
+		cfg.DisableLogColor = true
+	}
+
 	cfg.Token, _ = conf.Get("common", "token")
 	cfg.Token, _ = conf.Get("common", "token")
 
 
 	if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
 	if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {

+ 15 - 0
models/plugin/https2http.go

@@ -20,6 +20,7 @@ import (
 	"io"
 	"io"
 	"net/http"
 	"net/http"
 	"net/http/httputil"
 	"net/http/httputil"
+	"strings"
 
 
 	frpNet "github.com/fatedier/frp/utils/net"
 	frpNet "github.com/fatedier/frp/utils/net"
 )
 )
@@ -35,6 +36,7 @@ type HTTPS2HTTPPlugin struct {
 	keyPath           string
 	keyPath           string
 	hostHeaderRewrite string
 	hostHeaderRewrite string
 	localAddr         string
 	localAddr         string
+	headers           map[string]string
 
 
 	l *Listener
 	l *Listener
 	s *http.Server
 	s *http.Server
@@ -45,6 +47,15 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
 	keyPath := params["plugin_key_path"]
 	keyPath := params["plugin_key_path"]
 	localAddr := params["plugin_local_addr"]
 	localAddr := params["plugin_local_addr"]
 	hostHeaderRewrite := params["plugin_host_header_rewrite"]
 	hostHeaderRewrite := params["plugin_host_header_rewrite"]
+	headers := make(map[string]string)
+	for k, v := range params {
+		if !strings.HasPrefix(k, "plugin_header_") {
+			continue
+		}
+		if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
+			headers[k] = v
+		}
+	}
 
 
 	if crtPath == "" {
 	if crtPath == "" {
 		return nil, fmt.Errorf("plugin_crt_path is required")
 		return nil, fmt.Errorf("plugin_crt_path is required")
@@ -63,6 +74,7 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
 		keyPath:           keyPath,
 		keyPath:           keyPath,
 		localAddr:         localAddr,
 		localAddr:         localAddr,
 		hostHeaderRewrite: hostHeaderRewrite,
 		hostHeaderRewrite: hostHeaderRewrite,
+		headers:           headers,
 		l:                 listener,
 		l:                 listener,
 	}
 	}
 
 
@@ -73,6 +85,9 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
 			if p.hostHeaderRewrite != "" {
 			if p.hostHeaderRewrite != "" {
 				req.Host = p.hostHeaderRewrite
 				req.Host = p.hostHeaderRewrite
 			}
 			}
+			for k, v := range p.headers {
+				req.Header.Set(k, v)
+			}
 		},
 		},
 	}
 	}
 
 

+ 22 - 14
server/control.go

@@ -21,7 +21,6 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/consts"
 	"github.com/fatedier/frp/models/consts"
 	frpErr "github.com/fatedier/frp/models/errors"
 	frpErr "github.com/fatedier/frp/models/errors"
@@ -129,11 +128,19 @@ type Control struct {
 	allShutdown     *shutdown.Shutdown
 	allShutdown     *shutdown.Shutdown
 
 
 	mu sync.RWMutex
 	mu sync.RWMutex
+
+	// Server configuration information
+	serverCfg config.ServerCommonConf
 }
 }
 
 
 func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
 func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
-	statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login) *Control {
+	statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login,
+	serverCfg config.ServerCommonConf) *Control {
 
 
+	poolCount := loginMsg.PoolCount
+	if poolCount > int(serverCfg.MaxPoolCount) {
+		poolCount = int(serverCfg.MaxPoolCount)
+	}
 	return &Control{
 	return &Control{
 		rc:              rc,
 		rc:              rc,
 		pxyManager:      pxyManager,
 		pxyManager:      pxyManager,
@@ -142,9 +149,9 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
 		loginMsg:        loginMsg,
 		loginMsg:        loginMsg,
 		sendCh:          make(chan msg.Message, 10),
 		sendCh:          make(chan msg.Message, 10),
 		readCh:          make(chan msg.Message, 10),
 		readCh:          make(chan msg.Message, 10),
-		workConnCh:      make(chan net.Conn, loginMsg.PoolCount+10),
+		workConnCh:      make(chan net.Conn, poolCount+10),
 		proxies:         make(map[string]proxy.Proxy),
 		proxies:         make(map[string]proxy.Proxy),
-		poolCount:       loginMsg.PoolCount,
+		poolCount:       poolCount,
 		portsUsedNum:    0,
 		portsUsedNum:    0,
 		lastPing:        time.Now(),
 		lastPing:        time.Now(),
 		runId:           loginMsg.RunId,
 		runId:           loginMsg.RunId,
@@ -153,6 +160,7 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
 		writerShutdown:  shutdown.New(),
 		writerShutdown:  shutdown.New(),
 		managerShutdown: shutdown.New(),
 		managerShutdown: shutdown.New(),
 		allShutdown:     shutdown.New(),
 		allShutdown:     shutdown.New(),
+		serverCfg:       serverCfg,
 	}
 	}
 }
 }
 
 
@@ -161,7 +169,7 @@ func (ctl *Control) Start() {
 	loginRespMsg := &msg.LoginResp{
 	loginRespMsg := &msg.LoginResp{
 		Version:       version.Full(),
 		Version:       version.Full(),
 		RunId:         ctl.runId,
 		RunId:         ctl.runId,
-		ServerUdpPort: g.GlbServerCfg.BindUdpPort,
+		ServerUdpPort: ctl.serverCfg.BindUdpPort,
 		Error:         "",
 		Error:         "",
 	}
 	}
 	msg.WriteMsg(ctl.conn, loginRespMsg)
 	msg.WriteMsg(ctl.conn, loginRespMsg)
@@ -232,7 +240,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
 				return
 				return
 			}
 			}
 
 
-		case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second):
+		case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second):
 			err = fmt.Errorf("timeout trying to get work connection")
 			err = fmt.Errorf("timeout trying to get work connection")
 			ctl.conn.Warn("%v", err)
 			ctl.conn.Warn("%v", err)
 			return
 			return
@@ -263,7 +271,7 @@ func (ctl *Control) writer() {
 	defer ctl.allShutdown.Start()
 	defer ctl.allShutdown.Start()
 	defer ctl.writerShutdown.Done()
 	defer ctl.writerShutdown.Done()
 
 
-	encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token))
+	encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))
 	if err != nil {
 	if err != nil {
 		ctl.conn.Error("crypto new writer error: %v", err)
 		ctl.conn.Error("crypto new writer error: %v", err)
 		ctl.allShutdown.Start()
 		ctl.allShutdown.Start()
@@ -293,7 +301,7 @@ func (ctl *Control) reader() {
 	defer ctl.allShutdown.Start()
 	defer ctl.allShutdown.Start()
 	defer ctl.readerShutdown.Done()
 	defer ctl.readerShutdown.Done()
 
 
-	encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token))
+	encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token))
 	for {
 	for {
 		if m, err := msg.ReadMsg(encReader); err != nil {
 		if m, err := msg.ReadMsg(encReader); err != nil {
 			if err == io.EOF {
 			if err == io.EOF {
@@ -374,7 +382,7 @@ func (ctl *Control) manager() {
 	for {
 	for {
 		select {
 		select {
 		case <-heartbeat.C:
 		case <-heartbeat.C:
-			if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second {
+			if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartBeatTimeout)*time.Second {
 				ctl.conn.Warn("heartbeat timeout")
 				ctl.conn.Warn("heartbeat timeout")
 				return
 				return
 			}
 			}
@@ -417,22 +425,22 @@ func (ctl *Control) manager() {
 func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
 func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
 	var pxyConf config.ProxyConf
 	var pxyConf config.ProxyConf
 	// Load configures from NewProxy message and check.
 	// Load configures from NewProxy message and check.
-	pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
+	pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
 
 
 	// NewProxy will return a interface Proxy.
 	// NewProxy will return a interface Proxy.
 	// In fact it create different proxies by different proxy type, we just call run() here.
 	// In fact it create different proxies by different proxy type, we just call run() here.
-	pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf)
+	pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
 	if err != nil {
 	if err != nil {
 		return remoteAddr, err
 		return remoteAddr, err
 	}
 	}
 
 
 	// Check ports used number in each client
 	// Check ports used number in each client
-	if g.GlbServerCfg.MaxPortsPerClient > 0 {
+	if ctl.serverCfg.MaxPortsPerClient > 0 {
 		ctl.mu.Lock()
 		ctl.mu.Lock()
-		if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) {
+		if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.serverCfg.MaxPortsPerClient) {
 			ctl.mu.Unlock()
 			ctl.mu.Unlock()
 			err = fmt.Errorf("exceed the max_ports_per_client")
 			err = fmt.Errorf("exceed the max_ports_per_client")
 			return
 			return
@@ -478,7 +486,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
 		return
 		return
 	}
 	}
 
 
-	if g.GlbServerCfg.MaxPortsPerClient > 0 {
+	if ctl.serverCfg.MaxPortsPerClient > 0 {
 		ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
 		ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
 	}
 	}
 	pxy.Close()
 	pxy.Close()

+ 1 - 2
server/dashboard.go

@@ -21,7 +21,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/fatedier/frp/assets"
 	"github.com/fatedier/frp/assets"
-	"github.com/fatedier/frp/g"
 	frpNet "github.com/fatedier/frp/utils/net"
 	frpNet "github.com/fatedier/frp/utils/net"
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
@@ -36,7 +35,7 @@ func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
 	// url router
 	// url router
 	router := mux.NewRouter()
 	router := mux.NewRouter()
 
 
-	user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd
+	user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
 	router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
 	router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
 
 
 	// api, see dashboard_api.go
 	// api, see dashboard_api.go

+ 9 - 11
server/dashboard_api.go

@@ -18,7 +18,6 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"net/http"
 	"net/http"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/consts"
 	"github.com/fatedier/frp/models/consts"
 	"github.com/fatedier/frp/utils/log"
 	"github.com/fatedier/frp/utils/log"
@@ -63,19 +62,18 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
 	}()
 	}()
 
 
 	log.Info("Http request: [%s]", r.URL.Path)
 	log.Info("Http request: [%s]", r.URL.Path)
-	cfg := &g.GlbServerCfg.ServerCommonConf
 	serverStats := svr.statsCollector.GetServer()
 	serverStats := svr.statsCollector.GetServer()
 	svrResp := ServerInfoResp{
 	svrResp := ServerInfoResp{
 		Version:           version.Full(),
 		Version:           version.Full(),
-		BindPort:          cfg.BindPort,
-		BindUdpPort:       cfg.BindUdpPort,
-		VhostHttpPort:     cfg.VhostHttpPort,
-		VhostHttpsPort:    cfg.VhostHttpsPort,
-		KcpBindPort:       cfg.KcpBindPort,
-		SubdomainHost:     cfg.SubDomainHost,
-		MaxPoolCount:      cfg.MaxPoolCount,
-		MaxPortsPerClient: cfg.MaxPortsPerClient,
-		HeartBeatTimeout:  cfg.HeartBeatTimeout,
+		BindPort:          svr.cfg.BindPort,
+		BindUdpPort:       svr.cfg.BindUdpPort,
+		VhostHttpPort:     svr.cfg.VhostHttpPort,
+		VhostHttpsPort:    svr.cfg.VhostHttpsPort,
+		KcpBindPort:       svr.cfg.KcpBindPort,
+		SubdomainHost:     svr.cfg.SubDomainHost,
+		MaxPoolCount:      svr.cfg.MaxPoolCount,
+		MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
+		HeartBeatTimeout:  svr.cfg.HeartBeatTimeout,
 
 
 		TotalTrafficIn:  serverStats.TotalTrafficIn,
 		TotalTrafficIn:  serverStats.TotalTrafficIn,
 		TotalTrafficOut: serverStats.TotalTrafficOut,
 		TotalTrafficOut: serverStats.TotalTrafficOut,

+ 4 - 5
server/proxy/http.go

@@ -19,7 +19,6 @@ import (
 	"net"
 	"net"
 	"strings"
 	"strings"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/server/stats"
 	"github.com/fatedier/frp/server/stats"
 	frpNet "github.com/fatedier/frp/utils/net"
 	frpNet "github.com/fatedier/frp/utils/net"
@@ -88,13 +87,13 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
 					pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
 					pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
 				})
 				})
 			}
 			}
-			addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpPort)))
+			addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpPort)))
 			pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
 			pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
 		}
 		}
 	}
 	}
 
 
 	if pxy.cfg.SubDomain != "" {
 	if pxy.cfg.SubDomain != "" {
-		routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
+		routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
 		for _, location := range locations {
 		for _, location := range locations {
 			routeConfig.Location = location
 			routeConfig.Location = location
 			tmpDomain := routeConfig.Domain
 			tmpDomain := routeConfig.Domain
@@ -119,7 +118,7 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
 					pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
 					pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
 				})
 				})
 			}
 			}
-			addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
+			addrs = append(addrs, util.CanonicalAddr(tmpDomain, pxy.serverCfg.VhostHttpPort))
 
 
 			pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
 			pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
 		}
 		}
@@ -147,7 +146,7 @@ func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err
 
 
 	var rwc io.ReadWriteCloser = tmpConn
 	var rwc io.ReadWriteCloser = tmpConn
 	if pxy.cfg.UseEncryption {
 	if pxy.cfg.UseEncryption {
-		rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token))
+		rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
 		if err != nil {
 		if err != nil {
 			pxy.Error("create encryption stream error: %v", err)
 			pxy.Error("create encryption stream error: %v", err)
 			return
 			return

+ 3 - 4
server/proxy/https.go

@@ -17,7 +17,6 @@ package proxy
 import (
 import (
 	"strings"
 	"strings"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/utils/util"
 	"github.com/fatedier/frp/utils/util"
 	"github.com/fatedier/frp/utils/vhost"
 	"github.com/fatedier/frp/utils/vhost"
@@ -51,11 +50,11 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
 		l.AddLogPrefix(pxy.name)
 		l.AddLogPrefix(pxy.name)
 		pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
 		pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
 		pxy.listeners = append(pxy.listeners, l)
 		pxy.listeners = append(pxy.listeners, l)
-		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort))
+		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort))
 	}
 	}
 
 
 	if pxy.cfg.SubDomain != "" {
 	if pxy.cfg.SubDomain != "" {
-		routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
+		routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
 		l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
 		l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
 		if errRet != nil {
 		if errRet != nil {
 			err = errRet
 			err = errRet
@@ -64,7 +63,7 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
 		l.AddLogPrefix(pxy.name)
 		l.AddLogPrefix(pxy.name)
 		pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
 		pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
 		pxy.listeners = append(pxy.listeners, l)
 		pxy.listeners = append(pxy.listeners, l)
-		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort)))
+		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort)))
 	}
 	}
 
 
 	pxy.startListenHandler(pxy, HandleUserTcpConnection)
 	pxy.startListenHandler(pxy, HandleUserTcpConnection)

+ 7 - 6
server/proxy/proxy.go

@@ -21,7 +21,6 @@ import (
 	"strconv"
 	"strconv"
 	"sync"
 	"sync"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/server/controller"
 	"github.com/fatedier/frp/server/controller"
@@ -52,6 +51,7 @@ type BaseProxy struct {
 	usedPortsNum   int
 	usedPortsNum   int
 	poolCount      int
 	poolCount      int
 	getWorkConnFn  GetWorkConnFn
 	getWorkConnFn  GetWorkConnFn
+	serverCfg      config.ServerCommonConf
 
 
 	mu sync.RWMutex
 	mu sync.RWMutex
 	log.Logger
 	log.Logger
@@ -126,7 +126,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co
 // startListenHandler start a goroutine handler for each listener.
 // startListenHandler start a goroutine handler for each listener.
 // p: p will just be passed to handler(Proxy, frpNet.Conn).
 // p: p will just be passed to handler(Proxy, frpNet.Conn).
 // handler: each proxy type can set different handler function to deal with connections accepted from listeners.
 // handler: each proxy type can set different handler function to deal with connections accepted from listeners.
-func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector)) {
+func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector, config.ServerCommonConf)) {
 	for _, listener := range pxy.listeners {
 	for _, listener := range pxy.listeners {
 		go func(l frpNet.Listener) {
 		go func(l frpNet.Listener) {
 			for {
 			for {
@@ -138,14 +138,14 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Con
 					return
 					return
 				}
 				}
 				pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
 				pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
-				go handler(p, c, pxy.statsCollector)
+				go handler(p, c, pxy.statsCollector, pxy.serverCfg)
 			}
 			}
 		}(listener)
 		}(listener)
 	}
 	}
 }
 }
 
 
 func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
 func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
-	getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) {
+	getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (pxy Proxy, err error) {
 
 
 	basePxy := BaseProxy{
 	basePxy := BaseProxy{
 		name:           pxyConf.GetBaseInfo().ProxyName,
 		name:           pxyConf.GetBaseInfo().ProxyName,
@@ -155,6 +155,7 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
 		poolCount:      poolCount,
 		poolCount:      poolCount,
 		getWorkConnFn:  getWorkConnFn,
 		getWorkConnFn:  getWorkConnFn,
 		Logger:         log.NewPrefixLogger(runId),
 		Logger:         log.NewPrefixLogger(runId),
+		serverCfg:      serverCfg,
 	}
 	}
 	switch cfg := pxyConf.(type) {
 	switch cfg := pxyConf.(type) {
 	case *config.TcpProxyConf:
 	case *config.TcpProxyConf:
@@ -198,7 +199,7 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
 
 
 // HandleUserTcpConnection is used for incoming tcp user connections.
 // HandleUserTcpConnection is used for incoming tcp user connections.
 // It can be used for tcp, http, https type.
 // It can be used for tcp, http, https type.
-func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector) {
+func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector, serverCfg config.ServerCommonConf) {
 	defer userConn.Close()
 	defer userConn.Close()
 
 
 	// try all connections from the pool
 	// try all connections from the pool
@@ -211,7 +212,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
 	var local io.ReadWriteCloser = workConn
 	var local io.ReadWriteCloser = workConn
 	cfg := pxy.GetConf().GetBaseInfo()
 	cfg := pxy.GetConf().GetBaseInfo()
 	if cfg.UseEncryption {
 	if cfg.UseEncryption {
-		local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token))
+		local, err = frpIo.WithEncryption(local, []byte(serverCfg.Token))
 		if err != nil {
 		if err != nil {
 			pxy.Error("create encryption stream error: %v", err)
 			pxy.Error("create encryption stream error: %v", err)
 			return
 			return

+ 2 - 3
server/proxy/tcp.go

@@ -17,7 +17,6 @@ package proxy
 import (
 import (
 	"fmt"
 	"fmt"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	frpNet "github.com/fatedier/frp/utils/net"
 	frpNet "github.com/fatedier/frp/utils/net"
 )
 )
@@ -31,7 +30,7 @@ type TcpProxy struct {
 
 
 func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
 func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
 	if pxy.cfg.Group != "" {
 	if pxy.cfg.Group != "" {
-		l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort)
+		l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
 		if errRet != nil {
 		if errRet != nil {
 			err = errRet
 			err = errRet
 			return
 			return
@@ -56,7 +55,7 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
 				pxy.rc.TcpPortManager.Release(pxy.realPort)
 				pxy.rc.TcpPortManager.Release(pxy.realPort)
 			}
 			}
 		}()
 		}()
-		listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort)
+		listener, errRet := frpNet.ListenTcp(pxy.serverCfg.ProxyBindAddr, pxy.realPort)
 		if errRet != nil {
 		if errRet != nil {
 			err = errRet
 			err = errRet
 			return
 			return

+ 1 - 2
server/proxy/udp.go

@@ -20,7 +20,6 @@ import (
 	"net"
 	"net"
 	"time"
 	"time"
 
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/proto/udp"
 	"github.com/fatedier/frp/models/proto/udp"
@@ -67,7 +66,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
 
 
 	remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
 	remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
 	pxy.cfg.RemotePort = pxy.realPort
 	pxy.cfg.RemotePort = pxy.realPort
-	addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort))
+	addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
 	if errRet != nil {
 	if errRet != nil {
 		err = errRet
 		err = errRet
 		return
 		return

+ 16 - 16
server/service.go

@@ -29,7 +29,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/fatedier/frp/assets"
 	"github.com/fatedier/frp/assets"
-	"github.com/fatedier/frp/g"
+	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/nathole"
 	"github.com/fatedier/frp/models/nathole"
 	"github.com/fatedier/frp/server/controller"
 	"github.com/fatedier/frp/server/controller"
@@ -51,8 +51,6 @@ const (
 	connReadTimeout time.Duration = 10 * time.Second
 	connReadTimeout time.Duration = 10 * time.Second
 )
 )
 
 
-var ServerService *Service
-
 // Server service
 // Server service
 type Service struct {
 type Service struct {
 	// Dispatch connections to different handlers listen on same port
 	// Dispatch connections to different handlers listen on same port
@@ -86,10 +84,11 @@ type Service struct {
 	statsCollector stats.Collector
 	statsCollector stats.Collector
 
 
 	tlsConfig *tls.Config
 	tlsConfig *tls.Config
+
+	cfg config.ServerCommonConf
 }
 }
 
 
-func NewService() (svr *Service, err error) {
-	cfg := &g.GlbServerCfg.ServerCommonConf
+func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
 	svr = &Service{
 	svr = &Service{
 		ctlManager: NewControlManager(),
 		ctlManager: NewControlManager(),
 		pxyManager: proxy.NewProxyManager(),
 		pxyManager: proxy.NewProxyManager(),
@@ -100,6 +99,7 @@ func NewService() (svr *Service, err error) {
 		},
 		},
 		httpVhostRouter: vhost.NewVhostRouters(),
 		httpVhostRouter: vhost.NewVhostRouters(),
 		tlsConfig:       generateTLSConfig(),
 		tlsConfig:       generateTLSConfig(),
+		cfg:             cfg,
 	}
 	}
 
 
 	// Init group controller
 	// Init group controller
@@ -108,13 +108,6 @@ func NewService() (svr *Service, err error) {
 	// Init HTTP group controller
 	// Init HTTP group controller
 	svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
 	svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
 
 
-	// Init assets
-	err = assets.Load(cfg.AssetsDir)
-	if err != nil {
-		err = fmt.Errorf("Load assets error: %v", err)
-		return
-	}
-
 	// Init 404 not found page
 	// Init 404 not found page
 	vhost.NotFoundPagePath = cfg.Custom404Page
 	vhost.NotFoundPagePath = cfg.Custom404Page
 
 
@@ -231,6 +224,13 @@ func NewService() (svr *Service, err error) {
 	var statsEnable bool
 	var statsEnable bool
 	// Create dashboard web server.
 	// Create dashboard web server.
 	if cfg.DashboardPort > 0 {
 	if cfg.DashboardPort > 0 {
+		// Init dashboard assets
+		err = assets.Load(cfg.AssetsDir)
+		if err != nil {
+			err = fmt.Errorf("Load assets error: %v", err)
+			return
+		}
+
 		err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
 		err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
 		if err != nil {
 		if err != nil {
 			err = fmt.Errorf("Create dashboard web server error, %v", err)
 			err = fmt.Errorf("Create dashboard web server error, %v", err)
@@ -248,7 +248,7 @@ func (svr *Service) Run() {
 	if svr.rc.NatHoleController != nil {
 	if svr.rc.NatHoleController != nil {
 		go svr.rc.NatHoleController.Run()
 		go svr.rc.NatHoleController.Run()
 	}
 	}
-	if g.GlbServerCfg.KcpBindPort > 0 {
+	if svr.cfg.KcpBindPort > 0 {
 		go svr.HandleListener(svr.kcpListener)
 		go svr.HandleListener(svr.kcpListener)
 	}
 	}
 
 
@@ -324,7 +324,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
 				}
 				}
 			}
 			}
 
 
-			if g.GlbServerCfg.TcpMux {
+			if svr.cfg.TcpMux {
 				fmuxCfg := fmux.DefaultConfig()
 				fmuxCfg := fmux.DefaultConfig()
 				fmuxCfg.KeepAliveInterval = 20 * time.Second
 				fmuxCfg.KeepAliveInterval = 20 * time.Second
 				fmuxCfg.LogOutput = ioutil.Discard
 				fmuxCfg.LogOutput = ioutil.Discard
@@ -363,7 +363,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
 	}
 	}
 
 
 	// Check auth.
 	// Check auth.
-	if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
+	if util.GetAuthKey(svr.cfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
 		err = fmt.Errorf("authorization failed")
 		err = fmt.Errorf("authorization failed")
 		return
 		return
 	}
 	}
@@ -377,7 +377,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
 		}
 		}
 	}
 	}
 
 
-	ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg)
+	ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg)
 
 
 	if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
 	if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
 		oldCtl.allShutdown.WaitDone()
 		oldCtl.allShutdown.WaitDone()

+ 46 - 0
tests/ci/health/health_test.go

@@ -67,6 +67,20 @@ custom_domains = test2.com
 health_check_type = http
 health_check_type = http
 health_check_interval_s = 1
 health_check_interval_s = 1
 health_check_url = /health
 health_check_url = /health
+
+[http3]
+type = http
+local_port = 15005
+custom_domains = test.balancing.com
+group = test-balancing
+group_key = 123
+
+[http4]
+type = http
+local_port = 15006
+custom_domains = test.balancing.com
+group = test-balancing
+group_key = 123
 `
 `
 
 
 func TestHealthCheck(t *testing.T) {
 func TestHealthCheck(t *testing.T) {
@@ -124,6 +138,22 @@ func TestHealthCheck(t *testing.T) {
 		defer httpSvc2.Stop()
 		defer httpSvc2.Stop()
 	}
 	}
 
 
+	httpSvc3 := mock.NewHttpServer(15005, func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte("http3"))
+	})
+	err = httpSvc3.Start()
+	if assert.NoError(err) {
+		defer httpSvc3.Stop()
+	}
+
+	httpSvc4 := mock.NewHttpServer(15006, func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte("http4"))
+	})
+	err = httpSvc4.Start()
+	if assert.NoError(err) {
+		defer httpSvc4.Stop()
+	}
+
 	time.Sleep(200 * time.Millisecond)
 	time.Sleep(200 * time.Millisecond)
 
 
 	// ****** start frps and frpc ******
 	// ****** start frps and frpc ******
@@ -244,4 +274,20 @@ func TestHealthCheck(t *testing.T) {
 	assert.NoError(err)
 	assert.NoError(err)
 	assert.Equal(200, code)
 	assert.Equal(200, code)
 	assert.Equal("http2", body)
 	assert.Equal("http2", body)
+
+	// ****** load balancing type http ******
+	result = make([]string, 0)
+
+	code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
+	assert.NoError(err)
+	assert.Equal(200, code)
+	result = append(result, body)
+
+	code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
+	assert.NoError(err)
+	assert.Equal(200, code)
+	result = append(result, body)
+
+	assert.Contains(result, "http3")
+	assert.Contains(result, "http4")
 }
 }

+ 8 - 4
utils/log/log.go

@@ -29,16 +29,20 @@ func init() {
 	Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
 	Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
 }
 }
 
 
-func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
-	SetLogFile(logWay, logFile, maxdays)
+func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
+	SetLogFile(logWay, logFile, maxdays, disableLogColor)
 	SetLogLevel(logLevel)
 	SetLogLevel(logLevel)
 }
 }
 
 
 // SetLogFile to configure log params
 // SetLogFile to configure log params
 // logWay: file or console
 // logWay: file or console
-func SetLogFile(logWay string, logFile string, maxdays int64) {
+func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
 	if logWay == "console" {
 	if logWay == "console" {
-		Log.SetLogger("console", "")
+		params := ""
+		if disableLogColor {
+			params = fmt.Sprintf(`{"color": false}`)
+		}
+		Log.SetLogger("console", params)
 	} else {
 	} else {
 		params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
 		params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
 		Log.SetLogger("file", params)
 		Log.SetLogger("file", params)

+ 1 - 1
utils/version/version.go

@@ -19,7 +19,7 @@ import (
 	"strings"
 	"strings"
 )
 )
 
 
-var version string = "0.28.2"
+var version string = "0.29.0"
 
 
 func Full() string {
 func Full() string {
 	return version
 	return version