mirror of
https://github.com/fatedier/frp.git
synced 2026-03-08 02:49:10 +08:00
423 lines
12 KiB
Go
423 lines
12 KiB
Go
package client
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/fatedier/frp/client/configmgmt"
|
|
"github.com/fatedier/frp/client/proxy"
|
|
"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/config/v1/validation"
|
|
"github.com/fatedier/frp/pkg/util/log"
|
|
)
|
|
|
|
type serviceConfigManager struct {
|
|
svr *Service
|
|
}
|
|
|
|
func newServiceConfigManager(svr *Service) configmgmt.ConfigManager {
|
|
return &serviceConfigManager{svr: svr}
|
|
}
|
|
|
|
func (m *serviceConfigManager) ReloadFromFile(strict bool) error {
|
|
if m.svr.configFilePath == "" {
|
|
return fmt.Errorf("%w: frpc has no config file path", configmgmt.ErrInvalidArgument)
|
|
}
|
|
|
|
result, err := config.LoadClientConfigResult(m.svr.configFilePath, strict)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %v", configmgmt.ErrInvalidArgument, err)
|
|
}
|
|
|
|
proxyCfgsForValidation, visitorCfgsForValidation := config.FilterClientConfigurers(
|
|
result.Common,
|
|
result.Proxies,
|
|
result.Visitors,
|
|
)
|
|
proxyCfgsForValidation = config.CompleteProxyConfigurers(proxyCfgsForValidation)
|
|
visitorCfgsForValidation = config.CompleteVisitorConfigurers(visitorCfgsForValidation)
|
|
|
|
if _, err := validation.ValidateAllClientConfig(result.Common, proxyCfgsForValidation, visitorCfgsForValidation, m.svr.unsafeFeatures); err != nil {
|
|
return fmt.Errorf("%w: %v", configmgmt.ErrInvalidArgument, err)
|
|
}
|
|
|
|
if err := m.svr.UpdateConfigSource(result.Common, result.Proxies, result.Visitors); err != nil {
|
|
return fmt.Errorf("%w: %v", configmgmt.ErrApplyConfig, err)
|
|
}
|
|
|
|
log.Infof("success reload conf")
|
|
return nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) ReadConfigFile() (string, error) {
|
|
if m.svr.configFilePath == "" {
|
|
return "", fmt.Errorf("%w: frpc has no config file path", configmgmt.ErrInvalidArgument)
|
|
}
|
|
|
|
content, err := os.ReadFile(m.svr.configFilePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("%w: %v", configmgmt.ErrInvalidArgument, err)
|
|
}
|
|
return string(content), nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) WriteConfigFile(content []byte) error {
|
|
if len(content) == 0 {
|
|
return fmt.Errorf("%w: body can't be empty", configmgmt.ErrInvalidArgument)
|
|
}
|
|
|
|
if err := os.WriteFile(m.svr.configFilePath, content, 0o600); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) GetProxyStatus() []*proxy.WorkingStatus {
|
|
return m.svr.getAllProxyStatus()
|
|
}
|
|
|
|
func (m *serviceConfigManager) IsStoreProxyEnabled(name string) bool {
|
|
if name == "" {
|
|
return false
|
|
}
|
|
|
|
m.svr.reloadMu.Lock()
|
|
storeSource := m.svr.storeSource
|
|
m.svr.reloadMu.Unlock()
|
|
|
|
if storeSource == nil {
|
|
return false
|
|
}
|
|
|
|
cfg := storeSource.GetProxy(name)
|
|
if cfg == nil {
|
|
return false
|
|
}
|
|
enabled := cfg.GetBaseConfig().Enabled
|
|
return enabled == nil || *enabled
|
|
}
|
|
|
|
func (m *serviceConfigManager) StoreEnabled() bool {
|
|
m.svr.reloadMu.Lock()
|
|
storeSource := m.svr.storeSource
|
|
m.svr.reloadMu.Unlock()
|
|
return storeSource != nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) ListStoreProxies() ([]v1.ProxyConfigurer, error) {
|
|
storeSource, err := m.storeSourceOrError()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return storeSource.GetAllProxies()
|
|
}
|
|
|
|
func (m *serviceConfigManager) GetStoreProxy(name string) (v1.ProxyConfigurer, error) {
|
|
if name == "" {
|
|
return nil, fmt.Errorf("%w: proxy name is required", configmgmt.ErrInvalidArgument)
|
|
}
|
|
|
|
storeSource, err := m.storeSourceOrError()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfg := storeSource.GetProxy(name)
|
|
if cfg == nil {
|
|
return nil, fmt.Errorf("%w: proxy %q", configmgmt.ErrNotFound, name)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) CreateStoreProxy(cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
|
|
if err := m.validateStoreProxyConfigurer(cfg); err != nil {
|
|
return nil, fmt.Errorf("%w: validation error: %v", configmgmt.ErrInvalidArgument, err)
|
|
}
|
|
|
|
name := cfg.GetBaseConfig().Name
|
|
persisted, err := m.withStoreProxyMutationAndReload(name, func(storeSource *source.StoreSource) error {
|
|
if err := storeSource.AddProxy(cfg); err != nil {
|
|
if errors.Is(err, source.ErrAlreadyExists) {
|
|
return fmt.Errorf("%w: %v", configmgmt.ErrConflict, err)
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Infof("store: created proxy %q", name)
|
|
return persisted, nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) UpdateStoreProxy(name string, cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
|
|
if name == "" {
|
|
return nil, fmt.Errorf("%w: proxy name is required", configmgmt.ErrInvalidArgument)
|
|
}
|
|
if cfg == nil {
|
|
return nil, fmt.Errorf("%w: invalid proxy config: type is required", configmgmt.ErrInvalidArgument)
|
|
}
|
|
bodyName := cfg.GetBaseConfig().Name
|
|
if bodyName != name {
|
|
return nil, fmt.Errorf("%w: proxy name in URL must match name in body", configmgmt.ErrInvalidArgument)
|
|
}
|
|
if err := m.validateStoreProxyConfigurer(cfg); err != nil {
|
|
return nil, fmt.Errorf("%w: validation error: %v", configmgmt.ErrInvalidArgument, err)
|
|
}
|
|
|
|
persisted, err := m.withStoreProxyMutationAndReload(name, func(storeSource *source.StoreSource) error {
|
|
if err := storeSource.UpdateProxy(cfg); err != nil {
|
|
if errors.Is(err, source.ErrNotFound) {
|
|
return fmt.Errorf("%w: %v", configmgmt.ErrNotFound, err)
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("store: updated proxy %q", name)
|
|
return persisted, nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) DeleteStoreProxy(name string) error {
|
|
if name == "" {
|
|
return fmt.Errorf("%w: proxy name is required", configmgmt.ErrInvalidArgument)
|
|
}
|
|
|
|
if err := m.withStoreMutationAndReload(func(storeSource *source.StoreSource) error {
|
|
if err := storeSource.RemoveProxy(name); err != nil {
|
|
if errors.Is(err, source.ErrNotFound) {
|
|
return fmt.Errorf("%w: %v", configmgmt.ErrNotFound, err)
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("store: deleted proxy %q", name)
|
|
return nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) ListStoreVisitors() ([]v1.VisitorConfigurer, error) {
|
|
storeSource, err := m.storeSourceOrError()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return storeSource.GetAllVisitors()
|
|
}
|
|
|
|
func (m *serviceConfigManager) GetStoreVisitor(name string) (v1.VisitorConfigurer, error) {
|
|
if name == "" {
|
|
return nil, fmt.Errorf("%w: visitor name is required", configmgmt.ErrInvalidArgument)
|
|
}
|
|
|
|
storeSource, err := m.storeSourceOrError()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfg := storeSource.GetVisitor(name)
|
|
if cfg == nil {
|
|
return nil, fmt.Errorf("%w: visitor %q", configmgmt.ErrNotFound, name)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) CreateStoreVisitor(cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error) {
|
|
if err := m.validateStoreVisitorConfigurer(cfg); err != nil {
|
|
return nil, fmt.Errorf("%w: validation error: %v", configmgmt.ErrInvalidArgument, err)
|
|
}
|
|
|
|
name := cfg.GetBaseConfig().Name
|
|
persisted, err := m.withStoreVisitorMutationAndReload(name, func(storeSource *source.StoreSource) error {
|
|
if err := storeSource.AddVisitor(cfg); err != nil {
|
|
if errors.Is(err, source.ErrAlreadyExists) {
|
|
return fmt.Errorf("%w: %v", configmgmt.ErrConflict, err)
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("store: created visitor %q", name)
|
|
return persisted, nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) UpdateStoreVisitor(name string, cfg v1.VisitorConfigurer) (v1.VisitorConfigurer, error) {
|
|
if name == "" {
|
|
return nil, fmt.Errorf("%w: visitor name is required", configmgmt.ErrInvalidArgument)
|
|
}
|
|
if cfg == nil {
|
|
return nil, fmt.Errorf("%w: invalid visitor config: type is required", configmgmt.ErrInvalidArgument)
|
|
}
|
|
bodyName := cfg.GetBaseConfig().Name
|
|
if bodyName != name {
|
|
return nil, fmt.Errorf("%w: visitor name in URL must match name in body", configmgmt.ErrInvalidArgument)
|
|
}
|
|
if err := m.validateStoreVisitorConfigurer(cfg); err != nil {
|
|
return nil, fmt.Errorf("%w: validation error: %v", configmgmt.ErrInvalidArgument, err)
|
|
}
|
|
|
|
persisted, err := m.withStoreVisitorMutationAndReload(name, func(storeSource *source.StoreSource) error {
|
|
if err := storeSource.UpdateVisitor(cfg); err != nil {
|
|
if errors.Is(err, source.ErrNotFound) {
|
|
return fmt.Errorf("%w: %v", configmgmt.ErrNotFound, err)
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("store: updated visitor %q", name)
|
|
return persisted, nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) DeleteStoreVisitor(name string) error {
|
|
if name == "" {
|
|
return fmt.Errorf("%w: visitor name is required", configmgmt.ErrInvalidArgument)
|
|
}
|
|
|
|
if err := m.withStoreMutationAndReload(func(storeSource *source.StoreSource) error {
|
|
if err := storeSource.RemoveVisitor(name); err != nil {
|
|
if errors.Is(err, source.ErrNotFound) {
|
|
return fmt.Errorf("%w: %v", configmgmt.ErrNotFound, err)
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("store: deleted visitor %q", name)
|
|
return nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) GracefulClose(d time.Duration) {
|
|
m.svr.GracefulClose(d)
|
|
}
|
|
|
|
func (m *serviceConfigManager) storeSourceOrError() (*source.StoreSource, error) {
|
|
m.svr.reloadMu.Lock()
|
|
storeSource := m.svr.storeSource
|
|
m.svr.reloadMu.Unlock()
|
|
|
|
if storeSource == nil {
|
|
return nil, fmt.Errorf("%w: store API is disabled", configmgmt.ErrStoreDisabled)
|
|
}
|
|
return storeSource, nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) withStoreMutationAndReload(
|
|
fn func(storeSource *source.StoreSource) error,
|
|
) error {
|
|
m.svr.reloadMu.Lock()
|
|
defer m.svr.reloadMu.Unlock()
|
|
|
|
storeSource := m.svr.storeSource
|
|
if storeSource == nil {
|
|
return fmt.Errorf("%w: store API is disabled", configmgmt.ErrStoreDisabled)
|
|
}
|
|
|
|
if err := fn(storeSource); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := m.svr.reloadConfigFromSourcesLocked(); err != nil {
|
|
return fmt.Errorf("%w: failed to apply config: %v", configmgmt.ErrApplyConfig, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) withStoreProxyMutationAndReload(
|
|
name string,
|
|
fn func(storeSource *source.StoreSource) error,
|
|
) (v1.ProxyConfigurer, error) {
|
|
m.svr.reloadMu.Lock()
|
|
defer m.svr.reloadMu.Unlock()
|
|
|
|
storeSource := m.svr.storeSource
|
|
if storeSource == nil {
|
|
return nil, fmt.Errorf("%w: store API is disabled", configmgmt.ErrStoreDisabled)
|
|
}
|
|
|
|
if err := fn(storeSource); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := m.svr.reloadConfigFromSourcesLocked(); err != nil {
|
|
return nil, fmt.Errorf("%w: failed to apply config: %v", configmgmt.ErrApplyConfig, err)
|
|
}
|
|
|
|
persisted := storeSource.GetProxy(name)
|
|
if persisted == nil {
|
|
return nil, fmt.Errorf("%w: proxy %q not found in store after mutation", configmgmt.ErrApplyConfig, name)
|
|
}
|
|
return persisted.Clone(), nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) withStoreVisitorMutationAndReload(
|
|
name string,
|
|
fn func(storeSource *source.StoreSource) error,
|
|
) (v1.VisitorConfigurer, error) {
|
|
m.svr.reloadMu.Lock()
|
|
defer m.svr.reloadMu.Unlock()
|
|
|
|
storeSource := m.svr.storeSource
|
|
if storeSource == nil {
|
|
return nil, fmt.Errorf("%w: store API is disabled", configmgmt.ErrStoreDisabled)
|
|
}
|
|
|
|
if err := fn(storeSource); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := m.svr.reloadConfigFromSourcesLocked(); err != nil {
|
|
return nil, fmt.Errorf("%w: failed to apply config: %v", configmgmt.ErrApplyConfig, err)
|
|
}
|
|
|
|
persisted := storeSource.GetVisitor(name)
|
|
if persisted == nil {
|
|
return nil, fmt.Errorf("%w: visitor %q not found in store after mutation", configmgmt.ErrApplyConfig, name)
|
|
}
|
|
return persisted.Clone(), nil
|
|
}
|
|
|
|
func (m *serviceConfigManager) validateStoreProxyConfigurer(cfg v1.ProxyConfigurer) error {
|
|
if cfg == nil {
|
|
return fmt.Errorf("invalid proxy config")
|
|
}
|
|
runtimeCfg := cfg.Clone()
|
|
if runtimeCfg == nil {
|
|
return fmt.Errorf("invalid proxy config")
|
|
}
|
|
runtimeCfg.Complete()
|
|
return validation.ValidateProxyConfigurerForClient(runtimeCfg)
|
|
}
|
|
|
|
func (m *serviceConfigManager) validateStoreVisitorConfigurer(cfg v1.VisitorConfigurer) error {
|
|
if cfg == nil {
|
|
return fmt.Errorf("invalid visitor config")
|
|
}
|
|
runtimeCfg := cfg.Clone()
|
|
if runtimeCfg == nil {
|
|
return fmt.Errorf("invalid visitor config")
|
|
}
|
|
runtimeCfg.Complete()
|
|
return validation.ValidateVisitorConfigurer(runtimeCfg)
|
|
}
|