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

pull/3608/head
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

@ -125,11 +125,13 @@ issues:
linters: linters:
- errcheck - errcheck
- maligned - maligned
# keep it until we only support go1.20
- linters: - linters:
- staticcheck - revive
text: "SA1019: rand.Seed has been deprecated" - stylecheck
text: "use underscores in Go names"
- linters:
- revive
text: "unused-parameter"
# Independently from option `exclude` we use default exclude patterns, # Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all # it can be disabled by this option. To list all

View File

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

View File

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

View File

@ -30,6 +30,7 @@ import (
"github.com/fatedier/frp/client/proxy" "github.com/fatedier/frp/client/proxy"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/util/log" "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 { if err != nil {
res.Code = 400 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Warn("reload frpc proxy config error: %s", res.Msg) log.Warn("reload frpc proxy config error: %s", res.Msg)
return 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.Code = 500
res.Msg = err.Error() res.Msg = err.Error()
log.Warn("reload frpc proxy config error: %s", res.Msg) 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 { if baseCfg.LocalPort != 0 {
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort)) psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
} }
psr.Plugin = baseCfg.Plugin psr.Plugin = baseCfg.Plugin.Type
if status.Err == "" { if status.Err == "" {
psr.RemoteAddr = status.RemoteAddr psr.RemoteAddr = status.RemoteAddr
@ -172,24 +179,14 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
return return
} }
content, err := config.GetRenderedConfFromFile(svr.cfgFile) content, err := os.ReadFile(svr.cfgFile)
if err != nil { if err != nil {
res.Code = 400 res.Code = 400
res.Msg = err.Error() res.Msg = err.Error()
log.Warn("load frpc config file error: %s", res.Msg) log.Warn("load frpc config file error: %s", res.Msg)
return return
} }
res.Msg = string(content)
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")
} }
// PUT /api/config // PUT /api/config
@ -221,49 +218,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
return return
} }
// get token from origin content if err := os.WriteFile(svr.cfgFile, body, 0o644); err != nil {
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 {
res.Code = 500 res.Code = 500
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err) res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
log.Warn("%s", res.Msg) log.Warn("%s", res.Msg)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,6 +21,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/v1/validation"
) )
func init() { func init() {
@ -31,7 +32,20 @@ var verifyCmd = &cobra.Command{
Use: "verify", Use: "verify",
Short: "Verify that the configures is valid", Short: "Verify that the configures is valid",
RunE: func(cmd *cobra.Command, args []string) error { 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 { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

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

View File

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

View File

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

View File

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

9
go.mod
View File

@ -3,12 +3,12 @@ module github.com/fatedier/frp
go 1.20 go 1.20
require ( require (
github.com/BurntSushi/toml v0.3.1
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/coreos/go-oidc/v3 v3.6.0 github.com/coreos/go-oidc/v3 v3.6.0
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
github.com/fatedier/golib v0.1.1-0.20230725122706-dcbaee8eef40 github.com/fatedier/golib v0.1.1-0.20230725122706-dcbaee8eef40
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible 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/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
@ -37,11 +37,8 @@ require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // 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-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-logr/logr v1.2.4 // 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/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // 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/cpuid/v2 v2.0.6 // indirect
github.com/klauspost/reedsolomon v1.9.15 // indirect github.com/klauspost/reedsolomon v1.9.15 // indirect
github.com/kr/text v0.2.0 // 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/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/logging v0.2.2 // indirect github.com/pion/logging v0.2.2 // indirect
@ -76,8 +72,11 @@ require (
golang.org/x/tools v0.9.3 // indirect golang.org/x/tools v0.9.3 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/utils v0.0.0-20230209194617-a36077c30491 // 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. // 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= 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 h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= 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/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 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 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/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 h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= 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 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= 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 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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= 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/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 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= 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.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.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.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.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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/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 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 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 ( import (
"fmt" "fmt"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/consts" "github.com/fatedier/frp/pkg/consts"
"github.com/fatedier/frp/pkg/msg" "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 { type Setter interface {
SetLogin(*msg.Login) error SetLogin(*msg.Login) error
SetPing(*msg.Ping) error SetPing(*msg.Ping) error
SetNewWorkConn(*msg.NewWorkConn) error SetNewWorkConn(*msg.NewWorkConn) error
} }
func NewAuthSetter(cfg ClientConfig) (authProvider Setter) { func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter) {
switch cfg.AuthenticationMethod { switch cfg.Method {
case consts.TokenAuthMethod: case consts.TokenAuthMethod:
authProvider = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig) authProvider = NewTokenAuth(cfg.AdditionalAuthScopes, cfg.Token)
case consts.OidcAuthMethod: case consts.OidcAuthMethod:
authProvider = NewOidcAuthSetter(cfg.BaseConfig, cfg.OidcClientConfig) authProvider = NewOidcAuthSetter(cfg.AdditionalAuthScopes, cfg.OIDC)
default: default:
panic(fmt.Sprintf("wrong authentication method: '%s'", cfg.AuthenticationMethod)) panic(fmt.Sprintf("wrong method: '%s'", cfg.Method))
} }
return authProvider return authProvider
} }
@ -96,13 +46,12 @@ type Verifier interface {
VerifyNewWorkConn(*msg.NewWorkConn) error VerifyNewWorkConn(*msg.NewWorkConn) error
} }
func NewAuthVerifier(cfg ServerConfig) (authVerifier Verifier) { func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
switch cfg.AuthenticationMethod { switch cfg.Method {
case consts.TokenAuthMethod: case consts.TokenAuthMethod:
authVerifier = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig) authVerifier = NewTokenAuth(cfg.AdditionalAuthScopes, cfg.Token)
case consts.OidcAuthMethod: case consts.OidcAuthMethod:
authVerifier = NewOidcAuthVerifier(cfg.BaseConfig, cfg.OidcServerConfig) authVerifier = NewOidcAuthVerifier(cfg.AdditionalAuthScopes, cfg.OIDC)
} }
return authVerifier 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,104 +19,39 @@ import (
"fmt" "fmt"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/samber/lo"
"golang.org/x/oauth2/clientcredentials" "golang.org/x/oauth2/clientcredentials"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg" "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 { type OidcAuthProvider struct {
BaseConfig additionalAuthScopes []v1.AuthScope
tokenGenerator *clientcredentials.Config tokenGenerator *clientcredentials.Config
} }
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider { func NewOidcAuthSetter(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCClientConfig) *OidcAuthProvider {
eps := make(map[string][]string) eps := make(map[string][]string)
for k, v := range cfg.OidcAdditionalEndpointParams { for k, v := range cfg.AdditionalEndpointParams {
eps[k] = []string{v} eps[k] = []string{v}
} }
if cfg.OidcAudience != "" { if cfg.Audience != "" {
eps["audience"] = []string{cfg.OidcAudience} eps["audience"] = []string{cfg.Audience}
} }
tokenGenerator := &clientcredentials.Config{ tokenGenerator := &clientcredentials.Config{
ClientID: cfg.OidcClientID, ClientID: cfg.ClientID,
ClientSecret: cfg.OidcClientSecret, ClientSecret: cfg.ClientSecret,
Scopes: []string{cfg.OidcScope}, Scopes: []string{cfg.Scope},
TokenURL: cfg.OidcTokenEndpointURL, TokenURL: cfg.TokenEndpointURL,
EndpointParams: eps, EndpointParams: eps,
} }
return &OidcAuthProvider{ return &OidcAuthProvider{
BaseConfig: baseCfg, additionalAuthScopes: additionalAuthScopes,
tokenGenerator: tokenGenerator, tokenGenerator: tokenGenerator,
} }
} }
@ -135,7 +70,7 @@ func (auth *OidcAuthProvider) SetLogin(loginMsg *msg.Login) (err error) {
} }
func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) { func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
if !auth.AuthenticateHeartBeats { if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
return nil return nil
} }
@ -144,7 +79,7 @@ func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
} }
func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) { func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
if !auth.AuthenticateNewWorkConns { if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
return nil return nil
} }
@ -153,25 +88,25 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e
} }
type OidcAuthConsumer struct { type OidcAuthConsumer struct {
BaseConfig additionalAuthScopes []v1.AuthScope
verifier *oidc.IDTokenVerifier verifier *oidc.IDTokenVerifier
subjectFromLogin string subjectFromLogin string
} }
func NewOidcAuthVerifier(baseCfg BaseConfig, cfg OidcServerConfig) *OidcAuthConsumer { func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCServerConfig) *OidcAuthConsumer {
provider, err := oidc.NewProvider(context.Background(), cfg.OidcIssuer) provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
if err != nil { if err != nil {
panic(err) panic(err)
} }
verifierConf := oidc.Config{ verifierConf := oidc.Config{
ClientID: cfg.OidcAudience, ClientID: cfg.Audience,
SkipClientIDCheck: cfg.OidcAudience == "", SkipClientIDCheck: cfg.Audience == "",
SkipExpiryCheck: cfg.OidcSkipExpiryCheck, SkipExpiryCheck: cfg.SkipExpiryCheck,
SkipIssuerCheck: cfg.OidcSkipIssuerCheck, SkipIssuerCheck: cfg.SkipIssuerCheck,
} }
return &OidcAuthConsumer{ return &OidcAuthConsumer{
BaseConfig: baseCfg, additionalAuthScopes: additionalAuthScopes,
verifier: provider.Verifier(&verifierConf), 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) { func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
if !auth.AuthenticateHeartBeats { if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
return nil return nil
} }
@ -208,7 +143,7 @@ func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
} }
func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) { func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
if !auth.AuthenticateNewWorkConns { if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
return nil return nil
} }

View File

@ -18,43 +18,32 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/samber/lo"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg" "github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/util/util" "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 { type TokenAuthSetterVerifier struct {
BaseConfig additionalAuthScopes []v1.AuthScope
token string token string
} }
func NewTokenAuth(baseCfg BaseConfig, cfg TokenConfig) *TokenAuthSetterVerifier { func NewTokenAuth(additionalAuthScopes []v1.AuthScope, token string) *TokenAuthSetterVerifier {
return &TokenAuthSetterVerifier{ return &TokenAuthSetterVerifier{
BaseConfig: baseCfg, additionalAuthScopes: additionalAuthScopes,
token: cfg.Token, 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) loginMsg.PrivilegeKey = util.GetAuthKey(auth.token, loginMsg.Timestamp)
return nil return nil
} }
func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error { func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
if !auth.AuthenticateHeartBeats { if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
return nil return nil
} }
@ -64,7 +53,7 @@ func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
} }
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error { func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
if !auth.AuthenticateNewWorkConns { if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
return nil return nil
} }
@ -81,7 +70,7 @@ func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error {
} }
func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error { func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
if !auth.AuthenticateHeartBeats { if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
return nil return nil
} }
@ -92,7 +81,7 @@ func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
} }
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error { func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
if !auth.AuthenticateNewWorkConns { if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
return nil 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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with 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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package config package legacy
import ( import (
"fmt" "fmt"
@ -23,15 +23,16 @@ import (
"github.com/samber/lo" "github.com/samber/lo"
"gopkg.in/ini.v1" "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" "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 // recommended to use GetDefaultClientConf instead of creating this object
// directly, so that all unspecified fields have reasonable default values. // directly, so that all unspecified fields have reasonable default values.
type ClientCommonConf struct { type ClientCommonConf struct {
auth.ClientConfig `ini:",extends"` legacyauth.ClientConfig `ini:",extends"`
// ServerAddr specifies the address of the server to connect to. By // ServerAddr specifies the address of the server to connect to. By
// default, this value is "0.0.0.0". // default, this value is "0.0.0.0".
@ -168,85 +169,6 @@ type ClientCommonConf struct {
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"` 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. // Supported sources including: string(file path), []byte, Reader interface.
func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) { func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
f, err := ini.LoadSources(ini.LoadOptions{ f, err := ini.LoadSources(ini.LoadOptions{
@ -421,3 +343,74 @@ func copySection(source, target *ini.Section) {
_, _ = target.NewKey(key, value) _, _ = 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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package config package legacy
import ( import (
"bytes" "bytes"
@ -40,7 +40,6 @@ func ParseClientConfig(filePath string) (
if err != nil { if err != nil {
return return
} }
cfg.Complete()
if err = cfg.Validate(); err != nil { if err = cfg.Validate(); err != nil {
err = fmt.Errorf("parse config error: %v", err) err = fmt.Errorf("parse config error: %v", err)
return 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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with 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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package config package legacy
import ( import (
"fmt"
"strings" "strings"
"github.com/go-playground/validator/v10"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
"github.com/fatedier/frp/pkg/auth" legacyauth "github.com/fatedier/frp/pkg/auth/legacy"
plugin "github.com/fatedier/frp/pkg/plugin/server"
"github.com/fatedier/frp/pkg/util/util"
) )
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 // ServerCommonConf contains information for a server service. It is
// recommended to use GetDefaultServerConf instead of creating this object // recommended to use GetDefaultServerConf instead of creating this object
// directly, so that all unspecified fields have reasonable default values. // directly, so that all unspecified fields have reasonable default values.
type ServerCommonConf struct { type ServerCommonConf struct {
auth.ServerConfig `ini:",extends"` legacyauth.ServerConfig `ini:",extends"`
// BindAddr specifies the address that the server binds to. By default, // BindAddr specifies the address that the server binds to. By default,
// this value is "0.0.0.0". // this value is "0.0.0.0".
@ -185,7 +189,7 @@ type ServerCommonConf struct {
// connection. By default, this value is 10. // connection. By default, this value is 10.
UserConnTimeout int64 `ini:"user_conn_timeout" json:"user_conn_timeout"` UserConnTimeout int64 `ini:"user_conn_timeout" json:"user_conn_timeout"`
// HTTPPlugins specify the server plugins support HTTP protocol. // 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 // UDPPacketSize specifies the UDP packet size
// By default, this value is 1500 // By default, this value is 1500
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"` UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
@ -200,7 +204,7 @@ type ServerCommonConf struct {
// defaults. // defaults.
func GetDefaultServerConf() ServerCommonConf { func GetDefaultServerConf() ServerCommonConf {
return ServerCommonConf{ return ServerCommonConf{
ServerConfig: auth.GetDefaultServerConf(), ServerConfig: legacyauth.GetDefaultServerConf(),
BindAddr: "0.0.0.0", BindAddr: "0.0.0.0",
BindPort: 7000, BindPort: 7000,
QUICKeepalivePeriod: 10, QUICKeepalivePeriod: 10,
@ -221,7 +225,7 @@ func GetDefaultServerConf() ServerCommonConf {
MaxPortsPerClient: 0, MaxPortsPerClient: 0,
HeartbeatTimeout: 90, HeartbeatTimeout: 90,
UserConnTimeout: 10, UserConnTimeout: 10,
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions), HTTPPlugins: make(map[string]HTTPPluginOptions),
UDPPacketSize: 1500, UDPPacketSize: 1500,
NatHoleAnalysisDataReserveHours: 7 * 24, NatHoleAnalysisDataReserveHours: 7 * 24,
} }
@ -253,18 +257,11 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
// allow_ports // allow_ports
allowPortStr := s.Key("allow_ports").String() allowPortStr := s.Key("allow_ports").String()
if allowPortStr != "" { 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 common.AllowPortsStr = allowPortStr
} }
// plugin.xxx // plugin.xxx
pluginOpts := make(map[string]plugin.HTTPPluginOptions) pluginOpts := make(map[string]HTTPPluginOptions)
for _, section := range f.Sections() { for _, section := range f.Sections() {
name := section.Name() name := section.Name()
if !strings.HasPrefix(name, "plugin.") { if !strings.HasPrefix(name, "plugin.") {
@ -283,47 +280,10 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
return common, nil return common, nil
} }
func (cfg *ServerCommonConf) Complete() { func loadHTTPPluginOpt(section *ini.Section) (*HTTPPluginOptions, error) {
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) {
name := strings.TrimSpace(strings.TrimPrefix(section.Name(), "plugin.")) name := strings.TrimSpace(strings.TrimPrefix(section.Name(), "plugin."))
opt := new(plugin.HTTPPluginOptions) opt := &HTTPPluginOptions{}
err := section.MapTo(opt) err := section.MapTo(opt)
if err != nil { if err != nil {
return nil, err return nil, err

View File

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

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package config package legacy
import ( import (
"bytes" "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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with 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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package config package legacy
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/samber/lo"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
"github.com/fatedier/frp/pkg/consts" "github.com/fatedier/frp/pkg/consts"
@ -38,8 +37,16 @@ type VisitorConf interface {
GetBaseConfig() *BaseVisitorConf GetBaseConfig() *BaseVisitorConf
// UnmarshalFromIni unmarshals config from ini. // UnmarshalFromIni unmarshals config from ini.
UnmarshalFromIni(prefix string, name string, section *ini.Section) error 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 { type BaseVisitorConf struct {
@ -59,96 +66,14 @@ type BaseVisitorConf struct {
BindPort int `ini:"bind_port" json:"bind_port"` 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 // Base
func (cfg *BaseVisitorConf) GetBaseConfig() *BaseVisitorConf { func (cfg *BaseVisitorConf) GetBaseConfig() *BaseVisitorConf {
return cfg return cfg
} }
func (cfg *BaseVisitorConf) validate() (err error) { func (cfg *BaseVisitorConf) unmarshalFromIni(_ string, name string, _ *ini.Section) 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
// Custom decoration after basic unmarshal: // Custom decoration after basic unmarshal:
// proxy name cfg.ProxyName = name
cfg.ProxyName = prefix + name
// server_name
if cfg.ServerUser == "" {
cfg.ServerName = prefix + cfg.ServerName
} else {
cfg.ServerName = cfg.ServerUser + "." + cfg.ServerName
}
// bind_addr // bind_addr
if cfg.BindAddr == "" { if cfg.BindAddr == "" {
@ -170,8 +95,9 @@ func preVisitorUnmarshalFromIni(cfg VisitorConf, prefix string, name string, sec
return nil return nil
} }
// SUDP type SUDPVisitorConf struct {
var _ VisitorConf = &SUDPVisitorConf{} BaseVisitorConf `ini:",extends"`
}
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) { func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section) err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
@ -184,19 +110,10 @@ func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section
return return
} }
func (cfg *SUDPVisitorConf) Validate() (err error) { type STCPVisitorConf struct {
if err = cfg.BaseVisitorConf.validate(); err != nil { BaseVisitorConf `ini:",extends"`
return
}
// Add custom logic validate, if exists
return
} }
// STCP
var _ VisitorConf = &STCPVisitorConf{}
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) { func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section) err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
if err != nil { if err != nil {
@ -208,19 +125,17 @@ func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
return return
} }
func (cfg *STCPVisitorConf) Validate() (err error) { type XTCPVisitorConf struct {
if err = cfg.BaseVisitorConf.validate(); err != nil { BaseVisitorConf `ini:",extends"`
return
}
// Add custom logic validate, if exists Protocol string `ini:"protocol" json:"protocol,omitempty"`
KeepTunnelOpen bool `ini:"keep_tunnel_open" json:"keep_tunnel_open,omitempty"`
return 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) { func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section) err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
if err != nil { if err != nil {
@ -243,14 +158,22 @@ func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
return return
} }
func (cfg *XTCPVisitorConf) Validate() (err error) { // Visitor loaded from ini
if err = cfg.BaseVisitorConf.validate(); err != nil { func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (VisitorConf, error) {
return // 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 conf := DefaultVisitorConf(visitorType)
if !lo.Contains([]string{"", "kcp", "quic"}, cfg.Protocol) { if conf == nil {
return fmt.Errorf("protocol should be 'kcp' or 'quic'") 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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package config package types
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"strconv" "strconv"
"strings" "strings"
) )
@ -123,3 +124,62 @@ func (q *BandwidthQuantity) MarshalJSON() ([]byte, error) {
func (q *BandwidthQuantity) Bytes() int64 { func (q *BandwidthQuantity) Bytes() int64 {
return q.i 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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package config package types
import ( import (
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
) )
type Wrap struct { type Wrap struct {
@ -27,14 +27,46 @@ type Wrap struct {
} }
func TestBandwidthQuantity(t *testing.T) { func TestBandwidthQuantity(t *testing.T) {
assert := assert.New(t) require := require.New(t)
var w Wrap var w Wrap
err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w) err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w)
assert.NoError(err) require.NoError(err)
assert.EqualValues(1*KB, w.B.Bytes()) require.EqualValues(1*KB, w.B.Bytes())
buf, err := json.Marshal(&w) buf, err := json.Marshal(&w)
assert.NoError(err) require.NoError(err)
assert.Equal(`{"b":"1KB","int":5}`, string(buf)) 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,54 +16,33 @@ package plugin
import ( import (
"crypto/tls" "crypto/tls"
"fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"strings"
v1 "github.com/fatedier/frp/pkg/config/v1"
utilnet "github.com/fatedier/frp/pkg/util/net" utilnet "github.com/fatedier/frp/pkg/util/net"
) )
const PluginHTTP2HTTPS = "http2https"
func init() { func init() {
Register(PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin) Register(v1.PluginHTTP2HTTPS, NewHTTP2HTTPSPlugin)
} }
type HTTP2HTTPSPlugin struct { type HTTP2HTTPSPlugin struct {
hostHeaderRewrite string opts *v1.HTTP2HTTPSPluginOptions
localAddr string
headers map[string]string
l *Listener l *Listener
s *http.Server s *http.Server
} }
func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) { func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
localAddr := params["plugin_local_addr"] opts := options.(*v1.HTTP2HTTPSPluginOptions)
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")
}
listener := NewProxyListener() listener := NewProxyListener()
p := &HTTP2HTTPSPlugin{ p := &HTTP2HTTPSPlugin{
localAddr: localAddr, opts: opts,
hostHeaderRewrite: hostHeaderRewrite,
headers: headers,
l: listener, l: listener,
} }
@ -74,11 +53,11 @@ func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) {
rp := &httputil.ReverseProxy{ rp := &httputil.ReverseProxy{
Director: func(req *http.Request) { Director: func(req *http.Request) {
req.URL.Scheme = "https" req.URL.Scheme = "https"
req.URL.Host = p.localAddr req.URL.Host = p.opts.LocalAddr
if p.hostHeaderRewrite != "" { if p.opts.HostHeaderRewrite != "" {
req.Host = p.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) req.Header.Set(k, v)
} }
}, },
@ -103,7 +82,7 @@ func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _
} }
func (p *HTTP2HTTPSPlugin) Name() string { func (p *HTTP2HTTPSPlugin) Name() string {
return PluginHTTP2HTTPS return v1.PluginHTTP2HTTPS
} }
func (p *HTTP2HTTPSPlugin) Close() error { func (p *HTTP2HTTPSPlugin) Close() error {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,24 +25,18 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"strings" "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 { type httpPlugin struct {
options HTTPPluginOptions options v1.HTTPPluginOptions
url string url string
client *http.Client client *http.Client
} }
func NewHTTPPluginOptions(options HTTPPluginOptions) Plugin { func NewHTTPPluginOptions(options v1.HTTPPluginOptions) Plugin {
url := fmt.Sprintf("%s%s", options.Addr, options.Path) url := fmt.Sprintf("%s%s", options.Addr, options.Path)
var client *http.Client var client *http.Client

View File

@ -29,15 +29,14 @@ func init() {
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1) Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
} }
func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) { func InitLog(logFile string, logLevel string, maxdays int64, disableLogColor bool) {
SetLogFile(logWay, logFile, maxdays, disableLogColor) SetLogFile(logFile, maxdays, disableLogColor)
SetLogLevel(logLevel) SetLogLevel(logLevel)
} }
// SetLogFile to configure log params // SetLogFile to configure log params
// logWay: file or console func SetLogFile(logFile string, maxdays int64, disableLogColor bool) {
func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) { if logFile == "console" {
if logWay == "console" {
params := "" params := ""
if disableLogColor { if disableLogColor {
params = `{"color": false}` 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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with 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/control/shutdown"
"github.com/fatedier/golib/crypto" "github.com/fatedier/golib/crypto"
"github.com/fatedier/golib/errors" "github.com/fatedier/golib/errors"
"github.com/samber/lo"
"github.com/fatedier/frp/pkg/auth" "github.com/fatedier/frp/pkg/auth"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1"
pkgerr "github.com/fatedier/frp/pkg/errors" pkgerr "github.com/fatedier/frp/pkg/errors"
"github.com/fatedier/frp/pkg/msg" "github.com/fatedier/frp/pkg/msg"
plugin "github.com/fatedier/frp/pkg/plugin/server" plugin "github.com/fatedier/frp/pkg/plugin/server"
@ -151,7 +153,7 @@ type Control struct {
mu sync.RWMutex mu sync.RWMutex
// Server configuration information // Server configuration information
serverCfg config.ServerCommonConf serverCfg *v1.ServerConfig
xl *xlog.Logger xl *xlog.Logger
ctx context.Context ctx context.Context
@ -165,11 +167,11 @@ func NewControl(
authVerifier auth.Verifier, authVerifier auth.Verifier,
ctlConn net.Conn, ctlConn net.Conn,
loginMsg *msg.Login, loginMsg *msg.Login,
serverCfg config.ServerCommonConf, serverCfg *v1.ServerConfig,
) *Control { ) *Control {
poolCount := loginMsg.PoolCount poolCount := loginMsg.PoolCount
if poolCount > int(serverCfg.MaxPoolCount) { if poolCount > int(serverCfg.Transport.MaxPoolCount) {
poolCount = int(serverCfg.MaxPoolCount) poolCount = int(serverCfg.Transport.MaxPoolCount)
} }
ctl := &Control{ ctl := &Control{
rc: rc, rc: rc,
@ -320,7 +322,7 @@ func (ctl *Control) writer() {
defer ctl.allShutdown.Start() defer ctl.allShutdown.Start()
defer ctl.writerShutdown.Done() 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 { if err != nil {
xl.Error("crypto new writer error: %v", err) xl.Error("crypto new writer error: %v", err)
ctl.allShutdown.Start() ctl.allShutdown.Start()
@ -352,7 +354,7 @@ func (ctl *Control) reader() {
defer ctl.allShutdown.Start() defer ctl.allShutdown.Start()
defer ctl.readerShutdown.Done() defer ctl.readerShutdown.Done()
encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token)) encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Auth.Token))
for { for {
m, err := msg.ReadMsg(encReader) m, err := msg.ReadMsg(encReader)
if err != nil { if err != nil {
@ -400,7 +402,7 @@ func (ctl *Control) stoper() {
for _, pxy := range ctl.proxies { for _, pxy := range ctl.proxies {
pxy.Close() pxy.Close()
ctl.pxyManager.Del(pxy.GetName()) 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{ notifyContent := &plugin.CloseProxyContent{
User: plugin.UserInfo{ User: plugin.UserInfo{
@ -450,7 +452,7 @@ func (ctl *Control) manager() {
var heartbeatCh <-chan time.Time var heartbeatCh <-chan time.Time
// Don't need application heartbeat if TCPMux is enabled, // Don't need application heartbeat if TCPMux is enabled,
// yamux will do same thing. // 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) heartbeat := time.NewTicker(time.Second)
defer heartbeat.Stop() defer heartbeat.Stop()
heartbeatCh = heartbeat.C heartbeatCh = heartbeat.C
@ -459,7 +461,7 @@ func (ctl *Control) manager() {
for { for {
select { select {
case <-heartbeatCh: 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") xl.Warn("heartbeat timeout")
return return
} }
@ -491,7 +493,8 @@ func (ctl *Control) manager() {
} }
if err != nil { if err != nil {
xl.Warn("new proxy [%s] type [%s] error: %v", m.ProxyName, m.ProxyType, err) 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 { } else {
resp.RemoteAddr = remoteAddr resp.RemoteAddr = remoteAddr
xl.Info("new proxy [%s] type [%s] success", m.ProxyName, m.ProxyType) xl.Info("new proxy [%s] type [%s] success", m.ProxyName, m.ProxyType)
@ -524,7 +527,7 @@ func (ctl *Control) manager() {
if err != nil { if err != nil {
xl.Warn("received invalid ping: %v", err) xl.Warn("received invalid ping: %v", err)
ctl.sendCh <- &msg.Pong{ 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 return
} }
@ -549,9 +552,9 @@ func (ctl *Control) HandleNatHoleReport(m *msg.NatHoleReport) {
} }
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) { 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. // Load configures from NewProxy message and validate.
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg) pxyConf, err = config.NewProxyConfigurerFromMsg(pxyMsg, ctl.serverCfg)
if err != nil { if err != nil {
return return
} }
@ -632,7 +635,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
delete(ctl.proxies, closeMsg.ProxyName) delete(ctl.proxies, closeMsg.ProxyName)
ctl.mu.Unlock() ctl.mu.Unlock()
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseConfig().ProxyType) metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConfigurer().GetBaseConfig().Type)
notifyContent := &plugin.CloseProxyContent{ notifyContent := &plugin.CloseProxyContent{
User: plugin.UserInfo{ User: plugin.UserInfo{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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