support yaml/json/toml configuration format, make ini deprecated (#3599)

This commit is contained in:
fatedier 2023-09-06 10:18:02 +08:00 committed by GitHub
parent 885b029fcf
commit c95311d1a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 4178 additions and 3829 deletions

View File

@ -120,16 +120,18 @@ issues:
# - composite literal uses unkeyed fields
exclude-rules:
# Exclude some linters from running on test files.
- path: _test\.go$|^tests/|^samples/
linters:
- errcheck
- maligned
# keep it until we only support go1.20
- linters:
- staticcheck
text: "SA1019: rand.Seed has been deprecated"
# Exclude some linters from running on test files.
- path: _test\.go$|^tests/|^samples/
linters:
- errcheck
- maligned
- linters:
- revive
- stylecheck
text: "use underscores in Go names"
- linters:
- revive
text: "unused-parameter"
# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all

View File

@ -1,3 +1 @@
### Features
* Support Go 1.21.

View File

@ -38,7 +38,7 @@ func (svr *Service) RunAdminServer(address string) (err error) {
router.HandleFunc("/healthz", svr.healthz)
// debug
if svr.cfg.PprofEnable {
if svr.cfg.WebServer.PprofEnable {
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
@ -47,7 +47,7 @@ func (svr *Service) RunAdminServer(address string) (err error) {
}
subRouter := router.NewRoute().Subrouter()
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
user, passwd := svr.cfg.WebServer.User, svr.cfg.WebServer.Password
subRouter.Use(utilnet.NewHTTPAuthMiddleware(user, passwd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
// api, see admin_api.go

View File

@ -30,6 +30,7 @@ import (
"github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/util/log"
)
@ -56,15 +57,21 @@ func (svr *Service) apiReload(w http.ResponseWriter, _ *http.Request) {
}
}()
_, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(svr.cfgFile)
cliCfg, pxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(svr.cfgFile)
if err != nil {
res.Code = 400
res.Msg = err.Error()
log.Warn("reload frpc proxy config error: %s", res.Msg)
return
}
if _, err := validation.ValidateAllClientConfig(cliCfg, pxyCfgs, visitorCfgs); err != nil {
res.Code = 400
res.Msg = err.Error()
log.Warn("reload frpc proxy config error: %s", res.Msg)
return
}
if err = svr.ReloadConf(pxyCfgs, visitorCfgs); err != nil {
if err := svr.ReloadConf(pxyCfgs, visitorCfgs); err != nil {
res.Code = 500
res.Msg = err.Error()
log.Warn("reload frpc proxy config error: %s", res.Msg)
@ -112,7 +119,7 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
if baseCfg.LocalPort != 0 {
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
}
psr.Plugin = baseCfg.Plugin
psr.Plugin = baseCfg.Plugin.Type
if status.Err == "" {
psr.RemoteAddr = status.RemoteAddr
@ -172,24 +179,14 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
return
}
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
content, err := os.ReadFile(svr.cfgFile)
if err != nil {
res.Code = 400
res.Msg = err.Error()
log.Warn("load frpc config file error: %s", res.Msg)
return
}
rows := strings.Split(string(content), "\n")
newRows := make([]string, 0, len(rows))
for _, row := range rows {
row = strings.TrimSpace(row)
if strings.HasPrefix(row, "token") {
continue
}
newRows = append(newRows, row)
}
res.Msg = strings.Join(newRows, "\n")
res.Msg = string(content)
}
// PUT /api/config
@ -221,49 +218,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
return
}
// get token from origin content
token := ""
b, err := os.ReadFile(svr.cfgFile)
if err != nil {
res.Code = 400
res.Msg = err.Error()
log.Warn("load frpc config file error: %s", res.Msg)
return
}
content := string(b)
for _, row := range strings.Split(content, "\n") {
row = strings.TrimSpace(row)
if strings.HasPrefix(row, "token") {
token = row
break
}
}
tmpRows := make([]string, 0)
for _, row := range strings.Split(string(body), "\n") {
row = strings.TrimSpace(row)
if strings.HasPrefix(row, "token") {
continue
}
tmpRows = append(tmpRows, row)
}
newRows := make([]string, 0)
if token != "" {
for _, row := range tmpRows {
newRows = append(newRows, row)
if strings.HasPrefix(row, "[common]") {
newRows = append(newRows, token)
}
}
} else {
newRows = tmpRows
}
content = strings.Join(newRows, "\n")
err = os.WriteFile(svr.cfgFile, []byte(content), 0o644)
if err != nil {
if err := os.WriteFile(svr.cfgFile, body, 0o644); err != nil {
res.Code = 500
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
log.Warn("%s", res.Msg)

View File

@ -23,11 +23,12 @@ import (
"github.com/fatedier/golib/control/shutdown"
"github.com/fatedier/golib/crypto"
"github.com/samber/lo"
"github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/client/visitor"
"github.com/fatedier/frp/pkg/auth"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/xlog"
@ -43,7 +44,7 @@ type Control struct {
runID string
// manage all proxies
pxyCfgs map[string]config.ProxyConf
pxyCfgs []v1.ProxyConfigurer
pm *proxy.Manager
// manage all visitors
@ -69,7 +70,7 @@ type Control struct {
lastPong time.Time
// The client configuration
clientCfg config.ClientCommonConf
clientCfg *v1.ClientCommonConfig
readerShutdown *shutdown.Shutdown
writerShutdown *shutdown.Shutdown
@ -83,9 +84,9 @@ type Control struct {
func NewControl(
ctx context.Context, runID string, conn net.Conn, cm *ConnectionManager,
clientCfg config.ClientCommonConf,
pxyCfgs map[string]config.ProxyConf,
visitorCfgs map[string]config.VisitorConf,
clientCfg *v1.ClientCommonConfig,
pxyCfgs []v1.ProxyConfigurer,
visitorCfgs []v1.VisitorConfigurer,
authSetter auth.Setter,
) *Control {
// new xlog instance
@ -220,7 +221,7 @@ func (ctl *Control) reader() {
defer ctl.readerShutdown.Done()
defer close(ctl.closedCh)
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Auth.Token))
for {
m, err := msg.ReadMsg(encReader)
if err != nil {
@ -240,7 +241,7 @@ func (ctl *Control) reader() {
func (ctl *Control) writer() {
xl := ctl.xl
defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Auth.Token))
if err != nil {
xl.Error("crypto new writer error: %v", err)
ctl.conn.Close()
@ -274,15 +275,16 @@ func (ctl *Control) msgHandler() {
var hbSendCh <-chan time.Time
// TODO(fatedier): disable heartbeat if TCPMux is enabled.
// Just keep it here to keep compatible with old version frps.
if ctl.clientCfg.HeartbeatInterval > 0 {
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
if ctl.clientCfg.Transport.HeartbeatInterval > 0 {
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.Transport.HeartbeatInterval) * time.Second)
defer hbSend.Stop()
hbSendCh = hbSend.C
}
var hbCheckCh <-chan time.Time
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
if ctl.clientCfg.HeartbeatInterval > 0 && ctl.clientCfg.HeartbeatTimeout > 0 && !ctl.clientCfg.TCPMux {
if ctl.clientCfg.Transport.HeartbeatInterval > 0 && ctl.clientCfg.Transport.HeartbeatTimeout > 0 &&
!lo.FromPtr(ctl.clientCfg.Transport.TCPMux) {
hbCheck := time.NewTicker(time.Second)
defer hbCheck.Stop()
hbCheckCh = hbCheck.C
@ -301,7 +303,7 @@ func (ctl *Control) msgHandler() {
}
ctl.sendCh <- pingMsg
case <-hbCheckCh:
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second {
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.Transport.HeartbeatTimeout)*time.Second {
xl.Warn("heartbeat timeout")
// let reader() stop
ctl.conn.Close()
@ -354,7 +356,7 @@ func (ctl *Control) worker() {
ctl.cm.Close()
}
func (ctl *Control) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
func (ctl *Control) ReloadConf(pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
ctl.vm.Reload(visitorCfgs)
ctl.pm.Reload(pxyCfgs)
return nil

View File

@ -21,8 +21,10 @@ import (
"io"
"net"
"net/http"
"strings"
"time"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/util/xlog"
)
@ -49,26 +51,33 @@ type Monitor struct {
cancel context.CancelFunc
}
func NewMonitor(ctx context.Context, checkType string,
intervalS int, timeoutS int, maxFailedTimes int,
addr string, url string,
func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string,
statusNormalFn func(), statusFailedFn func(),
) *Monitor {
if intervalS <= 0 {
intervalS = 10
if cfg.IntervalSeconds <= 0 {
cfg.IntervalSeconds = 10
}
if timeoutS <= 0 {
timeoutS = 3
if cfg.TimeoutSeconds <= 0 {
cfg.TimeoutSeconds = 3
}
if maxFailedTimes <= 0 {
maxFailedTimes = 1
if cfg.MaxFailed <= 0 {
cfg.MaxFailed = 1
}
newctx, cancel := context.WithCancel(ctx)
var url string
if cfg.Type == "http" && cfg.Path != "" {
s := "http://" + addr
if !strings.HasPrefix(cfg.Path, "/") {
s += "/"
}
url = s + cfg.Path
}
return &Monitor{
checkType: checkType,
interval: time.Duration(intervalS) * time.Second,
timeout: time.Duration(timeoutS) * time.Second,
maxFailedTimes: maxFailedTimes,
checkType: cfg.Type,
interval: time.Duration(cfg.IntervalSeconds) * time.Second,
timeout: time.Duration(cfg.TimeoutSeconds) * time.Second,
maxFailedTimes: cfg.MaxFailed,
addr: addr,
url: url,
statusOK: false,

View File

@ -17,16 +17,16 @@ package proxy
import (
"reflect"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func init() {
pxyConfs := []config.ProxyConf{
&config.TCPProxyConf{},
&config.HTTPProxyConf{},
&config.HTTPSProxyConf{},
&config.STCPProxyConf{},
&config.TCPMuxProxyConf{},
pxyConfs := []v1.ProxyConfigurer{
&v1.TCPProxyConfig{},
&v1.HTTPProxyConfig{},
&v1.HTTPSProxyConfig{},
&v1.STCPProxyConfig{},
&v1.TCPMuxProxyConfig{},
}
for _, cfg := range pxyConfs {
RegisterProxyFactory(reflect.TypeOf(cfg), NewGeneralTCPProxy)
@ -40,7 +40,7 @@ type GeneralTCPProxy struct {
*BaseProxy
}
func NewGeneralTCPProxy(baseProxy *BaseProxy, _ config.ProxyConf) Proxy {
func NewGeneralTCPProxy(baseProxy *BaseProxy, _ v1.ProxyConfigurer) Proxy {
return &GeneralTCPProxy{
BaseProxy: baseProxy,
}

View File

@ -30,7 +30,8 @@ import (
pp "github.com/pires/go-proxyproto"
"golang.org/x/time/rate"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
plugin "github.com/fatedier/frp/pkg/plugin/client"
"github.com/fatedier/frp/pkg/transport"
@ -38,9 +39,9 @@ import (
"github.com/fatedier/frp/pkg/util/xlog"
)
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, config.ProxyConf) Proxy{}
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{}
func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, config.ProxyConf) Proxy) {
func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, v1.ProxyConfigurer) Proxy) {
proxyFactoryRegistry[proxyConfType] = factory
}
@ -56,23 +57,23 @@ type Proxy interface {
func NewProxy(
ctx context.Context,
pxyConf config.ProxyConf,
clientCfg config.ClientCommonConf,
pxyConf v1.ProxyConfigurer,
clientCfg *v1.ClientCommonConfig,
msgTransporter transport.MessageTransporter,
) (pxy Proxy) {
var limiter *rate.Limiter
limitBytes := pxyConf.GetBaseConfig().BandwidthLimit.Bytes()
if limitBytes > 0 && pxyConf.GetBaseConfig().BandwidthLimitMode == config.BandwidthLimitModeClient {
limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes()
if limitBytes > 0 && pxyConf.GetBaseConfig().Transport.BandwidthLimitMode == types.BandwidthLimitModeClient {
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
}
baseProxy := BaseProxy{
baseProxyConfig: pxyConf.GetBaseConfig(),
clientCfg: clientCfg,
limiter: limiter,
msgTransporter: msgTransporter,
xl: xlog.FromContextSafe(ctx),
ctx: ctx,
baseCfg: pxyConf.GetBaseConfig(),
clientCfg: clientCfg,
limiter: limiter,
msgTransporter: msgTransporter,
xl: xlog.FromContextSafe(ctx),
ctx: ctx,
}
factory := proxyFactoryRegistry[reflect.TypeOf(pxyConf)]
@ -83,10 +84,10 @@ func NewProxy(
}
type BaseProxy struct {
baseProxyConfig *config.BaseProxyConf
clientCfg config.ClientCommonConf
msgTransporter transport.MessageTransporter
limiter *rate.Limiter
baseCfg *v1.ProxyBaseConfig
clientCfg *v1.ClientCommonConfig
msgTransporter transport.MessageTransporter
limiter *rate.Limiter
// proxyPlugin is used to handle connections instead of dialing to local service.
// It's only validate for TCP protocol now.
proxyPlugin plugin.Plugin
@ -97,8 +98,8 @@ type BaseProxy struct {
}
func (pxy *BaseProxy) Run() error {
if pxy.baseProxyConfig.Plugin != "" {
p, err := plugin.Create(pxy.baseProxyConfig.Plugin, pxy.baseProxyConfig.PluginParams)
if pxy.baseCfg.Plugin.Type != "" {
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, pxy.baseCfg.Plugin.ClientPluginOptions)
if err != nil {
return err
}
@ -114,13 +115,13 @@ func (pxy *BaseProxy) Close() {
}
func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
pxy.HandleTCPWorkConnection(conn, m, []byte(pxy.clientCfg.Token))
pxy.HandleTCPWorkConnection(conn, m, []byte(pxy.clientCfg.Auth.Token))
}
// Common handler for tcp work connections.
func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWorkConn, encKey []byte) {
xl := pxy.xl
baseConfig := pxy.baseProxyConfig
baseCfg := pxy.baseCfg
var (
remote io.ReadWriteCloser
err error
@ -133,8 +134,8 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
}
xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
baseConfig.UseEncryption, baseConfig.UseCompression)
if baseConfig.UseEncryption {
baseCfg.Transport.UseEncryption, baseCfg.Transport.UseCompression)
if baseCfg.Transport.UseEncryption {
remote, err = libio.WithEncryption(remote, encKey)
if err != nil {
workConn.Close()
@ -143,13 +144,13 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
}
}
var compressionResourceRecycleFn func()
if baseConfig.UseCompression {
if baseCfg.Transport.UseCompression {
remote, compressionResourceRecycleFn = libio.WithCompressionFromPool(remote)
}
// check if we need to send proxy protocol info
var extraInfo []byte
if baseConfig.ProxyProtocolVersion != "" {
if baseCfg.Transport.ProxyProtocolVersion != "" {
if m.SrcAddr != "" && m.SrcPort != 0 {
if m.DstAddr == "" {
m.DstAddr = "127.0.0.1"
@ -168,9 +169,9 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
h.TransportProtocol = pp.TCPv6
}
if baseConfig.ProxyProtocolVersion == "v1" {
if baseCfg.Transport.ProxyProtocolVersion == "v1" {
h.Version = 1
} else if baseConfig.ProxyProtocolVersion == "v2" {
} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
h.Version = 2
}
@ -189,12 +190,12 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
}
localConn, err := libdial.Dial(
net.JoinHostPort(baseConfig.LocalIP, strconv.Itoa(baseConfig.LocalPort)),
net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort)),
libdial.WithTimeout(10*time.Second),
)
if err != nil {
workConn.Close()
xl.Error("connect to local service [%s:%d] error: %v", baseConfig.LocalIP, baseConfig.LocalPort, err)
xl.Error("connect to local service [%s:%d] error: %v", baseCfg.LocalIP, baseCfg.LocalPort, err)
return
}

View File

@ -21,8 +21,10 @@ import (
"reflect"
"sync"
"github.com/samber/lo"
"github.com/fatedier/frp/client/event"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/xlog"
@ -35,14 +37,14 @@ type Manager struct {
closed bool
mu sync.RWMutex
clientCfg config.ClientCommonConf
clientCfg *v1.ClientCommonConfig
ctx context.Context
}
func NewManager(
ctx context.Context,
clientCfg config.ClientCommonConf,
clientCfg *v1.ClientCommonConfig,
msgTransporter transport.MessageTransporter,
) *Manager {
return &Manager{
@ -113,15 +115,18 @@ func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
return ps
}
func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
func (pm *Manager) Reload(pxyCfgs []v1.ProxyConfigurer) {
xl := xlog.FromContextSafe(pm.ctx)
pxyCfgsMap := lo.KeyBy(pxyCfgs, func(c v1.ProxyConfigurer) string {
return c.GetBaseConfig().Name
})
pm.mu.Lock()
defer pm.mu.Unlock()
delPxyNames := make([]string, 0)
for name, pxy := range pm.proxies {
del := false
cfg, ok := pxyCfgs[name]
cfg, ok := pxyCfgsMap[name]
if !ok || !reflect.DeepEqual(pxy.Cfg, cfg) {
del = true
}
@ -137,7 +142,8 @@ func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
}
addPxyNames := make([]string, 0)
for name, cfg := range pxyCfgs {
for _, cfg := range pxyCfgs {
name := cfg.GetBaseConfig().Name
if _, ok := pm.proxies[name]; !ok {
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter)
pm.proxies[name] = pxy

View File

@ -18,6 +18,7 @@ import (
"context"
"fmt"
"net"
"strconv"
"sync"
"sync/atomic"
"time"
@ -26,7 +27,7 @@ import (
"github.com/fatedier/frp/client/event"
"github.com/fatedier/frp/client/health"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/xlog"
@ -48,11 +49,11 @@ var (
)
type WorkingStatus struct {
Name string `json:"name"`
Type string `json:"type"`
Phase string `json:"status"`
Err string `json:"err"`
Cfg config.ProxyConf `json:"cfg"`
Name string `json:"name"`
Type string `json:"type"`
Phase string `json:"status"`
Err string `json:"err"`
Cfg v1.ProxyConfigurer `json:"cfg"`
// Got from server.
RemoteAddr string `json:"remote_addr"`
@ -86,17 +87,17 @@ type Wrapper struct {
func NewWrapper(
ctx context.Context,
cfg config.ProxyConf,
clientCfg config.ClientCommonConf,
cfg v1.ProxyConfigurer,
clientCfg *v1.ClientCommonConfig,
eventHandler event.Handler,
msgTransporter transport.MessageTransporter,
) *Wrapper {
baseInfo := cfg.GetBaseConfig()
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name)
pw := &Wrapper{
WorkingStatus: WorkingStatus{
Name: baseInfo.ProxyName,
Type: baseInfo.ProxyType,
Name: baseInfo.Name,
Type: baseInfo.Type,
Phase: ProxyPhaseNew,
Cfg: cfg,
},
@ -108,11 +109,11 @@ func NewWrapper(
ctx: xlog.NewContext(ctx, xl),
}
if baseInfo.HealthCheckType != "" {
if baseInfo.HealthCheck.Type != "" && baseInfo.LocalPort > 0 {
pw.health = 1 // means failed
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr,
baseInfo.HealthCheckURL, pw.statusNormalCallback, pw.statusFailedCallback)
addr := net.JoinHostPort(baseInfo.LocalIP, strconv.Itoa(baseInfo.LocalPort))
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheck, addr,
pw.statusNormalCallback, pw.statusFailedCallback)
xl.Trace("enable health check monitor")
}

View File

@ -25,7 +25,7 @@ import (
"github.com/fatedier/golib/errors"
libio "github.com/fatedier/golib/io"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/proto/udp"
"github.com/fatedier/frp/pkg/util/limit"
@ -33,21 +33,21 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.SUDPProxyConf{}), NewSUDPProxy)
RegisterProxyFactory(reflect.TypeOf(&v1.SUDPProxyConfig{}), NewSUDPProxy)
}
type SUDPProxy struct {
*BaseProxy
cfg *config.SUDPProxyConf
cfg *v1.SUDPProxyConfig
localAddr *net.UDPAddr
closeCh chan struct{}
}
func NewSUDPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.SUDPProxyConf)
func NewSUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
unwrapped, ok := cfg.(*v1.SUDPProxyConfig)
if !ok {
return nil
}
@ -88,15 +88,15 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
return conn.Close()
})
}
if pxy.cfg.UseEncryption {
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
if pxy.cfg.Transport.UseEncryption {
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
if err != nil {
conn.Close()
xl.Error("create encryption stream error: %v", err)
return
}
}
if pxy.cfg.UseCompression {
if pxy.cfg.Transport.UseCompression {
rwc = libio.WithCompression(rwc)
}
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)

View File

@ -24,7 +24,7 @@ import (
"github.com/fatedier/golib/errors"
libio "github.com/fatedier/golib/io"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/proto/udp"
"github.com/fatedier/frp/pkg/util/limit"
@ -32,13 +32,13 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.UDPProxyConf{}), NewUDPProxy)
RegisterProxyFactory(reflect.TypeOf(&v1.UDPProxyConfig{}), NewUDPProxy)
}
type UDPProxy struct {
*BaseProxy
cfg *config.UDPProxyConf
cfg *v1.UDPProxyConfig
localAddr *net.UDPAddr
readCh chan *msg.UDPPacket
@ -49,8 +49,8 @@ type UDPProxy struct {
closed bool
}
func NewUDPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.UDPProxyConf)
func NewUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
unwrapped, ok := cfg.(*v1.UDPProxyConfig)
if !ok {
return nil
}
@ -99,15 +99,15 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
return conn.Close()
})
}
if pxy.cfg.UseEncryption {
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
if pxy.cfg.Transport.UseEncryption {
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
if err != nil {
conn.Close()
xl.Error("create encryption stream error: %v", err)
return
}
}
if pxy.cfg.UseCompression {
if pxy.cfg.Transport.UseCompression {
rwc = libio.WithCompression(rwc)
}
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)

View File

@ -23,7 +23,7 @@ import (
fmux "github.com/hashicorp/yamux"
"github.com/quic-go/quic-go"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/nathole"
"github.com/fatedier/frp/pkg/transport"
@ -31,17 +31,17 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.XTCPProxyConf{}), NewXTCPProxy)
RegisterProxyFactory(reflect.TypeOf(&v1.XTCPProxyConfig{}), NewXTCPProxy)
}
type XTCPProxy struct {
*BaseProxy
cfg *config.XTCPProxyConf
cfg *v1.XTCPProxyConfig
}
func NewXTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.XTCPProxyConf)
func NewXTCPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
unwrapped, ok := cfg.(*v1.XTCPProxyConfig)
if !ok {
return nil
}
@ -75,7 +75,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
transactionID := nathole.NewTransactionID()
natHoleClientMsg := &msg.NatHoleClient{
TransactionID: transactionID,
ProxyName: pxy.cfg.ProxyName,
ProxyName: pxy.cfg.Name,
Sid: natHoleSidMsg.Sid,
MappedAddrs: prepareResult.Addrs,
AssistedAddrs: prepareResult.AssistedAddrs,
@ -93,7 +93,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
listenConn := prepareResult.ListenConn
newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Sk))
newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Secretkey))
if err != nil {
listenConn.Close()
xl.Warn("make hole error: %v", err)
@ -154,7 +154,7 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s
xl.Error("accept connection error: %v", err)
return
}
go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Sk))
go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Secretkey))
}
}
@ -170,9 +170,9 @@ func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, star
tlsConfig.NextProtos = []string{"frp"}
quicListener, err := quic.Listen(listenConn, tlsConfig,
&quic.Config{
MaxIdleTimeout: time.Duration(pxy.clientCfg.QUICMaxIdleTimeout) * time.Second,
MaxIncomingStreams: int64(pxy.clientCfg.QUICMaxIncomingStreams),
KeepAlivePeriod: time.Duration(pxy.clientCfg.QUICKeepalivePeriod) * time.Second,
MaxIdleTimeout: time.Duration(pxy.clientCfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
MaxIncomingStreams: int64(pxy.clientCfg.Transport.QUIC.MaxIncomingStreams),
KeepAlivePeriod: time.Duration(pxy.clientCfg.Transport.QUIC.KeepalivePeriod) * time.Second,
},
)
if err != nil {
@ -192,6 +192,6 @@ func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, star
_ = c.CloseWithError(0, "")
return
}
go pxy.HandleTCPWorkConnection(utilnet.QuicStreamToNetConn(stream, c), startWorkConnMsg, []byte(pxy.cfg.Sk))
go pxy.HandleTCPWorkConnection(utilnet.QuicStreamToNetConn(stream, c), startWorkConnMsg, []byte(pxy.cfg.Secretkey))
}
}

View File

@ -19,7 +19,6 @@ import (
"crypto/tls"
"fmt"
"io"
"math/rand"
"net"
"runtime"
"strconv"
@ -32,10 +31,11 @@ import (
libdial "github.com/fatedier/golib/net/dial"
fmux "github.com/hashicorp/yamux"
quic "github.com/quic-go/quic-go"
"github.com/samber/lo"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/pkg/auth"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/log"
@ -47,8 +47,6 @@ import (
func init() {
crypto.DefaultSalt = "frp"
// TODO: remove this when we drop support for go1.19
rand.Seed(time.Now().UnixNano())
}
// Service is a client service.
@ -63,9 +61,9 @@ type Service struct {
// Sets authentication based on selected method
authSetter auth.Setter
cfg config.ClientCommonConf
pxyCfgs map[string]config.ProxyConf
visitorCfgs map[string]config.VisitorConf
cfg *v1.ClientCommonConfig
pxyCfgs []v1.ProxyConfigurer
visitorCfgs []v1.VisitorConfigurer
cfgMu sync.RWMutex
// The configuration file used to initialize this client, or an empty
@ -81,13 +79,13 @@ type Service struct {
}
func NewService(
cfg config.ClientCommonConf,
pxyCfgs map[string]config.ProxyConf,
visitorCfgs map[string]config.VisitorConf,
cfg *v1.ClientCommonConfig,
pxyCfgs []v1.ProxyConfigurer,
visitorCfgs []v1.VisitorConfigurer,
cfgFile string,
) (svr *Service, err error) {
svr = &Service{
authSetter: auth.NewAuthSetter(cfg.ClientConfig),
authSetter: auth.NewAuthSetter(cfg.Auth),
cfg: cfg,
cfgFile: cfgFile,
pxyCfgs: pxyCfgs,
@ -134,7 +132,7 @@ func (svr *Service) Run(ctx context.Context) error {
// if login_fail_exit is true, just exit this program
// otherwise sleep a while and try again to connect to server
if svr.cfg.LoginFailExit {
if lo.FromPtr(svr.cfg.LoginFailExit) {
return err
}
util.RandomSleep(5*time.Second, 0.9, 1.1)
@ -151,16 +149,16 @@ func (svr *Service) Run(ctx context.Context) error {
go svr.keepControllerWorking()
if svr.cfg.AdminPort != 0 {
if svr.cfg.WebServer.Port != 0 {
// Init admin server assets
assets.Load(svr.cfg.AssetsDir)
assets.Load(svr.cfg.WebServer.AssetsDir)
address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort))
address := net.JoinHostPort(svr.cfg.WebServer.Addr, strconv.Itoa(svr.cfg.WebServer.Port))
err := svr.RunAdminServer(address)
if err != nil {
log.Warn("run admin server error: %v", err)
}
log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
log.Info("admin server listen on %s:%d", svr.cfg.WebServer.Addr, svr.cfg.WebServer.Port)
}
<-svr.ctx.Done()
// service context may not be canceled by svr.Close(), we should call it here to release resources
@ -244,7 +242,7 @@ func (svr *Service) keepControllerWorking() {
// session: if it's not nil, using tcp mux
func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
xl := xlog.FromContextSafe(svr.ctx)
cm = NewConnectionManager(svr.ctx, &svr.cfg)
cm = NewConnectionManager(svr.ctx, svr.cfg)
if err = cm.OpenConnection(); err != nil {
return nil, nil, err
@ -264,12 +262,12 @@ func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
loginMsg := &msg.Login{
Arch: runtime.GOARCH,
Os: runtime.GOOS,
PoolCount: svr.cfg.PoolCount,
PoolCount: svr.cfg.Transport.PoolCount,
User: svr.cfg.User,
Version: version.Full(),
Timestamp: time.Now().Unix(),
RunID: svr.runID,
Metas: svr.cfg.Metas,
Metas: svr.cfg.Metadatas,
}
// Add auth
@ -302,7 +300,7 @@ func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
return
}
func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
func (svr *Service) ReloadConf(pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
svr.cfgMu.Lock()
svr.pxyCfgs = pxyCfgs
svr.visitorCfgs = visitorCfgs
@ -339,13 +337,13 @@ func (svr *Service) GracefulClose(d time.Duration) {
type ConnectionManager struct {
ctx context.Context
cfg *config.ClientCommonConf
cfg *v1.ClientCommonConfig
muxSession *fmux.Session
quicConn quic.Connection
}
func NewConnectionManager(ctx context.Context, cfg *config.ClientCommonConf) *ConnectionManager {
func NewConnectionManager(ctx context.Context, cfg *v1.ClientCommonConfig) *ConnectionManager {
return &ConnectionManager{
ctx: ctx,
cfg: cfg,
@ -356,18 +354,18 @@ func (cm *ConnectionManager) OpenConnection() error {
xl := xlog.FromContextSafe(cm.ctx)
// special for quic
if strings.EqualFold(cm.cfg.Protocol, "quic") {
if strings.EqualFold(cm.cfg.Transport.Protocol, "quic") {
var tlsConfig *tls.Config
var err error
sn := cm.cfg.TLSServerName
sn := cm.cfg.Transport.TLS.ServerName
if sn == "" {
sn = cm.cfg.ServerAddr
}
if cm.cfg.TLSEnable {
if lo.FromPtr(cm.cfg.Transport.TLS.Enable) {
tlsConfig, err = transport.NewClientTLSConfig(
cm.cfg.TLSCertFile,
cm.cfg.TLSKeyFile,
cm.cfg.TLSTrustedCaFile,
cm.cfg.Transport.TLS.CertFile,
cm.cfg.Transport.TLS.KeyFile,
cm.cfg.Transport.TLS.TrustedCaFile,
sn)
} else {
tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
@ -382,9 +380,9 @@ func (cm *ConnectionManager) OpenConnection() error {
cm.ctx,
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
tlsConfig, &quic.Config{
MaxIdleTimeout: time.Duration(cm.cfg.QUICMaxIdleTimeout) * time.Second,
MaxIncomingStreams: int64(cm.cfg.QUICMaxIncomingStreams),
KeepAlivePeriod: time.Duration(cm.cfg.QUICKeepalivePeriod) * time.Second,
MaxIdleTimeout: time.Duration(cm.cfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
MaxIncomingStreams: int64(cm.cfg.Transport.QUIC.MaxIncomingStreams),
KeepAlivePeriod: time.Duration(cm.cfg.Transport.QUIC.KeepalivePeriod) * time.Second,
})
if err != nil {
return err
@ -393,7 +391,7 @@ func (cm *ConnectionManager) OpenConnection() error {
return nil
}
if !cm.cfg.TCPMux {
if !lo.FromPtr(cm.cfg.Transport.TCPMux) {
return nil
}
@ -403,7 +401,7 @@ func (cm *ConnectionManager) OpenConnection() error {
}
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = time.Duration(cm.cfg.TCPMuxKeepaliveInterval) * time.Second
fmuxCfg.KeepAliveInterval = time.Duration(cm.cfg.Transport.TCPMuxKeepaliveInterval) * time.Second
fmuxCfg.LogOutput = io.Discard
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
session, err := fmux.Client(conn, fmuxCfg)
@ -436,20 +434,20 @@ func (cm *ConnectionManager) realConnect() (net.Conn, error) {
xl := xlog.FromContextSafe(cm.ctx)
var tlsConfig *tls.Config
var err error
tlsEnable := cm.cfg.TLSEnable
if cm.cfg.Protocol == "wss" {
tlsEnable := lo.FromPtr(cm.cfg.Transport.TLS.Enable)
if cm.cfg.Transport.Protocol == "wss" {
tlsEnable = true
}
if tlsEnable {
sn := cm.cfg.TLSServerName
sn := cm.cfg.Transport.TLS.ServerName
if sn == "" {
sn = cm.cfg.ServerAddr
}
tlsConfig, err = transport.NewClientTLSConfig(
cm.cfg.TLSCertFile,
cm.cfg.TLSKeyFile,
cm.cfg.TLSTrustedCaFile,
cm.cfg.Transport.TLS.CertFile,
cm.cfg.Transport.TLS.KeyFile,
cm.cfg.Transport.TLS.TrustedCaFile,
sn)
if err != nil {
xl.Warn("fail to build tls configuration, err: %v", err)
@ -457,19 +455,19 @@ func (cm *ConnectionManager) realConnect() (net.Conn, error) {
}
}
proxyType, addr, auth, err := libdial.ParseProxyURL(cm.cfg.HTTPProxy)
proxyType, addr, auth, err := libdial.ParseProxyURL(cm.cfg.Transport.ProxyURL)
if err != nil {
xl.Error("fail to parse proxy url")
return nil, err
}
dialOptions := []libdial.DialOption{}
protocol := cm.cfg.Protocol
protocol := cm.cfg.Transport.Protocol
switch protocol {
case "websocket":
protocol = "tcp"
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: utilnet.DialHookWebsocket(protocol, "")}))
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{
Hook: utilnet.DialHookCustomTLSHeadByte(tlsConfig != nil, cm.cfg.DisableCustomTLSFirstByte),
Hook: utilnet.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(cm.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
}))
dialOptions = append(dialOptions, libdial.WithTLSConfig(tlsConfig))
case "wss":
@ -481,13 +479,13 @@ func (cm *ConnectionManager) realConnect() (net.Conn, error) {
dialOptions = append(dialOptions, libdial.WithTLSConfig(tlsConfig))
}
if cm.cfg.ConnectServerLocalIP != "" {
dialOptions = append(dialOptions, libdial.WithLocalAddr(cm.cfg.ConnectServerLocalIP))
if cm.cfg.Transport.ConnectServerLocalIP != "" {
dialOptions = append(dialOptions, libdial.WithLocalAddr(cm.cfg.Transport.ConnectServerLocalIP))
}
dialOptions = append(dialOptions,
libdial.WithProtocol(protocol),
libdial.WithTimeout(time.Duration(cm.cfg.DialServerTimeout)*time.Second),
libdial.WithKeepAlive(time.Duration(cm.cfg.DialServerKeepAlive)*time.Second),
libdial.WithTimeout(time.Duration(cm.cfg.Transport.DialServerTimeout)*time.Second),
libdial.WithKeepAlive(time.Duration(cm.cfg.Transport.DialServerKeepAlive)*time.Second),
libdial.WithProxy(proxyType, addr),
libdial.WithProxyAuth(auth),
)

View File

@ -22,7 +22,7 @@ import (
libio "github.com/fatedier/golib/io"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/xlog"
@ -31,7 +31,7 @@ import (
type STCPVisitor struct {
*BaseVisitor
cfg *config.STCPVisitorConf
cfg *v1.STCPVisitorConfig
}
func (sv *STCPVisitor) Run() (err error) {
@ -90,10 +90,10 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
newVisitorConnMsg := &msg.NewVisitorConn{
RunID: sv.helper.RunID(),
ProxyName: sv.cfg.ServerName,
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
Timestamp: now,
UseEncryption: sv.cfg.UseEncryption,
UseCompression: sv.cfg.UseCompression,
UseEncryption: sv.cfg.Transport.UseEncryption,
UseCompression: sv.cfg.Transport.UseCompression,
}
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
if err != nil {
@ -117,15 +117,15 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
var remote io.ReadWriteCloser
remote = visitorConn
if sv.cfg.UseEncryption {
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.Sk))
if sv.cfg.Transport.UseEncryption {
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
if err != nil {
xl.Error("create encryption stream error: %v", err)
return
}
}
if sv.cfg.UseCompression {
if sv.cfg.Transport.UseCompression {
var recycleFn func()
remote, recycleFn = libio.WithCompressionFromPool(remote)
defer recycleFn()

View File

@ -25,7 +25,7 @@ import (
"github.com/fatedier/golib/errors"
libio "github.com/fatedier/golib/io"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/proto/udp"
utilnet "github.com/fatedier/frp/pkg/util/net"
@ -42,7 +42,7 @@ type SUDPVisitor struct {
readCh chan *msg.UDPPacket
sendCh chan *msg.UDPPacket
cfg *config.SUDPVisitorConf
cfg *v1.SUDPVisitorConfig
}
// SUDP Run start listen a udp port
@ -208,10 +208,10 @@ func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
newVisitorConnMsg := &msg.NewVisitorConn{
RunID: sv.helper.RunID(),
ProxyName: sv.cfg.ServerName,
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
Timestamp: now,
UseEncryption: sv.cfg.UseEncryption,
UseCompression: sv.cfg.UseCompression,
UseEncryption: sv.cfg.Transport.UseEncryption,
UseCompression: sv.cfg.Transport.UseCompression,
}
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
if err != nil {
@ -232,14 +232,14 @@ func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
var remote io.ReadWriteCloser
remote = visitorConn
if sv.cfg.UseEncryption {
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.Sk))
if sv.cfg.Transport.UseEncryption {
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
if err != nil {
xl.Error("create encryption stream error: %v", err)
return nil, err
}
}
if sv.cfg.UseCompression {
if sv.cfg.Transport.UseCompression {
remote = libio.WithCompression(remote)
}
return utilnet.WrapReadWriteCloserToConn(remote, visitorConn), nil

View File

@ -19,7 +19,7 @@ import (
"net"
"sync"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/transport"
utilnet "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/xlog"
@ -47,11 +47,11 @@ type Visitor interface {
func NewVisitor(
ctx context.Context,
cfg config.VisitorConf,
clientCfg config.ClientCommonConf,
cfg v1.VisitorConfigurer,
clientCfg *v1.ClientCommonConfig,
helper Helper,
) (visitor Visitor) {
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().ProxyName)
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name)
baseVisitor := BaseVisitor{
clientCfg: clientCfg,
helper: helper,
@ -59,18 +59,18 @@ func NewVisitor(
internalLn: utilnet.NewInternalListener(),
}
switch cfg := cfg.(type) {
case *config.STCPVisitorConf:
case *v1.STCPVisitorConfig:
visitor = &STCPVisitor{
BaseVisitor: &baseVisitor,
cfg: cfg,
}
case *config.XTCPVisitorConf:
case *v1.XTCPVisitorConfig:
visitor = &XTCPVisitor{
BaseVisitor: &baseVisitor,
cfg: cfg,
startTunnelCh: make(chan struct{}),
}
case *config.SUDPVisitorConf:
case *v1.SUDPVisitorConfig:
visitor = &SUDPVisitor{
BaseVisitor: &baseVisitor,
cfg: cfg,
@ -81,7 +81,7 @@ func NewVisitor(
}
type BaseVisitor struct {
clientCfg config.ClientCommonConf
clientCfg *v1.ClientCommonConfig
helper Helper
l net.Listener
internalLn *utilnet.InternalListener

View File

@ -22,14 +22,16 @@ import (
"sync"
"time"
"github.com/fatedier/frp/pkg/config"
"github.com/samber/lo"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/xlog"
)
type Manager struct {
clientCfg config.ClientCommonConf
cfgs map[string]config.VisitorConf
clientCfg *v1.ClientCommonConfig
cfgs map[string]v1.VisitorConfigurer
visitors map[string]Visitor
helper Helper
@ -44,13 +46,13 @@ type Manager struct {
func NewManager(
ctx context.Context,
runID string,
clientCfg config.ClientCommonConf,
clientCfg *v1.ClientCommonConfig,
connectServer func() (net.Conn, error),
msgTransporter transport.MessageTransporter,
) *Manager {
m := &Manager{
clientCfg: clientCfg,
cfgs: make(map[string]config.VisitorConf),
cfgs: make(map[string]v1.VisitorConfigurer),
visitors: make(map[string]Visitor),
checkInterval: 10 * time.Second,
ctx: ctx,
@ -79,7 +81,7 @@ func (vm *Manager) Run() {
case <-ticker.C:
vm.mu.Lock()
for _, cfg := range vm.cfgs {
name := cfg.GetBaseConfig().ProxyName
name := cfg.GetBaseConfig().Name
if _, exist := vm.visitors[name]; !exist {
xl.Info("try to start visitor [%s]", name)
_ = vm.startVisitor(cfg)
@ -104,9 +106,9 @@ func (vm *Manager) Close() {
}
// Hold lock before calling this function.
func (vm *Manager) startVisitor(cfg config.VisitorConf) (err error) {
func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
xl := xlog.FromContextSafe(vm.ctx)
name := cfg.GetBaseConfig().ProxyName
name := cfg.GetBaseConfig().Name
visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
err = visitor.Run()
if err != nil {
@ -118,15 +120,18 @@ func (vm *Manager) startVisitor(cfg config.VisitorConf) (err error) {
return
}
func (vm *Manager) Reload(cfgs map[string]config.VisitorConf) {
func (vm *Manager) Reload(cfgs []v1.VisitorConfigurer) {
xl := xlog.FromContextSafe(vm.ctx)
cfgsMap := lo.KeyBy(cfgs, func(c v1.VisitorConfigurer) string {
return c.GetBaseConfig().Name
})
vm.mu.Lock()
defer vm.mu.Unlock()
delNames := make([]string, 0)
for name, oldCfg := range vm.cfgs {
del := false
cfg, ok := cfgs[name]
cfg, ok := cfgsMap[name]
if !ok || !reflect.DeepEqual(oldCfg, cfg) {
del = true
}
@ -145,7 +150,8 @@ func (vm *Manager) Reload(cfgs map[string]config.VisitorConf) {
}
addNames := make([]string, 0)
for name, cfg := range cfgs {
for _, cfg := range cfgs {
name := cfg.GetBaseConfig().Name
if _, ok := vm.cfgs[name]; !ok {
vm.cfgs[name] = cfg
addNames = append(addNames, name)

View File

@ -29,7 +29,7 @@ import (
quic "github.com/quic-go/quic-go"
"golang.org/x/time/rate"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/nathole"
"github.com/fatedier/frp/pkg/transport"
@ -47,7 +47,7 @@ type XTCPVisitor struct {
retryLimiter *rate.Limiter
cancel context.CancelFunc
cfg *config.XTCPVisitorConf
cfg *v1.XTCPVisitorConfig
}
func (sv *XTCPVisitor) Run() (err error) {
@ -56,7 +56,7 @@ func (sv *XTCPVisitor) Run() (err error) {
if sv.cfg.Protocol == "kcp" {
sv.session = NewKCPTunnelSession()
} else {
sv.session = NewQUICTunnelSession(&sv.clientCfg)
sv.session = NewQUICTunnelSession(sv.clientCfg)
}
if sv.cfg.BindPort > 0 {
@ -192,14 +192,14 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
}
var muxConnRWCloser io.ReadWriteCloser = tunnelConn
if sv.cfg.UseEncryption {
muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
if sv.cfg.Transport.UseEncryption {
muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.SecretKey))
if err != nil {
xl.Error("create encryption stream error: %v", err)
return
}
}
if sv.cfg.UseCompression {
if sv.cfg.Transport.UseCompression {
var recycleFn func()
muxConnRWCloser, recycleFn = libio.WithCompressionFromPool(muxConnRWCloser)
defer recycleFn()
@ -292,7 +292,7 @@ func (sv *XTCPVisitor) makeNatHole() {
TransactionID: transactionID,
ProxyName: sv.cfg.ServerName,
Protocol: sv.cfg.Protocol,
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
Timestamp: now,
MappedAddrs: prepareResult.Addrs,
AssistedAddrs: prepareResult.AssistedAddrs,
@ -310,7 +310,7 @@ func (sv *XTCPVisitor) makeNatHole() {
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.Sk))
newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.SecretKey))
if err != nil {
listenConn.Close()
xl.Warn("make hole error: %v", err)
@ -398,10 +398,10 @@ type QUICTunnelSession struct {
listenConn *net.UDPConn
mu sync.RWMutex
clientCfg *config.ClientCommonConf
clientCfg *v1.ClientCommonConfig
}
func NewQUICTunnelSession(clientCfg *config.ClientCommonConf) TunnelSession {
func NewQUICTunnelSession(clientCfg *v1.ClientCommonConfig) TunnelSession {
return &QUICTunnelSession{
clientCfg: clientCfg,
}
@ -415,9 +415,9 @@ func (qs *QUICTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) e
tlsConfig.NextProtos = []string{"frp"}
quicConn, err := quic.Dial(context.Background(), listenConn, raddr, tlsConfig,
&quic.Config{
MaxIdleTimeout: time.Duration(qs.clientCfg.QUICMaxIdleTimeout) * time.Second,
MaxIncomingStreams: int64(qs.clientCfg.QUICMaxIncomingStreams),
KeepAlivePeriod: time.Duration(qs.clientCfg.QUICKeepalivePeriod) * time.Second,
MaxIdleTimeout: time.Duration(qs.clientCfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
MaxIncomingStreams: int64(qs.clientCfg.Transport.QUIC.MaxIncomingStreams),
KeepAlivePeriod: time.Duration(qs.clientCfg.Transport.QUIC.KeepalivePeriod) * time.Second,
})
if err != nil {
return fmt.Errorf("dial quic error: %v", err)

View File

@ -21,7 +21,9 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/consts"
)
@ -40,7 +42,7 @@ func init() {
httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
httpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
httpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
httpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", types.BandwidthLimitModeClient, "bandwidth limit mode")
rootCmd.AddCommand(httpCmd)
}
@ -55,40 +57,36 @@ var httpCmd = &cobra.Command{
os.Exit(1)
}
cfg := &config.HTTPProxyConf{}
cfg := &v1.HTTPProxyConfig{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.HTTPProxy
cfg.Name = prefix + proxyName
cfg.Type = consts.HTTPProxy
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.CustomDomains = strings.Split(customDomains, ",")
cfg.SubDomain = subDomain
cfg.Locations = strings.Split(locations, ",")
cfg.HTTPUser = httpUser
cfg.HTTPPwd = httpPwd
cfg.HTTPPassword = httpPwd
cfg.HostHeaderRewrite = hostHeaderRewrite
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.Transport.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidthLimit)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg.BandwidthLimitMode = bandwidthLimitMode
cfg.Transport.BandwidthLimitMode = bandwidthLimitMode
err = cfg.ValidateForClient()
if err != nil {
if err := validation.ValidateProxyConfigurerForClient(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(clientCfg, proxyConfs, nil, "")
err = startService(clientCfg, []v1.ProxyConfigurer{cfg}, nil, "")
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@ -21,7 +21,9 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/consts"
)
@ -36,7 +38,7 @@ func init() {
httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
httpsCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
httpsCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
httpsCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", types.BandwidthLimitModeClient, "bandwidth limit mode")
rootCmd.AddCommand(httpsCmd)
}
@ -51,36 +53,32 @@ var httpsCmd = &cobra.Command{
os.Exit(1)
}
cfg := &config.HTTPSProxyConf{}
cfg := &v1.HTTPSProxyConfig{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.HTTPSProxy
cfg.Name = prefix + proxyName
cfg.Type = consts.HTTPSProxy
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.CustomDomains = strings.Split(customDomains, ",")
cfg.SubDomain = subDomain
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.Transport.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidthLimit)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg.BandwidthLimitMode = bandwidthLimitMode
cfg.Transport.BandwidthLimitMode = bandwidthLimitMode
err = cfg.ValidateForClient()
if err != nil {
if err := validation.ValidateProxyConfigurerForClient(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(clientCfg, proxyConfs, nil, "")
err = startService(clientCfg, []v1.ProxyConfigurer{cfg}, nil, "")
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@ -21,6 +21,7 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/nathole"
)
@ -49,9 +50,9 @@ var natholeDiscoveryCmd = &cobra.Command{
Short: "Discover nathole information from stun server",
RunE: func(cmd *cobra.Command, args []string) error {
// ignore error here, because we can use command line pameters
cfg, _, _, err := config.ParseClientConfig(cfgFile)
cfg, _, _, _, err := config.LoadClientConfig(cfgFile)
if err != nil {
cfg = config.GetDefaultClientConf()
cfg = &v1.ClientCommonConfig{}
}
if natHoleSTUNServer != "" {
cfg.NatHoleSTUNServer = natHoleSTUNServer
@ -89,7 +90,7 @@ var natholeDiscoveryCmd = &cobra.Command{
},
}
func validateForNatHoleDiscovery(cfg config.ClientCommonConf) error {
func validateForNatHoleDiscovery(cfg *v1.ClientCommonConfig) error {
if cfg.NatHoleSTUNServer == "" {
return fmt.Errorf("nat_hole_stun_server can not be empty")
}

View File

@ -25,6 +25,7 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func init() {
@ -35,7 +36,7 @@ var reloadCmd = &cobra.Command{
Use: "reload",
Short: "Hot-Reload frpc configuration",
RunE: func(cmd *cobra.Command, args []string) error {
cfg, _, _, err := config.ParseClientConfig(cfgFile)
cfg, _, _, _, err := config.LoadClientConfig(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
@ -51,19 +52,20 @@ var reloadCmd = &cobra.Command{
},
}
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")
func reload(clientCfg *v1.ClientCommonConfig) error {
if clientCfg.WebServer.Port == 0 {
return fmt.Errorf("the port of web server shoud be set if you want to use reload feature")
}
req, err := http.NewRequest("GET", "http://"+
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
clientCfg.WebServer.Addr+":"+
fmt.Sprintf("%d", clientCfg.WebServer.Port)+"/api/reload", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
clientCfg.AdminPwd))
authStr := "Basic " + base64.StdEncoding.EncodeToString(
[]byte(clientCfg.WebServer.User+":"+clientCfg.WebServer.Password))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)

View File

@ -27,20 +27,17 @@ import (
"syscall"
"time"
"github.com/samber/lo"
"github.com/spf13/cobra"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/pkg/auth"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/pkg/util/version"
)
const (
CfgFileTypeIni = iota
CfgFileTypeCmd
)
var (
cfgFile string
cfgDir string
@ -160,78 +157,89 @@ func handleTermSignal(svr *client.Service) {
svr.GracefulClose(500 * time.Millisecond)
}
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
cfg = config.GetDefaultClientConf()
func parseClientCommonCfgFromCmd() (*v1.ClientCommonConfig, error) {
cfg := &v1.ClientCommonConfig{}
ipStr, portStr, err := net.SplitHostPort(serverAddr)
if err != nil {
err = fmt.Errorf("invalid server_addr: %v", err)
return
return nil, fmt.Errorf("invalid server_addr: %v", err)
}
cfg.ServerAddr = ipStr
cfg.ServerPort, err = strconv.Atoi(portStr)
if err != nil {
err = fmt.Errorf("invalid server_addr: %v", err)
return
return nil, fmt.Errorf("invalid server_addr: %v", err)
}
cfg.User = user
cfg.Protocol = protocol
cfg.LogLevel = logLevel
cfg.LogFile = logFile
cfg.LogMaxDays = int64(logMaxDays)
cfg.DisableLogColor = disableLogColor
cfg.Transport.Protocol = protocol
cfg.Log.Level = logLevel
cfg.Log.To = logFile
cfg.Log.MaxDays = int64(logMaxDays)
cfg.Log.DisablePrintColor = disableLogColor
cfg.DNSServer = dnsServer
// Only token authentication is supported in cmd mode
cfg.ClientConfig = auth.GetDefaultClientConf()
cfg.Token = token
cfg.TLSEnable = tlsEnable
cfg.TLSServerName = tlsServerName
cfg.Auth.Token = token
cfg.Transport.TLS.Enable = lo.ToPtr(tlsEnable)
cfg.Transport.TLS.ServerName = tlsServerName
cfg.Complete()
if err = cfg.Validate(); err != nil {
err = fmt.Errorf("parse config error: %v", err)
return
err, warning := validation.ValidateClientCommonConfig(cfg)
if warning != nil {
fmt.Printf("WARNING: %v\n", warning)
}
return
if err != nil {
return nil, fmt.Errorf("parse config error: %v", err)
}
return cfg, nil
}
func runClient(cfgFilePath string) error {
cfg, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(cfgFilePath)
cfg, pxyCfgs, visitorCfgs, isLegacyFormat, err := config.LoadClientConfig(cfgFilePath)
if err != nil {
fmt.Println(err)
return err
}
if isLegacyFormat {
fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " +
"please use yaml/json/toml format instead!\n")
}
warning, err := validation.ValidateAllClientConfig(cfg, pxyCfgs, visitorCfgs)
if warning != nil {
fmt.Printf("WARNING: %v\n", warning)
}
if err != nil {
return err
}
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
}
func startService(
cfg config.ClientCommonConf,
pxyCfgs map[string]config.ProxyConf,
visitorCfgs map[string]config.VisitorConf,
cfg *v1.ClientCommonConfig,
pxyCfgs []v1.ProxyConfigurer,
visitorCfgs []v1.VisitorConfigurer,
cfgFile string,
) (err error) {
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
cfg.LogMaxDays, cfg.DisableLogColor)
) error {
log.InitLog(cfg.Log.To, cfg.Log.Level, cfg.Log.MaxDays, cfg.Log.DisablePrintColor)
if cfgFile != "" {
log.Info("start frpc service for config file [%s]", cfgFile)
defer log.Info("frpc service for config file [%s] stopped", cfgFile)
}
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
if errRet != nil {
err = errRet
return
svr, err := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
if err != nil {
return err
}
shouldGracefulClose := cfg.Protocol == "kcp" || cfg.Protocol == "quic"
shouldGracefulClose := cfg.Transport.Protocol == "kcp" || cfg.Transport.Protocol == "quic"
// Capture the exit signal if we use kcp or quic.
if shouldGracefulClose {
go handleTermSignal(svr)
}
_ = svr.Run(context.Background())
return
return nil
}

View File

@ -28,6 +28,7 @@ import (
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func init() {
@ -38,7 +39,7 @@ var statusCmd = &cobra.Command{
Use: "status",
Short: "Overview of all proxies status",
RunE: func(cmd *cobra.Command, args []string) error {
cfg, _, _, err := config.ParseClientConfig(cfgFile)
cfg, _, _, _, err := config.LoadClientConfig(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
@ -52,19 +53,19 @@ var statusCmd = &cobra.Command{
},
}
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")
func status(clientCfg *v1.ClientCommonConfig) error {
if clientCfg.WebServer.Port == 0 {
return fmt.Errorf("the port of web server shoud be set if you want to get proxy status")
}
req, err := http.NewRequest("GET", "http://"+
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
clientCfg.WebServer.Addr+":"+fmt.Sprintf("%d", clientCfg.WebServer.Port)+"/api/status", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
clientCfg.AdminPwd))
authStr := "Basic " + base64.StdEncoding.EncodeToString(
[]byte(clientCfg.WebServer.User+":"+clientCfg.WebServer.Password))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)

View File

@ -20,7 +20,9 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/consts"
)
@ -38,7 +40,7 @@ func init() {
stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
stcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
stcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
stcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", types.BandwidthLimitModeClient, "bandwidth limit mode")
rootCmd.AddCommand(stcpCmd)
}
@ -53,8 +55,8 @@ var stcpCmd = &cobra.Command{
os.Exit(1)
}
proxyConfs := make(map[string]config.ProxyConf)
visitorConfs := make(map[string]config.VisitorConf)
pxyCfgs := make([]v1.ProxyConfigurer, 0)
visitorCfgs := make([]v1.VisitorConfigurer, 0)
var prefix string
if user != "" {
@ -63,50 +65,46 @@ var stcpCmd = &cobra.Command{
switch role {
case "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 := &v1.STCPProxyConfig{}
cfg.Name = prefix + proxyName
cfg.Type = consts.STCPProxy
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.Secretkey = sk
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
cfg.Transport.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidthLimit)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg.BandwidthLimitMode = bandwidthLimitMode
err = cfg.ValidateForClient()
if err != nil {
cfg.Transport.BandwidthLimitMode = bandwidthLimitMode
if err := validation.ValidateProxyConfigurerForClient(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs[cfg.ProxyName] = cfg
pxyCfgs = append(pxyCfgs, cfg)
case "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 := &v1.STCPVisitorConfig{}
cfg.Name = prefix + proxyName
cfg.Type = consts.STCPProxy
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.SecretKey = sk
cfg.ServerName = serverName
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
err = cfg.Validate()
if err != nil {
if err := validation.ValidateVisitorConfigurer(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
visitorConfs[cfg.ProxyName] = cfg
visitorCfgs = append(visitorCfgs, cfg)
default:
fmt.Println("invalid role")
os.Exit(1)
}
err = startService(clientCfg, proxyConfs, visitorConfs, "")
err = startService(clientCfg, pxyCfgs, visitorCfgs, "")
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@ -25,6 +25,7 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func init() {
@ -35,7 +36,7 @@ var stopCmd = &cobra.Command{
Use: "stop",
Short: "Stop the running frpc",
RunE: func(cmd *cobra.Command, args []string) error {
cfg, _, _, err := config.ParseClientConfig(cfgFile)
cfg, _, _, _, err := config.LoadClientConfig(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
@ -51,19 +52,20 @@ var stopCmd = &cobra.Command{
},
}
func stopClient(clientCfg config.ClientCommonConf) error {
if clientCfg.AdminPort == 0 {
return fmt.Errorf("admin_port shoud be set if you want to use stop feature")
func stopClient(clientCfg *v1.ClientCommonConfig) error {
if clientCfg.WebServer.Port == 0 {
return fmt.Errorf("the port of web server shoud be set if you want to use stop feature")
}
req, err := http.NewRequest("POST", "http://"+
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/stop", nil)
clientCfg.WebServer.Addr+":"+
fmt.Sprintf("%d", clientCfg.WebServer.Port)+"/api/stop", nil)
if err != nil {
return err
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
clientCfg.AdminPwd))
authStr := "Basic " + base64.StdEncoding.EncodeToString(
[]byte(clientCfg.WebServer.User+":"+clientCfg.WebServer.Password))
req.Header.Add("Authorization", authStr)
resp, err := http.DefaultClient.Do(req)

View File

@ -20,7 +20,9 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/consts"
)
@ -38,7 +40,7 @@ func init() {
sudpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
sudpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
sudpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
sudpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
sudpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", types.BandwidthLimitModeClient, "bandwidth limit mode")
rootCmd.AddCommand(sudpCmd)
}
@ -53,8 +55,8 @@ var sudpCmd = &cobra.Command{
os.Exit(1)
}
proxyConfs := make(map[string]config.ProxyConf)
visitorConfs := make(map[string]config.VisitorConf)
pxyCfgs := make([]v1.ProxyConfigurer, 0)
visitorCfgs := make([]v1.VisitorConfigurer, 0)
var prefix string
if user != "" {
@ -63,50 +65,46 @@ var sudpCmd = &cobra.Command{
switch role {
case "server":
cfg := &config.SUDPProxyConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.SUDPProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg := &v1.SUDPProxyConfig{}
cfg.Name = prefix + proxyName
cfg.Type = consts.SUDPProxy
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.Secretkey = sk
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
cfg.Transport.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidthLimit)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg.BandwidthLimitMode = bandwidthLimitMode
err = cfg.ValidateForClient()
if err != nil {
cfg.Transport.BandwidthLimitMode = bandwidthLimitMode
if err := validation.ValidateProxyConfigurerForClient(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs[cfg.ProxyName] = cfg
pxyCfgs = append(pxyCfgs, cfg)
case "visitor":
cfg := &config.SUDPVisitorConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.SUDPProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg := &v1.SUDPVisitorConfig{}
cfg.Name = prefix + proxyName
cfg.Type = consts.SUDPProxy
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.SecretKey = sk
cfg.ServerName = serverName
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
err = cfg.Validate()
if err != nil {
if err := validation.ValidateVisitorConfigurer(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
visitorConfs[cfg.ProxyName] = cfg
visitorCfgs = append(visitorCfgs, cfg)
default:
fmt.Println("invalid role")
os.Exit(1)
}
err = startService(clientCfg, proxyConfs, visitorConfs, "")
err = startService(clientCfg, pxyCfgs, visitorCfgs, "")
if err != nil {
os.Exit(1)
}

View File

@ -20,7 +20,9 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/consts"
)
@ -34,7 +36,7 @@ func init() {
tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
tcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
tcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
tcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", types.BandwidthLimitModeClient, "bandwidth limit mode")
rootCmd.AddCommand(tcpCmd)
}
@ -49,35 +51,30 @@ var tcpCmd = &cobra.Command{
os.Exit(1)
}
cfg := &config.TCPProxyConf{}
cfg := &v1.TCPProxyConfig{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.TCPProxy
cfg.Name = prefix + proxyName
cfg.Type = consts.TCPProxy
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.RemotePort = remotePort
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.Transport.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidthLimit)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg.BandwidthLimitMode = bandwidthLimitMode
cfg.Transport.BandwidthLimitMode = bandwidthLimitMode
err = cfg.ValidateForClient()
if err != nil {
if err := validation.ValidateProxyConfigurerForClient(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(clientCfg, proxyConfs, nil, "")
err = startService(clientCfg, []v1.ProxyConfigurer{cfg}, nil, "")
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@ -21,7 +21,9 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/consts"
)
@ -37,7 +39,7 @@ func init() {
tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
tcpMuxCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
tcpMuxCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
tcpMuxCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", types.BandwidthLimitModeClient, "bandwidth limit mode")
rootCmd.AddCommand(tcpMuxCmd)
}
@ -52,37 +54,33 @@ var tcpMuxCmd = &cobra.Command{
os.Exit(1)
}
cfg := &config.TCPMuxProxyConf{}
cfg := &v1.TCPMuxProxyConfig{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.TCPMuxProxy
cfg.Name = prefix + proxyName
cfg.Type = consts.TCPMuxProxy
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.CustomDomains = strings.Split(customDomains, ",")
cfg.SubDomain = subDomain
cfg.Multiplexer = multiplexer
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.Transport.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidthLimit)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg.BandwidthLimitMode = bandwidthLimitMode
cfg.Transport.BandwidthLimitMode = bandwidthLimitMode
err = cfg.ValidateForClient()
if err != nil {
if err := validation.ValidateProxyConfigurerForClient(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(clientCfg, proxyConfs, nil, "")
err = startService(clientCfg, []v1.ProxyConfigurer{cfg}, nil, "")
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@ -20,7 +20,9 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/consts"
)
@ -34,7 +36,7 @@ func init() {
udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
udpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
udpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
udpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", types.BandwidthLimitModeClient, "bandwidth limit mode")
rootCmd.AddCommand(udpCmd)
}
@ -49,35 +51,31 @@ var udpCmd = &cobra.Command{
os.Exit(1)
}
cfg := &config.UDPProxyConf{}
cfg := &v1.UDPProxyConfig{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.UDPProxy
cfg.Name = prefix + proxyName
cfg.Type = consts.UDPProxy
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.RemotePort = remotePort
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.Transport.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidthLimit)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg.BandwidthLimitMode = bandwidthLimitMode
cfg.Transport.BandwidthLimitMode = bandwidthLimitMode
err = cfg.ValidateForClient()
if err != nil {
if err := validation.ValidateProxyConfigurerForClient(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := map[string]config.ProxyConf{
cfg.ProxyName: cfg,
}
err = startService(clientCfg, proxyConfs, nil, "")
err = startService(clientCfg, []v1.ProxyConfigurer{cfg}, nil, "")
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@ -21,6 +21,7 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/v1/validation"
)
func init() {
@ -31,7 +32,20 @@ var verifyCmd = &cobra.Command{
Use: "verify",
Short: "Verify that the configures is valid",
RunE: func(cmd *cobra.Command, args []string) error {
_, _, _, err := config.ParseClientConfig(cfgFile)
if cfgFile == "" {
fmt.Println("frpc: the configuration file is not specified")
return nil
}
cliCfg, pxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
warning, err := validation.ValidateAllClientConfig(cliCfg, pxyCfgs, visitorCfgs)
if warning != nil {
fmt.Printf("WARNING: %v\n", warning)
}
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@ -20,7 +20,9 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/consts"
)
@ -38,7 +40,7 @@ func init() {
xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
xtcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
xtcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
xtcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", types.BandwidthLimitModeClient, "bandwidth limit mode")
rootCmd.AddCommand(xtcpCmd)
}
@ -53,8 +55,8 @@ var xtcpCmd = &cobra.Command{
os.Exit(1)
}
proxyConfs := make(map[string]config.ProxyConf)
visitorConfs := make(map[string]config.VisitorConf)
pxyCfgs := make([]v1.ProxyConfigurer, 0)
visitorCfgs := make([]v1.VisitorConfigurer, 0)
var prefix string
if user != "" {
@ -63,50 +65,48 @@ var xtcpCmd = &cobra.Command{
switch role {
case "server":
cfg := &config.XTCPProxyConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.XTCPProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg := &v1.XTCPProxyConfig{}
cfg.Name = prefix + proxyName
cfg.Type = consts.XTCPProxy
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.Secretkey = sk
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
cfg.Transport.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidthLimit)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cfg.BandwidthLimitMode = bandwidthLimitMode
err = cfg.ValidateForClient()
if err != nil {
cfg.Transport.BandwidthLimitMode = bandwidthLimitMode
if err := validation.ValidateProxyConfigurerForClient(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs[cfg.ProxyName] = cfg
pxyCfgs = append(pxyCfgs, cfg)
case "visitor":
cfg := &config.XTCPVisitorConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.XTCPProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg := &v1.XTCPVisitorConfig{}
cfg.Name = prefix + proxyName
cfg.Type = consts.XTCPProxy
cfg.Transport.UseEncryption = useEncryption
cfg.Transport.UseCompression = useCompression
cfg.SecretKey = sk
cfg.ServerName = serverName
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
err = cfg.Validate()
if err != nil {
if err := validation.ValidateVisitorConfigurer(cfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
visitorConfs[cfg.ProxyName] = cfg
visitorCfgs = append(visitorCfgs, cfg)
default:
fmt.Println("invalid role")
os.Exit(1)
}
err = startService(clientCfg, proxyConfs, visitorConfs, "")
err = startService(clientCfg, pxyCfgs, visitorCfgs, "")
if err != nil {
fmt.Println(err)
os.Exit(1)

View File

@ -15,9 +15,6 @@
package main
import (
"math/rand"
"time"
"github.com/fatedier/golib/crypto"
_ "github.com/fatedier/frp/assets/frps"
@ -26,8 +23,5 @@ import (
func main() {
crypto.DefaultSalt = "frp"
// TODO: remove this when we drop support for go1.19
rand.Seed(time.Now().UnixNano())
Execute()
}

View File

@ -21,19 +21,15 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/auth"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/version"
"github.com/fatedier/frp/server"
)
const (
CfgFileTypeIni = iota
CfgFileTypeCmd
)
var (
cfgFile string
showVersion bool
@ -104,24 +100,35 @@ var rootCmd = &cobra.Command{
return nil
}
var cfg config.ServerCommonConf
var err error
var (
svrCfg *v1.ServerConfig
isLegacyFormat bool
err error
)
if cfgFile != "" {
var content []byte
content, err = config.GetRenderedConfFromFile(cfgFile)
svrCfg, isLegacyFormat, err = config.LoadServerConfig(cfgFile)
if err != nil {
return err
}
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
if isLegacyFormat {
fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " +
"please use yaml/json/toml format instead!\n")
}
} else {
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, nil)
if svrCfg, err = parseServerConfigFromCmd(); err != nil {
return err
}
}
warning, err := validation.ValidateServerConfig(svrCfg)
if warning != nil {
fmt.Printf("WARNING: %v\n", warning)
}
if err != nil {
return err
}
err = runServer(cfg)
if err != nil {
if err := runServer(svrCfg); err != nil {
fmt.Println(err)
os.Exit(1)
}
@ -135,26 +142,8 @@ func Execute() {
}
}
func parseServerCommonCfg(fileType int, source []byte) (cfg config.ServerCommonConf, err error) {
if fileType == CfgFileTypeIni {
cfg, err = config.UnmarshalServerConfFromIni(source)
} else if fileType == CfgFileTypeCmd {
cfg, err = parseServerCommonCfgFromCmd()
}
if err != nil {
return
}
cfg.Complete()
err = cfg.Validate()
if err != nil {
err = fmt.Errorf("parse config error: %v", err)
return
}
return
}
func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
cfg = config.GetDefaultServerConf()
func parseServerConfigFromCmd() (*v1.ServerConfig, error) {
cfg := &v1.ServerConfig{}
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
@ -163,42 +152,42 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
cfg.VhostHTTPPort = vhostHTTPPort
cfg.VhostHTTPSPort = vhostHTTPSPort
cfg.VhostHTTPTimeout = vhostHTTPTimeout
cfg.DashboardAddr = dashboardAddr
cfg.DashboardPort = dashboardPort
cfg.DashboardUser = dashboardUser
cfg.DashboardPwd = dashboardPwd
cfg.WebServer.Addr = dashboardAddr
cfg.WebServer.Port = dashboardPort
cfg.WebServer.User = dashboardUser
cfg.WebServer.Password = dashboardPwd
cfg.EnablePrometheus = enablePrometheus
cfg.DashboardTLSCertFile = dashboardTLSCertFile
cfg.DashboardTLSKeyFile = dashboardTLSKeyFile
cfg.DashboardTLSMode = dashboardTLSMode
cfg.LogFile = logFile
cfg.LogLevel = logLevel
cfg.LogMaxDays = logMaxDays
cfg.SubDomainHost = subDomainHost
cfg.TLSOnly = tlsOnly
// Only token authentication is supported in cmd mode
cfg.ServerConfig = auth.GetDefaultServerConf()
cfg.Token = token
if len(allowPorts) > 0 {
// e.g. 1000-2000,2001,2002,3000-4000
ports, errRet := util.ParseRangeNumbers(allowPorts)
if errRet != nil {
err = fmt.Errorf("parse conf error: allow_ports: %v", errRet)
return
}
for _, port := range ports {
cfg.AllowPorts[int(port)] = struct{}{}
if dashboardTLSMode {
cfg.WebServer.TLS = &v1.TLSConfig{
CertFile: dashboardTLSCertFile,
KeyFile: dashboardTLSKeyFile,
}
}
cfg.Log.To = logFile
cfg.Log.Level = logLevel
cfg.Log.MaxDays = logMaxDays
cfg.Log.DisablePrintColor = disableLogColor
cfg.SubDomainHost = subDomainHost
cfg.TLS.Force = tlsOnly
cfg.MaxPortsPerClient = maxPortsPerClient
cfg.DisableLogColor = disableLogColor
return
// Only token authentication is supported in cmd mode
cfg.Auth.Token = token
if len(allowPorts) > 0 {
portsRanges, err := types.NewPortsRangeSliceFromString(allowPorts)
if err != nil {
return cfg, fmt.Errorf("allow_ports format error: %v", err)
}
cfg.AllowPorts = portsRanges
}
cfg.Complete()
return cfg, nil
}
func runServer(cfg config.ServerCommonConf) (err error) {
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
func runServer(cfg *v1.ServerConfig) (err error) {
log.InitLog(cfg.Log.To, cfg.Log.Level, cfg.Log.MaxDays, cfg.Log.DisablePrintColor)
if cfgFile != "" {
log.Info("frps uses config file: %s", cfgFile)

View File

@ -21,6 +21,7 @@ import (
"github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/v1/validation"
)
func init() {
@ -32,21 +33,23 @@ var verifyCmd = &cobra.Command{
Short: "Verify that the configures is valid",
RunE: func(cmd *cobra.Command, args []string) error {
if cfgFile == "" {
fmt.Println("no config file is specified")
fmt.Println("frps: the configuration file is not specified")
return nil
}
iniContent, err := config.GetRenderedConfFromFile(cfgFile)
svrCfg, _, err := config.LoadServerConfig(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
_, err = parseServerCommonCfg(CfgFileTypeIni, iniContent)
warning, err := validation.ValidateServerConfig(svrCfg)
if warning != nil {
fmt.Printf("WARNING: %v\n", warning)
}
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile)
return nil
},

9
go.mod
View File

@ -3,12 +3,12 @@ module github.com/fatedier/frp
go 1.20
require (
github.com/BurntSushi/toml v0.3.1
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/coreos/go-oidc/v3 v3.6.0
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
github.com/fatedier/golib v0.1.1-0.20230725122706-dcbaee8eef40
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
github.com/go-playground/validator/v10 v10.14.1
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0
@ -37,11 +37,8 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
@ -52,7 +49,6 @@ require (
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
github.com/klauspost/reedsolomon v1.9.15 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/logging v0.2.2 // indirect
@ -76,8 +72,11 @@ require (
golang.org/x/tools v0.9.3 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
// TODO(fatedier): Temporary use the modified version, update to the official version after merging into the official repository.

19
go.sum
View File

@ -1,6 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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=
@ -32,19 +33,10 @@ github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -93,8 +85,6 @@ github.com/klauspost/reedsolomon v1.9.15/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAK
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
@ -147,7 +137,6 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@ -275,6 +264,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@ -286,3 +277,7 @@ k8s.io/client-go v0.27.4 h1:vj2YTtSJ6J4KxaC88P4pMPEQECWMY8gqPqsTgUKzvjk=
k8s.io/client-go v0.27.4/go.mod h1:ragcly7lUlN0SRPk5/ZkGnDjPknzb37TICq07WhI6Xc=
k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

View File

@ -17,76 +17,26 @@ package auth
import (
"fmt"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/consts"
"github.com/fatedier/frp/pkg/msg"
)
type BaseConfig struct {
// AuthenticationMethod specifies what authentication method to use to
// authenticate frpc with frps. If "token" is specified - token will be
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
// token will be issued using OIDC settings. By default, this value is "token".
AuthenticationMethod string `ini:"authentication_method" json:"authentication_method"`
// AuthenticateHeartBeats specifies whether to include authentication token in
// heartbeats sent to frps. By default, this value is false.
AuthenticateHeartBeats bool `ini:"authenticate_heartbeats" json:"authenticate_heartbeats"`
// AuthenticateNewWorkConns specifies whether to include authentication token in
// new work connections sent to frps. By default, this value is false.
AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns" json:"authenticate_new_work_conns"`
}
func getDefaultBaseConf() BaseConfig {
return BaseConfig{
AuthenticationMethod: "token",
AuthenticateHeartBeats: false,
AuthenticateNewWorkConns: false,
}
}
type ClientConfig struct {
BaseConfig `ini:",extends"`
OidcClientConfig `ini:",extends"`
TokenConfig `ini:",extends"`
}
func GetDefaultClientConf() ClientConfig {
return ClientConfig{
BaseConfig: getDefaultBaseConf(),
OidcClientConfig: getDefaultOidcClientConf(),
TokenConfig: getDefaultTokenConf(),
}
}
type ServerConfig struct {
BaseConfig `ini:",extends"`
OidcServerConfig `ini:",extends"`
TokenConfig `ini:",extends"`
}
func GetDefaultServerConf() ServerConfig {
return ServerConfig{
BaseConfig: getDefaultBaseConf(),
OidcServerConfig: getDefaultOidcServerConf(),
TokenConfig: getDefaultTokenConf(),
}
}
type Setter interface {
SetLogin(*msg.Login) error
SetPing(*msg.Ping) error
SetNewWorkConn(*msg.NewWorkConn) error
}
func NewAuthSetter(cfg ClientConfig) (authProvider Setter) {
switch cfg.AuthenticationMethod {
func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter) {
switch cfg.Method {
case consts.TokenAuthMethod:
authProvider = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig)
authProvider = NewTokenAuth(cfg.AdditionalAuthScopes, cfg.Token)
case consts.OidcAuthMethod:
authProvider = NewOidcAuthSetter(cfg.BaseConfig, cfg.OidcClientConfig)
authProvider = NewOidcAuthSetter(cfg.AdditionalAuthScopes, cfg.OIDC)
default:
panic(fmt.Sprintf("wrong authentication method: '%s'", cfg.AuthenticationMethod))
panic(fmt.Sprintf("wrong method: '%s'", cfg.Method))
}
return authProvider
}
@ -96,13 +46,12 @@ type Verifier interface {
VerifyNewWorkConn(*msg.NewWorkConn) error
}
func NewAuthVerifier(cfg ServerConfig) (authVerifier Verifier) {
switch cfg.AuthenticationMethod {
func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
switch cfg.Method {
case consts.TokenAuthMethod:
authVerifier = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig)
authVerifier = NewTokenAuth(cfg.AdditionalAuthScopes, cfg.Token)
case consts.OidcAuthMethod:
authVerifier = NewOidcAuthVerifier(cfg.BaseConfig, cfg.OidcServerConfig)
authVerifier = NewOidcAuthVerifier(cfg.AdditionalAuthScopes, cfg.OIDC)
}
return authVerifier
}

145
pkg/auth/legacy/legacy.go Normal file
View File

@ -0,0 +1,145 @@
// Copyright 2023 The frp Authors
//
// 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 legacy
type BaseConfig struct {
// AuthenticationMethod specifies what authentication method to use to
// authenticate frpc with frps. If "token" is specified - token will be
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
// token will be issued using OIDC settings. By default, this value is "token".
AuthenticationMethod string `ini:"authentication_method" json:"authentication_method"`
// AuthenticateHeartBeats specifies whether to include authentication token in
// heartbeats sent to frps. By default, this value is false.
AuthenticateHeartBeats bool `ini:"authenticate_heartbeats" json:"authenticate_heartbeats"`
// AuthenticateNewWorkConns specifies whether to include authentication token in
// new work connections sent to frps. By default, this value is false.
AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns" json:"authenticate_new_work_conns"`
}
func getDefaultBaseConf() BaseConfig {
return BaseConfig{
AuthenticationMethod: "token",
AuthenticateHeartBeats: false,
AuthenticateNewWorkConns: false,
}
}
type ClientConfig struct {
BaseConfig `ini:",extends"`
OidcClientConfig `ini:",extends"`
TokenConfig `ini:",extends"`
}
func GetDefaultClientConf() ClientConfig {
return ClientConfig{
BaseConfig: getDefaultBaseConf(),
OidcClientConfig: getDefaultOidcClientConf(),
TokenConfig: getDefaultTokenConf(),
}
}
type ServerConfig struct {
BaseConfig `ini:",extends"`
OidcServerConfig `ini:",extends"`
TokenConfig `ini:",extends"`
}
func GetDefaultServerConf() ServerConfig {
return ServerConfig{
BaseConfig: getDefaultBaseConf(),
OidcServerConfig: getDefaultOidcServerConf(),
TokenConfig: getDefaultTokenConf(),
}
}
type OidcClientConfig struct {
// OidcClientID specifies the client ID to use to get a token in OIDC
// authentication if AuthenticationMethod == "oidc". By default, this value
// is "".
OidcClientID string `ini:"oidc_client_id" json:"oidc_client_id"`
// OidcClientSecret specifies the client secret to use to get a token in OIDC
// authentication if AuthenticationMethod == "oidc". By default, this value
// is "".
OidcClientSecret string `ini:"oidc_client_secret" json:"oidc_client_secret"`
// OidcAudience specifies the audience of the token in OIDC authentication
// if AuthenticationMethod == "oidc". By default, this value is "".
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
// OidcScope specifies the scope of the token in OIDC authentication
// if AuthenticationMethod == "oidc". By default, this value is "".
OidcScope string `ini:"oidc_scope" json:"oidc_scope"`
// OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
// By default, this value is "".
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
// OidcAdditionalEndpointParams specifies additional parameters to be sent
// this field will be transfer to map[string][]string in OIDC token generator
// The field will be set by prefix "oidc_additional_"
OidcAdditionalEndpointParams map[string]string `ini:"-" json:"oidc_additional_endpoint_params"`
}
func getDefaultOidcClientConf() OidcClientConfig {
return OidcClientConfig{
OidcClientID: "",
OidcClientSecret: "",
OidcAudience: "",
OidcScope: "",
OidcTokenEndpointURL: "",
OidcAdditionalEndpointParams: make(map[string]string),
}
}
type OidcServerConfig struct {
// OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer
// will be used to load public keys to verify signature and will be compared
// with the issuer claim in the OIDC token. It will be used if
// AuthenticationMethod == "oidc". By default, this value is "".
OidcIssuer string `ini:"oidc_issuer" json:"oidc_issuer"`
// OidcAudience specifies the audience OIDC tokens should contain when validated.
// If this value is empty, audience ("client ID") verification will be skipped.
// It will be used when AuthenticationMethod == "oidc". By default, this
// value is "".
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
// OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is
// expired. It will be used when AuthenticationMethod == "oidc". By default, this
// value is false.
OidcSkipExpiryCheck bool `ini:"oidc_skip_expiry_check" json:"oidc_skip_expiry_check"`
// OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's
// issuer claim matches the issuer specified in OidcIssuer. It will be used when
// AuthenticationMethod == "oidc". By default, this value is false.
OidcSkipIssuerCheck bool `ini:"oidc_skip_issuer_check" json:"oidc_skip_issuer_check"`
}
func getDefaultOidcServerConf() OidcServerConfig {
return OidcServerConfig{
OidcIssuer: "",
OidcAudience: "",
OidcSkipExpiryCheck: false,
OidcSkipIssuerCheck: false,
}
}
type TokenConfig struct {
// 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 `ini:"token" json:"token"`
}
func getDefaultTokenConf() TokenConfig {
return TokenConfig{
Token: "",
}
}

View File

@ -19,105 +19,40 @@ import (
"fmt"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/samber/lo"
"golang.org/x/oauth2/clientcredentials"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
)
type OidcClientConfig struct {
// OidcClientID specifies the client ID to use to get a token in OIDC
// authentication if AuthenticationMethod == "oidc". By default, this value
// is "".
OidcClientID string `ini:"oidc_client_id" json:"oidc_client_id"`
// OidcClientSecret specifies the client secret to use to get a token in OIDC
// authentication if AuthenticationMethod == "oidc". By default, this value
// is "".
OidcClientSecret string `ini:"oidc_client_secret" json:"oidc_client_secret"`
// OidcAudience specifies the audience of the token in OIDC authentication
// if AuthenticationMethod == "oidc". By default, this value is "".
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
// OidcScope specifies the scope of the token in OIDC authentication
// if AuthenticationMethod == "oidc". By default, this value is "".
OidcScope string `ini:"oidc_scope" json:"oidc_scope"`
// OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
// By default, this value is "".
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
// OidcAdditionalEndpointParams specifies additional parameters to be sent
// this field will be transfer to map[string][]string in OIDC token generator
// The field will be set by prefix "oidc_additional_"
OidcAdditionalEndpointParams map[string]string `ini:"-" json:"oidc_additional_endpoint_params"`
}
func getDefaultOidcClientConf() OidcClientConfig {
return OidcClientConfig{
OidcClientID: "",
OidcClientSecret: "",
OidcAudience: "",
OidcScope: "",
OidcTokenEndpointURL: "",
OidcAdditionalEndpointParams: make(map[string]string),
}
}
type OidcServerConfig struct {
// OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer
// will be used to load public keys to verify signature and will be compared
// with the issuer claim in the OIDC token. It will be used if
// AuthenticationMethod == "oidc". By default, this value is "".
OidcIssuer string `ini:"oidc_issuer" json:"oidc_issuer"`
// OidcAudience specifies the audience OIDC tokens should contain when validated.
// If this value is empty, audience ("client ID") verification will be skipped.
// It will be used when AuthenticationMethod == "oidc". By default, this
// value is "".
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
// OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is
// expired. It will be used when AuthenticationMethod == "oidc". By default, this
// value is false.
OidcSkipExpiryCheck bool `ini:"oidc_skip_expiry_check" json:"oidc_skip_expiry_check"`
// OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's
// issuer claim matches the issuer specified in OidcIssuer. It will be used when
// AuthenticationMethod == "oidc". By default, this value is false.
OidcSkipIssuerCheck bool `ini:"oidc_skip_issuer_check" json:"oidc_skip_issuer_check"`
}
func getDefaultOidcServerConf() OidcServerConfig {
return OidcServerConfig{
OidcIssuer: "",
OidcAudience: "",
OidcSkipExpiryCheck: false,
OidcSkipIssuerCheck: false,
}
}
type OidcAuthProvider struct {
BaseConfig
additionalAuthScopes []v1.AuthScope
tokenGenerator *clientcredentials.Config
}
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider {
func NewOidcAuthSetter(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCClientConfig) *OidcAuthProvider {
eps := make(map[string][]string)
for k, v := range cfg.OidcAdditionalEndpointParams {
for k, v := range cfg.AdditionalEndpointParams {
eps[k] = []string{v}
}
if cfg.OidcAudience != "" {
eps["audience"] = []string{cfg.OidcAudience}
if cfg.Audience != "" {
eps["audience"] = []string{cfg.Audience}
}
tokenGenerator := &clientcredentials.Config{
ClientID: cfg.OidcClientID,
ClientSecret: cfg.OidcClientSecret,
Scopes: []string{cfg.OidcScope},
TokenURL: cfg.OidcTokenEndpointURL,
ClientID: cfg.ClientID,
ClientSecret: cfg.ClientSecret,
Scopes: []string{cfg.Scope},
TokenURL: cfg.TokenEndpointURL,
EndpointParams: eps,
}
return &OidcAuthProvider{
BaseConfig: baseCfg,
tokenGenerator: tokenGenerator,
additionalAuthScopes: additionalAuthScopes,
tokenGenerator: tokenGenerator,
}
}
@ -135,7 +70,7 @@ func (auth *OidcAuthProvider) SetLogin(loginMsg *msg.Login) (err error) {
}
func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
if !auth.AuthenticateHeartBeats {
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
return nil
}
@ -144,7 +79,7 @@ func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
}
func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
if !auth.AuthenticateNewWorkConns {
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
return nil
}
@ -153,26 +88,26 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e
}
type OidcAuthConsumer struct {
BaseConfig
additionalAuthScopes []v1.AuthScope
verifier *oidc.IDTokenVerifier
subjectFromLogin string
}
func NewOidcAuthVerifier(baseCfg BaseConfig, cfg OidcServerConfig) *OidcAuthConsumer {
provider, err := oidc.NewProvider(context.Background(), cfg.OidcIssuer)
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCServerConfig) *OidcAuthConsumer {
provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
if err != nil {
panic(err)
}
verifierConf := oidc.Config{
ClientID: cfg.OidcAudience,
SkipClientIDCheck: cfg.OidcAudience == "",
SkipExpiryCheck: cfg.OidcSkipExpiryCheck,
SkipIssuerCheck: cfg.OidcSkipIssuerCheck,
ClientID: cfg.Audience,
SkipClientIDCheck: cfg.Audience == "",
SkipExpiryCheck: cfg.SkipExpiryCheck,
SkipIssuerCheck: cfg.SkipIssuerCheck,
}
return &OidcAuthConsumer{
BaseConfig: baseCfg,
verifier: provider.Verifier(&verifierConf),
additionalAuthScopes: additionalAuthScopes,
verifier: provider.Verifier(&verifierConf),
}
}
@ -200,7 +135,7 @@ func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err err
}
func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
if !auth.AuthenticateHeartBeats {
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
return nil
}
@ -208,7 +143,7 @@ func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
}
func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
if !auth.AuthenticateNewWorkConns {
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
return nil
}

View File

@ -18,43 +18,32 @@ import (
"fmt"
"time"
"github.com/samber/lo"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/util/util"
)
type TokenConfig struct {
// 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 `ini:"token" json:"token"`
}
func getDefaultTokenConf() TokenConfig {
return TokenConfig{
Token: "",
}
}
type TokenAuthSetterVerifier struct {
BaseConfig
token string
additionalAuthScopes []v1.AuthScope
token string
}
func NewTokenAuth(baseCfg BaseConfig, cfg TokenConfig) *TokenAuthSetterVerifier {
func NewTokenAuth(additionalAuthScopes []v1.AuthScope, token string) *TokenAuthSetterVerifier {
return &TokenAuthSetterVerifier{
BaseConfig: baseCfg,
token: cfg.Token,
additionalAuthScopes: additionalAuthScopes,
token: token,
}
}
func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) (err error) {
func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) error {
loginMsg.PrivilegeKey = util.GetAuthKey(auth.token, loginMsg.Timestamp)
return nil
}
func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
if !auth.AuthenticateHeartBeats {
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
return nil
}
@ -64,7 +53,7 @@ func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
}
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
if !auth.AuthenticateNewWorkConns {
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
return nil
}
@ -81,7 +70,7 @@ func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error {
}
func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
if !auth.AuthenticateHeartBeats {
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
return nil
}
@ -92,7 +81,7 @@ func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
}
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
if !auth.AuthenticateNewWorkConns {
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
return nil
}

View File

@ -1,680 +0,0 @@
// Copyright 2020 The frp Authors
//
// 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 (
"testing"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/pkg/auth"
"github.com/fatedier/frp/pkg/consts"
)
const (
testUser = "test"
)
var testClientBytesWithFull = []byte(`
# [common] is integral section
[common]
server_addr = 0.0.0.9
server_port = 7009
http_proxy = http://user:passwd@192.168.1.128:8080
log_file = ./frpc.log9
log_way = file
log_level = info9
log_max_days = 39
disable_log_color = false
authenticate_heartbeats = false
authenticate_new_work_conns = false
token = 12345678
oidc_client_id = client-id
oidc_client_secret = client-secret
oidc_audience = audience
oidc_token_endpoint_url = endpoint_url
admin_addr = 127.0.0.9
admin_port = 7409
admin_user = admin9
admin_pwd = admin9
assets_dir = ./static9
pool_count = 59
tcp_mux
user = your_name
login_fail_exit
protocol = tcp
tls_enable = true
tls_cert_file = client.crt
tls_key_file = client.key
tls_trusted_ca_file = ca.crt
tls_server_name = example.com
dns_server = 8.8.8.9
start = ssh,dns
heartbeat_interval = 39
heartbeat_timeout = 99
meta_var1 = 123
meta_var2 = 234
udp_packet_size = 1509
# all proxy
[ssh]
type = tcp
local_ip = 127.0.0.9
local_port = 29
bandwidth_limit = 19MB
bandwidth_limit_mode = server
use_encryption
use_compression
remote_port = 6009
group = test_group
group_key = 123456
health_check_type = tcp
health_check_timeout_s = 3
health_check_max_failed = 3
health_check_interval_s = 19
meta_var1 = 123
meta_var2 = 234
[ssh_random]
type = tcp
local_ip = 127.0.0.9
local_port = 29
remote_port = 9
[range:tcp_port]
type = tcp
local_ip = 127.0.0.9
local_port = 6010-6011,6019
remote_port = 6010-6011,6019
use_encryption = false
use_compression = false
[dns]
type = udp
local_ip = 114.114.114.114
local_port = 59
remote_port = 6009
use_encryption
use_compression
[range:udp_port]
type = udp
local_ip = 114.114.114.114
local_port = 6000,6010-6011
remote_port = 6000,6010-6011
use_encryption
use_compression
[web01]
type = http
local_ip = 127.0.0.9
local_port = 89
use_encryption
use_compression
http_user = admin
http_pwd = admin
subdomain = web01
custom_domains = web02.yourdomain.com
locations = /,/pic
host_header_rewrite = example.com
header_X-From-Where = frp
health_check_type = http
health_check_url = /status
health_check_interval_s = 19
health_check_max_failed = 3
health_check_timeout_s = 3
[web02]
type = https
local_ip = 127.0.0.9
local_port = 8009
use_encryption
use_compression
subdomain = web01
custom_domains = web02.yourdomain.com
proxy_protocol_version = v2
[secret_tcp]
type = stcp
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
use_encryption = false
use_compression = false
[p2p_tcp]
type = xtcp
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
use_encryption = false
use_compression = false
[tcpmuxhttpconnect]
type = tcpmux
multiplexer = httpconnect
local_ip = 127.0.0.1
local_port = 10701
custom_domains = tunnel1
[plugin_unix_domain_socket]
type = tcp
remote_port = 6003
plugin = unix_domain_socket
plugin_unix_path = /var/run/docker.sock
[plugin_http_proxy]
type = tcp
remote_port = 6004
plugin = http_proxy
plugin_http_user = abc
plugin_http_passwd = abc
[plugin_socks5]
type = tcp
remote_port = 6005
plugin = socks5
plugin_user = abc
plugin_passwd = abc
[plugin_static_file]
type = tcp
remote_port = 6006
plugin = static_file
plugin_local_path = /var/www/blog
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
[plugin_https2http]
type = https
custom_domains = test.yourdomain.com
plugin = https2http
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
[plugin_http2https]
type = http
custom_domains = test.yourdomain.com
plugin = http2https
plugin_local_addr = 127.0.0.1:443
plugin_host_header_rewrite = 127.0.0.1
plugin_header_X-From-Where = frp
# visitor
[secret_tcp_visitor]
role = visitor
type = stcp
server_name = secret_tcp
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 9000
use_encryption = false
use_compression = false
[p2p_tcp_visitor]
role = visitor
type = xtcp
server_name = p2p_tcp
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 9001
use_encryption = false
use_compression = false
`)
func Test_LoadClientCommonConf(t *testing.T) {
assert := assert.New(t)
expected := ClientCommonConf{
ClientConfig: auth.ClientConfig{
BaseConfig: auth.BaseConfig{
AuthenticationMethod: "token",
AuthenticateHeartBeats: false,
AuthenticateNewWorkConns: false,
},
TokenConfig: auth.TokenConfig{
Token: "12345678",
},
OidcClientConfig: auth.OidcClientConfig{
OidcClientID: "client-id",
OidcClientSecret: "client-secret",
OidcAudience: "audience",
OidcTokenEndpointURL: "endpoint_url",
},
},
ServerAddr: "0.0.0.9",
ServerPort: 7009,
NatHoleSTUNServer: "stun.easyvoip.com:3478",
DialServerTimeout: 10,
DialServerKeepAlive: 7200,
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
LogFile: "./frpc.log9",
LogWay: "file",
LogLevel: "info9",
LogMaxDays: 39,
DisableLogColor: false,
AdminAddr: "127.0.0.9",
AdminPort: 7409,
AdminUser: "admin9",
AdminPwd: "admin9",
AssetsDir: "./static9",
PoolCount: 59,
TCPMux: true,
TCPMuxKeepaliveInterval: 60,
User: "your_name",
LoginFailExit: true,
Protocol: "tcp",
QUICKeepalivePeriod: 10,
QUICMaxIdleTimeout: 30,
QUICMaxIncomingStreams: 100000,
TLSEnable: true,
TLSCertFile: "client.crt",
TLSKeyFile: "client.key",
TLSTrustedCaFile: "ca.crt",
TLSServerName: "example.com",
DisableCustomTLSFirstByte: true,
DNSServer: "8.8.8.9",
Start: []string{"ssh", "dns"},
HeartbeatInterval: 39,
HeartbeatTimeout: 99,
Metas: map[string]string{
"var1": "123",
"var2": "234",
},
UDPPacketSize: 1509,
IncludeConfigFiles: []string{},
}
common, err := UnmarshalClientConfFromIni(testClientBytesWithFull)
assert.NoError(err)
assert.EqualValues(expected, common)
}
func Test_LoadClientBasicConf(t *testing.T) {
assert := assert.New(t)
proxyExpected := map[string]ProxyConf{
testUser + ".ssh": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".ssh",
ProxyType: consts.TCPProxy,
UseCompression: true,
UseEncryption: true,
Group: "test_group",
GroupKey: "123456",
BandwidthLimit: MustBandwidthQuantity("19MB"),
BandwidthLimitMode: BandwidthLimitModeServer,
Metas: map[string]string{
"var1": "123",
"var2": "234",
},
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 29,
},
HealthCheckConf: HealthCheckConf{
HealthCheckType: consts.TCPProxy,
HealthCheckTimeoutS: 3,
HealthCheckMaxFailed: 3,
HealthCheckIntervalS: 19,
HealthCheckAddr: "127.0.0.9:29",
},
},
RemotePort: 6009,
},
testUser + ".ssh_random": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".ssh_random",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 29,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 9,
},
testUser + ".tcp_port_0": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".tcp_port_0",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 6010,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6010,
},
testUser + ".tcp_port_1": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".tcp_port_1",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 6011,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6011,
},
testUser + ".tcp_port_2": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".tcp_port_2",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 6019,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6019,
},
testUser + ".dns": &UDPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".dns",
ProxyType: consts.UDPProxy,
UseEncryption: true,
UseCompression: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "114.114.114.114",
LocalPort: 59,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6009,
},
testUser + ".udp_port_0": &UDPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".udp_port_0",
ProxyType: consts.UDPProxy,
UseEncryption: true,
UseCompression: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "114.114.114.114",
LocalPort: 6000,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6000,
},
testUser + ".udp_port_1": &UDPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".udp_port_1",
ProxyType: consts.UDPProxy,
UseEncryption: true,
UseCompression: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "114.114.114.114",
LocalPort: 6010,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6010,
},
testUser + ".udp_port_2": &UDPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".udp_port_2",
ProxyType: consts.UDPProxy,
UseEncryption: true,
UseCompression: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "114.114.114.114",
LocalPort: 6011,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6011,
},
testUser + ".web01": &HTTPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".web01",
ProxyType: consts.HTTPProxy,
UseCompression: true,
UseEncryption: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 89,
},
HealthCheckConf: HealthCheckConf{
HealthCheckType: consts.HTTPProxy,
HealthCheckTimeoutS: 3,
HealthCheckMaxFailed: 3,
HealthCheckIntervalS: 19,
HealthCheckURL: "http://127.0.0.9:89/status",
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
DomainConf: DomainConf{
CustomDomains: []string{"web02.yourdomain.com"},
SubDomain: "web01",
},
Locations: []string{"/", "/pic"},
HTTPUser: "admin",
HTTPPwd: "admin",
HostHeaderRewrite: "example.com",
Headers: map[string]string{
"X-From-Where": "frp",
},
},
testUser + ".web02": &HTTPSProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".web02",
ProxyType: consts.HTTPSProxy,
UseCompression: true,
UseEncryption: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 8009,
},
ProxyProtocolVersion: "v2",
BandwidthLimitMode: BandwidthLimitModeClient,
},
DomainConf: DomainConf{
CustomDomains: []string{"web02.yourdomain.com"},
SubDomain: "web01",
},
},
testUser + ".secret_tcp": &STCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".secret_tcp",
ProxyType: consts.STCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
LocalPort: 22,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RoleServerCommonConf: RoleServerCommonConf{
Role: "server",
Sk: "abcdefg",
},
},
testUser + ".p2p_tcp": &XTCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".p2p_tcp",
ProxyType: consts.XTCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
LocalPort: 22,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RoleServerCommonConf: RoleServerCommonConf{
Role: "server",
Sk: "abcdefg",
},
},
testUser + ".tcpmuxhttpconnect": &TCPMuxProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".tcpmuxhttpconnect",
ProxyType: consts.TCPMuxProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
LocalPort: 10701,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
DomainConf: DomainConf{
CustomDomains: []string{"tunnel1"},
SubDomain: "",
},
Multiplexer: "httpconnect",
},
testUser + ".plugin_unix_domain_socket": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".plugin_unix_domain_socket",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
Plugin: "unix_domain_socket",
PluginParams: map[string]string{
"plugin_unix_path": "/var/run/docker.sock",
},
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6003,
},
testUser + ".plugin_http_proxy": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".plugin_http_proxy",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
Plugin: "http_proxy",
PluginParams: map[string]string{
"plugin_http_user": "abc",
"plugin_http_passwd": "abc",
},
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6004,
},
testUser + ".plugin_socks5": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".plugin_socks5",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
Plugin: "socks5",
PluginParams: map[string]string{
"plugin_user": "abc",
"plugin_passwd": "abc",
},
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6005,
},
testUser + ".plugin_static_file": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".plugin_static_file",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
Plugin: "static_file",
PluginParams: map[string]string{
"plugin_local_path": "/var/www/blog",
"plugin_strip_prefix": "static",
"plugin_http_user": "abc",
"plugin_http_passwd": "abc",
},
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6006,
},
testUser + ".plugin_https2http": &HTTPSProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".plugin_https2http",
ProxyType: consts.HTTPSProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
Plugin: "https2http",
PluginParams: map[string]string{
"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",
},
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
DomainConf: DomainConf{
CustomDomains: []string{"test.yourdomain.com"},
},
},
testUser + ".plugin_http2https": &HTTPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testUser + ".plugin_http2https",
ProxyType: consts.HTTPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
Plugin: "http2https",
PluginParams: map[string]string{
"plugin_local_addr": "127.0.0.1:443",
"plugin_host_header_rewrite": "127.0.0.1",
"plugin_header_X-From-Where": "frp",
},
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
DomainConf: DomainConf{
CustomDomains: []string{"test.yourdomain.com"},
},
},
}
visitorExpected := map[string]VisitorConf{
testUser + ".secret_tcp_visitor": &STCPVisitorConf{
BaseVisitorConf: BaseVisitorConf{
ProxyName: testUser + ".secret_tcp_visitor",
ProxyType: consts.STCPProxy,
Role: "visitor",
Sk: "abcdefg",
ServerName: testVisitorPrefix + "secret_tcp",
BindAddr: "127.0.0.1",
BindPort: 9000,
},
},
testUser + ".p2p_tcp_visitor": &XTCPVisitorConf{
BaseVisitorConf: BaseVisitorConf{
ProxyName: testUser + ".p2p_tcp_visitor",
ProxyType: consts.XTCPProxy,
Role: "visitor",
Sk: "abcdefg",
ServerName: testProxyPrefix + "p2p_tcp",
BindAddr: "127.0.0.1",
BindPort: 9001,
},
Protocol: "quic",
MaxRetriesAnHour: 8,
MinRetryInterval: 90,
FallbackTimeoutMs: 1000,
},
}
proxyActual, visitorActual, err := LoadAllProxyConfsFromIni(testUser, testClientBytesWithFull, nil)
assert.NoError(err)
assert.Equal(proxyExpected, proxyActual)
assert.Equal(visitorExpected, visitorActual)
}

View File

@ -1,4 +1,4 @@
// Copyright 2020 The frp Authors
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package config
package legacy
import (
"fmt"
@ -23,15 +23,16 @@ import (
"github.com/samber/lo"
"gopkg.in/ini.v1"
"github.com/fatedier/frp/pkg/auth"
legacyauth "github.com/fatedier/frp/pkg/auth/legacy"
"github.com/fatedier/frp/pkg/util/util"
)
// ClientCommonConf contains information for a client service. It is
// ClientCommonConf is the configuration parsed from ini.
// It 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 {
auth.ClientConfig `ini:",extends"`
legacyauth.ClientConfig `ini:",extends"`
// ServerAddr specifies the address of the server to connect to. By
// default, this value is "0.0.0.0".
@ -168,85 +169,6 @@ type ClientCommonConf struct {
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
}
// GetDefaultClientConf returns a client configuration with default values.
func GetDefaultClientConf() ClientCommonConf {
return ClientCommonConf{
ClientConfig: auth.GetDefaultClientConf(),
ServerAddr: "0.0.0.0",
ServerPort: 7000,
NatHoleSTUNServer: "stun.easyvoip.com:3478",
DialServerTimeout: 10,
DialServerKeepAlive: 7200,
HTTPProxy: os.Getenv("http_proxy"),
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
AdminAddr: "127.0.0.1",
PoolCount: 1,
TCPMux: true,
TCPMuxKeepaliveInterval: 60,
LoginFailExit: true,
Start: make([]string, 0),
Protocol: "tcp",
QUICKeepalivePeriod: 10,
QUICMaxIdleTimeout: 30,
QUICMaxIncomingStreams: 100000,
TLSEnable: true,
DisableCustomTLSFirstByte: true,
HeartbeatInterval: 30,
HeartbeatTimeout: 90,
Metas: make(map[string]string),
UDPPacketSize: 1500,
IncludeConfigFiles: make([]string, 0),
}
}
func (cfg *ClientCommonConf) Complete() {
if cfg.LogFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
}
func (cfg *ClientCommonConf) Validate() error {
if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
}
}
if !cfg.TLSEnable {
if cfg.TLSCertFile != "" {
fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
}
if cfg.TLSKeyFile != "" {
fmt.Println("WARNING! tls_key_file is invalid when tls_enable is false")
}
if cfg.TLSTrustedCaFile != "" {
fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
}
}
if !lo.Contains([]string{"tcp", "kcp", "quic", "websocket", "wss"}, cfg.Protocol) {
return fmt.Errorf("invalid protocol")
}
for _, f := range cfg.IncludeConfigFiles {
absDir, err := filepath.Abs(filepath.Dir(f))
if err != nil {
return fmt.Errorf("include: parse directory of %s failed: %v", f, absDir)
}
if _, err := os.Stat(absDir); os.IsNotExist(err) {
return fmt.Errorf("include: directory of %s not exist", f)
}
}
return nil
}
// Supported sources including: string(file path), []byte, Reader interface.
func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
f, err := ini.LoadSources(ini.LoadOptions{
@ -421,3 +343,74 @@ func copySection(source, target *ini.Section) {
_, _ = target.NewKey(key, value)
}
}
// GetDefaultClientConf returns a client configuration with default values.
func GetDefaultClientConf() ClientCommonConf {
return ClientCommonConf{
ClientConfig: legacyauth.GetDefaultClientConf(),
ServerAddr: "0.0.0.0",
ServerPort: 7000,
NatHoleSTUNServer: "stun.easyvoip.com:3478",
DialServerTimeout: 10,
DialServerKeepAlive: 7200,
HTTPProxy: os.Getenv("http_proxy"),
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
AdminAddr: "127.0.0.1",
PoolCount: 1,
TCPMux: true,
TCPMuxKeepaliveInterval: 60,
LoginFailExit: true,
Start: make([]string, 0),
Protocol: "tcp",
QUICKeepalivePeriod: 10,
QUICMaxIdleTimeout: 30,
QUICMaxIncomingStreams: 100000,
TLSEnable: true,
DisableCustomTLSFirstByte: true,
HeartbeatInterval: 30,
HeartbeatTimeout: 90,
Metas: make(map[string]string),
UDPPacketSize: 1500,
IncludeConfigFiles: make([]string, 0),
}
}
func (cfg *ClientCommonConf) Validate() error {
if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
}
}
if !cfg.TLSEnable {
if cfg.TLSCertFile != "" {
fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
}
if cfg.TLSKeyFile != "" {
fmt.Println("WARNING! tls_key_file is invalid when tls_enable is false")
}
if cfg.TLSTrustedCaFile != "" {
fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
}
}
if !lo.Contains([]string{"tcp", "kcp", "quic", "websocket", "wss"}, cfg.Protocol) {
return fmt.Errorf("invalid protocol")
}
for _, f := range cfg.IncludeConfigFiles {
absDir, err := filepath.Abs(filepath.Dir(f))
if err != nil {
return fmt.Errorf("include: parse directory of %s failed: %v", f, err)
}
if _, err := os.Stat(absDir); os.IsNotExist(err) {
return fmt.Errorf("include: directory of %s not exist", f)
}
}
return nil
}

View File

@ -0,0 +1,350 @@
// Copyright 2023 The frp Authors
//
// 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 legacy
import (
"strings"
"github.com/samber/lo"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConfig {
out := &v1.ClientCommonConfig{}
out.User = conf.User
out.Auth.Method = conf.ClientConfig.AuthenticationMethod
out.Auth.Token = conf.ClientConfig.Token
if conf.ClientConfig.AuthenticateHeartBeats {
out.Auth.AdditionalAuthScopes = append(out.Auth.AdditionalAuthScopes, v1.AuthScopeHeartBeats)
}
if conf.ClientConfig.AuthenticateNewWorkConns {
out.Auth.AdditionalAuthScopes = append(out.Auth.AdditionalAuthScopes, v1.AuthScopeNewWorkConns)
}
out.Auth.OIDC.ClientID = conf.ClientConfig.OidcClientID
out.Auth.OIDC.ClientSecret = conf.ClientConfig.OidcClientSecret
out.Auth.OIDC.Audience = conf.ClientConfig.OidcAudience
out.Auth.OIDC.Scope = conf.ClientConfig.OidcScope
out.Auth.OIDC.TokenEndpointURL = conf.ClientConfig.OidcTokenEndpointURL
out.Auth.OIDC.AdditionalEndpointParams = conf.ClientConfig.OidcAdditionalEndpointParams
out.ServerAddr = conf.ServerAddr
out.ServerPort = conf.ServerPort
out.NatHoleSTUNServer = conf.NatHoleSTUNServer
out.Transport.DialServerTimeout = conf.DialServerTimeout
out.Transport.DialServerKeepAlive = conf.DialServerKeepAlive
out.Transport.ConnectServerLocalIP = conf.ConnectServerLocalIP
out.Transport.ProxyURL = conf.HTTPProxy
out.Transport.PoolCount = conf.PoolCount
out.Transport.TCPMux = lo.ToPtr(conf.TCPMux)
out.Transport.TCPMuxKeepaliveInterval = conf.TCPMuxKeepaliveInterval
out.Transport.Protocol = conf.Protocol
out.Transport.HeartbeatInterval = conf.HeartbeatInterval
out.Transport.HeartbeatTimeout = conf.HeartbeatTimeout
out.Transport.QUIC.KeepalivePeriod = conf.QUICKeepalivePeriod
out.Transport.QUIC.MaxIdleTimeout = conf.QUICMaxIdleTimeout
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
out.Transport.TLS.Enable = lo.ToPtr(conf.TLSEnable)
out.Transport.TLS.DisableCustomTLSFirstByte = lo.ToPtr(conf.DisableCustomTLSFirstByte)
out.Transport.TLS.TLSConfig.CertFile = conf.TLSCertFile
out.Transport.TLS.TLSConfig.KeyFile = conf.TLSKeyFile
out.Transport.TLS.TLSConfig.TrustedCaFile = conf.TLSTrustedCaFile
out.Transport.TLS.TLSConfig.ServerName = conf.TLSServerName
out.Log.To = conf.LogFile
out.Log.Level = conf.LogLevel
out.Log.MaxDays = conf.LogMaxDays
out.Log.DisablePrintColor = conf.DisableLogColor
out.WebServer.Addr = conf.AdminAddr
out.WebServer.Port = conf.AdminPort
out.WebServer.Password = conf.AdminPwd
out.WebServer.AssetsDir = conf.AssetsDir
out.WebServer.PprofEnable = conf.PprofEnable
out.DNSServer = conf.DNSServer
out.LoginFailExit = lo.ToPtr(conf.LoginFailExit)
out.Start = conf.Start
out.UDPPacketSize = conf.UDPPacketSize
out.Metadatas = conf.Metas
out.IncludeConfigFiles = conf.IncludeConfigFiles
return out
}
func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig {
out := &v1.ServerConfig{}
out.Auth.Method = conf.ServerConfig.AuthenticationMethod
out.Auth.Token = conf.ServerConfig.Token
if conf.ServerConfig.AuthenticateHeartBeats {
out.Auth.AdditionalAuthScopes = append(out.Auth.AdditionalAuthScopes, v1.AuthScopeHeartBeats)
}
if conf.ServerConfig.AuthenticateNewWorkConns {
out.Auth.AdditionalAuthScopes = append(out.Auth.AdditionalAuthScopes, v1.AuthScopeNewWorkConns)
}
out.Auth.OIDC.Audience = conf.ServerConfig.OidcAudience
out.Auth.OIDC.Issuer = conf.ServerConfig.OidcIssuer
out.Auth.OIDC.SkipExpiryCheck = conf.ServerConfig.OidcSkipExpiryCheck
out.Auth.OIDC.SkipIssuerCheck = conf.ServerConfig.OidcSkipIssuerCheck
out.BindAddr = conf.BindAddr
out.BindPort = conf.BindPort
out.KCPBindPort = conf.KCPBindPort
out.QUICBindPort = conf.QUICBindPort
out.Transport.QUIC.KeepalivePeriod = conf.QUICKeepalivePeriod
out.Transport.QUIC.MaxIdleTimeout = conf.QUICMaxIdleTimeout
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
out.ProxyBindAddr = conf.ProxyBindAddr
out.VhostHTTPPort = conf.VhostHTTPPort
out.VhostHTTPSPort = conf.VhostHTTPSPort
out.TCPMuxHTTPConnectPort = conf.TCPMuxHTTPConnectPort
out.TCPMuxPassthrough = conf.TCPMuxPassthrough
out.VhostHTTPTimeout = conf.VhostHTTPTimeout
out.WebServer.Addr = conf.DashboardAddr
out.WebServer.Port = conf.DashboardPort
out.WebServer.User = conf.DashboardUser
out.WebServer.Password = conf.DashboardPwd
out.WebServer.AssetsDir = conf.AssetsDir
if conf.DashboardTLSMode {
out.WebServer.TLS = &v1.TLSConfig{}
out.WebServer.TLS.CertFile = conf.DashboardTLSCertFile
out.WebServer.TLS.KeyFile = conf.DashboardTLSKeyFile
out.WebServer.PprofEnable = conf.PprofEnable
}
out.EnablePrometheus = conf.EnablePrometheus
out.Log.To = conf.LogFile
out.Log.Level = conf.LogLevel
out.Log.MaxDays = conf.LogMaxDays
out.Log.DisablePrintColor = conf.DisableLogColor
out.DetailedErrorsToClient = lo.ToPtr(conf.DetailedErrorsToClient)
out.SubDomainHost = conf.SubDomainHost
out.Custom404Page = conf.Custom404Page
out.UserConnTimeout = conf.UserConnTimeout
out.UDPPacketSize = conf.UDPPacketSize
out.NatHoleAnalysisDataReserveHours = conf.NatHoleAnalysisDataReserveHours
out.Transport.TCPMux = lo.ToPtr(conf.TCPMux)
out.Transport.TCPMuxKeepaliveInterval = conf.TCPMuxKeepaliveInterval
out.Transport.TCPKeepAlive = conf.TCPKeepAlive
out.Transport.MaxPoolCount = conf.MaxPoolCount
out.Transport.HeartbeatTimeout = conf.HeartbeatTimeout
out.MaxPortsPerClient = conf.MaxPortsPerClient
out.TLS.Force = conf.TLSOnly
out.TLS.CertFile = conf.TLSCertFile
out.TLS.KeyFile = conf.TLSKeyFile
out.TLS.TrustedCaFile = conf.TLSTrustedCaFile
for _, v := range conf.HTTPPlugins {
out.HTTPPlugins = append(out.HTTPPlugins, v1.HTTPPluginOptions{
Name: v.Name,
Addr: v.Addr,
Path: v.Path,
Ops: v.Ops,
TLSVerify: v.TLSVerify,
})
}
out.AllowPorts, _ = types.NewPortsRangeSliceFromString(conf.AllowPortsStr)
return out
}
func transformHeadersFromPluginParams(params map[string]string) v1.HeaderOperations {
out := v1.HeaderOperations{}
for k, v := range params {
if !strings.HasPrefix(k, "plugin_header_") {
continue
}
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
out.Set[k] = v
}
}
return out
}
func Convert_ProxyConf_To_v1_Base(conf ProxyConf) *v1.ProxyBaseConfig {
out := &v1.ProxyBaseConfig{}
base := conf.GetBaseConfig()
out.Name = base.ProxyName
out.Type = base.ProxyType
out.Metadatas = base.Metas
out.Transport.UseEncryption = base.UseEncryption
out.Transport.UseCompression = base.UseCompression
out.Transport.BandwidthLimit = base.BandwidthLimit
out.Transport.BandwidthLimitMode = base.BandwidthLimitMode
out.Transport.ProxyProtocolVersion = base.ProxyProtocolVersion
out.LoadBalancer.Group = base.Group
out.LoadBalancer.GroupKey = base.GroupKey
out.HealthCheck.Type = base.HealthCheckType
out.HealthCheck.TimeoutSeconds = base.HealthCheckTimeoutS
out.HealthCheck.MaxFailed = base.HealthCheckMaxFailed
out.HealthCheck.IntervalSeconds = base.HealthCheckIntervalS
out.HealthCheck.Path = base.HealthCheckURL
out.LocalIP = base.LocalIP
out.LocalPort = base.LocalPort
switch base.Plugin {
case "http2https":
out.Plugin.ClientPluginOptions = &v1.HTTP2HTTPSPluginOptions{
LocalAddr: base.PluginParams["plugin_local_addr"],
HostHeaderRewrite: base.PluginParams["plugin_host_header_rewrite"],
RequestHeaders: transformHeadersFromPluginParams(base.PluginParams),
}
case "http_proxy":
out.Plugin.ClientPluginOptions = &v1.HTTPProxyPluginOptions{
HTTPUser: base.PluginParams["plugin_http_user"],
HTTPPassword: base.PluginParams["plugin_http_passwd"],
}
case "https2http":
out.Plugin.ClientPluginOptions = &v1.HTTPS2HTTPPluginOptions{
LocalAddr: base.PluginParams["plugin_local_addr"],
HostHeaderRewrite: base.PluginParams["plugin_host_header_rewrite"],
RequestHeaders: transformHeadersFromPluginParams(base.PluginParams),
CrtPath: base.PluginParams["plugin_crt_path"],
KeyPath: base.PluginParams["plugin_key_path"],
}
case "https2https":
out.Plugin.ClientPluginOptions = &v1.HTTPS2HTTPSPluginOptions{
LocalAddr: base.PluginParams["plugin_local_addr"],
HostHeaderRewrite: base.PluginParams["plugin_host_header_rewrite"],
RequestHeaders: transformHeadersFromPluginParams(base.PluginParams),
CrtPath: base.PluginParams["plugin_crt_path"],
KeyPath: base.PluginParams["plugin_key_path"],
}
case "socks5":
out.Plugin.ClientPluginOptions = &v1.Socks5PluginOptions{
Username: base.PluginParams["plugin_user"],
Password: base.PluginParams["plugin_passwd"],
}
case "static_file":
out.Plugin.ClientPluginOptions = &v1.StaticFilePluginOptions{
LocalPath: base.PluginParams["plugin_local_path"],
StripPrefix: base.PluginParams["plugin_strip_prefix"],
HTTPUser: base.PluginParams["plugin_http_user"],
HTTPPassword: base.PluginParams["plugin_http_passwd"],
}
case "unix_domain_socket":
out.Plugin.ClientPluginOptions = &v1.UnixDomainSocketPluginOptions{
UnixPath: base.PluginParams["plugin_unix_path"],
}
}
out.Plugin.Type = base.Plugin
return out
}
func Convert_ProxyConf_To_v1(conf ProxyConf) v1.ProxyConfigurer {
outBase := Convert_ProxyConf_To_v1_Base(conf)
var out v1.ProxyConfigurer
switch v := conf.(type) {
case *TCPProxyConf:
c := &v1.TCPProxyConfig{ProxyBaseConfig: *outBase}
c.RemotePort = v.RemotePort
out = c
case *UDPProxyConf:
c := &v1.UDPProxyConfig{ProxyBaseConfig: *outBase}
c.RemotePort = v.RemotePort
out = c
case *HTTPProxyConf:
c := &v1.HTTPProxyConfig{ProxyBaseConfig: *outBase}
c.CustomDomains = v.CustomDomains
c.SubDomain = v.SubDomain
c.Locations = v.Locations
c.HTTPUser = v.HTTPUser
c.HTTPPassword = v.HTTPPwd
c.HostHeaderRewrite = v.HostHeaderRewrite
c.RequestHeaders.Set = v.Headers
c.RouteByHTTPUser = v.RouteByHTTPUser
out = c
case *HTTPSProxyConf:
c := &v1.HTTPSProxyConfig{ProxyBaseConfig: *outBase}
c.CustomDomains = v.CustomDomains
c.SubDomain = v.SubDomain
out = c
case *TCPMuxProxyConf:
c := &v1.TCPMuxProxyConfig{ProxyBaseConfig: *outBase}
c.CustomDomains = v.CustomDomains
c.SubDomain = v.SubDomain
c.HTTPUser = v.HTTPUser
c.HTTPPassword = v.HTTPPwd
c.RouteByHTTPUser = v.RouteByHTTPUser
c.Multiplexer = v.Multiplexer
out = c
case *STCPProxyConf:
c := &v1.STCPProxyConfig{ProxyBaseConfig: *outBase}
c.Secretkey = v.Sk
c.AllowUsers = v.AllowUsers
out = c
case *SUDPProxyConf:
c := &v1.SUDPProxyConfig{ProxyBaseConfig: *outBase}
c.Secretkey = v.Sk
c.AllowUsers = v.AllowUsers
out = c
case *XTCPProxyConf:
c := &v1.XTCPProxyConfig{ProxyBaseConfig: *outBase}
c.Secretkey = v.Sk
c.AllowUsers = v.AllowUsers
}
return out
}
func Convert_VisitorConf_To_v1_Base(conf VisitorConf) *v1.VisitorBaseConfig {
out := &v1.VisitorBaseConfig{}
base := conf.GetBaseConfig()
out.Name = base.ProxyName
out.Type = base.ProxyType
out.Transport.UseEncryption = base.UseEncryption
out.Transport.UseCompression = base.UseCompression
out.SecretKey = base.Sk
out.ServerUser = base.ServerUser
out.ServerName = base.ServerName
out.BindAddr = base.BindAddr
out.BindPort = base.BindPort
return out
}
func Convert_VisitorConf_To_v1(conf VisitorConf) v1.VisitorConfigurer {
outBase := Convert_VisitorConf_To_v1_Base(conf)
var out v1.VisitorConfigurer
switch v := conf.(type) {
case *STCPVisitorConf:
c := &v1.STCPVisitorConfig{VisitorBaseConfig: *outBase}
out = c
case *SUDPVisitorConf:
c := &v1.SUDPVisitorConfig{VisitorBaseConfig: *outBase}
out = c
case *XTCPVisitorConf:
c := &v1.XTCPVisitorConfig{VisitorBaseConfig: *outBase}
c.Protocol = v.Protocol
c.KeepTunnelOpen = v.KeepTunnelOpen
c.MaxRetriesAnHour = v.MaxRetriesAnHour
c.MinRetryInterval = v.MinRetryInterval
c.FallbackTo = v.FallbackTo
c.FallbackTimeoutMs = v.FallbackTimeoutMs
out = c
}
return out
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package config
package legacy
import (
"bytes"
@ -40,7 +40,6 @@ func ParseClientConfig(filePath string) (
if err != nil {
return
}
cfg.Complete()
if err = cfg.Validate(); err != nil {
err = fmt.Errorf("parse config error: %v", err)
return

375
pkg/config/legacy/proxy.go Normal file
View File

@ -0,0 +1,375 @@
// Copyright 2023 The frp Authors
//
// 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 legacy
import (
"fmt"
"reflect"
"gopkg.in/ini.v1"
"github.com/fatedier/frp/pkg/config/types"
"github.com/fatedier/frp/pkg/consts"
)
// Proxy
var (
proxyConfTypeMap = map[string]reflect.Type{
consts.TCPProxy: reflect.TypeOf(TCPProxyConf{}),
consts.TCPMuxProxy: reflect.TypeOf(TCPMuxProxyConf{}),
consts.UDPProxy: reflect.TypeOf(UDPProxyConf{}),
consts.HTTPProxy: reflect.TypeOf(HTTPProxyConf{}),
consts.HTTPSProxy: reflect.TypeOf(HTTPSProxyConf{}),
consts.STCPProxy: reflect.TypeOf(STCPProxyConf{}),
consts.XTCPProxy: reflect.TypeOf(XTCPProxyConf{}),
consts.SUDPProxy: reflect.TypeOf(SUDPProxyConf{}),
}
)
type ProxyConf interface {
// GetBaseConfig returns the BaseProxyConf for this config.
GetBaseConfig() *BaseProxyConf
// UnmarshalFromIni unmarshals a ini.Section into this config. This function
// will be called on the frpc side.
UnmarshalFromIni(string, string, *ini.Section) error
}
func NewConfByType(proxyType string) ProxyConf {
v, ok := proxyConfTypeMap[proxyType]
if !ok {
return nil
}
cfg := reflect.New(v).Interface().(ProxyConf)
return cfg
}
// Proxy Conf Loader
// DefaultProxyConf creates a empty ProxyConf object by proxyType.
// If proxyType doesn't exist, return nil.
func DefaultProxyConf(proxyType string) ProxyConf {
return NewConfByType(proxyType)
}
// Proxy loaded from ini
func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf, error) {
// section.Key: if key not exists, section will set it with default value.
proxyType := section.Key("type").String()
if proxyType == "" {
proxyType = consts.TCPProxy
}
conf := DefaultProxyConf(proxyType)
if conf == nil {
return nil, fmt.Errorf("invalid type [%s]", proxyType)
}
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
return nil, err
}
return conf, nil
}
// LocalSvrConf configures what location the client will to, or what
// plugin will be used.
type LocalSvrConf struct {
// LocalIP specifies the IP address or host name to to.
LocalIP string `ini:"local_ip" json:"local_ip"`
// LocalPort specifies the port to to.
LocalPort int `ini:"local_port" json:"local_port"`
// Plugin specifies what plugin should be used for ng. If this value
// is set, the LocalIp and LocalPort values will be ignored. By default,
// this value is "".
Plugin string `ini:"plugin" 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 `ini:"-"`
}
// HealthCheckConf configures health checking. This can be useful for load
// balancing purposes to detect and remove proxies to failing services.
type HealthCheckConf struct {
// 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 `ini:"health_check_type" 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 `ini:"health_check_timeout_s" json:"health_check_timeout_s"`
// HealthCheckMaxFailed specifies the number of allowed failures before the
// is stopped. By default, this value is 1.
HealthCheckMaxFailed int `ini:"health_check_max_failed" json:"health_check_max_failed"`
// HealthCheckIntervalS specifies the time in seconds between health
// checks. By default, this value is 10.
HealthCheckIntervalS int `ini:"health_check_interval_s" json:"health_check_interval_s"`
// HealthCheckURL specifies the address to send health checks to if the
// health check type is "http".
HealthCheckURL string `ini:"health_check_url" json:"health_check_url"`
// HealthCheckAddr specifies the address to connect to if the health check
// type is "tcp".
HealthCheckAddr string `ini:"-"`
}
// BaseProxyConf provides configuration info that is common to all types.
type BaseProxyConf struct {
// ProxyName is the name of this
ProxyName string `ini:"name" json:"name"`
// ProxyType specifies the type of this Valid values include "tcp",
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
// "tcp".
ProxyType string `ini:"type" json:"type"`
// 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 `ini:"use_encryption" json:"use_encryption"`
// UseCompression controls whether or not communication with the server
// will be compressed. By default, this value is false.
UseCompression bool `ini:"use_compression" json:"use_compression"`
// Group specifies which group the is a part of. The server will use
// this information to load balance proxies in the same group. If the value
// is "", this will not be in a group. By default, this value is "".
Group string `ini:"group" 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 `ini:"group_key" json:"group_key"`
// 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 `ini:"proxy_protocol_version" json:"proxy_protocol_version"`
// BandwidthLimit limit the bandwidth
// 0 means no limit
BandwidthLimit types.BandwidthQuantity `ini:"bandwidth_limit" json:"bandwidth_limit"`
// BandwidthLimitMode specifies whether to limit the bandwidth on the
// client or server side. Valid values include "client" and "server".
// By default, this value is "client".
BandwidthLimitMode string `ini:"bandwidth_limit_mode" json:"bandwidth_limit_mode"`
// meta info for each proxy
Metas map[string]string `ini:"-" json:"metas"`
LocalSvrConf `ini:",extends"`
HealthCheckConf `ini:",extends"`
}
// Base
func (cfg *BaseProxyConf) GetBaseConfig() *BaseProxyConf {
return cfg
}
// BaseProxyConf apply custom logic changes.
func (cfg *BaseProxyConf) decorate(_ string, name string, section *ini.Section) error {
cfg.ProxyName = name
// metas_xxx
cfg.Metas = GetMapWithoutPrefix(section.KeysHash(), "meta_")
// bandwidth_limit
if bandwidth, err := section.GetKey("bandwidth_limit"); err == nil {
cfg.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidth.String())
if err != nil {
return err
}
}
// plugin_xxx
cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
return nil
}
type DomainConf struct {
CustomDomains []string `ini:"custom_domains" json:"custom_domains"`
SubDomain string `ini:"subdomain" json:"subdomain"`
}
type RoleServerCommonConf struct {
Role string `ini:"role" json:"role"`
Sk string `ini:"sk" json:"sk"`
AllowUsers []string `ini:"allow_users" json:"allow_users"`
}
// HTTP
type HTTPProxyConf struct {
BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends"`
Locations []string `ini:"locations" json:"locations"`
HTTPUser string `ini:"http_user" json:"http_user"`
HTTPPwd string `ini:"http_pwd" json:"http_pwd"`
HostHeaderRewrite string `ini:"host_header_rewrite" json:"host_header_rewrite"`
Headers map[string]string `ini:"-" json:"headers"`
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
}
func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
cfg.Headers = GetMapWithoutPrefix(section.KeysHash(), "header_")
return nil
}
// HTTPS
type HTTPSProxyConf struct {
BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends"`
}
func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
return nil
}
// TCP
type TCPProxyConf struct {
BaseProxyConf `ini:",extends"`
RemotePort int `ini:"remote_port" json:"remote_port"`
}
func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
return nil
}
// UDP
type UDPProxyConf struct {
BaseProxyConf `ini:",extends"`
RemotePort int `ini:"remote_port" json:"remote_port"`
}
func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
return nil
}
// TCPMux
type TCPMuxProxyConf struct {
BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends"`
HTTPUser string `ini:"http_user" json:"http_user,omitempty"`
HTTPPwd string `ini:"http_pwd" json:"http_pwd,omitempty"`
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
Multiplexer string `ini:"multiplexer"`
}
func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
return nil
}
// STCP
type STCPProxyConf struct {
BaseProxyConf `ini:",extends"`
RoleServerCommonConf `ini:",extends"`
}
func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
if cfg.Role == "" {
cfg.Role = "server"
}
return nil
}
// XTCP
type XTCPProxyConf struct {
BaseProxyConf `ini:",extends"`
RoleServerCommonConf `ini:",extends"`
}
func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
if cfg.Role == "" {
cfg.Role = "server"
}
return nil
}
// SUDP
type SUDPProxyConf struct {
BaseProxyConf `ini:",extends"`
RoleServerCommonConf `ini:",extends"`
}
func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
return nil
}
func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, section *ini.Section) error {
err := section.MapTo(cfg)
if err != nil {
return err
}
err = cfg.GetBaseConfig().decorate(prefix, name, section)
if err != nil {
return err
}
return nil
}

View File

@ -1,4 +1,4 @@
// Copyright 2020 The frp Authors
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,25 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package config
package legacy
import (
"fmt"
"strings"
"github.com/go-playground/validator/v10"
"gopkg.in/ini.v1"
"github.com/fatedier/frp/pkg/auth"
plugin "github.com/fatedier/frp/pkg/plugin/server"
"github.com/fatedier/frp/pkg/util/util"
legacyauth "github.com/fatedier/frp/pkg/auth/legacy"
)
type HTTPPluginOptions struct {
Name string `ini:"name"`
Addr string `ini:"addr"`
Path string `ini:"path"`
Ops []string `ini:"ops"`
TLSVerify bool `ini:"tls_verify"`
}
// 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 {
auth.ServerConfig `ini:",extends"`
legacyauth.ServerConfig `ini:",extends"`
// BindAddr specifies the address that the server binds to. By default,
// this value is "0.0.0.0".
@ -185,7 +189,7 @@ type ServerCommonConf struct {
// connection. By default, this value is 10.
UserConnTimeout int64 `ini:"user_conn_timeout" json:"user_conn_timeout"`
// HTTPPlugins specify the server plugins support HTTP protocol.
HTTPPlugins map[string]plugin.HTTPPluginOptions `ini:"-" json:"http_plugins"`
HTTPPlugins map[string]HTTPPluginOptions `ini:"-" json:"http_plugins"`
// UDPPacketSize specifies the UDP packet size
// By default, this value is 1500
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
@ -200,7 +204,7 @@ type ServerCommonConf struct {
// defaults.
func GetDefaultServerConf() ServerCommonConf {
return ServerCommonConf{
ServerConfig: auth.GetDefaultServerConf(),
ServerConfig: legacyauth.GetDefaultServerConf(),
BindAddr: "0.0.0.0",
BindPort: 7000,
QUICKeepalivePeriod: 10,
@ -221,7 +225,7 @@ func GetDefaultServerConf() ServerCommonConf {
MaxPortsPerClient: 0,
HeartbeatTimeout: 90,
UserConnTimeout: 10,
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
HTTPPlugins: make(map[string]HTTPPluginOptions),
UDPPacketSize: 1500,
NatHoleAnalysisDataReserveHours: 7 * 24,
}
@ -253,18 +257,11 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
// allow_ports
allowPortStr := s.Key("allow_ports").String()
if allowPortStr != "" {
allowPorts, err := util.ParseRangeNumbers(allowPortStr)
if err != nil {
return ServerCommonConf{}, fmt.Errorf("invalid allow_ports: %v", err)
}
for _, port := range allowPorts {
common.AllowPorts[int(port)] = struct{}{}
}
common.AllowPortsStr = allowPortStr
}
// plugin.xxx
pluginOpts := make(map[string]plugin.HTTPPluginOptions)
pluginOpts := make(map[string]HTTPPluginOptions)
for _, section := range f.Sections() {
name := section.Name()
if !strings.HasPrefix(name, "plugin.") {
@ -283,47 +280,10 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
return common, nil
}
func (cfg *ServerCommonConf) Complete() {
if cfg.LogFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
if cfg.ProxyBindAddr == "" {
cfg.ProxyBindAddr = cfg.BindAddr
}
if cfg.TLSTrustedCaFile != "" {
cfg.TLSOnly = true
}
}
func (cfg *ServerCommonConf) Validate() error {
if !cfg.DashboardTLSMode {
if cfg.DashboardTLSCertFile != "" {
fmt.Println("WARNING! dashboard_tls_cert_file is invalid when dashboard_tls_mode is false")
}
if cfg.DashboardTLSKeyFile != "" {
fmt.Println("WARNING! dashboard_tls_key_file is invalid when dashboard_tls_mode is false")
}
} else {
if cfg.DashboardTLSCertFile == "" {
return fmt.Errorf("ERROR! dashboard_tls_cert_file must be specified when dashboard_tls_mode is true")
}
if cfg.DashboardTLSKeyFile == "" {
return fmt.Errorf("ERROR! dashboard_tls_cert_file must be specified when dashboard_tls_mode is true")
}
}
return validator.New().Struct(cfg)
}
func loadHTTPPluginOpt(section *ini.Section) (*plugin.HTTPPluginOptions, error) {
func loadHTTPPluginOpt(section *ini.Section) (*HTTPPluginOptions, error) {
name := strings.TrimSpace(strings.TrimPrefix(section.Name(), "plugin."))
opt := new(plugin.HTTPPluginOptions)
opt := &HTTPPluginOptions{}
err := section.MapTo(opt)
if err != nil {
return nil, err

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package config
package legacy
import (
"strings"

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package config
package legacy
import (
"bytes"

View File

@ -1,4 +1,4 @@
// Copyright 2018 fatedier, fatedier@gmail.com
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package config
package legacy
import (
"fmt"
"reflect"
"github.com/samber/lo"
"gopkg.in/ini.v1"
"github.com/fatedier/frp/pkg/consts"
@ -38,8 +37,16 @@ type VisitorConf interface {
GetBaseConfig() *BaseVisitorConf
// UnmarshalFromIni unmarshals config from ini.
UnmarshalFromIni(prefix string, name string, section *ini.Section) error
// Validate validates config.
Validate() error
}
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
// If visitorType doesn't exist, return nil.
func DefaultVisitorConf(visitorType string) VisitorConf {
v, ok := visitorConfTypeMap[visitorType]
if !ok {
return nil
}
return reflect.New(v).Interface().(VisitorConf)
}
type BaseVisitorConf struct {
@ -59,96 +66,14 @@ type BaseVisitorConf struct {
BindPort int `ini:"bind_port" json:"bind_port"`
}
type SUDPVisitorConf struct {
BaseVisitorConf `ini:",extends"`
}
type STCPVisitorConf struct {
BaseVisitorConf `ini:",extends"`
}
type XTCPVisitorConf struct {
BaseVisitorConf `ini:",extends"`
Protocol string `ini:"protocol" json:"protocol,omitempty"`
KeepTunnelOpen bool `ini:"keep_tunnel_open" json:"keep_tunnel_open,omitempty"`
MaxRetriesAnHour int `ini:"max_retries_an_hour" json:"max_retries_an_hour,omitempty"`
MinRetryInterval int `ini:"min_retry_interval" json:"min_retry_interval,omitempty"`
FallbackTo string `ini:"fallback_to" json:"fallback_to,omitempty"`
FallbackTimeoutMs int `ini:"fallback_timeout_ms" json:"fallback_timeout_ms,omitempty"`
}
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
// If visitorType doesn't exist, return nil.
func DefaultVisitorConf(visitorType string) VisitorConf {
v, ok := visitorConfTypeMap[visitorType]
if !ok {
return nil
}
return reflect.New(v).Interface().(VisitorConf)
}
// Visitor loaded from ini
func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (VisitorConf, error) {
// section.Key: if key not exists, section will set it with default value.
visitorType := section.Key("type").String()
if visitorType == "" {
return nil, fmt.Errorf("type shouldn't be empty")
}
conf := DefaultVisitorConf(visitorType)
if conf == nil {
return nil, fmt.Errorf("type [%s] error", visitorType)
}
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
return nil, fmt.Errorf("type [%s] error", visitorType)
}
if err := conf.Validate(); err != nil {
return nil, err
}
return conf, nil
}
// Base
func (cfg *BaseVisitorConf) GetBaseConfig() *BaseVisitorConf {
return cfg
}
func (cfg *BaseVisitorConf) validate() (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
}
// BindPort can be less than 0, it means don't bind to the port and only receive connections redirected from
// other visitors
if cfg.BindPort == 0 {
err = fmt.Errorf("bind_port is required")
return
}
return
}
func (cfg *BaseVisitorConf) unmarshalFromIni(prefix string, name string, section *ini.Section) error {
_ = section
func (cfg *BaseVisitorConf) unmarshalFromIni(_ string, name string, _ *ini.Section) error {
// Custom decoration after basic unmarshal:
// proxy name
cfg.ProxyName = prefix + name
// server_name
if cfg.ServerUser == "" {
cfg.ServerName = prefix + cfg.ServerName
} else {
cfg.ServerName = cfg.ServerUser + "." + cfg.ServerName
}
cfg.ProxyName = name
// bind_addr
if cfg.BindAddr == "" {
@ -170,8 +95,9 @@ func preVisitorUnmarshalFromIni(cfg VisitorConf, prefix string, name string, sec
return nil
}
// SUDP
var _ VisitorConf = &SUDPVisitorConf{}
type SUDPVisitorConf struct {
BaseVisitorConf `ini:",extends"`
}
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
@ -184,19 +110,10 @@ func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section
return
}
func (cfg *SUDPVisitorConf) Validate() (err error) {
if err = cfg.BaseVisitorConf.validate(); err != nil {
return
}
// Add custom logic validate, if exists
return
type STCPVisitorConf struct {
BaseVisitorConf `ini:",extends"`
}
// STCP
var _ VisitorConf = &STCPVisitorConf{}
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
@ -208,19 +125,17 @@ func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
return
}
func (cfg *STCPVisitorConf) Validate() (err error) {
if err = cfg.BaseVisitorConf.validate(); err != nil {
return
}
type XTCPVisitorConf struct {
BaseVisitorConf `ini:",extends"`
// Add custom logic validate, if exists
return
Protocol string `ini:"protocol" json:"protocol,omitempty"`
KeepTunnelOpen bool `ini:"keep_tunnel_open" json:"keep_tunnel_open,omitempty"`
MaxRetriesAnHour int `ini:"max_retries_an_hour" json:"max_retries_an_hour,omitempty"`
MinRetryInterval int `ini:"min_retry_interval" json:"min_retry_interval,omitempty"`
FallbackTo string `ini:"fallback_to" json:"fallback_to,omitempty"`
FallbackTimeoutMs int `ini:"fallback_timeout_ms" json:"fallback_timeout_ms,omitempty"`
}
// XTCP
var _ VisitorConf = &XTCPVisitorConf{}
func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
@ -243,14 +158,22 @@ func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
return
}
func (cfg *XTCPVisitorConf) Validate() (err error) {
if err = cfg.BaseVisitorConf.validate(); err != nil {
return
// Visitor loaded from ini
func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (VisitorConf, error) {
// section.Key: if key not exists, section will set it with default value.
visitorType := section.Key("type").String()
if visitorType == "" {
return nil, fmt.Errorf("type shouldn't be empty")
}
// Add custom logic validate, if exists
if !lo.Contains([]string{"", "kcp", "quic"}, cfg.Protocol) {
return fmt.Errorf("protocol should be 'kcp' or 'quic'")
conf := DefaultVisitorConf(visitorType)
if conf == nil {
return nil, fmt.Errorf("type [%s] error", visitorType)
}
return
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
return nil, fmt.Errorf("type [%s] error", visitorType)
}
return conf, nil
}

283
pkg/config/load.go Normal file
View File

@ -0,0 +1,283 @@
// Copyright 2023 The frp Authors
//
// 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 (
"bytes"
"encoding/json"
"fmt"
"html/template"
"os"
"path/filepath"
"strings"
"github.com/BurntSushi/toml"
"github.com/samber/lo"
"gopkg.in/ini.v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/yaml"
"github.com/fatedier/frp/pkg/config/legacy"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/consts"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/util/util"
)
var glbEnvs map[string]string
func init() {
glbEnvs = make(map[string]string)
envs := os.Environ()
for _, env := range envs {
pair := strings.SplitN(env, "=", 2)
if len(pair) != 2 {
continue
}
glbEnvs[pair[0]] = pair[1]
}
}
type Values struct {
Envs map[string]string // environment vars
}
func GetValues() *Values {
return &Values{
Envs: glbEnvs,
}
}
func DetectLegacyINIFormat(content []byte) bool {
f, err := ini.Load(content)
if err != nil {
return false
}
if _, err := f.GetSection("common"); err == nil {
return true
}
return false
}
func DetectLegacyINIFormatFromFile(path string) bool {
b, err := os.ReadFile(path)
if err != nil {
return false
}
return DetectLegacyINIFormat(b)
}
func RenderWithTemplate(in []byte, values *Values) ([]byte, error) {
tmpl, err := template.New("frp").Parse(string(in))
if err != nil {
return nil, err
}
buffer := bytes.NewBufferString("")
if err := tmpl.Execute(buffer, values); err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func LoadFileContentWithTemplate(path string, values *Values) ([]byte, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return RenderWithTemplate(b, values)
}
func LoadConfigureFromFile(path string, c any) error {
content, err := LoadFileContentWithTemplate(path, GetValues())
if err != nil {
return err
}
return LoadConfigure(content, c)
}
// LoadConfigure loads configuration from bytes and unmarshal into c.
// Now it supports json, yaml and toml format.
func LoadConfigure(b []byte, c any) error {
var tomlObj interface{}
if err := toml.Unmarshal(b, &tomlObj); err == nil {
b, err = json.Marshal(&tomlObj)
if err != nil {
return err
}
}
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(b), 4096)
return decoder.Decode(c)
}
func NewProxyConfigurerFromMsg(m *msg.NewProxy, serverCfg *v1.ServerConfig) (v1.ProxyConfigurer, error) {
m.ProxyType = util.EmptyOr(m.ProxyType, consts.TCPProxy)
configurer := v1.NewProxyConfigurerByType(m.ProxyType)
if configurer == nil {
return nil, fmt.Errorf("unknown proxy type: %s", m.ProxyType)
}
configurer.UnmarshalFromMsg(m)
configurer.Complete("")
if err := validation.ValidateProxyConfigurerForServer(configurer, serverCfg); err != nil {
return nil, err
}
return configurer, nil
}
func LoadServerConfig(path string) (*v1.ServerConfig, bool, error) {
var (
svrCfg *v1.ServerConfig
isLegacyFormat bool
)
// detect legacy ini format
if DetectLegacyINIFormatFromFile(path) {
content, err := legacy.GetRenderedConfFromFile(path)
if err != nil {
return nil, true, err
}
legacyCfg, err := legacy.UnmarshalServerConfFromIni(content)
if err != nil {
return nil, true, err
}
svrCfg = legacy.Convert_ServerCommonConf_To_v1(&legacyCfg)
isLegacyFormat = true
} else {
svrCfg = &v1.ServerConfig{}
if err := LoadConfigureFromFile(path, svrCfg); err != nil {
return nil, false, err
}
}
if svrCfg != nil {
svrCfg.Complete()
}
return svrCfg, isLegacyFormat, nil
}
func LoadClientConfig(path string) (
*v1.ClientCommonConfig,
[]v1.ProxyConfigurer,
[]v1.VisitorConfigurer,
bool, error,
) {
var (
cliCfg *v1.ClientCommonConfig
pxyCfgs = make([]v1.ProxyConfigurer, 0)
visitorCfgs = make([]v1.VisitorConfigurer, 0)
isLegacyFormat bool
)
if DetectLegacyINIFormatFromFile(path) {
legacyCommon, legacyPxyCfgs, legacyVisitorCfgs, err := legacy.ParseClientConfig(path)
if err != nil {
return nil, nil, nil, true, err
}
cliCfg = legacy.Convert_ClientCommonConf_To_v1(&legacyCommon)
for _, c := range legacyPxyCfgs {
pxyCfgs = append(pxyCfgs, legacy.Convert_ProxyConf_To_v1(c))
}
for _, c := range legacyVisitorCfgs {
visitorCfgs = append(visitorCfgs, legacy.Convert_VisitorConf_To_v1(c))
}
isLegacyFormat = true
} else {
allCfg := v1.ClientConfig{}
if err := LoadConfigureFromFile(path, &allCfg); err != nil {
return nil, nil, nil, false, err
}
cliCfg = &allCfg.ClientCommonConfig
for _, c := range allCfg.Proxies {
pxyCfgs = append(pxyCfgs, c.ProxyConfigurer)
}
for _, c := range allCfg.Visitors {
visitorCfgs = append(visitorCfgs, c.VisitorConfigurer)
}
}
// Load additional config from includes.
// legacy ini format alredy handle this in ParseClientConfig.
if len(cliCfg.IncludeConfigFiles) > 0 && !isLegacyFormat {
extPxyCfgs, extVisitorCfgs, err := LoadAdditionalClientConfigs(cliCfg.IncludeConfigFiles, isLegacyFormat)
if err != nil {
return nil, nil, nil, isLegacyFormat, err
}
pxyCfgs = append(pxyCfgs, extPxyCfgs...)
visitorCfgs = append(visitorCfgs, extVisitorCfgs...)
}
// Filter by start
if len(cliCfg.Start) > 0 {
startSet := sets.New(cliCfg.Start...)
pxyCfgs = lo.Filter(pxyCfgs, func(c v1.ProxyConfigurer, _ int) bool {
return startSet.Has(c.GetBaseConfig().Name)
})
visitorCfgs = lo.Filter(visitorCfgs, func(c v1.VisitorConfigurer, _ int) bool {
return startSet.Has(c.GetBaseConfig().Name)
})
}
if cliCfg != nil {
cliCfg.Complete()
}
for _, c := range pxyCfgs {
c.Complete(cliCfg.User)
}
for _, c := range visitorCfgs {
c.Complete(cliCfg)
}
return cliCfg, pxyCfgs, visitorCfgs, isLegacyFormat, nil
}
func LoadAdditionalClientConfigs(paths []string, isLegacyFormat bool) ([]v1.ProxyConfigurer, []v1.VisitorConfigurer, error) {
pxyCfgs := make([]v1.ProxyConfigurer, 0)
visitorCfgs := make([]v1.VisitorConfigurer, 0)
for _, path := range paths {
absDir, err := filepath.Abs(filepath.Dir(path))
if err != nil {
return nil, nil, err
}
if _, err := os.Stat(absDir); os.IsNotExist(err) {
return nil, nil, err
}
files, err := os.ReadDir(absDir)
if err != nil {
return nil, nil, err
}
for _, fi := range files {
if fi.IsDir() {
continue
}
absFile := filepath.Join(absDir, fi.Name())
if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched {
// support yaml/json/toml
cfg := v1.ClientConfig{}
if err := LoadConfigureFromFile(absFile, &cfg); err != nil {
return nil, nil, fmt.Errorf("load additional config from %s error: %v", absFile, err)
}
for _, c := range cfg.Proxies {
pxyCfgs = append(pxyCfgs, c.ProxyConfigurer)
}
for _, c := range cfg.Visitors {
visitorCfgs = append(visitorCfgs, c.VisitorConfigurer)
}
}
}
}
return pxyCfgs, visitorCfgs, nil
}

View File

@ -1,921 +0,0 @@
// Copyright 2016 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"
"net"
"reflect"
"strconv"
"strings"
"gopkg.in/ini.v1"
"github.com/fatedier/frp/pkg/consts"
"github.com/fatedier/frp/pkg/msg"
)
// Proxy
var (
proxyConfTypeMap = map[string]reflect.Type{
consts.TCPProxy: reflect.TypeOf(TCPProxyConf{}),
consts.TCPMuxProxy: reflect.TypeOf(TCPMuxProxyConf{}),
consts.UDPProxy: reflect.TypeOf(UDPProxyConf{}),
consts.HTTPProxy: reflect.TypeOf(HTTPProxyConf{}),
consts.HTTPSProxy: reflect.TypeOf(HTTPSProxyConf{}),
consts.STCPProxy: reflect.TypeOf(STCPProxyConf{}),
consts.XTCPProxy: reflect.TypeOf(XTCPProxyConf{}),
consts.SUDPProxy: reflect.TypeOf(SUDPProxyConf{}),
}
)
func NewConfByType(proxyType string) ProxyConf {
v, ok := proxyConfTypeMap[proxyType]
if !ok {
return nil
}
cfg := reflect.New(v).Interface().(ProxyConf)
return cfg
}
type ProxyConf interface {
// GetBaseConfig returns the BaseProxyConf for this config.
GetBaseConfig() *BaseProxyConf
// SetDefaultValues sets the default values for this config.
SetDefaultValues()
// UnmarshalFromMsg unmarshals a msg.NewProxy message into this config.
// This function will be called on the frps side.
UnmarshalFromMsg(*msg.NewProxy)
// UnmarshalFromIni unmarshals a ini.Section into this config. This function
// will be called on the frpc side.
UnmarshalFromIni(string, string, *ini.Section) error
// MarshalToMsg marshals this config into a msg.NewProxy message. This
// function will be called on the frpc side.
MarshalToMsg(*msg.NewProxy)
// ValidateForClient checks that the config is valid for the frpc side.
ValidateForClient() error
// ValidateForServer checks that the config is valid for the frps side.
ValidateForServer(ServerCommonConf) error
}
// LocalSvrConf configures what location the client will to, or what
// plugin will be used.
type LocalSvrConf struct {
// LocalIP specifies the IP address or host name to to.
LocalIP string `ini:"local_ip" json:"local_ip"`
// LocalPort specifies the port to to.
LocalPort int `ini:"local_port" json:"local_port"`
// Plugin specifies what plugin should be used for ng. If this value
// is set, the LocalIp and LocalPort values will be ignored. By default,
// this value is "".
Plugin string `ini:"plugin" 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 `ini:"-"`
}
// HealthCheckConf configures health checking. This can be useful for load
// balancing purposes to detect and remove proxies to failing services.
type HealthCheckConf struct {
// 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 `ini:"health_check_type" 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 `ini:"health_check_timeout_s" json:"health_check_timeout_s"`
// HealthCheckMaxFailed specifies the number of allowed failures before the
// is stopped. By default, this value is 1.
HealthCheckMaxFailed int `ini:"health_check_max_failed" json:"health_check_max_failed"`
// HealthCheckIntervalS specifies the time in seconds between health
// checks. By default, this value is 10.
HealthCheckIntervalS int `ini:"health_check_interval_s" json:"health_check_interval_s"`
// HealthCheckURL specifies the address to send health checks to if the
// health check type is "http".
HealthCheckURL string `ini:"health_check_url" json:"health_check_url"`
// HealthCheckAddr specifies the address to connect to if the health check
// type is "tcp".
HealthCheckAddr string `ini:"-"`
}
// BaseProxyConf provides configuration info that is common to all types.
type BaseProxyConf struct {
// ProxyName is the name of this
ProxyName string `ini:"name" json:"name"`
// ProxyType specifies the type of this Valid values include "tcp",
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
// "tcp".
ProxyType string `ini:"type" json:"type"`
// 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 `ini:"use_encryption" json:"use_encryption"`
// UseCompression controls whether or not communication with the server
// will be compressed. By default, this value is false.
UseCompression bool `ini:"use_compression" json:"use_compression"`
// Group specifies which group the is a part of. The server will use
// this information to load balance proxies in the same group. If the value
// is "", this will not be in a group. By default, this value is "".
Group string `ini:"group" 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 `ini:"group_key" json:"group_key"`
// 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 `ini:"proxy_protocol_version" json:"proxy_protocol_version"`
// BandwidthLimit limit the bandwidth
// 0 means no limit
BandwidthLimit BandwidthQuantity `ini:"bandwidth_limit" json:"bandwidth_limit"`
// BandwidthLimitMode specifies whether to limit the bandwidth on the
// client or server side. Valid values include "client" and "server".
// By default, this value is "client".
BandwidthLimitMode string `ini:"bandwidth_limit_mode" json:"bandwidth_limit_mode"`
// meta info for each proxy
Metas map[string]string `ini:"-" json:"metas"`
LocalSvrConf `ini:",extends"`
HealthCheckConf `ini:",extends"`
}
type DomainConf struct {
CustomDomains []string `ini:"custom_domains" json:"custom_domains"`
SubDomain string `ini:"subdomain" json:"subdomain"`
}
type RoleServerCommonConf struct {
Role string `ini:"role" json:"role"`
Sk string `ini:"sk" json:"sk"`
AllowUsers []string `ini:"allow_users" json:"allow_users"`
}
func (cfg *RoleServerCommonConf) setDefaultValues() {
cfg.Role = "server"
}
func (cfg *RoleServerCommonConf) marshalToMsg(m *msg.NewProxy) {
m.Sk = cfg.Sk
m.AllowUsers = cfg.AllowUsers
}
func (cfg *RoleServerCommonConf) unmarshalFromMsg(m *msg.NewProxy) {
cfg.Sk = m.Sk
cfg.AllowUsers = m.AllowUsers
}
// HTTP
type HTTPProxyConf struct {
BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends"`
Locations []string `ini:"locations" json:"locations"`
HTTPUser string `ini:"http_user" json:"http_user"`
HTTPPwd string `ini:"http_pwd" json:"http_pwd"`
HostHeaderRewrite string `ini:"host_header_rewrite" json:"host_header_rewrite"`
Headers map[string]string `ini:"-" json:"headers"`
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
}
// HTTPS
type HTTPSProxyConf struct {
BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends"`
}
// TCP
type TCPProxyConf struct {
BaseProxyConf `ini:",extends"`
RemotePort int `ini:"remote_port" json:"remote_port"`
}
// UDP
type UDPProxyConf struct {
BaseProxyConf `ini:",extends"`
RemotePort int `ini:"remote_port" json:"remote_port"`
}
// TCPMux
type TCPMuxProxyConf struct {
BaseProxyConf `ini:",extends"`
DomainConf `ini:",extends"`
HTTPUser string `ini:"http_user" json:"http_user,omitempty"`
HTTPPwd string `ini:"http_pwd" json:"http_pwd,omitempty"`
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
Multiplexer string `ini:"multiplexer"`
}
// STCP
type STCPProxyConf struct {
BaseProxyConf `ini:",extends"`
RoleServerCommonConf `ini:",extends"`
}
// XTCP
type XTCPProxyConf struct {
BaseProxyConf `ini:",extends"`
RoleServerCommonConf `ini:",extends"`
}
// SUDP
type SUDPProxyConf struct {
BaseProxyConf `ini:",extends"`
RoleServerCommonConf `ini:",extends"`
}
// Proxy Conf Loader
// DefaultProxyConf creates a empty ProxyConf object by proxyType.
// If proxyType doesn't exist, return nil.
func DefaultProxyConf(proxyType string) ProxyConf {
conf := NewConfByType(proxyType)
if conf != nil {
conf.SetDefaultValues()
}
return conf
}
// Proxy loaded from ini
func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf, error) {
// section.Key: if key not exists, section will set it with default value.
proxyType := section.Key("type").String()
if proxyType == "" {
proxyType = consts.TCPProxy
}
conf := DefaultProxyConf(proxyType)
if conf == nil {
return nil, fmt.Errorf("invalid type [%s]", proxyType)
}
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
return nil, err
}
if err := conf.ValidateForClient(); err != nil {
return nil, err
}
return conf, nil
}
// Proxy loaded from msg
func NewProxyConfFromMsg(m *msg.NewProxy, serverCfg ServerCommonConf) (ProxyConf, error) {
if m.ProxyType == "" {
m.ProxyType = consts.TCPProxy
}
conf := DefaultProxyConf(m.ProxyType)
if conf == nil {
return nil, fmt.Errorf("proxy [%s] type [%s] error", m.ProxyName, m.ProxyType)
}
conf.UnmarshalFromMsg(m)
err := conf.ValidateForServer(serverCfg)
if err != nil {
return nil, err
}
return conf, nil
}
// Base
func (cfg *BaseProxyConf) GetBaseConfig() *BaseProxyConf {
return cfg
}
func (cfg *BaseProxyConf) SetDefaultValues() {
cfg.LocalSvrConf = LocalSvrConf{
LocalIP: "127.0.0.1",
}
cfg.BandwidthLimitMode = BandwidthLimitModeClient
}
// BaseProxyConf apply custom logic changes.
func (cfg *BaseProxyConf) decorate(prefix string, name string, section *ini.Section) error {
// proxy_name
cfg.ProxyName = prefix + name
// metas_xxx
cfg.Metas = GetMapWithoutPrefix(section.KeysHash(), "meta_")
// bandwidth_limit
if bandwidth, err := section.GetKey("bandwidth_limit"); err == nil {
cfg.BandwidthLimit, err = NewBandwidthQuantity(bandwidth.String())
if err != nil {
return err
}
}
// plugin_xxx
cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
// custom logic code
if cfg.HealthCheckType == "tcp" && cfg.Plugin == "" {
cfg.HealthCheckAddr = cfg.LocalIP + fmt.Sprintf(":%d", cfg.LocalPort)
}
if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckURL != "" {
s := "http://" + net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
if !strings.HasPrefix(cfg.HealthCheckURL, "/") {
s += "/"
}
cfg.HealthCheckURL = s + cfg.HealthCheckURL
}
return nil
}
func (cfg *BaseProxyConf) marshalToMsg(m *msg.NewProxy) {
m.ProxyName = cfg.ProxyName
m.ProxyType = cfg.ProxyType
m.UseEncryption = cfg.UseEncryption
m.UseCompression = cfg.UseCompression
m.BandwidthLimit = cfg.BandwidthLimit.String()
// leave it empty for default value to reduce traffic
if cfg.BandwidthLimitMode != "client" {
m.BandwidthLimitMode = cfg.BandwidthLimitMode
}
m.Group = cfg.Group
m.GroupKey = cfg.GroupKey
m.Metas = cfg.Metas
}
func (cfg *BaseProxyConf) unmarshalFromMsg(m *msg.NewProxy) {
cfg.ProxyName = m.ProxyName
cfg.ProxyType = m.ProxyType
cfg.UseEncryption = m.UseEncryption
cfg.UseCompression = m.UseCompression
if m.BandwidthLimit != "" {
cfg.BandwidthLimit, _ = NewBandwidthQuantity(m.BandwidthLimit)
}
if m.BandwidthLimitMode != "" {
cfg.BandwidthLimitMode = m.BandwidthLimitMode
}
cfg.Group = m.Group
cfg.GroupKey = m.GroupKey
cfg.Metas = m.Metas
}
func (cfg *BaseProxyConf) validateForClient() (err error) {
if cfg.ProxyProtocolVersion != "" {
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
}
}
if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
return fmt.Errorf("bandwidth_limit_mode should be client or server")
}
if err = cfg.LocalSvrConf.validateForClient(); err != nil {
return
}
if err = cfg.HealthCheckConf.validateForClient(); err != nil {
return
}
return nil
}
func (cfg *BaseProxyConf) validateForServer() (err error) {
if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
return fmt.Errorf("bandwidth_limit_mode should be client or server")
}
return nil
}
// DomainConf
func (cfg *DomainConf) check() (err error) {
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
err = fmt.Errorf("custom_domains and subdomain should set at least one of them")
return
}
return
}
func (cfg *DomainConf) validateForClient() (err error) {
if err = cfg.check(); err != nil {
return
}
return
}
func (cfg *DomainConf) validateForServer(serverCfg ServerCommonConf) (err error) {
if err = cfg.check(); err != nil {
return
}
for _, domain := range cfg.CustomDomains {
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 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, "*") {
return fmt.Errorf("'.' and '*' is not supported in subdomain")
}
}
return nil
}
// LocalSvrConf
func (cfg *LocalSvrConf) validateForClient() (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
}
// HealthCheckConf
func (cfg *HealthCheckConf) validateForClient() 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'")
}
}
return nil
}
func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, section *ini.Section) error {
err := section.MapTo(cfg)
if err != nil {
return err
}
err = cfg.GetBaseConfig().decorate(prefix, name, section)
if err != nil {
return err
}
return nil
}
// TCP
func (cfg *TCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists
cfg.RemotePort = m.RemotePort
}
func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
return nil
}
func (cfg *TCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists
m.RemotePort = cfg.RemotePort
}
func (cfg *TCPProxyConf) ValidateForClient() (err error) {
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
return
}
// Add custom logic check if exists
return
}
func (cfg *TCPProxyConf) ValidateForServer(_ ServerCommonConf) error {
return cfg.BaseProxyConf.validateForServer()
}
// TCPMux
func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
return nil
}
func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists
cfg.CustomDomains = m.CustomDomains
cfg.SubDomain = m.SubDomain
cfg.Multiplexer = m.Multiplexer
cfg.HTTPUser = m.HTTPUser
cfg.HTTPPwd = m.HTTPPwd
cfg.RouteByHTTPUser = m.RouteByHTTPUser
}
func (cfg *TCPMuxProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists
m.CustomDomains = cfg.CustomDomains
m.SubDomain = cfg.SubDomain
m.Multiplexer = cfg.Multiplexer
m.HTTPUser = cfg.HTTPUser
m.HTTPPwd = cfg.HTTPPwd
m.RouteByHTTPUser = cfg.RouteByHTTPUser
}
func (cfg *TCPMuxProxyConf) ValidateForClient() (err error) {
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
return
}
// Add custom logic check if exists
if err = cfg.DomainConf.validateForClient(); err != nil {
return
}
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
return fmt.Errorf("parse conf error: incorrect multiplexer [%s]", cfg.Multiplexer)
}
return
}
func (cfg *TCPMuxProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
return err
}
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
return fmt.Errorf("proxy [%s] incorrect multiplexer [%s]", cfg.ProxyName, cfg.Multiplexer)
}
if cfg.Multiplexer == consts.HTTPConnectTCPMultiplexer && serverCfg.TCPMuxHTTPConnectPort == 0 {
return fmt.Errorf("proxy [%s] type [tcpmux] with multiplexer [httpconnect] requires tcpmux_httpconnect_port configuration", cfg.ProxyName)
}
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
return
}
return
}
// UDP
func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
return nil
}
func (cfg *UDPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists
cfg.RemotePort = m.RemotePort
}
func (cfg *UDPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists
m.RemotePort = cfg.RemotePort
}
func (cfg *UDPProxyConf) ValidateForClient() (err error) {
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
return
}
// Add custom logic check if exists
return
}
func (cfg *UDPProxyConf) ValidateForServer(_ ServerCommonConf) error {
return cfg.BaseProxyConf.validateForServer()
}
// HTTP
func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
cfg.Headers = GetMapWithoutPrefix(section.KeysHash(), "header_")
return nil
}
func (cfg *HTTPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists
cfg.CustomDomains = m.CustomDomains
cfg.SubDomain = m.SubDomain
cfg.Locations = m.Locations
cfg.HostHeaderRewrite = m.HostHeaderRewrite
cfg.HTTPUser = m.HTTPUser
cfg.HTTPPwd = m.HTTPPwd
cfg.Headers = m.Headers
cfg.RouteByHTTPUser = m.RouteByHTTPUser
}
func (cfg *HTTPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists
m.CustomDomains = cfg.CustomDomains
m.SubDomain = cfg.SubDomain
m.Locations = cfg.Locations
m.HostHeaderRewrite = cfg.HostHeaderRewrite
m.HTTPUser = cfg.HTTPUser
m.HTTPPwd = cfg.HTTPPwd
m.Headers = cfg.Headers
m.RouteByHTTPUser = cfg.RouteByHTTPUser
}
func (cfg *HTTPProxyConf) ValidateForClient() (err error) {
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
return
}
// Add custom logic check if exists
if err = cfg.DomainConf.validateForClient(); err != nil {
return
}
return
}
func (cfg *HTTPProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
return err
}
if serverCfg.VhostHTTPPort == 0 {
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
}
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
return
}
return
}
// HTTPS
func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
return nil
}
func (cfg *HTTPSProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists
cfg.CustomDomains = m.CustomDomains
cfg.SubDomain = m.SubDomain
}
func (cfg *HTTPSProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists
m.CustomDomains = cfg.CustomDomains
m.SubDomain = cfg.SubDomain
}
func (cfg *HTTPSProxyConf) ValidateForClient() (err error) {
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
return
}
// Add custom logic check if exists
if err = cfg.DomainConf.validateForClient(); err != nil {
return
}
return
}
func (cfg *HTTPSProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
return err
}
if serverCfg.VhostHTTPSPort == 0 {
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
}
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
return
}
return
}
// SUDP
func (cfg *SUDPProxyConf) SetDefaultValues() {
cfg.BaseProxyConf.SetDefaultValues()
cfg.RoleServerCommonConf.setDefaultValues()
}
func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
return nil
}
// Only for role server.
func (cfg *SUDPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists
cfg.RoleServerCommonConf.unmarshalFromMsg(m)
}
func (cfg *SUDPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists
cfg.RoleServerCommonConf.marshalToMsg(m)
}
func (cfg *SUDPProxyConf) ValidateForClient() (err error) {
if err := cfg.BaseProxyConf.validateForClient(); err != nil {
return err
}
// Add custom logic check if exists
if cfg.Role != "server" {
return fmt.Errorf("role should be 'server'")
}
return nil
}
func (cfg *SUDPProxyConf) ValidateForServer(_ ServerCommonConf) error {
return cfg.BaseProxyConf.validateForServer()
}
// STCP
func (cfg *STCPProxyConf) SetDefaultValues() {
cfg.BaseProxyConf.SetDefaultValues()
cfg.RoleServerCommonConf.setDefaultValues()
}
func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
if cfg.Role == "" {
cfg.Role = "server"
}
return nil
}
// Only for role server.
func (cfg *STCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists
cfg.RoleServerCommonConf.unmarshalFromMsg(m)
}
func (cfg *STCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists
cfg.RoleServerCommonConf.marshalToMsg(m)
}
func (cfg *STCPProxyConf) ValidateForClient() (err error) {
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
return
}
// Add custom logic check if exists
if cfg.Role != "server" {
return fmt.Errorf("role should be 'server'")
}
return
}
func (cfg *STCPProxyConf) ValidateForServer(_ ServerCommonConf) error {
return cfg.BaseProxyConf.validateForServer()
}
// XTCP
func (cfg *XTCPProxyConf) SetDefaultValues() {
cfg.BaseProxyConf.SetDefaultValues()
cfg.RoleServerCommonConf.setDefaultValues()
}
func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
err := preUnmarshalFromIni(cfg, prefix, name, section)
if err != nil {
return err
}
// Add custom logic unmarshal if exists
if cfg.Role == "" {
cfg.Role = "server"
}
return nil
}
// Only for role server.
func (cfg *XTCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.unmarshalFromMsg(m)
// Add custom logic unmarshal if exists
cfg.RoleServerCommonConf.unmarshalFromMsg(m)
}
func (cfg *XTCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
cfg.BaseProxyConf.marshalToMsg(m)
// Add custom logic marshal if exists
cfg.RoleServerCommonConf.marshalToMsg(m)
}
func (cfg *XTCPProxyConf) ValidateForClient() (err error) {
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
return
}
// Add custom logic check if exists
if cfg.Role != "server" {
return fmt.Errorf("role should be 'server'")
}
return
}
func (cfg *XTCPProxyConf) ValidateForServer(_ ServerCommonConf) error {
return cfg.BaseProxyConf.validateForServer()
}

View File

@ -1,478 +0,0 @@
// Copyright 2020 The frp Authors
//
// 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 (
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
"github.com/fatedier/frp/pkg/consts"
)
var (
testLoadOptions = ini.LoadOptions{
Insensitive: false,
InsensitiveSections: false,
InsensitiveKeys: false,
IgnoreInlineComment: true,
AllowBooleanKeys: true,
}
testProxyPrefix = "test."
)
func Test_Proxy_Interface(_ *testing.T) {
for name := range proxyConfTypeMap {
NewConfByType(name)
}
}
func Test_Proxy_UnmarshalFromIni(t *testing.T) {
assert := assert.New(t)
testcases := []struct {
sname string
source []byte
expected ProxyConf
}{
{
sname: "ssh",
source: []byte(`
[ssh]
# tcp | udp | http | https | stcp | xtcp, default is tcp
type = tcp
local_ip = 127.0.0.9
local_port = 29
bandwidth_limit = 19MB
bandwidth_limit_mode = server
use_encryption
use_compression
remote_port = 6009
group = test_group
group_key = 123456
health_check_type = tcp
health_check_timeout_s = 3
health_check_max_failed = 3
health_check_interval_s = 19
meta_var1 = 123
meta_var2 = 234`),
expected: &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "ssh",
ProxyType: consts.TCPProxy,
UseCompression: true,
UseEncryption: true,
Group: "test_group",
GroupKey: "123456",
BandwidthLimit: MustBandwidthQuantity("19MB"),
BandwidthLimitMode: BandwidthLimitModeServer,
Metas: map[string]string{
"var1": "123",
"var2": "234",
},
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 29,
},
HealthCheckConf: HealthCheckConf{
HealthCheckType: consts.TCPProxy,
HealthCheckTimeoutS: 3,
HealthCheckMaxFailed: 3,
HealthCheckIntervalS: 19,
HealthCheckAddr: "127.0.0.9:29",
},
},
RemotePort: 6009,
},
},
{
sname: "ssh_random",
source: []byte(`
[ssh_random]
type = tcp
local_ip = 127.0.0.9
local_port = 29
remote_port = 9
`),
expected: &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "ssh_random",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 29,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 9,
},
},
{
sname: "dns",
source: []byte(`
[dns]
type = udp
local_ip = 114.114.114.114
local_port = 59
remote_port = 6009
use_encryption
use_compression
`),
expected: &UDPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "dns",
ProxyType: consts.UDPProxy,
UseEncryption: true,
UseCompression: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "114.114.114.114",
LocalPort: 59,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6009,
},
},
{
sname: "web01",
source: []byte(`
[web01]
type = http
local_ip = 127.0.0.9
local_port = 89
use_encryption
use_compression
http_user = admin
http_pwd = admin
subdomain = web01
custom_domains = web02.yourdomain.com
locations = /,/pic
host_header_rewrite = example.com
header_X-From-Where = frp
health_check_type = http
health_check_url = /status
health_check_interval_s = 19
health_check_max_failed = 3
health_check_timeout_s = 3
`),
expected: &HTTPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "web01",
ProxyType: consts.HTTPProxy,
UseCompression: true,
UseEncryption: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 89,
},
HealthCheckConf: HealthCheckConf{
HealthCheckType: consts.HTTPProxy,
HealthCheckTimeoutS: 3,
HealthCheckMaxFailed: 3,
HealthCheckIntervalS: 19,
HealthCheckURL: "http://127.0.0.9:89/status",
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
DomainConf: DomainConf{
CustomDomains: []string{"web02.yourdomain.com"},
SubDomain: "web01",
},
Locations: []string{"/", "/pic"},
HTTPUser: "admin",
HTTPPwd: "admin",
HostHeaderRewrite: "example.com",
Headers: map[string]string{
"X-From-Where": "frp",
},
},
},
{
sname: "web02",
source: []byte(`
[web02]
type = https
local_ip = 127.0.0.9
local_port = 8009
use_encryption
use_compression
subdomain = web01
custom_domains = web02.yourdomain.com
proxy_protocol_version = v2
`),
expected: &HTTPSProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "web02",
ProxyType: consts.HTTPSProxy,
UseCompression: true,
UseEncryption: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 8009,
},
ProxyProtocolVersion: "v2",
BandwidthLimitMode: BandwidthLimitModeClient,
},
DomainConf: DomainConf{
CustomDomains: []string{"web02.yourdomain.com"},
SubDomain: "web01",
},
},
},
{
sname: "secret_tcp",
source: []byte(`
[secret_tcp]
type = stcp
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
use_encryption = false
use_compression = false
`),
expected: &STCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "secret_tcp",
ProxyType: consts.STCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
LocalPort: 22,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RoleServerCommonConf: RoleServerCommonConf{
Role: "server",
Sk: "abcdefg",
},
},
},
{
sname: "p2p_tcp",
source: []byte(`
[p2p_tcp]
type = xtcp
sk = abcdefg
local_ip = 127.0.0.1
local_port = 22
use_encryption = false
use_compression = false
`),
expected: &XTCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "p2p_tcp",
ProxyType: consts.XTCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
LocalPort: 22,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RoleServerCommonConf: RoleServerCommonConf{
Role: "server",
Sk: "abcdefg",
},
},
},
{
sname: "tcpmuxhttpconnect",
source: []byte(`
[tcpmuxhttpconnect]
type = tcpmux
multiplexer = httpconnect
local_ip = 127.0.0.1
local_port = 10701
custom_domains = tunnel1
`),
expected: &TCPMuxProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "tcpmuxhttpconnect",
ProxyType: consts.TCPMuxProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.1",
LocalPort: 10701,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
DomainConf: DomainConf{
CustomDomains: []string{"tunnel1"},
SubDomain: "",
},
Multiplexer: "httpconnect",
},
},
}
for _, c := range testcases {
f, err := ini.LoadSources(testLoadOptions, c.source)
assert.NoError(err)
proxyType := f.Section(c.sname).Key("type").String()
assert.NotEmpty(proxyType)
actual := DefaultProxyConf(proxyType)
assert.NotNil(actual)
err = actual.UnmarshalFromIni(testProxyPrefix, c.sname, f.Section(c.sname))
assert.NoError(err)
assert.Equal(c.expected, actual)
}
}
func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
assert := assert.New(t)
testcases := []struct {
sname string
source []byte
expected map[string]ProxyConf
}{
{
sname: "range:tcp_port",
source: []byte(`
[range:tcp_port]
type = tcp
local_ip = 127.0.0.9
local_port = 6010-6011,6019
remote_port = 6010-6011,6019
use_encryption = false
use_compression = false
`),
expected: map[string]ProxyConf{
"tcp_port_0": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "tcp_port_0",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 6010,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6010,
},
"tcp_port_1": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "tcp_port_1",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 6011,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6011,
},
"tcp_port_2": &TCPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "tcp_port_2",
ProxyType: consts.TCPProxy,
LocalSvrConf: LocalSvrConf{
LocalIP: "127.0.0.9",
LocalPort: 6019,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6019,
},
},
},
{
sname: "range:udp_port",
source: []byte(`
[range:udp_port]
type = udp
local_ip = 114.114.114.114
local_port = 6000,6010-6011
remote_port = 6000,6010-6011
use_encryption
use_compression
`),
expected: map[string]ProxyConf{
"udp_port_0": &UDPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "udp_port_0",
ProxyType: consts.UDPProxy,
UseEncryption: true,
UseCompression: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "114.114.114.114",
LocalPort: 6000,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6000,
},
"udp_port_1": &UDPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "udp_port_1",
ProxyType: consts.UDPProxy,
UseEncryption: true,
UseCompression: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "114.114.114.114",
LocalPort: 6010,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6010,
},
"udp_port_2": &UDPProxyConf{
BaseProxyConf: BaseProxyConf{
ProxyName: testProxyPrefix + "udp_port_2",
ProxyType: consts.UDPProxy,
UseEncryption: true,
UseCompression: true,
LocalSvrConf: LocalSvrConf{
LocalIP: "114.114.114.114",
LocalPort: 6011,
},
BandwidthLimitMode: BandwidthLimitModeClient,
},
RemotePort: 6011,
},
},
},
}
for _, c := range testcases {
f, err := ini.LoadSources(testLoadOptions, c.source)
assert.NoError(err)
actual := make(map[string]ProxyConf)
s := f.Section(c.sname)
err = renderRangeProxyTemplates(f, s)
assert.NoError(err)
f.DeleteSection(ini.DefaultSection)
f.DeleteSection(c.sname)
for _, section := range f.Sections() {
proxyType := section.Key("type").String()
newsname := section.Name()
tmp := DefaultProxyConf(proxyType)
err = tmp.UnmarshalFromIni(testProxyPrefix, newsname, section)
assert.NoError(err)
actual[newsname] = tmp
}
assert.Equal(c.expected, actual)
}
}

View File

@ -1,217 +0,0 @@
// Copyright 2020 The frp Authors
//
// 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 (
"testing"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/pkg/auth"
plugin "github.com/fatedier/frp/pkg/plugin/server"
)
func Test_LoadServerCommonConf(t *testing.T) {
assert := assert.New(t)
testcases := []struct {
source []byte
expected ServerCommonConf
}{
{
source: []byte(`
# [common] is integral section
[common]
bind_addr = 0.0.0.9
bind_port = 7009
kcp_bind_port = 7007
proxy_bind_addr = 127.0.0.9
vhost_http_port = 89
vhost_https_port = 449
vhost_http_timeout = 69
tcpmux_httpconnect_port = 1339
dashboard_addr = 0.0.0.9
dashboard_port = 7509
dashboard_user = admin9
dashboard_pwd = admin9
enable_prometheus
assets_dir = ./static9
log_file = ./frps.log9
log_way = file
log_level = info9
log_max_days = 39
disable_log_color = false
detailed_errors_to_client
authentication_method = token
authenticate_heartbeats = false
authenticate_new_work_conns = false
token = 123456789
oidc_issuer = test9
oidc_audience = test9
oidc_skip_expiry_check
oidc_skip_issuer_check
heartbeat_timeout = 99
user_conn_timeout = 9
allow_ports = 10-12,99
max_pool_count = 59
max_ports_per_client = 9
tls_only = false
tls_cert_file = server.crt
tls_key_file = server.key
tls_trusted_ca_file = ca.crt
subdomain_host = frps.com
tcp_mux
udp_packet_size = 1509
[plugin.user-manager]
addr = 127.0.0.1:9009
path = /handler
ops = Login
[plugin.port-manager]
addr = 127.0.0.1:9009
path = /handler
ops = NewProxy
tls_verify
`),
expected: ServerCommonConf{
ServerConfig: auth.ServerConfig{
BaseConfig: auth.BaseConfig{
AuthenticationMethod: "token",
AuthenticateHeartBeats: false,
AuthenticateNewWorkConns: false,
},
TokenConfig: auth.TokenConfig{
Token: "123456789",
},
OidcServerConfig: auth.OidcServerConfig{
OidcIssuer: "test9",
OidcAudience: "test9",
OidcSkipExpiryCheck: true,
OidcSkipIssuerCheck: true,
},
},
BindAddr: "0.0.0.9",
BindPort: 7009,
KCPBindPort: 7007,
QUICKeepalivePeriod: 10,
QUICMaxIdleTimeout: 30,
QUICMaxIncomingStreams: 100000,
ProxyBindAddr: "127.0.0.9",
VhostHTTPPort: 89,
VhostHTTPSPort: 449,
VhostHTTPTimeout: 69,
TCPMuxHTTPConnectPort: 1339,
DashboardAddr: "0.0.0.9",
DashboardPort: 7509,
DashboardUser: "admin9",
DashboardPwd: "admin9",
EnablePrometheus: true,
AssetsDir: "./static9",
LogFile: "./frps.log9",
LogWay: "file",
LogLevel: "info9",
LogMaxDays: 39,
DisableLogColor: false,
DetailedErrorsToClient: true,
HeartbeatTimeout: 99,
UserConnTimeout: 9,
AllowPorts: map[int]struct{}{
10: {},
11: {},
12: {},
99: {},
},
AllowPortsStr: "10-12,99",
MaxPoolCount: 59,
MaxPortsPerClient: 9,
TLSOnly: true,
TLSCertFile: "server.crt",
TLSKeyFile: "server.key",
TLSTrustedCaFile: "ca.crt",
SubDomainHost: "frps.com",
TCPMux: true,
TCPMuxKeepaliveInterval: 60,
TCPKeepAlive: 7200,
UDPPacketSize: 1509,
NatHoleAnalysisDataReserveHours: 7 * 24,
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
"user-manager": {
Name: "user-manager",
Addr: "127.0.0.1:9009",
Path: "/handler",
Ops: []string{"Login"},
},
"port-manager": {
Name: "port-manager",
Addr: "127.0.0.1:9009",
Path: "/handler",
Ops: []string{"NewProxy"},
TLSVerify: true,
},
},
},
},
{
source: []byte(`
# [common] is integral section
[common]
bind_addr = 0.0.0.9
bind_port = 7009
`),
expected: ServerCommonConf{
ServerConfig: auth.ServerConfig{
BaseConfig: auth.BaseConfig{
AuthenticationMethod: "token",
AuthenticateHeartBeats: false,
AuthenticateNewWorkConns: false,
},
},
BindAddr: "0.0.0.9",
BindPort: 7009,
QUICKeepalivePeriod: 10,
QUICMaxIdleTimeout: 30,
QUICMaxIncomingStreams: 100000,
ProxyBindAddr: "0.0.0.9",
VhostHTTPTimeout: 60,
DashboardAddr: "0.0.0.0",
DashboardUser: "",
DashboardPwd: "",
EnablePrometheus: false,
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
DetailedErrorsToClient: true,
TCPMux: true,
TCPMuxKeepaliveInterval: 60,
TCPKeepAlive: 7200,
AllowPorts: make(map[int]struct{}),
MaxPoolCount: 5,
HeartbeatTimeout: 90,
UserConnTimeout: 10,
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
UDPPacketSize: 1500,
NatHoleAnalysisDataReserveHours: 7 * 24,
},
},
}
for _, c := range testcases {
actual, err := UnmarshalServerConfFromIni(c.source)
assert.NoError(err)
actual.Complete()
assert.Equal(c.expected, actual)
}
}

View File

@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package config
package types
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
)
@ -123,3 +124,62 @@ func (q *BandwidthQuantity) MarshalJSON() ([]byte, error) {
func (q *BandwidthQuantity) Bytes() int64 {
return q.i
}
type PortsRange struct {
Start int `json:"start,omitempty"`
End int `json:"end,omitempty"`
Single int `json:"single,omitempty"`
}
type PortsRangeSlice []PortsRange
func (p PortsRangeSlice) String() string {
strs := []string{}
for _, v := range p {
if v.Single > 0 {
strs = append(strs, strconv.Itoa(v.Single))
} else {
strs = append(strs, strconv.Itoa(v.Start)+"-"+strconv.Itoa(v.End))
}
}
return strings.Join(strs, ",")
}
// the format of str is like "1000-2000,3000,4000-5000"
func NewPortsRangeSliceFromString(str string) ([]PortsRange, error) {
str = strings.TrimSpace(str)
out := []PortsRange{}
numRanges := strings.Split(str, ",")
for _, numRangeStr := range numRanges {
// 1000-2000 or 2001
numArray := strings.Split(numRangeStr, "-")
// length: only 1 or 2 is correct
rangeType := len(numArray)
switch rangeType {
case 1:
// single number
singleNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
if err != nil {
return nil, fmt.Errorf("range number is invalid, %v", err)
}
out = append(out, PortsRange{Single: int(singleNum)})
case 2:
// range numbers
min, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
if err != nil {
return nil, fmt.Errorf("range number is invalid, %v", err)
}
max, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
if err != nil {
return nil, fmt.Errorf("range number is invalid, %v", err)
}
if max < min {
return nil, fmt.Errorf("range number is invalid")
}
out = append(out, PortsRange{Start: int(min), End: int(max)})
default:
return nil, fmt.Errorf("range number is invalid")
}
}
return out, nil
}

View File

@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package config
package types
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type Wrap struct {
@ -27,14 +27,46 @@ type Wrap struct {
}
func TestBandwidthQuantity(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
var w Wrap
err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w)
assert.NoError(err)
assert.EqualValues(1*KB, w.B.Bytes())
require.NoError(err)
require.EqualValues(1*KB, w.B.Bytes())
buf, err := json.Marshal(&w)
assert.NoError(err)
assert.Equal(`{"b":"1KB","int":5}`, string(buf))
require.NoError(err)
require.Equal(`{"b":"1KB","int":5}`, string(buf))
}
func TestPortsRangeSlice2String(t *testing.T) {
require := require.New(t)
ports := []PortsRange{
{
Start: 1000,
End: 2000,
},
{
Single: 3000,
},
}
str := PortsRangeSlice(ports).String()
require.Equal("1000-2000,3000", str)
}
func TestNewPortsRangeSliceFromString(t *testing.T) {
require := require.New(t)
ports, err := NewPortsRangeSliceFromString("1000-2000,3000")
require.NoError(err)
require.Equal([]PortsRange{
{
Start: 1000,
End: 2000,
},
{
Single: 3000,
},
}, ports)
}

19
pkg/config/v1/api.go Normal file
View File

@ -0,0 +1,19 @@
// Copyright 2023 The frp Authors
//
// 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 v1
type APIMetadata struct {
Version string `json:"version"`
}

199
pkg/config/v1/client.go Normal file
View File

@ -0,0 +1,199 @@
// Copyright 2023 The frp Authors
//
// 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 v1
import (
"os"
"github.com/samber/lo"
"github.com/fatedier/frp/pkg/util/util"
)
type ClientConfig struct {
ClientCommonConfig
Proxies []TypedProxyConfig `json:"proxies,omitempty"`
Visitors []TypedVisitorConfig `json:"visitors,omitempty"`
}
type ClientCommonConfig struct {
APIMetadata
Auth AuthClientConfig `json:"auth,omitempty"`
// 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}".
User string `json:"user,omitempty"`
// ServerAddr specifies the address of the server to connect to. By
// default, this value is "0.0.0.0".
ServerAddr string `json:"serverAddr,omitempty"`
// ServerPort specifies the port to connect to the server on. By default,
// this value is 7000.
ServerPort int `json:"serverPort,omitempty"`
// STUN server to help penetrate NAT hole.
NatHoleSTUNServer string `json:"natHoleStunServer,omitempty"`
// DNSServer specifies a DNS server address for FRPC to use. If this value
// is "", the default DNS will be used.
DNSServer string `json:"dnsServer,omitempty"`
// 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:"loginFailExit,omitempty"`
// 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 []string `json:"start,omitempty"`
Log LogConfig `json:"log,omitempty"`
WebServer WebServerConfig `json:"webServer,omitempty"`
Transport ClientTransportConfig `json:"transport,omitempty"`
// UDPPacketSize specifies the udp packet size
// By default, this value is 1500
UDPPacketSize int64 `json:"udpPacketSize,omitempty"`
// Client metadata info
Metadatas map[string]string `json:"metadatas,omitempty"`
// Include other config files for proxies.
IncludeConfigFiles []string `json:"includes,omitempty"`
}
func (c *ClientCommonConfig) Complete() {
c.ServerAddr = util.EmptyOr(c.ServerAddr, "0.0.0.0")
c.ServerPort = util.EmptyOr(c.ServerPort, 7000)
c.LoginFailExit = util.EmptyOr(c.LoginFailExit, lo.ToPtr(true))
c.Auth.Complete()
c.Log.Complete()
c.Transport.Complete()
c.WebServer.Complete()
c.UDPPacketSize = util.EmptyOr(c.UDPPacketSize, 1500)
}
type ClientTransportConfig struct {
// Protocol specifies the protocol to use when interacting with the server.
// Valid values are "tcp", "kcp", "quic", "websocket" and "wss". By default, this value
// is "tcp".
Protocol string `json:"protocol,omitempty"`
// The maximum amount of time a dial to server will wait for a connect to complete.
DialServerTimeout int64 `json:"dialServerTimeout,omitempty"`
// DialServerKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
// If negative, keep-alive probes are disabled.
DialServerKeepAlive int64 `json:"dialServerKeepalive,omitempty"`
// ConnectServerLocalIP specifies the address of the client bind when it connect to server.
// Note: This value only use in TCP/Websocket protocol. Not support in KCP protocol.
ConnectServerLocalIP string `json:"connectServerLocalIP,omitempty"`
// ProxyURL 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.
ProxyURL string `json:"proxyURL,omitempty"`
// PoolCount specifies the number of connections the client will make to
// the server in advance.
PoolCount int `json:"poolCount,omitempty"`
// 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:"tcpMux,omitempty"`
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
TCPMuxKeepaliveInterval int64 `json:"tcpMuxKeepaliveInterval,omitempty"`
// QUIC protocol options.
QUIC QUICOptions `json:"quic,omitempty"`
// 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. Set negative value to disable it.
HeartbeatInterval int64 `json:"heartbeatInterval,omitempty"`
// 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. Set negative value to disable it.
HeartbeatTimeout int64 `json:"heartbeatTimeout,omitempty"`
// TLS specifies TLS settings for the connection to the server.
TLS TLSClientConfig `json:"tls,omitempty"`
}
func (c *ClientTransportConfig) Complete() {
c.Protocol = util.EmptyOr(c.Protocol, "tcp")
c.DialServerTimeout = util.EmptyOr(c.DialServerTimeout, 10)
c.DialServerKeepAlive = util.EmptyOr(c.DialServerKeepAlive, 7200)
c.ProxyURL = util.EmptyOr(c.ProxyURL, os.Getenv("http_proxy"))
c.PoolCount = util.EmptyOr(c.PoolCount, 1)
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30)
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
c.QUIC.Complete()
c.TLS.Complete()
}
type TLSClientConfig struct {
// TLSEnable specifies whether or not TLS should be used when communicating
// with the server. If "tls.certFile" and "tls.keyFile" are valid,
// client will load the supplied tls configuration.
// Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
Enable *bool `json:"enable,omitempty"`
// If DisableCustomTLSFirstByte is set to false, frpc will establish a connection with frps using the
// first custom byte when tls is enabled.
// Since v0.50.0, the default value has been changed to true, and the first custom byte is disabled by default.
DisableCustomTLSFirstByte *bool `json:"disableCustomTLSFirstByte,omitempty"`
TLSConfig
}
func (c *TLSClientConfig) Complete() {
c.Enable = util.EmptyOr(c.Enable, lo.ToPtr(true))
c.DisableCustomTLSFirstByte = util.EmptyOr(c.DisableCustomTLSFirstByte, lo.ToPtr(true))
}
type AuthClientConfig struct {
// Method specifies what authentication method to use to
// authenticate frpc with frps. If "token" is specified - token will be
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
// token will be issued using OIDC settings. By default, this value is "token".
Method string `json:"method,omitempty"`
// Specify whether to include auth info in additional scope.
// Current supported scopes are: "HeartBeats", "NewWorkConns".
AdditionalAuthScopes []AuthScope `json:"additionalAuthScopes,omitempty"`
// 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,omitempty"`
OIDC AuthOIDCClientConfig `json:"oidc,omitempty"`
}
func (c *AuthClientConfig) Complete() {
c.Method = util.EmptyOr(c.Method, "token")
}
type AuthOIDCClientConfig struct {
// ClientID specifies the client ID to use to get a token in OIDC authentication.
ClientID string `json:"clientID,omitempty"`
// ClientSecret specifies the client secret to use to get a token in OIDC
// authentication.
ClientSecret string `json:"clientSecret,omitempty"`
// Audience specifies the audience of the token in OIDC authentication.
Audience string `json:"audience,omitempty"`
// Scope specifies the scope of the token in OIDC authentication.
Scope string `json:"scope,omitempty"`
// TokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
// It will be used to get an OIDC token.
TokenEndpointURL string `json:"tokenEndpointURL,omitempty"`
// AdditionalEndpointParams specifies additional parameters to be sent
// this field will be transfer to map[string][]string in OIDC token generator.
AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"`
}

View File

@ -0,0 +1,34 @@
// Copyright 2023 The frp Authors
//
// 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 v1
import (
"testing"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
)
func TestClientConfigComplete(t *testing.T) {
require := require.New(t)
c := &ClientConfig{}
c.Complete()
require.Equal("token", c.Auth.Method)
require.Equal(true, lo.FromPtr(c.Transport.TCPMux))
require.Equal(true, lo.FromPtr(c.LoginFailExit))
require.Equal(true, lo.FromPtr(c.Transport.TLS.Enable))
require.Equal(true, lo.FromPtr(c.Transport.TLS.DisableCustomTLSFirstByte))
}

110
pkg/config/v1/common.go Normal file
View File

@ -0,0 +1,110 @@
// Copyright 2023 The frp Authors
//
// 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 v1
import (
"github.com/fatedier/frp/pkg/util/util"
)
type AuthScope string
const (
AuthScopeHeartBeats AuthScope = "HeartBeats"
AuthScopeNewWorkConns AuthScope = "NewWorkConns"
)
// QUIC protocol options
type QUICOptions struct {
KeepalivePeriod int `json:"quicKeepalivePeriod,omitempty" validate:"gte=0"`
MaxIdleTimeout int `json:"quicMaxIdleTimeout,omitempty" validate:"gte=0"`
MaxIncomingStreams int `json:"quicMaxIncomingStreams,omitempty" validate:"gte=0"`
}
func (c *QUICOptions) Complete() {
c.KeepalivePeriod = util.EmptyOr(c.KeepalivePeriod, 10)
c.MaxIdleTimeout = util.EmptyOr(c.MaxIdleTimeout, 30)
c.MaxIncomingStreams = util.EmptyOr(c.MaxIncomingStreams, 100000)
}
type WebServerConfig struct {
// This is the network address to bind on for serving the web interface and API.
// By default, this value is "127.0.0.1".
Addr string `json:"addr,omitempty"`
// Port specifies the port for the web server to listen on. If this
// value is 0, the admin server will not be started.
Port int `json:"port,omitempty"`
// User specifies the username that the web server will use for login.
User string `json:"user,omitempty"`
// Password specifies the password that the admin server will use for login.
Password string `json:"password,omitempty"`
// 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 embed package.
AssetsDir string `json:"assetsDir,omitempty"`
// Enable golang pprof handlers.
PprofEnable bool `json:"pprofEnable,omitempty"`
// Enable TLS if TLSConfig is not nil.
TLS *TLSConfig `json:"tls,omitempty"`
}
func (c *WebServerConfig) Complete() {
c.Addr = util.EmptyOr(c.Addr, "127.0.0.1")
}
type TLSConfig struct {
// CertPath specifies the path of the cert file that client will load.
CertFile string `json:"certFile,omitempty"`
// KeyPath specifies the path of the secret key file that client will load.
KeyFile string `json:"keyFile,omitempty"`
// TrustedCaFile specifies the path of the trusted ca file that will load.
TrustedCaFile string `json:"trustedCaFile,omitempty"`
// ServerName specifies the custom server name of tls certificate. By
// default, server name if same to ServerAddr.
ServerName string `json:"serverName,omitempty"`
}
type LogConfig struct {
// This is destination where frp should wirte the logs.
// If "console" is used, logs will be printed to stdout, otherwise,
// logs will be written to the specified file.
// By default, this value is "console".
To string `json:"to,omitempty"`
// Level specifies the minimum log level. Valid values are "trace",
// "debug", "info", "warn", and "error". By default, this value is "info".
Level string `json:"level,omitempty"`
// MaxDays specifies the maximum number of days to store log information
// before deletion.
MaxDays int64 `json:"maxDays"`
// DisablePrintColor disables log colors when log.to is "console".
DisablePrintColor bool `json:"disablePrintColor,omitempty"`
}
func (c *LogConfig) Complete() {
c.To = util.EmptyOr(c.To, "console")
c.Level = util.EmptyOr(c.Level, "info")
c.MaxDays = util.EmptyOr(c.MaxDays, 3)
}
type HTTPPluginOptions struct {
Name string `json:"name"`
Addr string `json:"addr"`
Path string `json:"path"`
Ops []string `json:"ops"`
TLSVerify bool `json:"tls_verify,omitempty"`
}
type HeaderOperations struct {
Set map[string]string `json:"set,omitempty"`
}

117
pkg/config/v1/plugin.go Normal file
View File

@ -0,0 +1,117 @@
// Copyright 2023 The frp Authors
//
// 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 v1
import (
"encoding/json"
"errors"
"fmt"
"reflect"
)
type ClientPluginOptions interface{}
type TypedClientPluginOptions struct {
Type string `json:"type"`
ClientPluginOptions
}
func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
if len(b) == 4 && string(b) == "null" {
return errors.New("type is required")
}
typeStruct := struct {
Type string `json:"type"`
}{}
if err := json.Unmarshal(b, &typeStruct); err != nil {
return err
}
c.Type = typeStruct.Type
v, ok := clientPluginOptionsTypeMap[typeStruct.Type]
if !ok {
return fmt.Errorf("unknown plugin type: %s", typeStruct.Type)
}
if err := json.Unmarshal(b, v); err != nil {
return err
}
c.ClientPluginOptions = v
return nil
}
const (
PluginHTTP2HTTPS = "http2https"
PluginHTTPProxy = "http_proxy"
PluginHTTPS2HTTP = "https2http"
PluginHTTPS2HTTPS = "https2https"
PluginSocks5 = "socks5"
PluginStaticFile = "static_file"
PluginUnixDomainSocket = "unix_domain_socket"
)
var clientPluginOptionsTypeMap = map[string]reflect.Type{
PluginHTTP2HTTPS: reflect.TypeOf(HTTP2HTTPSPluginOptions{}),
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
}
type HTTP2HTTPSPluginOptions struct {
LocalAddr string `json:"localAddr,omitempty"`
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
}
type HTTPProxyPluginOptions struct {
HTTPUser string `json:"httpUser,omitempty"`
HTTPPassword string `json:"httpPassword,omitempty"`
}
type HTTPS2HTTPPluginOptions struct {
LocalAddr string `json:"localAddr,omitempty"`
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
CrtPath string `json:"crtPath,omitempty"`
KeyPath string `json:"keyPath,omitempty"`
}
type HTTPS2HTTPSPluginOptions struct {
LocalAddr string `json:"localAddr,omitempty"`
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
CrtPath string `json:"crtPath,omitempty"`
KeyPath string `json:"keyPath,omitempty"`
}
type Socks5PluginOptions struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
type StaticFilePluginOptions struct {
LocalPath string `json:"localPath,omitempty"`
StripPrefix string `json:"stripPrefix,omitempty"`
HTTPUser string `json:"httpUser,omitempty"`
HTTPPassword string `json:"httpPassword,omitempty"`
}
type UnixDomainSocketPluginOptions struct {
UnixPath string `json:"unixPath,omitempty"`
}

420
pkg/config/v1/proxy.go Normal file
View File

@ -0,0 +1,420 @@
// Copyright 2023 The frp Authors
//
// 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 v1
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"github.com/samber/lo"
"github.com/fatedier/frp/pkg/config/types"
"github.com/fatedier/frp/pkg/consts"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/util/util"
)
type ProxyTransport struct {
// 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.
UseEncryption bool `json:"useEncryption,omitempty"`
// UseCompression controls whether or not communication with the server
// will be compressed.
UseCompression bool `json:"useCompression,omitempty"`
// BandwidthLimit limit the bandwidth
// 0 means no limit
BandwidthLimit types.BandwidthQuantity `json:"bandwidthLimit,omitempty"`
// BandwidthLimitMode specifies whether to limit the bandwidth on the
// client or server side. Valid values include "client" and "server".
// By default, this value is "client".
BandwidthLimitMode string `json:"bandwidthLimitMode,omitempty"`
// 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:"proxyProtocolVersion,omitempty"`
}
type LoadBalancerConfig struct {
// Group specifies which group the is a part of. The server will use
// this information to load balance proxies in the same group. If the value
// is "", this will not be in a group.
Group string `json:"group"`
// GroupKey specifies a group key, which should be the same among proxies
// of the same group.
GroupKey string `json:"groupKey,omitempty"`
}
type ProxyBackend struct {
// LocalIP specifies the IP address or host name of the backend.
LocalIP string `json:"localIP,omitempty"`
// LocalPort specifies the port of the backend.
LocalPort int `json:"localPort,omitempty"`
// Plugin specifies what plugin should be used for handling connections. If this value
// is set, the LocalIP and LocalPort values will be ignored.
Plugin TypedClientPluginOptions `json:"plugin,omitempty"`
}
// HealthCheckConfig configures health checking. This can be useful for load
// balancing purposes to detect and remove proxies to failing services.
type HealthCheckConfig struct {
// Type specifies what protocol to use for health checking.
// Valid values include "tcp", "http", and "". If this value is "", health
// checking will not be performed.
//
// 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.
Type string `json:"type"` // tcp | http
// TimeoutSeconds 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.
TimeoutSeconds int `json:"timeoutSeconds,omitempty"`
// MaxFailed specifies the number of allowed failures before the
// is stopped. By default, this value is 1.
MaxFailed int `json:"maxFailed,omitempty"`
// IntervalSeconds specifies the time in seconds between health
// checks. By default, this value is 10.
IntervalSeconds int `json:"intervalSeconds"`
// Path specifies the path to send health checks to if the
// health check type is "http".
Path string `json:"path,omitempty"`
}
type DomainConfig struct {
CustomDomains []string `json:"customDomains,omitempty"`
SubDomain string `json:"subdomain,omitempty"`
}
type ProxyBaseConfig struct {
Name string `json:"name"`
Type string `json:"type"`
Transport ProxyTransport `json:"transport,omitempty"`
// metadata info for each proxy
Metadatas map[string]string `json:"metadatas,omitempty"`
LoadBalancer LoadBalancerConfig `json:"loadBalancer,omitempty"`
HealthCheck HealthCheckConfig `json:"healthCheck,omitempty"`
ProxyBackend
}
func (c *ProxyBaseConfig) GetBaseConfig() *ProxyBaseConfig {
return c
}
func (c *ProxyBaseConfig) Complete(namePrefix string) {
c.Name = lo.Ternary(namePrefix == "", "", namePrefix+".") + c.Name
c.LocalIP = util.EmptyOr(c.LocalIP, "127.0.0.1")
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
}
func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
m.ProxyName = c.Name
m.ProxyType = c.Type
m.UseEncryption = c.Transport.UseEncryption
m.UseCompression = c.Transport.UseCompression
m.BandwidthLimit = c.Transport.BandwidthLimit.String()
// leave it empty for default value to reduce traffic
if c.Transport.BandwidthLimitMode != "client" {
m.BandwidthLimitMode = c.Transport.BandwidthLimitMode
}
m.Group = c.LoadBalancer.Group
m.GroupKey = c.LoadBalancer.GroupKey
m.Metas = c.Metadatas
}
func (c *ProxyBaseConfig) UnmarshalFromMsg(m *msg.NewProxy) {
c.Name = m.ProxyName
c.Type = m.ProxyType
c.Transport.UseEncryption = m.UseEncryption
c.Transport.UseCompression = m.UseCompression
if m.BandwidthLimit != "" {
c.Transport.BandwidthLimit, _ = types.NewBandwidthQuantity(m.BandwidthLimit)
}
if m.BandwidthLimitMode != "" {
c.Transport.BandwidthLimitMode = m.BandwidthLimitMode
}
c.LoadBalancer.Group = m.Group
c.LoadBalancer.GroupKey = m.GroupKey
c.Metadatas = m.Metas
}
type TypedProxyConfig struct {
Type string `json:"type"`
ProxyConfigurer
}
func (c *TypedProxyConfig) UnmarshalJSON(b []byte) error {
if len(b) == 4 && string(b) == "null" {
return errors.New("type is required")
}
typeStruct := struct {
Type string `json:"type"`
}{}
if err := json.Unmarshal(b, &typeStruct); err != nil {
return err
}
c.Type = typeStruct.Type
configurer := NewProxyConfigurerByType(typeStruct.Type)
if configurer == nil {
return fmt.Errorf("unknown proxy type: %s", typeStruct.Type)
}
if err := json.Unmarshal(b, configurer); err != nil {
return err
}
c.ProxyConfigurer = configurer
return nil
}
type ProxyConfigurer interface {
Complete(namePrefix string)
GetBaseConfig() *ProxyBaseConfig
// MarshalToMsg marshals this config into a msg.NewProxy message. This
// function will be called on the frpc side.
MarshalToMsg(*msg.NewProxy)
// UnmarshalFromMsg unmarshals a msg.NewProxy message into this config.
// This function will be called on the frps side.
UnmarshalFromMsg(*msg.NewProxy)
}
var proxyConfigTypeMap = map[string]reflect.Type{
consts.TCPProxy: reflect.TypeOf(TCPProxyConfig{}),
consts.UDPProxy: reflect.TypeOf(UDPProxyConfig{}),
consts.HTTPProxy: reflect.TypeOf(HTTPProxyConfig{}),
consts.HTTPSProxy: reflect.TypeOf(HTTPSProxyConfig{}),
consts.TCPMuxProxy: reflect.TypeOf(TCPMuxProxyConfig{}),
consts.STCPProxy: reflect.TypeOf(STCPProxyConfig{}),
consts.XTCPProxy: reflect.TypeOf(XTCPProxyConfig{}),
consts.SUDPProxy: reflect.TypeOf(SUDPProxyConfig{}),
}
func NewProxyConfigurerByType(proxyType string) ProxyConfigurer {
v, ok := proxyConfigTypeMap[proxyType]
if !ok {
return nil
}
return reflect.New(v).Interface().(ProxyConfigurer)
}
var _ ProxyConfigurer = &TCPProxyConfig{}
type TCPProxyConfig struct {
ProxyBaseConfig
RemotePort int `json:"remotePort,omitempty"`
}
func (c *TCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.MarshalToMsg(m)
m.RemotePort = c.RemotePort
}
func (c *TCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.UnmarshalFromMsg(m)
c.RemotePort = m.RemotePort
}
var _ ProxyConfigurer = &UDPProxyConfig{}
type UDPProxyConfig struct {
ProxyBaseConfig
RemotePort int `json:"remotePort,omitempty"`
}
func (c *UDPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.MarshalToMsg(m)
m.RemotePort = c.RemotePort
}
func (c *UDPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.UnmarshalFromMsg(m)
c.RemotePort = m.RemotePort
}
var _ ProxyConfigurer = &HTTPProxyConfig{}
type HTTPProxyConfig struct {
ProxyBaseConfig
DomainConfig
Locations []string `json:"locations,omitempty"`
HTTPUser string `json:"httpUser,omitempty"`
HTTPPassword string `json:"httpPassword,omitempty"`
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
RouteByHTTPUser string `json:"routeByHttpUser,omitempty"`
}
func (c *HTTPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.MarshalToMsg(m)
m.CustomDomains = c.CustomDomains
m.SubDomain = c.SubDomain
m.Locations = c.Locations
m.HostHeaderRewrite = c.HostHeaderRewrite
m.HTTPUser = c.HTTPUser
m.HTTPPwd = c.HTTPPassword
m.Headers = c.RequestHeaders.Set
m.RouteByHTTPUser = c.RouteByHTTPUser
}
func (c *HTTPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.UnmarshalFromMsg(m)
c.CustomDomains = m.CustomDomains
c.SubDomain = m.SubDomain
c.Locations = m.Locations
c.HostHeaderRewrite = m.HostHeaderRewrite
c.HTTPUser = m.HTTPUser
c.HTTPPassword = m.HTTPPwd
c.RequestHeaders.Set = m.Headers
c.RouteByHTTPUser = m.RouteByHTTPUser
}
var _ ProxyConfigurer = &HTTPSProxyConfig{}
type HTTPSProxyConfig struct {
ProxyBaseConfig
DomainConfig
}
func (c *HTTPSProxyConfig) MarshalToMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.MarshalToMsg(m)
m.CustomDomains = c.CustomDomains
m.SubDomain = c.SubDomain
}
func (c *HTTPSProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.UnmarshalFromMsg(m)
c.CustomDomains = m.CustomDomains
c.SubDomain = m.SubDomain
}
var _ ProxyConfigurer = &TCPMuxProxyConfig{}
type TCPMuxProxyConfig struct {
ProxyBaseConfig
DomainConfig
HTTPUser string `json:"httpUser,omitempty"`
HTTPPassword string `json:"httpPassword,omitempty"`
RouteByHTTPUser string `json:"routeByHttpUser,omitempty"`
Multiplexer string `json:"multiplexer,omitempty"`
}
func (c *TCPMuxProxyConfig) MarshalToMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.MarshalToMsg(m)
m.CustomDomains = c.CustomDomains
m.SubDomain = c.SubDomain
m.Multiplexer = c.Multiplexer
m.HTTPUser = c.HTTPUser
m.HTTPPwd = c.HTTPPassword
m.RouteByHTTPUser = c.RouteByHTTPUser
}
func (c *TCPMuxProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.UnmarshalFromMsg(m)
c.CustomDomains = m.CustomDomains
c.SubDomain = m.SubDomain
c.Multiplexer = m.Multiplexer
c.HTTPUser = m.HTTPUser
c.HTTPPassword = m.HTTPPwd
c.RouteByHTTPUser = m.RouteByHTTPUser
}
var _ ProxyConfigurer = &STCPProxyConfig{}
type STCPProxyConfig struct {
ProxyBaseConfig
Secretkey string `json:"secretKey,omitempty"`
AllowUsers []string `json:"allowUsers,omitempty"`
}
func (c *STCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.MarshalToMsg(m)
m.Sk = c.Secretkey
m.AllowUsers = c.AllowUsers
}
func (c *STCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.UnmarshalFromMsg(m)
c.Secretkey = m.Sk
c.AllowUsers = m.AllowUsers
}
var _ ProxyConfigurer = &XTCPProxyConfig{}
type XTCPProxyConfig struct {
ProxyBaseConfig
Secretkey string `json:"secretKey,omitempty"`
AllowUsers []string `json:"allowUsers,omitempty"`
}
func (c *XTCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.MarshalToMsg(m)
m.Sk = c.Secretkey
m.AllowUsers = c.AllowUsers
}
func (c *XTCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.UnmarshalFromMsg(m)
c.Secretkey = m.Sk
c.AllowUsers = m.AllowUsers
}
var _ ProxyConfigurer = &SUDPProxyConfig{}
type SUDPProxyConfig struct {
ProxyBaseConfig
Secretkey string `json:"secretKey,omitempty"`
AllowUsers []string `json:"allowUsers,omitempty"`
}
func (c *SUDPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.MarshalToMsg(m)
m.Sk = c.Secretkey
m.AllowUsers = c.AllowUsers
}
func (c *SUDPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
c.ProxyBaseConfig.UnmarshalFromMsg(m)
c.Secretkey = m.Sk
c.AllowUsers = m.AllowUsers
}

View File

@ -0,0 +1,49 @@
// Copyright 2023 The frp Authors
//
// 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 v1
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestUnmarshalTypedProxyConfig(t *testing.T) {
require := require.New(t)
proxyConfigs := struct {
Proxies []TypedProxyConfig `json:"proxies,omitempty"`
}{}
strs := `{
"proxies": [
{
"type": "tcp",
"localPort": 22,
"remotePort": 6000
},
{
"type": "http",
"localPort": 80,
"customDomains": ["www.example.com"]
}
]
}`
err := json.Unmarshal([]byte(strs), &proxyConfigs)
require.NoError(err)
require.IsType(&TCPProxyConfig{}, proxyConfigs.Proxies[0].ProxyConfigurer)
require.IsType(&HTTPProxyConfig{}, proxyConfigs.Proxies[1].ProxyConfigurer)
}

190
pkg/config/v1/server.go Normal file
View File

@ -0,0 +1,190 @@
// Copyright 2023 The frp Authors
//
// 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 v1
import (
"github.com/samber/lo"
"github.com/fatedier/frp/pkg/config/types"
"github.com/fatedier/frp/pkg/util/util"
)
type ServerConfig struct {
APIMetadata
Auth AuthServerConfig `json:"auth,omitempty"`
// BindAddr specifies the address that the server binds to. By default,
// this value is "0.0.0.0".
BindAddr string `json:"bindAddr,omitempty"`
// BindPort specifies the port that the server listens on. By default, this
// value is 7000.
BindPort int `json:"bindPort,omitempty" validate:"gte=0,lte=65535"`
// KCPBindPort specifies the KCP port that the server listens on. If this
// value is 0, the server will not listen for KCP connections.
KCPBindPort int `json:"kcpBindPort,omitempty" validate:"gte=0,lte=65535"`
// QUICBindPort specifies the QUIC port that the server listens on.
// Set this value to 0 will disable this feature.
QUICBindPort int `json:"quicBindPort,omitempty" validate:"gte=0,lte=65535"`
// ProxyBindAddr specifies the address that the proxy binds to. This value
// may be the same as BindAddr.
ProxyBindAddr string `json:"proxyBindAddr,omitempty"`
// 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.
VhostHTTPPort int `json:"vhostHTTPPort,omitempty" validate:"gte=0,lte=65535"`
// VhostHTTPTimeout specifies the response header timeout for the Vhost
// HTTP server, in seconds. By default, this value is 60.
VhostHTTPTimeout int64 `json:"vhostHTTPTimeout,omitempty"`
// 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.
VhostHTTPSPort int `json:"vhostHTTPSPort,omitempty" validate:"gte=0,lte=65535"`
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
// requests on one single port. If it's not - it will listen on this value for
// HTTP CONNECT requests.
TCPMuxHTTPConnectPort int `json:"tcpmuxHTTPConnectPort,omitempty" validate:"gte=0,lte=65535"`
// If TCPMuxPassthrough is true, frps won't do any update on traffic.
TCPMuxPassthrough bool `json:"tcpmuxPassthrough,omitempty"`
// 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".
SubDomainHost string `json:"subdomainHost,omitempty"`
// Custom404Page specifies a path to a custom 404 page to display. If this
// value is "", a default page will be displayed.
Custom404Page string `json:"custom404Page,omitempty"`
WebServer WebServerConfig `json:"webServer,omitempty"`
// EnablePrometheus will export prometheus metrics on webserver address
// in /metrics api.
EnablePrometheus bool `json:"enablePrometheus,omitempty"`
Log LogConfig `json:"log,omitempty"`
Transport ServerTransportConfig `json:"transport,omitempty"`
TLS TLSServerConfig `json:"tls,omitempty"`
// DetailedErrorsToClient defines whether to send the specific error (with
// debug info) to frpc. By default, this value is true.
DetailedErrorsToClient *bool `json:"detailedErrorsToClient,omitempty"`
// MaxPortsPerClient specifies the maximum number of ports a single client
// may proxy to. If this value is 0, no limit will be applied.
MaxPortsPerClient int64 `json:"maxPortsPerClient,omitempty"`
// UserConnTimeout specifies the maximum time to wait for a work
// connection. By default, this value is 10.
UserConnTimeout int64 `json:"userConnTimeout,omitempty"`
// UDPPacketSize specifies the UDP packet size
// By default, this value is 1500
UDPPacketSize int64 `json:"udpPacketSize,omitempty"`
// NatHoleAnalysisDataReserveHours specifies the hours to reserve nat hole analysis data.
NatHoleAnalysisDataReserveHours int64 `json:"natholeAnalysisDataReserveHours,omitempty"`
AllowPorts []types.PortsRange `json:"allowPorts,omitempty"`
HTTPPlugins []HTTPPluginOptions `json:"httpPlugins,omitempty"`
}
func (c *ServerConfig) Complete() {
c.Auth.Complete()
c.Log.Complete()
c.Transport.Complete()
c.WebServer.Complete()
c.BindAddr = util.EmptyOr(c.BindAddr, "0.0.0.0")
c.BindPort = util.EmptyOr(c.BindPort, 7000)
if c.ProxyBindAddr == "" {
c.ProxyBindAddr = c.BindAddr
}
if c.TLS.TrustedCaFile != "" {
c.TLS.Force = true
}
if c.WebServer.Port > 0 {
c.WebServer.Addr = util.EmptyOr(c.WebServer.Addr, "0.0.0.0")
}
c.VhostHTTPTimeout = util.EmptyOr(c.VhostHTTPTimeout, 60)
c.DetailedErrorsToClient = util.EmptyOr(c.DetailedErrorsToClient, lo.ToPtr(true))
c.UserConnTimeout = util.EmptyOr(c.UserConnTimeout, 10)
c.UDPPacketSize = util.EmptyOr(c.UDPPacketSize, 1500)
c.NatHoleAnalysisDataReserveHours = util.EmptyOr(c.NatHoleAnalysisDataReserveHours, 7*24)
}
type AuthServerConfig struct {
Method string `json:"method,omitempty"`
AdditionalAuthScopes []AuthScope `json:"additionalAuthScopes,omitempty"`
Token string `json:"token,omitempty"`
OIDC AuthOIDCServerConfig `json:"oidc,omitempty"`
}
func (c *AuthServerConfig) Complete() {
c.Method = util.EmptyOr(c.Method, "token")
}
type AuthOIDCServerConfig struct {
// Issuer specifies the issuer to verify OIDC tokens with. This issuer
// will be used to load public keys to verify signature and will be compared
// with the issuer claim in the OIDC token.
Issuer string `json:"issuer,omitempty"`
// Audience specifies the audience OIDC tokens should contain when validated.
// If this value is empty, audience ("client ID") verification will be skipped.
Audience string `json:"audience,omitempty"`
// SkipExpiryCheck specifies whether to skip checking if the OIDC token is
// expired.
SkipExpiryCheck bool `json:"skipExpiryCheck,omitempty"`
// SkipIssuerCheck specifies whether to skip checking if the OIDC token's
// issuer claim matches the issuer specified in OidcIssuer.
SkipIssuerCheck bool `json:"skipIssuerCheck,omitempty"`
}
type ServerTransportConfig struct {
// 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:"tcpMux,omitempty"`
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
TCPMuxKeepaliveInterval int64 `json:"tcpMuxKeepaliveInterval,omitempty"`
// TCPKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
// If negative, keep-alive probes are disabled.
TCPKeepAlive int64 `json:"tcpKeepalive,omitempty"`
// MaxPoolCount specifies the maximum pool size for each proxy. By default,
// this value is 5.
MaxPoolCount int64 `json:"maxPoolCount,omitempty"`
// 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. Set negative value to disable it.
HeartbeatTimeout int64 `json:"heartbeatTimeout,omitempty"`
// QUIC options.
QUIC QUICOptions `json:"quic,omitempty"`
}
func (c *ServerTransportConfig) Complete() {
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
c.TCPKeepAlive = util.EmptyOr(c.TCPKeepAlive, 7200)
c.MaxPoolCount = util.EmptyOr(c.MaxPoolCount, 5)
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
c.QUIC.Complete()
}
type TLSServerConfig struct {
// Force specifies whether to only accept TLS-encrypted connections.
Force bool `json:"force,omitempty"`
TLSConfig
}

View File

@ -0,0 +1,32 @@
// Copyright 2023 The frp Authors
//
// 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 v1
import (
"testing"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
)
func TestServerConfigComplete(t *testing.T) {
require := require.New(t)
c := &ServerConfig{}
c.Complete()
require.Equal("token", c.Auth.Method)
require.Equal(true, lo.FromPtr(c.Transport.TCPMux))
require.Equal(true, lo.FromPtr(c.DetailedErrorsToClient))
}

View File

@ -0,0 +1,90 @@
// Copyright 2023 The frp Authors
//
// 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 validation
import (
"fmt"
"os"
"path/filepath"
"github.com/samber/lo"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
var (
warnings Warning
errs error
)
if c.Transport.HeartbeatTimeout > 0 && c.Transport.HeartbeatInterval > 0 {
if c.Transport.HeartbeatTimeout < c.Transport.HeartbeatInterval {
errs = AppendError(errs, fmt.Errorf("invalid transport.heartbeatTimeout, heartbeat timeout should not less than heartbeat interval"))
}
}
if !lo.FromPtr(c.Transport.TLS.Enable) {
checkTLSConfig := func(name string, value string) Warning {
if value != "" {
return fmt.Errorf("%s is invalid when transport.tls.enable is false", name)
}
return nil
}
warnings = AppendError(warnings, checkTLSConfig("transport.tls.certFile", c.Transport.TLS.CertFile))
warnings = AppendError(warnings, checkTLSConfig("transport.tls.keyFile", c.Transport.TLS.KeyFile))
warnings = AppendError(warnings, checkTLSConfig("transport.tls.trustedCaFile", c.Transport.TLS.TrustedCaFile))
}
if !lo.Contains([]string{"tcp", "kcp", "quic", "websocket", "wss"}, c.Transport.Protocol) {
errs = AppendError(errs, fmt.Errorf("invalid transport.protocol, only support tcp, kcp, quic, websocket, wss"))
}
for _, f := range c.IncludeConfigFiles {
absDir, err := filepath.Abs(filepath.Dir(f))
if err != nil {
errs = AppendError(errs, fmt.Errorf("include: parse directory of %s failed: %v", f, err))
continue
}
if _, err := os.Stat(absDir); os.IsNotExist(err) {
errs = AppendError(errs, fmt.Errorf("include: directory of %s not exist", f))
}
}
return warnings, errs
}
func ValidateAllClientConfig(c *v1.ClientCommonConfig, pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) (Warning, error) {
var warnings Warning
if c != nil {
warning, err := ValidateClientCommonConfig(c)
warnings = AppendError(warnings, warning)
if err != nil {
return err, warnings
}
}
for _, c := range pxyCfgs {
if err := ValidateProxyConfigurerForClient(c); err != nil {
return warnings, fmt.Errorf("proxy %s: %v", c.GetBaseConfig().Name, err)
}
}
for _, c := range visitorCfgs {
if err := ValidateVisitorConfigurer(c); err != nil {
return warnings, fmt.Errorf("visitor %s: %v", c.GetBaseConfig().Name, err)
}
}
return warnings, nil
}

View File

@ -0,0 +1,41 @@
// Copyright 2023 The frp Authors
//
// 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 validation
import (
"fmt"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func validateWebServerConfig(c *v1.WebServerConfig) error {
if c.TLS != nil {
if c.TLS.CertFile == "" {
return fmt.Errorf("tls.certFile must be specified when tls is enabled")
}
if c.TLS.KeyFile == "" {
return fmt.Errorf("tls.keyFile must be specified when tls is enabled")
}
}
return nil
}
// ValidatePort checks that the network port is in range
func ValidatePort(port int) error {
if 0 <= port && port <= 65535 {
return nil
}
return fmt.Errorf("port number %d must be in the range 0..65535", port)
}

View File

@ -0,0 +1,72 @@
// Copyright 2023 The frp Authors
//
// 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 validation
import (
"errors"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func ValidateClientPluginOptions(c v1.ClientPluginOptions) error {
switch v := c.(type) {
case *v1.HTTP2HTTPSPluginOptions:
return validateHTTP2HTTPSPluginOptions(v)
case *v1.HTTPS2HTTPPluginOptions:
return validateHTTPS2HTTPPluginOptions(v)
case *v1.HTTPS2HTTPSPluginOptions:
return validateHTTPS2HTTPSPluginOptions(v)
case *v1.StaticFilePluginOptions:
return validateStaticFilePluginOptions(v)
case *v1.UnixDomainSocketPluginOptions:
return validateUnixDomainSocketPluginOptions(v)
}
return nil
}
func validateHTTP2HTTPSPluginOptions(c *v1.HTTP2HTTPSPluginOptions) error {
if c.LocalAddr == "" {
return errors.New("localAddr is required")
}
return nil
}
func validateHTTPS2HTTPPluginOptions(c *v1.HTTPS2HTTPPluginOptions) error {
if c.LocalAddr == "" {
return errors.New("localAddr is required")
}
return nil
}
func validateHTTPS2HTTPSPluginOptions(c *v1.HTTPS2HTTPSPluginOptions) error {
if c.LocalAddr == "" {
return errors.New("localAddr is required")
}
return nil
}
func validateStaticFilePluginOptions(c *v1.StaticFilePluginOptions) error {
if c.LocalPath == "" {
return errors.New("localPath is required")
}
return nil
}
func validateUnixDomainSocketPluginOptions(c *v1.UnixDomainSocketPluginOptions) error {
if c.UnixPath == "" {
return errors.New("unixPath is required")
}
return nil
}

View File

@ -0,0 +1,234 @@
// Copyright 2023 The frp Authors
//
// 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 validation
import (
"errors"
"fmt"
"strings"
"github.com/samber/lo"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/consts"
)
func validateProxyBaseConfigForClient(c *v1.ProxyBaseConfig) error {
if c.Name == "" {
return errors.New("name should not be empty")
}
if !lo.Contains([]string{"", "v1", "v2"}, c.Transport.ProxyProtocolVersion) {
return fmt.Errorf("not support proxy protocol version: %s", c.Transport.ProxyProtocolVersion)
}
if !lo.Contains([]string{"client", "server"}, c.Transport.BandwidthLimitMode) {
return fmt.Errorf("bandwidth limit mode should be client or server")
}
if c.Plugin.Type == "" {
if err := ValidatePort(c.LocalPort); err != nil {
return fmt.Errorf("localPort: %v", err)
}
}
if !lo.Contains([]string{"", "tcp", "http"}, c.HealthCheck.Type) {
return fmt.Errorf("not support health check type: %s", c.HealthCheck.Type)
}
if c.HealthCheck.Type != "" {
if c.HealthCheck.Type == "http" &&
c.HealthCheck.Path == "" {
return fmt.Errorf("health check path should not be empty")
}
}
if c.Plugin.Type != "" {
if err := ValidateClientPluginOptions(c.Plugin.ClientPluginOptions); err != nil {
return fmt.Errorf("plugin %s: %v", c.Plugin.Type, err)
}
}
return nil
}
func validateProxyBaseConfigForServer(c *v1.ProxyBaseConfig, s *v1.ServerConfig) error {
return nil
}
func validateDomainConfigForClient(c *v1.DomainConfig) error {
if c.SubDomain == "" && len(c.CustomDomains) == 0 {
return errors.New("subdomain and custom domains should not be both empty")
}
return nil
}
func validateDomainConfigForServer(c *v1.DomainConfig, s *v1.ServerConfig) error {
for _, domain := range c.CustomDomains {
if s.SubDomainHost != "" && len(strings.Split(s.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
if strings.Contains(domain, s.SubDomainHost) {
return fmt.Errorf("custom domain [%s] should not belong to subdomain host [%s]", domain, s.SubDomainHost)
}
}
}
if c.SubDomain != "" {
if s.SubDomainHost == "" {
return errors.New("subdomain is not supported because this feature is not enabled in server")
}
if strings.Contains(c.SubDomain, ".") || strings.Contains(c.SubDomain, "*") {
return errors.New("'.' and '*' are not supported in subdomain")
}
}
return nil
}
func ValidateProxyConfigurerForClient(c v1.ProxyConfigurer) error {
base := c.GetBaseConfig()
if err := validateProxyBaseConfigForClient(base); err != nil {
return err
}
switch v := c.(type) {
case *v1.TCPProxyConfig:
return validateTCPProxyConfigForClient(v)
case *v1.UDPProxyConfig:
return validateUDPProxyConfigForClient(v)
case *v1.TCPMuxProxyConfig:
return validateTCPMuxProxyConfigForClient(v)
case *v1.HTTPProxyConfig:
return validateHTTPProxyConfigForClient(v)
case *v1.HTTPSProxyConfig:
return validateHTTPSProxyConfigForClient(v)
case *v1.STCPProxyConfig:
return validateSTCPProxyConfigForClient(v)
case *v1.XTCPProxyConfig:
return validateXTCPProxyConfigForClient(v)
case *v1.SUDPProxyConfig:
return validateSUDPProxyConfigForClient(v)
}
return errors.New("unknown proxy config type")
}
func validateTCPProxyConfigForClient(c *v1.TCPProxyConfig) error {
return nil
}
func validateUDPProxyConfigForClient(c *v1.UDPProxyConfig) error {
return nil
}
func validateTCPMuxProxyConfigForClient(c *v1.TCPMuxProxyConfig) error {
if err := validateDomainConfigForClient(&c.DomainConfig); err != nil {
return err
}
if !lo.Contains([]string{consts.HTTPConnectTCPMultiplexer}, c.Multiplexer) {
return fmt.Errorf("not support multiplexer: %s", c.Multiplexer)
}
return nil
}
func validateHTTPProxyConfigForClient(c *v1.HTTPProxyConfig) error {
return validateDomainConfigForClient(&c.DomainConfig)
}
func validateHTTPSProxyConfigForClient(c *v1.HTTPSProxyConfig) error {
return validateDomainConfigForClient(&c.DomainConfig)
}
func validateSTCPProxyConfigForClient(c *v1.STCPProxyConfig) error {
return nil
}
func validateXTCPProxyConfigForClient(c *v1.XTCPProxyConfig) error {
return nil
}
func validateSUDPProxyConfigForClient(c *v1.SUDPProxyConfig) error {
return nil
}
func ValidateProxyConfigurerForServer(c v1.ProxyConfigurer, s *v1.ServerConfig) error {
base := c.GetBaseConfig()
if err := validateProxyBaseConfigForServer(base, s); err != nil {
return err
}
switch v := c.(type) {
case *v1.TCPProxyConfig:
return validateTCPProxyConfigForServer(v, s)
case *v1.UDPProxyConfig:
return validateUDPProxyConfigForServer(v, s)
case *v1.TCPMuxProxyConfig:
return validateTCPMuxProxyConfigForServer(v, s)
case *v1.HTTPProxyConfig:
return validateHTTPProxyConfigForServer(v, s)
case *v1.HTTPSProxyConfig:
return validateHTTPSProxyConfigForServer(v, s)
case *v1.STCPProxyConfig:
return validateSTCPProxyConfigForServer(v, s)
case *v1.XTCPProxyConfig:
return validateXTCPProxyConfigForServer(v, s)
case *v1.SUDPProxyConfig:
return validateSUDPProxyConfigForServer(v, s)
default:
return errors.New("unknown proxy config type")
}
}
func validateTCPProxyConfigForServer(c *v1.TCPProxyConfig, s *v1.ServerConfig) error {
return nil
}
func validateUDPProxyConfigForServer(c *v1.UDPProxyConfig, s *v1.ServerConfig) error {
return nil
}
func validateTCPMuxProxyConfigForServer(c *v1.TCPMuxProxyConfig, s *v1.ServerConfig) error {
if c.Multiplexer == consts.HTTPConnectTCPMultiplexer &&
s.TCPMuxHTTPConnectPort == 0 {
return fmt.Errorf("tcpmux with multiplexer httpconnect not supported because this feature is not enabled in server")
}
return validateDomainConfigForServer(&c.DomainConfig, s)
}
func validateHTTPProxyConfigForServer(c *v1.HTTPProxyConfig, s *v1.ServerConfig) error {
if s.VhostHTTPPort == 0 {
return fmt.Errorf("type [http] not supported when vhost http port is not set")
}
return validateDomainConfigForServer(&c.DomainConfig, s)
}
func validateHTTPSProxyConfigForServer(c *v1.HTTPSProxyConfig, s *v1.ServerConfig) error {
if s.VhostHTTPSPort == 0 {
return fmt.Errorf("type [https] not supported when vhost https port is not set")
}
return validateDomainConfigForServer(&c.DomainConfig, s)
}
func validateSTCPProxyConfigForServer(c *v1.STCPProxyConfig, s *v1.ServerConfig) error {
return nil
}
func validateXTCPProxyConfigForServer(c *v1.XTCPProxyConfig, s *v1.ServerConfig) error {
return nil
}
func validateSUDPProxyConfigForServer(c *v1.SUDPProxyConfig, s *v1.ServerConfig) error {
return nil
}

View File

@ -0,0 +1,37 @@
// Copyright 2023 The frp Authors
//
// 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 validation
import (
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func ValidateServerConfig(c *v1.ServerConfig) (Warning, error) {
var (
warnings Warning
errs error
)
if err := validateWebServerConfig(&c.WebServer); err != nil {
errs = AppendError(errs, err)
}
errs = AppendError(errs, ValidatePort(c.BindPort))
errs = AppendError(errs, ValidatePort(c.KCPBindPort))
errs = AppendError(errs, ValidatePort(c.QUICBindPort))
errs = AppendError(errs, ValidatePort(c.VhostHTTPPort))
errs = AppendError(errs, ValidatePort(c.VhostHTTPSPort))
errs = AppendError(errs, ValidatePort(c.TCPMuxHTTPConnectPort))
return warnings, errs
}

View File

@ -0,0 +1,28 @@
// Copyright 2023 The frp Authors
//
// 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 validation
import (
"errors"
)
type Warning error
func AppendError(err error, errs ...error) error {
if len(errs) == 0 {
return err
}
return errors.Join(append([]error{err}, errs...)...)
}

View File

@ -0,0 +1,59 @@
// Copyright 2023 The frp Authors
//
// 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 validation
import (
"errors"
"fmt"
"github.com/samber/lo"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func ValidateVisitorConfigurer(c v1.VisitorConfigurer) error {
base := c.GetBaseConfig()
if err := validateVisitorBaseConfig(base); err != nil {
return err
}
switch v := c.(type) {
case *v1.STCPVisitorConfig:
case *v1.SUDPVisitorConfig:
case *v1.XTCPVisitorConfig:
return validateXTCPVisitorConfig(v)
default:
return errors.New("unknown visitor config type")
}
return nil
}
func validateVisitorBaseConfig(c *v1.VisitorBaseConfig) error {
if c.Name == "" {
return errors.New("name should not be empty")
}
if c.BindPort == 0 {
return errors.New("bind port is required")
}
return nil
}
func validateXTCPVisitorConfig(c *v1.XTCPVisitorConfig) error {
if !lo.Contains([]string{"kcp", "quic"}, c.Protocol) {
return fmt.Errorf("protocol should be kcp or quic")
}
return nil
}

155
pkg/config/v1/visitor.go Normal file
View File

@ -0,0 +1,155 @@
// Copyright 2023 The frp Authors
//
// 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 v1
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"github.com/samber/lo"
"github.com/fatedier/frp/pkg/consts"
"github.com/fatedier/frp/pkg/util/util"
)
type VisitorTransport struct {
UseEncryption bool `json:"useEncryption,omitempty"`
UseCompression bool `json:"useCompression,omitempty"`
}
type VisitorBaseConfig struct {
Name string `json:"name"`
Type string `json:"type"`
Transport VisitorTransport `json:"transport,omitempty"`
SecretKey string `json:"sk,omitempty"`
// if the server user is not set, it defaults to the current user
ServerUser string `json:"serverUser,omitempty"`
ServerName string `json:"serverName,omitempty"`
BindAddr string `json:"bindAddr,omitempty"`
// BindPort is the port that visitor listens on.
// It can be less than 0, it means don't bind to the port and only receive connections redirected from
// other visitors. (This is not supported for SUDP now)
BindPort int `json:"bindPort,omitempty"`
}
func (c *VisitorBaseConfig) GetBaseConfig() *VisitorBaseConfig {
return c
}
func (c *VisitorBaseConfig) Complete(g *ClientCommonConfig) {
if c.BindAddr == "" {
c.BindAddr = "127.0.0.1"
}
namePrefix := ""
if g.User != "" {
namePrefix = g.User + "."
}
c.Name = namePrefix + c.Name
if c.ServerUser != "" {
c.ServerName = c.ServerUser + "." + c.ServerName
} else {
c.ServerName = namePrefix + c.ServerName
}
}
type VisitorConfigurer interface {
Complete(*ClientCommonConfig)
GetBaseConfig() *VisitorBaseConfig
}
var visitorConfigTypeMap = map[string]reflect.Type{
consts.STCPProxy: reflect.TypeOf(STCPVisitorConfig{}),
consts.XTCPProxy: reflect.TypeOf(XTCPVisitorConfig{}),
consts.SUDPProxy: reflect.TypeOf(SUDPVisitorConfig{}),
}
type TypedVisitorConfig struct {
Type string `json:"type"`
VisitorConfigurer
}
func (c *TypedVisitorConfig) UnmarshalJSON(b []byte) error {
if len(b) == 4 && string(b) == "null" {
return errors.New("type is required")
}
typeStruct := struct {
Type string `json:"type"`
}{}
if err := json.Unmarshal(b, &typeStruct); err != nil {
return err
}
c.Type = typeStruct.Type
configurer := NewVisitorConfigurerByType(typeStruct.Type)
if configurer == nil {
return fmt.Errorf("unknown visitor type: %s" + typeStruct.Type)
}
if err := json.Unmarshal(b, configurer); err != nil {
return err
}
c.VisitorConfigurer = configurer
return nil
}
func NewVisitorConfigurerByType(t string) VisitorConfigurer {
v, ok := visitorConfigTypeMap[t]
if !ok {
return nil
}
return reflect.New(v).Interface().(VisitorConfigurer)
}
var _ VisitorConfigurer = &STCPVisitorConfig{}
type STCPVisitorConfig struct {
VisitorBaseConfig
}
var _ VisitorConfigurer = &SUDPVisitorConfig{}
type SUDPVisitorConfig struct {
VisitorBaseConfig
}
var _ VisitorConfigurer = &XTCPVisitorConfig{}
type XTCPVisitorConfig struct {
VisitorBaseConfig
Protocol string `json:"protocol,omitempty"`
KeepTunnelOpen bool `json:"keepTunnelOpen,omitempty"`
MaxRetriesAnHour int `json:"maxRetriesAnHour,omitempty"`
MinRetryInterval int `json:"minRetryInterval,omitempty"`
FallbackTo string `json:"fallbackTo,omitempty"`
FallbackTimeoutMs int `json:"fallbackTimeoutMs,omitempty"`
}
func (c *XTCPVisitorConfig) Complete(g *ClientCommonConfig) {
c.VisitorBaseConfig.Complete(g)
c.Protocol = util.EmptyOr(c.Protocol, "quic")
c.MaxRetriesAnHour = util.EmptyOr(c.MaxRetriesAnHour, 8)
c.MinRetryInterval = util.EmptyOr(c.MinRetryInterval, 90)
c.FallbackTimeoutMs = util.EmptyOr(c.FallbackTimeoutMs, 1000)
if c.FallbackTo != "" {
c.FallbackTo = lo.Ternary(g.User == "", "", g.User+".") + c.FallbackTo
}
}

View File

@ -1,112 +0,0 @@
// Copyright 2020 The frp Authors
//
// 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 (
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
"github.com/fatedier/frp/pkg/consts"
)
const testVisitorPrefix = "test."
func Test_Visitor_Interface(_ *testing.T) {
for name := range visitorConfTypeMap {
DefaultVisitorConf(name)
}
}
func Test_Visitor_UnmarshalFromIni(t *testing.T) {
assert := assert.New(t)
testcases := []struct {
sname string
source []byte
expected VisitorConf
}{
{
sname: "secret_tcp_visitor",
source: []byte(`
[secret_tcp_visitor]
role = visitor
type = stcp
server_name = secret_tcp
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 9000
use_encryption = false
use_compression = false
`),
expected: &STCPVisitorConf{
BaseVisitorConf: BaseVisitorConf{
ProxyName: testVisitorPrefix + "secret_tcp_visitor",
ProxyType: consts.STCPProxy,
Role: "visitor",
Sk: "abcdefg",
ServerName: testVisitorPrefix + "secret_tcp",
BindAddr: "127.0.0.1",
BindPort: 9000,
},
},
},
{
sname: "p2p_tcp_visitor",
source: []byte(`
[p2p_tcp_visitor]
role = visitor
type = xtcp
server_name = p2p_tcp
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 9001
use_encryption = false
use_compression = false
`),
expected: &XTCPVisitorConf{
BaseVisitorConf: BaseVisitorConf{
ProxyName: testVisitorPrefix + "p2p_tcp_visitor",
ProxyType: consts.XTCPProxy,
Role: "visitor",
Sk: "abcdefg",
ServerName: testProxyPrefix + "p2p_tcp",
BindAddr: "127.0.0.1",
BindPort: 9001,
},
Protocol: "quic",
MaxRetriesAnHour: 8,
MinRetryInterval: 90,
FallbackTimeoutMs: 1000,
},
},
}
for _, c := range testcases {
f, err := ini.LoadSources(testLoadOptions, c.source)
assert.NoError(err)
visitorType := f.Section(c.sname).Key("type").String()
assert.NotEmpty(visitorType)
actual := DefaultVisitorConf(visitorType)
assert.NotNil(actual)
err = actual.UnmarshalFromIni(testVisitorPrefix, c.sname, f.Section(c.sname))
assert.NoError(err)
assert.Equal(c.expected, actual)
}
}

View File

@ -16,55 +16,34 @@ package plugin
import (
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"strings"
v1 "github.com/fatedier/frp/pkg/config/v1"
utilnet "github.com/fatedier/frp/pkg/util/net"
)
const PluginHTTP2HTTPS = "http2https"
func init() {
Register(PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin)
Register(v1.PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin)
}
type HTTP2HTTPSPlugin struct {
hostHeaderRewrite string
localAddr string
headers map[string]string
opts *v1.HTTP2HTTPSPluginOptions
l *Listener
s *http.Server
}
func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) {
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 localAddr == "" {
return nil, fmt.Errorf("plugin_local_addr is required")
}
func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.HTTP2HTTPSPluginOptions)
listener := NewProxyListener()
p := &HTTP2HTTPSPlugin{
localAddr: localAddr,
hostHeaderRewrite: hostHeaderRewrite,
headers: headers,
l: listener,
opts: opts,
l: listener,
}
tr := &http.Transport{
@ -74,11 +53,11 @@ func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) {
rp := &httputil.ReverseProxy{
Director: func(req *http.Request) {
req.URL.Scheme = "https"
req.URL.Host = p.localAddr
if p.hostHeaderRewrite != "" {
req.Host = p.hostHeaderRewrite
req.URL.Host = p.opts.LocalAddr
if p.opts.HostHeaderRewrite != "" {
req.Host = p.opts.HostHeaderRewrite
}
for k, v := range p.headers {
for k, v := range p.opts.RequestHeaders.Set {
req.Header.Set(k, v)
}
},
@ -103,7 +82,7 @@ func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _
}
func (p *HTTP2HTTPSPlugin) Name() string {
return PluginHTTP2HTTPS
return v1.PluginHTTP2HTTPS
}
func (p *HTTP2HTTPSPlugin) Close() error {

View File

@ -26,32 +26,29 @@ import (
libio "github.com/fatedier/golib/io"
libnet "github.com/fatedier/golib/net"
v1 "github.com/fatedier/frp/pkg/config/v1"
utilnet "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/util"
)
const PluginHTTPProxy = "http_proxy"
func init() {
Register(PluginHTTPProxy, NewHTTPProxyPlugin)
Register(v1.PluginHTTPProxy, NewHTTPProxyPlugin)
}
type HTTPProxy struct {
l *Listener
s *http.Server
AuthUser string
AuthPasswd string
opts *v1.HTTPProxyPluginOptions
l *Listener
s *http.Server
}
func NewHTTPProxyPlugin(params map[string]string) (Plugin, error) {
user := params["plugin_http_user"]
passwd := params["plugin_http_passwd"]
func NewHTTPProxyPlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.HTTPProxyPluginOptions)
listener := NewProxyListener()
hp := &HTTPProxy{
l: listener,
AuthUser: user,
AuthPasswd: passwd,
l: listener,
opts: opts,
}
hp.s = &http.Server{
@ -65,7 +62,7 @@ func NewHTTPProxyPlugin(params map[string]string) (Plugin, error) {
}
func (hp *HTTPProxy) Name() string {
return PluginHTTPProxy
return v1.PluginHTTPProxy
}
func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ []byte) {
@ -162,7 +159,7 @@ func (hp *HTTPProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
}
func (hp *HTTPProxy) Auth(req *http.Request) bool {
if hp.AuthUser == "" && hp.AuthPasswd == "" {
if hp.opts.HTTPUser == "" && hp.opts.HTTPPassword == "" {
return true
}
@ -181,8 +178,8 @@ func (hp *HTTPProxy) Auth(req *http.Request) bool {
return false
}
if !util.ConstantTimeEqString(pair[0], hp.AuthUser) ||
!util.ConstantTimeEqString(pair[1], hp.AuthPasswd) {
if !util.ConstantTimeEqString(pair[0], hp.opts.HTTPUser) ||
!util.ConstantTimeEqString(pair[1], hp.opts.HTTPPassword) {
time.Sleep(200 * time.Millisecond)
return false
}

View File

@ -21,67 +21,40 @@ import (
"net"
"net/http"
"net/http/httputil"
"strings"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/transport"
utilnet "github.com/fatedier/frp/pkg/util/net"
)
const PluginHTTPS2HTTP = "https2http"
func init() {
Register(PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin)
Register(v1.PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin)
}
type HTTPS2HTTPPlugin struct {
crtPath string
keyPath string
hostHeaderRewrite string
localAddr string
headers map[string]string
opts *v1.HTTPS2HTTPPluginOptions
l *Listener
s *http.Server
}
func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
crtPath := params["plugin_crt_path"]
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 localAddr == "" {
return nil, fmt.Errorf("plugin_local_addr is required")
}
func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.HTTPS2HTTPPluginOptions)
listener := NewProxyListener()
p := &HTTPS2HTTPPlugin{
crtPath: crtPath,
keyPath: keyPath,
localAddr: localAddr,
hostHeaderRewrite: hostHeaderRewrite,
headers: headers,
l: listener,
opts: opts,
l: listener,
}
rp := &httputil.ReverseProxy{
Director: func(req *http.Request) {
req.URL.Scheme = "http"
req.URL.Host = p.localAddr
if p.hostHeaderRewrite != "" {
req.Host = p.hostHeaderRewrite
req.URL.Host = p.opts.LocalAddr
if p.opts.HostHeaderRewrite != "" {
req.Host = p.opts.HostHeaderRewrite
}
for k, v := range p.headers {
for k, v := range p.opts.RequestHeaders.Set {
req.Header.Set(k, v)
}
},
@ -95,7 +68,7 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
tlsConfig *tls.Config
err error
)
if crtPath != "" || keyPath != "" {
if opts.CrtPath != "" || opts.KeyPath != "" {
tlsConfig, err = p.genTLSConfig()
} else {
tlsConfig, err = transport.NewServerTLSConfig("", "", "")
@ -113,7 +86,7 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
}
func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(p.crtPath, p.keyPath)
cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
if err != nil {
return nil, err
}
@ -128,7 +101,7 @@ func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _
}
func (p *HTTPS2HTTPPlugin) Name() string {
return PluginHTTPS2HTTP
return v1.PluginHTTPS2HTTP
}
func (p *HTTPS2HTTPPlugin) Close() error {

View File

@ -21,57 +21,31 @@ import (
"net"
"net/http"
"net/http/httputil"
"strings"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/transport"
utilnet "github.com/fatedier/frp/pkg/util/net"
)
const PluginHTTPS2HTTPS = "https2https"
func init() {
Register(PluginHTTPS2HTTPS, NewHTTPS2HTTPSPlugin)
Register(v1.PluginHTTPS2HTTPS, NewHTTPS2HTTPSPlugin)
}
type HTTPS2HTTPSPlugin struct {
crtPath string
keyPath string
hostHeaderRewrite string
localAddr string
headers map[string]string
opts *v1.HTTPS2HTTPSPluginOptions
l *Listener
s *http.Server
}
func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
crtPath := params["plugin_crt_path"]
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 localAddr == "" {
return nil, fmt.Errorf("plugin_local_addr is required")
}
func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.HTTPS2HTTPSPluginOptions)
listener := NewProxyListener()
p := &HTTPS2HTTPSPlugin{
crtPath: crtPath,
keyPath: keyPath,
localAddr: localAddr,
hostHeaderRewrite: hostHeaderRewrite,
headers: headers,
l: listener,
opts: opts,
l: listener,
}
tr := &http.Transport{
@ -81,11 +55,11 @@ func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
rp := &httputil.ReverseProxy{
Director: func(req *http.Request) {
req.URL.Scheme = "https"
req.URL.Host = p.localAddr
if p.hostHeaderRewrite != "" {
req.Host = p.hostHeaderRewrite
req.URL.Host = p.opts.LocalAddr
if p.opts.HostHeaderRewrite != "" {
req.Host = p.opts.HostHeaderRewrite
}
for k, v := range p.headers {
for k, v := range p.opts.RequestHeaders.Set {
req.Header.Set(k, v)
}
},
@ -100,7 +74,7 @@ func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
tlsConfig *tls.Config
err error
)
if crtPath != "" || keyPath != "" {
if opts.CrtPath != "" || opts.KeyPath != "" {
tlsConfig, err = p.genTLSConfig()
} else {
tlsConfig, err = transport.NewServerTLSConfig("", "", "")
@ -118,7 +92,7 @@ func NewHTTPS2HTTPSPlugin(params map[string]string) (Plugin, error) {
}
func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(p.crtPath, p.keyPath)
cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
if err != nil {
return nil, err
}
@ -133,7 +107,7 @@ func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _
}
func (p *HTTPS2HTTPSPlugin) Name() string {
return PluginHTTPS2HTTPS
return v1.PluginHTTPS2HTTPS
}
func (p *HTTPS2HTTPSPlugin) Close() error {

View File

@ -21,21 +21,23 @@ import (
"sync"
"github.com/fatedier/golib/errors"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
// Creators is used for create plugins to handle connections.
var creators = make(map[string]CreatorFn)
// params has prefix "plugin_"
type CreatorFn func(params map[string]string) (Plugin, error)
type CreatorFn func(options v1.ClientPluginOptions) (Plugin, error)
func Register(name string, fn CreatorFn) {
creators[name] = fn
}
func Create(name string, params map[string]string) (p Plugin, err error) {
func Create(name string, options v1.ClientPluginOptions) (p Plugin, err error) {
if fn, ok := creators[name]; ok {
p, err = fn(params)
p, err = fn(options)
} else {
err = fmt.Errorf("plugin [%s] is not registered", name)
}

View File

@ -21,28 +21,26 @@ import (
gosocks5 "github.com/armon/go-socks5"
v1 "github.com/fatedier/frp/pkg/config/v1"
utilnet "github.com/fatedier/frp/pkg/util/net"
)
const PluginSocks5 = "socks5"
func init() {
Register(PluginSocks5, NewSocks5Plugin)
Register(v1.PluginSocks5, NewSocks5Plugin)
}
type Socks5Plugin struct {
Server *gosocks5.Server
}
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
user := params["plugin_user"]
passwd := params["plugin_passwd"]
func NewSocks5Plugin(options v1.ClientPluginOptions) (p Plugin, err error) {
opts := options.(*v1.Socks5PluginOptions)
cfg := &gosocks5.Config{
Logger: log.New(io.Discard, "", log.LstdFlags),
}
if user != "" || passwd != "" {
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})
if opts.Username != "" || opts.Password != "" {
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{opts.Username: opts.Password})
}
sp := &Socks5Plugin{}
sp.Server, err = gosocks5.New(cfg)
@ -57,7 +55,7 @@ func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ []b
}
func (sp *Socks5Plugin) Name() string {
return PluginSocks5
return v1.PluginSocks5
}
func (sp *Socks5Plugin) Close() error {

View File

@ -22,51 +22,41 @@ import (
"github.com/gorilla/mux"
v1 "github.com/fatedier/frp/pkg/config/v1"
utilnet "github.com/fatedier/frp/pkg/util/net"
)
const PluginStaticFile = "static_file"
func init() {
Register(PluginStaticFile, NewStaticFilePlugin)
Register(v1.PluginStaticFile, NewStaticFilePlugin)
}
type StaticFilePlugin struct {
localPath string
stripPrefix string
httpUser string
httpPasswd string
opts *v1.StaticFilePluginOptions
l *Listener
s *http.Server
}
func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
localPath := params["plugin_local_path"]
stripPrefix := params["plugin_strip_prefix"]
httpUser := params["plugin_http_user"]
httpPasswd := params["plugin_http_passwd"]
func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.StaticFilePluginOptions)
listener := NewProxyListener()
sp := &StaticFilePlugin{
localPath: localPath,
stripPrefix: stripPrefix,
httpUser: httpUser,
httpPasswd: httpPasswd,
opts: opts,
l: listener,
}
var prefix string
if stripPrefix != "" {
prefix = "/" + stripPrefix + "/"
if opts.StripPrefix != "" {
prefix = "/" + opts.StripPrefix + "/"
} else {
prefix = "/"
}
router := mux.NewRouter()
router.Use(utilnet.NewHTTPAuthMiddleware(httpUser, httpPasswd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
router.PathPrefix(prefix).Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
router.Use(utilnet.NewHTTPAuthMiddleware(opts.HTTPUser, opts.HTTPPassword).SetAuthFailDelay(200 * time.Millisecond).Middleware)
router.PathPrefix(prefix).Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(opts.LocalPath))))).Methods("GET")
sp.s = &http.Server{
Handler: router,
}
@ -82,7 +72,7 @@ func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _
}
func (sp *StaticFilePlugin) Name() string {
return PluginStaticFile
return v1.PluginStaticFile
}
func (sp *StaticFilePlugin) Close() error {

View File

@ -15,31 +15,26 @@
package plugin
import (
"fmt"
"io"
"net"
libio "github.com/fatedier/golib/io"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
const PluginUnixDomainSocket = "unix_domain_socket"
func init() {
Register(PluginUnixDomainSocket, NewUnixDomainSocketPlugin)
Register(v1.PluginUnixDomainSocket, NewUnixDomainSocketPlugin)
}
type UnixDomainSocketPlugin struct {
UnixAddr *net.UnixAddr
}
func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
unixPath, ok := params["plugin_unix_path"]
if !ok {
err = fmt.Errorf("plugin_unix_path not found")
return
}
func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err error) {
opts := options.(*v1.UnixDomainSocketPluginOptions)
unixAddr, errRet := net.ResolveUnixAddr("unix", unixPath)
unixAddr, errRet := net.ResolveUnixAddr("unix", opts.UnixPath)
if errRet != nil {
err = errRet
return
@ -66,7 +61,7 @@ func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, _ net.Conn, e
}
func (uds *UnixDomainSocketPlugin) Name() string {
return PluginUnixDomainSocket
return v1.PluginUnixDomainSocket
}
func (uds *UnixDomainSocketPlugin) Close() error {

View File

@ -25,24 +25,18 @@ import (
"net/url"
"reflect"
"strings"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
type HTTPPluginOptions struct {
Name string `ini:"name"`
Addr string `ini:"addr"`
Path string `ini:"path"`
Ops []string `ini:"ops"`
TLSVerify bool `ini:"tls_verify"`
}
type httpPlugin struct {
options HTTPPluginOptions
options v1.HTTPPluginOptions
url string
client *http.Client
}
func NewHTTPPluginOptions(options HTTPPluginOptions) Plugin {
func NewHTTPPluginOptions(options v1.HTTPPluginOptions) Plugin {
url := fmt.Sprintf("%s%s", options.Addr, options.Path)
var client *http.Client

View File

@ -29,15 +29,14 @@ func init() {
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
}
func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
SetLogFile(logWay, logFile, maxdays, disableLogColor)
func InitLog(logFile string, logLevel string, maxdays int64, disableLogColor bool) {
SetLogFile(logFile, maxdays, disableLogColor)
SetLogLevel(logLevel)
}
// SetLogFile to configure log params
// logWay: file or console
func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
if logWay == "console" {
func SetLogFile(logFile string, maxdays int64, disableLogColor bool) {
if logFile == "console" {
params := ""
if disableLogColor {
params = `{"color": false}`

View File

@ -1,4 +1,4 @@
// Copyright 2020 guylewin, guy@lewin.co.il
// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

23
pkg/util/util/types.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2023 The frp Authors
//
// 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 util
func EmptyOr[T comparable](v T, fallback T) T {
var zero T
if zero == v {
return fallback
}
return v
}

View File

@ -26,9 +26,11 @@ import (
"github.com/fatedier/golib/control/shutdown"
"github.com/fatedier/golib/crypto"
"github.com/fatedier/golib/errors"
"github.com/samber/lo"
"github.com/fatedier/frp/pkg/auth"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
pkgerr "github.com/fatedier/frp/pkg/errors"
"github.com/fatedier/frp/pkg/msg"
plugin "github.com/fatedier/frp/pkg/plugin/server"
@ -151,7 +153,7 @@ type Control struct {
mu sync.RWMutex
// Server configuration information
serverCfg config.ServerCommonConf
serverCfg *v1.ServerConfig
xl *xlog.Logger
ctx context.Context
@ -165,11 +167,11 @@ func NewControl(
authVerifier auth.Verifier,
ctlConn net.Conn,
loginMsg *msg.Login,
serverCfg config.ServerCommonConf,
serverCfg *v1.ServerConfig,
) *Control {
poolCount := loginMsg.PoolCount
if poolCount > int(serverCfg.MaxPoolCount) {
poolCount = int(serverCfg.MaxPoolCount)
if poolCount > int(serverCfg.Transport.MaxPoolCount) {
poolCount = int(serverCfg.Transport.MaxPoolCount)
}
ctl := &Control{
rc: rc,
@ -320,7 +322,7 @@ func (ctl *Control) writer() {
defer ctl.allShutdown.Start()
defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Auth.Token))
if err != nil {
xl.Error("crypto new writer error: %v", err)
ctl.allShutdown.Start()
@ -352,7 +354,7 @@ func (ctl *Control) reader() {
defer ctl.allShutdown.Start()
defer ctl.readerShutdown.Done()
encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token))
encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Auth.Token))
for {
m, err := msg.ReadMsg(encReader)
if err != nil {
@ -400,7 +402,7 @@ func (ctl *Control) stoper() {
for _, pxy := range ctl.proxies {
pxy.Close()
ctl.pxyManager.Del(pxy.GetName())
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseConfig().ProxyType)
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConfigurer().GetBaseConfig().Type)
notifyContent := &plugin.CloseProxyContent{
User: plugin.UserInfo{
@ -450,7 +452,7 @@ func (ctl *Control) manager() {
var heartbeatCh <-chan time.Time
// Don't need application heartbeat if TCPMux is enabled,
// yamux will do same thing.
if !ctl.serverCfg.TCPMux && ctl.serverCfg.HeartbeatTimeout > 0 {
if !lo.FromPtr(ctl.serverCfg.Transport.TCPMux) && ctl.serverCfg.Transport.HeartbeatTimeout > 0 {
heartbeat := time.NewTicker(time.Second)
defer heartbeat.Stop()
heartbeatCh = heartbeat.C
@ -459,7 +461,7 @@ func (ctl *Control) manager() {
for {
select {
case <-heartbeatCh:
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartbeatTimeout)*time.Second {
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second {
xl.Warn("heartbeat timeout")
return
}
@ -491,7 +493,8 @@ func (ctl *Control) manager() {
}
if err != nil {
xl.Warn("new proxy [%s] type [%s] error: %v", m.ProxyName, m.ProxyType, err)
resp.Error = util.GenerateResponseErrorString(fmt.Sprintf("new proxy [%s] error", m.ProxyName), err, ctl.serverCfg.DetailedErrorsToClient)
resp.Error = util.GenerateResponseErrorString(fmt.Sprintf("new proxy [%s] error", m.ProxyName),
err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient))
} else {
resp.RemoteAddr = remoteAddr
xl.Info("new proxy [%s] type [%s] success", m.ProxyName, m.ProxyType)
@ -524,7 +527,7 @@ func (ctl *Control) manager() {
if err != nil {
xl.Warn("received invalid ping: %v", err)
ctl.sendCh <- &msg.Pong{
Error: util.GenerateResponseErrorString("invalid ping", err, ctl.serverCfg.DetailedErrorsToClient),
Error: util.GenerateResponseErrorString("invalid ping", err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient)),
}
return
}
@ -549,9 +552,9 @@ func (ctl *Control) HandleNatHoleReport(m *msg.NatHoleReport) {
}
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
var pxyConf config.ProxyConf
var pxyConf v1.ProxyConfigurer
// Load configures from NewProxy message and validate.
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg)
pxyConf, err = config.NewProxyConfigurerFromMsg(pxyMsg, ctl.serverCfg)
if err != nil {
return
}
@ -632,7 +635,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
delete(ctl.proxies, closeMsg.ProxyName)
ctl.mu.Unlock()
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseConfig().ProxyType)
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConfigurer().GetBaseConfig().Type)
notifyContent := &plugin.CloseProxyContent{
User: plugin.UserInfo{

View File

@ -39,7 +39,7 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
router.HandleFunc("/healthz", svr.Healthz)
// debug
if svr.cfg.PprofEnable {
if svr.cfg.WebServer.PprofEnable {
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
@ -49,7 +49,7 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
subRouter := router.NewRoute().Subrouter()
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
user, passwd := svr.cfg.WebServer.User, svr.cfg.WebServer.Password
subRouter.Use(utilnet.NewHTTPAuthMiddleware(user, passwd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
// metrics
@ -82,8 +82,8 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
return err
}
if svr.cfg.DashboardTLSMode {
cert, err := tls.LoadX509KeyPair(svr.cfg.DashboardTLSCertFile, svr.cfg.DashboardTLSKeyFile)
if svr.cfg.WebServer.TLS != nil {
cert, err := tls.LoadX509KeyPair(svr.cfg.WebServer.TLS.CertFile, svr.cfg.WebServer.TLS.KeyFile)
if err != nil {
return err
}

View File

@ -20,7 +20,8 @@ import (
"github.com/gorilla/mux"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/consts"
"github.com/fatedier/frp/pkg/metrics/mem"
"github.com/fatedier/frp/pkg/util/log"
@ -81,11 +82,11 @@ func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
KCPBindPort: svr.cfg.KCPBindPort,
QUICBindPort: svr.cfg.QUICBindPort,
SubdomainHost: svr.cfg.SubDomainHost,
MaxPoolCount: svr.cfg.MaxPoolCount,
MaxPoolCount: svr.cfg.Transport.MaxPoolCount,
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
HeartBeatTimeout: svr.cfg.HeartbeatTimeout,
AllowPortsStr: svr.cfg.AllowPortsStr,
TLSOnly: svr.cfg.TLSOnly,
HeartBeatTimeout: svr.cfg.Transport.HeartbeatTimeout,
AllowPortsStr: types.PortsRangeSlice(svr.cfg.AllowPorts).String(),
TLSOnly: svr.cfg.TLS.Force,
TotalTrafficIn: serverStats.TotalTrafficIn,
TotalTrafficOut: serverStats.TotalTrafficOut,
@ -99,7 +100,7 @@ func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
}
type BaseOutConf struct {
config.BaseProxyConf
v1.ProxyBaseConfig
}
type TCPOutConf struct {
@ -109,7 +110,7 @@ type TCPOutConf struct {
type TCPMuxOutConf struct {
BaseOutConf
config.DomainConf
v1.DomainConfig
Multiplexer string `json:"multiplexer"`
}
@ -120,14 +121,14 @@ type UDPOutConf struct {
type HTTPOutConf struct {
BaseOutConf
config.DomainConf
v1.DomainConfig
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"`
}
type HTTPSOutConf struct {
BaseOutConf
config.DomainConf
v1.DomainConfig
}
type STCPOutConf struct {
@ -204,7 +205,7 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
for _, ps := range proxyStats {
proxyInfo := &ProxyStatsInfo{}
if pxy, ok := svr.pxyManager.GetByName(ps.Name); ok {
content, err := json.Marshal(pxy.GetConf())
content, err := json.Marshal(pxy.GetConfigurer())
if err != nil {
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
continue
@ -278,7 +279,7 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
msg = "no proxy info found"
} else {
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
content, err := json.Marshal(pxy.GetConf())
content, err := json.Marshal(pxy.GetConfigurer())
if err != nil {
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
code = 400

View File

@ -6,6 +6,8 @@ import (
"strconv"
"sync"
"time"
"github.com/fatedier/frp/pkg/config/types"
)
const (
@ -39,7 +41,7 @@ type Manager struct {
mu sync.Mutex
}
func NewManager(netType string, bindAddr string, allowPorts map[int]struct{}) *Manager {
func NewManager(netType string, bindAddr string, allowPorts []types.PortsRange) *Manager {
pm := &Manager{
reservedPorts: make(map[string]*PortCtx),
usedPorts: make(map[int]*PortCtx),
@ -48,8 +50,14 @@ func NewManager(netType string, bindAddr string, allowPorts map[int]struct{}) *M
netType: netType,
}
if len(allowPorts) > 0 {
for port := range allowPorts {
pm.freePorts[port] = struct{}{}
for _, pair := range allowPorts {
if pair.Single > 0 {
pm.freePorts[pair.Single] = struct{}{}
} else {
for i := pair.Start; i <= pair.End; i++ {
pm.freePorts[i] = struct{}{}
}
}
}
} else {
for i := MinPort; i <= MaxPort; i++ {

View File

@ -22,7 +22,7 @@ import (
libio "github.com/fatedier/golib/io"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/util/limit"
utilnet "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/util"
@ -31,18 +31,18 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.HTTPProxyConf{}), NewHTTPProxy)
RegisterProxyFactory(reflect.TypeOf(&v1.HTTPProxyConfig{}), NewHTTPProxy)
}
type HTTPProxy struct {
*BaseProxy
cfg *config.HTTPProxyConf
cfg *v1.HTTPProxyConfig
closeFuncs []func()
}
func NewHTTPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.HTTPProxyConf)
func NewHTTPProxy(baseProxy *BaseProxy) Proxy {
unwrapped, ok := baseProxy.GetConfigurer().(*v1.HTTPProxyConfig)
if !ok {
return nil
}
@ -57,9 +57,9 @@ func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
routeConfig := vhost.RouteConfig{
RewriteHost: pxy.cfg.HostHeaderRewrite,
RouteByHTTPUser: pxy.cfg.RouteByHTTPUser,
Headers: pxy.cfg.Headers,
Headers: pxy.cfg.RequestHeaders.Set,
Username: pxy.cfg.HTTPUser,
Password: pxy.cfg.HTTPPwd,
Password: pxy.cfg.HTTPPassword,
CreateConnFn: pxy.GetRealConn,
}
@ -87,14 +87,14 @@ func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
tmpRouteConfig := routeConfig
// handle group
if pxy.cfg.Group != "" {
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
if pxy.cfg.LoadBalancer.Group != "" {
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.LoadBalancer.Group, pxy.cfg.LoadBalancer.GroupKey, routeConfig)
if err != nil {
return
}
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpRouteConfig)
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.LoadBalancer.Group, tmpRouteConfig)
})
} else {
// no group
@ -108,7 +108,7 @@ func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
}
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPPort))
xl.Info("http proxy listen for host [%s] location [%s] group [%s], routeByHTTPUser [%s]",
routeConfig.Domain, routeConfig.Location, pxy.cfg.Group, pxy.cfg.RouteByHTTPUser)
routeConfig.Domain, routeConfig.Location, pxy.cfg.LoadBalancer.Group, pxy.cfg.RouteByHTTPUser)
}
}
@ -120,14 +120,14 @@ func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
tmpRouteConfig := routeConfig
// handle group
if pxy.cfg.Group != "" {
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, routeConfig)
if pxy.cfg.LoadBalancer.Group != "" {
err = pxy.rc.HTTPGroupCtl.Register(pxy.name, pxy.cfg.LoadBalancer.Group, pxy.cfg.LoadBalancer.GroupKey, routeConfig)
if err != nil {
return
}
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpRouteConfig)
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.LoadBalancer.Group, tmpRouteConfig)
})
} else {
err = pxy.rc.HTTPReverseProxy.Register(routeConfig)
@ -141,17 +141,13 @@ func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
addrs = append(addrs, util.CanonicalAddr(tmpRouteConfig.Domain, pxy.serverCfg.VhostHTTPPort))
xl.Info("http proxy listen for host [%s] location [%s] group [%s], routeByHTTPUser [%s]",
routeConfig.Domain, routeConfig.Location, pxy.cfg.Group, pxy.cfg.RouteByHTTPUser)
routeConfig.Domain, routeConfig.Location, pxy.cfg.LoadBalancer.Group, pxy.cfg.RouteByHTTPUser)
}
}
remoteAddr = strings.Join(addrs, ",")
return
}
func (pxy *HTTPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err error) {
xl := pxy.xl
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
@ -167,14 +163,14 @@ func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err err
}
var rwc io.ReadWriteCloser = tmpConn
if pxy.cfg.UseEncryption {
rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
if pxy.cfg.Transport.UseEncryption {
rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Auth.Token))
if err != nil {
xl.Error("create encryption stream error: %v", err)
return
}
}
if pxy.cfg.UseCompression {
if pxy.cfg.Transport.UseCompression {
rwc = libio.WithCompression(rwc)
}
@ -186,13 +182,13 @@ func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err err
workConn = utilnet.WrapReadWriteCloserToConn(rwc, tmpConn)
workConn = utilnet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
metrics.Server.OpenConnection(pxy.GetName(), pxy.GetConf().GetBaseConfig().ProxyType)
metrics.Server.OpenConnection(pxy.GetName(), pxy.GetConfigurer().GetBaseConfig().Type)
return
}
func (pxy *HTTPProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
name := pxy.GetName()
proxyType := pxy.GetConf().GetBaseConfig().ProxyType
proxyType := pxy.GetConfigurer().GetBaseConfig().Type
metrics.Server.CloseConnection(name, proxyType)
metrics.Server.AddTrafficIn(name, proxyType, totalWrite)
metrics.Server.AddTrafficOut(name, proxyType, totalRead)

View File

@ -18,22 +18,22 @@ import (
"reflect"
"strings"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/vhost"
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.HTTPSProxyConf{}), NewHTTPSProxy)
RegisterProxyFactory(reflect.TypeOf(&v1.HTTPSProxyConfig{}), NewHTTPSProxy)
}
type HTTPSProxy struct {
*BaseProxy
cfg *config.HTTPSProxyConf
cfg *v1.HTTPSProxyConfig
}
func NewHTTPSProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.HTTPSProxyConf)
func NewHTTPSProxy(baseProxy *BaseProxy) Proxy {
unwrapped, ok := baseProxy.GetConfigurer().(*v1.HTTPSProxyConfig)
if !ok {
return nil
}
@ -86,10 +86,6 @@ func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) {
return
}
func (pxy *HTTPSProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HTTPSProxy) Close() {
pxy.BaseProxy.Close()
}

View File

@ -27,7 +27,8 @@ import (
libio "github.com/fatedier/golib/io"
"golang.org/x/time/rate"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/types"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
plugin "github.com/fatedier/frp/pkg/plugin/server"
"github.com/fatedier/frp/pkg/util/limit"
@ -37,9 +38,9 @@ import (
"github.com/fatedier/frp/server/metrics"
)
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, config.ProxyConf) Proxy{}
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy) Proxy{}
func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, config.ProxyConf) Proxy) {
func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy) Proxy) {
proxyFactoryRegistry[proxyConfType] = factory
}
@ -49,7 +50,7 @@ type Proxy interface {
Context() context.Context
Run() (remoteAddr string, err error)
GetName() string
GetConf() config.ProxyConf
GetConfigurer() v1.ProxyConfigurer
GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error)
GetUsedPortsNum() int
GetResourceController() *controller.ResourceController
@ -66,11 +67,11 @@ type BaseProxy struct {
usedPortsNum int
poolCount int
getWorkConnFn GetWorkConnFn
serverCfg config.ServerCommonConf
serverCfg *v1.ServerConfig
limiter *rate.Limiter
userInfo plugin.UserInfo
loginMsg *msg.Login
pxyConf config.ProxyConf
configurer v1.ProxyConfigurer
mu sync.RWMutex
xl *xlog.Logger
@ -105,6 +106,10 @@ func (pxy *BaseProxy) GetLimiter() *rate.Limiter {
return pxy.limiter
}
func (pxy *BaseProxy) GetConfigurer() v1.ProxyConfigurer {
return pxy.configurer
}
func (pxy *BaseProxy) Close() {
xl := xlog.FromContextSafe(pxy.ctx)
xl.Info("proxy closing")
@ -209,13 +214,13 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
defer userConn.Close()
serverCfg := pxy.serverCfg
cfg := pxy.pxyConf.GetBaseConfig()
cfg := pxy.configurer.GetBaseConfig()
// server plugin hook
rc := pxy.GetResourceController()
content := &plugin.NewUserConnContent{
User: pxy.GetUserInfo(),
ProxyName: pxy.GetName(),
ProxyType: cfg.ProxyType,
ProxyType: cfg.Type,
RemoteAddr: userConn.RemoteAddr().String(),
}
_, err := rc.PluginManager.NewUserConn(content)
@ -232,15 +237,16 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
defer workConn.Close()
var local io.ReadWriteCloser = workConn
xl.Trace("handler user tcp connection, use_encryption: %t, use_compression: %t", cfg.UseEncryption, cfg.UseCompression)
if cfg.UseEncryption {
local, err = libio.WithEncryption(local, []byte(serverCfg.Token))
xl.Trace("handler user tcp connection, use_encryption: %t, use_compression: %t",
cfg.Transport.UseEncryption, cfg.Transport.UseCompression)
if cfg.Transport.UseEncryption {
local, err = libio.WithEncryption(local, []byte(serverCfg.Auth.Token))
if err != nil {
xl.Error("create encryption stream error: %v", err)
return
}
}
if cfg.UseCompression {
if cfg.Transport.UseCompression {
var recycleFn func()
local, recycleFn = libio.WithCompressionFromPool(local)
defer recycleFn()
@ -256,7 +262,7 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
name := pxy.GetName()
proxyType := cfg.ProxyType
proxyType := cfg.Type
metrics.Server.OpenConnection(name, proxyType)
inCount, outCount, _ := libio.Join(local, userConn)
metrics.Server.CloseConnection(name, proxyType)
@ -266,18 +272,18 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
}
func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.ResourceController, poolCount int,
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf, loginMsg *msg.Login,
getWorkConnFn GetWorkConnFn, configurer v1.ProxyConfigurer, serverCfg *v1.ServerConfig, loginMsg *msg.Login,
) (pxy Proxy, err error) {
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseConfig().ProxyName)
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(configurer.GetBaseConfig().Name)
var limiter *rate.Limiter
limitBytes := pxyConf.GetBaseConfig().BandwidthLimit.Bytes()
if limitBytes > 0 && pxyConf.GetBaseConfig().BandwidthLimitMode == config.BandwidthLimitModeServer {
limitBytes := configurer.GetBaseConfig().Transport.BandwidthLimit.Bytes()
if limitBytes > 0 && configurer.GetBaseConfig().Transport.BandwidthLimitMode == types.BandwidthLimitModeServer {
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
}
basePxy := BaseProxy{
name: pxyConf.GetBaseConfig().ProxyName,
name: configurer.GetBaseConfig().Name,
rc: rc,
listeners: make([]net.Listener, 0),
poolCount: poolCount,
@ -288,14 +294,14 @@ func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.Reso
ctx: xlog.NewContext(ctx, xl),
userInfo: userInfo,
loginMsg: loginMsg,
pxyConf: pxyConf,
configurer: configurer,
}
factory := proxyFactoryRegistry[reflect.TypeOf(pxyConf)]
factory := proxyFactoryRegistry[reflect.TypeOf(configurer)]
if factory == nil {
return pxy, fmt.Errorf("proxy type not support")
}
pxy = factory(&basePxy, pxyConf)
pxy = factory(&basePxy)
if pxy == nil {
return nil, fmt.Errorf("proxy not created")
}

View File

@ -17,20 +17,20 @@ package proxy
import (
"reflect"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.STCPProxyConf{}), NewSTCPProxy)
RegisterProxyFactory(reflect.TypeOf(&v1.STCPProxyConfig{}), NewSTCPProxy)
}
type STCPProxy struct {
*BaseProxy
cfg *config.STCPProxyConf
cfg *v1.STCPProxyConfig
}
func NewSTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.STCPProxyConf)
func NewSTCPProxy(baseProxy *BaseProxy) Proxy {
unwrapped, ok := baseProxy.GetConfigurer().(*v1.STCPProxyConfig)
if !ok {
return nil
}
@ -47,7 +47,7 @@ func (pxy *STCPProxy) Run() (remoteAddr string, err error) {
if len(allowUsers) == 0 {
allowUsers = []string{pxy.GetUserInfo().User}
}
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk, allowUsers)
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Secretkey, allowUsers)
if errRet != nil {
err = errRet
return
@ -59,10 +59,6 @@ func (pxy *STCPProxy) Run() (remoteAddr string, err error) {
return
}
func (pxy *STCPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *STCPProxy) Close() {
pxy.BaseProxy.Close()
pxy.rc.VisitorManager.CloseListener(pxy.GetName())

View File

@ -17,20 +17,20 @@ package proxy
import (
"reflect"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.SUDPProxyConf{}), NewSUDPProxy)
RegisterProxyFactory(reflect.TypeOf(&v1.SUDPProxyConfig{}), NewSUDPProxy)
}
type SUDPProxy struct {
*BaseProxy
cfg *config.SUDPProxyConf
cfg *v1.SUDPProxyConfig
}
func NewSUDPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.SUDPProxyConf)
func NewSUDPProxy(baseProxy *BaseProxy) Proxy {
unwrapped, ok := baseProxy.GetConfigurer().(*v1.SUDPProxyConfig)
if !ok {
return nil
}
@ -47,7 +47,7 @@ func (pxy *SUDPProxy) Run() (remoteAddr string, err error) {
if len(allowUsers) == 0 {
allowUsers = []string{pxy.GetUserInfo().User}
}
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk, allowUsers)
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Secretkey, allowUsers)
if errRet != nil {
err = errRet
return
@ -59,10 +59,6 @@ func (pxy *SUDPProxy) Run() (remoteAddr string, err error) {
return
}
func (pxy *SUDPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *SUDPProxy) Close() {
pxy.BaseProxy.Close()
pxy.rc.VisitorManager.CloseListener(pxy.GetName())

View File

@ -20,22 +20,22 @@ import (
"reflect"
"strconv"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.TCPProxyConf{}), NewTCPProxy)
RegisterProxyFactory(reflect.TypeOf(&v1.TCPProxyConfig{}), NewTCPProxy)
}
type TCPProxy struct {
*BaseProxy
cfg *config.TCPProxyConf
cfg *v1.TCPProxyConfig
realBindPort int
}
func NewTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.TCPProxyConf)
func NewTCPProxy(baseProxy *BaseProxy) Proxy {
unwrapped, ok := baseProxy.GetConfigurer().(*v1.TCPProxyConfig)
if !ok {
return nil
}
@ -48,8 +48,9 @@ func NewTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl
if pxy.cfg.Group != "" {
l, realBindPort, errRet := pxy.rc.TCPGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
if pxy.cfg.LoadBalancer.Group != "" {
l, realBindPort, errRet := pxy.rc.TCPGroupCtl.Listen(pxy.name, pxy.cfg.LoadBalancer.Group, pxy.cfg.LoadBalancer.GroupKey,
pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
if errRet != nil {
err = errRet
return
@ -61,7 +62,7 @@ func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
}()
pxy.realBindPort = realBindPort
pxy.listeners = append(pxy.listeners, l)
xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.LoadBalancer.Group)
} else {
pxy.realBindPort, err = pxy.rc.TCPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
if err != nil {
@ -87,13 +88,9 @@ func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
return
}
func (pxy *TCPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *TCPProxy) Close() {
pxy.BaseProxy.Close()
if pxy.cfg.Group == "" {
if pxy.cfg.LoadBalancer.Group == "" {
pxy.rc.TCPPortManager.Release(pxy.realBindPort)
}
}

View File

@ -20,23 +20,23 @@ import (
"reflect"
"strings"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/consts"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/vhost"
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.TCPMuxProxyConf{}), NewTCPMuxProxy)
RegisterProxyFactory(reflect.TypeOf(&v1.TCPMuxProxyConfig{}), NewTCPMuxProxy)
}
type TCPMuxProxy struct {
*BaseProxy
cfg *config.TCPMuxProxyConf
cfg *v1.TCPMuxProxyConfig
}
func NewTCPMuxProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.TCPMuxProxyConf)
func NewTCPMuxProxy(baseProxy *BaseProxy) Proxy {
unwrapped, ok := baseProxy.GetConfigurer().(*v1.TCPMuxProxyConfig)
if !ok {
return nil
}
@ -57,8 +57,9 @@ func (pxy *TCPMuxProxy) httpConnectListen(
Username: httpUser,
Password: httpPwd,
}
if pxy.cfg.Group != "" {
l, err = pxy.rc.TCPMuxGroupCtl.Listen(pxy.ctx, pxy.cfg.Multiplexer, pxy.cfg.Group, pxy.cfg.GroupKey, *routeConfig)
if pxy.cfg.LoadBalancer.Group != "" {
l, err = pxy.rc.TCPMuxGroupCtl.Listen(pxy.ctx, pxy.cfg.Multiplexer,
pxy.cfg.LoadBalancer.Group, pxy.cfg.LoadBalancer.GroupKey, *routeConfig)
} else {
l, err = pxy.rc.TCPMuxHTTPConnectMuxer.Listen(pxy.ctx, routeConfig)
}
@ -66,7 +67,7 @@ func (pxy *TCPMuxProxy) httpConnectListen(
return nil, err
}
pxy.xl.Info("tcpmux httpconnect multiplexer listens for host [%s], group [%s] routeByHTTPUser [%s]",
domain, pxy.cfg.Group, pxy.cfg.RouteByHTTPUser)
domain, pxy.cfg.LoadBalancer.Group, pxy.cfg.RouteByHTTPUser)
pxy.listeners = append(pxy.listeners, l)
return append(addrs, util.CanonicalAddr(domain, pxy.serverCfg.TCPMuxHTTPConnectPort)), nil
}
@ -78,7 +79,7 @@ func (pxy *TCPMuxProxy) httpConnectRun() (remoteAddr string, err error) {
continue
}
addrs, err = pxy.httpConnectListen(domain, pxy.cfg.RouteByHTTPUser, pxy.cfg.HTTPUser, pxy.cfg.HTTPPwd, addrs)
addrs, err = pxy.httpConnectListen(domain, pxy.cfg.RouteByHTTPUser, pxy.cfg.HTTPUser, pxy.cfg.HTTPPassword, addrs)
if err != nil {
return "", err
}
@ -86,7 +87,7 @@ func (pxy *TCPMuxProxy) httpConnectRun() (remoteAddr string, err error) {
if pxy.cfg.SubDomain != "" {
addrs, err = pxy.httpConnectListen(pxy.cfg.SubDomain+"."+pxy.serverCfg.SubDomainHost,
pxy.cfg.RouteByHTTPUser, pxy.cfg.HTTPUser, pxy.cfg.HTTPPwd, addrs)
pxy.cfg.RouteByHTTPUser, pxy.cfg.HTTPUser, pxy.cfg.HTTPPassword, addrs)
if err != nil {
return "", err
}
@ -111,10 +112,6 @@ func (pxy *TCPMuxProxy) Run() (remoteAddr string, err error) {
return remoteAddr, err
}
func (pxy *TCPMuxProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *TCPMuxProxy) Close() {
pxy.BaseProxy.Close()
}

View File

@ -26,7 +26,7 @@ import (
"github.com/fatedier/golib/errors"
libio "github.com/fatedier/golib/io"
"github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/proto/udp"
"github.com/fatedier/frp/pkg/util/limit"
@ -35,12 +35,12 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.UDPProxyConf{}), NewUDPProxy)
RegisterProxyFactory(reflect.TypeOf(&v1.UDPProxyConfig{}), NewUDPProxy)
}
type UDPProxy struct {
*BaseProxy
cfg *config.UDPProxyConf
cfg *v1.UDPProxyConfig
realBindPort int
@ -63,8 +63,8 @@ type UDPProxy struct {
isClosed bool
}
func NewUDPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.UDPProxyConf)
func NewUDPProxy(baseProxy *BaseProxy) Proxy {
unwrapped, ok := baseProxy.GetConfigurer().(*v1.UDPProxyConfig)
if !ok {
return nil
}
@ -140,7 +140,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
pxy.readCh <- m
metrics.Server.AddTrafficOut(
pxy.GetName(),
pxy.GetConf().GetBaseConfig().ProxyType,
pxy.GetConfigurer().GetBaseConfig().Type,
int64(len(m.Content)),
)
}); errRet != nil {
@ -170,7 +170,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
xl.Trace("send message to udp workConn: %s", udpMsg.Content)
metrics.Server.AddTrafficIn(
pxy.GetName(),
pxy.GetConf().GetBaseConfig().ProxyType,
pxy.GetConfigurer().GetBaseConfig().Type,
int64(len(udpMsg.Content)),
)
continue
@ -204,15 +204,15 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
}
var rwc io.ReadWriteCloser = workConn
if pxy.cfg.UseEncryption {
rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
if pxy.cfg.Transport.UseEncryption {
rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Auth.Token))
if err != nil {
xl.Error("create encryption stream error: %v", err)
workConn.Close()
continue
}
}
if pxy.cfg.UseCompression {
if pxy.cfg.Transport.UseCompression {
rwc = libio.WithCompression(rwc)
}
@ -245,10 +245,6 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
return remoteAddr, nil
}
func (pxy *UDPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *UDPProxy) Close() {
pxy.mu.Lock()
defer pxy.mu.Unlock()

Some files were not shown because too many files have changed in this diff Show More