forked from Mxmilu666/frp
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
@@ -19,8 +19,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -29,13 +29,14 @@ import (
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/config/source"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/pkg/util/wait"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
"github.com/fatedier/frp/pkg/vnet"
|
||||
@@ -61,9 +62,11 @@ func (e cancelErr) Error() string {
|
||||
|
||||
// ServiceOptions contains options for creating a new client service.
|
||||
type ServiceOptions struct {
|
||||
Common *v1.ClientCommonConfig
|
||||
ProxyCfgs []v1.ProxyConfigurer
|
||||
VisitorCfgs []v1.VisitorConfigurer
|
||||
Common *v1.ClientCommonConfig
|
||||
|
||||
// ConfigSourceAggregator manages internal config and optional store sources.
|
||||
// It is required for creating a Service.
|
||||
ConfigSourceAggregator *source.Aggregator
|
||||
|
||||
UnsafeFeatures *security.UnsafeFeatures
|
||||
|
||||
@@ -119,11 +122,23 @@ type Service struct {
|
||||
|
||||
vnetController *vnet.Controller
|
||||
|
||||
cfgMu sync.RWMutex
|
||||
common *v1.ClientCommonConfig
|
||||
proxyCfgs []v1.ProxyConfigurer
|
||||
visitorCfgs []v1.VisitorConfigurer
|
||||
clientSpec *msg.ClientSpec
|
||||
cfgMu sync.RWMutex
|
||||
// reloadMu serializes reload transactions to keep reloadCommon and applied
|
||||
// config in sync across concurrent API operations.
|
||||
reloadMu sync.Mutex
|
||||
common *v1.ClientCommonConfig
|
||||
// reloadCommon is used for filtering/defaulting during config-source reloads.
|
||||
// It can be updated by /api/reload without mutating startup-only common behavior.
|
||||
reloadCommon *v1.ClientCommonConfig
|
||||
proxyCfgs []v1.ProxyConfigurer
|
||||
visitorCfgs []v1.VisitorConfigurer
|
||||
clientSpec *msg.ClientSpec
|
||||
|
||||
// aggregator manages multiple configuration sources.
|
||||
// When set, the service watches for config changes and reloads automatically.
|
||||
aggregator *source.Aggregator
|
||||
configSource *source.ConfigSource
|
||||
storeSource *source.StoreSource
|
||||
|
||||
unsafeFeatures *security.UnsafeFeatures
|
||||
|
||||
@@ -146,6 +161,28 @@ func NewService(options ServiceOptions) (*Service, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authRuntime, err := auth.BuildClientAuth(&options.Common.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.ConfigSourceAggregator == nil {
|
||||
return nil, fmt.Errorf("config source aggregator is required")
|
||||
}
|
||||
|
||||
configSource := options.ConfigSourceAggregator.ConfigSource()
|
||||
storeSource := options.ConfigSourceAggregator.StoreSource()
|
||||
|
||||
proxyCfgs, visitorCfgs, loadErr := options.ConfigSourceAggregator.Load()
|
||||
if loadErr != nil {
|
||||
return nil, fmt.Errorf("failed to load config from aggregator: %w", loadErr)
|
||||
}
|
||||
proxyCfgs, visitorCfgs = config.FilterClientConfigurers(options.Common, proxyCfgs, visitorCfgs)
|
||||
proxyCfgs = config.CompleteProxyConfigurers(proxyCfgs)
|
||||
visitorCfgs = config.CompleteVisitorConfigurers(visitorCfgs)
|
||||
|
||||
// Create the web server after all fallible steps so its listener is not
|
||||
// leaked when an earlier error causes NewService to return.
|
||||
var webServer *httppkg.Server
|
||||
if options.Common.WebServer.Port > 0 {
|
||||
ws, err := httppkg.NewServer(options.Common.WebServer)
|
||||
@@ -155,24 +192,24 @@ func NewService(options ServiceOptions) (*Service, error) {
|
||||
webServer = ws
|
||||
}
|
||||
|
||||
authRuntime, err := auth.BuildClientAuth(&options.Common.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Service{
|
||||
ctx: context.Background(),
|
||||
auth: authRuntime,
|
||||
webServer: webServer,
|
||||
common: options.Common,
|
||||
reloadCommon: options.Common,
|
||||
configFilePath: options.ConfigFilePath,
|
||||
unsafeFeatures: options.UnsafeFeatures,
|
||||
proxyCfgs: options.ProxyCfgs,
|
||||
visitorCfgs: options.VisitorCfgs,
|
||||
proxyCfgs: proxyCfgs,
|
||||
visitorCfgs: visitorCfgs,
|
||||
clientSpec: options.ClientSpec,
|
||||
aggregator: options.ConfigSourceAggregator,
|
||||
configSource: configSource,
|
||||
storeSource: storeSource,
|
||||
connectorCreator: options.ConnectorCreator,
|
||||
handleWorkConnCb: options.HandleWorkConnCb,
|
||||
}
|
||||
|
||||
if webServer != nil {
|
||||
webServer.RouteRegister(s.registerRouteHandlers)
|
||||
}
|
||||
@@ -193,22 +230,25 @@ func (svr *Service) Run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if svr.vnetController != nil {
|
||||
vnetController := svr.vnetController
|
||||
if err := svr.vnetController.Init(); err != nil {
|
||||
log.Errorf("init virtual network controller error: %v", err)
|
||||
svr.stop()
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
log.Infof("virtual network controller start...")
|
||||
if err := svr.vnetController.Run(); err != nil {
|
||||
if err := vnetController.Run(); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
log.Warnf("virtual network controller exit with error: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if svr.webServer != nil {
|
||||
webServer := svr.webServer
|
||||
go func() {
|
||||
log.Infof("admin server listen on %s", svr.webServer.Address())
|
||||
if err := svr.webServer.Run(); err != nil {
|
||||
log.Infof("admin server listen on %s", webServer.Address())
|
||||
if err := webServer.Run(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Warnf("admin server exit with error: %v", err)
|
||||
}
|
||||
}()
|
||||
@@ -219,6 +259,7 @@ func (svr *Service) Run(ctx context.Context) error {
|
||||
if svr.ctl == nil {
|
||||
cancelCause := cancelErr{}
|
||||
_ = errors.As(context.Cause(svr.ctx), &cancelCause)
|
||||
svr.stop()
|
||||
return fmt.Errorf("登录服务器失败: %v. 启用 loginFailExit 后,将不再尝试重试", cancelCause.Err)
|
||||
}
|
||||
|
||||
@@ -260,80 +301,20 @@ func (svr *Service) keepControllerWorking() {
|
||||
), true, svr.ctx.Done())
|
||||
}
|
||||
|
||||
// login creates a connection to frps and registers it self as a client
|
||||
// conn: control connection
|
||||
// session: if it's not nil, using tcp mux
|
||||
func (svr *Service) login() (conn net.Conn, connector Connector, err error) {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
connector = svr.connectorCreator(svr.ctx, svr.common)
|
||||
if err = connector.Open(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
connector.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err = connector.Connect()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hostname, _ := os.Hostname()
|
||||
|
||||
loginMsg := &msg.Login{
|
||||
Arch: runtime.GOARCH,
|
||||
Os: runtime.GOOS,
|
||||
Hostname: hostname,
|
||||
PoolCount: svr.common.Transport.PoolCount,
|
||||
User: svr.common.User,
|
||||
ClientID: svr.common.ClientID,
|
||||
Version: version.Full(),
|
||||
Timestamp: time.Now().Unix(),
|
||||
RunID: svr.runID,
|
||||
Metas: svr.common.Metadatas,
|
||||
}
|
||||
if svr.clientSpec != nil {
|
||||
loginMsg.ClientSpec = *svr.clientSpec
|
||||
}
|
||||
|
||||
// Add auth
|
||||
if err = svr.auth.Setter.SetLogin(loginMsg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = msg.WriteMsg(conn, loginMsg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var loginRespMsg msg.LoginResp
|
||||
_ = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
|
||||
return
|
||||
}
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
|
||||
if loginRespMsg.Error != "" {
|
||||
err = fmt.Errorf("%s", loginRespMsg.Error)
|
||||
xl.Errorf("%s", loginRespMsg.Error)
|
||||
return
|
||||
}
|
||||
|
||||
svr.runID = loginRespMsg.RunID
|
||||
xl.AddPrefix(xlog.LogPrefix{Name: "runID", Value: svr.runID})
|
||||
|
||||
xl.Infof("登录服务器成功, 获取 run id [%s]", loginRespMsg.RunID)
|
||||
return
|
||||
}
|
||||
|
||||
func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginExit bool) {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
|
||||
loginFunc := func() (bool, error) {
|
||||
xl.Infof("尝试连接到服务器...")
|
||||
conn, connector, err := svr.login()
|
||||
dialer := &controlSessionDialer{
|
||||
ctx: svr.ctx,
|
||||
common: svr.common,
|
||||
auth: svr.auth,
|
||||
clientSpec: svr.clientSpec,
|
||||
vnetController: svr.vnetController,
|
||||
connectorCreator: svr.connectorCreator,
|
||||
}
|
||||
sessionCtx, err := dialer.Dial(svr.runID)
|
||||
if err != nil {
|
||||
xl.Warnf("连接服务器错误: %v", err)
|
||||
if firstLoginExit {
|
||||
@@ -342,25 +323,19 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
||||
return false, err
|
||||
}
|
||||
|
||||
svr.runID = sessionCtx.RunID
|
||||
xl.AddPrefix(xlog.LogPrefix{Name: "runID", Value: svr.runID})
|
||||
xl.Infof("login to server success, get run id [%s]", svr.runID)
|
||||
|
||||
svr.cfgMu.RLock()
|
||||
proxyCfgs := svr.proxyCfgs
|
||||
visitorCfgs := svr.visitorCfgs
|
||||
svr.cfgMu.RUnlock()
|
||||
|
||||
connEncrypted := svr.clientSpec == nil || svr.clientSpec.Type != "ssh-tunnel"
|
||||
|
||||
sessionCtx := &SessionContext{
|
||||
Common: svr.common,
|
||||
RunID: svr.runID,
|
||||
Conn: conn,
|
||||
ConnEncrypted: connEncrypted,
|
||||
Auth: svr.auth,
|
||||
Connector: connector,
|
||||
VnetController: svr.vnetController,
|
||||
}
|
||||
ctl, err := NewControl(svr.ctx, sessionCtx)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
sessionCtx.Conn.Close()
|
||||
sessionCtx.Connector.Close()
|
||||
xl.Errorf("new control error: %v", err)
|
||||
return false, err
|
||||
}
|
||||
@@ -403,6 +378,35 @@ func (svr *Service) UpdateAllConfigurer(proxyCfgs []v1.ProxyConfigurer, visitorC
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Service) UpdateConfigSource(
|
||||
common *v1.ClientCommonConfig,
|
||||
proxyCfgs []v1.ProxyConfigurer,
|
||||
visitorCfgs []v1.VisitorConfigurer,
|
||||
) error {
|
||||
svr.reloadMu.Lock()
|
||||
defer svr.reloadMu.Unlock()
|
||||
|
||||
cfgSource := svr.configSource
|
||||
if cfgSource == nil {
|
||||
return fmt.Errorf("config source is not available")
|
||||
}
|
||||
|
||||
if err := cfgSource.ReplaceAll(proxyCfgs, visitorCfgs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Non-atomic update semantics: source has been updated at this point.
|
||||
// Even if reload fails below, keep this common config for subsequent reloads.
|
||||
svr.cfgMu.Lock()
|
||||
svr.reloadCommon = common
|
||||
svr.cfgMu.Unlock()
|
||||
|
||||
if err := svr.reloadConfigFromSourcesLocked(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Service) Close() {
|
||||
svr.GracefulClose(time.Duration(0))
|
||||
}
|
||||
@@ -413,6 +417,15 @@ func (svr *Service) GracefulClose(d time.Duration) {
|
||||
}
|
||||
|
||||
func (svr *Service) stop() {
|
||||
// Coordinate shutdown with reload/update paths that read source pointers.
|
||||
svr.reloadMu.Lock()
|
||||
if svr.aggregator != nil {
|
||||
svr.aggregator = nil
|
||||
}
|
||||
svr.configSource = nil
|
||||
svr.storeSource = nil
|
||||
svr.reloadMu.Unlock()
|
||||
|
||||
svr.ctlMu.Lock()
|
||||
defer svr.ctlMu.Unlock()
|
||||
if svr.ctl != nil {
|
||||
@@ -423,6 +436,10 @@ func (svr *Service) stop() {
|
||||
svr.webServer.Close()
|
||||
svr.webServer = nil
|
||||
}
|
||||
if svr.vnetController != nil {
|
||||
_ = svr.vnetController.Stop()
|
||||
svr.vnetController = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (svr *Service) getProxyStatus(name string) (*proxy.WorkingStatus, bool) {
|
||||
@@ -436,6 +453,17 @@ func (svr *Service) getProxyStatus(name string) (*proxy.WorkingStatus, bool) {
|
||||
return ctl.pm.GetProxyStatus(name)
|
||||
}
|
||||
|
||||
func (svr *Service) getVisitorCfg(name string) (v1.VisitorConfigurer, bool) {
|
||||
svr.ctlMu.RLock()
|
||||
ctl := svr.ctl
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
if ctl == nil {
|
||||
return nil, false
|
||||
}
|
||||
return ctl.vm.GetVisitorCfg(name)
|
||||
}
|
||||
|
||||
func (svr *Service) StatusExporter() StatusExporter {
|
||||
return &statusExporterImpl{
|
||||
getProxyStatusFunc: svr.getProxyStatus,
|
||||
@@ -453,3 +481,35 @@ type statusExporterImpl struct {
|
||||
func (s *statusExporterImpl) GetProxyStatus(name string) (*proxy.WorkingStatus, bool) {
|
||||
return s.getProxyStatusFunc(name)
|
||||
}
|
||||
|
||||
func (svr *Service) reloadConfigFromSources() error {
|
||||
svr.reloadMu.Lock()
|
||||
defer svr.reloadMu.Unlock()
|
||||
return svr.reloadConfigFromSourcesLocked()
|
||||
}
|
||||
|
||||
func (svr *Service) reloadConfigFromSourcesLocked() error {
|
||||
aggregator := svr.aggregator
|
||||
if aggregator == nil {
|
||||
return errors.New("config aggregator is not initialized")
|
||||
}
|
||||
|
||||
svr.cfgMu.RLock()
|
||||
reloadCommon := svr.reloadCommon
|
||||
svr.cfgMu.RUnlock()
|
||||
|
||||
proxies, visitors, err := aggregator.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("reload config from sources failed: %w", err)
|
||||
}
|
||||
|
||||
proxies, visitors = config.FilterClientConfigurers(reloadCommon, proxies, visitors)
|
||||
proxies = config.CompleteProxyConfigurers(proxies)
|
||||
visitors = config.CompleteVisitorConfigurers(visitors)
|
||||
|
||||
// Atomically replace the entire configuration
|
||||
if err := svr.UpdateAllConfigurer(proxies, visitors); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user