more e2e test cases (#2450)

This commit is contained in:
fatedier
2021-06-18 16:48:36 +08:00
committed by GitHub
parent c7d4637382
commit 900454e58b
45 changed files with 1736 additions and 1953 deletions

View File

@@ -6,7 +6,7 @@ import (
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server"
"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"
@@ -81,7 +81,7 @@ var _ = Describe("[Feature: Basic]", func() {
for _, test := range tests {
framework.NewRequestExpect(f).
RequestModify(framework.SetRequestProtocol(protocol)).
Protocol(protocol).
PortName(test.portName).
Explain(test.proxyName).
Ensure()
@@ -90,6 +90,88 @@ var _ = Describe("[Feature: Basic]", func() {
}
})
Describe("HTTP", func() {
It("proxy to HTTP server", func() {
serverConf := consts.DefaultServerConfig
vhostHTTPPort := f.AllocPort()
serverConf += fmt.Sprintf(`
vhost_http_port = %d
`, vhostHTTPPort)
clientConf := consts.DefaultClientConfig
getProxyConf := func(proxyName string, customDomains string, extra string) string {
return fmt.Sprintf(`
[%s]
type = http
local_port = {{ .%s }}
custom_domains = %s
`+extra, proxyName, framework.HTTPSimpleServerPort, customDomains)
}
tests := []struct {
proxyName string
customDomains string
extraConfig string
}{
{
proxyName: "normal",
},
{
proxyName: "with-encryption",
extraConfig: "use_encryption = true",
},
{
proxyName: "with-compression",
extraConfig: "use_compression = true",
},
{
proxyName: "with-encryption-and-compression",
extraConfig: `
use_encryption = true
use_compression = 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 = 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)
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))
})
})
Describe("STCP && SUDP", func() {
types := []string{"stcp", "sudp"}
for _, t := range types {
@@ -186,12 +268,11 @@ var _ = Describe("[Feature: Basic]", func() {
for _, test := range tests {
framework.NewRequestExpect(f).
RequestModify(framework.SetRequestProtocol(protocol)).
Protocol(protocol).
PortName(test.bindPortName).
Explain(test.proxyName).
ExpectError(test.expectError).
Ensure()
}
})
}
@@ -245,7 +326,7 @@ var _ = Describe("[Feature: Basic]", func() {
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.extraConfig) + "\n"
localServer := server.New(server.TCP, server.WithBindPort(f.AllocPort()), server.WithRespContent([]byte(test.proxyName)))
localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(f.AllocPort()), streamserver.WithRespContent([]byte(test.proxyName)))
f.RunServer(port.GenName(test.proxyName), localServer)
}
@@ -262,13 +343,13 @@ var _ = Describe("[Feature: Basic]", func() {
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.Proxy(proxyURL, "invalid")
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.Proxy(proxyURL, test.proxyName)
r.Addr(test.proxyName).Proxy(proxyURL)
}).ExpectResp([]byte(test.proxyName)).Explain(test.proxyName).Ensure()
}
})

65
test/e2e/basic/chaos.go Normal file
View File

@@ -0,0 +1,65 @@
package basic
import (
"fmt"
"time"
"github.com/fatedier/frp/test/e2e/framework"
. "github.com/onsi/ginkgo"
)
var _ = Describe("[Feature: Chaos]", func() {
f := framework.NewDefaultFramework()
It("reconnect after frps restart", func() {
serverPort := f.AllocPort()
serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[common]
bind_addr = 0.0.0.0
bind_port = %d
`, serverPort))
remotePort := f.AllocPort()
clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[common]
server_port = %d
log_level = trace
[tcp]
type = tcp
local_port = %d
remote_port = %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()
})
})

78
test/e2e/basic/client.go Normal file
View File

@@ -0,0 +1,78 @@
package basic
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client"
. "github.com/onsi/ginkgo"
)
var _ = Describe("[Feature: ClientManage]", func() {
f := framework.NewDefaultFramework()
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(`
admin_port = %d
[p1]
type = tcp
local_port = {{ .%s }}
remote_port = %d
[p2]
type = tcp
local_port = {{ .%s }}
remote_port = %d
[p3]
type = tcp
local_port = {{ .%s }}
remote_port = %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.Index(newClientConf, "[p3]")
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()
})
})

View File

@@ -49,7 +49,7 @@ func defineClientServerTest(desc string, f *framework.Framework, configures *gen
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).PortName(tcpPortName).ExpectError(configures.expectError).Explain("tcp proxy").Ensure()
framework.NewRequestExpect(f).RequestModify(framework.SetRequestProtocol("udp")).
framework.NewRequestExpect(f).Protocol("udp").
PortName(udpPortName).ExpectError(configures.expectError).Explain("udp proxy").Ensure()
})
}

113
test/e2e/basic/cmd.go Normal file
View File

@@ -0,0 +1,113 @@
package basic
import (
"fmt"
"strconv"
"strings"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/pkg/request"
. "github.com/onsi/ginkgo"
)
const (
ConfigValidStr = "syntax is ok"
)
var _ = Describe("[Feature: Cmd]", func() {
f := framework.NewDefaultFramework()
Describe("Verify", func() {
It("frps valid", func() {
path := f.GenerateConfigFile(`
[common]
bind_addr = 0.0.0.0
bind_port = 7000
`)
_, output, err := f.RunFrps("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(strings.Contains(output, ConfigValidStr), "output: %s", output)
})
It("frps invalid", func() {
path := f.GenerateConfigFile(`
[common]
bind_addr = 0.0.0.0
bind_port = 70000
`)
_, output, err := f.RunFrps("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(!strings.Contains(output, ConfigValidStr), "output: %s", output)
})
It("frpc valid", func() {
path := f.GenerateConfigFile(`
[common]
server_addr = 0.0.0.0
server_port = 7000
`)
_, output, err := f.RunFrpc("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(strings.Contains(output, ConfigValidStr), "output: %s", output)
})
It("frpc invalid", func() {
path := f.GenerateConfigFile(`
[common]
server_addr = 0.0.0.0
server_port = 7000
protocol = invalid
`)
_, output, err := f.RunFrpc("verify", "-c", path)
framework.ExpectNoError(err)
framework.ExpectTrue(!strings.Contains(output, ConfigValidStr), "output: %s", output)
})
})
Describe("Single proxy", func() {
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()
})
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()
})
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()
})
})
})

83
test/e2e/basic/config.go Normal file
View File

@@ -0,0 +1,83 @@
package basic
import (
"fmt"
"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/onsi/ginkgo"
)
var _ = Describe("[Feature: Config]", func() {
f := framework.NewDefaultFramework()
Describe("Template", func() {
It("render by env", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
portName := port.GenName("TCP")
serverConf += fmt.Sprintf(`
token = {{ %s{{ .Envs.FRP_TOKEN }}%s }}
`, "`", "`")
clientConf += fmt.Sprintf(`
token = {{ %s{{ .Envs.FRP_TOKEN }}%s }}
[tcp]
type = tcp
local_port = {{ .%s }}
remote_port = {{ .%s }}
`, "`", "`", framework.TCPEchoServerPort, portName)
f.SetEnvs([]string{"FRP_TOKEN=123"})
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).PortName(portName).Ensure()
})
})
Describe("Includes", func() {
It("split tcp proxies into different files", func() {
serverPort := f.AllocPort()
serverConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[common]
bind_addr = 0.0.0.0
bind_port = %d
`, serverPort))
remotePort := f.AllocPort()
proxyConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[tcp]
type = tcp
local_port = %d
remote_port = %d
`, f.PortByName(framework.TCPEchoServerPort), remotePort))
remotePort2 := f.AllocPort()
proxyConfigPath2 := f.GenerateConfigFile(fmt.Sprintf(`
[tcp2]
type = tcp
local_port = %d
remote_port = %d
`, f.PortByName(framework.TCPEchoServerPort), remotePort2))
clientConfigPath := f.GenerateConfigFile(fmt.Sprintf(`
[common]
server_port = %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()
})
})
})

238
test/e2e/basic/group.go Normal file
View File

@@ -0,0 +1,238 @@
package basic
import (
"fmt"
"strconv"
"sync"
"time"
"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/onsi/ginkgo"
)
var _ = 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
}
Describe("Load Balancing", func() {
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(`
[foo]
type = tcp
local_port = %d
remote_port = %d
group = test
group_key = 123
[bar]
type = tcp
local_port = %d
remote_port = %d
group = test
group_key = 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)
})
})
Describe("Health Check", func() {
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(`
[foo]
type = tcp
local_port = %d
remote_port = %d
group = test
group_key = 123
health_check_type = tcp
health_check_interval_s = 1
[bar]
type = tcp
local_port = %d
remote_port = %d
group = test
group_key = 123
health_check_type = tcp
health_check_interval_s = 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"})
})
It("HTTP", func() {
vhostPort := f.AllocPort()
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
vhost_http_port = %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(`
[foo]
type = http
local_port = %d
custom_domains = example.com
group = test
group_key = 123
health_check_type = http
health_check_interval_s = 1
health_check_url = /healthz
[bar]
type = http
local_port = %d
custom_domains = example.com
group = test
group_key = 123
health_check_type = http
health_check_interval_s = 1
health_check_url = /healthz
`, fooPort, barPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// 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"})
})
})
})

326
test/e2e/basic/http.go Normal file
View File

@@ -0,0 +1,326 @@
package basic
import (
"fmt"
"net/http"
"net/url"
"strconv"
"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"
"github.com/fatedier/frp/test/e2e/pkg/utils"
"github.com/gorilla/websocket"
. "github.com/onsi/ginkgo"
)
var _ = Describe("[Feature: HTTP]", func() {
f := framework.NewDefaultFramework()
getDefaultServerConf := func(vhostHTTPPort int) string {
conf := consts.DefaultServerConfig + `
vhost_http_port = %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))),
)
}
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(`
[foo]
type = http
local_port = %d
custom_domains = normal.example.com
locations = /,/foo
[bar]
type = http
local_port = %d
custom_domains = normal.example.com
locations = /bar
`, fooPort, barPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
// foo path
framework.NewRequestExpect(f).Explain("foo path").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPPath("/foo")
}).
ExpectResp([]byte("foo")).
Ensure()
// bar path
framework.NewRequestExpect(f).Explain("bar path").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPPath("/bar")
}).
ExpectResp([]byte("bar")).
Ensure()
// other path
framework.NewRequestExpect(f).Explain("other path").Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com").HTTPPath("/other")
}).
ExpectResp([]byte("foo")).
Ensure()
})
It("HTTP Basic Auth", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
local_port = {{ .%s }}
custom_domains = normal.example.com
http_user = test
http_pwd = 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").HTTPHeaders(map[string]string{
"Authorization": utils.BasicAuth("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").HTTPHeaders(map[string]string{
"Authorization": utils.BasicAuth("test", "test"),
})
}).
Ensure()
})
It("Wildcard domain", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[test]
type = http
local_port = {{ .%s }}
custom_domains = *.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()
})
It("Subdomain", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
serverConf += `
subdomain_host = example.com
`
fooPort := f.AllocPort()
f.RunServer("", newHTTPServer(fooPort, "foo"))
barPort := f.AllocPort()
f.RunServer("", newHTTPServer(barPort, "bar"))
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[foo]
type = http
local_port = %d
subdomain = foo
[bar]
type = http
local_port = %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()
})
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(`
[test]
type = http
local_port = %d
custom_domains = normal.example.com
header_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()
})
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(`
[test]
type = http
local_port = %d
custom_domains = normal.example.com
host_header_rewrite = 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()
})
It("websocket", 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(`
[test]
type = http
local_port = %d
custom_domains = 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))
})
})

View File

@@ -2,11 +2,14 @@ package basic
import (
"fmt"
"net"
"strconv"
"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"
. "github.com/onsi/ginkgo"
)
@@ -19,10 +22,10 @@ var _ = Describe("[Feature: Server Manager]", func() {
clientConf := consts.DefaultClientConfig
serverConf += `
allow_ports = 10000-20000,20002,30000-50000
allow_ports = 20000-25000,25002,30000-50000
`
tcpPortName := port.GenName("TCP", port.WithRangePorts(10000, 20000))
tcpPortName := port.GenName("TCP", port.WithRangePorts(20000, 25000))
udpPortName := port.GenName("UDP", port.WithRangePorts(30000, 50000))
clientConf += fmt.Sprintf(`
[tcp-allowded-in-range]
@@ -62,18 +65,62 @@ var _ = Describe("[Feature: Server Manager]", func() {
framework.NewRequestExpect(f).PortName(tcpPortName).Ensure()
// Not Allowed
framework.NewRequestExpect(f).RequestModify(framework.SetRequestPort(20001)).ExpectError(true).Ensure()
framework.NewRequestExpect(f).Port(25003).ExpectError(true).Ensure()
// Unavailable, already bind by frps
framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure()
// UDP
// Allowed in range
framework.NewRequestExpect(f).RequestModify(framework.SetRequestProtocol("udp")).PortName(udpPortName).Ensure()
framework.NewRequestExpect(f).Protocol("udp").PortName(udpPortName).Ensure()
// Not Allowed
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.UDP().Port(20003)
r.UDP().Port(25003)
}).ExpectError(true).Ensure()
})
It("Alloc Random Port", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
adminPort := f.AllocPort()
clientConf += fmt.Sprintf(`
admin_port = %d
[tcp]
type = tcp
local_port = {{ .%s }}
[udp]
type = udp
local_port = {{ .%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()
})
})

View File

@@ -5,7 +5,6 @@ import (
"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/onsi/ginkgo"
)
@@ -18,17 +17,17 @@ var _ = Describe("[Feature: Example]", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
portName := port.GenName("TCP")
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[tcp]
type = tcp
local_port = {{ .%s }}
remote_port = {{ .%s }}
`, framework.TCPEchoServerPort, portName)
remote_port = %d
`, framework.TCPEchoServerPort, remotePort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).PortName(portName).Ensure()
framework.NewRequestExpect(f).Port(remotePort).Ensure()
})
})
})

View File

@@ -14,6 +14,10 @@ func ExpectEqualValues(actual interface{}, extra interface{}, explain ...interfa
gomega.ExpectWithOffset(1, actual).To(gomega.BeEquivalentTo(extra), explain...)
}
func ExpectEqualValuesWithOffset(offset int, actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1+offset, actual).To(gomega.BeEquivalentTo(extra), explain...)
}
// ExpectNotEqual expects the specified two are not the same, otherwise an exception raises
func ExpectNotEqual(actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).NotTo(gomega.Equal(extra), explain...)
@@ -24,6 +28,10 @@ func ExpectError(err error, explain ...interface{}) {
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...)
}
func ExpectErrorWithOffset(offset int, err error, explain ...interface{}) {
gomega.ExpectWithOffset(1+offset, err).To(gomega.HaveOccurred(), explain...)
}
// ExpectNoError checks if "err" is set, and if so, fails assertion while logging the error.
func ExpectNoError(err error, explain ...interface{}) {
ExpectNoErrorWithOffset(1, err, explain...)
@@ -40,6 +48,14 @@ func ExpectConsistOf(actual interface{}, extra interface{}, explain ...interface
gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...)
}
func ExpectContainElements(actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).To(gomega.ContainElements(extra), explain...)
}
func ExpectNotContainElements(actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).NotTo(gomega.ContainElements(extra), explain...)
}
// ExpectHaveKey expects the actual map has the key in the keyset
func ExpectHaveKey(actual interface{}, key interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).To(gomega.HaveKey(key), explain...)
@@ -53,3 +69,7 @@ func ExpectEmpty(actual interface{}, explain ...interface{}) {
func ExpectTrue(actual interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).Should(gomega.BeTrue(), explain...)
}
func ExpectTrueWithOffset(offset int, actual interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1+offset, actual).Should(gomega.BeTrue(), explain...)
}

View File

@@ -30,6 +30,9 @@ type Framework struct {
// ports used in this framework indexed by port name.
usedPorts map[string]int
// record ports alloced by this framework and release them after each test
allocedPorts []int
// portAllocator to alloc port for this test case.
portAllocator *port.Allocator
@@ -50,7 +53,13 @@ type Framework struct {
clientProcesses []*process.Process
// Manual registered mock servers.
servers []*server.Server
servers []server.Server
// used to generate unique config file name.
configFileIndex int64
// envs used to start processes, the form is `key=value`.
osEnvs []string
}
func NewDefaultFramework() *Framework {
@@ -80,7 +89,7 @@ func (f *Framework) BeforeEach() {
f.cleanupHandle = AddCleanupAction(f.AfterEach)
dir, err := ioutil.TempDir(os.TempDir(), "frpe2e-test-*")
dir, err := ioutil.TempDir(os.TempDir(), "frp-e2e-test-*")
ExpectNoError(err)
f.TempDirectory = dir
@@ -88,6 +97,14 @@ func (f *Framework) BeforeEach() {
if err := f.mockServers.Run(); err != nil {
Failf("%v", err)
}
params := f.mockServers.GetTemplateParams()
for k, v := range params {
switch t := v.(type) {
case int:
f.usedPorts[k] = int(t)
}
}
}
func (f *Framework) AfterEach() {
@@ -126,14 +143,23 @@ func (f *Framework) AfterEach() {
// clean directory
os.RemoveAll(f.TempDirectory)
f.TempDirectory = ""
f.serverConfPaths = nil
f.clientConfPaths = nil
f.serverConfPaths = []string{}
f.clientConfPaths = []string{}
// release used ports
for _, port := range f.usedPorts {
f.portAllocator.Release(port)
}
f.usedPorts = make(map[string]int)
// release alloced ports
for _, port := range f.allocedPorts {
f.portAllocator.Release(port)
}
f.allocedPorts = make([]int, 0)
// clear os envs
f.osEnvs = make([]string, 0)
}
var portRegex = regexp.MustCompile(`{{ \.Port.*? }}`)
@@ -210,6 +236,7 @@ func (f *Framework) PortByName(name string) int {
func (f *Framework) AllocPort() int {
port := f.portAllocator.Get()
ExpectTrue(port > 0, "alloc port failed")
f.allocedPorts = append(f.allocedPorts, port)
return port
}
@@ -217,11 +244,15 @@ func (f *Framework) ReleasePort(port int) {
f.portAllocator.Release(port)
}
func (f *Framework) RunServer(portName string, s *server.Server) {
func (f *Framework) RunServer(portName string, s server.Server) {
f.servers = append(f.servers, s)
if s.BindPort() > 0 {
if s.BindPort() > 0 && portName != "" {
f.usedPorts[portName] = s.BindPort()
}
err := s.Run()
ExpectNoError(err, portName)
ExpectNoError(err, "RunServer: with PortName %s", portName)
}
func (f *Framework) SetEnvs(envs []string) {
f.osEnvs = envs
}

View File

@@ -2,35 +2,45 @@ package framework
import (
"fmt"
"net/http"
"os"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/mock/server"
"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"
)
const (
TCPEchoServerPort = "TCPEchoServerPort"
UDPEchoServerPort = "UDPEchoServerPort"
UDSEchoServerAddr = "UDSEchoServerAddr"
TCPEchoServerPort = "TCPEchoServerPort"
UDPEchoServerPort = "UDPEchoServerPort"
UDSEchoServerAddr = "UDSEchoServerAddr"
HTTPSimpleServerPort = "HTTPSimpleServerPort"
)
type MockServers struct {
tcpEchoServer *server.Server
udpEchoServer *server.Server
udsEchoServer *server.Server
tcpEchoServer server.Server
udpEchoServer server.Server
udsEchoServer server.Server
httpSimpleServer server.Server
}
func NewMockServers(portAllocator *port.Allocator) *MockServers {
s := &MockServers{}
tcpPort := portAllocator.Get()
udpPort := portAllocator.Get()
s.tcpEchoServer = server.New(server.TCP, server.WithBindPort(tcpPort), server.WithEchoMode(true))
s.udpEchoServer = server.New(server.UDP, server.WithBindPort(udpPort), server.WithEchoMode(true))
httpPort := portAllocator.Get()
s.tcpEchoServer = streamserver.New(streamserver.TCP, streamserver.WithBindPort(tcpPort), streamserver.WithEchoMode(true))
s.udpEchoServer = streamserver.New(streamserver.UDP, streamserver.WithBindPort(udpPort), streamserver.WithEchoMode(true))
s.httpSimpleServer = httpserver.New(httpserver.WithBindPort(httpPort), httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte(consts.TestString))
})))
udsIndex := portAllocator.Get()
udsAddr := fmt.Sprintf("%s/frp_echo_server_%d.sock", os.TempDir(), udsIndex)
os.Remove(udsAddr)
s.udsEchoServer = server.New(server.Unix, server.WithBindAddr(udsAddr), server.WithEchoMode(true))
s.udsEchoServer = streamserver.New(streamserver.Unix, streamserver.WithBindAddr(udsAddr), streamserver.WithEchoMode(true))
return s
}
@@ -44,6 +54,9 @@ func (m *MockServers) Run() error {
if err := m.udsEchoServer.Run(); err != nil {
return err
}
if err := m.httpSimpleServer.Run(); err != nil {
return err
}
return nil
}
@@ -51,6 +64,7 @@ func (m *MockServers) Close() {
m.tcpEchoServer.Close()
m.udpEchoServer.Close()
m.udsEchoServer.Close()
m.httpSimpleServer.Close()
os.Remove(m.udsEchoServer.BindAddr())
}
@@ -59,6 +73,7 @@ func (m *MockServers) GetTemplateParams() map[string]interface{} {
ret[TCPEchoServerPort] = m.tcpEchoServer.BindPort()
ret[UDPEchoServerPort] = m.udpEchoServer.BindPort()
ret[UDSEchoServerAddr] = m.udsEchoServer.BindAddr()
ret[HTTPSimpleServerPort] = m.httpSimpleServer.BindPort()
return ret
}

View File

@@ -10,10 +10,6 @@ import (
"github.com/fatedier/frp/test/e2e/pkg/process"
)
func GenerateConfigFile(path string, content string) error {
return ioutil.WriteFile(path, []byte(content), 0666)
}
// RunProcesses run multiple processes from templates.
// The first template should always be frps.
func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) {
@@ -37,7 +33,8 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
err = ioutil.WriteFile(path, []byte(outs[i]), 0666)
ExpectNoError(err)
flog.Trace("[%s] %s", path, outs[i])
p := process.New(TestContext.FRPServerPath, []string{"-c", path})
p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs)
f.serverConfPaths = append(f.serverConfPaths, path)
f.serverProcesses = append(f.serverProcesses, p)
err = p.Start()
@@ -51,7 +48,8 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
err = ioutil.WriteFile(path, []byte(outs[index]), 0666)
ExpectNoError(err)
flog.Trace("[%s] %s", path, outs[index])
p := process.New(TestContext.FRPClientPath, []string{"-c", path})
p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs)
f.clientConfPaths = append(f.clientConfPaths, path)
f.clientProcesses = append(f.clientProcesses, p)
err = p.Start()
@@ -60,3 +58,34 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
}
time.Sleep(500 * time.Millisecond)
}
func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) {
p := process.NewWithEnvs(TestContext.FRPServerPath, args, f.osEnvs)
f.serverProcesses = append(f.serverProcesses, p)
err := p.Start()
if err != nil {
return p, p.StdOutput(), err
}
// sleep for a while to get std output
time.Sleep(500 * time.Millisecond)
return p, p.StdOutput(), nil
}
func (f *Framework) RunFrpc(args ...string) (*process.Process, string, error) {
p := process.NewWithEnvs(TestContext.FRPClientPath, args, f.osEnvs)
f.clientProcesses = append(f.clientProcesses, p)
err := p.Start()
if err != nil {
return p, p.StdOutput(), err
}
time.Sleep(500 * time.Millisecond)
return p, p.StdOutput(), nil
}
func (f *Framework) GenerateConfigFile(content string) string {
f.configFileIndex++
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-config-%d", f.configFileIndex))
err := ioutil.WriteFile(path, []byte(content), 0666)
ExpectNoError(err)
return path
}

View File

@@ -1,38 +1,35 @@
package framework
import (
"bytes"
"net/http"
flog "github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/test/e2e/framework/consts"
"github.com/fatedier/frp/test/e2e/pkg/request"
)
func SetRequestProtocol(protocol string) func(*request.Request) {
return func(r *request.Request) {
r.Protocol(protocol)
func SpecifiedHTTPBodyHandler(body []byte) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
w.Write(body)
}
}
func SetRequestPort(port int) func(*request.Request) {
return func(r *request.Request) {
r.Port(port)
func ExpectResponseCode(code int) EnsureFunc {
return func(resp *request.Response) bool {
return resp.Code == code
}
}
// NewRequest return a default TCP request with default timeout and content.
// NewRequest return a default request with default timeout and content.
func NewRequest() *request.Request {
return request.New().
Timeout(consts.DefaultTimeout).
Body([]byte(consts.TestString))
}
func ExpectResponse(req *request.Request, expectResp []byte, explain ...interface{}) {
ret, err := req.Do()
ExpectNoError(err, explain...)
ExpectEqualValues(expectResp, ret, explain...)
}
func ExpectResponseError(req *request.Request, explain ...interface{}) {
_, err := req.Do()
ExpectError(err, explain...)
func NewHTTPRequest() *request.Request {
return request.New().HTTP().HTTPParams("GET", "", "/", nil)
}
type RequestExpect struct {
@@ -59,6 +56,11 @@ func (e *RequestExpect) RequestModify(f func(r *request.Request)) *RequestExpect
return e
}
func (e *RequestExpect) Protocol(protocol string) *RequestExpect {
e.req.Protocol(protocol)
return e
}
func (e *RequestExpect) PortName(name string) *RequestExpect {
if e.f != nil {
e.req.Port(e.f.PortByName(name))
@@ -66,6 +68,13 @@ func (e *RequestExpect) PortName(name string) *RequestExpect {
return e
}
func (e *RequestExpect) Port(port int) *RequestExpect {
if e.f != nil {
e.req.Port(port)
}
return e
}
func (e *RequestExpect) ExpectResp(resp []byte) *RequestExpect {
e.expectResp = resp
return e
@@ -81,10 +90,32 @@ func (e *RequestExpect) Explain(explain ...interface{}) *RequestExpect {
return e
}
func (e *RequestExpect) Ensure() {
type EnsureFunc func(*request.Response) bool
func (e *RequestExpect) Ensure(fns ...EnsureFunc) {
ret, err := e.req.Do()
if e.expectError {
ExpectResponseError(e.req, e.explain...)
ExpectErrorWithOffset(1, err, e.explain...)
return
}
ExpectNoErrorWithOffset(1, err, e.explain...)
if len(fns) == 0 {
if !bytes.Equal(e.expectResp, ret.Content) {
flog.Trace("Response info: %+v", ret)
}
ExpectEqualValuesWithOffset(1, e.expectResp, ret.Content, e.explain...)
} else {
ExpectResponse(e.req, e.expectResp, e.explain...)
for _, fn := range fns {
ok := fn(ret)
if !ok {
flog.Trace("Response info: %+v", ret)
}
ExpectTrueWithOffset(1, ok, e.explain...)
}
}
}
func (e *RequestExpect) Do() (*request.Response, error) {
return e.req.Do()
}

View File

@@ -9,6 +9,5 @@ import (
var RunID string
func init() {
uuid, _ := uuid.NewUUID()
RunID = uuid.String()
RunID = uuid.NewString()
}

View File

@@ -0,0 +1,86 @@
package httpserver
import (
"fmt"
"net"
"net/http"
"strconv"
)
type Server struct {
bindAddr string
bindPort int
hanlder http.Handler
l net.Listener
hs *http.Server
}
type Option func(*Server) *Server
func New(options ...Option) *Server {
s := &Server{
bindAddr: "127.0.0.1",
}
for _, option := range options {
s = option(s)
}
return s
}
func WithBindAddr(addr string) Option {
return func(s *Server) *Server {
s.bindAddr = addr
return s
}
}
func WithBindPort(port int) Option {
return func(s *Server) *Server {
s.bindPort = port
return s
}
}
func WithHandler(h http.Handler) Option {
return func(s *Server) *Server {
s.hanlder = h
return s
}
}
func (s *Server) Run() error {
if err := s.initListener(); err != nil {
return err
}
addr := net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort))
hs := &http.Server{
Addr: addr,
Handler: s.hanlder,
}
s.hs = hs
go hs.Serve(s.l)
return nil
}
func (s *Server) Close() error {
if s.hs != nil {
return s.hs.Close()
}
return nil
}
func (s *Server) initListener() (err error) {
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
return
}
func (s *Server) BindAddr() string {
return s.bindAddr
}
func (s *Server) BindPort() int {
return s.bindPort
}

View File

@@ -0,0 +1,8 @@
package server
type Server interface {
Run() error
Close() error
BindAddr() string
BindPort() int
}

View File

@@ -1,4 +1,4 @@
package server
package streamserver
import (
"fmt"
@@ -7,16 +7,16 @@ import (
libnet "github.com/fatedier/frp/pkg/util/net"
)
type ServerType string
type Type string
const (
TCP ServerType = "tcp"
UDP ServerType = "udp"
Unix ServerType = "unix"
TCP Type = "tcp"
UDP Type = "udp"
Unix Type = "unix"
)
type Server struct {
netType ServerType
netType Type
bindAddr string
bindPort int
respContent []byte
@@ -29,7 +29,7 @@ type Server struct {
type Option func(*Server) *Server
func New(netType ServerType, options ...Option) *Server {
func New(netType Type, options ...Option) *Server {
s := &Server{
netType: netType,
bindAddr: "127.0.0.1",

View File

@@ -22,6 +22,7 @@ func NewAllocator(from int, to int, mod int, index int) *Allocator {
reserved: sets.NewInt(),
used: sets.NewInt(),
}
for i := from; i <= to; i++ {
if i%mod == index {
pa.reserved.Insert(i)
@@ -50,7 +51,7 @@ func (pa *Allocator) GetByName(portName string) int {
pa.mu.Lock()
defer pa.mu.Unlock()
for i := 0; i < 10; i++ {
for i := 0; i < 20; i++ {
port := pa.getByRange(builder.rangePortFrom, builder.rangePortTo)
if port == 0 {
return 0

View File

@@ -13,11 +13,17 @@ type Process struct {
stdOutput *bytes.Buffer
beforeStopHandler func()
stopped bool
}
func New(path string, params []string) *Process {
return NewWithEnvs(path, params, nil)
}
func NewWithEnvs(path string, params []string, envs []string) *Process {
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, path, params...)
cmd.Env = envs
p := &Process{
cmd: cmd,
cancel: cancel,
@@ -34,6 +40,12 @@ func (p *Process) Start() error {
}
func (p *Process) Stop() error {
if p.stopped {
return nil
}
defer func() {
p.stopped = true
}()
if p.beforeStopHandler != nil {
p.beforeStopHandler()
}

View File

@@ -1,26 +1,44 @@
package request
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"time"
libnet "github.com/fatedier/golib/net"
)
type Request struct {
protocol string
addr string
port int
body []byte
timeout time.Duration
proxyURL string
proxyHost string
protocol string
// for all protocol
addr string
port int
body []byte
timeout time.Duration
// for http
method string
host string
path string
headers map[string]string
proxyURL string
}
func New() *Request {
return &Request{
protocol: "tcp",
addr: "127.0.0.1",
method: "GET",
path: "/",
}
}
@@ -39,9 +57,13 @@ func (r *Request) UDP() *Request {
return r
}
func (r *Request) Proxy(url, host string) *Request {
func (r *Request) HTTP() *Request {
r.protocol = "http"
return r
}
func (r *Request) Proxy(url string) *Request {
r.proxyURL = url
r.proxyHost = host
return r
}
@@ -55,6 +77,29 @@ func (r *Request) Port(port int) *Request {
return r
}
func (r *Request) HTTPParams(method, host, path string, headers map[string]string) *Request {
r.method = method
r.host = host
r.path = path
r.headers = headers
return r
}
func (r *Request) HTTPHost(host string) *Request {
r.host = host
return r
}
func (r *Request) HTTPPath(path string) *Request {
r.path = path
return r
}
func (r *Request) HTTPHeaders(headers map[string]string) *Request {
r.headers = headers
return r
}
func (r *Request) Timeout(timeout time.Duration) *Request {
r.timeout = timeout
return r
@@ -65,28 +110,34 @@ func (r *Request) Body(content []byte) *Request {
return r
}
func (r *Request) Do() ([]byte, error) {
func (r *Request) Do() (*Response, error) {
var (
conn net.Conn
err error
)
addr := net.JoinHostPort(r.addr, strconv.Itoa(r.port))
// for protocol http
if r.protocol == "http" {
return sendHTTPRequest(r.method, fmt.Sprintf("http://%s%s", addr, r.path),
r.host, r.headers, r.proxyURL, r.body)
}
// for protocol tcp and udp
if len(r.proxyURL) > 0 {
if r.protocol != "tcp" {
return nil, fmt.Errorf("only tcp protocol is allowed for proxy")
}
conn, err = libnet.DialTcpByProxy(r.proxyURL, r.proxyHost)
conn, err = libnet.DialTcpByProxy(r.proxyURL, addr)
if err != nil {
return nil, err
}
} else {
if r.addr == "" {
r.addr = fmt.Sprintf("127.0.0.1:%d", r.port)
}
switch r.protocol {
case "tcp":
conn, err = net.Dial("tcp", r.addr)
conn, err = net.Dial("tcp", addr)
case "udp":
conn, err = net.Dial("udp", r.addr)
conn, err = net.Dial("udp", addr)
default:
return nil, fmt.Errorf("invalid protocol")
}
@@ -99,29 +150,63 @@ func (r *Request) Do() ([]byte, error) {
if r.timeout > 0 {
conn.SetDeadline(time.Now().Add(r.timeout))
}
return sendRequestByConn(conn, r.body)
buf, err := sendRequestByConn(conn, r.body)
if err != nil {
return nil, err
}
return &Response{Content: buf}, nil
}
func SendTCPRequest(port int, content []byte, timeout time.Duration) ([]byte, error) {
c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
return nil, fmt.Errorf("connect to tcp server error: %v", err)
}
defer c.Close()
c.SetDeadline(time.Now().Add(timeout))
return sendRequestByConn(c, content)
type Response struct {
Code int
Header http.Header
Content []byte
}
func SendUDPRequest(port int, content []byte, timeout time.Duration) ([]byte, error) {
c, err := net.Dial("udp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
return nil, fmt.Errorf("connect to udp server error: %v", err)
func sendHTTPRequest(method, urlstr string, host string, headers map[string]string, proxy string, body []byte) (*Response, error) {
var inBody io.Reader
if len(body) != 0 {
inBody = bytes.NewReader(body)
}
req, err := http.NewRequest(method, urlstr, inBody)
if err != nil {
return nil, err
}
if host != "" {
req.Host = host
}
for k, v := range headers {
req.Header.Set(k, v)
}
tr := &http.Transport{
DialContext: (&net.Dialer{
Timeout: time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if len(proxy) != 0 {
tr.Proxy = func(req *http.Request) (*url.URL, error) {
return url.Parse(proxy)
}
}
client := http.Client{Transport: tr}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer c.Close()
c.SetDeadline(time.Now().Add(timeout))
return sendRequestByConn(c, content)
ret := &Response{Code: resp.StatusCode, Header: resp.Header}
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
ret.Content = buf
return ret, nil
}
func sendRequestByConn(c net.Conn, content []byte) ([]byte, error) {

View File

@@ -0,0 +1,133 @@
package client
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"strconv"
"strings"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/test/e2e/pkg/utils"
)
type Client struct {
address string
authUser string
authPwd string
}
func New(host string, port int) *Client {
return &Client{
address: net.JoinHostPort(host, strconv.Itoa(port)),
}
}
func (c *Client) SetAuth(user, pwd string) {
c.authUser = user
c.authPwd = pwd
}
func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil)
if err != nil {
return nil, err
}
content, err := c.do(req)
if err != nil {
return nil, err
}
allStatus := &client.StatusResp{}
if err = json.Unmarshal([]byte(content), &allStatus); err != nil {
return nil, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(content))
}
for _, s := range allStatus.TCP {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.UDP {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.HTTP {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.HTTPS {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.STCP {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.XTCP {
if s.Name == name {
return &s, nil
}
}
for _, s := range allStatus.SUDP {
if s.Name == name {
return &s, nil
}
}
return nil, fmt.Errorf("no proxy status found")
}
func (c *Client) Reload() error {
req, err := http.NewRequest("GET", "http://"+c.address+"/api/reload", nil)
if err != nil {
return err
}
_, err = c.do(req)
return err
}
func (c *Client) GetConfig() (string, error) {
req, err := http.NewRequest("GET", "http://"+c.address+"/api/config", nil)
if err != nil {
return "", err
}
return c.do(req)
}
func (c *Client) UpdateConfig(content string) error {
req, err := http.NewRequest("PUT", "http://"+c.address+"/api/config", strings.NewReader(content))
if err != nil {
return err
}
_, err = c.do(req)
return err
}
func (c *Client) setAuthHeader(req *http.Request) {
if c.authUser != "" || c.authPwd != "" {
req.Header.Set("Authorization", utils.BasicAuth(c.authUser, c.authPwd))
}
}
func (c *Client) do(req *http.Request) (string, error) {
c.setAuthHeader(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("api status code [%d]", resp.StatusCode)
}
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(buf), nil
}

View File

@@ -0,0 +1,10 @@
package utils
import (
"encoding/base64"
)
func BasicAuth(username, passwd string) string {
auth := username + ":" + passwd
return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
}

View File

@@ -2,10 +2,12 @@ package plugin
import (
"fmt"
"strconv"
"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"
. "github.com/onsi/ginkgo"
)
@@ -65,12 +67,40 @@ var _ = Describe("[Feature: Client-Plugins]", func() {
f.RunProcesses([]string{serverConf}, []string{clientConf})
for _, test := range tests {
framework.ExpectResponse(
framework.NewRequest().Port(f.PortByName(test.portName)),
[]byte(consts.TestString),
test.proxyName,
)
framework.NewRequestExpect(f).Port(f.PortByName(test.portName)).Ensure()
}
})
})
It("plugin http_proxy", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[tcp]
type = tcp
remote_port = %d
plugin = http_proxy
plugin_http_user = abc
plugin_http_passwd = 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))
})
})
})