From c33b5152e722a50d65cd6925ba498f3016c7c19c Mon Sep 17 00:00:00 2001 From: fatedier Date: Mon, 25 Jun 2018 18:22:35 +0800 Subject: [PATCH] split visitors from proxies and add health check config --- client/admin_api.go | 2 +- client/control.go | 17 ++- client/health.go | 32 +++++ client/proxy_manager.go | 87 +++--------- client/service.go | 2 +- client/visitor.go | 14 +- client/visitor_manager.go | 111 +++++++++++++++ cmd/frpc/sub/root.go | 4 +- cmd/frpc/sub/stcp.go | 75 +++++----- cmd/frpc/sub/xtcp.go | 75 +++++----- conf/frpc_full.ini | 9 ++ models/config/proxy.go | 287 ++++++++++++++++++++------------------ models/config/visitor.go | 213 ++++++++++++++++++++++++++++ 13 files changed, 641 insertions(+), 287 deletions(-) create mode 100644 client/health.go create mode 100644 client/visitor_manager.go create mode 100644 models/config/visitor.go diff --git a/client/admin_api.go b/client/admin_api.go index 854e9708..4eafa103 100644 --- a/client/admin_api.go +++ b/client/admin_api.go @@ -77,7 +77,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { return } - pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromIni(g.GlbClientCfg.User, conf, newCommonCfg.Start) + pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, conf, newCommonCfg.Start) if err != nil { res.Code = 3 res.Msg = err.Error() diff --git a/client/control.go b/client/control.go index 53669ae5..04be13ce 100644 --- a/client/control.go +++ b/client/control.go @@ -47,8 +47,12 @@ type Control struct { // login message to server, only used loginMsg *msg.Login + // manage all proxies pm *ProxyManager + // manage all visitors + vm *VisitorManager + // control connection conn frpNet.Conn @@ -82,7 +86,7 @@ type Control struct { log.Logger } -func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) *Control { +func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control { loginMsg := &msg.Login{ Arch: runtime.GOARCH, Os: runtime.GOOS, @@ -102,7 +106,9 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs m Logger: log.NewPrefixLogger(""), } ctl.pm = NewProxyManager(ctl, ctl.sendCh, "") - ctl.pm.Reload(pxyCfgs, visitorCfgs, false) + ctl.pm.Reload(pxyCfgs, false) + ctl.vm = NewVisitorManager(ctl) + ctl.vm.Reload(visitorCfgs) return ctl } @@ -129,6 +135,8 @@ func (ctl *Control) Run() (err error) { // start all local visitors and send NewProxy message for all configured proxies ctl.pm.Reset(ctl.sendCh, ctl.runId) ctl.pm.CheckAndStartProxy([]string{ProxyStatusNew}) + + go ctl.vm.Run() return nil } @@ -444,7 +452,8 @@ func (ctl *Control) worker() { } } -func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) error { - err := ctl.pm.Reload(pxyCfgs, visitorCfgs, true) +func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error { + ctl.vm.Reload(visitorCfgs) + err := ctl.pm.Reload(pxyCfgs, true) return err } diff --git a/client/health.go b/client/health.go new file mode 100644 index 00000000..ad58554d --- /dev/null +++ b/client/health.go @@ -0,0 +1,32 @@ +// Copyright 2018 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "github.com/fatedier/frp/models/config" +) + +type HealthCheckMonitor struct { + cfg config.HealthCheckConf +} + +func NewHealthCheckMonitor(cfg *config.HealthCheckConf) *HealthCheckMonitor { + return &HealthCheckMonitor{ + cfg: *cfg, + } +} + +func (monitor *HealthCheckMonitor) Start() { +} diff --git a/client/proxy_manager.go b/client/proxy_manager.go index 67029724..cfa56fc5 100644 --- a/client/proxy_manager.go +++ b/client/proxy_manager.go @@ -13,25 +13,21 @@ import ( ) const ( - ProxyStatusNew = "new" - ProxyStatusStartErr = "start error" - ProxyStatusWaitStart = "wait start" - ProxyStatusRunning = "running" - ProxyStatusClosed = "closed" + ProxyStatusNew = "new" + ProxyStatusStartErr = "start error" + ProxyStatusWaitStart = "wait start" + ProxyStatusRunning = "running" + ProxyStatusCheckFailed = "check failed" + ProxyStatusCheckSuccess = "check success" + ProxyStatusClosed = "closed" ) type ProxyManager struct { - ctl *Control - + ctl *Control + sendCh chan (msg.Message) proxies map[string]*ProxyWrapper - - visitorCfgs map[string]config.ProxyConf - visitors map[string]Visitor - - sendCh chan (msg.Message) - - closed bool - mu sync.RWMutex + closed bool + mu sync.RWMutex log.Logger } @@ -151,13 +147,11 @@ func (pw *ProxyWrapper) Close() { func NewProxyManager(ctl *Control, msgSendCh chan (msg.Message), logPrefix string) *ProxyManager { return &ProxyManager{ - ctl: ctl, - proxies: make(map[string]*ProxyWrapper), - visitorCfgs: make(map[string]config.ProxyConf), - visitors: make(map[string]Visitor), - sendCh: msgSendCh, - closed: false, - Logger: log.NewPrefixLogger(logPrefix), + ctl: ctl, + proxies: make(map[string]*ProxyWrapper), + sendCh: msgSendCh, + closed: false, + Logger: log.NewPrefixLogger(logPrefix), } } @@ -239,24 +233,9 @@ func (pm *ProxyManager) CheckAndStartProxy(pxyStatus []string) { } } } - - for _, cfg := range pm.visitorCfgs { - name := cfg.GetBaseInfo().ProxyName - if _, exist := pm.visitors[name]; !exist { - pm.Info("try to start visitor [%s]", name) - visitor := NewVisitor(pm.ctl, cfg) - err := visitor.Run() - if err != nil { - visitor.Warn("start error: %v", err) - continue - } - pm.visitors[name] = visitor - visitor.Info("start visitor success") - } - } } -func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf, startNow bool) error { +func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, startNow bool) error { pm.mu.Lock() defer func() { pm.mu.Unlock() @@ -308,38 +287,6 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf, visitorCfgs } } pm.Info("proxy added: %v", addPxyNames) - - delVisitorName := make([]string, 0) - for name, oldVisitorCfg := range pm.visitorCfgs { - del := false - cfg, ok := visitorCfgs[name] - if !ok { - del = true - } else { - if !oldVisitorCfg.Compare(cfg) { - del = true - } - } - - if del { - delVisitorName = append(delVisitorName, name) - delete(pm.visitorCfgs, name) - if visitor, ok := pm.visitors[name]; ok { - visitor.Close() - } - delete(pm.visitors, name) - } - } - pm.Info("visitor removed: %v", delVisitorName) - - addVisitorName := make([]string, 0) - for name, visitorCfg := range visitorCfgs { - if _, ok := pm.visitorCfgs[name]; !ok { - pm.visitorCfgs[name] = visitorCfg - addVisitorName = append(addVisitorName, name) - } - } - pm.Info("visitor added: %v", addVisitorName) return nil } diff --git a/client/service.go b/client/service.go index 5fbf33c7..2589f520 100644 --- a/client/service.go +++ b/client/service.go @@ -27,7 +27,7 @@ type Service struct { closedCh chan int } -func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (svr *Service) { +func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service) { svr = &Service{ closedCh: make(chan int), } diff --git a/client/visitor.go b/client/visitor.go index 44e384d6..6e1e1c8d 100644 --- a/client/visitor.go +++ b/client/visitor.go @@ -44,18 +44,18 @@ type Visitor interface { log.Logger } -func NewVisitor(ctl *Control, pxyConf config.ProxyConf) (visitor Visitor) { +func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) { baseVisitor := BaseVisitor{ ctl: ctl, - Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName), + Logger: log.NewPrefixLogger(cfg.GetBaseInfo().ProxyName), } - switch cfg := pxyConf.(type) { - case *config.StcpProxyConf: + switch cfg := cfg.(type) { + case *config.StcpVisitorConf: visitor = &StcpVisitor{ BaseVisitor: baseVisitor, cfg: cfg, } - case *config.XtcpProxyConf: + case *config.XtcpVisitorConf: visitor = &XtcpVisitor{ BaseVisitor: baseVisitor, cfg: cfg, @@ -75,7 +75,7 @@ type BaseVisitor struct { type StcpVisitor struct { BaseVisitor - cfg *config.StcpProxyConf + cfg *config.StcpVisitorConf } func (sv *StcpVisitor) Run() (err error) { @@ -162,7 +162,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) { type XtcpVisitor struct { BaseVisitor - cfg *config.XtcpProxyConf + cfg *config.XtcpVisitorConf } func (sv *XtcpVisitor) Run() (err error) { diff --git a/client/visitor_manager.go b/client/visitor_manager.go new file mode 100644 index 00000000..3e0aa80b --- /dev/null +++ b/client/visitor_manager.go @@ -0,0 +1,111 @@ +// Copyright 2018 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "sync" + "time" + + "github.com/fatedier/frp/models/config" + "github.com/fatedier/frp/utils/log" +) + +type VisitorManager struct { + ctl *Control + + cfgs map[string]config.VisitorConf + visitors map[string]Visitor + + checkInterval time.Duration + + mu sync.Mutex +} + +func NewVisitorManager(ctl *Control) *VisitorManager { + return &VisitorManager{ + ctl: ctl, + cfgs: make(map[string]config.VisitorConf), + visitors: make(map[string]Visitor), + checkInterval: 10 * time.Second, + } +} + +func (vm *VisitorManager) Run() { + for { + time.Sleep(vm.checkInterval) + vm.mu.Lock() + for _, cfg := range vm.cfgs { + name := cfg.GetBaseInfo().ProxyName + if _, exist := vm.visitors[name]; !exist { + log.Info("try to start visitor [%s]", name) + vm.startVisitor(cfg) + } + } + vm.mu.Unlock() + } +} + +// Hold lock before calling this function. +func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) { + name := cfg.GetBaseInfo().ProxyName + visitor := NewVisitor(vm.ctl, cfg) + err = visitor.Run() + if err != nil { + visitor.Warn("start error: %v", err) + } else { + vm.visitors[name] = visitor + visitor.Info("start visitor success") + } + return +} + +func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) { + vm.mu.Lock() + defer vm.mu.Unlock() + + delNames := make([]string, 0) + for name, oldCfg := range vm.cfgs { + del := false + cfg, ok := cfgs[name] + if !ok { + del = true + } else { + if !oldCfg.Compare(cfg) { + del = true + } + } + + if del { + delNames = append(delNames, name) + delete(vm.cfgs, name) + if visitor, ok := vm.visitors[name]; ok { + visitor.Close() + } + delete(vm.visitors, name) + } + } + log.Info("visitor removed: %v", delNames) + + addNames := make([]string, 0) + for name, cfg := range cfgs { + if _, ok := vm.cfgs[name]; !ok { + vm.cfgs[name] = cfg + addNames = append(addNames, name) + vm.startVisitor(cfg) + } + } + log.Info("visitor added: %v", addNames) + return +} diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go index 9b9a2262..23b3bf9c 100644 --- a/cmd/frpc/sub/root.go +++ b/cmd/frpc/sub/root.go @@ -180,7 +180,7 @@ func runClient(cfgFilePath string) (err error) { return err } - pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromIni(g.GlbClientCfg.User, conf, g.GlbClientCfg.Start) + pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, conf, g.GlbClientCfg.Start) if err != nil { return err } @@ -189,7 +189,7 @@ func runClient(cfgFilePath string) (err error) { return } -func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (err error) { +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 diff --git a/cmd/frpc/sub/stcp.go b/cmd/frpc/sub/stcp.go index 4915e520..0920927b 100644 --- a/cmd/frpc/sub/stcp.go +++ b/cmd/frpc/sub/stcp.go @@ -57,48 +57,57 @@ var stcpCmd = &cobra.Command{ os.Exit(1) } - cfg := &config.StcpProxyConf{} + proxyConfs := make(map[string]config.ProxyConf) + visitorConfs := make(map[string]config.VisitorConf) + var prefix string if user != "" { prefix = user + "." } - cfg.ProxyName = prefix + proxyName - cfg.ProxyType = consts.StcpProxy - cfg.Role = role - cfg.Sk = sk - cfg.ServerName = serverName - cfg.LocalIp = localIp - cfg.LocalPort = localPort - cfg.BindAddr = bindAddr - cfg.BindPort = bindPort - cfg.UseEncryption = useEncryption - cfg.UseCompression = useCompression - err = cfg.CheckForCli() + if role == "server" { + cfg := &config.StcpProxyConf{} + cfg.ProxyName = prefix + proxyName + cfg.ProxyType = consts.StcpProxy + cfg.UseEncryption = useEncryption + cfg.UseCompression = useCompression + cfg.Role = role + cfg.Sk = sk + cfg.LocalIp = localIp + cfg.LocalPort = localPort + err = cfg.CheckForCli() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + proxyConfs[cfg.ProxyName] = cfg + } else if role == "visitor" { + cfg := &config.StcpVisitorConf{} + cfg.ProxyName = prefix + proxyName + cfg.ProxyType = consts.StcpProxy + cfg.UseEncryption = useEncryption + cfg.UseCompression = useCompression + cfg.Role = role + cfg.Sk = sk + cfg.ServerName = serverName + cfg.BindAddr = bindAddr + cfg.BindPort = bindPort + err = cfg.Check() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + visitorConfs[cfg.ProxyName] = cfg + } else { + fmt.Println("invalid role") + os.Exit(1) + } + + err = startService(proxyConfs, visitorConfs) if err != nil { fmt.Println(err) os.Exit(1) } - - if cfg.Role == "server" { - proxyConfs := map[string]config.ProxyConf{ - cfg.ProxyName: cfg, - } - err = startService(proxyConfs, nil) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - } else { - visitorConfs := map[string]config.ProxyConf{ - cfg.ProxyName: cfg, - } - err = startService(nil, visitorConfs) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - } return nil }, } diff --git a/cmd/frpc/sub/xtcp.go b/cmd/frpc/sub/xtcp.go index 8c18a859..b6ae541d 100644 --- a/cmd/frpc/sub/xtcp.go +++ b/cmd/frpc/sub/xtcp.go @@ -57,48 +57,57 @@ var xtcpCmd = &cobra.Command{ os.Exit(1) } - cfg := &config.XtcpProxyConf{} + proxyConfs := make(map[string]config.ProxyConf) + visitorConfs := make(map[string]config.VisitorConf) + var prefix string if user != "" { prefix = user + "." } - cfg.ProxyName = prefix + proxyName - cfg.ProxyType = consts.XtcpProxy - cfg.Role = role - cfg.Sk = sk - cfg.ServerName = serverName - cfg.LocalIp = localIp - cfg.LocalPort = localPort - cfg.BindAddr = bindAddr - cfg.BindPort = bindPort - cfg.UseEncryption = useEncryption - cfg.UseCompression = useCompression - err = cfg.CheckForCli() + if role == "server" { + cfg := &config.XtcpProxyConf{} + cfg.ProxyName = prefix + proxyName + cfg.ProxyType = consts.StcpProxy + cfg.UseEncryption = useEncryption + cfg.UseCompression = useCompression + cfg.Role = role + cfg.Sk = sk + cfg.LocalIp = localIp + cfg.LocalPort = localPort + err = cfg.CheckForCli() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + proxyConfs[cfg.ProxyName] = cfg + } else if role == "visitor" { + cfg := &config.XtcpVisitorConf{} + cfg.ProxyName = prefix + proxyName + cfg.ProxyType = consts.StcpProxy + cfg.UseEncryption = useEncryption + cfg.UseCompression = useCompression + cfg.Role = role + cfg.Sk = sk + cfg.ServerName = serverName + cfg.BindAddr = bindAddr + cfg.BindPort = bindPort + err = cfg.Check() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + visitorConfs[cfg.ProxyName] = cfg + } else { + fmt.Println("invalid role") + os.Exit(1) + } + + err = startService(proxyConfs, visitorConfs) if err != nil { fmt.Println(err) os.Exit(1) } - - if cfg.Role == "server" { - proxyConfs := map[string]config.ProxyConf{ - cfg.ProxyName: cfg, - } - err = startService(proxyConfs, nil) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - } else { - visitorConfs := map[string]config.ProxyConf{ - cfg.ProxyName: cfg, - } - err = startService(nil, visitorConfs) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - } return nil }, } diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini index c3d13ffd..2307eeb3 100644 --- a/conf/frpc_full.ini +++ b/conf/frpc_full.ini @@ -73,6 +73,10 @@ remote_port = 6001 group = test_group # group should have same group key group_key = 123456 +# enable health check for the backend service, it support 'tcp' and 'http' now +# frpc will connect local service's port to detect it's healthy status +health_check_type = tcp +health_check_interval_s = 10 [ssh_random] type = tcp @@ -126,6 +130,11 @@ locations = /,/pic host_header_rewrite = example.com # params with prefix "header_" will be used to update http request headers header_X-From-Where = frp +health_check_type = http +# frpc will send a GET http request '/status' to local http service +# http service is alive when it return 2xx http response code +health_check_url = /status +health_check_interval_s = 10 [web02] type = https diff --git a/models/config/proxy.go b/models/config/proxy.go index 0270c1c0..b600be5c 100644 --- a/models/config/proxy.go +++ b/models/config/proxy.go @@ -91,7 +91,9 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil { return } - err = cfg.CheckForCli() + if err = cfg.CheckForCli(); err != nil { + return + } return } @@ -104,6 +106,9 @@ type BaseProxyConf struct { UseCompression bool `json:"use_compression"` Group string `json:"group"` GroupKey string `json:"group_key"` + + LocalSvrConf + HealthCheckConf // only used for client } func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf { @@ -119,6 +124,12 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool { cfg.GroupKey != cmp.GroupKey { return false } + if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) { + return false + } + if !cfg.HealthCheckConf.compare(&cmp.HealthCheckConf) { + return false + } return true } @@ -151,6 +162,14 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i cfg.Group = section["group"] cfg.GroupKey = section["group_key"] + + if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { + return err + } + + if err := cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil { + return err + } return nil } @@ -163,6 +182,16 @@ func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { pMsg.GroupKey = cfg.GroupKey } +func (cfg *BaseProxyConf) checkForCli() (err error) { + if err = cfg.LocalSvrConf.checkForCli(); err != nil { + return + } + if err = cfg.HealthCheckConf.checkForCli(); err != nil { + return + } + return nil +} + // Bind info type BindInfoConf struct { RemotePort int `json:"remote_port"` @@ -335,12 +364,70 @@ func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section in return } +func (cfg *LocalSvrConf) checkForCli() (err error) { + if cfg.Plugin == "" { + if cfg.LocalIp == "" { + err = fmt.Errorf("local ip or plugin is required") + return + } + if cfg.LocalPort <= 0 { + err = fmt.Errorf("error local_port") + return + } + } + return +} + +// Health check info +type HealthCheckConf struct { + HealthCheckType string `json:"health_check_type"` // tcp | http + HealthCheckIntervalS int `json:"health_check_interval_s"` + HealthCheckUrl string `json:"health_check_url"` + + // local_ip + local_port + HealthCheckAddr string `json:"-"` +} + +func (cfg *HealthCheckConf) compare(cmp *HealthCheckConf) bool { + if cfg.HealthCheckType != cmp.HealthCheckType || + cfg.HealthCheckUrl != cmp.HealthCheckUrl || + cfg.HealthCheckIntervalS != cmp.HealthCheckIntervalS { + return false + } + return true +} + +func (cfg *HealthCheckConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { + cfg.HealthCheckType = section["health_check_type"] + cfg.HealthCheckUrl = section["health_check_url"] + + if tmpStr, ok := section["health_check_interval_s"]; ok { + if cfg.HealthCheckIntervalS, err = strconv.Atoi(tmpStr); err != nil { + return fmt.Errorf("Parse conf error: proxy [%s] health_check_interval_s error", name) + } + } + return +} + +func (cfg *HealthCheckConf) checkForCli() error { + if cfg.HealthCheckType != "" && cfg.HealthCheckType != "tcp" && cfg.HealthCheckType != "http" { + return fmt.Errorf("unsupport health check type") + } + if cfg.HealthCheckType != "" { + if cfg.HealthCheckType == "http" && cfg.HealthCheckUrl == "" { + return fmt.Errorf("health_check_url is required for health check type 'http'") + } + if cfg.HealthCheckIntervalS <= 0 { + return fmt.Errorf("health_check_interval_s is required and should greater than 0") + } + } + return nil +} + // TCP type TcpProxyConf struct { BaseProxyConf BindInfoConf - - LocalSvrConf } func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool { @@ -350,8 +437,7 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool { } if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) || - !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) { + !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) { return false } return true @@ -369,9 +455,6 @@ func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section in if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil { return } - if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } return } @@ -380,7 +463,12 @@ func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { cfg.BindInfoConf.MarshalToMsg(pMsg) } -func (cfg *TcpProxyConf) CheckForCli() error { return nil } +func (cfg *TcpProxyConf) CheckForCli() (err error) { + if err = cfg.BaseProxyConf.checkForCli(); err != nil { + return err + } + return +} func (cfg *TcpProxyConf) CheckForSvr() error { return nil } @@ -388,8 +476,6 @@ func (cfg *TcpProxyConf) CheckForSvr() error { return nil } type UdpProxyConf struct { BaseProxyConf BindInfoConf - - LocalSvrConf } func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool { @@ -399,8 +485,7 @@ func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool { } if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) || - !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) { + !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) { return false } return true @@ -418,9 +503,6 @@ func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section in if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil { return } - if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } return } @@ -429,7 +511,12 @@ func (cfg *UdpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { cfg.BindInfoConf.MarshalToMsg(pMsg) } -func (cfg *UdpProxyConf) CheckForCli() error { return nil } +func (cfg *UdpProxyConf) CheckForCli() (err error) { + if err = cfg.BaseProxyConf.checkForCli(); err != nil { + return + } + return +} func (cfg *UdpProxyConf) CheckForSvr() error { return nil } @@ -438,8 +525,6 @@ type HttpProxyConf struct { BaseProxyConf DomainConf - LocalSvrConf - Locations []string `json:"locations"` HttpUser string `json:"http_user"` HttpPwd string `json:"http_pwd"` @@ -455,7 +540,6 @@ func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool { if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || !cfg.DomainConf.compare(&cmpConf.DomainConf) || - !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") || cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite || cfg.HttpUser != cmpConf.HttpUser || @@ -494,9 +578,6 @@ func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section i if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { return } - if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } var ( tmpStr string @@ -533,6 +614,9 @@ func (cfg *HttpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { } func (cfg *HttpProxyConf) CheckForCli() (err error) { + if err = cfg.BaseProxyConf.checkForCli(); err != nil { + return + } if err = cfg.DomainConf.checkForCli(); err != nil { return } @@ -554,8 +638,6 @@ func (cfg *HttpProxyConf) CheckForSvr() (err error) { type HttpsProxyConf struct { BaseProxyConf DomainConf - - LocalSvrConf } func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool { @@ -565,8 +647,7 @@ func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool { } if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.DomainConf.compare(&cmpConf.DomainConf) || - !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) { + !cfg.DomainConf.compare(&cmpConf.DomainConf) { return false } return true @@ -584,9 +665,6 @@ func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { return } - if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } return } @@ -596,6 +674,9 @@ func (cfg *HttpsProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { } func (cfg *HttpsProxyConf) CheckForCli() (err error) { + if err = cfg.BaseProxyConf.checkForCli(); err != nil { + return + } if err = cfg.DomainConf.checkForCli(); err != nil { return } @@ -619,14 +700,6 @@ type StcpProxyConf struct { Role string `json:"role"` Sk string `json:"sk"` - - // used in role server - LocalSvrConf - - // used in role visitor - ServerName string `json:"server_name"` - BindAddr string `json:"bind_addr"` - BindPort int `json:"bind_port"` } func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool { @@ -636,12 +709,8 @@ func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool { } if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || cfg.Role != cmpConf.Role || - cfg.Sk != cmpConf.Sk || - cfg.ServerName != cmpConf.ServerName || - cfg.BindAddr != cmpConf.BindAddr || - cfg.BindPort != cmpConf.BindPort { + cfg.Sk != cmpConf.Sk { return false } return true @@ -658,35 +727,15 @@ func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section i return } - tmpStr := section["role"] - if tmpStr == "" { - tmpStr = "server" - } - if tmpStr == "server" || tmpStr == "visitor" { - cfg.Role = tmpStr - } else { - return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, tmpStr) + cfg.Role = section["role"] + if cfg.Role != "server" { + return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role) } cfg.Sk = section["sk"] - if tmpStr == "visitor" { - cfg.ServerName = prefix + section["server_name"] - if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" { - cfg.BindAddr = "127.0.0.1" - } - - if tmpStr, ok := section["bind_port"]; ok { - if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil { - return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name) - } - } else { - return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name) - } - } else { - if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } + if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { + return } return } @@ -697,19 +746,12 @@ func (cfg *StcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { } func (cfg *StcpProxyConf) CheckForCli() (err error) { - if cfg.Role != "server" && cfg.Role != "visitor" { - err = fmt.Errorf("role should be 'server' or 'visitor'") + if err = cfg.BaseProxyConf.checkForCli(); err != nil { return } - if cfg.Role == "visitor" { - if cfg.BindAddr == "" { - err = fmt.Errorf("bind_addr shouldn't be empty") - return - } - if cfg.BindPort == 0 { - err = fmt.Errorf("bind_port should be set") - return - } + if cfg.Role != "server" { + err = fmt.Errorf("role should be 'server'") + return } return } @@ -724,14 +766,6 @@ type XtcpProxyConf struct { Role string `json:"role"` Sk string `json:"sk"` - - // used in role server - LocalSvrConf - - // used in role visitor - ServerName string `json:"server_name"` - BindAddr string `json:"bind_addr"` - BindPort int `json:"bind_port"` } func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool { @@ -743,10 +777,7 @@ func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool { if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || cfg.Role != cmpConf.Role || - cfg.Sk != cmpConf.Sk || - cfg.ServerName != cmpConf.ServerName || - cfg.BindAddr != cmpConf.BindAddr || - cfg.BindPort != cmpConf.BindPort { + cfg.Sk != cmpConf.Sk { return false } return true @@ -763,35 +794,15 @@ func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section i return } - tmpStr := section["role"] - if tmpStr == "" { - tmpStr = "server" - } - if tmpStr == "server" || tmpStr == "visitor" { - cfg.Role = tmpStr - } else { - return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, tmpStr) + cfg.Role = section["role"] + if cfg.Role != "server" { + return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role) } cfg.Sk = section["sk"] - if tmpStr == "visitor" { - cfg.ServerName = prefix + section["server_name"] - if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" { - cfg.BindAddr = "127.0.0.1" - } - - if tmpStr, ok := section["bind_port"]; ok { - if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil { - return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name) - } - } else { - return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name) - } - } else { - if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } + if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { + return } return } @@ -802,19 +813,12 @@ func (cfg *XtcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { } func (cfg *XtcpProxyConf) CheckForCli() (err error) { - if cfg.Role != "server" && cfg.Role != "visitor" { - err = fmt.Errorf("role should be 'server' or 'visitor'") + if err = cfg.BaseProxyConf.checkForCli(); err != nil { return } - if cfg.Role == "visitor" { - if cfg.BindAddr == "" { - err = fmt.Errorf("bind_addr shouldn't be empty") - return - } - if cfg.BindPort == 0 { - err = fmt.Errorf("bind_port should be set") - return - } + if cfg.Role != "server" { + err = fmt.Errorf("role should be 'server'") + return } return } @@ -857,8 +861,8 @@ func ParseRangeSection(name string, section ini.Section) (sections map[string]in // if len(startProxy) is 0, start all // otherwise just start proxies in startProxy map -func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]struct{}) ( - proxyConfs map[string]ProxyConf, visitorConfs map[string]ProxyConf, err error) { +func LoadAllConfFromIni(prefix string, conf ini.File, startProxy map[string]struct{}) ( + proxyConfs map[string]ProxyConf, visitorConfs map[string]VisitorConf, err error) { if prefix != "" { prefix += "." @@ -869,7 +873,7 @@ func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]st startAll = false } proxyConfs = make(map[string]ProxyConf) - visitorConfs = make(map[string]ProxyConf) + visitorConfs = make(map[string]VisitorConf) for name, section := range conf { if name == "common" { continue @@ -894,16 +898,27 @@ func LoadProxyConfFromIni(prefix string, conf ini.File, startProxy map[string]st } for subName, subSection := range subSections { - cfg, err := NewProxyConfFromIni(prefix, subName, subSection) - if err != nil { - return proxyConfs, visitorConfs, err + if subSection["role"] == "" { + subSection["role"] = "server" } - role := subSection["role"] - if role == "visitor" { + if role == "server" { + cfg, errRet := NewProxyConfFromIni(prefix, subName, subSection) + if errRet != nil { + err = errRet + return + } + proxyConfs[prefix+subName] = cfg + } else if role == "visitor" { + cfg, errRet := NewVisitorConfFromIni(prefix, subName, subSection) + if errRet != nil { + err = errRet + return + } visitorConfs[prefix+subName] = cfg } else { - proxyConfs[prefix+subName] = cfg + err = fmt.Errorf("role should be 'server' or 'visitor'") + return } } } diff --git a/models/config/visitor.go b/models/config/visitor.go new file mode 100644 index 00000000..4233375c --- /dev/null +++ b/models/config/visitor.go @@ -0,0 +1,213 @@ +// Copyright 2018 fatedier, fatedier@gmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "fmt" + "reflect" + "strconv" + + "github.com/fatedier/frp/models/consts" + + ini "github.com/vaughan0/go-ini" +) + +var ( + visitorConfTypeMap map[string]reflect.Type +) + +func init() { + visitorConfTypeMap = make(map[string]reflect.Type) + visitorConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpVisitorConf{}) + visitorConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpVisitorConf{}) +} + +type VisitorConf interface { + GetBaseInfo() *BaseVisitorConf + Compare(cmp VisitorConf) bool + UnmarshalFromIni(prefix string, name string, section ini.Section) error + Check() error +} + +func NewVisitorConfByType(cfgType string) VisitorConf { + v, ok := visitorConfTypeMap[cfgType] + if !ok { + return nil + } + cfg := reflect.New(v).Interface().(VisitorConf) + return cfg +} + +func NewVisitorConfFromIni(prefix string, name string, section ini.Section) (cfg VisitorConf, err error) { + cfgType := section["type"] + if cfgType == "" { + err = fmt.Errorf("visitor [%s] type shouldn't be empty", name) + return + } + cfg = NewVisitorConfByType(cfgType) + if cfg == nil { + err = fmt.Errorf("visitor [%s] type [%s] error", name, cfgType) + return + } + if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil { + return + } + if err = cfg.Check(); err != nil { + return + } + return +} + +type BaseVisitorConf struct { + ProxyName string `json:"proxy_name"` + ProxyType string `json:"proxy_type"` + UseEncryption bool `json:"use_encryption"` + UseCompression bool `json:"use_compression"` + Role string `json:"role"` + Sk string `json:"sk"` + ServerName string `json:"server_name"` + BindAddr string `json:"bind_addr"` + BindPort int `json:"bind_port"` +} + +func (cfg *BaseVisitorConf) GetBaseInfo() *BaseVisitorConf { + return cfg +} + +func (cfg *BaseVisitorConf) compare(cmp *BaseVisitorConf) bool { + if cfg.ProxyName != cmp.ProxyName || + cfg.ProxyType != cmp.ProxyType || + cfg.UseEncryption != cmp.UseEncryption || + cfg.UseCompression != cmp.UseCompression || + cfg.Role != cmp.Role || + cfg.Sk != cmp.Sk || + cfg.ServerName != cmp.ServerName || + cfg.BindAddr != cmp.BindAddr || + cfg.BindPort != cmp.BindPort { + return false + } + return true +} + +func (cfg *BaseVisitorConf) check() (err error) { + if cfg.Role != "visitor" { + err = fmt.Errorf("invalid role") + return + } + if cfg.BindAddr == "" { + err = fmt.Errorf("bind_addr shouldn't be empty") + return + } + if cfg.BindPort <= 0 { + err = fmt.Errorf("bind_port is required") + return + } + return +} + +func (cfg *BaseVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { + var ( + tmpStr string + ok bool + ) + cfg.ProxyName = prefix + name + cfg.ProxyType = section["type"] + + if tmpStr, ok = section["use_encryption"]; ok && tmpStr == "true" { + cfg.UseEncryption = true + } + if tmpStr, ok = section["use_compression"]; ok && tmpStr == "true" { + cfg.UseCompression = true + } + + cfg.Role = section["role"] + if cfg.Role != "visitor" { + return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role) + } + cfg.Sk = section["sk"] + cfg.ServerName = prefix + section["server_name"] + if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" { + cfg.BindAddr = "127.0.0.1" + } + + if tmpStr, ok = section["bind_port"]; ok { + if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil { + return fmt.Errorf("Parse conf error: proxy [%s] bind_port incorrect", name) + } + } else { + return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name) + } + return nil +} + +type StcpVisitorConf struct { + BaseVisitorConf +} + +func (cfg *StcpVisitorConf) Compare(cmp VisitorConf) bool { + cmpConf, ok := cmp.(*StcpVisitorConf) + if !ok { + return false + } + + if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) { + return false + } + return true +} + +func (cfg *StcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { + if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil { + return + } + return +} + +func (cfg *StcpVisitorConf) Check() (err error) { + if err = cfg.BaseVisitorConf.check(); err != nil { + return + } + return +} + +type XtcpVisitorConf struct { + BaseVisitorConf +} + +func (cfg *XtcpVisitorConf) Compare(cmp VisitorConf) bool { + cmpConf, ok := cmp.(*XtcpVisitorConf) + if !ok { + return false + } + + if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) { + return false + } + return true +} + +func (cfg *XtcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { + if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil { + return + } + return +} + +func (cfg *XtcpVisitorConf) Check() (err error) { + if err = cfg.BaseVisitorConf.check(); err != nil { + return + } + return +}