diff --git a/Release.md b/Release.md index cff4dbc1..7f01beef 100644 --- a/Release.md +++ b/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 diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go index 01c73c01..e1194fb8 100644 --- a/cmd/frpc/sub/root.go +++ b/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) } diff --git a/cmd/frps/root.go b/cmd/frps/root.go index af94aa47..601e068e 100644 --- a/cmd/frps/root.go +++ b/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 diff --git a/conf/frpc.ini b/conf/frpc.ini deleted file mode 100644 index 13a8e5f6..00000000 --- a/conf/frpc.ini +++ /dev/null @@ -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 diff --git a/conf/frpc_full.ini b/conf/frpc_legacy_full.ini similarity index 100% rename from conf/frpc_full.ini rename to conf/frpc_legacy_full.ini diff --git a/conf/frps.ini b/conf/frps.ini deleted file mode 100644 index 229567a9..00000000 --- a/conf/frps.ini +++ /dev/null @@ -1,2 +0,0 @@ -[common] -bind_port = 7000 diff --git a/conf/frps_full.ini b/conf/frps_legacy_full.ini similarity index 100% rename from conf/frps_full.ini rename to conf/frps_legacy_full.ini diff --git a/go.mod b/go.mod index 49e74d57..f7996399 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index f7ed6f4b..4cab567e 100644 --- a/go.sum +++ b/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= diff --git a/hack/run-e2e.sh b/hack/run-e2e.sh index 953df0ee..a7d4688a 100755 --- a/hack/run-e2e.sh +++ b/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 diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 2a79149c..4c654906 100644 --- a/pkg/auth/auth.go +++ b/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 } diff --git a/pkg/config/legacy/conversion.go b/pkg/config/legacy/conversion.go index 8d9074bf..2540217f 100644 --- a/pkg/config/legacy/conversion.go +++ b/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{ diff --git a/pkg/config/load.go b/pkg/config/load.go index 2f454eb2..739c4a8c 100644 --- a/pkg/config/load.go +++ b/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) } diff --git a/pkg/config/v1/client.go b/pkg/config/v1/client.go index 878ae2b7..6c68738b 100644 --- a/pkg/config/v1/client.go +++ b/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 "". diff --git a/pkg/config/v1/plugin.go b/pkg/config/v1/plugin.go index e593c6ff..a01d1022 100644 --- a/pkg/config/v1/plugin.go +++ b/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 } diff --git a/pkg/config/v1/server.go b/pkg/config/v1/server.go index 65c5dc46..5a5316d8 100644 --- a/pkg/config/v1/server.go +++ b/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 { diff --git a/pkg/config/v1/validation/client.go b/pkg/config/v1/validation/client.go index 537f386d..2868789b 100644 --- a/pkg/config/v1/validation/client.go +++ b/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 } } diff --git a/pkg/config/v1/visitor.go b/pkg/config/v1/visitor.go index 523b863b..ad216e35 100644 --- a/pkg/config/v1/visitor.go +++ b/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"` diff --git a/pkg/plugin/client/plugin.go b/pkg/plugin/client/plugin.go index 98e1bb73..da0ceb59 100644 --- a/pkg/plugin/client/plugin.go +++ b/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 } diff --git a/server/dashboard_api.go b/server/dashboard_api.go index e54ebe77..7a4fdfff 100644 --- a/server/dashboard_api.go +++ b/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, diff --git a/server/service.go b/server/service.go index c81ea4d2..a5e84ea9 100644 --- a/server/service.go +++ b/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() diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index af2ca261..27c36e0c 100644 --- a/test/e2e/e2e_test.go +++ b/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. diff --git a/test/e2e/examples.go b/test/e2e/examples.go index 9accdf9c..135ce706 100644 --- a/test/e2e/examples.go +++ b/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}) diff --git a/test/e2e/framework/consts/consts.go b/test/e2e/framework/consts/consts.go index 622eba92..1e5ca500 100644 --- a/test/e2e/framework/consts/consts.go +++ b/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")) } diff --git a/test/e2e/framework/process.go b/test/e2e/framework/process.go index ca717e25..437c8997 100644 --- a/test/e2e/framework/process.go +++ b/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) diff --git a/test/e2e/basic/basic.go b/test/e2e/legacy/basic/basic.go similarity index 95% rename from test/e2e/basic/basic.go rename to test/e2e/legacy/basic/basic.go index 805b4601..763d3353 100644 --- a/test/e2e/basic/basic.go +++ b/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(` diff --git a/test/e2e/basic/client.go b/test/e2e/legacy/basic/client.go similarity index 90% rename from test/e2e/basic/client.go rename to test/e2e/legacy/basic/client.go index fb9b16b5..b2e462bb 100644 --- a/test/e2e/basic/client.go +++ b/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] diff --git a/test/e2e/basic/client_server.go b/test/e2e/legacy/basic/client_server.go similarity index 98% rename from test/e2e/basic/client_server.go rename to test/e2e/legacy/basic/client_server.go index e7730f45..03bca0c5 100644 --- a/test/e2e/basic/client_server.go +++ b/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 } diff --git a/test/e2e/basic/cmd.go b/test/e2e/legacy/basic/cmd.go similarity index 100% rename from test/e2e/basic/cmd.go rename to test/e2e/legacy/basic/cmd.go diff --git a/test/e2e/basic/config.go b/test/e2e/legacy/basic/config.go similarity index 95% rename from test/e2e/basic/config.go rename to test/e2e/legacy/basic/config.go index acee2c56..6e9f3564 100644 --- a/test/e2e/basic/config.go +++ b/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(` diff --git a/test/e2e/basic/http.go b/test/e2e/legacy/basic/http.go similarity index 95% rename from test/e2e/basic/http.go rename to test/e2e/legacy/basic/http.go index f8b17387..77ba1052 100644 --- a/test/e2e/basic/http.go +++ b/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 diff --git a/test/e2e/basic/server.go b/test/e2e/legacy/basic/server.go similarity index 91% rename from test/e2e/basic/server.go rename to test/e2e/legacy/basic/server.go index bfa2f2eb..08bc6b22 100644 --- a/test/e2e/basic/server.go +++ b/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 }} diff --git a/test/e2e/basic/tcpmux.go b/test/e2e/legacy/basic/tcpmux.go similarity index 96% rename from test/e2e/basic/tcpmux.go rename to test/e2e/legacy/basic/tcpmux.go index a1106b95..5bb742bc 100644 --- a/test/e2e/basic/tcpmux.go +++ b/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 diff --git a/test/e2e/basic/xtcp.go b/test/e2e/legacy/basic/xtcp.go similarity index 91% rename from test/e2e/basic/xtcp.go rename to test/e2e/legacy/basic/xtcp.go index a501d793..3c47f577 100644 --- a/test/e2e/basic/xtcp.go +++ b/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(` diff --git a/test/e2e/features/bandwidth_limit.go b/test/e2e/legacy/features/bandwidth_limit.go similarity index 91% rename from test/e2e/features/bandwidth_limit.go rename to test/e2e/legacy/features/bandwidth_limit.go index 96d23646..cbc6e928 100644 --- a/test/e2e/features/bandwidth_limit.go +++ b/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)) diff --git a/test/e2e/features/chaos.go b/test/e2e/legacy/features/chaos.go similarity index 100% rename from test/e2e/features/chaos.go rename to test/e2e/legacy/features/chaos.go diff --git a/test/e2e/features/group.go b/test/e2e/legacy/features/group.go similarity index 95% rename from test/e2e/features/group.go rename to test/e2e/legacy/features/group.go index 8ec61125..dc15f75c 100644 --- a/test/e2e/features/group.go +++ b/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") diff --git a/test/e2e/features/heartbeat.go b/test/e2e/legacy/features/heartbeat.go similarity index 100% rename from test/e2e/features/heartbeat.go rename to test/e2e/legacy/features/heartbeat.go diff --git a/test/e2e/features/monitor.go b/test/e2e/legacy/features/monitor.go similarity index 91% rename from test/e2e/features/monitor.go rename to test/e2e/legacy/features/monitor.go index 74996d2c..8968df9f 100644 --- a/test/e2e/features/monitor.go +++ b/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] diff --git a/test/e2e/features/real_ip.go b/test/e2e/legacy/features/real_ip.go similarity index 92% rename from test/e2e/features/real_ip.go rename to test/e2e/legacy/features/real_ip.go index 31f02fe2..082df8c6 100644 --- a/test/e2e/features/real_ip.go +++ b/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 diff --git a/test/e2e/plugin/client.go b/test/e2e/legacy/plugin/client.go similarity index 91% rename from test/e2e/plugin/client.go rename to test/e2e/legacy/plugin/client.go index 3046b900..b69c6aed 100644 --- a/test/e2e/plugin/client.go +++ b/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 diff --git a/test/e2e/plugin/server.go b/test/e2e/legacy/plugin/server.go similarity index 90% rename from test/e2e/plugin/server.go rename to test/e2e/legacy/plugin/server.go index 955e3b54..0011b1b9 100644 --- a/test/e2e/plugin/server.go +++ b/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 diff --git a/test/e2e/plugin/utils.go b/test/e2e/legacy/plugin/utils.go similarity index 100% rename from test/e2e/plugin/utils.go rename to test/e2e/legacy/plugin/utils.go diff --git a/test/e2e/v1/basic/basic.go b/test/e2e/v1/basic/basic.go new file mode 100644 index 00000000..9d9d34cb --- /dev/null +++ b/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() + } + }) + }) +}) diff --git a/test/e2e/v1/basic/client.go b/test/e2e/v1/basic/client.go new file mode 100644 index 00000000..111a238a --- /dev/null +++ b/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() + }) +}) diff --git a/test/e2e/v1/basic/client_server.go b/test/e2e/v1/basic/client_server.go new file mode 100644 index 00000000..082c0de5 --- /dev/null +++ b/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), + }) + } + }) +}) diff --git a/test/e2e/v1/basic/cmd.go b/test/e2e/v1/basic/cmd.go new file mode 100644 index 00000000..9d3120fa --- /dev/null +++ b/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() + }) + }) +}) diff --git a/test/e2e/v1/basic/config.go b/test/e2e/v1/basic/config.go new file mode 100644 index 00000000..3aba9ae0 --- /dev/null +++ b/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() + }) + }) +}) diff --git a/test/e2e/v1/basic/http.go b/test/e2e/v1/basic/http.go new file mode 100644 index 00000000..e649dbfb --- /dev/null +++ b/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)) + }) +}) diff --git a/test/e2e/v1/basic/server.go b/test/e2e/v1/basic/server.go new file mode 100644 index 00000000..ff1225b8 --- /dev/null +++ b/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)) + }) +}) diff --git a/test/e2e/v1/basic/tcpmux.go b/test/e2e/v1/basic/tcpmux.go new file mode 100644 index 00000000..356a18be --- /dev/null +++ b/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") + }) +}) diff --git a/test/e2e/v1/basic/xtcp.go b/test/e2e/v1/basic/xtcp.go new file mode 100644 index 00000000..a5aaf67b --- /dev/null +++ b/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() + }) +}) diff --git a/test/e2e/v1/features/bandwidth_limit.go b/test/e2e/v1/features/bandwidth_limit.go new file mode 100644 index 00000000..5bf1c796 --- /dev/null +++ b/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()) + }) +}) diff --git a/test/e2e/v1/features/chaos.go b/test/e2e/v1/features/chaos.go new file mode 100644 index 00000000..f5f8b388 --- /dev/null +++ b/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() + }) +}) diff --git a/test/e2e/v1/features/group.go b/test/e2e/v1/features/group.go new file mode 100644 index 00000000..fe0c957b --- /dev/null +++ b/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"}) + }) + }) +}) diff --git a/test/e2e/v1/features/heartbeat.go b/test/e2e/v1/features/heartbeat.go new file mode 100644 index 00000000..4f5409fe --- /dev/null +++ b/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() + }) +}) diff --git a/test/e2e/v1/features/monitor.go b/test/e2e/v1/features/monitor.go new file mode 100644 index 00000000..5d4b8f9f --- /dev/null +++ b/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 + }) + }) +}) diff --git a/test/e2e/v1/features/real_ip.go b/test/e2e/v1/features/real_ip.go new file mode 100644 index 00000000..85338c62 --- /dev/null +++ b/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()) + }) + }) +}) diff --git a/test/e2e/v1/plugin/client.go b/test/e2e/v1/plugin/client.go new file mode 100644 index 00000000..f544570a --- /dev/null +++ b/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() + }) +}) diff --git a/test/e2e/v1/plugin/server.go b/test/e2e/v1/plugin/server.go new file mode 100644 index 00000000..02e8469d --- /dev/null +++ b/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) + }) + }) +}) diff --git a/test/e2e/v1/plugin/utils.go b/test/e2e/v1/plugin/utils.go new file mode 100644 index 00000000..51de01d9 --- /dev/null +++ b/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) + })), + ) +}