diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 18f69d5..b3e2cb4 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -23,7 +23,7 @@ jobs: uses: golangci/golangci-lint-action@v4 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.56 + version: v1.57 # Optional: golangci-lint command line arguments. # args: --issues-exit-code=0 diff --git a/.golangci.yml b/.golangci.yml index cd92e43..e2f8a24 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,5 @@ service: - golangci-lint-version: 1.56.x # use the fixed version to not introduce new linters unexpectedly + golangci-lint-version: 1.57.x # use the fixed version to not introduce new linters unexpectedly run: concurrency: 4 @@ -8,23 +8,6 @@ run: build-tags: - integ - integfuzz - # which dirs to skip: they won't be analyzed; - # can use regexp here: generated.*, regexp is applied on full path; - # default value is empty list, but next dirs are always skipped independently - # from this option's value: - # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - skip-dirs: - - genfiles$ - - vendor$ - - bin$ - - # which files to skip: they will be analyzed, but issues from them - # won't be reported. Default value is empty list, but there is - # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - skip-files: - - ".*\\.pb\\.go" - - ".*\\.gen\\.go" linters: disable-all: true @@ -136,6 +119,14 @@ issues: - unparam text: "is always false" + exclude-dirs: + - genfiles$ + - vendor$ + - bin$ + exclude-files: + - ".*\\.pb\\.go" + - ".*\\.gen\\.go" + # Independently from option `exclude` we use default exclude patterns, # it can be disabled by this option. To list all # excluded by default patterns execute `golangci-lint run --help`. diff --git a/Makefile.cross-compiles b/Makefile.cross-compiles index 1040ef2..8887cad 100644 --- a/Makefile.cross-compiles +++ b/Makefile.cross-compiles @@ -2,7 +2,7 @@ export PATH := $(PATH):`go env GOPATH`/bin export GO111MODULE=on LDFLAGS := -s -w -os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 +os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 android:arm64 all: build diff --git a/README.md b/README.md index 8dbf45a..69cce9d 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ frp also offers a P2P connect mode. * [URL Routing](#url-routing) * [TCP Port Multiplexing](#tcp-port-multiplexing) * [Connecting to frps via PROXY](#connecting-to-frps-via-proxy) + * [Port range mapping](#port-range-mapping) * [Client Plugins](#client-plugins) * [Server Manage Plugins](#server-manage-plugins) * [SSH Tunnel Gateway](#ssh-tunnel-gateway) @@ -1158,6 +1159,24 @@ serverPort = 7000 transport.proxyURL = "http://user:pwd@192.168.1.128:8080" ``` +### Port range mapping + +*Added in v0.56.0* + +We can use the range syntax of Go template combined with the built-in `parseNumberRangePair` function to achieve port range mapping. + +The following example, when run, will create 8 proxies named `test-6000, test-6001 ... test-6007`, each mapping the remote port to the local port. + +``` +{{- range $_, $v := parseNumberRangePair "6000-6006,6007" "6000-6006,6007" }} +[[proxies]] +name = "tcp-{{ $v.First }}" +type = "tcp" +localPort = {{ $v.First }} +remotePort = {{ $v.Second }} +{{- end }} +``` + ### Client Plugins frpc only forwards requests to local TCP or UDP ports by default. diff --git a/Release.md b/Release.md index bf512ca..9682800 100644 --- a/Release.md +++ b/Release.md @@ -1 +1,25 @@ -No feature changes, just a fix for the issue of no released assets in version 0.55.0. +### Features + +* Support range ports mapping in TOML/YAML/JSON configuration file by using go template syntax. + + For example: + + ``` + {{- range $_, $v := parseNumberRangePair "6000-6006,6007" "6000-6006,6007" }} + [[proxies]] + name = "tcp-{{ $v.First }}" + type = "tcp" + localPort = {{ $v.First }} + remotePort = {{ $v.Second }} + {{- end }} + ``` + + This will create 8 proxies such as `tcp-6000, tcp-6001, ... tcp-6007`. + +* Health check supports custom request headers. +* Enable compatibility mode for the Android system to solve the issues of incorrect log time caused by time zone problems and default DNS resolution failures. + +### Fixes + +* Fix the issue of incorrect interval time for rotating the log by day. +* Disable quic-go's ECN support by default. It may cause issues on certain operating systems. diff --git a/client/health/health.go b/client/health/health.go index 7147f77..d298e61 100644 --- a/client/health/health.go +++ b/client/health/health.go @@ -40,8 +40,8 @@ type Monitor struct { addr string // For http - url string - + url string + header http.Header failedTimes uint64 statusOK bool statusNormalFn func() @@ -73,6 +73,11 @@ func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string, } url = s + cfg.Path } + header := make(http.Header) + for _, h := range cfg.HTTPHeaders { + header.Set(h.Name, h.Value) + } + return &Monitor{ checkType: cfg.Type, interval: time.Duration(cfg.IntervalSeconds) * time.Second, @@ -80,6 +85,7 @@ func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string, maxFailedTimes: cfg.MaxFailed, addr: addr, url: url, + header: header, statusOK: false, statusNormalFn: statusNormalFn, statusFailedFn: statusFailedFn, @@ -163,6 +169,8 @@ func (monitor *Monitor) doHTTPCheck(ctx context.Context) error { if err != nil { return err } + req.Header = monitor.header + req.Host = monitor.header.Get("Host") resp, err := http.DefaultClient.Do(req) if err != nil { return err diff --git a/client/service.go b/client/service.go index 25e108f..7a7f6dc 100644 --- a/client/service.go +++ b/client/service.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "net" + "os" "runtime" "sync" "time" @@ -40,6 +41,12 @@ import ( func init() { crypto.DefaultSalt = "frp" + // Disable quic-go's receive buffer warning. + os.Setenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING", "true") + // Disable quic-go's ECN support by default. It may cause issues on certain operating systems. + if os.Getenv("QUIC_GO_DISABLE_ECN") == "" { + os.Setenv("QUIC_GO_DISABLE_ECN", "true") + } } type cancelErr struct { diff --git a/cmd/frpc/main.go b/cmd/frpc/main.go index f9a6c25..f7651bc 100644 --- a/cmd/frpc/main.go +++ b/cmd/frpc/main.go @@ -17,8 +17,10 @@ package main import ( _ "github.com/fatedier/frp/assets/frpc" "github.com/fatedier/frp/cmd/frpc/sub" + "github.com/fatedier/frp/pkg/util/system" ) func main() { + system.EnableCompatibilityMode() sub.Execute() } diff --git a/cmd/frps/main.go b/cmd/frps/main.go index 34676a2..3cb398d 100644 --- a/cmd/frps/main.go +++ b/cmd/frps/main.go @@ -15,13 +15,12 @@ package main import ( - "github.com/fatedier/golib/crypto" - _ "github.com/fatedier/frp/assets/frps" _ "github.com/fatedier/frp/pkg/metrics" + "github.com/fatedier/frp/pkg/util/system" ) func main() { - crypto.DefaultSalt = "frp" + system.EnableCompatibilityMode() Execute() } diff --git a/conf/frpc_full_example.toml b/conf/frpc_full_example.toml index 59de6fa..0528dde 100644 --- a/conf/frpc_full_example.toml +++ b/conf/frpc_full_example.toml @@ -216,6 +216,10 @@ healthCheck.path = "/status" healthCheck.intervalSeconds = 10 healthCheck.maxFailed = 3 healthCheck.timeoutSeconds = 3 +# set health check headers +healthCheck.httpHeaders=[ + { name = "x-from-where", value = "frp" } +] [[proxies]] name = "web02" diff --git a/go.mod b/go.mod index 3d82efe..d820b96 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22 require ( github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/coreos/go-oidc/v3 v3.6.0 - github.com/fatedier/golib v0.4.0 + github.com/fatedier/golib v0.4.2 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 diff --git a/go.sum b/go.sum index 474da7b..b00cb78 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatedier/golib v0.4.0 h1:lafvYRMhFmqrfIUChKy/f5AXqs1eDSk+GAUtLexN5bU= -github.com/fatedier/golib v0.4.0/go.mod h1:gpu+1vXxtJ072NYaNsn/YWgojDL8Ap2kFZQtbzT2qkg= +github.com/fatedier/golib v0.4.2 h1:k+ZBdUFTTipnP1RHfEhGbzyShRdz/rZtFGnjpXG9D9c= +github.com/fatedier/golib v0.4.2/go.mod h1:gpu+1vXxtJ072NYaNsn/YWgojDL8Ap2kFZQtbzT2qkg= github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo= github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= diff --git a/pkg/config/load.go b/pkg/config/load.go index cdbb8e9..f9a705e 100644 --- a/pkg/config/load.go +++ b/pkg/config/load.go @@ -80,7 +80,10 @@ func DetectLegacyINIFormatFromFile(path string) bool { } func RenderWithTemplate(in []byte, values *Values) ([]byte, error) { - tmpl, err := template.New("frp").Parse(string(in)) + tmpl, err := template.New("frp").Funcs(template.FuncMap{ + "parseNumberRange": parseNumberRange, + "parseNumberRangePair": parseNumberRangePair, + }).Parse(string(in)) if err != nil { return nil, err } diff --git a/pkg/config/template.go b/pkg/config/template.go new file mode 100644 index 0000000..44bc456 --- /dev/null +++ b/pkg/config/template.go @@ -0,0 +1,52 @@ +// Copyright 2024 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "fmt" + + "github.com/fatedier/frp/pkg/util/util" +) + +type NumberPair struct { + First int64 + Second int64 +} + +func parseNumberRangePair(firstRangeStr, secondRangeStr string) ([]NumberPair, error) { + firstRangeNumbers, err := util.ParseRangeNumbers(firstRangeStr) + if err != nil { + return nil, err + } + secondRangeNumbers, err := util.ParseRangeNumbers(secondRangeStr) + if err != nil { + return nil, err + } + if len(firstRangeNumbers) != len(secondRangeNumbers) { + return nil, fmt.Errorf("first and second range numbers are not in pairs") + } + pairs := make([]NumberPair, 0, len(firstRangeNumbers)) + for i := 0; i < len(firstRangeNumbers); i++ { + pairs = append(pairs, NumberPair{ + First: firstRangeNumbers[i], + Second: secondRangeNumbers[i], + }) + } + return pairs, nil +} + +func parseNumberRange(firstRangeStr string) ([]int64, error) { + return util.ParseRangeNumbers(firstRangeStr) +} diff --git a/pkg/config/v1/common.go b/pkg/config/v1/common.go index 24ec9b0..ddb2335 100644 --- a/pkg/config/v1/common.go +++ b/pkg/config/v1/common.go @@ -129,3 +129,8 @@ type HTTPPluginOptions struct { type HeaderOperations struct { Set map[string]string `json:"set,omitempty"` } + +type HTTPHeader struct { + Name string `json:"name"` + Value string `json:"value"` +} diff --git a/pkg/config/v1/proxy.go b/pkg/config/v1/proxy.go index c906543..1949cfd 100644 --- a/pkg/config/v1/proxy.go +++ b/pkg/config/v1/proxy.go @@ -97,6 +97,9 @@ type HealthCheckConfig struct { // Path specifies the path to send health checks to if the // health check type is "http". Path string `json:"path,omitempty"` + // HTTPHeaders specifies the headers to send with the health request, if + // the health check type is "http". + HTTPHeaders []HTTPHeader `json:"httpHeaders,omitempty"` } type DomainConfig struct { diff --git a/pkg/nathole/nathole.go b/pkg/nathole/nathole.go index 18cd12b..bdd0ee8 100644 --- a/pkg/nathole/nathole.go +++ b/pkg/nathole/nathole.go @@ -17,7 +17,7 @@ package nathole import ( "context" "fmt" - "math/rand" + "math/rand/v2" "net" "slices" "strconv" @@ -341,7 +341,7 @@ func sendSidMessage( TransactionID: transactionID, Sid: sid, Response: false, - Nonce: strings.Repeat("0", rand.Intn(20)), + Nonce: strings.Repeat("0", rand.IntN(20)), } buf, err := EncodeMessage(m, key) if err != nil { @@ -398,7 +398,7 @@ func sendSidMessageToRandomPorts( used := sets.New[int]() getUnusedPort := func() int { for i := 0; i < 10; i++ { - port := rand.Intn(65535-1024) + 1024 + port := rand.IntN(65535-1024) + 1024 if !used.Has(port) { used.Insert(port) return port diff --git a/pkg/util/net/dns.go b/pkg/util/net/dns.go index 5e1d5cc..441b1e6 100644 --- a/pkg/util/net/dns.go +++ b/pkg/util/net/dns.go @@ -26,8 +26,8 @@ func SetDefaultDNSAddress(dnsAddress string) { // Change default dns server net.DefaultResolver = &net.Resolver{ PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - return net.Dial("udp", dnsAddress) + Dial: func(ctx context.Context, network, _ string) (net.Conn, error) { + return net.Dial(network, dnsAddress) }, } } diff --git a/pkg/util/system/system.go b/pkg/util/system/system.go new file mode 100644 index 0000000..ae40e85 --- /dev/null +++ b/pkg/util/system/system.go @@ -0,0 +1,22 @@ +// Copyright 2024 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !android + +package system + +// EnableCompatibilityMode enables compatibility mode for different system. +// For example, on Android, the inability to obtain the correct time zone will result in incorrect log time output. +func EnableCompatibilityMode() { +} diff --git a/pkg/util/system/system_android.go b/pkg/util/system/system_android.go new file mode 100644 index 0000000..bfcf401 --- /dev/null +++ b/pkg/util/system/system_android.go @@ -0,0 +1,66 @@ +// Copyright 2024 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package system + +import ( + "context" + "net" + "os/exec" + "strings" + "time" +) + +func EnableCompatibilityMode() { + fixTimezone() + fixDNSResolver() +} + +// fixTimezone is used to try our best to fix timezone issue on some Android devices. +func fixTimezone() { + out, err := exec.Command("/system/bin/getprop", "persist.sys.timezone").Output() + if err != nil { + return + } + loc, err := time.LoadLocation(strings.TrimSpace(string(out))) + if err != nil { + return + } + time.Local = loc +} + +// fixDNSResolver will first attempt to resolve google.com to check if the current DNS is available. +// If it is not available, it will default to using 8.8.8.8 as the DNS server. +// This is a workaround for the issue that golang can't get the default DNS servers on Android. +func fixDNSResolver() { + // First, we attempt to resolve a domain. If resolution is successful, no modifications are necessary. + // In real-world scenarios, users may have already configured /etc/resolv.conf, or compiled directly + // in the Android environment instead of using cross-platform compilation, so this issue does not arise. + if net.DefaultResolver != nil { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _, err := net.DefaultResolver.LookupHost(timeoutCtx, "google.com") + if err == nil { + return + } + } + // If the resolution fails, use 8.8.8.8 as the DNS server. + // Note: If there are other methods to obtain the default DNS servers, the default DNS servers should be used preferentially. + net.DefaultResolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, _ string) (net.Conn, error) { + return net.Dial(network, "8.8.8.8:53") + }, + } +} diff --git a/pkg/util/util/util.go b/pkg/util/util/util.go index c7fd997..7758054 100644 --- a/pkg/util/util/util.go +++ b/pkg/util/util/util.go @@ -20,7 +20,7 @@ import ( "crypto/subtle" "encoding/hex" "fmt" - mathrand "math/rand" + mathrand "math/rand/v2" "net" "strconv" "strings" @@ -124,7 +124,7 @@ func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Durati if max <= min { n = min } else { - n = mathrand.Int63n(max-min) + min + n = mathrand.Int64N(max-min) + min } d := duration * time.Duration(n) / time.Duration(1000) time.Sleep(d) diff --git a/pkg/util/version/version.go b/pkg/util/version/version.go index f5bac1b..90af781 100644 --- a/pkg/util/version/version.go +++ b/pkg/util/version/version.go @@ -14,7 +14,7 @@ package version -var version = "0.55.1" +var version = "0.56.0" func Full() string { return version diff --git a/pkg/util/wait/backoff.go b/pkg/util/wait/backoff.go index 16d9dd6..4d01ace 100644 --- a/pkg/util/wait/backoff.go +++ b/pkg/util/wait/backoff.go @@ -15,7 +15,7 @@ package wait import ( - "math/rand" + "math/rand/v2" "time" "github.com/fatedier/frp/pkg/util/util" diff --git a/server/service.go b/server/service.go index 1256432..325f7f6 100644 --- a/server/service.go +++ b/server/service.go @@ -22,9 +22,11 @@ import ( "io" "net" "net/http" + "os" "strconv" "time" + "github.com/fatedier/golib/crypto" "github.com/fatedier/golib/net/mux" fmux "github.com/hashicorp/yamux" quic "github.com/quic-go/quic-go" @@ -59,6 +61,16 @@ const ( vhostReadWriteTimeout time.Duration = 30 * time.Second ) +func init() { + crypto.DefaultSalt = "frp" + // Disable quic-go's receive buffer warning. + os.Setenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING", "true") + // Disable quic-go's ECN support by default. It may cause issues on certain operating systems. + if os.Getenv("QUIC_GO_DISABLE_ECN") == "" { + os.Setenv("QUIC_GO_DISABLE_ECN", "true") + } +} + // Server service type Service struct { // Dispatch connections to different handlers listen on same port diff --git a/test/e2e/framework/client.go b/test/e2e/framework/client.go index 7661435..c13cee1 100644 --- a/test/e2e/framework/client.go +++ b/test/e2e/framework/client.go @@ -1,11 +1,9 @@ package framework -type FRPClient struct { - port int -} +import ( + clientsdk "github.com/fatedier/frp/pkg/sdk/client" +) -func (f *Framework) FRPClient(port int) *FRPClient { - return &FRPClient{ - port: port, - } +func (f *Framework) APIClientForFrpc(port int) *clientsdk.Client { + return clientsdk.New("127.0.0.1", port) } diff --git a/test/e2e/framework/expect.go b/test/e2e/framework/expect.go index dab6da7..3c357bb 100644 --- a/test/e2e/framework/expect.go +++ b/test/e2e/framework/expect.go @@ -43,6 +43,10 @@ func ExpectNoErrorWithOffset(offset int, err error, explain ...interface{}) { gomega.ExpectWithOffset(1+offset, err).NotTo(gomega.HaveOccurred(), explain...) } +func ExpectContainSubstring(actual, substr string, explain ...interface{}) { + gomega.ExpectWithOffset(1, actual).To(gomega.ContainSubstring(substr), explain...) +} + // ExpectConsistOf expects actual contains precisely the extra elements. The ordering of the elements does not matter. func ExpectConsistOf(actual interface{}, extra interface{}, explain ...interface{}) { gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...) diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index e0be5af..527096c 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -217,7 +217,7 @@ func (f *Framework) RenderTemplates(templates []string) (outs []string, ports ma } for _, t := range templates { - tmpl, err := template.New("").Parse(t) + tmpl, err := template.New("frp-e2e").Parse(t) if err != nil { return nil, nil, err } diff --git a/test/e2e/legacy/basic/client.go b/test/e2e/legacy/basic/client.go index d4862e5..98f675b 100644 --- a/test/e2e/legacy/basic/client.go +++ b/test/e2e/legacy/basic/client.go @@ -8,7 +8,6 @@ import ( "github.com/onsi/ginkgo/v2" - clientsdk "github.com/fatedier/frp/pkg/sdk/client" "github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/pkg/request" @@ -54,7 +53,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() { framework.NewRequestExpect(f).Port(p2Port).Ensure() framework.NewRequestExpect(f).Port(p3Port).Ensure() - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) conf, err := client.GetConfig() framework.ExpectNoError(err) @@ -120,7 +119,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() { framework.NewRequestExpect(f).Port(testPort).Ensure() - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) err := client.Stop() framework.ExpectNoError(err) diff --git a/test/e2e/legacy/basic/server.go b/test/e2e/legacy/basic/server.go index ca6717c..62bfd62 100644 --- a/test/e2e/legacy/basic/server.go +++ b/test/e2e/legacy/basic/server.go @@ -7,7 +7,6 @@ import ( "github.com/onsi/ginkgo/v2" - clientsdk "github.com/fatedier/frp/pkg/sdk/client" "github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/pkg/port" @@ -99,7 +98,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { f.RunProcesses([]string{serverConf}, []string{clientConf}) - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) // tcp random port status, err := client.GetProxyStatus("tcp") diff --git a/test/e2e/v1/basic/client.go b/test/e2e/v1/basic/client.go index b0b258d..ef5e262 100644 --- a/test/e2e/v1/basic/client.go +++ b/test/e2e/v1/basic/client.go @@ -8,7 +8,6 @@ import ( "github.com/onsi/ginkgo/v2" - clientsdk "github.com/fatedier/frp/pkg/sdk/client" "github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/pkg/request" @@ -57,7 +56,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() { framework.NewRequestExpect(f).Port(p2Port).Ensure() framework.NewRequestExpect(f).Port(p3Port).Ensure() - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) conf, err := client.GetConfig() framework.ExpectNoError(err) @@ -124,7 +123,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() { framework.NewRequestExpect(f).Port(testPort).Ensure() - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) err := client.Stop() framework.ExpectNoError(err) diff --git a/test/e2e/v1/basic/config.go b/test/e2e/v1/basic/config.go index 686b3c5..7dc3ced 100644 --- a/test/e2e/v1/basic/config.go +++ b/test/e2e/v1/basic/config.go @@ -38,6 +38,51 @@ var _ = ginkgo.Describe("[Feature: Config]", func() { framework.NewRequestExpect(f).PortName(portName).Ensure() }) + + ginkgo.It("Range ports mapping", func() { + serverConf := consts.DefaultServerConfig + clientConf := consts.DefaultClientConfig + + adminPort := f.AllocPort() + + localPortsRange := "13010-13012,13014" + remotePortsRange := "23010-23012,23014" + escapeTemplate := func(s string) string { + return "{{ `" + s + "` }}" + } + clientConf += fmt.Sprintf(` + webServer.port = %d + + %s + [[proxies]] + name = "tcp-%s" + type = "tcp" + localPort = %s + remotePort = %s + %s + `, adminPort, + escapeTemplate(fmt.Sprintf(`{{- range $_, $v := parseNumberRangePair "%s" "%s" }}`, localPortsRange, remotePortsRange)), + escapeTemplate("{{ $v.First }}"), + escapeTemplate("{{ $v.First }}"), + escapeTemplate("{{ $v.Second }}"), + escapeTemplate("{{- end }}"), + ) + + f.RunProcesses([]string{serverConf}, []string{clientConf}) + + client := f.APIClientForFrpc(adminPort) + checkProxyFn := func(name string, localPort, remotePort int) { + status, err := client.GetProxyStatus(name) + framework.ExpectNoError(err) + + framework.ExpectContainSubstring(status.LocalAddr, fmt.Sprintf(":%d", localPort)) + framework.ExpectContainSubstring(status.RemoteAddr, fmt.Sprintf(":%d", remotePort)) + } + checkProxyFn("tcp-13010", 13010, 23010) + checkProxyFn("tcp-13011", 13011, 23011) + checkProxyFn("tcp-13012", 13012, 23012) + checkProxyFn("tcp-13014", 13014, 23014) + }) }) ginkgo.Describe("Includes", func() { diff --git a/test/e2e/v1/basic/server.go b/test/e2e/v1/basic/server.go index f2f4e0a..fe8af4d 100644 --- a/test/e2e/v1/basic/server.go +++ b/test/e2e/v1/basic/server.go @@ -7,7 +7,6 @@ import ( "github.com/onsi/ginkgo/v2" - clientsdk "github.com/fatedier/frp/pkg/sdk/client" "github.com/fatedier/frp/test/e2e/framework" "github.com/fatedier/frp/test/e2e/framework/consts" "github.com/fatedier/frp/test/e2e/pkg/port" @@ -110,7 +109,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() { f.RunProcesses([]string{serverConf}, []string{clientConf}) - client := clientsdk.New("127.0.0.1", adminPort) + client := f.APIClientForFrpc(adminPort) // tcp random port status, err := client.GetProxyStatus("tcp")