diff --git a/Release.md b/Release.md index cff4dbc..7f01bee 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 01c73c0..e1194fb 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 af94aa4..601e068 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 13a8e5f..0000000 --- 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 229567a..0000000 --- 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 49e74d5..f799639 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 f7ed6f4..4cab567 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 953df0e..a7d4688 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 2a79149..4c65490 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 8d9074b..2540217 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 2f454eb..739c4a8 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 878ae2b..6c68738 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 e593c6f..a01d102 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 65c5dc4..5a5316d 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 537f386..2868789 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 523b863..ad216e3 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 98e1bb7..da0ceb5 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 e54ebe7..7a4fdff 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 c81ea4d..a5e84ea 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 af2ca26..27c36e0 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 9accdf9..135ce70 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 622eba9..1e5ca50 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 ca717e2..437c899 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 805b460..763d335 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 fb9b16b..b2e462b 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 e7730f4..03bca0c 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 acee2c5..6e9f356 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 f8b1738..77ba105 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 bfa2f2e..08bc6b2 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 a1106b9..5bb742b 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 a501d79..3c47f57 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 96d2364..cbc6e92 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 8ec6112..dc15f75 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 74996d2..8968df9 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 31f02fe..082df8c 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 3046b90..b69c6ae 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 955e3b5..0011b1b 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 0000000..9d9d34c --- /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 0000000..111a238 --- /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 0000000..082c0de --- /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 0000000..9d3120f --- /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 0000000..3aba9ae --- /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 0000000..e649dbf --- /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 0000000..ff1225b --- /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 0000000..356a18b --- /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 0000000..a5aaf67 --- /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 0000000..5bf1c79 --- /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 0000000..f5f8b38 --- /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 0000000..fe0c957 --- /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 0000000..4f5409f --- /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 0000000..5d4b8f9 --- /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 0000000..85338c6 --- /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 0000000..f544570 --- /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 0000000..02e8469 --- /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 0000000..51de01d --- /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) + })), + ) +}