mirror of
https://github.com/fatedier/frp.git
synced 2025-06-14 06:08:24 +00:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
de690a55c8 | ||
|
1ced733340 | ||
|
ce366ee17f | ||
|
3128350dd6 | ||
|
3be6efdd28 | ||
|
6cbb26283c | ||
|
75edea3370 | ||
|
e687aef37e | ||
|
a23455a737 |
10
.github/workflows/golangci-lint.yml
vendored
10
.github/workflows/golangci-lint.yml
vendored
@ -20,10 +20,10 @@ jobs:
|
|||||||
go-version: '1.23'
|
go-version: '1.23'
|
||||||
cache: false
|
cache: false
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v4
|
uses: golangci/golangci-lint-action@v8
|
||||||
with:
|
with:
|
||||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
# 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.61
|
version: v2.1
|
||||||
|
|
||||||
# Optional: golangci-lint command line arguments.
|
# Optional: golangci-lint command line arguments.
|
||||||
# args: --issues-exit-code=0
|
# args: --issues-exit-code=0
|
||||||
@ -34,9 +34,3 @@ jobs:
|
|||||||
# Optional: if set to true then the all caching functionality will be complete disabled,
|
# Optional: if set to true then the all caching functionality will be complete disabled,
|
||||||
# takes precedence over all other caching options.
|
# takes precedence over all other caching options.
|
||||||
# skip-cache: true
|
# skip-cache: true
|
||||||
|
|
||||||
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
|
|
||||||
# skip-pkg-cache: true
|
|
||||||
|
|
||||||
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
|
||||||
# skip-build-cache: true
|
|
||||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: "Close stale issues"
|
name: "Close stale issues and PRs"
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "20 0 * * *"
|
- cron: "20 0 * * *"
|
||||||
|
216
.golangci.yml
216
.golangci.yml
@ -1,139 +1,111 @@
|
|||||||
service:
|
version: "2"
|
||||||
golangci-lint-version: 1.61.x # use the fixed version to not introduce new linters unexpectedly
|
|
||||||
|
|
||||||
run:
|
run:
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
|
||||||
timeout: 20m
|
|
||||||
build-tags:
|
build-tags:
|
||||||
- integ
|
- integ
|
||||||
- integfuzz
|
- integfuzz
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- unused
|
- asciicheck
|
||||||
- errcheck
|
|
||||||
- copyloopvar
|
- copyloopvar
|
||||||
|
- errcheck
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofumpt
|
- gosec
|
||||||
- goimports
|
|
||||||
- revive
|
|
||||||
- gosimple
|
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- lll
|
- lll
|
||||||
|
- makezero
|
||||||
- misspell
|
- misspell
|
||||||
- staticcheck
|
|
||||||
- stylecheck
|
|
||||||
- typecheck
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- gci
|
|
||||||
- gosec
|
|
||||||
- asciicheck
|
|
||||||
- prealloc
|
- prealloc
|
||||||
- predeclared
|
- predeclared
|
||||||
- makezero
|
- revive
|
||||||
fast: false
|
- staticcheck
|
||||||
|
- unconvert
|
||||||
linters-settings:
|
- unparam
|
||||||
errcheck:
|
- unused
|
||||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
settings:
|
||||||
# default is false: such cases aren't reported by default.
|
errcheck:
|
||||||
check-type-assertions: false
|
check-type-assertions: false
|
||||||
|
check-blank: false
|
||||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
gocritic:
|
||||||
# default is false: such cases aren't reported by default.
|
disabled-checks:
|
||||||
check-blank: false
|
- exitAfterDefer
|
||||||
govet:
|
gosec:
|
||||||
# report about shadowed variables
|
excludes:
|
||||||
disable:
|
- G401
|
||||||
- shadow
|
- G402
|
||||||
maligned:
|
- G404
|
||||||
# print struct with more effective memory layout or not, false by default
|
- G501
|
||||||
suggest-new: true
|
- G115
|
||||||
misspell:
|
severity: low
|
||||||
# Correct spellings using locale preferences for US or UK.
|
confidence: low
|
||||||
# Default is to use a neutral variety of English.
|
govet:
|
||||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
disable:
|
||||||
locale: US
|
- shadow
|
||||||
ignore-words:
|
lll:
|
||||||
- cancelled
|
line-length: 160
|
||||||
- marshalled
|
tab-width: 1
|
||||||
lll:
|
misspell:
|
||||||
# max line length, lines longer will be reported. Default is 120.
|
locale: US
|
||||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
ignore-rules:
|
||||||
line-length: 160
|
- cancelled
|
||||||
# tab width in spaces. Default to 1.
|
- marshalled
|
||||||
tab-width: 1
|
unparam:
|
||||||
gocritic:
|
check-exported: false
|
||||||
disabled-checks:
|
exclusions:
|
||||||
- exitAfterDefer
|
generated: lax
|
||||||
unused:
|
presets:
|
||||||
check-exported: false
|
- comments
|
||||||
unparam:
|
- common-false-positives
|
||||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
- legacy
|
||||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
- std-error-handling
|
||||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
rules:
|
||||||
# with golangci-lint call it on a directory with the changed file.
|
- linters:
|
||||||
check-exported: false
|
- errcheck
|
||||||
gci:
|
- maligned
|
||||||
sections:
|
path: _test\.go$|^tests/|^samples/
|
||||||
- standard
|
- linters:
|
||||||
- default
|
- revive
|
||||||
- prefix(github.com/fatedier/frp/)
|
- staticcheck
|
||||||
gosec:
|
text: use underscores in Go names
|
||||||
severity: "low"
|
- linters:
|
||||||
confidence: "low"
|
- revive
|
||||||
excludes:
|
text: unused-parameter
|
||||||
- G401
|
- linters:
|
||||||
- G402
|
- unparam
|
||||||
- G404
|
text: is always false
|
||||||
- G501
|
paths:
|
||||||
- G115 # integer overflow conversion
|
- .*\.pb\.go
|
||||||
|
- .*\.gen\.go
|
||||||
|
- genfiles$
|
||||||
|
- vendor$
|
||||||
|
- bin$
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gci
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- default
|
||||||
|
- prefix(github.com/fatedier/frp/)
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- .*\.pb\.go
|
||||||
|
- .*\.gen\.go
|
||||||
|
- genfiles$
|
||||||
|
- vendor$
|
||||||
|
- bin$
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
issues:
|
issues:
|
||||||
# List of regexps of issue texts to exclude, empty list by default.
|
max-issues-per-linter: 0
|
||||||
# But independently from this option we use default exclude patterns,
|
|
||||||
# it can be disabled by `exclude-use-default: false`. To list all
|
|
||||||
# excluded by default patterns execute `golangci-lint run --help`
|
|
||||||
# exclude:
|
|
||||||
# - composite literal uses unkeyed fields
|
|
||||||
|
|
||||||
exclude-rules:
|
|
||||||
# Exclude some linters from running on test files.
|
|
||||||
- path: _test\.go$|^tests/|^samples/
|
|
||||||
linters:
|
|
||||||
- errcheck
|
|
||||||
- maligned
|
|
||||||
- linters:
|
|
||||||
- revive
|
|
||||||
- stylecheck
|
|
||||||
text: "use underscores in Go names"
|
|
||||||
- linters:
|
|
||||||
- revive
|
|
||||||
text: "unused-parameter"
|
|
||||||
- linters:
|
|
||||||
- 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`.
|
|
||||||
# Default value for this option is true.
|
|
||||||
exclude-use-default: true
|
|
||||||
|
|
||||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
|
||||||
max-per-linter: 0
|
|
||||||
|
|
||||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
|
@ -2,7 +2,7 @@ export PATH := $(PATH):`go env GOPATH`/bin
|
|||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
LDFLAGS := -s -w
|
LDFLAGS := -s -w
|
||||||
|
|
||||||
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm:7 linux:arm:5 linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 linux:loong64 android:arm64
|
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 openbsd:amd64 linux:amd64 linux:arm:7 linux:arm:5 linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 linux:loong64 android:arm64
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
|
@ -1025,7 +1025,7 @@ You can get user's real IP from HTTP request headers `X-Forwarded-For`.
|
|||||||
|
|
||||||
#### Proxy Protocol
|
#### Proxy Protocol
|
||||||
|
|
||||||
frp supports Proxy Protocol to send user's real IP to local services. It support all types except UDP.
|
frp supports Proxy Protocol to send user's real IP to local services.
|
||||||
|
|
||||||
Here is an example for https service:
|
Here is an example for https service:
|
||||||
|
|
||||||
@ -1283,9 +1283,7 @@ frp supports feature gates to enable or disable experimental features. This allo
|
|||||||
To enable an experimental feature, add the feature gate to your configuration:
|
To enable an experimental feature, add the feature gate to your configuration:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
featureGates = {
|
featureGates = { VirtualNet = true }
|
||||||
VirtualNet = true
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Feature Lifecycle
|
### Feature Lifecycle
|
||||||
|
10
Release.md
10
Release.md
@ -1,8 +1,4 @@
|
|||||||
### Notes
|
## Features
|
||||||
|
|
||||||
* **Feature Gates Introduced:** This version introduces a new experimental mechanism called Feature Gates. This allows users to enable or disable specific experimental features before they become generally available. Feature gates can be configured in the `featureGates` map within the configuration file.
|
* Support for YAML merge functionality (anchors and references with dot-prefixed fields) in strict configuration mode without requiring `--strict-config=false` parameter.
|
||||||
* **VirtualNet Feature Gate:** The first available feature gate is `VirtualNet`, which enables the experimental Virtual Network functionality (currently in Alpha stage).
|
* Support for proxy protocol in UDP proxies to preserve real client IP addresses.
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **Virtual Network (VirtualNet):** Introduce experimental virtual network capabilities (Alpha). This allows creating a TUN device managed by frp, enabling Layer 3 connectivity between different clients within the frp network. Requires root/admin privileges and is currently supported on Linux and macOS. Configuration is done via the `virtualNet` section and the `virtual_net` plugin. Enable with feature gate `VirtualNet`. **Note: As an Alpha feature, configuration details may change in future releases.**
|
|
@ -165,9 +165,9 @@ func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
|
|||||||
res StatusResp = make(map[string][]ProxyStatusResp)
|
res StatusResp = make(map[string][]ProxyStatusResp)
|
||||||
)
|
)
|
||||||
|
|
||||||
log.Infof("Http request [/api/status]")
|
log.Infof("http request [/api/status]")
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [/api/status]")
|
log.Infof("http response [/api/status]")
|
||||||
buf, _ = json.Marshal(&res)
|
buf, _ = json.Marshal(&res)
|
||||||
_, _ = w.Write(buf)
|
_, _ = w.Write(buf)
|
||||||
}()
|
}()
|
||||||
@ -198,9 +198,9 @@ func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
|
|||||||
func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
log.Infof("Http get request [/api/config]")
|
log.Infof("http get request [/api/config]")
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http get response [/api/config], code [%d]", res.Code)
|
log.Infof("http get response [/api/config], code [%d]", res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
@ -228,9 +228,9 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
|||||||
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
log.Infof("Http put request [/api/config]")
|
log.Infof("http put request [/api/config]")
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http put response [/api/config], code [%d]", res.Code)
|
log.Infof("http put response [/api/config], code [%d]", res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
|
@ -189,7 +189,7 @@ func (ctl *Control) handlePong(m msg.Message) {
|
|||||||
inMsg := m.(*msg.Pong)
|
inMsg := m.(*msg.Pong)
|
||||||
|
|
||||||
if inMsg.Error != "" {
|
if inMsg.Error != "" {
|
||||||
xl.Errorf("Pong message contains error: %s", inMsg.Error)
|
xl.Errorf("pong message contains error: %s", inMsg.Error)
|
||||||
ctl.closeSession()
|
ctl.closeSession()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,11 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
libio "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
libnet "github.com/fatedier/golib/net"
|
libnet "github.com/fatedier/golib/net"
|
||||||
pp "github.com/pires/go-proxyproto"
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config/types"
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
@ -35,6 +33,7 @@ import (
|
|||||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/limit"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
"github.com/fatedier/frp/pkg/vnet"
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
@ -176,24 +175,9 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
}
|
}
|
||||||
|
|
||||||
if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
|
if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
h := &pp.Header{
|
// Use the common proxy protocol builder function
|
||||||
Command: pp.PROXY,
|
header := netpkg.BuildProxyProtocolHeaderStruct(connInfo.SrcAddr, connInfo.DstAddr, baseCfg.Transport.ProxyProtocolVersion)
|
||||||
SourceAddr: connInfo.SrcAddr,
|
connInfo.ProxyProtocolHeader = header
|
||||||
DestinationAddr: connInfo.DstAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(m.SrcAddr, ".") {
|
|
||||||
h.TransportProtocol = pp.TCPv4
|
|
||||||
} else {
|
|
||||||
h.TransportProtocol = pp.TCPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
if baseCfg.Transport.ProxyProtocolVersion == "v1" {
|
|
||||||
h.Version = 1
|
|
||||||
} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
|
|
||||||
h.Version = 2
|
|
||||||
}
|
|
||||||
connInfo.ProxyProtocolHeader = h
|
|
||||||
}
|
}
|
||||||
connInfo.Conn = remote
|
connInfo.Conn = remote
|
||||||
connInfo.UnderlyingConn = workConn
|
connInfo.UnderlyingConn = workConn
|
||||||
|
@ -205,5 +205,5 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
go workConnReaderFn(workConn, readCh)
|
go workConnReaderFn(workConn, readCh)
|
||||||
go heartbeatFn(sendCh)
|
go heartbeatFn(sendCh)
|
||||||
|
|
||||||
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
|
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize), pxy.cfg.Transport.ProxyProtocolVersion)
|
||||||
}
|
}
|
||||||
|
@ -171,5 +171,7 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
|||||||
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||||
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||||
go heartbeatFn(pxy.sendCh)
|
go heartbeatFn(pxy.sendCh)
|
||||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
|
|
||||||
|
// Call Forwarder with proxy protocol version (empty string means no proxy protocol)
|
||||||
|
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize), pxy.cfg.Transport.ProxyProtocolVersion)
|
||||||
}
|
}
|
||||||
|
@ -325,10 +325,9 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
|||||||
proxyCfgs := svr.proxyCfgs
|
proxyCfgs := svr.proxyCfgs
|
||||||
visitorCfgs := svr.visitorCfgs
|
visitorCfgs := svr.visitorCfgs
|
||||||
svr.cfgMu.RUnlock()
|
svr.cfgMu.RUnlock()
|
||||||
connEncrypted := true
|
|
||||||
if svr.clientSpec != nil && svr.clientSpec.Type == "ssh-tunnel" {
|
connEncrypted := svr.clientSpec == nil || svr.clientSpec.Type != "ssh-tunnel"
|
||||||
connEncrypted = false
|
|
||||||
}
|
|
||||||
sessionCtx := &SessionContext{
|
sessionCtx := &SessionContext{
|
||||||
Common: svr.common,
|
Common: svr.common,
|
||||||
RunID: svr.runID,
|
RunID: svr.runID,
|
||||||
@ -341,7 +340,7 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
|||||||
ctl, err := NewControl(svr.ctx, sessionCtx)
|
ctl, err := NewControl(svr.ctx, sessionCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
xl.Errorf("NewControl error: %v", err)
|
xl.Errorf("new control error: %v", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
ctl.SetInWorkConnCallback(svr.handleWorkConnCb)
|
ctl.SetInWorkConnCallback(svr.handleWorkConnCb)
|
||||||
|
@ -121,7 +121,7 @@ Create new proxy
|
|||||||
// http and https only
|
// http and https only
|
||||||
"custom_domains": []<string>,
|
"custom_domains": []<string>,
|
||||||
"subdomain": <string>,
|
"subdomain": <string>,
|
||||||
"locations": <string>,
|
"locations": []<string>,
|
||||||
"http_user": <string>,
|
"http_user": <string>,
|
||||||
"http_pwd": <string>,
|
"http_pwd": <string>,
|
||||||
"host_header_rewrite": <string>,
|
"host_header_rewrite": <string>,
|
||||||
|
@ -49,9 +49,7 @@ type = "virtual_net"
|
|||||||
# frpc.toml (client side)
|
# frpc.toml (client side)
|
||||||
serverAddr = "x.x.x.x"
|
serverAddr = "x.x.x.x"
|
||||||
serverPort = 7000
|
serverPort = 7000
|
||||||
featureGates = {
|
featureGates = { VirtualNet = true }
|
||||||
VirtualNet = true
|
|
||||||
}
|
|
||||||
|
|
||||||
# Configure the virtual network interface
|
# Configure the virtual network interface
|
||||||
virtualNet.address = "100.86.0.2/24"
|
virtualNet.address = "100.86.0.2/24"
|
||||||
|
16
go.mod
16
go.mod
@ -10,8 +10,8 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/hashicorp/yamux v0.1.1
|
github.com/hashicorp/yamux v0.1.1
|
||||||
github.com/onsi/ginkgo/v2 v2.22.0
|
github.com/onsi/ginkgo/v2 v2.23.4
|
||||||
github.com/onsi/gomega v1.34.2
|
github.com/onsi/gomega v1.36.3
|
||||||
github.com/pelletier/go-toml/v2 v2.2.0
|
github.com/pelletier/go-toml/v2 v2.2.0
|
||||||
github.com/pion/stun/v2 v2.0.0
|
github.com/pion/stun/v2 v2.0.0
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
@ -46,12 +46,11 @@ require (
|
|||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20241206021119-61a79c692802 // indirect
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/klauspost/reedsolomon v1.12.0 // indirect
|
github.com/klauspost/reedsolomon v1.12.0 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||||
github.com/pion/logging v0.2.2 // indirect
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
github.com/pion/transport/v2 v2.2.1 // indirect
|
github.com/pion/transport/v2 v2.2.1 // indirect
|
||||||
@ -67,14 +66,15 @@ require (
|
|||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect
|
||||||
golang.org/x/mod v0.22.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/sys v0.32.0 // indirect
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
golang.org/x/tools v0.28.0 // indirect
|
golang.org/x/tools v0.31.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||||
|
32
go.sum
32
go.sum
@ -14,7 +14,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
|
|||||||
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||||
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -50,10 +49,11 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
|||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20241206021119-61a79c692802 h1:US08AXzP0bLurpzFUV3Poa9ZijrRdd1zAIOVtoHEiS8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/pprof v0.0.0-20241206021119-61a79c692802/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||||
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
@ -72,10 +72,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
|
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||||
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||||
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||||
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||||
@ -94,6 +94,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
@ -152,6 +154,8 @@ github.com/xtaci/kcp-go/v5 v5.6.13/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu
|
|||||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
@ -170,8 +174,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
|
|||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -239,8 +243,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
|||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
@ -261,8 +265,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
|||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
SCRIPT=$(readlink -f "$0")
|
SCRIPT=$(readlink -f "$0")
|
||||||
ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
|
ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
|
||||||
|
|
||||||
ginkgo_command=$(which ginkgo 2>/dev/null)
|
# Check if ginkgo is available
|
||||||
if [ -z "$ginkgo_command" ]; then
|
if ! command -v ginkgo >/dev/null 2>&1; then
|
||||||
echo "ginkgo not found, try to install..."
|
echo "ginkgo not found, try to install..."
|
||||||
go install github.com/onsi/ginkgo/v2/ginkgo@v2.17.1
|
go install github.com/onsi/ginkgo/v2/ginkgo@v2.23.4
|
||||||
fi
|
fi
|
||||||
|
|
||||||
debug=false
|
debug=false
|
||||||
|
@ -17,7 +17,7 @@ make -f ./Makefile.cross-compiles
|
|||||||
rm -rf ./release/packages
|
rm -rf ./release/packages
|
||||||
mkdir -p ./release/packages
|
mkdir -p ./release/packages
|
||||||
|
|
||||||
os_all='linux windows darwin freebsd android'
|
os_all='linux windows darwin freebsd openbsd android'
|
||||||
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64 loong64'
|
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64 loong64'
|
||||||
extra_all='_ hf'
|
extra_all='_ hf'
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ func UnmarshalClientConfFromIni(source any) (ClientCommonConf, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
||||||
common.ClientConfig.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
|
common.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
|
||||||
|
|
||||||
return common, nil
|
return common, nil
|
||||||
}
|
}
|
||||||
@ -229,10 +229,7 @@ func LoadAllProxyConfsFromIni(
|
|||||||
startProxy[s] = struct{}{}
|
startProxy[s] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
startAll := true
|
startAll := len(startProxy) == 0
|
||||||
if len(startProxy) > 0 {
|
|
||||||
startAll = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build template sections from range section And append to ini.File.
|
// Build template sections from range section And append to ini.File.
|
||||||
rangeSections := make([]*ini.Section, 0)
|
rangeSections := make([]*ini.Section, 0)
|
||||||
|
@ -26,20 +26,20 @@ import (
|
|||||||
func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConfig {
|
func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConfig {
|
||||||
out := &v1.ClientCommonConfig{}
|
out := &v1.ClientCommonConfig{}
|
||||||
out.User = conf.User
|
out.User = conf.User
|
||||||
out.Auth.Method = v1.AuthMethod(conf.ClientConfig.AuthenticationMethod)
|
out.Auth.Method = v1.AuthMethod(conf.AuthenticationMethod)
|
||||||
out.Auth.Token = conf.ClientConfig.Token
|
out.Auth.Token = conf.Token
|
||||||
if conf.ClientConfig.AuthenticateHeartBeats {
|
if conf.AuthenticateHeartBeats {
|
||||||
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
|
||||||
}
|
}
|
||||||
if conf.ClientConfig.AuthenticateNewWorkConns {
|
if conf.AuthenticateNewWorkConns {
|
||||||
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
|
||||||
}
|
}
|
||||||
out.Auth.OIDC.ClientID = conf.ClientConfig.OidcClientID
|
out.Auth.OIDC.ClientID = conf.OidcClientID
|
||||||
out.Auth.OIDC.ClientSecret = conf.ClientConfig.OidcClientSecret
|
out.Auth.OIDC.ClientSecret = conf.OidcClientSecret
|
||||||
out.Auth.OIDC.Audience = conf.ClientConfig.OidcAudience
|
out.Auth.OIDC.Audience = conf.OidcAudience
|
||||||
out.Auth.OIDC.Scope = conf.ClientConfig.OidcScope
|
out.Auth.OIDC.Scope = conf.OidcScope
|
||||||
out.Auth.OIDC.TokenEndpointURL = conf.ClientConfig.OidcTokenEndpointURL
|
out.Auth.OIDC.TokenEndpointURL = conf.OidcTokenEndpointURL
|
||||||
out.Auth.OIDC.AdditionalEndpointParams = conf.ClientConfig.OidcAdditionalEndpointParams
|
out.Auth.OIDC.AdditionalEndpointParams = conf.OidcAdditionalEndpointParams
|
||||||
|
|
||||||
out.ServerAddr = conf.ServerAddr
|
out.ServerAddr = conf.ServerAddr
|
||||||
out.ServerPort = conf.ServerPort
|
out.ServerPort = conf.ServerPort
|
||||||
@ -59,10 +59,10 @@ func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConf
|
|||||||
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
|
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
|
||||||
out.Transport.TLS.Enable = lo.ToPtr(conf.TLSEnable)
|
out.Transport.TLS.Enable = lo.ToPtr(conf.TLSEnable)
|
||||||
out.Transport.TLS.DisableCustomTLSFirstByte = lo.ToPtr(conf.DisableCustomTLSFirstByte)
|
out.Transport.TLS.DisableCustomTLSFirstByte = lo.ToPtr(conf.DisableCustomTLSFirstByte)
|
||||||
out.Transport.TLS.TLSConfig.CertFile = conf.TLSCertFile
|
out.Transport.TLS.CertFile = conf.TLSCertFile
|
||||||
out.Transport.TLS.TLSConfig.KeyFile = conf.TLSKeyFile
|
out.Transport.TLS.KeyFile = conf.TLSKeyFile
|
||||||
out.Transport.TLS.TLSConfig.TrustedCaFile = conf.TLSTrustedCaFile
|
out.Transport.TLS.TrustedCaFile = conf.TLSTrustedCaFile
|
||||||
out.Transport.TLS.TLSConfig.ServerName = conf.TLSServerName
|
out.Transport.TLS.ServerName = conf.TLSServerName
|
||||||
|
|
||||||
out.Log.To = conf.LogFile
|
out.Log.To = conf.LogFile
|
||||||
out.Log.Level = conf.LogLevel
|
out.Log.Level = conf.LogLevel
|
||||||
@ -87,18 +87,18 @@ func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConf
|
|||||||
|
|
||||||
func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig {
|
func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig {
|
||||||
out := &v1.ServerConfig{}
|
out := &v1.ServerConfig{}
|
||||||
out.Auth.Method = v1.AuthMethod(conf.ServerConfig.AuthenticationMethod)
|
out.Auth.Method = v1.AuthMethod(conf.AuthenticationMethod)
|
||||||
out.Auth.Token = conf.ServerConfig.Token
|
out.Auth.Token = conf.Token
|
||||||
if conf.ServerConfig.AuthenticateHeartBeats {
|
if conf.AuthenticateHeartBeats {
|
||||||
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
|
||||||
}
|
}
|
||||||
if conf.ServerConfig.AuthenticateNewWorkConns {
|
if conf.AuthenticateNewWorkConns {
|
||||||
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
|
||||||
}
|
}
|
||||||
out.Auth.OIDC.Audience = conf.ServerConfig.OidcAudience
|
out.Auth.OIDC.Audience = conf.OidcAudience
|
||||||
out.Auth.OIDC.Issuer = conf.ServerConfig.OidcIssuer
|
out.Auth.OIDC.Issuer = conf.OidcIssuer
|
||||||
out.Auth.OIDC.SkipExpiryCheck = conf.ServerConfig.OidcSkipExpiryCheck
|
out.Auth.OIDC.SkipExpiryCheck = conf.OidcSkipExpiryCheck
|
||||||
out.Auth.OIDC.SkipIssuerCheck = conf.ServerConfig.OidcSkipIssuerCheck
|
out.Auth.OIDC.SkipIssuerCheck = conf.OidcSkipIssuerCheck
|
||||||
|
|
||||||
out.BindAddr = conf.BindAddr
|
out.BindAddr = conf.BindAddr
|
||||||
out.BindPort = conf.BindPort
|
out.BindPort = conf.BindPort
|
||||||
|
@ -206,7 +206,7 @@ func (cfg *BaseProxyConf) decorate(_ string, name string, section *ini.Section)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// plugin_xxx
|
// plugin_xxx
|
||||||
cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
|
cfg.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +111,33 @@ func LoadConfigureFromFile(path string, c any, strict bool) error {
|
|||||||
return LoadConfigure(content, c, strict)
|
return LoadConfigure(content, c, strict)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseYAMLWithDotFieldsHandling parses YAML with dot-prefixed fields handling
|
||||||
|
// This function handles both cases efficiently: with or without dot fields
|
||||||
|
func parseYAMLWithDotFieldsHandling(content []byte, target any) error {
|
||||||
|
var temp any
|
||||||
|
if err := yaml.Unmarshal(content, &temp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove dot fields if it's a map
|
||||||
|
if tempMap, ok := temp.(map[string]any); ok {
|
||||||
|
for key := range tempMap {
|
||||||
|
if strings.HasPrefix(key, ".") {
|
||||||
|
delete(tempMap, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to JSON and decode with strict validation
|
||||||
|
jsonBytes, err := json.Marshal(temp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
return decoder.Decode(target)
|
||||||
|
}
|
||||||
|
|
||||||
// LoadConfigure loads configuration from bytes and unmarshal into c.
|
// LoadConfigure loads configuration from bytes and unmarshal into c.
|
||||||
// Now it supports json, yaml and toml format.
|
// Now it supports json, yaml and toml format.
|
||||||
func LoadConfigure(b []byte, c any, strict bool) error {
|
func LoadConfigure(b []byte, c any, strict bool) error {
|
||||||
@ -134,10 +161,13 @@ func LoadConfigure(b []byte, c any, strict bool) error {
|
|||||||
}
|
}
|
||||||
return decoder.Decode(c)
|
return decoder.Decode(c)
|
||||||
}
|
}
|
||||||
// It wasn't JSON. Unmarshal as YAML.
|
|
||||||
|
// Handle YAML content
|
||||||
if strict {
|
if strict {
|
||||||
return yaml.UnmarshalStrict(b, c)
|
// In strict mode, always use our custom handler to support YAML merge
|
||||||
|
return parseYAMLWithDotFieldsHandling(b, c)
|
||||||
}
|
}
|
||||||
|
// Non-strict mode, parse normally
|
||||||
return yaml.Unmarshal(b, c)
|
return yaml.Unmarshal(b, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,3 +187,122 @@ unixPath = "/tmp/uds.sock"
|
|||||||
err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
|
err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
|
||||||
require.Error(err)
|
require.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestYAMLMergeInStrictMode tests that YAML merge functionality works
|
||||||
|
// even in strict mode by properly handling dot-prefixed fields
|
||||||
|
func TestYAMLMergeInStrictMode(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
yamlContent := `
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
serverPort: 7000
|
||||||
|
|
||||||
|
.common: &common
|
||||||
|
type: stcp
|
||||||
|
secretKey: "test-secret"
|
||||||
|
localIP: 127.0.0.1
|
||||||
|
transport:
|
||||||
|
useEncryption: true
|
||||||
|
useCompression: true
|
||||||
|
|
||||||
|
proxies:
|
||||||
|
- name: ssh
|
||||||
|
localPort: 22
|
||||||
|
<<: *common
|
||||||
|
- name: web
|
||||||
|
localPort: 80
|
||||||
|
<<: *common
|
||||||
|
`
|
||||||
|
|
||||||
|
clientCfg := v1.ClientConfig{}
|
||||||
|
// This should work in strict mode
|
||||||
|
err := LoadConfigure([]byte(yamlContent), &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Verify the merge worked correctly
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Equal(7000, clientCfg.ServerPort)
|
||||||
|
require.Len(clientCfg.Proxies, 2)
|
||||||
|
|
||||||
|
// Check first proxy
|
||||||
|
sshProxy := clientCfg.Proxies[0].ProxyConfigurer
|
||||||
|
require.Equal("ssh", sshProxy.GetBaseConfig().Name)
|
||||||
|
require.Equal("stcp", sshProxy.GetBaseConfig().Type)
|
||||||
|
|
||||||
|
// Check second proxy
|
||||||
|
webProxy := clientCfg.Proxies[1].ProxyConfigurer
|
||||||
|
require.Equal("web", webProxy.GetBaseConfig().Name)
|
||||||
|
require.Equal("stcp", webProxy.GetBaseConfig().Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOptimizedYAMLProcessing tests the optimization logic for YAML processing
|
||||||
|
func TestOptimizedYAMLProcessing(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
yamlWithDotFields := []byte(`
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
.common: &common
|
||||||
|
type: stcp
|
||||||
|
proxies:
|
||||||
|
- name: test
|
||||||
|
<<: *common
|
||||||
|
`)
|
||||||
|
|
||||||
|
yamlWithoutDotFields := []byte(`
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
proxies:
|
||||||
|
- name: test
|
||||||
|
type: tcp
|
||||||
|
localPort: 22
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Test that YAML without dot fields works in strict mode
|
||||||
|
clientCfg := v1.ClientConfig{}
|
||||||
|
err := LoadConfigure(yamlWithoutDotFields, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Len(clientCfg.Proxies, 1)
|
||||||
|
require.Equal("test", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Name)
|
||||||
|
|
||||||
|
// Test that YAML with dot fields still works in strict mode
|
||||||
|
err = LoadConfigure(yamlWithDotFields, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Len(clientCfg.Proxies, 1)
|
||||||
|
require.Equal("test", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Name)
|
||||||
|
require.Equal("stcp", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestYAMLEdgeCases tests edge cases for YAML parsing, including non-map types
|
||||||
|
func TestYAMLEdgeCases(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
// Test array at root (should fail for frp config)
|
||||||
|
arrayYAML := []byte(`
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
`)
|
||||||
|
clientCfg := v1.ClientConfig{}
|
||||||
|
err := LoadConfigure(arrayYAML, &clientCfg, true)
|
||||||
|
require.Error(err) // Should fail because ClientConfig expects an object
|
||||||
|
|
||||||
|
// Test scalar at root (should fail for frp config)
|
||||||
|
scalarYAML := []byte(`"just a string"`)
|
||||||
|
err = LoadConfigure(scalarYAML, &clientCfg, true)
|
||||||
|
require.Error(err) // Should fail because ClientConfig expects an object
|
||||||
|
|
||||||
|
// Test empty object (should work)
|
||||||
|
emptyYAML := []byte(`{}`)
|
||||||
|
err = LoadConfigure(emptyYAML, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Test nested structure without dots (should work)
|
||||||
|
nestedYAML := []byte(`
|
||||||
|
serverAddr: "127.0.0.1"
|
||||||
|
serverPort: 7000
|
||||||
|
`)
|
||||||
|
err = LoadConfigure(nestedYAML, &clientCfg, true)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal("127.0.0.1", clientCfg.ServerAddr)
|
||||||
|
require.Equal(7000, clientCfg.ServerPort)
|
||||||
|
}
|
||||||
|
@ -129,7 +129,7 @@ func (c *ProxyBaseConfig) Complete(namePrefix string) {
|
|||||||
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
|
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
|
||||||
|
|
||||||
if c.Plugin.ClientPluginOptions != nil {
|
if c.Plugin.ClientPluginOptions != nil {
|
||||||
c.Plugin.ClientPluginOptions.Complete()
|
c.Plugin.Complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ func (m *serverMetrics) NewProxy(name string, proxyType string) {
|
|||||||
m.info.ProxyTypeCounts[proxyType] = counter
|
m.info.ProxyTypeCounts[proxyType] = counter
|
||||||
|
|
||||||
proxyStats, ok := m.info.ProxyStatistics[name]
|
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||||
if !(ok && proxyStats.ProxyType == proxyType) {
|
if !ok || proxyStats.ProxyType != proxyType {
|
||||||
proxyStats = &ProxyStatistics{
|
proxyStats = &ProxyStatistics{
|
||||||
Name: name,
|
Name: name,
|
||||||
ProxyType: proxyType,
|
ProxyType: proxyType,
|
||||||
|
@ -18,9 +18,10 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -30,6 +31,8 @@ func init() {
|
|||||||
type VirtualNetPlugin struct {
|
type VirtualNetPlugin struct {
|
||||||
pluginCtx PluginContext
|
pluginCtx PluginContext
|
||||||
opts *v1.VirtualNetPluginOptions
|
opts *v1.VirtualNetPluginOptions
|
||||||
|
mu sync.Mutex
|
||||||
|
conns map[io.ReadWriteCloser]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVirtualNetPlugin(pluginCtx PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
func NewVirtualNetPlugin(pluginCtx PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
@ -43,19 +46,32 @@ func NewVirtualNetPlugin(pluginCtx PluginContext, options v1.ClientPluginOptions
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *VirtualNetPlugin) Handle(ctx context.Context, connInfo *ConnectionInfo) {
|
func (p *VirtualNetPlugin) Handle(ctx context.Context, connInfo *ConnectionInfo) {
|
||||||
xl := xlog.FromContextSafe(ctx)
|
|
||||||
|
|
||||||
// Verify if virtual network controller is available
|
// Verify if virtual network controller is available
|
||||||
if p.pluginCtx.VnetController == nil {
|
if p.pluginCtx.VnetController == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the connection with the controller
|
// Add the connection before starting the read loop to avoid race condition
|
||||||
routeName := p.pluginCtx.Name
|
// where RemoveConn might be called before the connection is added.
|
||||||
err := p.pluginCtx.VnetController.RegisterServerConn(ctx, routeName, connInfo.Conn)
|
p.mu.Lock()
|
||||||
if err != nil {
|
if p.conns == nil {
|
||||||
xl.Errorf("virtual net failed to register server connection: %v", err)
|
p.conns = make(map[io.ReadWriteCloser]struct{})
|
||||||
return
|
}
|
||||||
|
p.conns[connInfo.Conn] = struct{}{}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
// Register the connection with the controller and pass the cleanup function
|
||||||
|
p.pluginCtx.VnetController.StartServerConnReadLoop(ctx, connInfo.Conn, func() {
|
||||||
|
p.RemoveConn(connInfo.Conn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VirtualNetPlugin) RemoveConn(conn io.ReadWriteCloser) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
// Check if the map exists, as Close might have set it to nil concurrently
|
||||||
|
if p.conns != nil {
|
||||||
|
delete(p.conns, conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,8 +80,13 @@ func (p *VirtualNetPlugin) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *VirtualNetPlugin) Close() error {
|
func (p *VirtualNetPlugin) Close() error {
|
||||||
if p.pluginCtx.VnetController != nil {
|
p.mu.Lock()
|
||||||
p.pluginCtx.VnetController.UnregisterServerConn(p.pluginCtx.Name)
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
// Close any remaining connections
|
||||||
|
for conn := range p.conns {
|
||||||
|
_ = conn.Close()
|
||||||
}
|
}
|
||||||
|
p.conns = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ func NewVirtualNetPlugin(pluginCtx PluginContext, options v1.VisitorPluginOption
|
|||||||
return nil, errors.New("destinationIP is required")
|
return nil, errors.New("destinationIP is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse DestinationIP as a single IP and create a host route
|
// Parse DestinationIP and create a host route.
|
||||||
ip := net.ParseIP(opts.DestinationIP)
|
ip := net.ParseIP(opts.DestinationIP)
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return nil, fmt.Errorf("invalid destination IP address [%s]", opts.DestinationIP)
|
return nil, fmt.Errorf("invalid destination IP address [%s]", opts.DestinationIP)
|
||||||
@ -91,7 +91,7 @@ func (p *VirtualNetPlugin) Start() {
|
|||||||
if len(p.routes) > 0 {
|
if len(p.routes) > 0 {
|
||||||
routeStr = p.routes[0].String()
|
routeStr = p.routes[0].String()
|
||||||
}
|
}
|
||||||
xl.Infof("Starting VirtualNetPlugin for visitor [%s], attempting to register routes for %s", p.pluginCtx.Name, routeStr)
|
xl.Infof("starting VirtualNetPlugin for visitor [%s], attempting to register routes for %s", p.pluginCtx.Name, routeStr)
|
||||||
|
|
||||||
go p.run()
|
go p.run()
|
||||||
}
|
}
|
||||||
@ -101,10 +101,8 @@ func (p *VirtualNetPlugin) run() {
|
|||||||
reconnectDelay := 10 * time.Second
|
reconnectDelay := 10 * time.Second
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Create a signal channel for this connection attempt
|
|
||||||
currentCloseSignal := make(chan struct{})
|
currentCloseSignal := make(chan struct{})
|
||||||
|
|
||||||
// Store the signal channel under lock
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
p.closeSignal = currentCloseSignal
|
p.closeSignal = currentCloseSignal
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
@ -112,7 +110,6 @@ func (p *VirtualNetPlugin) run() {
|
|||||||
select {
|
select {
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
xl.Infof("VirtualNetPlugin run loop for visitor [%s] stopping (context cancelled before pipe creation).", p.pluginCtx.Name)
|
xl.Infof("VirtualNetPlugin run loop for visitor [%s] stopping (context cancelled before pipe creation).", p.pluginCtx.Name)
|
||||||
// Ensure controllerConn from previous loop is cleaned up if necessary
|
|
||||||
p.cleanupControllerConn(xl)
|
p.cleanupControllerConn(xl)
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
@ -120,65 +117,43 @@ func (p *VirtualNetPlugin) run() {
|
|||||||
|
|
||||||
controllerConn, pluginConn := net.Pipe()
|
controllerConn, pluginConn := net.Pipe()
|
||||||
|
|
||||||
// Store controllerConn under lock for cleanup purposes
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
p.controllerConn = controllerConn
|
p.controllerConn = controllerConn
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
|
|
||||||
// Wrap pluginConn using CloseNotifyConn
|
|
||||||
pluginNotifyConn := netutil.WrapCloseNotifyConn(pluginConn, func() {
|
pluginNotifyConn := netutil.WrapCloseNotifyConn(pluginConn, func() {
|
||||||
close(currentCloseSignal) // Signal the run loop
|
close(currentCloseSignal) // Signal the run loop on close.
|
||||||
})
|
})
|
||||||
|
|
||||||
xl.Infof("Attempting to register client route for visitor [%s]", p.pluginCtx.Name)
|
xl.Infof("attempting to register client route for visitor [%s]", p.pluginCtx.Name)
|
||||||
err := p.pluginCtx.VnetController.RegisterClientRoute(p.ctx, p.pluginCtx.Name, p.routes, controllerConn)
|
p.pluginCtx.VnetController.RegisterClientRoute(p.ctx, p.pluginCtx.Name, p.routes, controllerConn)
|
||||||
if err != nil {
|
xl.Infof("successfully registered client route for visitor [%s]. Starting connection handler with CloseNotifyConn.", p.pluginCtx.Name)
|
||||||
xl.Errorf("Failed to register client route for visitor [%s]: %v. Retrying after %v", p.pluginCtx.Name, err, reconnectDelay)
|
|
||||||
p.cleanupPipePair(xl, controllerConn, pluginConn) // Close both ends on registration failure
|
|
||||||
|
|
||||||
// Wait before retrying registration, unless context is cancelled
|
|
||||||
select {
|
|
||||||
case <-time.After(reconnectDelay):
|
|
||||||
continue // Retry the loop
|
|
||||||
case <-p.ctx.Done():
|
|
||||||
xl.Infof("VirtualNetPlugin registration retry wait interrupted for visitor [%s]", p.pluginCtx.Name)
|
|
||||||
return // Exit loop if context is cancelled during wait
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xl.Infof("Successfully registered client route for visitor [%s]. Starting connection handler with CloseNotifyConn.", p.pluginCtx.Name)
|
|
||||||
|
|
||||||
// Pass the CloseNotifyConn to HandleConn.
|
// Pass the CloseNotifyConn to HandleConn.
|
||||||
// HandleConn is responsible for calling Close() on pluginNotifyConn.
|
// HandleConn is responsible for calling Close() on pluginNotifyConn.
|
||||||
p.pluginCtx.HandleConn(pluginNotifyConn)
|
p.pluginCtx.HandleConn(pluginNotifyConn)
|
||||||
|
|
||||||
// Wait for either the plugin context to be cancelled or the wrapper's Close() to be called via the signal channel.
|
// Wait for context cancellation or connection close.
|
||||||
select {
|
select {
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
xl.Infof("VirtualNetPlugin run loop stopping for visitor [%s] (context cancelled while waiting).", p.pluginCtx.Name)
|
xl.Infof("VirtualNetPlugin run loop stopping for visitor [%s] (context cancelled while waiting).", p.pluginCtx.Name)
|
||||||
// Context cancelled, ensure controller side is closed if HandleConn didn't close its side yet.
|
|
||||||
p.cleanupControllerConn(xl)
|
p.cleanupControllerConn(xl)
|
||||||
return
|
return
|
||||||
case <-currentCloseSignal:
|
case <-currentCloseSignal:
|
||||||
xl.Infof("Detected connection closed via CloseNotifyConn for visitor [%s].", p.pluginCtx.Name)
|
xl.Infof("detected connection closed via CloseNotifyConn for visitor [%s].", p.pluginCtx.Name)
|
||||||
// HandleConn closed the plugin side (pluginNotifyConn). The closeFn was called, closing currentCloseSignal.
|
// HandleConn closed the plugin side. Close the controller side.
|
||||||
// We still need to close the controller side.
|
|
||||||
p.cleanupControllerConn(xl)
|
p.cleanupControllerConn(xl)
|
||||||
|
|
||||||
// Add a delay before attempting to reconnect, respecting context cancellation.
|
xl.Infof("waiting %v before attempting reconnection for visitor [%s]...", reconnectDelay, p.pluginCtx.Name)
|
||||||
xl.Infof("Waiting %v before attempting reconnection for visitor [%s]...", reconnectDelay, p.pluginCtx.Name)
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(reconnectDelay):
|
case <-time.After(reconnectDelay):
|
||||||
// Delay completed, loop will continue.
|
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
xl.Infof("VirtualNetPlugin reconnection delay interrupted for visitor [%s]", p.pluginCtx.Name)
|
xl.Infof("VirtualNetPlugin reconnection delay interrupted for visitor [%s]", p.pluginCtx.Name)
|
||||||
return // Exit loop if context is cancelled during wait
|
return
|
||||||
}
|
}
|
||||||
// Loop will continue to reconnect.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop will restart, context check at the beginning of the loop is sufficient.
|
xl.Infof("re-establishing virtual connection for visitor [%s]...", p.pluginCtx.Name)
|
||||||
xl.Infof("Re-establishing virtual connection for visitor [%s]...", p.pluginCtx.Name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,46 +162,31 @@ func (p *VirtualNetPlugin) cleanupControllerConn(xl *xlog.Logger) {
|
|||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
if p.controllerConn != nil {
|
if p.controllerConn != nil {
|
||||||
xl.Debugf("Cleaning up controllerConn for visitor [%s]", p.pluginCtx.Name)
|
xl.Debugf("cleaning up controllerConn for visitor [%s]", p.pluginCtx.Name)
|
||||||
p.controllerConn.Close()
|
p.controllerConn.Close()
|
||||||
p.controllerConn = nil
|
p.controllerConn = nil
|
||||||
}
|
}
|
||||||
// Also clear the closeSignal reference for the completed/cancelled connection attempt
|
|
||||||
p.closeSignal = nil
|
p.closeSignal = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanupPipePair closes both ends of a pipe, used typically when registration fails.
|
|
||||||
func (p *VirtualNetPlugin) cleanupPipePair(xl *xlog.Logger, controllerConn, pluginConn net.Conn) {
|
|
||||||
xl.Debugf("Cleaning up pipe pair for visitor [%s] after registration failure", p.pluginCtx.Name)
|
|
||||||
controllerConn.Close()
|
|
||||||
pluginConn.Close()
|
|
||||||
p.mu.Lock()
|
|
||||||
p.controllerConn = nil // Ensure field is nil if it was briefly set
|
|
||||||
p.closeSignal = nil // Ensure field is nil if it was briefly set
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close initiates the plugin shutdown.
|
// Close initiates the plugin shutdown.
|
||||||
func (p *VirtualNetPlugin) Close() error {
|
func (p *VirtualNetPlugin) Close() error {
|
||||||
xl := xlog.FromContextSafe(p.pluginCtx.Ctx) // Use base context for close logging
|
xl := xlog.FromContextSafe(p.pluginCtx.Ctx)
|
||||||
xl.Infof("Closing VirtualNetPlugin for visitor [%s]", p.pluginCtx.Name)
|
xl.Infof("closing VirtualNetPlugin for visitor [%s]", p.pluginCtx.Name)
|
||||||
|
|
||||||
// 1. Signal the run loop goroutine to stop via context cancellation.
|
// Signal the run loop goroutine to stop.
|
||||||
p.cancel()
|
p.cancel()
|
||||||
|
|
||||||
// 2. Unregister the route from the controller.
|
// Unregister the route from the controller.
|
||||||
// This might implicitly cause the VnetController to close its end of the pipe (controllerConn).
|
|
||||||
if p.pluginCtx.VnetController != nil {
|
if p.pluginCtx.VnetController != nil {
|
||||||
p.pluginCtx.VnetController.UnregisterClientRoute(p.pluginCtx.Name)
|
p.pluginCtx.VnetController.UnregisterClientRoute(p.pluginCtx.Name)
|
||||||
xl.Infof("Unregistered client route for visitor [%s]", p.pluginCtx.Name)
|
xl.Infof("unregistered client route for visitor [%s]", p.pluginCtx.Name)
|
||||||
} else {
|
|
||||||
xl.Warnf("VnetController is nil during close for visitor [%s], cannot unregister route", p.pluginCtx.Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Explicitly close the controller side of the pipe managed by this plugin.
|
// Explicitly close the controller side of the pipe.
|
||||||
// This ensures the pipe is broken even if the run loop is stuck or HandleConn hasn't closed its end.
|
// This ensures the pipe is broken even if the run loop is stuck or HandleConn hasn't closed its end.
|
||||||
p.cleanupControllerConn(xl)
|
p.cleanupControllerConn(xl)
|
||||||
xl.Infof("Finished cleaning up connections during close for visitor [%s]", p.pluginCtx.Name)
|
xl.Infof("finished cleaning up connections during close for visitor [%s]", p.pluginCtx.Name)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUDPPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UDPPacket {
|
func NewUDPPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UDPPacket {
|
||||||
@ -69,7 +70,7 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UDPPacket, sendCh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<- msg.Message, bufSize int) {
|
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<- msg.Message, bufSize int, proxyProtocolVersion string) {
|
||||||
var mu sync.RWMutex
|
var mu sync.RWMutex
|
||||||
udpConnMap := make(map[string]*net.UDPConn)
|
udpConnMap := make(map[string]*net.UDPConn)
|
||||||
|
|
||||||
@ -110,6 +111,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<-
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
udpConn, ok := udpConnMap[udpMsg.RemoteAddr.String()]
|
udpConn, ok := udpConnMap[udpMsg.RemoteAddr.String()]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -122,6 +124,18 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<-
|
|||||||
}
|
}
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
|
||||||
|
// Add proxy protocol header if configured
|
||||||
|
if proxyProtocolVersion != "" && udpMsg.RemoteAddr != nil {
|
||||||
|
ppBuf, err := netpkg.BuildProxyProtocolHeader(udpMsg.RemoteAddr, dstAddr, proxyProtocolVersion)
|
||||||
|
if err == nil {
|
||||||
|
// Prepend proxy protocol header to the UDP payload
|
||||||
|
finalBuf := make([]byte, len(ppBuf)+len(buf))
|
||||||
|
copy(finalBuf, ppBuf)
|
||||||
|
copy(finalBuf[len(ppBuf):], buf)
|
||||||
|
buf = finalBuf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_, err = udpConn.Write(buf)
|
_, err = udpConn.Write(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
udpConn.Close()
|
udpConn.Close()
|
||||||
|
@ -3,16 +3,16 @@ package udp
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUdpPacket(t *testing.T) {
|
func TestUdpPacket(t *testing.T) {
|
||||||
assert := assert.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
buf := []byte("hello world")
|
buf := []byte("hello world")
|
||||||
udpMsg := NewUDPPacket(buf, nil, nil)
|
udpMsg := NewUDPPacket(buf, nil, nil)
|
||||||
|
|
||||||
newBuf, err := GetContent(udpMsg)
|
newBuf, err := GetContent(udpMsg)
|
||||||
assert.NoError(err)
|
require.NoError(err)
|
||||||
assert.EqualValues(buf, newBuf)
|
require.EqualValues(buf, newBuf)
|
||||||
}
|
}
|
||||||
|
@ -3,21 +3,21 @@ package metric
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCounter(t *testing.T) {
|
func TestCounter(t *testing.T) {
|
||||||
assert := assert.New(t)
|
require := require.New(t)
|
||||||
c := NewCounter()
|
c := NewCounter()
|
||||||
c.Inc(10)
|
c.Inc(10)
|
||||||
assert.EqualValues(10, c.Count())
|
require.EqualValues(10, c.Count())
|
||||||
|
|
||||||
c.Dec(5)
|
c.Dec(5)
|
||||||
assert.EqualValues(5, c.Count())
|
require.EqualValues(5, c.Count())
|
||||||
|
|
||||||
cTmp := c.Snapshot()
|
cTmp := c.Snapshot()
|
||||||
assert.EqualValues(5, cTmp.Count())
|
require.EqualValues(5, cTmp.Count())
|
||||||
|
|
||||||
c.Clear()
|
c.Clear()
|
||||||
assert.EqualValues(0, c.Count())
|
require.EqualValues(0, c.Count())
|
||||||
}
|
}
|
||||||
|
@ -3,25 +3,25 @@ package metric
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDateCounter(t *testing.T) {
|
func TestDateCounter(t *testing.T) {
|
||||||
assert := assert.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
dc := NewDateCounter(3)
|
dc := NewDateCounter(3)
|
||||||
dc.Inc(10)
|
dc.Inc(10)
|
||||||
assert.EqualValues(10, dc.TodayCount())
|
require.EqualValues(10, dc.TodayCount())
|
||||||
|
|
||||||
dc.Dec(5)
|
dc.Dec(5)
|
||||||
assert.EqualValues(5, dc.TodayCount())
|
require.EqualValues(5, dc.TodayCount())
|
||||||
|
|
||||||
counts := dc.GetLastDaysCount(3)
|
counts := dc.GetLastDaysCount(3)
|
||||||
assert.EqualValues(3, len(counts))
|
require.EqualValues(3, len(counts))
|
||||||
assert.EqualValues(5, counts[0])
|
require.EqualValues(5, counts[0])
|
||||||
assert.EqualValues(0, counts[1])
|
require.EqualValues(0, counts[1])
|
||||||
assert.EqualValues(0, counts[2])
|
require.EqualValues(0, counts[2])
|
||||||
|
|
||||||
dcTmp := dc.Snapshot()
|
dcTmp := dc.Snapshot()
|
||||||
assert.EqualValues(5, dcTmp.TodayCount())
|
require.EqualValues(5, dcTmp.TodayCount())
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ func (conn *wrapQuicStream) RemoteAddr() net.Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (conn *wrapQuicStream) Close() error {
|
func (conn *wrapQuicStream) Close() error {
|
||||||
conn.Stream.CancelRead(0)
|
conn.CancelRead(0)
|
||||||
return conn.Stream.Close()
|
return conn.Stream.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
45
pkg/util/net/proxyprotocol.go
Normal file
45
pkg/util/net/proxyprotocol.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2025 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 net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
pp "github.com/pires/go-proxyproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildProxyProtocolHeaderStruct(srcAddr, dstAddr net.Addr, version string) *pp.Header {
|
||||||
|
var versionByte byte
|
||||||
|
if version == "v1" {
|
||||||
|
versionByte = 1
|
||||||
|
} else {
|
||||||
|
versionByte = 2 // default to v2
|
||||||
|
}
|
||||||
|
return pp.HeaderProxyFromAddrs(versionByte, srcAddr, dstAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildProxyProtocolHeader(srcAddr, dstAddr net.Addr, version string) ([]byte, error) {
|
||||||
|
h := BuildProxyProtocolHeaderStruct(srcAddr, dstAddr, version)
|
||||||
|
|
||||||
|
// Convert header to bytes using a buffer
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err := h.WriteTo(&buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to write proxy protocol header: %v", err)
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
178
pkg/util/net/proxyprotocol_test.go
Normal file
178
pkg/util/net/proxyprotocol_test.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
pp "github.com/pires/go-proxyproto"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildProxyProtocolHeader(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
srcAddr net.Addr
|
||||||
|
dstAddr net.Addr
|
||||||
|
version string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "UDP IPv4 v2",
|
||||||
|
srcAddr: &net.UDPAddr{IP: net.ParseIP("192.168.1.100"), Port: 12345},
|
||||||
|
dstAddr: &net.UDPAddr{IP: net.ParseIP("10.0.0.1"), Port: 3306},
|
||||||
|
version: "v2",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TCP IPv4 v1",
|
||||||
|
srcAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.100"), Port: 12345},
|
||||||
|
dstAddr: &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 80},
|
||||||
|
version: "v1",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UDP IPv6 v2",
|
||||||
|
srcAddr: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 12345},
|
||||||
|
dstAddr: &net.UDPAddr{IP: net.ParseIP("::1"), Port: 3306},
|
||||||
|
version: "v2",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TCP IPv6 v1",
|
||||||
|
srcAddr: &net.TCPAddr{IP: net.ParseIP("::1"), Port: 12345},
|
||||||
|
dstAddr: &net.TCPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80},
|
||||||
|
version: "v1",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil source address",
|
||||||
|
srcAddr: nil,
|
||||||
|
dstAddr: &net.UDPAddr{IP: net.ParseIP("10.0.0.1"), Port: 3306},
|
||||||
|
version: "v2",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil destination address",
|
||||||
|
srcAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.100"), Port: 12345},
|
||||||
|
dstAddr: nil,
|
||||||
|
version: "v2",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported address type",
|
||||||
|
srcAddr: &net.UnixAddr{Name: "/tmp/test.sock", Net: "unix"},
|
||||||
|
dstAddr: &net.UDPAddr{IP: net.ParseIP("10.0.0.1"), Port: 3306},
|
||||||
|
version: "v2",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
header, err := BuildProxyProtocolHeader(tt.srcAddr, tt.dstAddr, tt.version)
|
||||||
|
|
||||||
|
if tt.expectError {
|
||||||
|
require.Error(err, "test case: %s", tt.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err, "test case: %s", tt.name)
|
||||||
|
require.NotEmpty(header, "test case: %s", tt.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildProxyProtocolHeaderStruct(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
srcAddr net.Addr
|
||||||
|
dstAddr net.Addr
|
||||||
|
version string
|
||||||
|
expectedProtocol pp.AddressFamilyAndProtocol
|
||||||
|
expectedVersion byte
|
||||||
|
expectedCommand pp.ProtocolVersionAndCommand
|
||||||
|
expectedSourceAddr net.Addr
|
||||||
|
expectedDestAddr net.Addr
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "TCP IPv4 v2",
|
||||||
|
srcAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.100"), Port: 12345},
|
||||||
|
dstAddr: &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 80},
|
||||||
|
version: "v2",
|
||||||
|
expectedProtocol: pp.TCPv4,
|
||||||
|
expectedVersion: 2,
|
||||||
|
expectedCommand: pp.PROXY,
|
||||||
|
expectedSourceAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.100"), Port: 12345},
|
||||||
|
expectedDestAddr: &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 80},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UDP IPv6 v1",
|
||||||
|
srcAddr: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 12345},
|
||||||
|
dstAddr: &net.UDPAddr{IP: net.ParseIP("::1"), Port: 3306},
|
||||||
|
version: "v1",
|
||||||
|
expectedProtocol: pp.UDPv6,
|
||||||
|
expectedVersion: 1,
|
||||||
|
expectedCommand: pp.PROXY,
|
||||||
|
expectedSourceAddr: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 12345},
|
||||||
|
expectedDestAddr: &net.UDPAddr{IP: net.ParseIP("::1"), Port: 3306},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TCP IPv6 default version",
|
||||||
|
srcAddr: &net.TCPAddr{IP: net.ParseIP("::1"), Port: 12345},
|
||||||
|
dstAddr: &net.TCPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80},
|
||||||
|
version: "",
|
||||||
|
expectedProtocol: pp.TCPv6,
|
||||||
|
expectedVersion: 2, // default to v2
|
||||||
|
expectedCommand: pp.PROXY,
|
||||||
|
expectedSourceAddr: &net.TCPAddr{IP: net.ParseIP("::1"), Port: 12345},
|
||||||
|
expectedDestAddr: &net.TCPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil source address",
|
||||||
|
srcAddr: nil,
|
||||||
|
dstAddr: &net.UDPAddr{IP: net.ParseIP("10.0.0.1"), Port: 3306},
|
||||||
|
version: "v2",
|
||||||
|
expectedProtocol: pp.UNSPEC,
|
||||||
|
expectedVersion: 2,
|
||||||
|
expectedCommand: pp.LOCAL,
|
||||||
|
expectedSourceAddr: nil, // go-proxyproto sets both to nil when srcAddr is nil
|
||||||
|
expectedDestAddr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil destination address",
|
||||||
|
srcAddr: &net.TCPAddr{IP: net.ParseIP("192.168.1.100"), Port: 12345},
|
||||||
|
dstAddr: nil,
|
||||||
|
version: "v2",
|
||||||
|
expectedProtocol: pp.UNSPEC,
|
||||||
|
expectedVersion: 2,
|
||||||
|
expectedCommand: pp.LOCAL,
|
||||||
|
expectedSourceAddr: nil, // go-proxyproto sets both to nil when dstAddr is nil
|
||||||
|
expectedDestAddr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsupported address type",
|
||||||
|
srcAddr: &net.UnixAddr{Name: "/tmp/test.sock", Net: "unix"},
|
||||||
|
dstAddr: &net.UDPAddr{IP: net.ParseIP("10.0.0.1"), Port: 3306},
|
||||||
|
version: "v2",
|
||||||
|
expectedProtocol: pp.UNSPEC,
|
||||||
|
expectedVersion: 2,
|
||||||
|
expectedCommand: pp.LOCAL,
|
||||||
|
expectedSourceAddr: nil, // go-proxyproto sets both to nil for unsupported types
|
||||||
|
expectedDestAddr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
header := BuildProxyProtocolHeaderStruct(tt.srcAddr, tt.dstAddr, tt.version)
|
||||||
|
|
||||||
|
require.NotNil(header, "test case: %s", tt.name)
|
||||||
|
|
||||||
|
require.Equal(tt.expectedCommand, header.Command, "test case: %s", tt.name)
|
||||||
|
require.Equal(tt.expectedSourceAddr, header.SourceAddr, "test case: %s", tt.name)
|
||||||
|
require.Equal(tt.expectedDestAddr, header.DestinationAddr, "test case: %s", tt.name)
|
||||||
|
require.Equal(tt.expectedProtocol, header.TransportProtocol, "test case: %s", tt.name)
|
||||||
|
require.Equal(tt.expectedVersion, header.Version, "test case: %s", tt.name)
|
||||||
|
}
|
||||||
|
}
|
@ -3,45 +3,41 @@ package util
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRandId(t *testing.T) {
|
func TestRandId(t *testing.T) {
|
||||||
assert := assert.New(t)
|
require := require.New(t)
|
||||||
id, err := RandID()
|
id, err := RandID()
|
||||||
assert.NoError(err)
|
require.NoError(err)
|
||||||
t.Log(id)
|
t.Log(id)
|
||||||
assert.Equal(16, len(id))
|
require.Equal(16, len(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAuthKey(t *testing.T) {
|
func TestGetAuthKey(t *testing.T) {
|
||||||
assert := assert.New(t)
|
require := require.New(t)
|
||||||
key := GetAuthKey("1234", 1488720000)
|
key := GetAuthKey("1234", 1488720000)
|
||||||
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
|
require.Equal("6df41a43725f0c770fd56379e12acf8c", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseRangeNumbers(t *testing.T) {
|
func TestParseRangeNumbers(t *testing.T) {
|
||||||
assert := assert.New(t)
|
require := require.New(t)
|
||||||
numbers, err := ParseRangeNumbers("2-5")
|
numbers, err := ParseRangeNumbers("2-5")
|
||||||
if assert.NoError(err) {
|
require.NoError(err)
|
||||||
assert.Equal([]int64{2, 3, 4, 5}, numbers)
|
require.Equal([]int64{2, 3, 4, 5}, numbers)
|
||||||
}
|
|
||||||
|
|
||||||
numbers, err = ParseRangeNumbers("1")
|
numbers, err = ParseRangeNumbers("1")
|
||||||
if assert.NoError(err) {
|
require.NoError(err)
|
||||||
assert.Equal([]int64{1}, numbers)
|
require.Equal([]int64{1}, numbers)
|
||||||
}
|
|
||||||
|
|
||||||
numbers, err = ParseRangeNumbers("3-5,8")
|
numbers, err = ParseRangeNumbers("3-5,8")
|
||||||
if assert.NoError(err) {
|
require.NoError(err)
|
||||||
assert.Equal([]int64{3, 4, 5, 8}, numbers)
|
require.Equal([]int64{3, 4, 5, 8}, numbers)
|
||||||
}
|
|
||||||
|
|
||||||
numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ")
|
numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ")
|
||||||
if assert.NoError(err) {
|
require.NoError(err)
|
||||||
assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers)
|
require.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers)
|
||||||
}
|
|
||||||
|
|
||||||
_, err = ParseRangeNumbers("3-a")
|
_, err = ParseRangeNumbers("3-a")
|
||||||
assert.Error(err)
|
require.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
var version = "0.62.0"
|
var version = "0.63.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
@ -162,7 +162,7 @@ func (rp *HTTPReverseProxy) UnRegister(routeCfg RouteConfig) {
|
|||||||
func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser string) *RouteConfig {
|
func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser string) *RouteConfig {
|
||||||
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
|
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
|
||||||
if ok {
|
if ok {
|
||||||
log.Debugf("get new HTTP request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser)
|
log.Debugf("get new http request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser)
|
||||||
return vr.payload.(*RouteConfig)
|
return vr.payload.(*RouteConfig)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -225,11 +225,7 @@ func (rp *HTTPReverseProxy) getVhost(domain, location, routeByHTTPUser string) (
|
|||||||
// *.example.com
|
// *.example.com
|
||||||
// *.com
|
// *.com
|
||||||
domainSplit := strings.Split(domain, ".")
|
domainSplit := strings.Split(domain, ".")
|
||||||
for {
|
for len(domainSplit) >= 3 {
|
||||||
if len(domainSplit) < 3 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
domainSplit[0] = "*"
|
domainSplit[0] = "*"
|
||||||
domain = strings.Join(domainSplit, ".")
|
domain = strings.Join(domainSplit, ".")
|
||||||
vr, ok = findRouter(domain, location, routeByHTTPUser)
|
vr, ok = findRouter(domain, location, routeByHTTPUser)
|
||||||
|
@ -169,11 +169,7 @@ func (v *Muxer) getListener(name, path, httpUser string) (*Listener, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
domainSplit := strings.Split(name, ".")
|
domainSplit := strings.Split(name, ".")
|
||||||
for {
|
for len(domainSplit) >= 3 {
|
||||||
if len(domainSplit) < 3 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
domainSplit[0] = "*"
|
domainSplit[0] = "*"
|
||||||
name = strings.Join(domainSplit, ".")
|
name = strings.Join(domainSplit, ".")
|
||||||
|
|
||||||
@ -275,7 +271,7 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
xl := xlog.FromContextSafe(l.ctx)
|
xl := xlog.FromContextSafe(l.ctx)
|
||||||
conn, ok := <-l.accept
|
conn, ok := <-l.accept
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("Listener closed")
|
return nil, fmt.Errorf("listener closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if rewriteHost func is exist
|
// if rewriteHost func is exist
|
||||||
|
@ -87,7 +87,7 @@ func (c *Controller) handlePacket(buf []byte) {
|
|||||||
case waterutil.IsIPv4(buf):
|
case waterutil.IsIPv4(buf):
|
||||||
header, err := ipv4.ParseHeader(buf)
|
header, err := ipv4.ParseHeader(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("parse ipv4 header error:", err)
|
log.Warnf("parse ipv4 header error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
src = header.Src
|
src = header.Src
|
||||||
@ -98,7 +98,7 @@ func (c *Controller) handlePacket(buf []byte) {
|
|||||||
case waterutil.IsIPv6(buf):
|
case waterutil.IsIPv6(buf):
|
||||||
header, err := ipv6.ParseHeader(buf)
|
header, err := ipv6.ParseHeader(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("parse ipv6 header error:", err)
|
log.Warnf("parse ipv6 header error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
src = header.Src
|
src = header.Src
|
||||||
@ -137,6 +137,12 @@ func (c *Controller) Stop() error {
|
|||||||
// Client connection read loop
|
// Client connection read loop
|
||||||
func (c *Controller) readLoopClient(ctx context.Context, conn io.ReadWriteCloser) {
|
func (c *Controller) readLoopClient(ctx context.Context, conn io.ReadWriteCloser) {
|
||||||
xl := xlog.FromContextSafe(ctx)
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
defer func() {
|
||||||
|
// Remove the route when read loop ends (connection closed)
|
||||||
|
c.clientRouter.removeConnRoute(conn)
|
||||||
|
conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
data, err := ReadMessage(conn)
|
data, err := ReadMessage(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -181,8 +187,18 @@ func (c *Controller) readLoopClient(ctx context.Context, conn io.ReadWriteCloser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Server connection read loop
|
// Server connection read loop
|
||||||
func (c *Controller) readLoopServer(ctx context.Context, conn io.ReadWriteCloser) {
|
func (c *Controller) readLoopServer(ctx context.Context, conn io.ReadWriteCloser, onClose func()) {
|
||||||
xl := xlog.FromContextSafe(ctx)
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
defer func() {
|
||||||
|
// Clean up all IP mappings associated with this connection when it closes
|
||||||
|
c.serverRouter.cleanupConnIPs(conn)
|
||||||
|
// Call the provided callback upon closure
|
||||||
|
if onClose != nil {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
data, err := ReadMessage(conn)
|
data, err := ReadMessage(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -220,27 +236,11 @@ func (c *Controller) readLoopServer(ctx context.Context, conn io.ReadWriteCloser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterClientRoute Register client route (based on destination IP CIDR)
|
// RegisterClientRoute registers a client route (based on destination IP CIDR)
|
||||||
func (c *Controller) RegisterClientRoute(ctx context.Context, name string, routes []net.IPNet, conn io.ReadWriteCloser) error {
|
// and starts the read loop
|
||||||
if err := c.clientRouter.addRoute(name, routes, conn); err != nil {
|
func (c *Controller) RegisterClientRoute(ctx context.Context, name string, routes []net.IPNet, conn io.ReadWriteCloser) {
|
||||||
return err
|
c.clientRouter.addRoute(name, routes, conn)
|
||||||
}
|
|
||||||
go c.readLoopClient(ctx, conn)
|
go c.readLoopClient(ctx, conn)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterServerConn Register server connection (dynamically associates with source IPs)
|
|
||||||
func (c *Controller) RegisterServerConn(ctx context.Context, name string, conn io.ReadWriteCloser) error {
|
|
||||||
if err := c.serverRouter.addConn(name, conn); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go c.readLoopServer(ctx, conn)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnregisterServerConn Remove server connection from routing table
|
|
||||||
func (c *Controller) UnregisterServerConn(name string) {
|
|
||||||
c.serverRouter.delConn(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnregisterClientRoute Remove client route from routing table
|
// UnregisterClientRoute Remove client route from routing table
|
||||||
@ -248,6 +248,12 @@ func (c *Controller) UnregisterClientRoute(name string) {
|
|||||||
c.clientRouter.delRoute(name)
|
c.clientRouter.delRoute(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartServerConnReadLoop starts the read loop for a server connection
|
||||||
|
// (dynamically associates with source IPs)
|
||||||
|
func (c *Controller) StartServerConnReadLoop(ctx context.Context, conn io.ReadWriteCloser, onClose func()) {
|
||||||
|
go c.readLoopServer(ctx, conn, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
// ParseRoutes Convert route strings to IPNet objects
|
// ParseRoutes Convert route strings to IPNet objects
|
||||||
func ParseRoutes(routeStrings []string) ([]net.IPNet, error) {
|
func ParseRoutes(routeStrings []string) ([]net.IPNet, error) {
|
||||||
routes := make([]net.IPNet, 0, len(routeStrings))
|
routes := make([]net.IPNet, 0, len(routeStrings))
|
||||||
@ -273,7 +279,7 @@ func newClientRouter() *clientRouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *clientRouter) addRoute(name string, routes []net.IPNet, conn io.ReadWriteCloser) error {
|
func (r *clientRouter) addRoute(name string, routes []net.IPNet, conn io.ReadWriteCloser) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
r.routes[name] = &routeElement{
|
r.routes[name] = &routeElement{
|
||||||
@ -281,7 +287,6 @@ func (r *clientRouter) addRoute(name string, routes []net.IPNet, conn io.ReadWri
|
|||||||
routes: routes,
|
routes: routes,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *clientRouter) findConn(dst net.IP) (io.Writer, error) {
|
func (r *clientRouter) findConn(dst net.IP) (io.Writer, error) {
|
||||||
@ -303,32 +308,29 @@ func (r *clientRouter) delRoute(name string) {
|
|||||||
delete(r.routes, name)
|
delete(r.routes, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server router (based on source IP routing)
|
func (r *clientRouter) removeConnRoute(conn io.Writer) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
for name, re := range r.routes {
|
||||||
|
if re.conn == conn {
|
||||||
|
delete(r.routes, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server router (based solely on source IP routing)
|
||||||
type serverRouter struct {
|
type serverRouter struct {
|
||||||
namedConns map[string]io.ReadWriteCloser // Name to connection mapping
|
srcIPConns map[string]io.Writer // Source IP string to connection mapping
|
||||||
srcIPConns map[string]io.Writer // Source IP string to connection mapping
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServerRouter() *serverRouter {
|
func newServerRouter() *serverRouter {
|
||||||
return &serverRouter{
|
return &serverRouter{
|
||||||
namedConns: make(map[string]io.ReadWriteCloser),
|
|
||||||
srcIPConns: make(map[string]io.Writer),
|
srcIPConns: make(map[string]io.Writer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *serverRouter) addConn(name string, conn io.ReadWriteCloser) error {
|
|
||||||
r.mu.Lock()
|
|
||||||
original, ok := r.namedConns[name]
|
|
||||||
r.namedConns[name] = conn
|
|
||||||
r.mu.Unlock()
|
|
||||||
if ok {
|
|
||||||
// Close the original connection if it exists
|
|
||||||
_ = original.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *serverRouter) findConnBySrc(src net.IP) (io.Writer, error) {
|
func (r *serverRouter) findConnBySrc(src net.IP) (io.Writer, error) {
|
||||||
r.mu.RLock()
|
r.mu.RLock()
|
||||||
defer r.mu.RUnlock()
|
defer r.mu.RUnlock()
|
||||||
@ -340,17 +342,41 @@ func (r *serverRouter) findConnBySrc(src net.IP) (io.Writer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *serverRouter) registerSrcIP(src net.IP, conn io.Writer) {
|
func (r *serverRouter) registerSrcIP(src net.IP, conn io.Writer) {
|
||||||
|
key := src.String()
|
||||||
|
|
||||||
|
r.mu.RLock()
|
||||||
|
existingConn, ok := r.srcIPConns[key]
|
||||||
|
r.mu.RUnlock()
|
||||||
|
|
||||||
|
// If the entry exists and the connection is the same, no need to do anything.
|
||||||
|
if ok && existingConn == conn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire write lock to update the map.
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
r.srcIPConns[src.String()] = conn
|
|
||||||
|
// Double-check after acquiring the write lock to handle potential race conditions.
|
||||||
|
existingConn, ok = r.srcIPConns[key]
|
||||||
|
if ok && existingConn == conn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.srcIPConns[key] = conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *serverRouter) delConn(name string) {
|
// cleanupConnIPs removes all IP mappings associated with the specified connection
|
||||||
|
func (r *serverRouter) cleanupConnIPs(conn io.Writer) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
delete(r.namedConns, name)
|
|
||||||
// Note: We don't delete mappings from srcIPConns because we don't know which source IPs are associated with this connection
|
// Find and delete all IP mappings pointing to this connection
|
||||||
// This might cause dangling references, but they will be overwritten on new connections or restart
|
for ip, mappedConn := range r.srcIPConns {
|
||||||
|
if mappedConn == conn {
|
||||||
|
delete(r.srcIPConns, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type routeElement struct {
|
type routeElement struct {
|
||||||
|
@ -33,7 +33,7 @@ func ReadMessage(r io.Reader) ([]byte, error) {
|
|||||||
var length uint32
|
var length uint32
|
||||||
err := binary.Read(r, binary.LittleEndian, &length)
|
err := binary.Read(r, binary.LittleEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read message length error: %v", err)
|
return nil, fmt.Errorf("read message length error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check length to prevent DoS
|
// Check length to prevent DoS
|
||||||
@ -48,7 +48,7 @@ func ReadMessage(r io.Reader) ([]byte, error) {
|
|||||||
data := make([]byte, length)
|
data := make([]byte, length)
|
||||||
_, err = io.ReadFull(r, data)
|
_, err = io.ReadFull(r, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read message data error: %v", err)
|
return nil, fmt.Errorf("read message data error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
@ -68,13 +68,13 @@ func WriteMessage(w io.Writer, data []byte) error {
|
|||||||
// Write length
|
// Write length
|
||||||
err := binary.Write(w, binary.LittleEndian, length)
|
err := binary.Write(w, binary.LittleEndian, length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("write message length error: %v", err)
|
return fmt.Errorf("write message length error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write message data
|
// Write message data
|
||||||
_, err = w.Write(data)
|
_, err = w.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("write message data error: %v", err)
|
return fmt.Errorf("write message data error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -23,7 +23,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
offset = 16
|
offset = 16
|
||||||
|
defaultPacketSize = 1420
|
||||||
)
|
)
|
||||||
|
|
||||||
type TunDevice interface {
|
type TunDevice interface {
|
||||||
@ -35,20 +36,45 @@ func OpenTun(ctx context.Context, addr string) (TunDevice, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &tunDeviceWrapper{dev: td}, nil
|
|
||||||
|
mtu, err := td.MTU()
|
||||||
|
if err != nil {
|
||||||
|
mtu = defaultPacketSize
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferSize := max(mtu, defaultPacketSize)
|
||||||
|
batchSize := td.BatchSize()
|
||||||
|
|
||||||
|
device := &tunDeviceWrapper{
|
||||||
|
dev: td,
|
||||||
|
bufferSize: bufferSize,
|
||||||
|
readBuffers: make([][]byte, batchSize),
|
||||||
|
sizeBuffer: make([]int, batchSize),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range device.readBuffers {
|
||||||
|
device.readBuffers[i] = make([]byte, offset+bufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return device, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type tunDeviceWrapper struct {
|
type tunDeviceWrapper struct {
|
||||||
dev tun.Device
|
dev tun.Device
|
||||||
|
bufferSize int
|
||||||
|
readBuffers [][]byte
|
||||||
|
packetBuffers [][]byte
|
||||||
|
sizeBuffer []int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *tunDeviceWrapper) Read(p []byte) (int, error) {
|
func (d *tunDeviceWrapper) Read(p []byte) (int, error) {
|
||||||
buf := pool.GetBuf(len(p) + offset)
|
if len(d.packetBuffers) > 0 {
|
||||||
defer pool.PutBuf(buf)
|
n := copy(p, d.packetBuffers[0])
|
||||||
|
d.packetBuffers = d.packetBuffers[1:]
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
sz := make([]int, 1)
|
n, err := d.dev.Read(d.readBuffers, d.sizeBuffer, offset)
|
||||||
|
|
||||||
n, err := d.dev.Read([][]byte{buf}, sz, offset)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -56,20 +82,26 @@ func (d *tunDeviceWrapper) Read(p []byte) (int, error) {
|
|||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
dataSize := sz[0]
|
for i := range n {
|
||||||
if dataSize > len(p) {
|
if d.sizeBuffer[i] <= 0 {
|
||||||
dataSize = len(p)
|
continue
|
||||||
|
}
|
||||||
|
d.packetBuffers = append(d.packetBuffers, d.readBuffers[i][offset:offset+d.sizeBuffer[i]])
|
||||||
}
|
}
|
||||||
copy(p, buf[offset:offset+dataSize])
|
|
||||||
|
dataSize := copy(p, d.packetBuffers[0])
|
||||||
|
d.packetBuffers = d.packetBuffers[1:]
|
||||||
|
|
||||||
return dataSize, nil
|
return dataSize, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *tunDeviceWrapper) Write(p []byte) (int, error) {
|
func (d *tunDeviceWrapper) Write(p []byte) (int, error) {
|
||||||
buf := pool.GetBuf(len(p) + offset)
|
buf := pool.GetBuf(offset + d.bufferSize)
|
||||||
defer pool.PutBuf(buf)
|
defer pool.PutBuf(buf)
|
||||||
|
|
||||||
copy(buf[offset:], p)
|
n := copy(buf[offset:], p)
|
||||||
return d.dev.Write([][]byte{buf}, offset)
|
_, err := d.dev.Write([][]byte{buf[:offset+n]}, offset)
|
||||||
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *tunDeviceWrapper) Close() error {
|
func (d *tunDeviceWrapper) Close() error {
|
||||||
|
@ -16,35 +16,44 @@ package vnet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultTunName = "utun"
|
baseTunName = "utun"
|
||||||
defaultMTU = 1420
|
defaultMTU = 1420
|
||||||
)
|
)
|
||||||
|
|
||||||
func openTun(_ context.Context, addr string) (tun.Device, error) {
|
func openTun(_ context.Context, addr string) (tun.Device, error) {
|
||||||
dev, err := tun.CreateTUN(defaultTunName, defaultMTU)
|
name, err := findNextTunName(baseTunName)
|
||||||
|
if err != nil {
|
||||||
|
name = getFallbackTunName(baseTunName, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tunDevice, err := tun.CreateTUN(name, defaultMTU)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create TUN device '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualName, err := tunDevice.Name()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
name, err := dev.Name()
|
ifn, err := net.InterfaceByName(actualName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ifn, err := net.InterfaceByName(name)
|
link, err := netlink.LinkByName(actualName)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
link, err := netlink.LinkByName(name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -69,7 +78,34 @@ func openTun(_ context.Context, addr string) (tun.Device, error) {
|
|||||||
if err = addRoutes(ifn, cidr); err != nil {
|
if err = addRoutes(ifn, cidr); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return dev, nil
|
return tunDevice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNextTunName(basename string) (string, error) {
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get network interfaces: %w", err)
|
||||||
|
}
|
||||||
|
maxSuffix := -1
|
||||||
|
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
name := iface.Name
|
||||||
|
if strings.HasPrefix(name, basename) {
|
||||||
|
suffix := name[len(basename):]
|
||||||
|
if suffix == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
numSuffix, err := strconv.Atoi(suffix)
|
||||||
|
if err == nil && numSuffix > maxSuffix {
|
||||||
|
maxSuffix = numSuffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSuffix := maxSuffix + 1
|
||||||
|
name := fmt.Sprintf("%s%d", basename, nextSuffix)
|
||||||
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRoutes(ifn *net.Interface, cidr *net.IPNet) error {
|
func addRoutes(ifn *net.Interface, cidr *net.IPNet) error {
|
||||||
@ -82,3 +118,14 @@ func addRoutes(ifn *net.Interface, cidr *net.IPNet) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFallbackTunName generates a deterministic fallback TUN device name
|
||||||
|
// based on the base name and the provided address string using a hash.
|
||||||
|
func getFallbackTunName(baseName, addr string) string {
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write([]byte(addr))
|
||||||
|
hashBytes := hasher.Sum(nil)
|
||||||
|
// Use first 4 bytes -> 8 hex chars for brevity, respecting IFNAMSIZ limit.
|
||||||
|
shortHash := hex.EncodeToString(hashBytes[:4])
|
||||||
|
return fmt.Sprintf("%s%s", baseName, shortHash)
|
||||||
|
}
|
||||||
|
@ -224,7 +224,7 @@ func (ctl *Control) Close() error {
|
|||||||
|
|
||||||
func (ctl *Control) Replaced(newCtl *Control) {
|
func (ctl *Control) Replaced(newCtl *Control) {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
xl.Infof("Replaced by client [%s]", newCtl.runID)
|
xl.Infof("replaced by client [%s]", newCtl.runID)
|
||||||
ctl.runID = ""
|
ctl.runID = ""
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
}
|
}
|
||||||
|
@ -97,14 +97,14 @@ func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
|
|||||||
func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Infof("Http request: [%s]", r.URL.Path)
|
log.Infof("http request: [%s]", r.URL.Path)
|
||||||
serverStats := mem.StatsCollector.GetServer()
|
serverStats := mem.StatsCollector.GetServer()
|
||||||
svrResp := serverInfoResp{
|
svrResp := serverInfoResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
@ -218,13 +218,13 @@ func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
|
|||||||
proxyType := params["type"]
|
proxyType := params["type"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
log.Infof("Http request: [%s]", r.URL.Path)
|
log.Infof("http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
proxyInfoResp := GetProxyInfoResp{}
|
proxyInfoResp := GetProxyInfoResp{}
|
||||||
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
||||||
@ -290,13 +290,13 @@ func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request
|
|||||||
name := params["name"]
|
name := params["name"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
log.Infof("Http request: [%s]", r.URL.Path)
|
log.Infof("http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
var proxyStatsResp GetProxyStatsResp
|
var proxyStatsResp GetProxyStatsResp
|
||||||
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
||||||
@ -358,13 +358,13 @@ func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
|||||||
name := params["name"]
|
name := params["name"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
log.Infof("Http request: [%s]", r.URL.Path)
|
log.Infof("http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
trafficResp := GetProxyTrafficResp{}
|
trafficResp := GetProxyTrafficResp{}
|
||||||
trafficResp.Name = name
|
trafficResp.Name = name
|
||||||
@ -386,9 +386,9 @@ func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
log.Infof("Http request: [%s]", r.URL.Path)
|
log.Infof("http request: [%s]", r.URL.Path)
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
|
@ -427,7 +427,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna
|
|||||||
|
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
_ = conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
||||||
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
||||||
log.Tracef("Failed to read message: %v", err)
|
log.Tracef("failed to read message: %v", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -475,7 +475,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Warnf("Error message type for the new connection [%s]", conn.RemoteAddr().String())
|
log.Warnf("error message type for the new connection [%s]", conn.RemoteAddr().String())
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -488,7 +488,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) {
|
|||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Listener for incoming connections from client closed")
|
log.Warnf("listener for incoming connections from client closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// inject xlog object into net.Conn context
|
// inject xlog object into net.Conn context
|
||||||
@ -504,7 +504,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) {
|
|||||||
var isTLS, custom bool
|
var isTLS, custom bool
|
||||||
c, isTLS, custom, err = netpkg.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, forceTLS, connReadTimeout)
|
c, isTLS, custom, err = netpkg.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, forceTLS, connReadTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
|
log.Warnf("checkAndEnableTLSServerConnWithTimeout error: %v", err)
|
||||||
originConn.Close()
|
originConn.Close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -520,7 +520,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) {
|
|||||||
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
||||||
session, err := fmux.Server(frpConn, fmuxCfg)
|
session, err := fmux.Server(frpConn, fmuxCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to create mux connection: %v", err)
|
log.Warnf("failed to create mux connection: %v", err)
|
||||||
frpConn.Close()
|
frpConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -528,7 +528,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) {
|
|||||||
for {
|
for {
|
||||||
stream, err := session.AcceptStream()
|
stream, err := session.AcceptStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Accept new mux stream error: %v", err)
|
log.Debugf("accept new mux stream error: %v", err)
|
||||||
session.Close()
|
session.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -546,7 +546,7 @@ func (svr *Service) HandleQUICListener(l *quic.Listener) {
|
|||||||
for {
|
for {
|
||||||
c, err := l.Accept(context.Background())
|
c, err := l.Accept(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("QUICListener for incoming connections from client closed")
|
log.Warnf("quic listener for incoming connections from client closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Start a new goroutine to handle connection.
|
// Start a new goroutine to handle connection.
|
||||||
@ -554,7 +554,7 @@ func (svr *Service) HandleQUICListener(l *quic.Listener) {
|
|||||||
for {
|
for {
|
||||||
stream, err := frpConn.AcceptStream(context.Background())
|
stream, err := frpConn.AcceptStream(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Accept new quic mux stream error: %v", err)
|
log.Debugf("accept new quic mux stream error: %v", err)
|
||||||
_ = frpConn.CloseWithError(0, "")
|
_ = frpConn.CloseWithError(0, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -620,7 +620,7 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn)
|
|||||||
xl := netpkg.NewLogFromConn(workConn)
|
xl := netpkg.NewLogFromConn(workConn)
|
||||||
ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
|
ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
|
||||||
if !exist {
|
if !exist {
|
||||||
xl.Warnf("No client control found for run id [%s]", newMsg.RunID)
|
xl.Warnf("no client control found for run id [%s]", newMsg.RunID)
|
||||||
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
|
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
|
||||||
}
|
}
|
||||||
// server plugin hook
|
// server plugin hook
|
||||||
|
@ -38,7 +38,7 @@ func RunE2ETests(t *testing.T) {
|
|||||||
// Randomize specs as well as suites
|
// Randomize specs as well as suites
|
||||||
suiteConfig.RandomizeAllSpecs = true
|
suiteConfig.RandomizeAllSpecs = true
|
||||||
|
|
||||||
log.Infof("Starting e2e run %q on Ginkgo node %d of total %d",
|
log.Infof("starting e2e run %q on Ginkgo node %d of total %d",
|
||||||
framework.RunID, suiteConfig.ParallelProcess, suiteConfig.ParallelTotal)
|
framework.RunID, suiteConfig.ParallelProcess, suiteConfig.ParallelTotal)
|
||||||
ginkgo.RunSpecs(t, "frp e2e suite", suiteConfig, reporterConfig)
|
ginkgo.RunSpecs(t, "frp e2e suite", suiteConfig, reporterConfig)
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ func ExpectResponseCode(code int) EnsureFunc {
|
|||||||
if resp.Code == code {
|
if resp.Code == code {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
flog.Warnf("Expect code %d, but got %d", code, resp.Code)
|
flog.Warnf("expect code %d, but got %d", code, resp.Code)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,14 +111,14 @@ func (e *RequestExpect) Ensure(fns ...EnsureFunc) {
|
|||||||
|
|
||||||
if len(fns) == 0 {
|
if len(fns) == 0 {
|
||||||
if !bytes.Equal(e.expectResp, ret.Content) {
|
if !bytes.Equal(e.expectResp, ret.Content) {
|
||||||
flog.Tracef("Response info: %+v", ret)
|
flog.Tracef("response info: %+v", ret)
|
||||||
}
|
}
|
||||||
ExpectEqualValuesWithOffset(1, string(ret.Content), string(e.expectResp), e.explain...)
|
ExpectEqualValuesWithOffset(1, string(ret.Content), string(e.expectResp), e.explain...)
|
||||||
} else {
|
} else {
|
||||||
for _, fn := range fns {
|
for _, fn := range fns {
|
||||||
ok := fn(ret)
|
ok := fn(ret)
|
||||||
if !ok {
|
if !ok {
|
||||||
flog.Tracef("Response info: %+v", ret)
|
flog.Tracef("response info: %+v", ret)
|
||||||
}
|
}
|
||||||
ExpectTrueWithOffset(1, ok, e.explain...)
|
ExpectTrueWithOffset(1, ok, e.explain...)
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,14 @@ type generalTestConfigures struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderBindPortConfig(protocol string) string {
|
func renderBindPortConfig(protocol string) string {
|
||||||
if protocol == "kcp" {
|
switch protocol {
|
||||||
|
case "kcp":
|
||||||
return fmt.Sprintf(`kcp_bind_port = {{ .%s }}`, consts.PortServerName)
|
return fmt.Sprintf(`kcp_bind_port = {{ .%s }}`, consts.PortServerName)
|
||||||
} else if protocol == "quic" {
|
case "quic":
|
||||||
return fmt.Sprintf(`quic_bind_port = {{ .%s }}`, consts.PortServerName)
|
return fmt.Sprintf(`quic_bind_port = {{ .%s }}`, consts.PortServerName)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
|
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
|
||||||
|
@ -93,7 +93,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
|
|||||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
framework.NewRequestExpect(f).Port(remotePort).Ensure(func(resp *request.Response) bool {
|
framework.NewRequestExpect(f).Port(remotePort).Ensure(func(resp *request.Response) bool {
|
||||||
log.Tracef("ProxyProtocol get SourceAddr: %s", string(resp.Content))
|
log.Tracef("proxy protocol get SourceAddr: %s", string(resp.Content))
|
||||||
addr, err := net.ResolveTCPAddr("tcp", string(resp.Content))
|
addr, err := net.ResolveTCPAddr("tcp", string(resp.Content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@ -142,7 +142,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
|
|||||||
r.HTTP().HTTPHost("normal.example.com")
|
r.HTTP().HTTPHost("normal.example.com")
|
||||||
}).Ensure(framework.ExpectResponseCode(404))
|
}).Ensure(framework.ExpectResponseCode(404))
|
||||||
|
|
||||||
log.Tracef("ProxyProtocol get SourceAddr: %s", srcAddrRecord)
|
log.Tracef("proxy protocol get SourceAddr: %s", srcAddrRecord)
|
||||||
addr, err := net.ResolveTCPAddr("tcp", srcAddrRecord)
|
addr, err := net.ResolveTCPAddr("tcp", srcAddrRecord)
|
||||||
framework.ExpectNoError(err, srcAddrRecord)
|
framework.ExpectNoError(err, srcAddrRecord)
|
||||||
framework.ExpectEqualValues("127.0.0.1", addr.IP.String())
|
framework.ExpectEqualValues("127.0.0.1", addr.IP.String())
|
||||||
|
@ -223,7 +223,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
|
|||||||
handler := func(req *plugin.Request) *plugin.Response {
|
handler := func(req *plugin.Request) *plugin.Response {
|
||||||
var ret plugin.Response
|
var ret plugin.Response
|
||||||
content := req.Content.(*plugin.PingContent)
|
content := req.Content.(*plugin.PingContent)
|
||||||
record = content.Ping.PrivilegeKey
|
record = content.PrivilegeKey
|
||||||
ret.Unchange = true
|
ret.Unchange = true
|
||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
@ -273,7 +273,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
|
|||||||
handler := func(req *plugin.Request) *plugin.Response {
|
handler := func(req *plugin.Request) *plugin.Response {
|
||||||
var ret plugin.Response
|
var ret plugin.Response
|
||||||
content := req.Content.(*plugin.NewWorkConnContent)
|
content := req.Content.(*plugin.NewWorkConnContent)
|
||||||
record = content.NewWorkConn.RunID
|
record = content.RunID
|
||||||
ret.Unchange = true
|
ret.Unchange = true
|
||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,14 @@ type generalTestConfigures struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderBindPortConfig(protocol string) string {
|
func renderBindPortConfig(protocol string) string {
|
||||||
if protocol == "kcp" {
|
switch protocol {
|
||||||
|
case "kcp":
|
||||||
return fmt.Sprintf(`kcpBindPort = {{ .%s }}`, consts.PortServerName)
|
return fmt.Sprintf(`kcpBindPort = {{ .%s }}`, consts.PortServerName)
|
||||||
} else if protocol == "quic" {
|
case "quic":
|
||||||
return fmt.Sprintf(`quicBindPort = {{ .%s }}`, consts.PortServerName)
|
return fmt.Sprintf(`quicBindPort = {{ .%s }}`, consts.PortServerName)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
|
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
|
||||||
|
@ -215,7 +215,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
|
|||||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
framework.NewRequestExpect(f).Port(remotePort).Ensure(func(resp *request.Response) bool {
|
framework.NewRequestExpect(f).Port(remotePort).Ensure(func(resp *request.Response) bool {
|
||||||
log.Tracef("ProxyProtocol get SourceAddr: %s", string(resp.Content))
|
log.Tracef("proxy protocol get SourceAddr: %s", string(resp.Content))
|
||||||
addr, err := net.ResolveTCPAddr("tcp", string(resp.Content))
|
addr, err := net.ResolveTCPAddr("tcp", string(resp.Content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@ -227,6 +227,56 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ginkgo.It("UDP", func() {
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
clientConf := consts.DefaultClientConfig
|
||||||
|
|
||||||
|
localPort := f.AllocPort()
|
||||||
|
localServer := streamserver.New(streamserver.UDP, streamserver.WithBindPort(localPort),
|
||||||
|
streamserver.WithCustomHandler(func(c net.Conn) {
|
||||||
|
defer c.Close()
|
||||||
|
rd := bufio.NewReader(c)
|
||||||
|
ppHeader, err := pp.Read(rd)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("read proxy protocol error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the actual UDP content after proxy protocol header
|
||||||
|
if _, err := rpc.ReadBytes(rd); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := []byte(ppHeader.SourceAddr.String())
|
||||||
|
_, _ = rpc.WriteBytes(c, buf)
|
||||||
|
}))
|
||||||
|
f.RunServer("", localServer)
|
||||||
|
|
||||||
|
remotePort := f.AllocPort()
|
||||||
|
clientConf += fmt.Sprintf(`
|
||||||
|
[[proxies]]
|
||||||
|
name = "udp"
|
||||||
|
type = "udp"
|
||||||
|
localPort = %d
|
||||||
|
remotePort = %d
|
||||||
|
transport.proxyProtocolVersion = "v2"
|
||||||
|
`, localPort, remotePort)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).Protocol("udp").Port(remotePort).Ensure(func(resp *request.Response) bool {
|
||||||
|
log.Tracef("udp proxy protocol get SourceAddr: %s", string(resp.Content))
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", string(resp.Content))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if addr.IP.String() != "127.0.0.1" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
ginkgo.It("HTTP", func() {
|
ginkgo.It("HTTP", func() {
|
||||||
vhostHTTPPort := f.AllocPort()
|
vhostHTTPPort := f.AllocPort()
|
||||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||||
@ -265,7 +315,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
|
|||||||
r.HTTP().HTTPHost("normal.example.com")
|
r.HTTP().HTTPHost("normal.example.com")
|
||||||
}).Ensure(framework.ExpectResponseCode(404))
|
}).Ensure(framework.ExpectResponseCode(404))
|
||||||
|
|
||||||
log.Tracef("ProxyProtocol get SourceAddr: %s", srcAddrRecord)
|
log.Tracef("proxy protocol get SourceAddr: %s", srcAddrRecord)
|
||||||
addr, err := net.ResolveTCPAddr("tcp", srcAddrRecord)
|
addr, err := net.ResolveTCPAddr("tcp", srcAddrRecord)
|
||||||
framework.ExpectNoError(err, srcAddrRecord)
|
framework.ExpectNoError(err, srcAddrRecord)
|
||||||
framework.ExpectEqualValues("127.0.0.1", addr.IP.String())
|
framework.ExpectEqualValues("127.0.0.1", addr.IP.String())
|
||||||
|
@ -232,7 +232,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
|
|||||||
handler := func(req *plugin.Request) *plugin.Response {
|
handler := func(req *plugin.Request) *plugin.Response {
|
||||||
var ret plugin.Response
|
var ret plugin.Response
|
||||||
content := req.Content.(*plugin.PingContent)
|
content := req.Content.(*plugin.PingContent)
|
||||||
record = content.Ping.PrivilegeKey
|
record = content.PrivilegeKey
|
||||||
ret.Unchange = true
|
ret.Unchange = true
|
||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
@ -284,7 +284,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
|
|||||||
handler := func(req *plugin.Request) *plugin.Response {
|
handler := func(req *plugin.Request) *plugin.Response {
|
||||||
var ret plugin.Response
|
var ret plugin.Response
|
||||||
content := req.Content.(*plugin.NewWorkConnContent)
|
content := req.Content.(*plugin.NewWorkConnContent)
|
||||||
record = content.NewWorkConn.RunID
|
record = content.RunID
|
||||||
ret.Unchange = true
|
ret.Unchange = true
|
||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user