diff --git a/README.md b/README.md index c8008d8e..2e3dacd9 100644 --- a/README.md +++ b/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. -**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 @@ -265,6 +265,7 @@ Configure frps same as above. plugin_crt_path = ./server.crt plugin_key_path = ./server.key plugin_host_header_rewrite = 127.0.0.1 + plugin_header_X-From-Where = frp ``` 2. Visit `https://test.yourdomain.com`. diff --git a/README_zh.md b/README_zh.md index a918930d..a9e03cb2 100644 --- a/README_zh.md +++ b/README_zh.md @@ -129,7 +129,7 @@ master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试 vhost_http_port = 8080 ``` -2. 启动 frps; +2. 启动 frps: `./frps -c ./frps.ini` @@ -270,6 +270,7 @@ frps 的部署步骤同上。 plugin_crt_path = ./server.crt plugin_key_path = ./server.key plugin_host_header_rewrite = 127.0.0.1 + plugin_header_X-From-Where = frp ``` 2. 通过浏览器访问 `https://test.yourdomain.com` 即可。 diff --git a/client/admin.go b/client/admin.go index 90456857..b3e3f81a 100644 --- a/client/admin.go +++ b/client/admin.go @@ -21,7 +21,6 @@ import ( "time" "github.com/fatedier/frp/assets" - "github.com/fatedier/frp/g" frpNet "github.com/fatedier/frp/utils/net" "github.com/gorilla/mux" @@ -36,7 +35,7 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) { // url router 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) // api, see dashboard_api.go diff --git a/client/admin_api.go b/client/admin_api.go index 4d7205bb..af61b395 100644 --- a/client/admin_api.go +++ b/client/admin_api.go @@ -23,7 +23,6 @@ import ( "strings" "github.com/fatedier/frp/client/proxy" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "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 { res.Code = 400 res.Msg = err.Error() @@ -55,7 +54,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { return } - newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content) + newCommonCfg, err := config.UnmarshalClientConfFromIni(content) if err != nil { res.Code = 400 res.Msg = err.Error() @@ -63,7 +62,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { 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 { res.Code = 400 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) 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{ Name: status.Name, Type: status.Type, @@ -121,18 +120,18 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp { } psr.Plugin = cfg.Plugin if status.Err != "" { - psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort) + psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort) } else { - psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr + psr.RemoteAddr = serverAddr + status.RemoteAddr } case *config.UdpProxyConf: if cfg.LocalPort != 0 { psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort) } if status.Err != "" { - psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort) + psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort) } else { - psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr + psr.RemoteAddr = serverAddr + status.RemoteAddr } case *config.HttpProxyConf: if cfg.LocalPort != 0 { @@ -184,17 +183,17 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) { for _, status := range ps { switch status.Type { case "tcp": - res.Tcp = append(res.Tcp, NewProxyStatusResp(status)) + res.Tcp = append(res.Tcp, NewProxyStatusResp(status, svr.cfg.ServerAddr)) case "udp": - res.Udp = append(res.Udp, NewProxyStatusResp(status)) + res.Udp = append(res.Udp, NewProxyStatusResp(status, svr.cfg.ServerAddr)) case "http": - res.Http = append(res.Http, NewProxyStatusResp(status)) + res.Http = append(res.Http, NewProxyStatusResp(status, svr.cfg.ServerAddr)) case "https": - res.Https = append(res.Https, NewProxyStatusResp(status)) + res.Https = append(res.Https, NewProxyStatusResp(status, svr.cfg.ServerAddr)) case "stcp": - res.Stcp = append(res.Stcp, NewProxyStatusResp(status)) + res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr)) case "xtcp": - res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status)) + res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr)) } } 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.Msg = "frpc has no config file path" log.Warn("%s", res.Msg) return } - content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile) + content, err := config.GetRenderedConfFromFile(svr.cfgFile) if err != nil { res.Code = 400 res.Msg = err.Error() @@ -277,7 +276,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) { // get token from origin content token := "" - b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile) + b, err := ioutil.ReadFile(svr.cfgFile) if err != nil { res.Code = 400 res.Msg = err.Error() @@ -316,7 +315,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) { } 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 { res.Code = 500 res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err) diff --git a/client/control.go b/client/control.go index 6a12b8c1..30992f9a 100644 --- a/client/control.go +++ b/client/control.go @@ -23,7 +23,6 @@ import ( "time" "github.com/fatedier/frp/client/proxy" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/utils/log" @@ -65,16 +64,24 @@ type Control struct { // last time got the Pong message lastPong time.Time + // The client configuration + clientCfg config.ClientCommonConf + readerShutdown *shutdown.Shutdown writerShutdown *shutdown.Shutdown msgHandlerShutdown *shutdown.Shutdown + // The UDP port that the server is listening on + serverUDPPort int + mu sync.RWMutex 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{ runId: runId, conn: conn, @@ -84,12 +91,14 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs m readCh: make(chan msg.Message, 100), closedCh: make(chan struct{}), closedDoneCh: make(chan struct{}), + clientCfg: clientCfg, readerShutdown: shutdown.New(), writerShutdown: shutdown.New(), msgHandlerShutdown: shutdown.New(), + serverUDPPort: serverUDPPort, 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.Reload(visitorCfgs) @@ -161,7 +170,7 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} { // connectServer return a new connection to frps func (ctl *Control) connectServer() (conn frpNet.Conn, err error) { - if g.GlbClientCfg.TcpMux { + if ctl.clientCfg.TcpMux { stream, errRet := ctl.session.OpenStream() if errRet != nil { err = errRet @@ -171,13 +180,13 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) { conn = frpNet.WrapConn(stream) } else { var tlsConfig *tls.Config - if g.GlbClientCfg.TLSEnable { + if ctl.clientCfg.TLSEnable { tlsConfig = &tls.Config{ 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 { ctl.Warn("start new connection to server error: %v", err) return @@ -197,7 +206,7 @@ func (ctl *Control) reader() { defer ctl.readerShutdown.Done() defer close(ctl.closedCh) - encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token)) + encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token)) for { if m, err := msg.ReadMsg(encReader); err != nil { if err == io.EOF { @@ -217,7 +226,7 @@ func (ctl *Control) reader() { // writer writes messages got from sendCh to frps func (ctl *Control) writer() { 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 { ctl.conn.Error("crypto new writer error: %v", err) ctl.conn.Close() @@ -246,7 +255,7 @@ func (ctl *Control) msgHandler() { }() 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() hbCheck := time.NewTicker(time.Second) defer hbCheck.Stop() @@ -260,7 +269,7 @@ func (ctl *Control) msgHandler() { ctl.Debug("send heartbeat to server") ctl.sendCh <- &msg.Ping{} 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") // let reader() stop ctl.conn.Close() diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index bc230f40..f5e1b603 100644 --- a/client/proxy/proxy.go +++ b/client/proxy/proxy.go @@ -25,7 +25,6 @@ import ( "sync" "time" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/models/plugin" @@ -51,9 +50,11 @@ type Proxy interface { log.Logger } -func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) { +func NewProxy(pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) { baseProxy := BaseProxy{ - Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName), + Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName), + clientCfg: clientCfg, + serverUDPPort: serverUDPPort, } switch cfg := pxyConf.(type) { case *config.TcpProxyConf: @@ -91,8 +92,10 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) { } type BaseProxy struct { - closed bool - mu sync.RWMutex + closed bool + mu sync.RWMutex + clientCfg config.ClientCommonConf + serverUDPPort int log.Logger } @@ -122,7 +125,7 @@ func (pxy *TcpProxy) Close() { func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, - []byte(g.GlbClientCfg.Token), m) + []byte(pxy.clientCfg.Token), m) } // HTTP @@ -151,7 +154,7 @@ func (pxy *HttpProxy) Close() { func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, - []byte(g.GlbClientCfg.Token), m) + []byte(pxy.clientCfg.Token), m) } // HTTPS @@ -180,7 +183,7 @@ func (pxy *HttpsProxy) Close() { func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, - []byte(g.GlbClientCfg.Token), m) + []byte(pxy.clientCfg.Token), m) } // STCP @@ -209,7 +212,7 @@ func (pxy *StcpProxy) Close() { func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, - []byte(g.GlbClientCfg.Token), m) + []byte(pxy.clientCfg.Token), m) } // XTCP @@ -250,7 +253,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) { Sid: natHoleSidMsg.Sid, } 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) defer clientConn.Close() @@ -518,7 +521,7 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin. DestinationPort: m.DstPort, } - if h.SourceAddress.To16() == nil { + if strings.Contains(m.SrcAddr, ".") { h.TransportProtocol = pp.TCPv4 } else { h.TransportProtocol = pp.TCPv6 diff --git a/client/proxy/proxy_manager.go b/client/proxy/proxy_manager.go index fb230b3e..1d0d8786 100644 --- a/client/proxy/proxy_manager.go +++ b/client/proxy/proxy_manager.go @@ -20,17 +20,24 @@ type ProxyManager struct { closed bool mu sync.RWMutex + clientCfg config.ClientCommonConf + + // The UDP port that the server is listening on + serverUDPPort int + logPrefix string 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{ - 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) for name, cfg := range pxyCfgs { 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 addPxyNames = append(addPxyNames, name) diff --git a/client/proxy/proxy_wrapper.go b/client/proxy/proxy_wrapper.go index 0b29e481..b02e5291 100644 --- a/client/proxy/proxy_wrapper.go +++ b/client/proxy/proxy_wrapper.go @@ -65,7 +65,7 @@ type ProxyWrapper struct { 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() pw := &ProxyWrapper{ ProxyStatus: ProxyStatus{ @@ -90,7 +90,7 @@ func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logP pw.Trace("enable health check monitor") } - pw.pxy = NewProxy(pw.Cfg) + pw.pxy = NewProxy(pw.Cfg, clientCfg, serverUDPPort) return pw } diff --git a/client/service.go b/client/service.go index f21be4fe..9f86ec7a 100644 --- a/client/service.go +++ b/client/service.go @@ -24,7 +24,6 @@ import ( "time" "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/utils/log" @@ -35,6 +34,7 @@ import ( fmux "github.com/hashicorp/yamux" ) +// Service is a client service. type Service struct { // uniq id got from frps, attach it in loginMsg runId string @@ -43,23 +43,27 @@ type Service struct { ctl *Control ctlMu sync.RWMutex + cfg config.ClientCommonConf pxyCfgs map[string]config.ProxyConf visitorCfgs map[string]config.VisitorConf 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 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{ + cfg: cfg, + cfgFile: cfgFile, pxyCfgs: pxyCfgs, visitorCfgs: visitorCfgs, exit: 0, @@ -83,14 +87,14 @@ func (svr *Service) Run() error { // if login_fail_exit is true, just exit this program // otherwise sleep a while and try again to connect to server - if g.GlbClientCfg.LoginFailExit { + if svr.cfg.LoginFailExit { return err } else { time.Sleep(10 * time.Second) } } else { // 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() svr.ctlMu.Lock() svr.ctl = ctl @@ -101,12 +105,18 @@ func (svr *Service) Run() error { 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 { 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 @@ -138,7 +148,7 @@ func (svr *Service) keepControllerWorking() { // reconnect success, init delayTime 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() svr.ctlMu.Lock() svr.ctl = ctl @@ -153,13 +163,13 @@ func (svr *Service) keepControllerWorking() { // session: if it's not nil, using tcp mux func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) { var tlsConfig *tls.Config - if g.GlbClientCfg.TLSEnable { + if svr.cfg.TLSEnable { tlsConfig = &tls.Config{ 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 { 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.KeepAliveInterval = 20 * time.Second fmuxCfg.LogOutput = ioutil.Discard @@ -194,10 +204,10 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) loginMsg := &msg.Login{ Arch: runtime.GOARCH, Os: runtime.GOOS, - PoolCount: g.GlbClientCfg.PoolCount, - User: g.GlbClientCfg.User, + PoolCount: svr.cfg.PoolCount, + User: svr.cfg.User, Version: version.Full(), - PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now), + PrivilegeKey: util.GetAuthKey(svr.cfg.Token, now), Timestamp: now, RunId: svr.runId, } @@ -220,7 +230,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) } 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) return } diff --git a/client/visitor.go b/client/visitor.go index 6eb3688c..773aa115 100644 --- a/client/visitor.go +++ b/client/visitor.go @@ -23,7 +23,6 @@ import ( "sync" "time" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/utils/log" @@ -193,13 +192,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { defer userConn.Close() 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") return } 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 { sv.Error("resolve server UDP addr error") return diff --git a/cmd/frpc/main.go b/cmd/frpc/main.go index ba3d932a..443c110c 100644 --- a/cmd/frpc/main.go +++ b/cmd/frpc/main.go @@ -15,6 +15,9 @@ package main import ( + "math/rand" + "time" + _ "github.com/fatedier/frp/assets/frpc/statik" "github.com/fatedier/frp/cmd/frpc/sub" @@ -23,6 +26,7 @@ import ( func main() { crypto.DefaultSalt = "frp" + rand.Seed(time.Now().UnixNano()) sub.Execute() } diff --git a/cmd/frpc/sub/http.go b/cmd/frpc/sub/http.go index 08e915cd..081102cb 100644 --- a/cmd/frpc/sub/http.go +++ b/cmd/frpc/sub/http.go @@ -33,6 +33,7 @@ func init() { httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console") httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") @@ -53,7 +54,7 @@ var httpCmd = &cobra.Command{ Use: "http", Short: "Run frpc with a single http proxy", RunE: func(cmd *cobra.Command, args []string) error { - err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") if err != nil { fmt.Println(err) os.Exit(1) @@ -86,7 +87,7 @@ var httpCmd = &cobra.Command{ proxyConfs := map[string]config.ProxyConf{ cfg.ProxyName: cfg, } - err = startService(proxyConfs, nil) + err = startService(clientCfg, proxyConfs, nil, "") if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/https.go b/cmd/frpc/sub/https.go index 5eb6a7d0..7d5fbe8b 100644 --- a/cmd/frpc/sub/https.go +++ b/cmd/frpc/sub/https.go @@ -33,6 +33,7 @@ func init() { httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console") httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") @@ -49,7 +50,7 @@ var httpsCmd = &cobra.Command{ Use: "https", Short: "Run frpc with a single https proxy", RunE: func(cmd *cobra.Command, args []string) error { - err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") if err != nil { fmt.Println(err) os.Exit(1) @@ -78,7 +79,7 @@ var httpsCmd = &cobra.Command{ proxyConfs := map[string]config.ProxyConf{ cfg.ProxyName: cfg, } - err = startService(proxyConfs, nil) + err = startService(clientCfg, proxyConfs, nil, "") if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/reload.go b/cmd/frpc/sub/reload.go index 46118c3e..f3514754 100644 --- a/cmd/frpc/sub/reload.go +++ b/cmd/frpc/sub/reload.go @@ -24,7 +24,6 @@ import ( "github.com/spf13/cobra" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" ) @@ -42,13 +41,13 @@ var reloadCmd = &cobra.Command{ os.Exit(1) } - err = parseClientCommonCfg(CfgFileTypeIni, iniContent) + clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent) if err != nil { fmt.Println(err) os.Exit(1) } - err = reload() + err = reload(clientCfg) if err != nil { fmt.Printf("frpc reload error: %v\n", err) 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") } 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 { 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) resp, err := http.DefaultClient.Do(req) diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go index 61d9b3dd..82a906bd 100644 --- a/cmd/frpc/sub/root.go +++ b/cmd/frpc/sub/root.go @@ -28,7 +28,6 @@ import ( "github.com/spf13/cobra" "github.com/fatedier/frp/client" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/utils/log" "github.com/fatedier/frp/utils/version" @@ -43,13 +42,14 @@ var ( cfgFile string 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 localIp string @@ -113,59 +113,62 @@ func handleSignal(svr *client.Service) { close(kcpDoneCh) } -func parseClientCommonCfg(fileType int, content string) (err error) { +func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommonConf, err error) { if fileType == CfgFileTypeIni { - err = parseClientCommonCfgFromIni(content) + cfg, err = parseClientCommonCfgFromIni(content) } else if fileType == CfgFileTypeCmd { - err = parseClientCommonCfgFromCmd() + cfg, err = parseClientCommonCfgFromCmd() } if err != nil { return } - err = g.GlbClientCfg.ClientCommonConf.Check() + err = cfg.Check() if err != nil { 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 { - 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, ":") if len(strs) < 2 { err = fmt.Errorf("invalid server_addr") return } 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 { err = fmt.Errorf("invalid server_addr") 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" { - g.GlbClientCfg.LogWay = "console" + cfg.LogWay = "console" } else { - g.GlbClientCfg.LogWay = "file" + cfg.LogWay = "file" } - return nil + cfg.DisableLogColor = disableLogColor + + return } func runClient(cfgFilePath string) (err error) { @@ -174,26 +177,27 @@ func runClient(cfgFilePath string) (err error) { if err != nil { return } - g.GlbClientCfg.CfgFile = cfgFilePath - err = parseClientCommonCfg(CfgFileTypeIni, content) + cfg, err := parseClientCommonCfg(CfgFileTypeIni, content) if err != nil { 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 { return err } - err = startService(pxyCfgs, visitorCfgs) + err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath) 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, ":") { 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 { err = errRet return } // Capture the exit signal if we use kcp. - if g.GlbClientCfg.Protocol == "kcp" { + if cfg.Protocol == "kcp" { go handleSignal(svr) } err = svr.Run() - if g.GlbClientCfg.Protocol == "kcp" { + if cfg.Protocol == "kcp" { <-kcpDoneCh } return diff --git a/cmd/frpc/sub/status.go b/cmd/frpc/sub/status.go index 8f55ce13..783ec29f 100644 --- a/cmd/frpc/sub/status.go +++ b/cmd/frpc/sub/status.go @@ -27,7 +27,6 @@ import ( "github.com/spf13/cobra" "github.com/fatedier/frp/client" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" ) @@ -45,13 +44,13 @@ var statusCmd = &cobra.Command{ os.Exit(1) } - err = parseClientCommonCfg(CfgFileTypeIni, iniContent) + clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent) if err != nil { fmt.Println(err) os.Exit(1) } - err = status() + err = status(clientCfg) if err != nil { fmt.Printf("frpc get status error: %v\n", err) 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") } 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 { 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) resp, err := http.DefaultClient.Do(req) diff --git a/cmd/frpc/sub/stcp.go b/cmd/frpc/sub/stcp.go index c91040ee..1a04fbf3 100644 --- a/cmd/frpc/sub/stcp.go +++ b/cmd/frpc/sub/stcp.go @@ -32,6 +32,7 @@ func init() { stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console") stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role") @@ -51,7 +52,7 @@ var stcpCmd = &cobra.Command{ Use: "stcp", Short: "Run frpc with a single stcp proxy", RunE: func(cmd *cobra.Command, args []string) error { - err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") if err != nil { fmt.Println(err) os.Exit(1) @@ -103,7 +104,7 @@ var stcpCmd = &cobra.Command{ os.Exit(1) } - err = startService(proxyConfs, visitorConfs) + err = startService(clientCfg, proxyConfs, visitorConfs, "") if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/tcp.go b/cmd/frpc/sub/tcp.go index 154db151..c2ccd2b8 100644 --- a/cmd/frpc/sub/tcp.go +++ b/cmd/frpc/sub/tcp.go @@ -32,6 +32,7 @@ func init() { tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console") tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") @@ -47,7 +48,7 @@ var tcpCmd = &cobra.Command{ Use: "tcp", Short: "Run frpc with a single tcp proxy", RunE: func(cmd *cobra.Command, args []string) error { - err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") if err != nil { fmt.Println(err) os.Exit(1) @@ -75,7 +76,7 @@ var tcpCmd = &cobra.Command{ proxyConfs := map[string]config.ProxyConf{ cfg.ProxyName: cfg, } - err = startService(proxyConfs, nil) + err = startService(clientCfg, proxyConfs, nil, "") if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/udp.go b/cmd/frpc/sub/udp.go index 3572e416..0d73c763 100644 --- a/cmd/frpc/sub/udp.go +++ b/cmd/frpc/sub/udp.go @@ -32,6 +32,7 @@ func init() { udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console") udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") @@ -47,7 +48,7 @@ var udpCmd = &cobra.Command{ Use: "udp", Short: "Run frpc with a single udp proxy", RunE: func(cmd *cobra.Command, args []string) error { - err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") if err != nil { fmt.Println(err) os.Exit(1) @@ -75,7 +76,7 @@ var udpCmd = &cobra.Command{ proxyConfs := map[string]config.ProxyConf{ cfg.ProxyName: cfg, } - err = startService(proxyConfs, nil) + err = startService(clientCfg, proxyConfs, nil, "") if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/xtcp.go b/cmd/frpc/sub/xtcp.go index c15ac5ad..558294da 100644 --- a/cmd/frpc/sub/xtcp.go +++ b/cmd/frpc/sub/xtcp.go @@ -32,6 +32,7 @@ func init() { xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") 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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console") xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role") @@ -51,7 +52,7 @@ var xtcpCmd = &cobra.Command{ Use: "xtcp", Short: "Run frpc with a single xtcp proxy", RunE: func(cmd *cobra.Command, args []string) error { - err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") if err != nil { fmt.Println(err) os.Exit(1) @@ -103,7 +104,7 @@ var xtcpCmd = &cobra.Command{ os.Exit(1) } - err = startService(proxyConfs, visitorConfs) + err = startService(clientCfg, proxyConfs, visitorConfs, "") if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frps/main.go b/cmd/frps/main.go index 4253aed8..8264dd70 100644 --- a/cmd/frps/main.go +++ b/cmd/frps/main.go @@ -15,6 +15,9 @@ package main import ( + "math/rand" + "time" + "github.com/fatedier/golib/crypto" _ "github.com/fatedier/frp/assets/frps/statik" @@ -22,6 +25,7 @@ import ( func main() { crypto.DefaultSalt = "frp" + rand.Seed(time.Now().UnixNano()) Execute() } diff --git a/cmd/frps/root.go b/cmd/frps/root.go index 4dcd8896..fd6cdbde 100644 --- a/cmd/frps/root.go +++ b/cmd/frps/root.go @@ -20,7 +20,6 @@ import ( "github.com/spf13/cobra" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/server" "github.com/fatedier/frp/utils/log" @@ -53,6 +52,7 @@ var ( logFile string logLevel string logMaxDays int64 + disableLogColor bool token string subDomainHost string tcpMux bool @@ -80,6 +80,8 @@ func init() { rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file") rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") 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(&subDomainHost, "subdomain_host", "", "", "subdomain host") rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports") @@ -95,6 +97,7 @@ var rootCmd = &cobra.Command{ return nil } + var cfg config.ServerCommonConf var err error if cfgFile != "" { var content string @@ -102,16 +105,15 @@ var rootCmd = &cobra.Command{ if err != nil { return err } - g.GlbServerCfg.CfgFile = cfgFile - err = parseServerCommonCfg(CfgFileTypeIni, content) + cfg, err = parseServerCommonCfg(CfgFileTypeIni, content) } else { - err = parseServerCommonCfg(CfgFileTypeCmd, "") + cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "") } if err != nil { return err } - err = runServer() + err = runServer(cfg) if err != nil { fmt.Println(err) 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 { - err = parseServerCommonCfgFromIni(content) + cfg, err = parseServerCommonCfgFromIni(content) } else if fileType == CfgFileTypeCmd { - err = parseServerCommonCfgFromCmd() + cfg, err = parseServerCommonCfgFromCmd() } if err != nil { return } - err = g.GlbServerCfg.ServerCommonConf.Check() + err = cfg.Check() if err != nil { return } - - config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf) 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 { - 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 { // e.g. 1000-2000,2001,2002,3000-4000 ports, errRet := util.ParseRangeNumbers(allowPorts) @@ -181,28 +182,27 @@ func parseServerCommonCfgFromCmd() (err error) { } 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" { - g.GlbServerCfg.LogWay = "console" + cfg.LogWay = "console" } else { - g.GlbServerCfg.LogWay = "file" + cfg.LogWay = "file" } + cfg.DisableLogColor = disableLogColor 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 { return err } log.Info("Start frps success") - server.ServerService = svr svr.Run() return } diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini index 71513eea..158ff23f 100644 --- a/conf/frpc_full.ini +++ b/conf/frpc_full.ini @@ -18,6 +18,9 @@ log_level = info log_max_days = 3 +# disable log colors when log_file is console, default is false +disable_log_color = false + # for authentication token = 12345678 @@ -26,6 +29,8 @@ admin_addr = 127.0.0.1 admin_port = 7400 admin_user = 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 pool_count = 5 @@ -198,6 +203,7 @@ plugin_local_addr = 127.0.0.1:80 plugin_crt_path = ./server.crt plugin_key_path = ./server.key plugin_host_header_rewrite = 127.0.0.1 +plugin_header_X-From-Where = frp [secret_tcp] # If the type is secret tcp, remote_port is useless diff --git a/conf/frps_full.ini b/conf/frps_full.ini index a8c0e635..ed507cef 100644 --- a/conf/frps_full.ini +++ b/conf/frps_full.ini @@ -43,6 +43,9 @@ log_level = info log_max_days = 3 +# disable log colors when log_file is console, default is false +disable_log_color = false + # auth token token = 12345678 diff --git a/g/g.go b/g/g.go deleted file mode 100644 index 3c7385f1..00000000 --- a/g/g.go +++ /dev/null @@ -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 -} diff --git a/go.sum b/go.sum index 3eeac454..5ddda4ef 100644 --- a/go.sum +++ b/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/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/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw= 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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= diff --git a/models/config/client_common.go b/models/config/client_common.go index 1cd9ffc5..fe87e08d 100644 --- a/models/config/client_common.go +++ b/models/config/client_common.go @@ -23,34 +23,103 @@ import ( 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 { - 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", ServerPort: 7000, HttpProxy: os.Getenv("http_proxy"), @@ -58,11 +127,13 @@ func GetDefaultClientConf() *ClientCommonConf { LogWay: "console", LogLevel: "info", LogMaxDays: 3, + DisableLogColor: false, Token: "", AdminAddr: "127.0.0.1", AdminPort: 0, AdminUser: "", AdminPwd: "", + AssetsDir: "", PoolCount: 1, TcpMux: true, 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)) 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 ( @@ -106,6 +173,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c 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 { cfg.HttpProxy = tmpStr } @@ -154,6 +225,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c 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 v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil { cfg.PoolCount = int(v) diff --git a/models/config/proxy.go b/models/config/proxy.go index a27416f9..9ab1ef88 100644 --- a/models/config/proxy.go +++ b/models/config/proxy.go @@ -58,11 +58,11 @@ type ProxyConf interface { UnmarshalFromIni(prefix string, name string, conf ini.Section) error MarshalToMsg(pMsg *msg.NewProxy) CheckForCli() error - CheckForSvr() error + CheckForSvr(serverCfg ServerCommonConf) error 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 == "" { pMsg.ProxyType = consts.TcpProxy } @@ -73,7 +73,7 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) { return } cfg.UnmarshalFromMsg(pMsg) - err = cfg.CheckForSvr() + err = cfg.CheckForSvr(serverCfg) return } @@ -97,17 +97,33 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P return } -// BaseProxy info +// BaseProxyConf provides configuration info that is common to all proxy types. type BaseProxyConf struct { + // ProxyName is the name of this proxy. 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"` - UseEncryption bool `json:"use_encryption"` - UseCompression bool `json:"use_compression"` - Group string `json:"group"` - GroupKey string `json:"group_key"` + // 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"` - // only used for client + // 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"` LocalSvrConf HealthCheckConf @@ -308,21 +324,21 @@ func (cfg *DomainConf) checkForCli() (err error) { return } -func (cfg *DomainConf) checkForSvr() (err error) { +func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) { if err = cfg.check(); err != nil { return } 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 subDomainHost == "" { + if serverCfg.SubDomainHost == "" { 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, "*") { @@ -332,12 +348,20 @@ func (cfg *DomainConf) checkForSvr() (err error) { return } -// Local service info +// LocalSvrConf configures what location the client will proxy to, or what +// plugin will be used. type LocalSvrConf struct { - LocalIp string `json:"local_ip"` - LocalPort int `json:"local_port"` + // 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 string `json:"plugin"` + // 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"` } @@ -399,15 +423,35 @@ func (cfg *LocalSvrConf) checkForCli() (err error) { 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 { - 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:"-"` } @@ -504,7 +548,7 @@ func (cfg *TcpProxyConf) CheckForCli() (err error) { return } -func (cfg *TcpProxyConf) CheckForSvr() error { return nil } +func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil } // UDP type UdpProxyConf struct { @@ -552,7 +596,7 @@ func (cfg *UdpProxyConf) CheckForCli() (err error) { return } -func (cfg *UdpProxyConf) CheckForSvr() error { return nil } +func (cfg *UdpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil } // HTTP type HttpProxyConf struct { @@ -657,11 +701,11 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) { 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") } - 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) return } @@ -717,11 +761,11 @@ func (cfg *HttpsProxyConf) CheckForCli() (err error) { 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") } - 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) return } @@ -790,7 +834,7 @@ func (cfg *StcpProxyConf) CheckForCli() (err error) { return } -func (cfg *StcpProxyConf) CheckForSvr() (err error) { +func (cfg *StcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { return } @@ -857,7 +901,7 @@ func (cfg *XtcpProxyConf) CheckForCli() (err error) { return } -func (cfg *XtcpProxyConf) CheckForSvr() (err error) { +func (cfg *XtcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { return } diff --git a/models/config/server_common.go b/models/config/server_common.go index 1e54bdd8..a190a61a 100644 --- a/models/config/server_common.go +++ b/models/config/server_common.go @@ -24,62 +24,122 @@ import ( "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 { - 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"` - // 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"` - // 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"` + // 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"` + // DashboardAddr specifies the address that the dashboard binds to. By + // default, this value is "0.0.0.0". 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"` - 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"` - 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"` - 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"` - 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", BindPort: 7000, BindUdpPort: 0, @@ -97,6 +157,7 @@ func GetDefaultServerConf() *ServerCommonConf { LogWay: "console", LogLevel: "info", LogMaxDays: 3, + DisableLogColor: false, Token: "", SubDomainHost: "", 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)) if err != nil { err = fmt.Errorf("parse ini conf file error: %v", err) - return nil, err + return ServerCommonConf{}, err } 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") if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok { diff --git a/models/plugin/https2http.go b/models/plugin/https2http.go index 6e84ad62..f840ebde 100644 --- a/models/plugin/https2http.go +++ b/models/plugin/https2http.go @@ -20,6 +20,7 @@ import ( "io" "net/http" "net/http/httputil" + "strings" frpNet "github.com/fatedier/frp/utils/net" ) @@ -35,6 +36,7 @@ type HTTPS2HTTPPlugin struct { keyPath string hostHeaderRewrite string localAddr string + headers map[string]string l *Listener s *http.Server @@ -45,6 +47,15 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) { keyPath := params["plugin_key_path"] localAddr := params["plugin_local_addr"] 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 == "" { return nil, fmt.Errorf("plugin_crt_path is required") @@ -63,6 +74,7 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) { keyPath: keyPath, localAddr: localAddr, hostHeaderRewrite: hostHeaderRewrite, + headers: headers, l: listener, } @@ -73,6 +85,9 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) { if p.hostHeaderRewrite != "" { req.Host = p.hostHeaderRewrite } + for k, v := range p.headers { + req.Header.Set(k, v) + } }, } diff --git a/server/control.go b/server/control.go index 8374ab20..bb802a75 100644 --- a/server/control.go +++ b/server/control.go @@ -21,7 +21,6 @@ import ( "sync" "time" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/consts" frpErr "github.com/fatedier/frp/models/errors" @@ -129,11 +128,19 @@ type Control struct { allShutdown *shutdown.Shutdown mu sync.RWMutex + + // Server configuration information + serverCfg config.ServerCommonConf } 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{ rc: rc, pxyManager: pxyManager, @@ -142,9 +149,9 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage loginMsg: loginMsg, sendCh: 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), - poolCount: loginMsg.PoolCount, + poolCount: poolCount, portsUsedNum: 0, lastPing: time.Now(), runId: loginMsg.RunId, @@ -153,6 +160,7 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage writerShutdown: shutdown.New(), managerShutdown: shutdown.New(), allShutdown: shutdown.New(), + serverCfg: serverCfg, } } @@ -161,7 +169,7 @@ func (ctl *Control) Start() { loginRespMsg := &msg.LoginResp{ Version: version.Full(), RunId: ctl.runId, - ServerUdpPort: g.GlbServerCfg.BindUdpPort, + ServerUdpPort: ctl.serverCfg.BindUdpPort, Error: "", } msg.WriteMsg(ctl.conn, loginRespMsg) @@ -232,7 +240,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { 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") ctl.conn.Warn("%v", err) return @@ -263,7 +271,7 @@ func (ctl *Control) writer() { defer ctl.allShutdown.Start() 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 { ctl.conn.Error("crypto new writer error: %v", err) ctl.allShutdown.Start() @@ -293,7 +301,7 @@ func (ctl *Control) reader() { defer ctl.allShutdown.Start() defer ctl.readerShutdown.Done() - encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token)) + encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token)) for { if m, err := msg.ReadMsg(encReader); err != nil { if err == io.EOF { @@ -374,7 +382,7 @@ func (ctl *Control) manager() { for { select { 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") return } @@ -417,22 +425,22 @@ func (ctl *Control) manager() { func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) { var pxyConf config.ProxyConf // Load configures from NewProxy message and check. - pxyConf, err = config.NewProxyConfFromMsg(pxyMsg) + pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg) if err != nil { return } // NewProxy will return a interface Proxy. // 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 { return remoteAddr, err } // Check ports used number in each client - if g.GlbServerCfg.MaxPortsPerClient > 0 { + if ctl.serverCfg.MaxPortsPerClient > 0 { ctl.mu.Lock() - if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) { + if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.serverCfg.MaxPortsPerClient) { ctl.mu.Unlock() err = fmt.Errorf("exceed the max_ports_per_client") return @@ -478,7 +486,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) { return } - if g.GlbServerCfg.MaxPortsPerClient > 0 { + if ctl.serverCfg.MaxPortsPerClient > 0 { ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum() } pxy.Close() diff --git a/server/dashboard.go b/server/dashboard.go index d682aeb9..0a65dbb2 100644 --- a/server/dashboard.go +++ b/server/dashboard.go @@ -21,7 +21,6 @@ import ( "time" "github.com/fatedier/frp/assets" - "github.com/fatedier/frp/g" frpNet "github.com/fatedier/frp/utils/net" "github.com/gorilla/mux" @@ -36,7 +35,7 @@ func (svr *Service) RunDashboardServer(addr string, port int) (err error) { // url router 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) // api, see dashboard_api.go diff --git a/server/dashboard_api.go b/server/dashboard_api.go index dcea7e61..30ee9a68 100644 --- a/server/dashboard_api.go +++ b/server/dashboard_api.go @@ -18,7 +18,6 @@ import ( "encoding/json" "net/http" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/consts" "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) - cfg := &g.GlbServerCfg.ServerCommonConf serverStats := svr.statsCollector.GetServer() svrResp := ServerInfoResp{ 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, TotalTrafficOut: serverStats.TotalTrafficOut, diff --git a/server/proxy/http.go b/server/proxy/http.go index 1fa8765e..5bd3139d 100644 --- a/server/proxy/http.go +++ b/server/proxy/http.go @@ -19,7 +19,6 @@ import ( "net" "strings" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/server/stats" 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) }) } - 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) } } if pxy.cfg.SubDomain != "" { - routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost + routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost for _, location := range locations { routeConfig.Location = location tmpDomain := routeConfig.Domain @@ -119,7 +118,7 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) { 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) } @@ -147,7 +146,7 @@ func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err var rwc io.ReadWriteCloser = tmpConn 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 { pxy.Error("create encryption stream error: %v", err) return diff --git a/server/proxy/https.go b/server/proxy/https.go index cb5ce928..87840421 100644 --- a/server/proxy/https.go +++ b/server/proxy/https.go @@ -17,7 +17,6 @@ package proxy import ( "strings" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/vhost" @@ -51,11 +50,11 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) { l.AddLogPrefix(pxy.name) pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) 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 != "" { - routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost + routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig) if errRet != nil { err = errRet @@ -64,7 +63,7 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) { l.AddLogPrefix(pxy.name) pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) 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) diff --git a/server/proxy/proxy.go b/server/proxy/proxy.go index 1a9a28e1..627b0e15 100644 --- a/server/proxy/proxy.go +++ b/server/proxy/proxy.go @@ -21,7 +21,6 @@ import ( "strconv" "sync" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" "github.com/fatedier/frp/server/controller" @@ -52,6 +51,7 @@ type BaseProxy struct { usedPortsNum int poolCount int getWorkConnFn GetWorkConnFn + serverCfg config.ServerCommonConf mu sync.RWMutex 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. // 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. -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 { go func(l frpNet.Listener) { for { @@ -138,14 +138,14 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Con return } 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) } } 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{ name: pxyConf.GetBaseInfo().ProxyName, @@ -155,6 +155,7 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st poolCount: poolCount, getWorkConnFn: getWorkConnFn, Logger: log.NewPrefixLogger(runId), + serverCfg: serverCfg, } switch cfg := pxyConf.(type) { case *config.TcpProxyConf: @@ -198,7 +199,7 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st // HandleUserTcpConnection is used for incoming tcp user connections. // 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() // try all connections from the pool @@ -211,7 +212,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta var local io.ReadWriteCloser = workConn cfg := pxy.GetConf().GetBaseInfo() if cfg.UseEncryption { - local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token)) + local, err = frpIo.WithEncryption(local, []byte(serverCfg.Token)) if err != nil { pxy.Error("create encryption stream error: %v", err) return diff --git a/server/proxy/tcp.go b/server/proxy/tcp.go index e00e3a0e..388531a6 100644 --- a/server/proxy/tcp.go +++ b/server/proxy/tcp.go @@ -17,7 +17,6 @@ package proxy import ( "fmt" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" frpNet "github.com/fatedier/frp/utils/net" ) @@ -31,7 +30,7 @@ type TcpProxy struct { func (pxy *TcpProxy) Run() (remoteAddr string, err error) { 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 { err = errRet return @@ -56,7 +55,7 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) { 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 { err = errRet return diff --git a/server/proxy/udp.go b/server/proxy/udp.go index b5dd5fbb..453c7b56 100644 --- a/server/proxy/udp.go +++ b/server/proxy/udp.go @@ -20,7 +20,6 @@ import ( "net" "time" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" "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) 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 { err = errRet return diff --git a/server/service.go b/server/service.go index e3a1d117..6d561016 100644 --- a/server/service.go +++ b/server/service.go @@ -29,7 +29,7 @@ import ( "time" "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/nathole" "github.com/fatedier/frp/server/controller" @@ -51,8 +51,6 @@ const ( connReadTimeout time.Duration = 10 * time.Second ) -var ServerService *Service - // Server service type Service struct { // Dispatch connections to different handlers listen on same port @@ -86,10 +84,11 @@ type Service struct { statsCollector stats.Collector 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{ ctlManager: NewControlManager(), pxyManager: proxy.NewProxyManager(), @@ -100,6 +99,7 @@ func NewService() (svr *Service, err error) { }, httpVhostRouter: vhost.NewVhostRouters(), tlsConfig: generateTLSConfig(), + cfg: cfg, } // Init group controller @@ -108,13 +108,6 @@ func NewService() (svr *Service, err error) { // Init HTTP group controller 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 vhost.NotFoundPagePath = cfg.Custom404Page @@ -231,6 +224,13 @@ func NewService() (svr *Service, err error) { var statsEnable bool // Create dashboard web server. 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) if err != nil { err = fmt.Errorf("Create dashboard web server error, %v", err) @@ -248,7 +248,7 @@ func (svr *Service) Run() { if svr.rc.NatHoleController != nil { go svr.rc.NatHoleController.Run() } - if g.GlbServerCfg.KcpBindPort > 0 { + if svr.cfg.KcpBindPort > 0 { 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.KeepAliveInterval = 20 * time.Second fmuxCfg.LogOutput = ioutil.Discard @@ -363,7 +363,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e } // 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") 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 { oldCtl.allShutdown.WaitDone() diff --git a/tests/ci/health/health_test.go b/tests/ci/health/health_test.go index 99ee22bd..f19c3750 100644 --- a/tests/ci/health/health_test.go +++ b/tests/ci/health/health_test.go @@ -67,6 +67,20 @@ custom_domains = test2.com health_check_type = http health_check_interval_s = 1 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) { @@ -124,6 +138,22 @@ func TestHealthCheck(t *testing.T) { 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) // ****** start frps and frpc ****** @@ -244,4 +274,20 @@ func TestHealthCheck(t *testing.T) { assert.NoError(err) assert.Equal(200, code) 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") } diff --git a/utils/log/log.go b/utils/log/log.go index c0ce7f12..1a9033bf 100644 --- a/utils/log/log.go +++ b/utils/log/log.go @@ -29,16 +29,20 @@ func init() { 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) } // SetLogFile to configure log params // 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" { - Log.SetLogger("console", "") + params := "" + if disableLogColor { + params = fmt.Sprintf(`{"color": false}`) + } + Log.SetLogger("console", params) } else { params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays) Log.SetLogger("file", params) diff --git a/utils/version/version.go b/utils/version/version.go index a4cb6c09..7a73de8f 100644 --- a/utils/version/version.go +++ b/utils/version/version.go @@ -19,7 +19,7 @@ import ( "strings" ) -var version string = "0.28.2" +var version string = "0.29.0" func Full() string { return version