Browse Source

add e2e tests for v1 config (#3608)

pull/3610/head
fatedier 1 year ago committed by GitHub
parent
commit
7cd02f5bd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Release.md
  2. 2
      cmd/frpc/sub/root.go
  3. 11
      cmd/frps/root.go
  4. 9
      conf/frpc.ini
  5. 0
      conf/frpc_legacy_full.ini
  6. 2
      conf/frps.ini
  7. 0
      conf/frps_legacy_full.ini
  8. 2
      go.mod
  9. 3
      go.sum
  10. 2
      hack/run-e2e.sh
  11. 8
      pkg/auth/auth.go
  12. 18
      pkg/config/legacy/conversion.go
  13. 3
      pkg/config/load.go
  14. 2
      pkg/config/v1/client.go
  15. 5
      pkg/config/v1/plugin.go
  16. 18
      pkg/config/v1/server.go
  17. 2
      pkg/config/v1/validation/client.go
  18. 2
      pkg/config/v1/visitor.go
  19. 3
      pkg/plugin/client/plugin.go
  20. 2
      server/dashboard_api.go
  21. 8
      server/service.go
  22. 9
      test/e2e/e2e_test.go
  23. 9
      test/e2e/examples.go
  24. 16
      test/e2e/framework/consts/consts.go
  25. 10
      test/e2e/framework/process.go
  26. 24
      test/e2e/legacy/basic/basic.go
  27. 12
      test/e2e/legacy/basic/client.go
  28. 6
      test/e2e/legacy/basic/client_server.go
  29. 0
      test/e2e/legacy/basic/cmd.go
  30. 4
      test/e2e/legacy/basic/config.go
  31. 18
      test/e2e/legacy/basic/http.go
  32. 16
      test/e2e/legacy/basic/server.go
  33. 8
      test/e2e/legacy/basic/tcpmux.go
  34. 4
      test/e2e/legacy/basic/xtcp.go
  35. 10
      test/e2e/legacy/features/bandwidth_limit.go
  36. 0
      test/e2e/legacy/features/chaos.go
  37. 12
      test/e2e/legacy/features/group.go
  38. 0
      test/e2e/legacy/features/heartbeat.go
  39. 4
      test/e2e/legacy/features/monitor.go
  40. 12
      test/e2e/legacy/features/real_ip.go
  41. 28
      test/e2e/legacy/plugin/client.go
  42. 34
      test/e2e/legacy/plugin/server.go
  43. 0
      test/e2e/legacy/plugin/utils.go
  44. 524
      test/e2e/v1/basic/basic.go
  45. 136
      test/e2e/v1/basic/client.go
  46. 325
      test/e2e/v1/basic/client_server.go
  47. 109
      test/e2e/v1/basic/cmd.go
  48. 84
      test/e2e/v1/basic/config.go
  49. 388
      test/e2e/v1/basic/http.go
  50. 192
      test/e2e/v1/basic/server.go
  51. 223
      test/e2e/v1/basic/tcpmux.go
  52. 53
      test/e2e/v1/basic/xtcp.go
  53. 108
      test/e2e/v1/features/bandwidth_limit.go
  54. 64
      test/e2e/v1/features/chaos.go
  55. 267
      test/e2e/v1/features/group.go
  56. 47
      test/e2e/v1/features/heartbeat.go
  57. 55
      test/e2e/v1/features/monitor.go
  58. 154
      test/e2e/v1/features/real_ip.go
  59. 331
      test/e2e/v1/plugin/client.go
  60. 415
      test/e2e/v1/plugin/server.go
  61. 41
      test/e2e/v1/plugin/utils.go

2
Release.md

@ -1 +1,3 @@
### Features
* Configuration: We now support TOML, YAML, and JSON for configuration. Please note that INI is deprecated and will be removed in future releases. New features will only be available in TOML, YAML, or JSON. Users wanting these new features should switch their configuration format accordingly. #2521

2
cmd/frpc/sub/root.go

@ -186,7 +186,7 @@ func parseClientCommonCfgFromCmd() (*v1.ClientCommonConfig, error) {
cfg.Complete()
err, warning := validation.ValidateClientCommonConfig(cfg)
warning, err := validation.ValidateClientCommonConfig(cfg)
if warning != nil {
fmt.Printf("WARNING: %v\n", warning)
}

11
cmd/frps/root.go

@ -108,7 +108,8 @@ var rootCmd = &cobra.Command{
if cfgFile != "" {
svrCfg, isLegacyFormat, err = config.LoadServerConfig(cfgFile)
if err != nil {
return err
fmt.Println(err)
os.Exit(1)
}
if isLegacyFormat {
fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " +
@ -116,7 +117,8 @@ var rootCmd = &cobra.Command{
}
} else {
if svrCfg, err = parseServerConfigFromCmd(); err != nil {
return err
fmt.Println(err)
os.Exit(1)
}
}
@ -125,7 +127,8 @@ var rootCmd = &cobra.Command{
fmt.Printf("WARNING: %v\n", warning)
}
if err != nil {
return err
fmt.Println(err)
os.Exit(1)
}
if err := runServer(svrCfg); err != nil {
@ -168,7 +171,7 @@ func parseServerConfigFromCmd() (*v1.ServerConfig, error) {
cfg.Log.MaxDays = logMaxDays
cfg.Log.DisablePrintColor = disableLogColor
cfg.SubDomainHost = subDomainHost
cfg.TLS.Force = tlsOnly
cfg.Transport.TLS.Force = tlsOnly
cfg.MaxPortsPerClient = maxPortsPerClient
// Only token authentication is supported in cmd mode

9
conf/frpc.ini

@ -1,9 +0,0 @@
[common]
server_addr = 127.0.0.1
server_port = 7000
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000

0
conf/frpc_full.ini → conf/frpc_legacy_full.ini

2
conf/frps.ini

@ -1,2 +0,0 @@
[common]
bind_port = 7000

0
conf/frps_full.ini → conf/frps_legacy_full.ini

2
go.mod

@ -3,7 +3,6 @@ module github.com/fatedier/frp
go 1.20
require (
github.com/BurntSushi/toml v0.3.1
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/coreos/go-oidc/v3 v3.6.0
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
@ -15,6 +14,7 @@ require (
github.com/hashicorp/yamux v0.1.1
github.com/onsi/ginkgo/v2 v2.11.0
github.com/onsi/gomega v1.27.8
github.com/pelletier/go-toml/v2 v2.1.0
github.com/pion/stun v0.6.1
github.com/pires/go-proxyproto v0.7.0
github.com/prometheus/client_golang v1.16.0

3
go.sum

@ -1,7 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
@ -93,6 +92,8 @@ github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=

2
hack/run-e2e.sh

@ -6,7 +6,7 @@ ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
ginkgo_command=$(which ginkgo 2>/dev/null)
if [ -z "$ginkgo_command" ]; then
echo "ginkgo not found, try to install..."
go install github.com/onsi/ginkgo/v2/ginkgo@v2.8.3
go install github.com/onsi/ginkgo/v2/ginkgo@v2.11.0
fi
debug=false

8
pkg/auth/auth.go

@ -31,9 +31,9 @@ type Setter interface {
func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter) {
switch cfg.Method {
case consts.TokenAuthMethod:
authProvider = NewTokenAuth(cfg.AdditionalAuthScopes, cfg.Token)
authProvider = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
case consts.OidcAuthMethod:
authProvider = NewOidcAuthSetter(cfg.AdditionalAuthScopes, cfg.OIDC)
authProvider = NewOidcAuthSetter(cfg.AdditionalScopes, cfg.OIDC)
default:
panic(fmt.Sprintf("wrong method: '%s'", cfg.Method))
}
@ -49,9 +49,9 @@ type Verifier interface {
func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
switch cfg.Method {
case consts.TokenAuthMethod:
authVerifier = NewTokenAuth(cfg.AdditionalAuthScopes, cfg.Token)
authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
case consts.OidcAuthMethod:
authVerifier = NewOidcAuthVerifier(cfg.AdditionalAuthScopes, cfg.OIDC)
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, cfg.OIDC)
}
return authVerifier
}

18
pkg/config/legacy/conversion.go

@ -29,10 +29,10 @@ func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConf
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)
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
}
if conf.ClientConfig.AuthenticateNewWorkConns {
out.Auth.AdditionalAuthScopes = append(out.Auth.AdditionalAuthScopes, v1.AuthScopeNewWorkConns)
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
}
out.Auth.OIDC.ClientID = conf.ClientConfig.OidcClientID
out.Auth.OIDC.ClientSecret = conf.ClientConfig.OidcClientSecret
@ -89,10 +89,10 @@ func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *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)
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
}
if conf.ServerConfig.AuthenticateNewWorkConns {
out.Auth.AdditionalAuthScopes = append(out.Auth.AdditionalAuthScopes, v1.AuthScopeNewWorkConns)
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
}
out.Auth.OIDC.Audience = conf.ServerConfig.OidcAudience
out.Auth.OIDC.Issuer = conf.ServerConfig.OidcIssuer
@ -146,12 +146,12 @@ func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig {
out.Transport.MaxPoolCount = conf.MaxPoolCount
out.Transport.HeartbeatTimeout = conf.HeartbeatTimeout
out.MaxPortsPerClient = conf.MaxPortsPerClient
out.Transport.TLS.Force = conf.TLSOnly
out.Transport.TLS.CertFile = conf.TLSCertFile
out.Transport.TLS.KeyFile = conf.TLSKeyFile
out.Transport.TLS.TrustedCaFile = conf.TLSTrustedCaFile
out.TLS.Force = conf.TLSOnly
out.TLS.CertFile = conf.TLSCertFile
out.TLS.KeyFile = conf.TLSKeyFile
out.TLS.TrustedCaFile = conf.TLSTrustedCaFile
out.MaxPortsPerClient = conf.MaxPortsPerClient
for _, v := range conf.HTTPPlugins {
out.HTTPPlugins = append(out.HTTPPlugins, v1.HTTPPluginOptions{

3
pkg/config/load.go

@ -23,7 +23,7 @@ import (
"path/filepath"
"strings"
"github.com/BurntSushi/toml"
toml "github.com/pelletier/go-toml/v2"
"github.com/samber/lo"
"gopkg.in/ini.v1"
"k8s.io/apimachinery/pkg/util/sets"
@ -119,7 +119,6 @@ func LoadConfigure(b []byte, c any) error {
return err
}
}
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(b), 4096)
return decoder.Decode(c)
}

2
pkg/config/v1/client.go

@ -168,7 +168,7 @@ type AuthClientConfig struct {
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"`
AdditionalScopes []AuthScope `json:"additionalScopes,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 "".

5
pkg/config/v1/plugin.go

@ -46,10 +46,11 @@ func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
if !ok {
return fmt.Errorf("unknown plugin type: %s", typeStruct.Type)
}
if err := json.Unmarshal(b, v); err != nil {
options := reflect.New(v).Interface().(ClientPluginOptions)
if err := json.Unmarshal(b, options); err != nil {
return err
}
c.ClientPluginOptions = v
c.ClientPluginOptions = options
return nil
}

18
pkg/config/v1/server.go

@ -76,8 +76,6 @@ type ServerConfig struct {
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"`
@ -109,9 +107,6 @@ func (c *ServerConfig) Complete() {
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")
@ -125,10 +120,10 @@ func (c *ServerConfig) Complete() {
}
type AuthServerConfig struct {
Method string `json:"method,omitempty"`
AdditionalAuthScopes []AuthScope `json:"additionalAuthScopes,omitempty"`
Token string `json:"token,omitempty"`
OIDC AuthOIDCServerConfig `json:"oidc,omitempty"`
Method string `json:"method,omitempty"`
AdditionalScopes []AuthScope `json:"additionalScopes,omitempty"`
Token string `json:"token,omitempty"`
OIDC AuthOIDCServerConfig `json:"oidc,omitempty"`
}
func (c *AuthServerConfig) Complete() {
@ -171,6 +166,8 @@ type ServerTransportConfig struct {
HeartbeatTimeout int64 `json:"heartbeatTimeout,omitempty"`
// QUIC options.
QUIC QUICOptions `json:"quic,omitempty"`
// TLS specifies TLS settings for the connection from the client.
TLS TLSServerConfig `json:"tls,omitempty"`
}
func (c *ServerTransportConfig) Complete() {
@ -180,6 +177,9 @@ func (c *ServerTransportConfig) Complete() {
c.MaxPoolCount = util.EmptyOr(c.MaxPoolCount, 5)
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
c.QUIC.Complete()
if c.TLS.TrustedCaFile != "" {
c.TLS.Force = true
}
}
type TLSServerConfig struct {

2
pkg/config/v1/validation/client.go

@ -71,7 +71,7 @@ func ValidateAllClientConfig(c *v1.ClientCommonConfig, pxyCfgs []v1.ProxyConfigu
warning, err := ValidateClientCommonConfig(c)
warnings = AppendError(warnings, warning)
if err != nil {
return err, warnings
return warnings, err
}
}

2
pkg/config/v1/visitor.go

@ -35,7 +35,7 @@ type VisitorBaseConfig struct {
Name string `json:"name"`
Type string `json:"type"`
Transport VisitorTransport `json:"transport,omitempty"`
SecretKey string `json:"sk,omitempty"`
SecretKey string `json:"secretKey,omitempty"`
// if the server user is not set, it defaults to the current user
ServerUser string `json:"serverUser,omitempty"`
ServerName string `json:"serverName,omitempty"`

3
pkg/plugin/client/plugin.go

@ -32,6 +32,9 @@ var creators = make(map[string]CreatorFn)
type CreatorFn func(options v1.ClientPluginOptions) (Plugin, error)
func Register(name string, fn CreatorFn) {
if _, exist := creators[name]; exist {
panic(fmt.Sprintf("plugin [%s] is already registered", name))
}
creators[name] = fn
}

2
server/dashboard_api.go

@ -86,7 +86,7 @@ func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
HeartBeatTimeout: svr.cfg.Transport.HeartbeatTimeout,
AllowPortsStr: types.PortsRangeSlice(svr.cfg.AllowPorts).String(),
TLSOnly: svr.cfg.TLS.Force,
TLSOnly: svr.cfg.Transport.TLS.Force,
TotalTrafficIn: serverStats.TotalTrafficIn,
TotalTrafficOut: serverStats.TotalTrafficOut,

8
server/service.go

@ -108,9 +108,9 @@ type Service struct {
func NewService(cfg *v1.ServerConfig) (svr *Service, err error) {
tlsConfig, err := transport.NewServerTLSConfig(
cfg.TLS.CertFile,
cfg.TLS.KeyFile,
cfg.TLS.TrustedCaFile)
cfg.Transport.TLS.CertFile,
cfg.Transport.TLS.KeyFile,
cfg.Transport.TLS.TrustedCaFile)
if err != nil {
return
}
@ -455,7 +455,7 @@ func (svr *Service) HandleListener(l net.Listener) {
log.Trace("start check TLS connection...")
originConn := c
var isTLS, custom bool
c, isTLS, custom, err = utilnet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TLS.Force, connReadTimeout)
c, isTLS, custom, err = utilnet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.Transport.TLS.Force, connReadTimeout)
if err != nil {
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
originConn.Close()

9
test/e2e/e2e_test.go

@ -10,10 +10,13 @@ import (
"github.com/fatedier/frp/pkg/util/log"
// test source
_ "github.com/fatedier/frp/test/e2e/basic"
_ "github.com/fatedier/frp/test/e2e/features"
"github.com/fatedier/frp/test/e2e/framework"
_ "github.com/fatedier/frp/test/e2e/plugin"
_ "github.com/fatedier/frp/test/e2e/legacy/basic"
_ "github.com/fatedier/frp/test/e2e/legacy/features"
_ "github.com/fatedier/frp/test/e2e/legacy/plugin"
_ "github.com/fatedier/frp/test/e2e/v1/basic"
_ "github.com/fatedier/frp/test/e2e/v1/features"
_ "github.com/fatedier/frp/test/e2e/v1/plugin"
)
// handleFlags sets up all flags and parses the command line.

9
test/e2e/examples.go

@ -19,10 +19,11 @@ var _ = ginkgo.Describe("[Feature: Example]", func() {
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[tcp]
type = tcp
local_port = {{ .%s }}
remote_port = %d
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, framework.TCPEchoServerPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})

16
test/e2e/framework/consts/consts.go

@ -18,12 +18,23 @@ var (
PortClientAdmin string
DefaultServerConfig = `
bindPort = {{ .%s }}
log.level = "trace"
`
DefaultClientConfig = `
serverAddr = "127.0.0.1"
serverPort = {{ .%s }}
log.level = "trace"
`
LegacyDefaultServerConfig = `
[common]
bind_port = {{ .%s }}
log_level = trace
`
DefaultClientConfig = `
LegacyDefaultClientConfig = `
[common]
server_addr = 127.0.0.1
server_port = {{ .%s }}
@ -34,6 +45,9 @@ var (
func init() {
PortServerName = port.GenName("Server")
PortClientAdmin = port.GenName("ClientAdmin")
LegacyDefaultServerConfig = fmt.Sprintf(LegacyDefaultServerConfig, port.GenName("Server"))
LegacyDefaultClientConfig = fmt.Sprintf(LegacyDefaultClientConfig, port.GenName("Server"))
DefaultServerConfig = fmt.Sprintf(DefaultServerConfig, port.GenName("Server"))
DefaultClientConfig = fmt.Sprintf(DefaultClientConfig, port.GenName("Server"))
}

10
test/e2e/framework/process.go

@ -29,7 +29,10 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
err = os.WriteFile(path, []byte(outs[i]), 0o666)
ExpectNoError(err)
flog.Trace("[%s] %s", path, outs[i])
if TestContext.Debug {
flog.Debug("[%s] %s", path, outs[i])
}
p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs)
f.serverConfPaths = append(f.serverConfPaths, path)
@ -46,7 +49,10 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i))
err = os.WriteFile(path, []byte(outs[index]), 0o666)
ExpectNoError(err)
flog.Trace("[%s] %s", path, outs[index])
if TestContext.Debug {
flog.Debug("[%s] %s", path, outs[index])
}
p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs)
f.clientConfPaths = append(f.clientConfPaths, path)

24
test/e2e/basic/basic.go → test/e2e/legacy/basic/basic.go

@ -25,8 +25,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
for _, t := range types {
proxyType := t
ginkgo.It(fmt.Sprintf("Expose a %s echo server", strings.ToUpper(proxyType)), func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
localPortName := ""
protocol := "tcp"
@ -96,13 +96,13 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
ginkgo.Describe("HTTP", func() {
ginkgo.It("proxy to HTTP server", func() {
serverConf := consts.DefaultServerConfig
serverConf := consts.LegacyDefaultServerConfig
vhostHTTPPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhost_http_port = %d
`, vhostHTTPPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
@ -178,14 +178,14 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
ginkgo.Describe("HTTPS", func() {
ginkgo.It("proxy to HTTPS server", func() {
serverConf := consts.DefaultServerConfig
serverConf := consts.LegacyDefaultServerConfig
vhostHTTPSPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhost_https_port = %d
`, vhostHTTPSPort)
localPort := f.AllocPort()
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
[%s]
@ -281,10 +281,10 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
for _, t := range types {
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"
serverConf := consts.LegacyDefaultServerConfig
clientServerConf := consts.LegacyDefaultClientConfig + "\nuser = user1"
clientVisitorConf := consts.LegacyDefaultClientConfig + "\nuser = user1"
clientUser2VisitorConf := consts.LegacyDefaultClientConfig + "\nuser = user2"
localPortName := ""
protocol := "tcp"
@ -439,8 +439,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
ginkgo.Describe("TCPMUX", func() {
ginkgo.It("Type tcpmux", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
tcpmuxHTTPConnectPortName := port.GenName("TCPMUX")
serverConf += fmt.Sprintf(`

12
test/e2e/basic/client.go → test/e2e/legacy/basic/client.go

@ -18,7 +18,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("Update && Reload API", func() {
serverConf := consts.DefaultServerConfig
serverConf := consts.LegacyDefaultServerConfig
adminPort := f.AllocPort()
@ -26,7 +26,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
p2Port := f.AllocPort()
p3Port := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
clientConf := consts.LegacyDefaultClientConfig + fmt.Sprintf(`
admin_port = %d
[p1]
@ -80,10 +80,10 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
})
ginkgo.It("healthz", func() {
serverConf := consts.DefaultServerConfig
serverConf := consts.LegacyDefaultServerConfig
dashboardPort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
clientConf := consts.LegacyDefaultClientConfig + fmt.Sprintf(`
admin_addr = 0.0.0.0
admin_port = %d
admin_user = admin
@ -103,11 +103,11 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
})
ginkgo.It("stop", func() {
serverConf := consts.DefaultServerConfig
serverConf := consts.LegacyDefaultServerConfig
adminPort := f.AllocPort()
testPort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
clientConf := consts.LegacyDefaultClientConfig + fmt.Sprintf(`
admin_port = %d
[test]

6
test/e2e/basic/client_server.go → test/e2e/legacy/basic/client_server.go

@ -33,8 +33,8 @@ func renderBindPortConfig(protocol string) string {
}
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
if configures.clientPrefix != "" {
clientConf = configures.clientPrefix
}
@ -64,7 +64,7 @@ func runClientServerTest(f *framework.Framework, configures *generalTestConfigur
clientConfs := []string{clientConf}
if configures.client2 != "" {
client2Conf := consts.DefaultClientConfig
client2Conf := consts.LegacyDefaultClientConfig
if configures.client2Prefix != "" {
client2Conf = configures.client2Prefix
}

0
test/e2e/basic/cmd.go → test/e2e/legacy/basic/cmd.go

4
test/e2e/basic/config.go → test/e2e/legacy/basic/config.go

@ -15,8 +15,8 @@ var _ = ginkgo.Describe("[Feature: Config]", func() {
ginkgo.Describe("Template", func() {
ginkgo.It("render by env", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
portName := port.GenName("TCP")
serverConf += fmt.Sprintf(`

18
test/e2e/basic/http.go → test/e2e/legacy/basic/http.go

@ -19,7 +19,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
f := framework.NewDefaultFramework()
getDefaultServerConf := func(vhostHTTPPort int) string {
conf := consts.DefaultServerConfig + `
conf := consts.LegacyDefaultServerConfig + `
vhost_http_port = %d
`
return fmt.Sprintf(conf, vhostHTTPPort)
@ -41,7 +41,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
barPort := f.AllocPort()
f.RunServer("", newHTTPServer(barPort, "bar"))
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[foo]
type = http
@ -91,7 +91,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
otherPort := f.AllocPort()
f.RunServer("", newHTTPServer(otherPort, "other"))
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[foo]
type = http
@ -142,7 +142,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
@ -180,7 +180,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
@ -225,7 +225,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
barPort := f.AllocPort()
f.RunServer("", newHTTPServer(barPort, "bar"))
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[foo]
type = http
@ -270,7 +270,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
@ -303,7 +303,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
@ -352,7 +352,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http

16
test/e2e/basic/server.go → test/e2e/legacy/basic/server.go

@ -18,8 +18,8 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("Ports Whitelist", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
serverConf += `
allow_ports = 20000-25000,25002,30000-50000
@ -81,8 +81,8 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
})
ginkgo.It("Alloc Random Port", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
adminPort := f.AllocPort()
clientConf += fmt.Sprintf(`
@ -125,13 +125,13 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
})
ginkgo.It("Port Reuse", func() {
serverConf := consts.DefaultServerConfig
serverConf := consts.LegacyDefaultServerConfig
// Use same port as PortServer
serverConf += fmt.Sprintf(`
vhost_http_port = {{ .%s }}
`, consts.PortServerName)
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
clientConf := consts.LegacyDefaultClientConfig + fmt.Sprintf(`
[http]
type = http
local_port = {{ .%s }}
@ -146,7 +146,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
})
ginkgo.It("healthz", func() {
serverConf := consts.DefaultServerConfig
serverConf := consts.LegacyDefaultServerConfig
dashboardPort := f.AllocPort()
// Use same port as PortServer
@ -158,7 +158,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
dashboard_pwd = admin
`, consts.PortServerName, dashboardPort)
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
clientConf := consts.LegacyDefaultClientConfig + fmt.Sprintf(`
[http]
type = http
local_port = {{ .%s }}

8
test/e2e/basic/tcpmux.go → test/e2e/legacy/basic/tcpmux.go

@ -20,7 +20,7 @@ var _ = ginkgo.Describe("[Feature: TCPMUX httpconnect]", func() {
f := framework.NewDefaultFramework()
getDefaultServerConf := func(httpconnectPort int) string {
conf := consts.DefaultServerConfig + `
conf := consts.LegacyDefaultServerConfig + `
tcpmux_httpconnect_port = %d
`
return fmt.Sprintf(conf, httpconnectPort)
@ -53,7 +53,7 @@ var _ = ginkgo.Describe("[Feature: TCPMUX httpconnect]", func() {
otherPort := f.AllocPort()
f.RunServer("", newServer(otherPort, "other"))
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[foo]
type = tcpmux
@ -110,7 +110,7 @@ var _ = ginkgo.Describe("[Feature: TCPMUX httpconnect]", func() {
fooPort := f.AllocPort()
f.RunServer("", newServer(fooPort, "foo"))
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = tcpmux
@ -195,7 +195,7 @@ var _ = ginkgo.Describe("[Feature: TCPMUX httpconnect]", func() {
localPort := f.AllocPort()
f.RunServer("", newServer(localPort))
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = tcpmux

4
test/e2e/basic/xtcp.go → test/e2e/legacy/basic/xtcp.go

@ -16,8 +16,8 @@ var _ = ginkgo.Describe("[Feature: XTCP]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("Fallback To STCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
bindPortName := port.GenName("XTCP")
clientConf += fmt.Sprintf(`

10
test/e2e/features/bandwidth_limit.go → test/e2e/legacy/features/bandwidth_limit.go

@ -10,17 +10,17 @@ import (
plugin "github.com/fatedier/frp/pkg/plugin/server"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
plugintest "github.com/fatedier/frp/test/e2e/legacy/plugin"
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
"github.com/fatedier/frp/test/e2e/pkg/request"
plugintest "github.com/fatedier/frp/test/e2e/plugin"
)
var _ = ginkgo.Describe("[Feature: Bandwidth Limit]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("Proxy Bandwidth Limit by Client", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
localPort := f.AllocPort()
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort))
@ -69,13 +69,13 @@ var _ = ginkgo.Describe("[Feature: Bandwidth Limit]", func() {
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
[plugin.test]
addr = 127.0.0.1:%d
path = /handler
ops = NewProxy
`, pluginPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
localPort := f.AllocPort()
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort))

0
test/e2e/features/chaos.go → test/e2e/legacy/features/chaos.go

12
test/e2e/features/group.go → test/e2e/legacy/features/group.go

@ -62,8 +62,8 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
ginkgo.Describe("Load Balancing", func() {
ginkgo.It("TCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
fooPort := f.AllocPort()
fooServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(fooPort), streamserver.WithRespContent([]byte("foo")))
@ -114,8 +114,8 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
ginkgo.Describe("Health Check", func() {
ginkgo.It("TCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
fooPort := f.AllocPort()
fooServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(fooPort), streamserver.WithRespContent([]byte("foo")))
@ -180,10 +180,10 @@ var _ = ginkgo.Describe("[Feature: Group]", func() {
ginkgo.It("HTTP", func() {
vhostPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
vhost_http_port = %d
`, vhostPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
fooPort := f.AllocPort()
fooServer := newHTTPServer(fooPort, "foo")

0
test/e2e/features/heartbeat.go → test/e2e/legacy/features/heartbeat.go

4
test/e2e/features/monitor.go → test/e2e/legacy/features/monitor.go

@ -18,13 +18,13 @@ var _ = ginkgo.Describe("[Feature: Monitor]", func() {
ginkgo.It("Prometheus metrics", func() {
dashboardPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
enable_prometheus = true
dashboard_addr = 0.0.0.0
dashboard_port = %d
`, dashboardPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[tcp]

12
test/e2e/features/real_ip.go → test/e2e/legacy/features/real_ip.go

@ -23,7 +23,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
ginkgo.It("HTTP X-Forwarded-For", func() {
vhostHTTPPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
vhost_http_port = %d
`, vhostHTTPPort)
@ -36,7 +36,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
@ -56,8 +56,8 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
ginkgo.Describe("Proxy Protocol", func() {
ginkgo.It("TCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
localPort := f.AllocPort()
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort),
@ -107,11 +107,11 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
ginkgo.It("HTTP", func() {
vhostHTTPPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
vhost_http_port = %d
`, vhostHTTPPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
localPort := f.AllocPort()
var srcAddrRecord string

28
test/e2e/plugin/client.go → test/e2e/legacy/plugin/client.go

@ -21,8 +21,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
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
getProxyConf := func(proxyName string, portName string, extra string) string {
return fmt.Sprintf(`
@ -77,8 +77,8 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
})
ginkgo.It("http_proxy", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
@ -109,8 +109,8 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
})
ginkgo.It("socks5 proxy", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf := consts.LegacyDefaultServerConfig
clientConf := consts.LegacyDefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
@ -137,10 +137,10 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
ginkgo.It("static_file", func() {
vhostPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
vhost_http_port = %d
`, vhostPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
remotePort := f.AllocPort()
f.WriteTempFile("test_static_file", "foo")
@ -185,14 +185,14 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
})
ginkgo.It("http2https", func() {
serverConf := consts.DefaultServerConfig
serverConf := consts.LegacyDefaultServerConfig
vhostHTTPPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhost_http_port = %d
`, vhostHTTPPort)
localPort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
clientConf := consts.LegacyDefaultClientConfig + fmt.Sprintf(`
[http2https]
type = http
custom_domains = example.com
@ -227,14 +227,14 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
crtPath := f.WriteTempFile("server.crt", string(artifacts.Cert))
keyPath := f.WriteTempFile("server.key", string(artifacts.Key))
serverConf := consts.DefaultServerConfig
serverConf := consts.LegacyDefaultServerConfig
vhostHTTPSPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhost_https_port = %d
`, vhostHTTPSPort)
localPort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
clientConf := consts.LegacyDefaultClientConfig + fmt.Sprintf(`
[https2http]
type = https
custom_domains = example.com
@ -271,14 +271,14 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
crtPath := f.WriteTempFile("server.crt", string(artifacts.Cert))
keyPath := f.WriteTempFile("server.key", string(artifacts.Key))
serverConf := consts.DefaultServerConfig
serverConf := consts.LegacyDefaultServerConfig
vhostHTTPSPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhost_https_port = %d
`, vhostHTTPSPort)
localPort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
clientConf := consts.LegacyDefaultClientConfig + fmt.Sprintf(`
[https2https]
type = https
custom_domains = example.com

34
test/e2e/plugin/server.go → test/e2e/legacy/plugin/server.go

@ -44,13 +44,13 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
[plugin.user-manager]
addr = 127.0.0.1:%d
path = /handler
ops = Login
`, localPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
@ -63,7 +63,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
`, framework.TCPEchoServerPort, remotePort)
remotePort2 := f.AllocPort()
invalidTokenClientConf := consts.DefaultClientConfig + fmt.Sprintf(`
invalidTokenClientConf := consts.LegacyDefaultClientConfig + fmt.Sprintf(`
[tcp2]
type = tcp
local_port = {{ .%s }}
@ -102,13 +102,13 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
[plugin.test]
addr = 127.0.0.1:%d
path = /handler
ops = NewProxy
`, localPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
@ -137,13 +137,13 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
[plugin.test]
addr = 127.0.0.1:%d
path = /handler
ops = NewProxy
`, localPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[tcp]
@ -178,13 +178,13 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
[plugin.test]
addr = 127.0.0.1:%d
path = /handler
ops = CloseProxy
`, localPort)
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
@ -230,7 +230,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
[plugin.test]
addr = 127.0.0.1:%d
path = /handler
@ -238,7 +238,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
`, localPort)
remotePort := f.AllocPort()
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
heartbeat_interval = 1
authenticate_heartbeats = true
@ -280,7 +280,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
[plugin.test]
addr = 127.0.0.1:%d
path = /handler
@ -288,7 +288,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
`, localPort)
remotePort := f.AllocPort()
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[tcp]
type = tcp
@ -325,7 +325,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
[plugin.test]
addr = 127.0.0.1:%d
path = /handler
@ -333,7 +333,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
`, localPort)
remotePort := f.AllocPort()
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[tcp]
type = tcp
@ -372,7 +372,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
serverConf := consts.LegacyDefaultServerConfig + fmt.Sprintf(`
[plugin.test]
addr = https://127.0.0.1:%d
path = /handler
@ -380,7 +380,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
`, localPort)
remotePort := f.AllocPort()
clientConf := consts.DefaultClientConfig
clientConf := consts.LegacyDefaultClientConfig
clientConf += fmt.Sprintf(`
[tcp]
type = tcp

0
test/e2e/plugin/utils.go → test/e2e/legacy/plugin/utils.go

524
test/e2e/v1/basic/basic.go

@ -0,0 +1,524 @@
package basic
import (
"crypto/tls"
"fmt"
"strings"
"time"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
"github.com/fatedier/frp/test/e2e/pkg/port"
"github.com/fatedier/frp/test/e2e/pkg/request"
)
var _ = ginkgo.Describe("[Feature: Basic]", func() {
f := framework.NewDefaultFramework()
ginkgo.Describe("TCP && UDP", func() {
types := []string{"tcp", "udp"}
for _, t := range types {
proxyType := t
ginkgo.It(fmt.Sprintf("Expose a %s echo server", strings.ToUpper(proxyType)), func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
localPortName := ""
protocol := "tcp"
switch proxyType {
case "tcp":
localPortName = framework.TCPEchoServerPort
protocol = "tcp"
case "udp":
localPortName = framework.UDPEchoServerPort
protocol = "udp"
}
getProxyConf := func(proxyName string, portName string, extra string) string {
return fmt.Sprintf(`
[[proxies]]
name = "%s"
type = "%s"
localPort = {{ .%s }}
remotePort = {{ .%s }}
`+extra, proxyName, proxyType, localPortName, portName)
}
tests := []struct {
proxyName string
portName string
extraConfig string
}{
{
proxyName: "normal",
portName: port.GenName("Normal"),
},
{
proxyName: "with-encryption",
portName: port.GenName("WithEncryption"),
extraConfig: "transport.useEncryption = true",
},
{
proxyName: "with-compression",
portName: port.GenName("WithCompression"),
extraConfig: "transport.useCompression = true",
},
{
proxyName: "with-encryption-and-compression",
portName: port.GenName("WithEncryptionAndCompression"),
extraConfig: `
transport.useEncryption = true
transport.useCompression = true
`,
},
}
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
for _, test := range tests {
framework.NewRequestExpect(f).
Protocol(protocol).
PortName(test.portName).
Explain(test.proxyName).
Ensure()
}
})
}
})
ginkgo.Describe("HTTP", func() {
ginkgo.It("proxy to HTTP server", func() {
serverConf := consts.DefaultServerConfig
vhostHTTPPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhostHTTPPort = %d
`, vhostHTTPPort)
clientConf := consts.DefaultClientConfig
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
[[proxies]]
name = "%s"
type = "http"
localPort = {{ .%s }}
customDomains = %s
`+extra, proxyName, framework.HTTPSimpleServerPort, customDomains)
}
tests := []struct {
proxyName string
customDomains string
extraConfig string
}{
{
proxyName: "normal",
},
{
proxyName: "with-encryption",
extraConfig: "transport.useEncryption = true",
},
{
proxyName: "with-compression",
extraConfig: "transport.useCompression = true",
},
{
proxyName: "with-encryption-and-compression",
extraConfig: `
transport.useEncryption = true
transport.useCompression = true
`,
},
{
proxyName: "multiple-custom-domains",
customDomains: `["a.example.com", "b.example.com"]`,
},
}
// build all client config
for i, test := range tests {
if tests[i].customDomains == "" {
tests[i].customDomains = fmt.Sprintf(`["%s"]`, test.proxyName+".example.com")
}
clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
for _, test := range tests {
for _, domain := range strings.Split(test.customDomains, ",") {
domain = strings.TrimSpace(domain)
domain = strings.TrimLeft(domain, "[\"")
domain = strings.TrimRight(domain, "]\"")
framework.NewRequestExpect(f).
Explain(test.proxyName + "-" + domain).
Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost(domain)
}).
Ensure()
}
}
// not exist host
framework.NewRequestExpect(f).
Explain("not exist host").
Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("not-exist.example.com")
}).
Ensure(framework.ExpectResponseCode(404))
})
})
ginkgo.Describe("HTTPS", func() {
ginkgo.It("proxy to HTTPS server", func() {
serverConf := consts.DefaultServerConfig
vhostHTTPSPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhostHTTPSPort = %d
`, vhostHTTPSPort)
localPort := f.AllocPort()
clientConf := consts.DefaultClientConfig
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
[[proxies]]
name = "%s"
type = "https"
localPort = %d
customDomains = %s
`+extra, proxyName, localPort, customDomains)
}
tests := []struct {
proxyName string
customDomains string
extraConfig string
}{
{
proxyName: "normal",
},
{
proxyName: "with-encryption",
extraConfig: "transport.useEncryption = true",
},
{
proxyName: "with-compression",
extraConfig: "transport.useCompression = true",
},
{
proxyName: "with-encryption-and-compression",
extraConfig: `
transport.useEncryption = true
transport.useCompression = true
`,
},
{
proxyName: "multiple-custom-domains",
customDomains: `["a.example.com", "b.example.com"]`,
},
}
// build all client config
for i, test := range tests {
if tests[i].customDomains == "" {
tests[i].customDomains = fmt.Sprintf(`["%s"]`, test.proxyName+".example.com")
}
clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
framework.ExpectNoError(err)
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithTLSConfig(tlsConfig),
httpserver.WithResponse([]byte("test")),
)
f.RunServer("", localServer)
for _, test := range tests {
for _, domain := range strings.Split(test.customDomains, ",") {
domain = strings.TrimSpace(domain)
domain = strings.TrimLeft(domain, "[\"")
domain = strings.TrimRight(domain, "]\"")
framework.NewRequestExpect(f).
Explain(test.proxyName + "-" + domain).
Port(vhostHTTPSPort).
RequestModify(func(r *request.Request) {
r.HTTPS().HTTPHost(domain).TLSConfig(&tls.Config{
ServerName: domain,
InsecureSkipVerify: true,
})
}).
ExpectResp([]byte("test")).
Ensure()
}
}
// not exist host
notExistDomain := "not-exist.example.com"
framework.NewRequestExpect(f).
Explain("not exist host").
Port(vhostHTTPSPort).
RequestModify(func(r *request.Request) {
r.HTTPS().HTTPHost(notExistDomain).TLSConfig(&tls.Config{
ServerName: notExistDomain,
InsecureSkipVerify: true,
})
}).
ExpectError(true).
Ensure()
})
})
ginkgo.Describe("STCP && SUDP && XTCP", func() {
types := []string{"stcp", "sudp", "xtcp"}
for _, t := range types {
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\""
localPortName := ""
protocol := "tcp"
switch proxyType {
case "stcp":
localPortName = framework.TCPEchoServerPort
protocol = "tcp"
case "sudp":
localPortName = framework.UDPEchoServerPort
protocol = "udp"
case "xtcp":
localPortName = framework.TCPEchoServerPort
protocol = "tcp"
ginkgo.Skip("stun server is not stable")
}
correctSK := "abc"
wrongSK := "123"
getProxyServerConf := func(proxyName string, extra string) string {
return fmt.Sprintf(`
[[proxies]]
name = "%s"
type = "%s"
secretKey = "%s"
localPort = {{ .%s }}
`+extra, proxyName, proxyType, correctSK, localPortName)
}
getProxyVisitorConf := func(proxyName string, portName, visitorSK, extra string) string {
return fmt.Sprintf(`
[[visitors]]
name = "%s"
type = "%s"
serverName = "%s"
secretKey = "%s"
bindPort = {{ .%s }}
`+extra, proxyName, proxyType, proxyName, visitorSK, portName)
}
tests := []struct {
proxyName string
bindPortName string
visitorSK string
commonExtraConfig string
proxyExtraConfig string
visitorExtraConfig string
expectError bool
deployUser2Client bool
// skipXTCP is used to skip xtcp test case
skipXTCP bool
}{
{
proxyName: "normal",
bindPortName: port.GenName("Normal"),
visitorSK: correctSK,
skipXTCP: true,
},
{
proxyName: "with-encryption",
bindPortName: port.GenName("WithEncryption"),
visitorSK: correctSK,
commonExtraConfig: "transport.useEncryption = true",
skipXTCP: true,
},
{
proxyName: "with-compression",
bindPortName: port.GenName("WithCompression"),
visitorSK: correctSK,
commonExtraConfig: "transport.useCompression = true",
skipXTCP: true,
},
{
proxyName: "with-encryption-and-compression",
bindPortName: port.GenName("WithEncryptionAndCompression"),
visitorSK: correctSK,
commonExtraConfig: `
transport.useEncryption = true
transport.useCompression = true
`,
skipXTCP: true,
},
{
proxyName: "with-error-sk",
bindPortName: port.GenName("WithErrorSK"),
visitorSK: wrongSK,
expectError: true,
},
{
proxyName: "allowed-user",
bindPortName: port.GenName("AllowedUser"),
visitorSK: correctSK,
proxyExtraConfig: `allowUsers = ["another", "user2"]`,
visitorExtraConfig: `serverUser = "user1"`,
deployUser2Client: true,
},
{
proxyName: "not-allowed-user",
bindPortName: port.GenName("NotAllowedUser"),
visitorSK: correctSK,
proxyExtraConfig: `allowUsers = ["invalid"]`,
visitorExtraConfig: `serverUser = "user1"`,
expectError: true,
},
{
proxyName: "allow-all",
bindPortName: port.GenName("AllowAll"),
visitorSK: correctSK,
proxyExtraConfig: `allowUsers = ["*"]`,
visitorExtraConfig: `serverUser = "user1"`,
deployUser2Client: true,
},
}
// build all client config
for _, test := range tests {
clientServerConf += 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
} else {
clientVisitorConf += config
}
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf, clientUser2VisitorConf})
for _, test := range tests {
timeout := time.Second
if t == "xtcp" {
if test.skipXTCP {
continue
}
timeout = 10 * time.Second
}
framework.NewRequestExpect(f).
RequestModify(func(r *request.Request) {
r.Timeout(timeout)
}).
Protocol(protocol).
PortName(test.bindPortName).
Explain(test.proxyName).
ExpectError(test.expectError).
Ensure()
}
})
}
})
ginkgo.Describe("TCPMUX", func() {
ginkgo.It("Type tcpmux", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
tcpmuxHTTPConnectPortName := port.GenName("TCPMUX")
serverConf += fmt.Sprintf(`
tcpmuxHTTPConnectPort = {{ .%s }}
`, tcpmuxHTTPConnectPortName)
getProxyConf := func(proxyName string, extra string) string {
return fmt.Sprintf(`
[[proxies]]
name = "%s"
type = "tcpmux"
multiplexer = "httpconnect"
localPort = {{ .%s }}
customDomains = ["%s"]
`+extra, proxyName, port.GenName(proxyName), proxyName)
}
tests := []struct {
proxyName string
extraConfig string
}{
{
proxyName: "normal",
},
{
proxyName: "with-encryption",
extraConfig: "transport.useEncryption = true",
},
{
proxyName: "with-compression",
extraConfig: "transport.useCompression = true",
},
{
proxyName: "with-encryption-and-compression",
extraConfig: `
transport.useEncryption = true
transport.useCompression = true
`,
},
}
// build all client config
for _, test := range tests {
clientConf += 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})
// Request without HTTP connect should get error
framework.NewRequestExpect(f).
PortName(tcpmuxHTTPConnectPortName).
ExpectError(true).
Explain("request without HTTP connect expect error").
Ensure()
proxyURL := fmt.Sprintf("http://127.0.0.1:%d", f.PortByName(tcpmuxHTTPConnectPortName))
// Request with incorrect connect hostname
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.Addr("invalid").Proxy(proxyURL)
}).ExpectError(true).Explain("request without HTTP connect expect error").Ensure()
// Request with correct connect hostname
for _, test := range tests {
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.Addr(test.proxyName).Proxy(proxyURL)
}).ExpectResp([]byte(test.proxyName)).Explain(test.proxyName).Ensure()
}
})
})
})

136
test/e2e/v1/basic/client.go

@ -0,0 +1,136 @@
package basic
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/request"
clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client"
)
var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("Update && Reload API", func() {
serverConf := consts.DefaultServerConfig
adminPort := f.AllocPort()
p1Port := f.AllocPort()
p2Port := f.AllocPort()
p3Port := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
webServer.port = %d
[[proxies]]
name = "p1"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
[[proxies]]
name = "p2"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
[[proxies]]
name = "p3"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, adminPort,
framework.TCPEchoServerPort, p1Port,
framework.TCPEchoServerPort, p2Port,
framework.TCPEchoServerPort, p3Port)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(p1Port).Ensure()
framework.NewRequestExpect(f).Port(p2Port).Ensure()
framework.NewRequestExpect(f).Port(p3Port).Ensure()
client := clientsdk.New("127.0.0.1", adminPort)
conf, err := client.GetConfig()
framework.ExpectNoError(err)
newP2Port := f.AllocPort()
// change p2 port and remove p3 proxy
newClientConf := strings.ReplaceAll(conf, strconv.Itoa(p2Port), strconv.Itoa(newP2Port))
p3Index := strings.LastIndex(newClientConf, "[[proxies]]")
if p3Index >= 0 {
newClientConf = newClientConf[:p3Index]
}
err = client.UpdateConfig(newClientConf)
framework.ExpectNoError(err)
err = client.Reload()
framework.ExpectNoError(err)
time.Sleep(time.Second)
framework.NewRequestExpect(f).Port(p1Port).Explain("p1 port").Ensure()
framework.NewRequestExpect(f).Port(p2Port).Explain("original p2 port").ExpectError(true).Ensure()
framework.NewRequestExpect(f).Port(newP2Port).Explain("new p2 port").Ensure()
framework.NewRequestExpect(f).Port(p3Port).Explain("p3 port").ExpectError(true).Ensure()
})
ginkgo.It("healthz", func() {
serverConf := consts.DefaultServerConfig
dashboardPort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
webServer.addr = "0.0.0.0"
webServer.port = %d
webServer.user = "admin"
webServer.password = "admin"
`, dashboardPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.HTTP().HTTPPath("/healthz")
}).Port(dashboardPort).ExpectResp([]byte("")).Ensure()
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.HTTP().HTTPPath("/")
}).Port(dashboardPort).
Ensure(framework.ExpectResponseCode(401))
})
ginkgo.It("stop", func() {
serverConf := consts.DefaultServerConfig
adminPort := f.AllocPort()
testPort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
webServer.port = %d
[[proxies]]
name = "test"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, adminPort, framework.TCPEchoServerPort, testPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(testPort).Ensure()
client := clientsdk.New("127.0.0.1", adminPort)
err := client.Stop()
framework.ExpectNoError(err)
time.Sleep(3 * time.Second)
// frpc stopped so the port is not listened, expect error
framework.NewRequestExpect(f).Port(testPort).ExpectError(true).Ensure()
})
})

325
test/e2e/v1/basic/client_server.go

@ -0,0 +1,325 @@
package basic
import (
"fmt"
"strings"
"time"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/cert"
"github.com/fatedier/frp/test/e2e/pkg/port"
)
type generalTestConfigures struct {
server string
client string
clientPrefix string
client2 string
client2Prefix string
testDelay time.Duration
expectError bool
}
func renderBindPortConfig(protocol string) string {
if protocol == "kcp" {
return fmt.Sprintf(`kcpBindPort = {{ .%s }}`, consts.PortServerName)
} else if protocol == "quic" {
return fmt.Sprintf(`quicBindPort = {{ .%s }}`, consts.PortServerName)
}
return ""
}
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
if configures.clientPrefix != "" {
clientConf = configures.clientPrefix
}
serverConf += fmt.Sprintf(`
%s
`, configures.server)
tcpPortName := port.GenName("TCP")
udpPortName := port.GenName("UDP")
clientConf += fmt.Sprintf(`
%s
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = {{ .%s }}
[[proxies]]
name = "udp"
type = "udp"
localPort = {{ .%s }}
remotePort = {{ .%s }}
`, configures.client,
framework.TCPEchoServerPort, tcpPortName,
framework.UDPEchoServerPort, udpPortName,
)
clientConfs := []string{clientConf}
if configures.client2 != "" {
client2Conf := consts.DefaultClientConfig
if configures.client2Prefix != "" {
client2Conf = configures.client2Prefix
}
client2Conf += fmt.Sprintf(`
%s
`, configures.client2)
clientConfs = append(clientConfs, client2Conf)
}
f.RunProcesses([]string{serverConf}, clientConfs)
if configures.testDelay > 0 {
time.Sleep(configures.testDelay)
}
framework.NewRequestExpect(f).PortName(tcpPortName).ExpectError(configures.expectError).Explain("tcp proxy").Ensure()
framework.NewRequestExpect(f).Protocol("udp").
PortName(udpPortName).ExpectError(configures.expectError).Explain("udp proxy").Ensure()
}
// defineClientServerTest test a normal tcp and udp proxy with specified TestConfigures.
func defineClientServerTest(desc string, f *framework.Framework, configures *generalTestConfigures) {
ginkgo.It(desc, func() {
runClientServerTest(f, configures)
})
}
var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
f := framework.NewDefaultFramework()
ginkgo.Describe("Protocol", func() {
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
for _, protocol := range supportProtocols {
configures := &generalTestConfigures{
server: fmt.Sprintf(`
%s
`, renderBindPortConfig(protocol)),
client: fmt.Sprintf(`transport.protocol = "%s"`, protocol),
}
defineClientServerTest(protocol, f, configures)
}
})
// wss is special, it needs to be tested separately.
// frps only supports ws, so there should be a proxy to terminate TLS before frps.
ginkgo.Describe("Protocol wss", func() {
wssPort := f.AllocPort()
configures := &generalTestConfigures{
clientPrefix: fmt.Sprintf(`
serverAddr = "127.0.0.1"
serverPort = %d
loginFailExit = false
transport.protocol = "wss"
log.level = "trace"
`, wssPort),
// Due to the fact that frps cannot directly accept wss connections, we use the https2http plugin of another frpc to terminate TLS.
client2: fmt.Sprintf(`
[[proxies]]
name = "wss2ws"
type = "tcp"
remotePort = %d
[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:{{ .%s }}"
`, wssPort, consts.PortServerName),
testDelay: 10 * time.Second,
}
defineClientServerTest("wss", f, configures)
})
ginkgo.Describe("Authentication", func() {
defineClientServerTest("Token Correct", f, &generalTestConfigures{
server: `auth.token = "123456"`,
client: `auth.token = "123456"`,
})
defineClientServerTest("Token Incorrect", f, &generalTestConfigures{
server: `auth.token = "123456"`,
client: `auth.token = "invalid"`,
expectError: true,
})
})
ginkgo.Describe("TLS", func() {
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
for _, protocol := range supportProtocols {
tmp := protocol
// Since v0.50.0, the default value of tls_enable has been changed to true.
// Therefore, here it needs to be set as false to test the scenario of turning it off.
defineClientServerTest("Disable TLS over "+strings.ToUpper(tmp), f, &generalTestConfigures{
server: fmt.Sprintf(`
%s
`, renderBindPortConfig(protocol)),
client: fmt.Sprintf(`transport.tls.enable = false
transport.protocol = "%s"
`, protocol),
})
}
defineClientServerTest("enable tls force, client with TLS", f, &generalTestConfigures{
server: "transport.tls.force = true",
})
defineClientServerTest("enable tls force, client without TLS", f, &generalTestConfigures{
server: "transport.tls.force = true",
client: "transport.tls.enable = false",
expectError: true,
})
})
ginkgo.Describe("TLS with custom certificate", func() {
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
var (
caCrtPath string
serverCrtPath, serverKeyPath string
clientCrtPath, clientKeyPath string
)
ginkgo.JustBeforeEach(func() {
generator := &cert.SelfSignedCertGenerator{}
artifacts, err := generator.Generate("127.0.0.1")
framework.ExpectNoError(err)
caCrtPath = f.WriteTempFile("ca.crt", string(artifacts.CACert))
serverCrtPath = f.WriteTempFile("server.crt", string(artifacts.Cert))
serverKeyPath = f.WriteTempFile("server.key", string(artifacts.Key))
generator.SetCA(artifacts.CACert, artifacts.CAKey)
_, err = generator.Generate("127.0.0.1")
framework.ExpectNoError(err)
clientCrtPath = f.WriteTempFile("client.crt", string(artifacts.Cert))
clientKeyPath = f.WriteTempFile("client.key", string(artifacts.Key))
})
for _, protocol := range supportProtocols {
tmp := protocol
ginkgo.It("one-way authentication: "+tmp, func() {
runClientServerTest(f, &generalTestConfigures{
server: fmt.Sprintf(`
%s
transport.tls.trustedCaFile = "%s"
`, renderBindPortConfig(tmp), caCrtPath),
client: fmt.Sprintf(`
transport.protocol = "%s"
transport.tls.certFile = "%s"
transport.tls.keyFile = "%s"
`, tmp, clientCrtPath, clientKeyPath),
})
})
ginkgo.It("mutual authentication: "+tmp, func() {
runClientServerTest(f, &generalTestConfigures{
server: fmt.Sprintf(`
%s
transport.tls.certFile = "%s"
transport.tls.keyFile = "%s"
transport.tls.trustedCaFile = "%s"
`, renderBindPortConfig(tmp), serverCrtPath, serverKeyPath, caCrtPath),
client: fmt.Sprintf(`
transport.protocol = "%s"
transport.tls.certFile = "%s"
transport.tls.keyFile = "%s"
transport.tls.trustedCaFile = "%s"
`, tmp, clientCrtPath, clientKeyPath, caCrtPath),
})
})
}
})
ginkgo.Describe("TLS with custom certificate and specified server name", func() {
var (
caCrtPath string
serverCrtPath, serverKeyPath string
clientCrtPath, clientKeyPath string
)
ginkgo.JustBeforeEach(func() {
generator := &cert.SelfSignedCertGenerator{}
artifacts, err := generator.Generate("example.com")
framework.ExpectNoError(err)
caCrtPath = f.WriteTempFile("ca.crt", string(artifacts.CACert))
serverCrtPath = f.WriteTempFile("server.crt", string(artifacts.Cert))
serverKeyPath = f.WriteTempFile("server.key", string(artifacts.Key))
generator.SetCA(artifacts.CACert, artifacts.CAKey)
_, err = generator.Generate("example.com")
framework.ExpectNoError(err)
clientCrtPath = f.WriteTempFile("client.crt", string(artifacts.Cert))
clientKeyPath = f.WriteTempFile("client.key", string(artifacts.Key))
})
ginkgo.It("mutual authentication", func() {
runClientServerTest(f, &generalTestConfigures{
server: fmt.Sprintf(`
transport.tls.certFile = "%s"
transport.tls.keyFile = "%s"
transport.tls.trustedCaFile = "%s"
`, serverCrtPath, serverKeyPath, caCrtPath),
client: fmt.Sprintf(`
transport.tls.serverName = "example.com"
transport.tls.certFile = "%s"
transport.tls.keyFile = "%s"
transport.tls.trustedCaFile = "%s"
`, clientCrtPath, clientKeyPath, caCrtPath),
})
})
ginkgo.It("mutual authentication with incorrect server name", func() {
runClientServerTest(f, &generalTestConfigures{
server: fmt.Sprintf(`
transport.tls.certFile = "%s"
transport.tls.keyFile = "%s"
transport.tls.trustedCaFile = "%s"
`, serverCrtPath, serverKeyPath, caCrtPath),
client: fmt.Sprintf(`
transport.tls.serverName = "invalid.com"
transport.tls.certFile = "%s"
transport.tls.keyFile = "%s"
transport.tls.trustedCaFile = "%s"
`, clientCrtPath, clientKeyPath, caCrtPath),
expectError: true,
})
})
})
ginkgo.Describe("TLS with disable_custom_tls_first_byte set to false", func() {
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
for _, protocol := range supportProtocols {
tmp := protocol
defineClientServerTest("TLS over "+strings.ToUpper(tmp), f, &generalTestConfigures{
server: fmt.Sprintf(`
%s
`, renderBindPortConfig(protocol)),
client: fmt.Sprintf(`
transport.protocol = "%s"
transport.tls.disableCustomTLSFirstByte = false
`, protocol),
})
}
})
ginkgo.Describe("IPv6 bind address", func() {
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
for _, protocol := range supportProtocols {
tmp := protocol
defineClientServerTest("IPv6 bind address: "+strings.ToUpper(tmp), f, &generalTestConfigures{
server: fmt.Sprintf(`
bindAddr = "::"
%s
`, renderBindPortConfig(protocol)),
client: fmt.Sprintf(`
transport.protocol = "%s"
`, protocol),
})
}
})
})

109
test/e2e/v1/basic/cmd.go

@ -0,0 +1,109 @@
package basic
import (
"fmt"
"strconv"
"strings"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/pkg/request"
)
const (
ConfigValidStr = "syntax is ok"
)
var _ = ginkgo.Describe("[Feature: Cmd]", func() {
f := framework.NewDefaultFramework()
ginkgo.Describe("Verify", func() {
ginkgo.It("frps valid", func() {
path := f.GenerateConfigFile(`
bindAddr = "0.0.0.0"
bindPort = 7000
`)
_, output, err := f.RunFrps("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(strings.Contains(output, ConfigValidStr), "output: %s", output)
})
ginkgo.It("frps invalid", func() {
path := f.GenerateConfigFile(`
bindAddr = "0.0.0.0"
bindPort = 70000
`)
_, output, err := f.RunFrps("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(!strings.Contains(output, ConfigValidStr), "output: %s", output)
})
ginkgo.It("frpc valid", func() {
path := f.GenerateConfigFile(`
serverAddr = "0.0.0.0"
serverPort = 7000
`)
_, output, err := f.RunFrpc("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(strings.Contains(output, ConfigValidStr), "output: %s", output)
})
ginkgo.It("frpc invalid", func() {
path := f.GenerateConfigFile(`
serverAddr = "0.0.0.0"
serverPort = 7000
transport.protocol = "invalid"
`)
_, output, err := f.RunFrpc("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(!strings.Contains(output, ConfigValidStr), "output: %s", output)
})
})
ginkgo.Describe("Single proxy", func() {
ginkgo.It("TCP", func() {
serverPort := f.AllocPort()
_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort))
framework.ExpectNoError(err)
localPort := f.PortByName(framework.TCPEchoServerPort)
remotePort := f.AllocPort()
_, _, err = f.RunFrpc("tcp", "-s", fmt.Sprintf("127.0.0.1:%d", serverPort), "-t", "123", "-u", "test",
"-l", strconv.Itoa(localPort), "-r", strconv.Itoa(remotePort), "-n", "tcp_test")
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
})
ginkgo.It("UDP", func() {
serverPort := f.AllocPort()
_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort))
framework.ExpectNoError(err)
localPort := f.PortByName(framework.UDPEchoServerPort)
remotePort := f.AllocPort()
_, _, err = f.RunFrpc("udp", "-s", fmt.Sprintf("127.0.0.1:%d", serverPort), "-t", "123", "-u", "test",
"-l", strconv.Itoa(localPort), "-r", strconv.Itoa(remotePort), "-n", "udp_test")
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Protocol("udp").
Port(remotePort).Ensure()
})
ginkgo.It("HTTP", func() {
serverPort := f.AllocPort()
vhostHTTPPort := f.AllocPort()
_, _, err := f.RunFrps("-t", "123", "-p", strconv.Itoa(serverPort), "--vhost_http_port", strconv.Itoa(vhostHTTPPort))
framework.ExpectNoError(err)
_, _, err = f.RunFrpc("http", "-s", "127.0.0.1:"+strconv.Itoa(serverPort), "-t", "123", "-u", "test",
"-n", "udp_test", "-l", strconv.Itoa(f.PortByName(framework.HTTPSimpleServerPort)),
"--custom_domain", "test.example.com")
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("test.example.com")
}).
Ensure()
})
})
})

84
test/e2e/v1/basic/config.go

@ -0,0 +1,84 @@
package basic
import (
"fmt"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/port"
)
var _ = ginkgo.Describe("[Feature: Config]", func() {
f := framework.NewDefaultFramework()
ginkgo.Describe("Template", func() {
ginkgo.It("render by env", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
portName := port.GenName("TCP")
serverConf += fmt.Sprintf(`
auth.token = "{{ %s{{ .Envs.FRP_TOKEN }}%s }}"
`, "`", "`")
clientConf += fmt.Sprintf(`
auth.token = "{{ %s{{ .Envs.FRP_TOKEN }}%s }}"
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = {{ .%s }}
`, "`", "`", framework.TCPEchoServerPort, portName)
f.SetEnvs([]string{"FRP_TOKEN=123"})
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).PortName(portName).Ensure()
})
})
ginkgo.Describe("Includes", func() {
ginkgo.It("split tcp proxies into different files", func() {
serverPort := f.AllocPort()
serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
bindAddr = "0.0.0.0"
bindPort = %d
`, serverPort))
remotePort := f.AllocPort()
proxyConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = %d
remotePort = %d
`, f.PortByName(framework.TCPEchoServerPort), remotePort))
remotePort2 := f.AllocPort()
proxyConfigPath2 := f.GenerateConfigFile(fmt.Sprintf(`
[[proxies]]
name = "tcp2"
type = "tcp"
localPort = %d
remotePort = %d
`, f.PortByName(framework.TCPEchoServerPort), remotePort2))
clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
serverPort = %d
includes = ["%s","%s"]
`, serverPort, proxyConfigPath, proxyConfigPath2))
_, _, err := f.RunFrps("-c", serverConfigPath)
framework.ExpectNoError(err)
_, _, err = f.RunFrpc("-c", clientConfigPath)
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
framework.NewRequestExpect(f).Port(remotePort2).Ensure()
})
})
})

388
test/e2e/v1/basic/http.go

@ -0,0 +1,388 @@
package basic
import (
"fmt"
"net/http"
"net/url"
"strconv"
"github.com/gorilla/websocket"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
"github.com/fatedier/frp/test/e2e/pkg/request"
)
var _ = ginkgo.Describe("[Feature: HTTP]", func() {
f := framework.NewDefaultFramework()
getDefaultServerConf := func(vhostHTTPPort int) string {
conf := consts.DefaultServerConfig + `
vhostHTTPPort = %d
`
return fmt.Sprintf(conf, vhostHTTPPort)
}
newHTTPServer := func(port int, respContent string) *httpserver.Server {
return httpserver.New(
httpserver.WithBindPort(port),
httpserver.WithHandler(framework.SpecifiedHTTPBodyHandler([]byte(respContent))),
)
}
ginkgo.It("HTTP route by locations", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
fooPort := f.AllocPort()
f.RunServer("", newHTTPServer(fooPort, "foo"))
barPort := f.AllocPort()
f.RunServer("", newHTTPServer(barPort, "bar"))
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "foo"
type = "http"
localPort = %d
customDomains = ["normal.example.com"]
locations = ["/","/foo"]
[[proxies]]
name = "bar"
type = "http"
localPort = %d
customDomains = ["normal.example.com"]
locations = ["/bar"]
`, fooPort, barPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
tests := []struct {
path string
expectResp string
desc string
}{
{path: "/foo", expectResp: "foo", desc: "foo path"},
{path: "/bar", expectResp: "bar", desc: "bar path"},
{path: "/other", expectResp: "foo", desc: "other path"},
}
for _, test := range tests {
framework.NewRequestExpect(f).Explain(test.desc).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPPath(test.path)
}).
ExpectResp([]byte(test.expectResp)).
Ensure()
}
})
ginkgo.It("HTTP route by HTTP user", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
fooPort := f.AllocPort()
f.RunServer("", newHTTPServer(fooPort, "foo"))
barPort := f.AllocPort()
f.RunServer("", newHTTPServer(barPort, "bar"))
otherPort := f.AllocPort()
f.RunServer("", newHTTPServer(otherPort, "other"))
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "foo"
type = "http"
localPort = %d
customDomains = ["normal.example.com"]
routeByHTTPUser = "user1"
[[proxies]]
name = "bar"
type = "http"
localPort = %d
customDomains = ["normal.example.com"]
routeByHTTPUser = "user2"
[[proxies]]
name = "catchAll"
type = "http"
localPort = %d
customDomains = ["normal.example.com"]
`, fooPort, barPort, otherPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// user1
framework.NewRequestExpect(f).Explain("user1").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPAuth("user1", "")
}).
ExpectResp([]byte("foo")).
Ensure()
// user2
framework.NewRequestExpect(f).Explain("user2").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPAuth("user2", "")
}).
ExpectResp([]byte("bar")).
Ensure()
// other user
framework.NewRequestExpect(f).Explain("other user").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPAuth("user3", "")
}).
ExpectResp([]byte("other")).
Ensure()
})
ginkgo.It("HTTP Basic Auth", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "test"
type = "http"
localPort = {{ .%s }}
customDomains = ["normal.example.com"]
httpUser = "test"
httpPassword = "test"
`, framework.HTTPSimpleServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// not set auth header
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).
Ensure(framework.ExpectResponseCode(401))
// set incorrect auth header
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPAuth("test", "invalid")
}).
Ensure(framework.ExpectResponseCode(401))
// set correct auth header
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPAuth("test", "test")
}).
Ensure()
})
ginkgo.It("Wildcard domain", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "test"
type = "http"
localPort = {{ .%s }}
customDomains = ["*.example.com"]
`, framework.HTTPSimpleServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// not match host
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("not-match.test.com")
}).
Ensure(framework.ExpectResponseCode(404))
// test.example.com match *.example.com
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("test.example.com")
}).
Ensure()
// sub.test.example.com match *.example.com
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("sub.test.example.com")
}).
Ensure()
})
ginkgo.It("Subdomain", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
serverConf += `
subdomainHost = "example.com"
`
fooPort := f.AllocPort()
f.RunServer("", newHTTPServer(fooPort, "foo"))
barPort := f.AllocPort()
f.RunServer("", newHTTPServer(barPort, "bar"))
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "foo"
type = "http"
localPort = %d
subdomain = "foo"
[[proxies]]
name = "bar"
type = "http"
localPort = %d
subdomain = "bar"
`, fooPort, barPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// foo
framework.NewRequestExpect(f).Explain("foo subdomain").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("foo.example.com")
}).
ExpectResp([]byte("foo")).
Ensure()
// bar
framework.NewRequestExpect(f).Explain("bar subdomain").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("bar.example.com")
}).
ExpectResp([]byte("bar")).
Ensure()
})
ginkgo.It("Modify headers", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
localPort := f.AllocPort()
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
_, _ = w.Write([]byte(req.Header.Get("X-From-Where")))
})),
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "test"
type = "http"
localPort = %d
customDomains = ["normal.example.com"]
requestHeaders.set.x-from-where = "frp"
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// not set auth header
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).
ExpectResp([]byte("frp")). // local http server will write this X-From-Where header to response body
Ensure()
})
ginkgo.It("Host Header Rewrite", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
localPort := f.AllocPort()
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
_, _ = w.Write([]byte(req.Host))
})),
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "test"
type = "http"
localPort = %d
customDomains = ["normal.example.com"]
hostHeaderRewrite = "rewrite.example.com"
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).
ExpectResp([]byte("rewrite.example.com")). // local http server will write host header to response body
Ensure()
})
ginkgo.It("Websocket protocol", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
upgrader := websocket.Upgrader{}
localPort := f.AllocPort()
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
c, err := upgrader.Upgrade(w, req, nil)
if err != nil {
return
}
defer c.Close()
for {
mt, message, err := c.ReadMessage()
if err != nil {
break
}
err = c.WriteMessage(mt, message)
if err != nil {
break
}
}
})),
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "test"
type = "http"
localPort = %d
customDomains = ["127.0.0.1"]
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
u := url.URL{Scheme: "ws", Host: "127.0.0.1:" + strconv.Itoa(vhostHTTPPort)}
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
framework.ExpectNoError(err)
err = c.WriteMessage(websocket.TextMessage, []byte(consts.TestString))
framework.ExpectNoError(err)
_, msg, err := c.ReadMessage()
framework.ExpectNoError(err)
framework.ExpectEqualValues(consts.TestString, string(msg))
})
})

192
test/e2e/v1/basic/server.go

@ -0,0 +1,192 @@
package basic
import (
"fmt"
"net"
"strconv"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/port"
"github.com/fatedier/frp/test/e2e/pkg/request"
clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client"
)
var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("Ports Whitelist", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf += `
allowPorts = [
{ start = 20000, end = 25000 },
{ single = 25002 },
{ start = 30000, end = 50000 },
]
`
tcpPortName := port.GenName("TCP", port.WithRangePorts(20000, 25000))
udpPortName := port.GenName("UDP", port.WithRangePorts(30000, 50000))
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp-allowded-in-range"
type = "tcp"
localPort = {{ .%s }}
remotePort = {{ .%s }}
`, framework.TCPEchoServerPort, tcpPortName)
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp-port-not-allowed"
type = "tcp"
localPort = {{ .%s }}
remotePort = 25001
`, framework.TCPEchoServerPort)
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp-port-unavailable"
type = "tcp"
localPort = {{ .%s }}
remotePort = {{ .%s }}
`, framework.TCPEchoServerPort, consts.PortServerName)
clientConf += fmt.Sprintf(`
[[proxies]]
name = "udp-allowed-in-range"
type = "udp"
localPort = {{ .%s }}
remotePort = {{ .%s }}
`, framework.UDPEchoServerPort, udpPortName)
clientConf += fmt.Sprintf(`
[[proxies]]
name = "udp-port-not-allowed"
type = "udp"
localPort = {{ .%s }}
remotePort = 25003
`, framework.UDPEchoServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// TCP
// Allowed in range
framework.NewRequestExpect(f).PortName(tcpPortName).Ensure()
// Not Allowed
framework.NewRequestExpect(f).Port(25001).ExpectError(true).Ensure()
// Unavailable, already bind by frps
framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure()
// UDP
// Allowed in range
framework.NewRequestExpect(f).Protocol("udp").PortName(udpPortName).Ensure()
// Not Allowed
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.UDP().Port(25003)
}).ExpectError(true).Ensure()
})
ginkgo.It("Alloc Random Port", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
adminPort := f.AllocPort()
clientConf += fmt.Sprintf(`
webServer.port = %d
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
[[proxies]]
name = "udp"
type = "udp"
localPort = {{ .%s }}
`, adminPort, framework.TCPEchoServerPort, framework.UDPEchoServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
client := clientsdk.New("127.0.0.1", adminPort)
// tcp random port
status, err := client.GetProxyStatus("tcp")
framework.ExpectNoError(err)
_, portStr, err := net.SplitHostPort(status.RemoteAddr)
framework.ExpectNoError(err)
port, err := strconv.Atoi(portStr)
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(port).Ensure()
// udp random port
status, err = client.GetProxyStatus("udp")
framework.ExpectNoError(err)
_, portStr, err = net.SplitHostPort(status.RemoteAddr)
framework.ExpectNoError(err)
port, err = strconv.Atoi(portStr)
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Protocol("udp").Port(port).Ensure()
})
ginkgo.It("Port Reuse", func() {
serverConf := consts.DefaultServerConfig
// Use same port as PortServer
serverConf += fmt.Sprintf(`
vhostHTTPPort = {{ .%s }}
`, consts.PortServerName)
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
[[proxies]]
name = "http"
type = "http"
localPort = {{ .%s }}
customDomains = ["example.com"]
`, framework.HTTPSimpleServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("example.com")
}).PortName(consts.PortServerName).Ensure()
})
ginkgo.It("healthz", func() {
serverConf := consts.DefaultServerConfig
dashboardPort := f.AllocPort()
// Use same port as PortServer
serverConf += fmt.Sprintf(`
vhostHTTPPort = {{ .%s }}
webServer.addr = "0.0.0.0"
webServer.port = %d
webServer.user = "admin"
webServer.password = "admin"
`, consts.PortServerName, dashboardPort)
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
[[proxies]]
name = "http"
type = "http"
localPort = {{ .%s }}
customDomains = ["example.com"]
`, framework.HTTPSimpleServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.HTTP().HTTPPath("/healthz")
}).Port(dashboardPort).ExpectResp([]byte("")).Ensure()
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.HTTP().HTTPPath("/")
}).Port(dashboardPort).
Ensure(framework.ExpectResponseCode(401))
})
})

223
test/e2e/v1/basic/tcpmux.go

@ -0,0 +1,223 @@
package basic
import (
"bufio"
"fmt"
"net"
"net/http"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
"github.com/fatedier/frp/test/e2e/pkg/request"
"github.com/fatedier/frp/test/e2e/pkg/rpc"
)
var _ = ginkgo.Describe("[Feature: TCPMUX httpconnect]", func() {
f := framework.NewDefaultFramework()
getDefaultServerConf := func(httpconnectPort int) string {
conf := consts.DefaultServerConfig + `
tcpmuxHTTPConnectPort = %d
`
return fmt.Sprintf(conf, httpconnectPort)
}
newServer := func(port int, respContent string) *streamserver.Server {
return streamserver.New(
streamserver.TCP,
streamserver.WithBindPort(port),
streamserver.WithRespContent([]byte(respContent)),
)
}
proxyURLWithAuth := func(username, password string, port int) string {
if username == "" {
return fmt.Sprintf("http://127.0.0.1:%d", port)
}
return fmt.Sprintf("http://%s:%s@127.0.0.1:%d", username, password, port)
}
ginkgo.It("Route by HTTP user", func() {
vhostPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostPort)
fooPort := f.AllocPort()
f.RunServer("", newServer(fooPort, "foo"))
barPort := f.AllocPort()
f.RunServer("", newServer(barPort, "bar"))
otherPort := f.AllocPort()
f.RunServer("", newServer(otherPort, "other"))
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "foo"
type = "tcpmux"
multiplexer = "httpconnect"
localPort = %d
customDomains = ["normal.example.com"]
routeByHTTPUser = "user1"
[[proxies]]
name = "bar"
type = "tcpmux"
multiplexer = "httpconnect"
localPort = %d
customDomains = ["normal.example.com"]
routeByHTTPUser = "user2"
[[proxies]]
name = "catchAll"
type = "tcpmux"
multiplexer = "httpconnect"
localPort = %d
customDomains = ["normal.example.com"]
`, fooPort, barPort, otherPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// user1
framework.NewRequestExpect(f).Explain("user1").
RequestModify(func(r *request.Request) {
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("user1", "", vhostPort))
}).
ExpectResp([]byte("foo")).
Ensure()
// user2
framework.NewRequestExpect(f).Explain("user2").
RequestModify(func(r *request.Request) {
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("user2", "", vhostPort))
}).
ExpectResp([]byte("bar")).
Ensure()
// other user
framework.NewRequestExpect(f).Explain("other user").
RequestModify(func(r *request.Request) {
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("user3", "", vhostPort))
}).
ExpectResp([]byte("other")).
Ensure()
})
ginkgo.It("Proxy auth", func() {
vhostPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostPort)
fooPort := f.AllocPort()
f.RunServer("", newServer(fooPort, "foo"))
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "test"
type = "tcpmux"
multiplexer = "httpconnect"
localPort = %d
customDomains = ["normal.example.com"]
httpUser = "test"
httpPassword = "test"
`, fooPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// not set auth header
framework.NewRequestExpect(f).Explain("no auth").
RequestModify(func(r *request.Request) {
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("", "", vhostPort))
}).
ExpectError(true).
Ensure()
// set incorrect auth header
framework.NewRequestExpect(f).Explain("incorrect auth").
RequestModify(func(r *request.Request) {
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("test", "invalid", vhostPort))
}).
ExpectError(true).
Ensure()
// set correct auth header
framework.NewRequestExpect(f).Explain("correct auth").
RequestModify(func(r *request.Request) {
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("test", "test", vhostPort))
}).
ExpectResp([]byte("foo")).
Ensure()
})
ginkgo.It("TCPMux Passthrough", func() {
vhostPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostPort)
serverConf += `
tcpmuxPassthrough = true
`
var (
respErr error
connectRequestHost string
)
newServer := func(port int) *streamserver.Server {
return streamserver.New(
streamserver.TCP,
streamserver.WithBindPort(port),
streamserver.WithCustomHandler(func(conn net.Conn) {
defer conn.Close()
// read HTTP CONNECT request
bufioReader := bufio.NewReader(conn)
req, err := http.ReadRequest(bufioReader)
if err != nil {
respErr = err
return
}
connectRequestHost = req.Host
// return ok response
res := util.OkResponse()
if res.Body != nil {
defer res.Body.Close()
}
_ = res.Write(conn)
buf, err := rpc.ReadBytes(conn)
if err != nil {
respErr = err
return
}
_, _ = rpc.WriteBytes(conn, buf)
}),
)
}
localPort := f.AllocPort()
f.RunServer("", newServer(localPort))
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "test"
type = "tcpmux"
multiplexer = "httpconnect"
localPort = %d
customDomains = ["normal.example.com"]
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).
RequestModify(func(r *request.Request) {
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("", "", vhostPort)).Body([]byte("frp"))
}).
ExpectResp([]byte("frp")).
Ensure()
framework.ExpectNoError(respErr)
framework.ExpectEqualValues(connectRequestHost, "normal.example.com")
})
})

53
test/e2e/v1/basic/xtcp.go

@ -0,0 +1,53 @@
package basic
import (
"fmt"
"time"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/port"
"github.com/fatedier/frp/test/e2e/pkg/request"
)
var _ = ginkgo.Describe("[Feature: XTCP]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("Fallback To STCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
bindPortName := port.GenName("XTCP")
clientConf += fmt.Sprintf(`
[[proxies]]
name = "foo"
type = "stcp"
localPort = {{ .%s }}
[[visitors]]
name = "foo-visitor"
type = "stcp"
serverName = "foo"
bindPort = -1
[[visitors]]
name = "bar-visitor"
type = "xtcp"
serverName = "bar"
bindPort = {{ .%s }}
keepTunnelOpen = true
fallbackTo = "foo-visitor"
fallbackTimeoutMs = 200
`, framework.TCPEchoServerPort, bindPortName)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).
RequestModify(func(r *request.Request) {
r.Timeout(time.Second)
}).
PortName(bindPortName).
Ensure()
})
})

108
test/e2e/v1/features/bandwidth_limit.go

@ -0,0 +1,108 @@
package features
import (
"fmt"
"strings"
"time"
"github.com/onsi/ginkgo/v2"
plugin "github.com/fatedier/frp/pkg/plugin/server"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
plugintest "github.com/fatedier/frp/test/e2e/legacy/plugin"
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
"github.com/fatedier/frp/test/e2e/pkg/request"
)
var _ = ginkgo.Describe("[Feature: Bandwidth Limit]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("Proxy Bandwidth Limit by Client", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
localPort := f.AllocPort()
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort))
f.RunServer("", localServer)
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = %d
remotePort = %d
transport.bandwidthLimit = "10KB"
`, localPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
content := strings.Repeat("a", 50*1024) // 5KB
start := time.Now()
framework.NewRequestExpect(f).Port(remotePort).RequestModify(func(r *request.Request) {
r.Body([]byte(content)).Timeout(30 * time.Second)
}).ExpectResp([]byte(content)).Ensure()
duration := time.Since(start)
framework.Logf("request duration: %s", duration.String())
framework.ExpectTrue(duration.Seconds() > 8, "100Kb with 10KB limit, want > 8 seconds, but got %s", duration.String())
})
ginkgo.It("Proxy Bandwidth Limit by Server", func() {
// new test plugin server
newFunc := func() *plugin.Request {
var r plugin.Request
r.Content = &plugin.NewProxyContent{}
return &r
}
pluginPort := f.AllocPort()
handler := func(req *plugin.Request) *plugin.Response {
var ret plugin.Response
content := req.Content.(*plugin.NewProxyContent)
content.BandwidthLimit = "10KB"
content.BandwidthLimitMode = "server"
ret.Content = content
return &ret
}
pluginServer := plugintest.NewHTTPPluginServer(pluginPort, newFunc, handler, nil)
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
[[httpPlugins]]
name = "test"
addr = "127.0.0.1:%d"
path = "/handler"
ops = ["NewProxy"]
`, pluginPort)
clientConf := consts.DefaultClientConfig
localPort := f.AllocPort()
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort))
f.RunServer("", localServer)
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = %d
remotePort = %d
`, localPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
content := strings.Repeat("a", 50*1024) // 5KB
start := time.Now()
framework.NewRequestExpect(f).Port(remotePort).RequestModify(func(r *request.Request) {
r.Body([]byte(content)).Timeout(30 * time.Second)
}).ExpectResp([]byte(content)).Ensure()
duration := time.Since(start)
framework.Logf("request duration: %s", duration.String())
framework.ExpectTrue(duration.Seconds() > 8, "100Kb with 10KB limit, want > 8 seconds, but got %s", duration.String())
})
})

64
test/e2e/v1/features/chaos.go

@ -0,0 +1,64 @@
package features
import (
"fmt"
"time"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/test/e2e/framework"
)
var _ = ginkgo.Describe("[Feature: Chaos]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("reconnect after frps restart", func() {
serverPort := f.AllocPort()
serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
bindAddr = "0.0.0.0"
bindPort = %d
`, serverPort))
remotePort := f.AllocPort()
clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
serverPort = %d
log.level = "trace"
[[proxies]]
name = "tcp"
type = "tcp"
localPort = %d
remotePort = %d
`, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort))
// 1. start frps and frpc, expect request success
ps, _, err := f.RunFrps("-c", serverConfigPath)
framework.ExpectNoError(err)
pc, _, err := f.RunFrpc("-c", clientConfigPath)
framework.ExpectNoError(err)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
// 2. stop frps, expect request failed
_ = ps.Stop()
time.Sleep(200 * time.Millisecond)
framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure()
// 3. restart frps, expect request success
_, _, err = f.RunFrps("-c", serverConfigPath)
framework.ExpectNoError(err)
time.Sleep(2 * time.Second)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
// 4. stop frpc, expect request failed
_ = pc.Stop()
time.Sleep(200 * time.Millisecond)
framework.NewRequestExpect(f).Port(remotePort).ExpectError(true).Ensure()
// 5. restart frpc, expect request success
_, _, err = f.RunFrpc("-c", clientConfigPath)
framework.ExpectNoError(err)
time.Sleep(time.Second)
framework.NewRequestExpect(f).Port(remotePort).Ensure()
})
})

267
test/e2e/v1/features/group.go

@ -0,0 +1,267 @@
package features
import (
"fmt"
"strconv"
"sync"
"time"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
"github.com/fatedier/frp/test/e2e/pkg/request"
)
var _ = ginkgo.Describe("[Feature: Group]", func() {
f := framework.NewDefaultFramework()
newHTTPServer := func(port int, respContent string) *httpserver.Server {
return httpserver.New(
httpserver.WithBindPort(port),
httpserver.WithHandler(framework.SpecifiedHTTPBodyHandler([]byte(respContent))),
)
}
validateFooBarResponse := func(resp *request.Response) bool {
if string(resp.Content) == "foo" || string(resp.Content) == "bar" {
return true
}
return false
}
doFooBarHTTPRequest := func(vhostPort int, host string) []string {
results := []string{}
var wait sync.WaitGroup
var mu sync.Mutex
expectFn := func() {
framework.NewRequestExpect(f).Port(vhostPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost(host)
}).
Ensure(validateFooBarResponse, func(resp *request.Response) bool {
mu.Lock()
defer mu.Unlock()
results = append(results, string(resp.Content))
return true
})
}
for i := 0; i < 10; i++ {
wait.Add(1)
go func() {
defer wait.Done()
expectFn()
}()
}
wait.Wait()
return results
}
ginkgo.Describe("Load Balancing", func() {
ginkgo.It("TCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
fooPort := f.AllocPort()
fooServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(fooPort), streamserver.WithRespContent([]byte("foo")))
f.RunServer("", fooServer)
barPort := f.AllocPort()
barServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(barPort), streamserver.WithRespContent([]byte("bar")))
f.RunServer("", barServer)
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[[proxies]]
name = "foo"
type = "tcp"
localPort = %d
remotePort = %d
loadBalancer.group = "test"
loadBalancer.groupKey = "123"
[[proxies]]
name = "bar"
type = "tcp"
localPort = %d
remotePort = %d
loadBalancer.group = "test"
loadBalancer.groupKey = "123"
`, fooPort, remotePort, barPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
fooCount := 0
barCount := 0
for i := 0; i < 10; i++ {
framework.NewRequestExpect(f).Explain("times " + strconv.Itoa(i)).Port(remotePort).Ensure(func(resp *request.Response) bool {
switch string(resp.Content) {
case "foo":
fooCount++
case "bar":
barCount++
default:
return false
}
return true
})
}
framework.ExpectTrue(fooCount > 1 && barCount > 1, "fooCount: %d, barCount: %d", fooCount, barCount)
})
})
ginkgo.Describe("Health Check", func() {
ginkgo.It("TCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
fooPort := f.AllocPort()
fooServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(fooPort), streamserver.WithRespContent([]byte("foo")))
f.RunServer("", fooServer)
barPort := f.AllocPort()
barServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(barPort), streamserver.WithRespContent([]byte("bar")))
f.RunServer("", barServer)
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[[proxies]]
name = "foo"
type = "tcp"
localPort = %d
remotePort = %d
loadBalancer.group = "test"
loadBalancer.groupKey = "123"
healthCheck.type = "tcp"
healthCheck.intervalSeconds = 1
[[proxies]]
name = "bar"
type = "tcp"
localPort = %d
remotePort = %d
loadBalancer.group = "test"
loadBalancer.groupKey = "123"
healthCheck.type = "tcp"
healthCheck.intervalSeconds = 1
`, fooPort, remotePort, barPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// check foo and bar is ok
results := []string{}
for i := 0; i < 10; i++ {
framework.NewRequestExpect(f).Port(remotePort).Ensure(validateFooBarResponse, func(resp *request.Response) bool {
results = append(results, string(resp.Content))
return true
})
}
framework.ExpectContainElements(results, []string{"foo", "bar"})
// close bar server, check foo is ok
barServer.Close()
time.Sleep(2 * time.Second)
for i := 0; i < 10; i++ {
framework.NewRequestExpect(f).Port(remotePort).ExpectResp([]byte("foo")).Ensure()
}
// resume bar server, check foo and bar is ok
f.RunServer("", barServer)
time.Sleep(2 * time.Second)
results = []string{}
for i := 0; i < 10; i++ {
framework.NewRequestExpect(f).Port(remotePort).Ensure(validateFooBarResponse, func(resp *request.Response) bool {
results = append(results, string(resp.Content))
return true
})
}
framework.ExpectContainElements(results, []string{"foo", "bar"})
})
ginkgo.It("HTTP", func() {
vhostPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
vhostHTTPPort = %d
`, vhostPort)
clientConf := consts.DefaultClientConfig
fooPort := f.AllocPort()
fooServer := newHTTPServer(fooPort, "foo")
f.RunServer("", fooServer)
barPort := f.AllocPort()
barServer := newHTTPServer(barPort, "bar")
f.RunServer("", barServer)
clientConf += fmt.Sprintf(`
[[proxies]]
name = "foo"
type = "http"
localPort = %d
customDomains = ["example.com"]
loadBalancer.group = "test"
loadBalancer.groupKey = "123"
healthCheck.type = "http"
healthCheck.intervalSeconds = 1
healthCheck.path = "/healthz"
[[proxies]]
name = "bar"
type = "http"
localPort = %d
customDomains = ["example.com"]
loadBalancer.group = "test"
loadBalancer.groupKey = "123"
healthCheck.type = "http"
healthCheck.intervalSeconds = 1
healthCheck.path = "/healthz"
`, fooPort, barPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// send first HTTP request
var contents []string
framework.NewRequestExpect(f).Port(vhostPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("example.com")
}).
Ensure(func(resp *request.Response) bool {
contents = append(contents, string(resp.Content))
return true
})
// send second HTTP request, should be forwarded to another service
framework.NewRequestExpect(f).Port(vhostPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("example.com")
}).
Ensure(func(resp *request.Response) bool {
contents = append(contents, string(resp.Content))
return true
})
framework.ExpectContainElements(contents, []string{"foo", "bar"})
// check foo and bar is ok
results := doFooBarHTTPRequest(vhostPort, "example.com")
framework.ExpectContainElements(results, []string{"foo", "bar"})
// close bar server, check foo is ok
barServer.Close()
time.Sleep(2 * time.Second)
results = doFooBarHTTPRequest(vhostPort, "example.com")
framework.ExpectContainElements(results, []string{"foo"})
framework.ExpectNotContainElements(results, []string{"bar"})
// resume bar server, check foo and bar is ok
f.RunServer("", barServer)
time.Sleep(2 * time.Second)
results = doFooBarHTTPRequest(vhostPort, "example.com")
framework.ExpectContainElements(results, []string{"foo", "bar"})
})
})
})

47
test/e2e/v1/features/heartbeat.go

@ -0,0 +1,47 @@
package features
import (
"fmt"
"time"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/test/e2e/framework"
)
var _ = ginkgo.Describe("[Feature: Heartbeat]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("disable application layer heartbeat", func() {
serverPort := f.AllocPort()
serverConf := fmt.Sprintf(`
bindAddr = "0.0.0.0"
bindPort = %d
transport.heartbeatTimeout = -1
transport.tcpMuxKeepaliveInterval = 2
`, serverPort)
remotePort := f.AllocPort()
clientConf := fmt.Sprintf(`
serverPort = %d
log.level = "trace"
transport.heartbeatInterval = -1
transport.heartbeatTimeout = -1
transport.tcpMuxKeepaliveInterval = 2
[[proxies]]
name = "tcp"
type = "tcp"
localPort = %d
remotePort = %d
`, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort)
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure()
time.Sleep(5 * time.Second)
framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure()
})
})

55
test/e2e/v1/features/monitor.go

@ -0,0 +1,55 @@
package features
import (
"fmt"
"strings"
"time"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/request"
)
var _ = ginkgo.Describe("[Feature: Monitor]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("Prometheus metrics", func() {
dashboardPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
enablePrometheus = true
webServer.addr = "0.0.0.0"
webServer.port = %d
`, dashboardPort)
clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, framework.TCPEchoServerPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
time.Sleep(500 * time.Millisecond)
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.HTTP().Port(dashboardPort).HTTPPath("/metrics")
}).Ensure(func(resp *request.Response) bool {
log.Trace("prometheus metrics response: \n%s", resp.Content)
if resp.Code != 200 {
return false
}
if !strings.Contains(string(resp.Content), "traffic_in") {
return false
}
return true
})
})
})

154
test/e2e/v1/features/real_ip.go

@ -0,0 +1,154 @@
package features
import (
"bufio"
"fmt"
"net"
"net/http"
"github.com/onsi/ginkgo/v2"
pp "github.com/pires/go-proxyproto"
"github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
"github.com/fatedier/frp/test/e2e/pkg/request"
"github.com/fatedier/frp/test/e2e/pkg/rpc"
)
var _ = ginkgo.Describe("[Feature: Real IP]", func() {
f := framework.NewDefaultFramework()
ginkgo.It("HTTP X-Forwarded-For", func() {
vhostHTTPPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
vhostHTTPPort = %d
`, vhostHTTPPort)
localPort := f.AllocPort()
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
_, _ = w.Write([]byte(req.Header.Get("X-Forwarded-For")))
})),
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "test"
type = "http"
localPort = %d
customDomains = ["normal.example.com"]
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).
ExpectResp([]byte("127.0.0.1")).
Ensure()
})
ginkgo.Describe("Proxy Protocol", func() {
ginkgo.It("TCP", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
localPort := f.AllocPort()
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort),
streamserver.WithCustomHandler(func(c net.Conn) {
defer c.Close()
rd := bufio.NewReader(c)
ppHeader, err := pp.Read(rd)
if err != nil {
log.Error("read proxy protocol error: %v", err)
return
}
for {
if _, err := rpc.ReadBytes(rd); err != nil {
return
}
buf := []byte(ppHeader.SourceAddr.String())
_, _ = rpc.WriteBytes(c, buf)
}
}))
f.RunServer("", localServer)
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = %d
remotePort = %d
transport.proxyProtocolVersion = "v2"
`, localPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure(func(resp *request.Response) bool {
log.Trace("ProxyProtocol get SourceAddr: %s", string(resp.Content))
addr, err := net.ResolveTCPAddr("tcp", string(resp.Content))
if err != nil {
return false
}
if addr.IP.String() != "127.0.0.1" {
return false
}
return true
})
})
ginkgo.It("HTTP", func() {
vhostHTTPPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
vhostHTTPPort = %d
`, vhostHTTPPort)
clientConf := consts.DefaultClientConfig
localPort := f.AllocPort()
var srcAddrRecord string
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort),
streamserver.WithCustomHandler(func(c net.Conn) {
defer c.Close()
rd := bufio.NewReader(c)
ppHeader, err := pp.Read(rd)
if err != nil {
log.Error("read proxy protocol error: %v", err)
return
}
srcAddrRecord = ppHeader.SourceAddr.String()
}))
f.RunServer("", localServer)
clientConf += fmt.Sprintf(`
[[proxies]]
name = "test"
type = "http"
localPort = %d
customDomains = ["normal.example.com"]
transport.proxyProtocolVersion = "v2"
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(vhostHTTPPort).RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).Ensure(framework.ExpectResponseCode(404))
log.Trace("ProxyProtocol get SourceAddr: %s", srcAddrRecord)
addr, err := net.ResolveTCPAddr("tcp", srcAddrRecord)
framework.ExpectNoError(err, srcAddrRecord)
framework.ExpectEqualValues("127.0.0.1", addr.IP.String())
})
})
})

331
test/e2e/v1/plugin/client.go

@ -0,0 +1,331 @@
package plugin
import (
"crypto/tls"
"fmt"
"strconv"
"github.com/onsi/ginkgo/v2"
"github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
"github.com/fatedier/frp/test/e2e/pkg/cert"
"github.com/fatedier/frp/test/e2e/pkg/port"
"github.com/fatedier/frp/test/e2e/pkg/request"
)
var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
f := framework.NewDefaultFramework()
ginkgo.Describe("UnixDomainSocket", func() {
ginkgo.It("Expose a unix domain socket echo server", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
getProxyConf := func(proxyName string, portName string, extra string) string {
return fmt.Sprintf(`
[[proxies]]
name = "%s"
type = "tcp"
remotePort = {{ .%s }}
[proxies.plugin]
type = "unix_domain_socket"
unixPath = "{{ .%s }}"
`+extra, proxyName, portName, framework.UDSEchoServerAddr)
}
tests := []struct {
proxyName string
portName string
extraConfig string
}{
{
proxyName: "normal",
portName: port.GenName("Normal"),
},
{
proxyName: "with-encryption",
portName: port.GenName("WithEncryption"),
extraConfig: "transport.useEncryption = true",
},
{
proxyName: "with-compression",
portName: port.GenName("WithCompression"),
extraConfig: "transport.useCompression = true",
},
{
proxyName: "with-encryption-and-compression",
portName: port.GenName("WithEncryptionAndCompression"),
extraConfig: `
transport.useEncryption = true
transport.useCompression = true
`,
},
}
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
for _, test := range tests {
framework.NewRequestExpect(f).Port(f.PortByName(test.portName)).Ensure()
}
})
})
ginkgo.It("http_proxy", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
remotePort = %d
[proxies.plugin]
type = "http_proxy"
httpUser = "abc"
httpPassword = "123"
`, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// http proxy, no auth info
framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
r.HTTP().Proxy("http://127.0.0.1:" + strconv.Itoa(remotePort))
}).Ensure(framework.ExpectResponseCode(407))
// http proxy, correct auth
framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
r.HTTP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
}).Ensure()
// connect TCP server by CONNECT method
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
r.TCP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
})
})
ginkgo.It("socks5 proxy", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
remotePort = %d
[proxies.plugin]
type = "socks5"
username = "abc"
password = "123"
`, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// http proxy, no auth info
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
r.TCP().Proxy("socks5://127.0.0.1:" + strconv.Itoa(remotePort))
}).ExpectError(true).Ensure()
// http proxy, correct auth
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
r.TCP().Proxy("socks5://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
}).Ensure()
})
ginkgo.It("static_file", func() {
vhostPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
vhostHTTPPort = %d
`, vhostPort)
clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
f.WriteTempFile("test_static_file", "foo")
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
remotePort = %d
[proxies.plugin]
type = "static_file"
localPath = "%s"
[[proxies]]
name = "http"
type = "http"
customDomains = ["example.com"]
[proxies.plugin]
type = "static_file"
localPath = "%s"
[[proxies]]
name = "http-with-auth"
type = "http"
customDomains = ["other.example.com"]
[proxies.plugin]
type = "static_file"
localPath = "%s"
httpUser = "abc"
httpPassword = "123"
`, remotePort, f.TempDirectory, f.TempDirectory, f.TempDirectory)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// from tcp proxy
framework.NewRequestExpect(f).Request(
framework.NewHTTPRequest().HTTPPath("/test_static_file").Port(remotePort),
).ExpectResp([]byte("foo")).Ensure()
// from http proxy without auth
framework.NewRequestExpect(f).Request(
framework.NewHTTPRequest().HTTPHost("example.com").HTTPPath("/test_static_file").Port(vhostPort),
).ExpectResp([]byte("foo")).Ensure()
// from http proxy with auth
framework.NewRequestExpect(f).Request(
framework.NewHTTPRequest().HTTPHost("other.example.com").HTTPPath("/test_static_file").Port(vhostPort).HTTPAuth("abc", "123"),
).ExpectResp([]byte("foo")).Ensure()
})
ginkgo.It("http2https", func() {
serverConf := consts.DefaultServerConfig
vhostHTTPPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhostHTTPPort = %d
`, vhostHTTPPort)
localPort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
[[proxies]]
name = "http2https"
type = "http"
customDomains = ["example.com"]
[proxies.plugin]
type = "http2https"
localAddr = "127.0.0.1:%d"
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
framework.ExpectNoError(err)
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithTLSConfig(tlsConfig),
httpserver.WithResponse([]byte("test")),
)
f.RunServer("", localServer)
framework.NewRequestExpect(f).
Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("example.com")
}).
ExpectResp([]byte("test")).
Ensure()
})
ginkgo.It("https2http", func() {
generator := &cert.SelfSignedCertGenerator{}
artifacts, err := generator.Generate("example.com")
framework.ExpectNoError(err)
crtPath := f.WriteTempFile("server.crt", string(artifacts.Cert))
keyPath := f.WriteTempFile("server.key", string(artifacts.Key))
serverConf := consts.DefaultServerConfig
vhostHTTPSPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhostHTTPSPort = %d
`, vhostHTTPSPort)
localPort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
[[proxies]]
name = "https2http"
type = "https"
customDomains = ["example.com"]
[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:%d"
crtPath = "%s"
keyPath = "%s"
`, localPort, crtPath, keyPath)
f.RunProcesses([]string{serverConf}, []string{clientConf})
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithResponse([]byte("test")),
)
f.RunServer("", localServer)
framework.NewRequestExpect(f).
Port(vhostHTTPSPort).
RequestModify(func(r *request.Request) {
r.HTTPS().HTTPHost("example.com").TLSConfig(&tls.Config{
ServerName: "example.com",
InsecureSkipVerify: true,
})
}).
ExpectResp([]byte("test")).
Ensure()
})
ginkgo.It("https2https", func() {
generator := &cert.SelfSignedCertGenerator{}
artifacts, err := generator.Generate("example.com")
framework.ExpectNoError(err)
crtPath := f.WriteTempFile("server.crt", string(artifacts.Cert))
keyPath := f.WriteTempFile("server.key", string(artifacts.Key))
serverConf := consts.DefaultServerConfig
vhostHTTPSPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhostHTTPSPort = %d
`, vhostHTTPSPort)
localPort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
[[proxies]]
name = "https2https"
type = "https"
customDomains = ["example.com"]
[proxies.plugin]
type = "https2https"
localAddr = "127.0.0.1:%d"
crtPath = "%s"
keyPath = "%s"
`, localPort, crtPath, keyPath)
f.RunProcesses([]string{serverConf}, []string{clientConf})
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
framework.ExpectNoError(err)
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithResponse([]byte("test")),
httpserver.WithTLSConfig(tlsConfig),
)
f.RunServer("", localServer)
framework.NewRequestExpect(f).
Port(vhostHTTPSPort).
RequestModify(func(r *request.Request) {
r.HTTPS().HTTPHost("example.com").TLSConfig(&tls.Config{
ServerName: "example.com",
InsecureSkipVerify: true,
})
}).
ExpectResp([]byte("test")).
Ensure()
})
})

415
test/e2e/v1/plugin/server.go

@ -0,0 +1,415 @@
package plugin
import (
"fmt"
"time"
"github.com/onsi/ginkgo/v2"
plugin "github.com/fatedier/frp/pkg/plugin/server"
"github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
)
var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
f := framework.NewDefaultFramework()
ginkgo.Describe("Login", func() {
newFunc := func() *plugin.Request {
var r plugin.Request
r.Content = &plugin.LoginContent{}
return &r
}
ginkgo.It("Auth for custom meta token", func() {
localPort := f.AllocPort()
clientAddressGot := false
handler := func(req *plugin.Request) *plugin.Response {
var ret plugin.Response
content := req.Content.(*plugin.LoginContent)
if content.ClientAddress != "" {
clientAddressGot = true
}
if content.Metas["token"] == "123" {
ret.Unchange = true
} else {
ret.Reject = true
ret.RejectReason = "invalid token"
}
return &ret
}
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
[[httpPlugins]]
name = "user-manager"
addr = "127.0.0.1:%d"
path = "/handler"
ops = ["Login"]
`, localPort)
clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
metadatas.token = "123"
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, framework.TCPEchoServerPort, remotePort)
remotePort2 := f.AllocPort()
invalidTokenClientConf := consts.DefaultClientConfig + fmt.Sprintf(`
[[proxies]]
name = "tcp2"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, framework.TCPEchoServerPort, remotePort2)
f.RunProcesses([]string{serverConf}, []string{clientConf, invalidTokenClientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
framework.NewRequestExpect(f).Port(remotePort2).ExpectError(true).Ensure()
framework.ExpectTrue(clientAddressGot)
})
})
ginkgo.Describe("NewProxy", func() {
newFunc := func() *plugin.Request {
var r plugin.Request
r.Content = &plugin.NewProxyContent{}
return &r
}
ginkgo.It("Validate Info", func() {
localPort := f.AllocPort()
handler := func(req *plugin.Request) *plugin.Response {
var ret plugin.Response
content := req.Content.(*plugin.NewProxyContent)
if content.ProxyName == "tcp" {
ret.Unchange = true
} else {
ret.Reject = true
}
return &ret
}
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
[[httpPlugins]]
name = "test"
addr = "127.0.0.1:%d"
path = "/handler"
ops = ["NewProxy"]
`, localPort)
clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, framework.TCPEchoServerPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
})
ginkgo.It("Mofify RemotePort", func() {
localPort := f.AllocPort()
remotePort := f.AllocPort()
handler := func(req *plugin.Request) *plugin.Response {
var ret plugin.Response
content := req.Content.(*plugin.NewProxyContent)
content.RemotePort = remotePort
ret.Content = content
return &ret
}
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
[[httpPlugins]]
name = "test"
addr = "127.0.0.1:%d"
path = "/handler"
ops = ["NewProxy"]
`, localPort)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = 0
`, framework.TCPEchoServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
})
})
ginkgo.Describe("CloseProxy", func() {
newFunc := func() *plugin.Request {
var r plugin.Request
r.Content = &plugin.CloseProxyContent{}
return &r
}
ginkgo.It("Validate Info", func() {
localPort := f.AllocPort()
var recordProxyName string
handler := func(req *plugin.Request) *plugin.Response {
var ret plugin.Response
content := req.Content.(*plugin.CloseProxyContent)
recordProxyName = content.ProxyName
return &ret
}
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
[[httpPlugins]]
name = "test"
addr = "127.0.0.1:%d"
path = "/handler"
ops = ["CloseProxy"]
`, localPort)
clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, framework.TCPEchoServerPort, remotePort)
_, clients := f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
for _, c := range clients {
_ = c.Stop()
}
time.Sleep(1 * time.Second)
framework.ExpectEqual(recordProxyName, "tcp")
})
})
ginkgo.Describe("Ping", func() {
newFunc := func() *plugin.Request {
var r plugin.Request
r.Content = &plugin.PingContent{}
return &r
}
ginkgo.It("Validate Info", func() {
localPort := f.AllocPort()
var record string
handler := func(req *plugin.Request) *plugin.Response {
var ret plugin.Response
content := req.Content.(*plugin.PingContent)
record = content.Ping.PrivilegeKey
ret.Unchange = true
return &ret
}
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
[[httpPlugins]]
name = "test"
addr = "127.0.0.1:%d"
path = "/handler"
ops = ["Ping"]
`, localPort)
remotePort := f.AllocPort()
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
transport.heartbeatInterval = 1
auth.additionalScopes = ["HeartBeats"]
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, framework.TCPEchoServerPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
time.Sleep(3 * time.Second)
framework.ExpectNotEqual("", record)
})
})
ginkgo.Describe("NewWorkConn", func() {
newFunc := func() *plugin.Request {
var r plugin.Request
r.Content = &plugin.NewWorkConnContent{}
return &r
}
ginkgo.It("Validate Info", func() {
localPort := f.AllocPort()
var record string
handler := func(req *plugin.Request) *plugin.Response {
var ret plugin.Response
content := req.Content.(*plugin.NewWorkConnContent)
record = content.NewWorkConn.RunID
ret.Unchange = true
return &ret
}
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
[[httpPlugins]]
name = "test"
addr = "127.0.0.1:%d"
path = "/handler"
ops = ["NewWorkConn"]
`, localPort)
remotePort := f.AllocPort()
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, framework.TCPEchoServerPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
framework.ExpectNotEqual("", record)
})
})
ginkgo.Describe("NewUserConn", func() {
newFunc := func() *plugin.Request {
var r plugin.Request
r.Content = &plugin.NewUserConnContent{}
return &r
}
ginkgo.It("Validate Info", func() {
localPort := f.AllocPort()
var record string
handler := func(req *plugin.Request) *plugin.Response {
var ret plugin.Response
content := req.Content.(*plugin.NewUserConnContent)
record = content.RemoteAddr
ret.Unchange = true
return &ret
}
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
[[httpPlugins]]
name = "test"
addr = "127.0.0.1:%d"
path = "/handler"
ops = ["NewUserConn"]
`, localPort)
remotePort := f.AllocPort()
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, framework.TCPEchoServerPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
framework.ExpectNotEqual("", record)
})
})
ginkgo.Describe("HTTPS Protocol", func() {
newFunc := func() *plugin.Request {
var r plugin.Request
r.Content = &plugin.NewUserConnContent{}
return &r
}
ginkgo.It("Validate Login Info, disable tls verify", func() {
localPort := f.AllocPort()
var record string
handler := func(req *plugin.Request) *plugin.Response {
var ret plugin.Response
content := req.Content.(*plugin.NewUserConnContent)
record = content.RemoteAddr
ret.Unchange = true
return &ret
}
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
framework.ExpectNoError(err)
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, tlsConfig)
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
[[httpPlugins]]
name = "test"
addr = "https://127.0.0.1:%d"
path = "/handler"
ops = ["NewUserConn"]
`, localPort)
remotePort := f.AllocPort()
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "tcp"
type = "tcp"
localPort = {{ .%s }}
remotePort = %d
`, framework.TCPEchoServerPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
framework.ExpectNotEqual("", record)
})
})
})

41
test/e2e/v1/plugin/utils.go

@ -0,0 +1,41 @@
package plugin
import (
"crypto/tls"
"encoding/json"
"io"
"net/http"
plugin "github.com/fatedier/frp/pkg/plugin/server"
"github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
)
type Handler func(req *plugin.Request) *plugin.Response
type NewPluginRequest func() *plugin.Request
func NewHTTPPluginServer(port int, newFunc NewPluginRequest, handler Handler, tlsConfig *tls.Config) *httpserver.Server {
return httpserver.New(
httpserver.WithBindPort(port),
httpserver.WithTLSConfig(tlsConfig),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
r := newFunc()
buf, err := io.ReadAll(req.Body)
if err != nil {
w.WriteHeader(500)
return
}
log.Trace("plugin request: %s", string(buf))
err = json.Unmarshal(buf, &r)
if err != nil {
w.WriteHeader(500)
return
}
resp := handler(r)
buf, _ = json.Marshal(resp)
log.Trace("plugin response: %s", string(buf))
_, _ = w.Write(buf)
})),
)
}
Loading…
Cancel
Save