Skip to content

Commit

Permalink
frpc: add admin server for reload configure file
Browse files Browse the repository at this point in the history
  • Loading branch information
fatedier committed Jul 12, 2017
1 parent f63a4f0 commit d246400
Show file tree
Hide file tree
Showing 11 changed files with 546 additions and 111 deletions.
60 changes: 60 additions & 0 deletions client/admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2017 fatedier, [email protected]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
"fmt"
"net"
"net/http"
"time"

"github.com/fatedier/frp/models/config"
frpNet "github.com/fatedier/frp/utils/net"

"github.com/julienschmidt/httprouter"
)

var (
httpServerReadTimeout = 10 * time.Second
httpServerWriteTimeout = 10 * time.Second
)

func (svr *Service) RunAdminServer(addr string, port int64) (err error) {
// url router
router := httprouter.New()

user, passwd := config.ClientCommonCfg.AdminUser, config.ClientCommonCfg.AdminPwd

// api, see dashboard_api.go
router.GET("/api/reload", frpNet.HttprouterBasicAuth(svr.apiReload, user, passwd))

address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{
Addr: address,
Handler: router,
ReadTimeout: httpServerReadTimeout,
WriteTimeout: httpServerWriteTimeout,
}
if address == "" {
address = ":http"
}
ln, err := net.Listen("tcp", address)
if err != nil {
return err
}

go server.Serve(ln)
return
}
78 changes: 78 additions & 0 deletions client/admin_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2017 fatedier, [email protected]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
"encoding/json"
"net/http"

"github.com/julienschmidt/httprouter"
ini "github.com/vaughan0/go-ini"

"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
)

type GeneralResponse struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
}

// api/reload
type ReloadResp struct {
GeneralResponse
}

func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res ReloadResp
)
defer func() {
log.Info("Http response [/api/reload]: code [%d]", res.Code)
buf, _ = json.Marshal(&res)
w.Write(buf)
}()

log.Info("Http request: [/api/reload]")

conf, err := ini.LoadFile(config.ClientCommonCfg.ConfigFile)
if err != nil {
res.Code = 1
res.Msg = err.Error()
log.Error("reload frpc config file error: %v", err)
return
}

newCommonCfg, err := config.LoadClientCommonConf(conf)
if err != nil {
res.Code = 2
res.Msg = err.Error()
log.Error("reload frpc common section error: %v", err)
return
}

pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(newCommonCfg.User, conf, newCommonCfg.Start)
if err != nil {
res.Code = 3
res.Msg = err.Error()
log.Error("reload frpc proxy config error: %v", err)
return
}

svr.ctl.reloadConf(pxyCfgs, vistorCfgs)
log.Info("success reload conf")
return
}
107 changes: 102 additions & 5 deletions client/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ func (ctl *Control) manager() {
ctl.Warn("[%s] start error: %s", m.ProxyName, m.Error)
continue
}
cfg, ok := ctl.pxyCfgs[m.ProxyName]
cfg, ok := ctl.getProxyConf(m.ProxyName)
if !ok {
// it will never go to this branch now
ctl.Warn("[%s] no proxy conf found", m.ProxyName)
Expand Down Expand Up @@ -424,20 +424,36 @@ func (ctl *Control) controler() {
maxDelayTime := 30 * time.Second
delayTime := time.Second

checkInterval := 30 * time.Second
checkInterval := 10 * time.Second
checkProxyTicker := time.NewTicker(checkInterval)
for {
select {
case <-checkProxyTicker.C:
// Every 30 seconds, check which proxy registered failed and reregister it to server.
// Every 10 seconds, check which proxy registered failed and reregister it to server.
ctl.mu.RLock()
for _, cfg := range ctl.pxyCfgs {
if _, exist := ctl.getProxy(cfg.GetName()); !exist {
ctl.Info("try to reregister proxy [%s]", cfg.GetName())
if _, exist := ctl.proxies[cfg.GetName()]; !exist {
ctl.Info("try to register proxy [%s]", cfg.GetName())
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
}

for _, cfg := range ctl.vistorCfgs {
if _, exist := ctl.vistors[cfg.GetName()]; !exist {
ctl.Info("try to start vistor [%s]", cfg.GetName())
vistor := NewVistor(ctl, cfg)
err = vistor.Run()
if err != nil {
vistor.Warn("start error: %v", err)
continue
}
ctl.vistors[cfg.GetName()] = vistor
vistor.Info("start vistor success")
}
}
ctl.mu.RUnlock()
case _, ok := <-ctl.closedCh:
// we won't get any variable from this channel
if !ok {
Expand Down Expand Up @@ -485,11 +501,13 @@ func (ctl *Control) controler() {
go ctl.reader()

// send NewProxy message for all configured proxies
ctl.mu.RLock()
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
ctl.mu.RUnlock()

checkProxyTicker.Stop()
checkProxyTicker = time.NewTicker(checkInterval)
Expand Down Expand Up @@ -522,3 +540,82 @@ func (ctl *Control) addProxy(name string, pxy Proxy) {
defer ctl.mu.Unlock()
ctl.proxies[name] = pxy
}

func (ctl *Control) getProxyConf(name string) (conf config.ProxyConf, ok bool) {
ctl.mu.RLock()
defer ctl.mu.RUnlock()
conf, ok = ctl.pxyCfgs[name]
return
}

func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) {
ctl.mu.Lock()
defer ctl.mu.Unlock()

removedPxyNames := make([]string, 0)
for name, oldCfg := range ctl.pxyCfgs {
del := false
cfg, ok := pxyCfgs[name]
if !ok {
del = true
} else {
if !oldCfg.Compare(cfg) {
del = true
}
}

if del {
removedPxyNames = append(removedPxyNames, name)
delete(ctl.pxyCfgs, name)
if pxy, ok := ctl.proxies[name]; ok {
pxy.Close()
}
delete(ctl.proxies, name)
ctl.sendCh <- &msg.CloseProxy{
ProxyName: name,
}
}
}
ctl.Info("proxy removed: %v", removedPxyNames)

addedPxyNames := make([]string, 0)
for name, cfg := range pxyCfgs {
if _, ok := ctl.pxyCfgs[name]; !ok {
ctl.pxyCfgs[name] = cfg
addedPxyNames = append(addedPxyNames, name)
}
}
ctl.Info("proxy added: %v", addedPxyNames)

removedVistorName := make([]string, 0)
for name, oldVistorCfg := range ctl.vistorCfgs {
del := false
cfg, ok := vistorCfgs[name]
if !ok {
del = true
} else {
if !oldVistorCfg.Compare(cfg) {
del = true
}
}

if del {
removedVistorName = append(removedVistorName, name)
delete(ctl.vistorCfgs, name)
if vistor, ok := ctl.vistors[name]; ok {
vistor.Close()
}
delete(ctl.vistors, name)
}
}
ctl.Info("vistor removed: %v", removedVistorName)

addedVistorName := make([]string, 0)
for name, vistorCfg := range vistorCfgs {
if _, ok := ctl.vistorCfgs[name]; !ok {
ctl.vistorCfgs[name] = vistorCfg
addedVistorName = append(addedVistorName, name)
}
}
ctl.Info("vistor added: %v", addedVistorName)
}
13 changes: 12 additions & 1 deletion client/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@

package client

import "github.com/fatedier/frp/models/config"
import (
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
)

type Service struct {
// manager control connection with server
Expand All @@ -38,6 +41,14 @@ func (svr *Service) Run() error {
return err
}

if config.ClientCommonCfg.AdminPort != 0 {
err = svr.RunAdminServer(config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
if err != nil {
log.Warn("run admin server error: %v", err)
}
log.Info("admin server listen on %s:%d", config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
}

<-svr.closedCh
return nil
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/frpc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Options:

func main() {
var err error
confFile := "./frpc.ini"
confFile := "./frps.ini"
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)

Expand All @@ -73,6 +73,7 @@ func main() {
fmt.Println(err)
os.Exit(1)
}
config.ClientCommonCfg.ConfigFile = confFile

if args["-L"] != nil {
if args["-L"].(string) == "console" {
Expand Down
6 changes: 6 additions & 0 deletions conf/frpc_full.ini
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ log_max_days = 3
# for authentication
privilege_token = 12345678

# set admin address for control frpc's action by http api such as reload
admin_addr = 127.0.0.1
admin_port = 7400
admin_user = admin
admin_pwd = admin

# connections will be established in advance, default value is zero
pool_count = 5

Expand Down
2 changes: 1 addition & 1 deletion conf/frps_full.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ kcp_bind_port = 7000
vhost_http_port = 80
vhost_https_port = 443

# if you want to configure or reload frps by dashboard, dashboard_port must be set
# set dashboard_port to view dashboard of frps
dashboard_port = 7500

# dashboard user and pwd for basic auth protect, if not set, both default value is admin
Expand Down
Loading

0 comments on commit d246400

Please sign in to comment.