Compare commits

...

5 Commits

Author SHA1 Message Date
fatedier
605f3bdece client/visitor: deduplicate visitor connection handshake and wrapping (#5219)
Extract two shared helpers to eliminate duplicated code across STCP,
SUDP, and XTCP visitors:

- dialRawVisitorConn: handles ConnectServer + NewVisitorConn handshake
  (auth, sign key, 10s read deadline, error check)
- wrapVisitorConn: handles encryption + pooled compression wrapping,
  returning a recycleFn for pool resource cleanup

SUDP is upgraded from WithCompression to WithCompressionFromPool,
aligning with the pooled compression used by STCP and XTCP.
2026-03-08 01:03:40 +08:00
fatedier
764a626b6e server/control: deduplicate close-proxy logic and UserInfo construction (#5218)
Extract closeProxy() helper to eliminate duplicated 4-step cleanup
sequence (Close, PxyManager.Del, metrics, plugin notify) between
worker() and CloseProxy().

Extract loginUserInfo() helper to eliminate 4 repeated plugin.UserInfo
constructions using LoginMsg fields.

Optimize worker() to snapshot and clear the proxies map under lock,
then perform cleanup outside the lock to reduce lock hold time.
2026-03-08 00:02:14 +08:00
Oleksandr Redko
c2454e7114 refactor: fix modernize lint issues (#5215) 2026-03-07 23:10:19 +08:00
fatedier
017d71717f server: introduce SessionContext to encapsulate NewControl parameters (#5217)
Replace 10 positional parameters in NewControl() with a single
SessionContext struct, matching the client-side pattern. This also
eliminates the post-construction mutation of clientRegistry and
removes two TODO comments.
2026-03-07 20:17:00 +08:00
Oleksandr Redko
bd200b1a3b fix: typos in comments, tests, functions (#5216) 2026-03-07 18:43:04 +08:00
46 changed files with 361 additions and 417 deletions

View File

@@ -18,6 +18,7 @@ linters:
- lll
- makezero
- misspell
- modernize
- prealloc
- predeclared
- revive
@@ -47,6 +48,9 @@ linters:
ignore-rules:
- cancelled
- marshalled
modernize:
disable:
- omitzero
unparam:
check-exported: false
exclusions:

View File

@@ -32,7 +32,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.SUDPProxyConfig{}), NewSUDPProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.SUDPProxyConfig](), NewSUDPProxy)
}
type SUDPProxy struct {

View File

@@ -31,7 +31,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.UDPProxyConfig{}), NewUDPProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.UDPProxyConfig](), NewUDPProxy)
}
type UDPProxy struct {

View File

@@ -34,7 +34,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.XTCPProxyConfig{}), NewXTCPProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.XTCPProxyConfig](), NewXTCPProxy)
}
type XTCPProxy struct {

View File

@@ -15,18 +15,12 @@
package visitor
import (
"fmt"
"io"
"net"
"strconv"
"time"
libio "github.com/fatedier/golib/io"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/naming"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/xlog"
)
@@ -61,7 +55,6 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
xl := xlog.FromContextSafe(sv.ctx)
var tunnelErr error
defer func() {
// If there was an error and connection supports CloseWithError, use it
if tunnelErr != nil {
if eConn, ok := userConn.(interface{ CloseWithError(error) error }); ok {
_ = eConn.CloseWithError(tunnelErr)
@@ -72,62 +65,21 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
}()
xl.Debugf("get a new stcp user connection")
visitorConn, err := sv.helper.ConnectServer()
visitorConn, err := sv.dialRawVisitorConn(sv.cfg.GetBaseConfig())
if err != nil {
xl.Warnf("dialRawVisitorConn error: %v", err)
tunnelErr = err
return
}
defer visitorConn.Close()
now := time.Now().Unix()
targetProxyName := naming.BuildTargetServerProxyName(sv.clientCfg.User, sv.cfg.ServerUser, sv.cfg.ServerName)
newVisitorConnMsg := &msg.NewVisitorConn{
RunID: sv.helper.RunID(),
ProxyName: targetProxyName,
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
Timestamp: now,
UseEncryption: sv.cfg.Transport.UseEncryption,
UseCompression: sv.cfg.Transport.UseCompression,
}
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
remote, recycleFn, err := wrapVisitorConn(visitorConn, sv.cfg.GetBaseConfig())
if err != nil {
xl.Warnf("send newVisitorConnMsg to server error: %v", err)
xl.Warnf("wrapVisitorConn error: %v", err)
tunnelErr = err
return
}
var newVisitorConnRespMsg msg.NewVisitorConnResp
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
if err != nil {
xl.Warnf("get newVisitorConnRespMsg error: %v", err)
tunnelErr = err
return
}
_ = visitorConn.SetReadDeadline(time.Time{})
if newVisitorConnRespMsg.Error != "" {
xl.Warnf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
tunnelErr = fmt.Errorf("%s", newVisitorConnRespMsg.Error)
return
}
var remote io.ReadWriteCloser
remote = visitorConn
if sv.cfg.Transport.UseEncryption {
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
if err != nil {
xl.Errorf("create encryption stream error: %v", err)
tunnelErr = err
return
}
}
if sv.cfg.Transport.UseCompression {
var recycleFn func()
remote, recycleFn = libio.WithCompressionFromPool(remote)
defer recycleFn()
}
defer recycleFn()
libio.Join(userConn, remote)
}

View File

@@ -16,21 +16,17 @@ package visitor
import (
"fmt"
"io"
"net"
"strconv"
"sync"
"time"
"github.com/fatedier/golib/errors"
libio "github.com/fatedier/golib/io"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/naming"
"github.com/fatedier/frp/pkg/proto/udp"
netpkg "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/xlog"
)
@@ -76,6 +72,7 @@ func (sv *SUDPVisitor) dispatcher() {
var (
visitorConn net.Conn
recycleFn func()
err error
firstPacket *msg.UDPPacket
@@ -93,14 +90,17 @@ func (sv *SUDPVisitor) dispatcher() {
return
}
visitorConn, err = sv.getNewVisitorConn()
visitorConn, recycleFn, err = sv.getNewVisitorConn()
if err != nil {
xl.Warnf("newVisitorConn to frps error: %v, try to reconnect", err)
continue
}
// visitorConn always be closed when worker done.
sv.worker(visitorConn, firstPacket)
func() {
defer recycleFn()
sv.worker(visitorConn, firstPacket)
}()
select {
case <-sv.checkCloseCh:
@@ -198,57 +198,17 @@ func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
xl.Infof("sudp worker is closed")
}
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
xl := xlog.FromContextSafe(sv.ctx)
visitorConn, err := sv.helper.ConnectServer()
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, func(), error) {
rawConn, err := sv.dialRawVisitorConn(sv.cfg.GetBaseConfig())
if err != nil {
return nil, fmt.Errorf("frpc connect frps error: %v", err)
return nil, func() {}, err
}
now := time.Now().Unix()
targetProxyName := naming.BuildTargetServerProxyName(sv.clientCfg.User, sv.cfg.ServerUser, sv.cfg.ServerName)
newVisitorConnMsg := &msg.NewVisitorConn{
RunID: sv.helper.RunID(),
ProxyName: targetProxyName,
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
Timestamp: now,
UseEncryption: sv.cfg.Transport.UseEncryption,
UseCompression: sv.cfg.Transport.UseCompression,
}
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
rwc, recycleFn, err := wrapVisitorConn(rawConn, sv.cfg.GetBaseConfig())
if err != nil {
visitorConn.Close()
return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err)
rawConn.Close()
return nil, func() {}, err
}
var newVisitorConnRespMsg msg.NewVisitorConnResp
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
if err != nil {
visitorConn.Close()
return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err)
}
_ = visitorConn.SetReadDeadline(time.Time{})
if newVisitorConnRespMsg.Error != "" {
visitorConn.Close()
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
}
var remote io.ReadWriteCloser
remote = visitorConn
if sv.cfg.Transport.UseEncryption {
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
if err != nil {
xl.Errorf("create encryption stream error: %v", err)
visitorConn.Close()
return nil, err
}
}
if sv.cfg.Transport.UseCompression {
remote = libio.WithCompression(remote)
}
return netpkg.WrapReadWriteCloserToConn(remote, visitorConn), nil
return netpkg.WrapReadWriteCloserToConn(rwc, rawConn), recycleFn, nil
}
func (sv *SUDPVisitor) Close() {

View File

@@ -16,13 +16,21 @@ package visitor
import (
"context"
"fmt"
"io"
"net"
"sync"
"time"
libio "github.com/fatedier/golib/io"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/naming"
plugin "github.com/fatedier/frp/pkg/plugin/visitor"
"github.com/fatedier/frp/pkg/transport"
netpkg "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/xlog"
"github.com/fatedier/frp/pkg/vnet"
)
@@ -142,3 +150,57 @@ func (v *BaseVisitor) Close() {
v.plugin.Close()
}
}
func (v *BaseVisitor) dialRawVisitorConn(cfg *v1.VisitorBaseConfig) (net.Conn, error) {
visitorConn, err := v.helper.ConnectServer()
if err != nil {
return nil, fmt.Errorf("connect to server error: %v", err)
}
now := time.Now().Unix()
targetProxyName := naming.BuildTargetServerProxyName(v.clientCfg.User, cfg.ServerUser, cfg.ServerName)
newVisitorConnMsg := &msg.NewVisitorConn{
RunID: v.helper.RunID(),
ProxyName: targetProxyName,
SignKey: util.GetAuthKey(cfg.SecretKey, now),
Timestamp: now,
UseEncryption: cfg.Transport.UseEncryption,
UseCompression: cfg.Transport.UseCompression,
}
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
if err != nil {
visitorConn.Close()
return nil, fmt.Errorf("send newVisitorConnMsg to server error: %v", err)
}
var newVisitorConnRespMsg msg.NewVisitorConnResp
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
if err != nil {
visitorConn.Close()
return nil, fmt.Errorf("read newVisitorConnRespMsg error: %v", err)
}
_ = visitorConn.SetReadDeadline(time.Time{})
if newVisitorConnRespMsg.Error != "" {
visitorConn.Close()
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
}
return visitorConn, nil
}
func wrapVisitorConn(conn io.ReadWriteCloser, cfg *v1.VisitorBaseConfig) (io.ReadWriteCloser, func(), error) {
rwc := conn
if cfg.Transport.UseEncryption {
var err error
rwc, err = libio.WithEncryption(rwc, []byte(cfg.SecretKey))
if err != nil {
return nil, func() {}, fmt.Errorf("create encryption stream error: %v", err)
}
}
recycleFn := func() {}
if cfg.Transport.UseCompression {
rwc, recycleFn = libio.WithCompressionFromPool(rwc)
}
return rwc, recycleFn, nil
}

View File

@@ -182,21 +182,14 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
return
}
var muxConnRWCloser io.ReadWriteCloser = tunnelConn
if sv.cfg.Transport.UseEncryption {
muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.SecretKey))
if err != nil {
xl.Errorf("create encryption stream error: %v", err)
tunnelConn.Close()
tunnelErr = err
return
}
}
if sv.cfg.Transport.UseCompression {
var recycleFn func()
muxConnRWCloser, recycleFn = libio.WithCompressionFromPool(muxConnRWCloser)
defer recycleFn()
muxConnRWCloser, recycleFn, err := wrapVisitorConn(tunnelConn, sv.cfg.GetBaseConfig())
if err != nil {
xl.Errorf("%v", err)
tunnelConn.Close()
tunnelErr = err
return
}
defer recycleFn()
_, _, errs := libio.Join(userConn, muxConnRWCloser)
xl.Debugf("join connections closed")

View File

@@ -47,7 +47,7 @@ var natholeDiscoveryCmd = &cobra.Command{
Use: "discover",
Short: "Discover nathole information from stun server",
RunE: func(cmd *cobra.Command, args []string) error {
// ignore error here, because we can use command line pameters
// ignore error here, because we can use command line parameters
cfg, _, _, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
if err != nil {
cfg = &v1.ClientCommonConfig{}

View File

@@ -39,14 +39,14 @@ const (
// Proxy
var (
proxyConfTypeMap = map[ProxyType]reflect.Type{
ProxyTypeTCP: reflect.TypeOf(TCPProxyConf{}),
ProxyTypeUDP: reflect.TypeOf(UDPProxyConf{}),
ProxyTypeTCPMUX: reflect.TypeOf(TCPMuxProxyConf{}),
ProxyTypeHTTP: reflect.TypeOf(HTTPProxyConf{}),
ProxyTypeHTTPS: reflect.TypeOf(HTTPSProxyConf{}),
ProxyTypeSTCP: reflect.TypeOf(STCPProxyConf{}),
ProxyTypeXTCP: reflect.TypeOf(XTCPProxyConf{}),
ProxyTypeSUDP: reflect.TypeOf(SUDPProxyConf{}),
ProxyTypeTCP: reflect.TypeFor[TCPProxyConf](),
ProxyTypeUDP: reflect.TypeFor[UDPProxyConf](),
ProxyTypeTCPMUX: reflect.TypeFor[TCPMuxProxyConf](),
ProxyTypeHTTP: reflect.TypeFor[HTTPProxyConf](),
ProxyTypeHTTPS: reflect.TypeFor[HTTPSProxyConf](),
ProxyTypeSTCP: reflect.TypeFor[STCPProxyConf](),
ProxyTypeXTCP: reflect.TypeFor[XTCPProxyConf](),
ProxyTypeSUDP: reflect.TypeFor[SUDPProxyConf](),
}
)

View File

@@ -32,9 +32,9 @@ const (
// Visitor
var (
visitorConfTypeMap = map[VisitorType]reflect.Type{
VisitorTypeSTCP: reflect.TypeOf(STCPVisitorConf{}),
VisitorTypeXTCP: reflect.TypeOf(XTCPVisitorConf{}),
VisitorTypeSUDP: reflect.TypeOf(SUDPVisitorConf{}),
VisitorTypeSTCP: reflect.TypeFor[STCPVisitorConf](),
VisitorTypeXTCP: reflect.TypeFor[XTCPVisitorConf](),
VisitorTypeSUDP: reflect.TypeFor[SUDPVisitorConf](),
}
)

View File

@@ -38,7 +38,7 @@ func parseNumberRangePair(firstRangeStr, secondRangeStr string) ([]NumberPair, e
return nil, fmt.Errorf("first and second range numbers are not in pairs")
}
pairs := make([]NumberPair, 0, len(firstRangeNumbers))
for i := 0; i < len(firstRangeNumbers); i++ {
for i := range firstRangeNumbers {
pairs = append(pairs, NumberPair{
First: firstRangeNumbers[i],
Second: secondRangeNumbers[i],

View File

@@ -137,8 +137,8 @@ func (p PortsRangeSlice) String() string {
func NewPortsRangeSliceFromString(str string) ([]PortsRange, error) {
str = strings.TrimSpace(str)
out := []PortsRange{}
numRanges := strings.Split(str, ",")
for _, numRangeStr := range numRanges {
numRanges := strings.SplitSeq(str, ",")
for numRangeStr := range numRanges {
// 1000-2000 or 2001
numArray := strings.Split(numRangeStr, "-")
// length: only 1 or 2 is correct

View File

@@ -239,14 +239,14 @@ const (
)
var proxyConfigTypeMap = map[ProxyType]reflect.Type{
ProxyTypeTCP: reflect.TypeOf(TCPProxyConfig{}),
ProxyTypeUDP: reflect.TypeOf(UDPProxyConfig{}),
ProxyTypeHTTP: reflect.TypeOf(HTTPProxyConfig{}),
ProxyTypeHTTPS: reflect.TypeOf(HTTPSProxyConfig{}),
ProxyTypeTCPMUX: reflect.TypeOf(TCPMuxProxyConfig{}),
ProxyTypeSTCP: reflect.TypeOf(STCPProxyConfig{}),
ProxyTypeXTCP: reflect.TypeOf(XTCPProxyConfig{}),
ProxyTypeSUDP: reflect.TypeOf(SUDPProxyConfig{}),
ProxyTypeTCP: reflect.TypeFor[TCPProxyConfig](),
ProxyTypeUDP: reflect.TypeFor[UDPProxyConfig](),
ProxyTypeHTTP: reflect.TypeFor[HTTPProxyConfig](),
ProxyTypeHTTPS: reflect.TypeFor[HTTPSProxyConfig](),
ProxyTypeTCPMUX: reflect.TypeFor[TCPMuxProxyConfig](),
ProxyTypeSTCP: reflect.TypeFor[STCPProxyConfig](),
ProxyTypeXTCP: reflect.TypeFor[XTCPProxyConfig](),
ProxyTypeSUDP: reflect.TypeFor[SUDPProxyConfig](),
}
func NewProxyConfigurerByType(proxyType ProxyType) ProxyConfigurer {

View File

@@ -37,16 +37,16 @@ const (
)
var clientPluginOptionsTypeMap = map[string]reflect.Type{
PluginHTTP2HTTPS: reflect.TypeOf(HTTP2HTTPSPluginOptions{}),
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
PluginHTTP2HTTP: reflect.TypeOf(HTTP2HTTPPluginOptions{}),
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
PluginTLS2Raw: reflect.TypeOf(TLS2RawPluginOptions{}),
PluginVirtualNet: reflect.TypeOf(VirtualNetPluginOptions{}),
PluginHTTP2HTTPS: reflect.TypeFor[HTTP2HTTPSPluginOptions](),
PluginHTTPProxy: reflect.TypeFor[HTTPProxyPluginOptions](),
PluginHTTPS2HTTP: reflect.TypeFor[HTTPS2HTTPPluginOptions](),
PluginHTTPS2HTTPS: reflect.TypeFor[HTTPS2HTTPSPluginOptions](),
PluginHTTP2HTTP: reflect.TypeFor[HTTP2HTTPPluginOptions](),
PluginSocks5: reflect.TypeFor[Socks5PluginOptions](),
PluginStaticFile: reflect.TypeFor[StaticFilePluginOptions](),
PluginUnixDomainSocket: reflect.TypeFor[UnixDomainSocketPluginOptions](),
PluginTLS2Raw: reflect.TypeFor[TLS2RawPluginOptions](),
PluginVirtualNet: reflect.TypeFor[VirtualNetPluginOptions](),
}
type ClientPluginOptions interface {

View File

@@ -79,9 +79,9 @@ const (
)
var visitorConfigTypeMap = map[VisitorType]reflect.Type{
VisitorTypeSTCP: reflect.TypeOf(STCPVisitorConfig{}),
VisitorTypeXTCP: reflect.TypeOf(XTCPVisitorConfig{}),
VisitorTypeSUDP: reflect.TypeOf(SUDPVisitorConfig{}),
VisitorTypeSTCP: reflect.TypeFor[STCPVisitorConfig](),
VisitorTypeXTCP: reflect.TypeFor[XTCPVisitorConfig](),
VisitorTypeSUDP: reflect.TypeFor[SUDPVisitorConfig](),
}
type TypedVisitorConfig struct {

View File

@@ -25,7 +25,7 @@ const (
)
var visitorPluginOptionsTypeMap = map[string]reflect.Type{
VisitorPluginVirtualNet: reflect.TypeOf(VirtualNetVisitorPluginOptions{}),
VisitorPluginVirtualNet: reflect.TypeFor[VirtualNetVisitorPluginOptions](),
}
type VisitorPluginOptions interface {

View File

@@ -61,7 +61,7 @@ var msgTypeMap = map[byte]any{
TypeNatHoleReport: NatHoleReport{},
}
var TypeNameNatHoleResp = reflect.TypeOf(&NatHoleResp{}).Elem().Name()
var TypeNameNatHoleResp = reflect.TypeFor[NatHoleResp]().Name()
type ClientSpec struct {
// Due to the support of VirtualClient, frps needs to know the client type in order to

View File

@@ -151,7 +151,7 @@ func getBehaviorScoresByMode(mode int, defaultScore int) []*BehaviorScore {
func getBehaviorScoresByMode2(mode int, senderScore, receiverScore int) []*BehaviorScore {
behaviors := getBehaviorByMode(mode)
scores := make([]*BehaviorScore, 0, len(behaviors))
for i := 0; i < len(behaviors); i++ {
for i := range behaviors {
score := receiverScore
if behaviors[i].A.Role == DetectRoleSender {
score = senderScore

View File

@@ -410,7 +410,7 @@ func sendSidMessageToRandomPorts(
xl := xlog.FromContextSafe(ctx)
used := sets.New[int]()
getUnusedPort := func() int {
for i := 0; i < 10; i++ {
for range 10 {
port := rand.IntN(65535-1024) + 1024
if !used.Has(port) {
used.Insert(port)
@@ -420,7 +420,7 @@ func sendSidMessageToRandomPorts(
return 0
}
for i := 0; i < count; i++ {
for range count {
select {
case <-ctx.Done():
return

View File

@@ -153,10 +153,7 @@ func (p *VirtualNetPlugin) run() {
// Exponential backoff: 60s, 120s, 240s, 300s (capped)
baseDelay := 60 * time.Second
reconnectDelay = baseDelay * time.Duration(1<<uint(p.consecutiveErrors-1))
if reconnectDelay > 300*time.Second {
reconnectDelay = 300 * time.Second
}
reconnectDelay = min(baseDelay*time.Duration(1<<uint(p.consecutiveErrors-1)), 300*time.Second)
} else {
// Reset consecutive errors on successful connection
if p.consecutiveErrors > 0 {

View File

@@ -16,6 +16,7 @@ package featuregate
import (
"fmt"
"maps"
"sort"
"strings"
"sync"
@@ -93,9 +94,7 @@ type featureGate struct {
// NewFeatureGate creates a new feature gate with the default features
func NewFeatureGate() MutableFeatureGate {
known := map[Feature]FeatureSpec{}
for k, v := range defaultFeatures {
known[k] = v
}
maps.Copy(known, defaultFeatures)
f := &featureGate{}
f.known.Store(known)
@@ -110,13 +109,9 @@ func (f *featureGate) SetFromMap(m map[string]bool) error {
// Copy existing state
known := map[Feature]FeatureSpec{}
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
known[k] = v
}
maps.Copy(known, f.known.Load().(map[Feature]FeatureSpec))
enabled := map[Feature]bool{}
for k, v := range f.enabled.Load().(map[Feature]bool) {
enabled[k] = v
}
maps.Copy(enabled, f.enabled.Load().(map[Feature]bool))
// Apply the new settings
for k, v := range m {
@@ -148,9 +143,7 @@ func (f *featureGate) Add(features map[Feature]FeatureSpec) error {
// Copy existing state
known := map[Feature]FeatureSpec{}
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
known[k] = v
}
maps.Copy(known, f.known.Load().(map[Feature]FeatureSpec))
// Add new features
for name, spec := range features {

View File

@@ -89,11 +89,11 @@ func ParseBasicAuth(auth string) (username, password string, ok bool) {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
before, after, found := strings.Cut(cs, ":")
if !found {
return
}
return cs[:s], cs[s+1:], true
return before, after, true
}
func BasicAuth(username, passwd string) string {

View File

@@ -86,11 +86,7 @@ func (c *FakeUDPConn) Read(b []byte) (n int, err error) {
c.lastActive = time.Now()
c.mu.Unlock()
if len(b) < len(content) {
n = len(b)
} else {
n = len(content)
}
n = min(len(b), len(content))
copy(b, content)
return n, nil
}

View File

@@ -68,8 +68,8 @@ func ParseRangeNumbers(rangeStr string) (numbers []int64, err error) {
rangeStr = strings.TrimSpace(rangeStr)
numbers = make([]int64, 0)
// e.g. 1000-2000,2001,2002,3000-4000
numRanges := strings.Split(rangeStr, ",")
for _, numRangeStr := range numRanges {
numRanges := strings.SplitSeq(rangeStr, ",")
for numRangeStr := range numRanges {
// 1000-2000 or 2001
numArray := strings.Split(numRangeStr, "-")
// length: only 1 or 2 is correct

View File

@@ -95,20 +95,33 @@ func (cm *ControlManager) Close() error {
return nil
}
type Control struct {
// SessionContext encapsulates the input parameters for creating a new Control.
type SessionContext struct {
// all resource managers and controllers
rc *controller.ResourceController
RC *controller.ResourceController
// proxy manager
pxyManager *proxy.Manager
PxyManager *proxy.Manager
// plugin manager
pluginManager *plugin.Manager
PluginManager *plugin.Manager
// verifies authentication based on selected method
authVerifier auth.Verifier
AuthVerifier auth.Verifier
// key used for connection encryption
encryptionKey []byte
EncryptionKey []byte
// control connection
Conn net.Conn
// indicates whether the connection is encrypted
ConnEncrypted bool
// login message
LoginMsg *msg.Login
// server configuration
ServerCfg *v1.ServerConfig
// client registry
ClientRegistry *registry.ClientRegistry
}
type Control struct {
// session context
sessionCtx *SessionContext
// other components can use this to communicate with client
msgTransporter transport.MessageTransporter
@@ -117,12 +130,6 @@ type Control struct {
// It provides a channel for sending messages, and you can register handlers to process messages based on their respective types.
msgDispatcher *msg.Dispatcher
// login message
loginMsg *msg.Login
// control connection
conn net.Conn
// work connections
workConnCh chan net.Conn
@@ -145,61 +152,34 @@ type Control struct {
mu sync.RWMutex
// Server configuration information
serverCfg *v1.ServerConfig
clientRegistry *registry.ClientRegistry
xl *xlog.Logger
ctx context.Context
doneCh chan struct{}
}
// TODO(fatedier): Referencing the implementation of frpc, encapsulate the input parameters as SessionContext.
func NewControl(
ctx context.Context,
rc *controller.ResourceController,
pxyManager *proxy.Manager,
pluginManager *plugin.Manager,
authVerifier auth.Verifier,
encryptionKey []byte,
ctlConn net.Conn,
ctlConnEncrypted bool,
loginMsg *msg.Login,
serverCfg *v1.ServerConfig,
) (*Control, error) {
poolCount := loginMsg.PoolCount
if poolCount > int(serverCfg.Transport.MaxPoolCount) {
poolCount = int(serverCfg.Transport.MaxPoolCount)
}
func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, error) {
poolCount := min(sessionCtx.LoginMsg.PoolCount, int(sessionCtx.ServerCfg.Transport.MaxPoolCount))
ctl := &Control{
rc: rc,
pxyManager: pxyManager,
pluginManager: pluginManager,
authVerifier: authVerifier,
encryptionKey: encryptionKey,
conn: ctlConn,
loginMsg: loginMsg,
workConnCh: make(chan net.Conn, poolCount+10),
proxies: make(map[string]proxy.Proxy),
poolCount: poolCount,
portsUsedNum: 0,
runID: loginMsg.RunID,
serverCfg: serverCfg,
xl: xlog.FromContextSafe(ctx),
ctx: ctx,
doneCh: make(chan struct{}),
sessionCtx: sessionCtx,
workConnCh: make(chan net.Conn, poolCount+10),
proxies: make(map[string]proxy.Proxy),
poolCount: poolCount,
portsUsedNum: 0,
runID: sessionCtx.LoginMsg.RunID,
xl: xlog.FromContextSafe(ctx),
ctx: ctx,
doneCh: make(chan struct{}),
}
ctl.lastPing.Store(time.Now())
if ctlConnEncrypted {
cryptoRW, err := netpkg.NewCryptoReadWriter(ctl.conn, ctl.encryptionKey)
if sessionCtx.ConnEncrypted {
cryptoRW, err := netpkg.NewCryptoReadWriter(sessionCtx.Conn, sessionCtx.EncryptionKey)
if err != nil {
return nil, err
}
ctl.msgDispatcher = msg.NewDispatcher(cryptoRW)
} else {
ctl.msgDispatcher = msg.NewDispatcher(ctl.conn)
ctl.msgDispatcher = msg.NewDispatcher(sessionCtx.Conn)
}
ctl.registerMsgHandlers()
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher)
@@ -213,7 +193,7 @@ func (ctl *Control) Start() {
RunID: ctl.runID,
Error: "",
}
_ = msg.WriteMsg(ctl.conn, loginRespMsg)
_ = msg.WriteMsg(ctl.sessionCtx.Conn, loginRespMsg)
go func() {
for i := 0; i < ctl.poolCount; i++ {
@@ -225,7 +205,7 @@ func (ctl *Control) Start() {
}
func (ctl *Control) Close() error {
ctl.conn.Close()
ctl.sessionCtx.Conn.Close()
return nil
}
@@ -233,7 +213,7 @@ func (ctl *Control) Replaced(newCtl *Control) {
xl := ctl.xl
xl.Infof("replaced by client [%s]", newCtl.runID)
ctl.runID = ""
ctl.conn.Close()
ctl.sessionCtx.Conn.Close()
}
func (ctl *Control) RegisterWorkConn(conn net.Conn) error {
@@ -291,7 +271,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
return
}
case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second):
case <-time.After(time.Duration(ctl.sessionCtx.ServerCfg.UserConnTimeout) * time.Second):
err = fmt.Errorf("timeout trying to get work connection")
xl.Warnf("%v", err)
return
@@ -304,15 +284,15 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
}
func (ctl *Control) heartbeatWorker() {
if ctl.serverCfg.Transport.HeartbeatTimeout <= 0 {
if ctl.sessionCtx.ServerCfg.Transport.HeartbeatTimeout <= 0 {
return
}
xl := ctl.xl
go wait.Until(func() {
if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second {
if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.sessionCtx.ServerCfg.Transport.HeartbeatTimeout)*time.Second {
xl.Warnf("heartbeat timeout")
ctl.conn.Close()
ctl.sessionCtx.Conn.Close()
return
}
}, time.Second, ctl.doneCh)
@@ -323,6 +303,30 @@ func (ctl *Control) WaitClosed() {
<-ctl.doneCh
}
func (ctl *Control) loginUserInfo() plugin.UserInfo {
return plugin.UserInfo{
User: ctl.sessionCtx.LoginMsg.User,
Metas: ctl.sessionCtx.LoginMsg.Metas,
RunID: ctl.sessionCtx.LoginMsg.RunID,
}
}
func (ctl *Control) closeProxy(pxy proxy.Proxy) {
pxy.Close()
ctl.sessionCtx.PxyManager.Del(pxy.GetName())
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConfigurer().GetBaseConfig().Type)
notifyContent := &plugin.CloseProxyContent{
User: ctl.loginUserInfo(),
CloseProxy: msg.CloseProxy{
ProxyName: pxy.GetName(),
},
}
go func() {
_ = ctl.sessionCtx.PluginManager.CloseProxy(notifyContent)
}()
}
func (ctl *Control) worker() {
xl := ctl.xl
@@ -330,38 +334,23 @@ func (ctl *Control) worker() {
go ctl.msgDispatcher.Run()
<-ctl.msgDispatcher.Done()
ctl.conn.Close()
ctl.sessionCtx.Conn.Close()
ctl.mu.Lock()
defer ctl.mu.Unlock()
close(ctl.workConnCh)
for workConn := range ctl.workConnCh {
workConn.Close()
}
proxies := ctl.proxies
ctl.proxies = make(map[string]proxy.Proxy)
ctl.mu.Unlock()
for _, pxy := range ctl.proxies {
pxy.Close()
ctl.pxyManager.Del(pxy.GetName())
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConfigurer().GetBaseConfig().Type)
notifyContent := &plugin.CloseProxyContent{
User: plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.loginMsg.RunID,
},
CloseProxy: msg.CloseProxy{
ProxyName: pxy.GetName(),
},
}
go func() {
_ = ctl.pluginManager.CloseProxy(notifyContent)
}()
for _, pxy := range proxies {
ctl.closeProxy(pxy)
}
metrics.Server.CloseClient()
ctl.clientRegistry.MarkOfflineByRunID(ctl.runID)
ctl.sessionCtx.ClientRegistry.MarkOfflineByRunID(ctl.runID)
xl.Infof("client exit success")
close(ctl.doneCh)
}
@@ -380,15 +369,11 @@ func (ctl *Control) handleNewProxy(m msg.Message) {
inMsg := m.(*msg.NewProxy)
content := &plugin.NewProxyContent{
User: plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.loginMsg.RunID,
},
User: ctl.loginUserInfo(),
NewProxy: *inMsg,
}
var remoteAddr string
retContent, err := ctl.pluginManager.NewProxy(content)
retContent, err := ctl.sessionCtx.PluginManager.NewProxy(content)
if err == nil {
inMsg = &retContent.NewProxy
remoteAddr, err = ctl.RegisterProxy(inMsg)
@@ -401,15 +386,15 @@ func (ctl *Control) handleNewProxy(m msg.Message) {
if err != nil {
xl.Warnf("new proxy [%s] type [%s] error: %v", inMsg.ProxyName, inMsg.ProxyType, err)
resp.Error = util.GenerateResponseErrorString(fmt.Sprintf("new proxy [%s] error", inMsg.ProxyName),
err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient))
err, lo.FromPtr(ctl.sessionCtx.ServerCfg.DetailedErrorsToClient))
} else {
resp.RemoteAddr = remoteAddr
xl.Infof("new proxy [%s] type [%s] success", inMsg.ProxyName, inMsg.ProxyType)
clientID := ctl.loginMsg.ClientID
clientID := ctl.sessionCtx.LoginMsg.ClientID
if clientID == "" {
clientID = ctl.loginMsg.RunID
clientID = ctl.sessionCtx.LoginMsg.RunID
}
metrics.Server.NewProxy(inMsg.ProxyName, inMsg.ProxyType, ctl.loginMsg.User, clientID)
metrics.Server.NewProxy(inMsg.ProxyName, inMsg.ProxyType, ctl.sessionCtx.LoginMsg.User, clientID)
}
_ = ctl.msgDispatcher.Send(resp)
}
@@ -419,22 +404,18 @@ func (ctl *Control) handlePing(m msg.Message) {
inMsg := m.(*msg.Ping)
content := &plugin.PingContent{
User: plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.loginMsg.RunID,
},
User: ctl.loginUserInfo(),
Ping: *inMsg,
}
retContent, err := ctl.pluginManager.Ping(content)
retContent, err := ctl.sessionCtx.PluginManager.Ping(content)
if err == nil {
inMsg = &retContent.Ping
err = ctl.authVerifier.VerifyPing(inMsg)
err = ctl.sessionCtx.AuthVerifier.VerifyPing(inMsg)
}
if err != nil {
xl.Warnf("received invalid ping: %v", err)
_ = ctl.msgDispatcher.Send(&msg.Pong{
Error: util.GenerateResponseErrorString("invalid ping", err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient)),
Error: util.GenerateResponseErrorString("invalid ping", err, lo.FromPtr(ctl.sessionCtx.ServerCfg.DetailedErrorsToClient)),
})
return
}
@@ -445,17 +426,17 @@ func (ctl *Control) handlePing(m msg.Message) {
func (ctl *Control) handleNatHoleVisitor(m msg.Message) {
inMsg := m.(*msg.NatHoleVisitor)
ctl.rc.NatHoleController.HandleVisitor(inMsg, ctl.msgTransporter, ctl.loginMsg.User)
ctl.sessionCtx.RC.NatHoleController.HandleVisitor(inMsg, ctl.msgTransporter, ctl.sessionCtx.LoginMsg.User)
}
func (ctl *Control) handleNatHoleClient(m msg.Message) {
inMsg := m.(*msg.NatHoleClient)
ctl.rc.NatHoleController.HandleClient(inMsg, ctl.msgTransporter)
ctl.sessionCtx.RC.NatHoleController.HandleClient(inMsg, ctl.msgTransporter)
}
func (ctl *Control) handleNatHoleReport(m msg.Message) {
inMsg := m.(*msg.NatHoleReport)
ctl.rc.NatHoleController.HandleReport(inMsg)
ctl.sessionCtx.RC.NatHoleController.HandleReport(inMsg)
}
func (ctl *Control) handleCloseProxy(m msg.Message) {
@@ -468,15 +449,15 @@ func (ctl *Control) handleCloseProxy(m msg.Message) {
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
var pxyConf v1.ProxyConfigurer
// Load configures from NewProxy message and validate.
pxyConf, err = config.NewProxyConfigurerFromMsg(pxyMsg, ctl.serverCfg)
pxyConf, err = config.NewProxyConfigurerFromMsg(pxyMsg, ctl.sessionCtx.ServerCfg)
if err != nil {
return
}
// User info
userInfo := plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
User: ctl.sessionCtx.LoginMsg.User,
Metas: ctl.sessionCtx.LoginMsg.Metas,
RunID: ctl.runID,
}
@@ -484,22 +465,22 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
// In fact, it creates different proxies based on the proxy type. We just call run() here.
pxy, err := proxy.NewProxy(ctl.ctx, &proxy.Options{
UserInfo: userInfo,
LoginMsg: ctl.loginMsg,
LoginMsg: ctl.sessionCtx.LoginMsg,
PoolCount: ctl.poolCount,
ResourceController: ctl.rc,
ResourceController: ctl.sessionCtx.RC,
GetWorkConnFn: ctl.GetWorkConn,
Configurer: pxyConf,
ServerCfg: ctl.serverCfg,
EncryptionKey: ctl.encryptionKey,
ServerCfg: ctl.sessionCtx.ServerCfg,
EncryptionKey: ctl.sessionCtx.EncryptionKey,
})
if err != nil {
return remoteAddr, err
}
// Check ports used number in each client
if ctl.serverCfg.MaxPortsPerClient > 0 {
if ctl.sessionCtx.ServerCfg.MaxPortsPerClient > 0 {
ctl.mu.Lock()
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.serverCfg.MaxPortsPerClient) {
if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.sessionCtx.ServerCfg.MaxPortsPerClient) {
ctl.mu.Unlock()
err = fmt.Errorf("exceed the max_ports_per_client")
return
@@ -516,7 +497,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
}()
}
if ctl.pxyManager.Exist(pxyMsg.ProxyName) {
if ctl.sessionCtx.PxyManager.Exist(pxyMsg.ProxyName) {
err = fmt.Errorf("proxy [%s] already exists", pxyMsg.ProxyName)
return
}
@@ -531,7 +512,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
}
}()
err = ctl.pxyManager.Add(pxyMsg.ProxyName, pxy)
err = ctl.sessionCtx.PxyManager.Add(pxyMsg.ProxyName, pxy)
if err != nil {
return
}
@@ -550,28 +531,12 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
return
}
if ctl.serverCfg.MaxPortsPerClient > 0 {
if ctl.sessionCtx.ServerCfg.MaxPortsPerClient > 0 {
ctl.portsUsedNum -= pxy.GetUsedPortsNum()
}
pxy.Close()
ctl.pxyManager.Del(pxy.GetName())
delete(ctl.proxies, closeMsg.ProxyName)
ctl.mu.Unlock()
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConfigurer().GetBaseConfig().Type)
notifyContent := &plugin.CloseProxyContent{
User: plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.loginMsg.RunID,
},
CloseProxy: msg.CloseProxy{
ProxyName: pxy.GetName(),
},
}
go func() {
_ = ctl.pluginManager.CloseProxy(notifyContent)
}()
ctl.closeProxy(pxy)
return
}

View File

@@ -31,7 +31,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.HTTPProxyConfig{}), NewHTTPProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.HTTPProxyConfig](), NewHTTPProxy)
}
type HTTPProxy struct {

View File

@@ -25,7 +25,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.HTTPSProxyConfig{}), NewHTTPSProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.HTTPSProxyConfig](), NewHTTPSProxy)
}
type HTTPSProxy struct {

View File

@@ -21,7 +21,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.STCPProxyConfig{}), NewSTCPProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.STCPProxyConfig](), NewSTCPProxy)
}
type STCPProxy struct {

View File

@@ -21,7 +21,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.SUDPProxyConfig{}), NewSUDPProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.SUDPProxyConfig](), NewSUDPProxy)
}
type SUDPProxy struct {

View File

@@ -24,7 +24,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.TCPProxyConfig{}), NewTCPProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.TCPProxyConfig](), NewTCPProxy)
}
type TCPProxy struct {

View File

@@ -26,7 +26,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.TCPMuxProxyConfig{}), NewTCPMuxProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.TCPMuxProxyConfig](), NewTCPMuxProxy)
}
type TCPMuxProxy struct {

View File

@@ -35,7 +35,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.UDPProxyConfig{}), NewUDPProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.UDPProxyConfig](), NewUDPProxy)
}
type UDPProxy struct {

View File

@@ -24,7 +24,7 @@ import (
)
func init() {
RegisterProxyFactory(reflect.TypeOf(&v1.XTCPProxyConfig{}), NewXTCPProxy)
RegisterProxyFactory(reflect.TypeFor[*v1.XTCPProxyConfig](), NewXTCPProxy)
}
type XTCPProxy struct {

View File

@@ -193,7 +193,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) {
if err != nil {
return nil, fmt.Errorf("create vhost tcpMuxer error, %v", err)
}
log.Infof("tcpmux httpconnect multiplexer listen on %s, passthough: %v", address, cfg.TCPMuxPassthrough)
log.Infof("tcpmux httpconnect multiplexer listen on %s, passthrough: %v", address, cfg.TCPMuxPassthrough)
}
// Init all plugins
@@ -604,8 +604,18 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, inter
return err
}
// TODO(fatedier): use SessionContext
ctl, err := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, authVerifier, svr.auth.EncryptionKey(), ctlConn, !internal, loginMsg, svr.cfg)
ctl, err := NewControl(ctx, &SessionContext{
RC: svr.rc,
PxyManager: svr.pxyManager,
PluginManager: svr.pluginManager,
AuthVerifier: authVerifier,
EncryptionKey: svr.auth.EncryptionKey(),
Conn: ctlConn,
ConnEncrypted: !internal,
LoginMsg: loginMsg,
ServerCfg: svr.cfg,
ClientRegistry: svr.clientRegistry,
})
if err != nil {
xl.Warnf("create new controller error: %v", err)
// don't return detailed errors to client
@@ -626,7 +636,6 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, inter
ctl.Close()
return fmt.Errorf("client_id [%s] for user [%s] is already online", loginMsg.ClientID, loginMsg.User)
}
ctl.clientRegistry = svr.clientRegistry
ctl.Start()
@@ -652,9 +661,9 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn)
// server plugin hook
content := &plugin.NewWorkConnContent{
User: plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.loginMsg.RunID,
User: ctl.sessionCtx.LoginMsg.User,
Metas: ctl.sessionCtx.LoginMsg.Metas,
RunID: ctl.sessionCtx.LoginMsg.RunID,
},
NewWorkConn: *newMsg,
}
@@ -662,7 +671,7 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn)
if err == nil {
newMsg = &retContent.NewWorkConn
// Check auth.
err = ctl.authVerifier.VerifyNewWorkConn(newMsg)
err = ctl.sessionCtx.AuthVerifier.VerifyNewWorkConn(newMsg)
}
if err != nil {
xl.Warnf("invalid NewWorkConn with run id [%s]", newMsg.RunID)
@@ -683,7 +692,7 @@ func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVis
if !exist {
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
}
visitorUser = ctl.loginMsg.User
visitorUser = ctl.sessionCtx.LoginMsg.User
}
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
newMsg.UseEncryption, newMsg.UseCompression, visitorUser)

View File

@@ -2,6 +2,7 @@ package framework
import (
"fmt"
"maps"
"os"
"path/filepath"
"time"
@@ -20,9 +21,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
ExpectNoError(err)
ExpectTrue(len(templates) > 0)
for name, port := range ports {
f.usedPorts[name] = port
}
maps.Copy(f.usedPorts, ports)
currentServerProcesses := make([]*process.Process, 0, len(serverTemplates))
for i := range serverTemplates {

View File

@@ -26,7 +26,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
proxyType := t
ginkgo.It(fmt.Sprintf("Expose a %s echo server", strings.ToUpper(proxyType)), func() {
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
var clientConf strings.Builder
clientConf.WriteString(consts.LegacyDefaultClientConfig)
localPortName := ""
protocol := "tcp"
@@ -78,10 +79,10 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
clientConf.WriteString(getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n")
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
f.RunProcesses([]string{serverConf}, []string{clientConf.String()})
for _, test := range tests {
framework.NewRequestExpect(f).
@@ -102,7 +103,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
vhost_http_port = %d
`, vhostHTTPPort)
clientConf := consts.LegacyDefaultClientConfig
var clientConf strings.Builder
clientConf.WriteString(consts.LegacyDefaultClientConfig)
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
@@ -147,13 +149,13 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
if tests[i].customDomains == "" {
tests[i].customDomains = test.proxyName + ".example.com"
}
clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
clientConf.WriteString(getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n")
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
f.RunProcesses([]string{serverConf}, []string{clientConf.String()})
for _, test := range tests {
for _, domain := range strings.Split(test.customDomains, ",") {
for domain := range strings.SplitSeq(test.customDomains, ",") {
domain = strings.TrimSpace(domain)
framework.NewRequestExpect(f).
Explain(test.proxyName + "-" + domain).
@@ -185,7 +187,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
`, vhostHTTPSPort)
localPort := f.AllocPort()
clientConf := consts.LegacyDefaultClientConfig
var clientConf strings.Builder
clientConf.WriteString(consts.LegacyDefaultClientConfig)
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
[%s]
@@ -229,10 +232,10 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
if tests[i].customDomains == "" {
tests[i].customDomains = test.proxyName + ".example.com"
}
clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
clientConf.WriteString(getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n")
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
f.RunProcesses([]string{serverConf}, []string{clientConf.String()})
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
framework.ExpectNoError(err)
@@ -244,7 +247,7 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
f.RunServer("", localServer)
for _, test := range tests {
for _, domain := range strings.Split(test.customDomains, ",") {
for domain := range strings.SplitSeq(test.customDomains, ",") {
domain = strings.TrimSpace(domain)
framework.NewRequestExpect(f).
Explain(test.proxyName + "-" + domain).
@@ -282,9 +285,12 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
proxyType := t
ginkgo.It(fmt.Sprintf("Expose echo server with %s", strings.ToUpper(proxyType)), func() {
serverConf := consts.LegacyDefaultServerConfig
clientServerConf := consts.LegacyDefaultClientConfig + "\nuser = user1"
clientVisitorConf := consts.LegacyDefaultClientConfig + "\nuser = user1"
clientUser2VisitorConf := consts.LegacyDefaultClientConfig + "\nuser = user2"
var clientServerConf strings.Builder
clientServerConf.WriteString(consts.LegacyDefaultClientConfig + "\nuser = user1")
var clientVisitorConf strings.Builder
clientVisitorConf.WriteString(consts.LegacyDefaultClientConfig + "\nuser = user1")
var clientUser2VisitorConf strings.Builder
clientUser2VisitorConf.WriteString(consts.LegacyDefaultClientConfig + "\nuser = user2")
localPortName := ""
protocol := "tcp"
@@ -400,20 +406,20 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
// build all client config
for _, test := range tests {
clientServerConf += getProxyServerConf(test.proxyName, test.commonExtraConfig+"\n"+test.proxyExtraConfig) + "\n"
clientServerConf.WriteString(getProxyServerConf(test.proxyName, test.commonExtraConfig+"\n"+test.proxyExtraConfig) + "\n")
}
for _, test := range tests {
config := getProxyVisitorConf(
test.proxyName, test.bindPortName, test.visitorSK, test.commonExtraConfig+"\n"+test.visitorExtraConfig,
) + "\n"
if test.deployUser2Client {
clientUser2VisitorConf += config
clientUser2VisitorConf.WriteString(config)
} else {
clientVisitorConf += config
clientVisitorConf.WriteString(config)
}
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf, clientUser2VisitorConf})
f.RunProcesses([]string{serverConf}, []string{clientServerConf.String(), clientVisitorConf.String(), clientUser2VisitorConf.String()})
for _, test := range tests {
timeout := time.Second
@@ -440,7 +446,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
ginkgo.Describe("TCPMUX", func() {
ginkgo.It("Type tcpmux", func() {
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
var clientConf strings.Builder
clientConf.WriteString(consts.LegacyDefaultClientConfig)
tcpmuxHTTPConnectPortName := port.GenName("TCPMUX")
serverConf += fmt.Sprintf(`
@@ -483,14 +490,14 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.extraConfig) + "\n"
clientConf.WriteString(getProxyConf(test.proxyName, test.extraConfig) + "\n")
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(f.AllocPort()), streamserver.WithRespContent([]byte(test.proxyName)))
f.RunServer(port.GenName(test.proxyName), localServer)
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
f.RunProcesses([]string{serverConf}, []string{clientConf.String()})
// Request without HTTP connect should get error
framework.NewRequestExpect(f).

View File

@@ -28,7 +28,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
tcpPortName := port.GenName("TCP", port.WithRangePorts(10000, 11000))
udpPortName := port.GenName("UDP", port.WithRangePorts(12000, 13000))
clientConf += fmt.Sprintf(`
[tcp-allowded-in-range]
[tcp-allowed-in-range]
type = tcp
local_port = {{ .%s }}
remote_port = {{ .%s }}

View File

@@ -48,12 +48,10 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
return true
})
}
for i := 0; i < 10; i++ {
wait.Add(1)
go func() {
defer wait.Done()
for range 10 {
wait.Go(func() {
expectFn()
}()
})
}
wait.Wait()
@@ -94,7 +92,7 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
fooCount := 0
barCount := 0
for i := 0; i < 10; i++ {
for i := range 10 {
framework.NewRequestExpect(f).Explain("times " + strconv.Itoa(i)).Port(remotePort).Ensure(func(resp *request.Response) bool {
switch string(resp.Content) {
case "foo":
@@ -150,7 +148,7 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
// check foo and bar is ok
results := []string{}
for i := 0; i < 10; i++ {
for range 10 {
framework.NewRequestExpect(f).Port(remotePort).Ensure(validateFooBarResponse, func(resp *request.Response) bool {
results = append(results, string(resp.Content))
return true
@@ -161,7 +159,7 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
// close bar server, check foo is ok
barServer.Close()
time.Sleep(2 * time.Second)
for i := 0; i < 10; i++ {
for range 10 {
framework.NewRequestExpect(f).Port(remotePort).ExpectResp([]byte("foo")).Ensure()
}
@@ -169,7 +167,7 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
f.RunServer("", barServer)
time.Sleep(2 * time.Second)
results = []string{}
for i := 0; i < 10; i++ {
for range 10 {
framework.NewRequestExpect(f).Port(remotePort).Ensure(validateFooBarResponse, func(resp *request.Response) bool {
results = append(results, string(resp.Content))
return true

View File

@@ -4,6 +4,7 @@ import (
"crypto/tls"
"fmt"
"strconv"
"strings"
"github.com/onsi/ginkgo/v2"
@@ -22,7 +23,8 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
ginkgo.Describe("UnixDomainSocket", func() {
ginkgo.It("Expose a unix domain socket echo server", func() {
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
var clientConf strings.Builder
clientConf.WriteString(consts.LegacyDefaultClientConfig)
getProxyConf := func(proxyName string, portName string, extra string) string {
return fmt.Sprintf(`
@@ -65,10 +67,10 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
clientConf.WriteString(getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n")
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
f.RunProcesses([]string{serverConf}, []string{clientConf.String()})
for _, test := range tests {
framework.NewRequestExpect(f).Port(f.PortByName(test.portName)).Ensure()

View File

@@ -52,7 +52,7 @@ func (pa *Allocator) GetByName(portName string) int {
pa.mu.Lock()
defer pa.mu.Unlock()
for i := 0; i < 20; i++ {
for range 20 {
port := pa.getByRange(builder.rangePortFrom, builder.rangePortTo)
if port == 0 {
return 0

View File

@@ -75,11 +75,11 @@ func (c *TunnelClient) serveListener() {
if err != nil {
return
}
go c.hanldeConn(conn)
go c.handleConn(conn)
}
}
func (c *TunnelClient) hanldeConn(conn net.Conn) {
func (c *TunnelClient) handleConn(conn net.Conn) {
defer conn.Close()
local, err := net.Dial("tcp", c.localAddr)
if err != nil {

View File

@@ -26,7 +26,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
proxyType := t
ginkgo.It(fmt.Sprintf("Expose a %s echo server", strings.ToUpper(proxyType)), func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
var clientConf strings.Builder
clientConf.WriteString(consts.DefaultClientConfig)
localPortName := ""
protocol := "tcp"
@@ -79,10 +80,10 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
clientConf.WriteString(getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n")
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
f.RunProcesses([]string{serverConf}, []string{clientConf.String()})
for _, test := range tests {
framework.NewRequestExpect(f).
@@ -103,7 +104,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
vhostHTTPPort = %d
`, vhostHTTPPort)
clientConf := consts.DefaultClientConfig
var clientConf strings.Builder
clientConf.WriteString(consts.DefaultClientConfig)
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
@@ -149,13 +151,13 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
if tests[i].customDomains == "" {
tests[i].customDomains = fmt.Sprintf(`["%s"]`, test.proxyName+".example.com")
}
clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
clientConf.WriteString(getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n")
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
f.RunProcesses([]string{serverConf}, []string{clientConf.String()})
for _, test := range tests {
for _, domain := range strings.Split(test.customDomains, ",") {
for domain := range strings.SplitSeq(test.customDomains, ",") {
domain = strings.TrimSpace(domain)
domain = strings.TrimLeft(domain, "[\"")
domain = strings.TrimRight(domain, "]\"")
@@ -189,7 +191,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
`, vhostHTTPSPort)
localPort := f.AllocPort()
clientConf := consts.DefaultClientConfig
var clientConf strings.Builder
clientConf.WriteString(consts.DefaultClientConfig)
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
[[proxies]]
@@ -234,10 +237,10 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
if tests[i].customDomains == "" {
tests[i].customDomains = fmt.Sprintf(`["%s"]`, test.proxyName+".example.com")
}
clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
clientConf.WriteString(getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n")
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
f.RunProcesses([]string{serverConf}, []string{clientConf.String()})
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
framework.ExpectNoError(err)
@@ -249,7 +252,7 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
f.RunServer("", localServer)
for _, test := range tests {
for _, domain := range strings.Split(test.customDomains, ",") {
for domain := range strings.SplitSeq(test.customDomains, ",") {
domain = strings.TrimSpace(domain)
domain = strings.TrimLeft(domain, "[\"")
domain = strings.TrimRight(domain, "]\"")
@@ -289,9 +292,12 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
proxyType := t
ginkgo.It(fmt.Sprintf("Expose echo server with %s", strings.ToUpper(proxyType)), func() {
serverConf := consts.DefaultServerConfig
clientServerConf := consts.DefaultClientConfig + "\nuser = \"user1\""
clientVisitorConf := consts.DefaultClientConfig + "\nuser = \"user1\""
clientUser2VisitorConf := consts.DefaultClientConfig + "\nuser = \"user2\""
var clientServerConf strings.Builder
clientServerConf.WriteString(consts.DefaultClientConfig + "\nuser = \"user1\"")
var clientVisitorConf strings.Builder
clientVisitorConf.WriteString(consts.DefaultClientConfig + "\nuser = \"user1\"")
var clientUser2VisitorConf strings.Builder
clientUser2VisitorConf.WriteString(consts.DefaultClientConfig + "\nuser = \"user2\"")
localPortName := ""
protocol := "tcp"
@@ -407,20 +413,20 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
// build all client config
for _, test := range tests {
clientServerConf += getProxyServerConf(test.proxyName, test.commonExtraConfig+"\n"+test.proxyExtraConfig) + "\n"
clientServerConf.WriteString(getProxyServerConf(test.proxyName, test.commonExtraConfig+"\n"+test.proxyExtraConfig) + "\n")
}
for _, test := range tests {
config := getProxyVisitorConf(
test.proxyName, test.bindPortName, test.visitorSK, test.commonExtraConfig+"\n"+test.visitorExtraConfig,
) + "\n"
if test.deployUser2Client {
clientUser2VisitorConf += config
clientUser2VisitorConf.WriteString(config)
} else {
clientVisitorConf += config
clientVisitorConf.WriteString(config)
}
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf, clientUser2VisitorConf})
f.RunProcesses([]string{serverConf}, []string{clientServerConf.String(), clientVisitorConf.String(), clientUser2VisitorConf.String()})
for _, test := range tests {
timeout := time.Second
@@ -447,7 +453,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
ginkgo.Describe("TCPMUX", func() {
ginkgo.It("Type tcpmux", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
var clientConf strings.Builder
clientConf.WriteString(consts.DefaultClientConfig)
tcpmuxHTTPConnectPortName := port.GenName("TCPMUX")
serverConf += fmt.Sprintf(`
@@ -491,14 +498,14 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.extraConfig) + "\n"
clientConf.WriteString(getProxyConf(test.proxyName, test.extraConfig) + "\n")
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(f.AllocPort()), streamserver.WithRespContent([]byte(test.proxyName)))
f.RunServer(port.GenName(test.proxyName), localServer)
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
f.RunProcesses([]string{serverConf}, []string{clientConf.String()})
// Request without HTTP connect should get error
framework.NewRequestExpect(f).

View File

@@ -33,7 +33,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
udpPortName := port.GenName("UDP", port.WithRangePorts(12000, 13000))
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp-allowded-in-range"
name = "tcp-allowed-in-range"
type = "tcp"
localPort = {{ .%s }}
remotePort = {{ .%s }}

View File

@@ -50,12 +50,10 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
return true
})
}
for i := 0; i < 10; i++ {
wait.Add(1)
go func() {
defer wait.Done()
for range 10 {
wait.Go(func() {
expectFn()
}()
})
}
wait.Wait()
@@ -98,7 +96,7 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
fooCount := 0
barCount := 0
for i := 0; i < 10; i++ {
for i := range 10 {
framework.NewRequestExpect(f).Explain("times " + strconv.Itoa(i)).Port(remotePort).Ensure(func(resp *request.Response) bool {
switch string(resp.Content) {
case "foo":
@@ -163,7 +161,7 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
fooCount := 0
barCount := 0
for i := 0; i < 10; i++ {
for i := range 10 {
framework.NewRequestExpect(f).
Explain("times " + strconv.Itoa(i)).
Port(vhostHTTPSPort).
@@ -230,7 +228,7 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
// check foo and bar is ok
results := []string{}
for i := 0; i < 10; i++ {
for range 10 {
framework.NewRequestExpect(f).Port(remotePort).Ensure(validateFooBarResponse, func(resp *request.Response) bool {
results = append(results, string(resp.Content))
return true
@@ -241,7 +239,7 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
// close bar server, check foo is ok
barServer.Close()
time.Sleep(2 * time.Second)
for i := 0; i < 10; i++ {
for range 10 {
framework.NewRequestExpect(f).Port(remotePort).ExpectResp([]byte("foo")).Ensure()
}
@@ -249,7 +247,7 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
f.RunServer("", barServer)
time.Sleep(2 * time.Second)
results = []string{}
for i := 0; i < 10; i++ {
for range 10 {
framework.NewRequestExpect(f).Port(remotePort).Ensure(validateFooBarResponse, func(resp *request.Response) bool {
results = append(results, string(resp.Content))
return true

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/onsi/ginkgo/v2"
@@ -23,7 +24,8 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
ginkgo.Describe("UnixDomainSocket", func() {
ginkgo.It("Expose a unix domain socket echo server", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
var clientConf strings.Builder
clientConf.WriteString(consts.DefaultClientConfig)
getProxyConf := func(proxyName string, portName string, extra string) string {
return fmt.Sprintf(`
@@ -69,10 +71,10 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
clientConf.WriteString(getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n")
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
f.RunProcesses([]string{serverConf}, []string{clientConf.String()})
for _, test := range tests {
framework.NewRequestExpect(f).Port(f.PortByName(test.portName)).Ensure()