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() } }) }) })