mirror of
https://github.com/fatedier/frp.git
synced 2025-07-08 08:49:29 +00:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f9065a6a78 | ||
|
61330d4d79 | ||
|
c777891f75 | ||
|
43cf1688e4 | ||
|
720c09c06b | ||
|
3fa76b72f3 | ||
|
8eb525a648 | ||
|
077ba80ba3 | ||
|
c99986fa28 |
.github/workflows
.gitignore.golangci.ymlMakefile.cross-compilesREADME.mdRelease.mdclient
cmd
conf
doc
go.modgo.sumhack
package.shpkg
server
test/e2e
legacy
v1
10
.github/workflows/golangci-lint.yml
vendored
10
.github/workflows/golangci-lint.yml
vendored
@ -20,10 +20,10 @@ jobs:
|
||||
go-version: '1.23'
|
||||
cache: false
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: v1.61
|
||||
version: v2.1
|
||||
|
||||
# Optional: golangci-lint command line arguments.
|
||||
# args: --issues-exit-code=0
|
||||
@ -34,9 +34,3 @@ jobs:
|
||||
# Optional: if set to true then the all caching functionality will be complete disabled,
|
||||
# takes precedence over all other caching options.
|
||||
# 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:
|
||||
schedule:
|
||||
- cron: "20 0 * * *"
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -39,3 +39,6 @@ client.key
|
||||
|
||||
# Cache
|
||||
*.swp
|
||||
|
||||
# AI
|
||||
CLAUDE.md
|
||||
|
215
.golangci.yml
215
.golangci.yml
@ -1,139 +1,112 @@
|
||||
service:
|
||||
golangci-lint-version: 1.61.x # use the fixed version to not introduce new linters unexpectedly
|
||||
|
||||
version: "2"
|
||||
run:
|
||||
concurrency: 4
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 20m
|
||||
build-tags:
|
||||
- integ
|
||||
- integfuzz
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
- unused
|
||||
- errcheck
|
||||
- asciicheck
|
||||
- copyloopvar
|
||||
- errcheck
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- goimports
|
||||
- revive
|
||||
- gosimple
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- makezero
|
||||
- misspell
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- gci
|
||||
- gosec
|
||||
- asciicheck
|
||||
- prealloc
|
||||
- predeclared
|
||||
- makezero
|
||||
fast: false
|
||||
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-type-assertions: false
|
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-blank: false
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
disable:
|
||||
- shadow
|
||||
maligned:
|
||||
# print struct with more effective memory layout or not, false by default
|
||||
suggest-new: true
|
||||
misspell:
|
||||
# Correct spellings using locale preferences for US or UK.
|
||||
# Default is to use a neutral variety of English.
|
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||
locale: US
|
||||
ignore-words:
|
||||
- cancelled
|
||||
- marshalled
|
||||
lll:
|
||||
# max line length, lines longer will be reported. Default is 120.
|
||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
||||
line-length: 160
|
||||
# tab width in spaces. Default to 1.
|
||||
tab-width: 1
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- exitAfterDefer
|
||||
unused:
|
||||
check-exported: false
|
||||
unparam:
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/fatedier/frp/)
|
||||
gosec:
|
||||
severity: "low"
|
||||
confidence: "low"
|
||||
excludes:
|
||||
- G401
|
||||
- G402
|
||||
- G404
|
||||
- G501
|
||||
- G115 # integer overflow conversion
|
||||
|
||||
- revive
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
settings:
|
||||
errcheck:
|
||||
check-type-assertions: false
|
||||
check-blank: false
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- exitAfterDefer
|
||||
gosec:
|
||||
excludes:
|
||||
- G401
|
||||
- G402
|
||||
- G404
|
||||
- G501
|
||||
- G115
|
||||
severity: low
|
||||
confidence: low
|
||||
govet:
|
||||
disable:
|
||||
- shadow
|
||||
lll:
|
||||
line-length: 160
|
||||
tab-width: 1
|
||||
misspell:
|
||||
locale: US
|
||||
ignore-rules:
|
||||
- cancelled
|
||||
- marshalled
|
||||
unparam:
|
||||
check-exported: false
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- errcheck
|
||||
- maligned
|
||||
path: _test\.go$|^tests/|^samples/
|
||||
- linters:
|
||||
- revive
|
||||
- staticcheck
|
||||
text: use underscores in Go names
|
||||
- linters:
|
||||
- revive
|
||||
text: unused-parameter
|
||||
- linters:
|
||||
- unparam
|
||||
text: is always false
|
||||
paths:
|
||||
- .*\.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:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# 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-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
@ -2,7 +2,7 @@ export PATH := $(PATH):`go env GOPATH`/bin
|
||||
export GO111MODULE=on
|
||||
LDFLAGS := -s -w
|
||||
|
||||
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm: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
|
||||
|
||||
|
17
README.md
17
README.md
@ -612,6 +612,21 @@ When specifying `auth.method = "token"` in `frpc.toml` and `frps.toml` - token b
|
||||
|
||||
Make sure to specify the same `auth.token` in `frps.toml` and `frpc.toml` for frpc to pass frps validation
|
||||
|
||||
##### Token Source
|
||||
|
||||
frp supports reading authentication tokens from external sources using the `tokenSource` configuration. Currently, file-based token source is supported.
|
||||
|
||||
**File-based token source:**
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
auth.method = "token"
|
||||
auth.tokenSource.type = "file"
|
||||
auth.tokenSource.file.path = "/path/to/token/file"
|
||||
```
|
||||
|
||||
The token will be read from the specified file at startup. This is useful for scenarios where tokens are managed by external systems or need to be kept separate from configuration files for security reasons.
|
||||
|
||||
#### OIDC Authentication
|
||||
|
||||
When specifying `auth.method = "oidc"` in `frpc.toml` and `frps.toml` - OIDC based authentication will be used.
|
||||
@ -1025,7 +1040,7 @@ You can get user's real IP from HTTP request headers `X-Forwarded-For`.
|
||||
|
||||
#### 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:
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
### Bug Fixes
|
||||
## Features
|
||||
|
||||
* **VirtualNet:** Resolved various issues related to connection handling, TUN device management, and stability in the virtual network feature.
|
||||
* Support tokenSource for loading authentication tokens from files
|
@ -48,7 +48,7 @@ type defaultConnectorImpl struct {
|
||||
cfg *v1.ClientCommonConfig
|
||||
|
||||
muxSession *fmux.Session
|
||||
quicConn quic.Connection
|
||||
quicConn *quic.Conn
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
|
@ -20,13 +20,11 @@ import (
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
libio "github.com/fatedier/golib/io"
|
||||
libnet "github.com/fatedier/golib/net"
|
||||
pp "github.com/pires/go-proxyproto"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
@ -35,6 +33,7 @@ import (
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"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/vnet"
|
||||
)
|
||||
@ -176,24 +175,9 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
||||
}
|
||||
|
||||
if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
|
||||
h := &pp.Header{
|
||||
Command: pp.PROXY,
|
||||
SourceAddr: connInfo.SrcAddr,
|
||||
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
|
||||
// Use the common proxy protocol builder function
|
||||
header := netpkg.BuildProxyProtocolHeaderStruct(connInfo.SrcAddr, connInfo.DstAddr, baseCfg.Transport.ProxyProtocolVersion)
|
||||
connInfo.ProxyProtocolHeader = header
|
||||
}
|
||||
connInfo.Conn = remote
|
||||
connInfo.UnderlyingConn = workConn
|
||||
|
@ -205,5 +205,5 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
go workConnReaderFn(workConn, readCh)
|
||||
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 workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||
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)
|
||||
}
|
||||
|
@ -88,13 +88,16 @@ type ServiceOptions struct {
|
||||
}
|
||||
|
||||
// setServiceOptionsDefault sets the default values for ServiceOptions.
|
||||
func setServiceOptionsDefault(options *ServiceOptions) {
|
||||
func setServiceOptionsDefault(options *ServiceOptions) error {
|
||||
if options.Common != nil {
|
||||
options.Common.Complete()
|
||||
if err := options.Common.Complete(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if options.ConnectorCreator == nil {
|
||||
options.ConnectorCreator = NewConnector
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Service is the client service that connects to frps and provides proxy services.
|
||||
@ -134,7 +137,9 @@ type Service struct {
|
||||
}
|
||||
|
||||
func NewService(options ServiceOptions) (*Service, error) {
|
||||
setServiceOptionsDefault(&options)
|
||||
if err := setServiceOptionsDefault(&options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var webServer *httppkg.Server
|
||||
if options.Common.WebServer.Port > 0 {
|
||||
@ -325,10 +330,9 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
||||
proxyCfgs := svr.proxyCfgs
|
||||
visitorCfgs := svr.visitorCfgs
|
||||
svr.cfgMu.RUnlock()
|
||||
connEncrypted := true
|
||||
if svr.clientSpec != nil && svr.clientSpec.Type == "ssh-tunnel" {
|
||||
connEncrypted = false
|
||||
}
|
||||
|
||||
connEncrypted := svr.clientSpec == nil || svr.clientSpec.Type != "ssh-tunnel"
|
||||
|
||||
sessionCtx := &SessionContext{
|
||||
Common: svr.common,
|
||||
RunID: svr.runID,
|
||||
|
@ -398,7 +398,7 @@ func (ks *KCPTunnelSession) Close() {
|
||||
}
|
||||
|
||||
type QUICTunnelSession struct {
|
||||
session quic.Connection
|
||||
session *quic.Conn
|
||||
listenConn *net.UDPConn
|
||||
mu sync.RWMutex
|
||||
|
||||
|
@ -51,7 +51,10 @@ var natholeDiscoveryCmd = &cobra.Command{
|
||||
cfg, _, _, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
|
||||
if err != nil {
|
||||
cfg = &v1.ClientCommonConfig{}
|
||||
cfg.Complete()
|
||||
if err := cfg.Complete(); err != nil {
|
||||
fmt.Printf("failed to complete config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if natHoleSTUNServer != "" {
|
||||
cfg.NatHoleSTUNServer = natHoleSTUNServer
|
||||
|
@ -73,7 +73,10 @@ func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.ClientComm
|
||||
Use: name,
|
||||
Short: fmt.Sprintf("Run frpc with a single %s proxy", name),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clientCfg.Complete()
|
||||
if err := clientCfg.Complete(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@ -99,7 +102,10 @@ func NewVisitorCommand(name string, c v1.VisitorConfigurer, clientCfg *v1.Client
|
||||
Use: "visitor",
|
||||
Short: fmt.Sprintf("Run frpc with a single %s visitor", name),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
clientCfg.Complete()
|
||||
if err := clientCfg.Complete(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
@ -70,7 +70,10 @@ var rootCmd = &cobra.Command{
|
||||
"please use yaml/json/toml format instead!\n")
|
||||
}
|
||||
} else {
|
||||
serverCfg.Complete()
|
||||
if err := serverCfg.Complete(); err != nil {
|
||||
fmt.Printf("failed to complete server config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
svrCfg = &serverCfg
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,11 @@ auth.method = "token"
|
||||
# auth token
|
||||
auth.token = "12345678"
|
||||
|
||||
# alternatively, you can use tokenSource to load the token from a file
|
||||
# this is mutually exclusive with auth.token
|
||||
# auth.tokenSource.type = "file"
|
||||
# auth.tokenSource.file.path = "/etc/frp/token"
|
||||
|
||||
# oidc.clientID specifies the client ID to use to get a token in OIDC authentication.
|
||||
# auth.oidc.clientID = ""
|
||||
# oidc.clientSecret specifies the client secret to use to get a token in OIDC authentication.
|
||||
|
@ -105,6 +105,11 @@ auth.method = "token"
|
||||
# auth token
|
||||
auth.token = "12345678"
|
||||
|
||||
# alternatively, you can use tokenSource to load the token from a file
|
||||
# this is mutually exclusive with auth.token
|
||||
# auth.tokenSource.type = "file"
|
||||
# auth.tokenSource.file.path = "/etc/frp/token"
|
||||
|
||||
# oidc issuer specifies the issuer to verify OIDC tokens with.
|
||||
auth.oidc.issuer = ""
|
||||
# oidc audience specifies the audience OIDC tokens should contain when validated.
|
||||
|
@ -121,7 +121,7 @@ Create new proxy
|
||||
// http and https only
|
||||
"custom_domains": []<string>,
|
||||
"subdomain": <string>,
|
||||
"locations": <string>,
|
||||
"locations": []<string>,
|
||||
"http_user": <string>,
|
||||
"http_pwd": <string>,
|
||||
"host_header_rewrite": <string>,
|
||||
|
19
go.mod
19
go.mod
@ -10,13 +10,13 @@ require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/yamux v0.1.1
|
||||
github.com/onsi/ginkgo/v2 v2.22.0
|
||||
github.com/onsi/gomega v1.34.2
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.36.3
|
||||
github.com/pelletier/go-toml/v2 v2.2.0
|
||||
github.com/pion/stun/v2 v2.0.0
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/quic-go/quic-go v0.48.2
|
||||
github.com/quic-go/quic-go v0.53.0
|
||||
github.com/rodaine/table v1.2.0
|
||||
github.com/samber/lo v1.47.0
|
||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||
@ -46,12 +46,11 @@ require (
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241206021119-61a79c692802 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // 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/logging v0.2.2 // indirect
|
||||
github.com/pion/transport/v2 v2.2.1 // indirect
|
||||
@ -67,14 +66,14 @@ require (
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // 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
|
||||
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/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
|
||||
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.v3 v3.0.1 // indirect
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||
|
38
go.sum
38
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/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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.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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
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/pprof v0.0.0-20241206021119-61a79c692802/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
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.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
||||
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||
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/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@ -103,8 +105,8 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
github.com/quic-go/quic-go v0.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI=
|
||||
github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
|
||||
@ -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/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
||||
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/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@ -163,15 +167,13 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -239,8 +241,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.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.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
@ -261,8 +263,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.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
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.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
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 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
@ -3,10 +3,10 @@
|
||||
SCRIPT=$(readlink -f "$0")
|
||||
ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
|
||||
|
||||
ginkgo_command=$(which ginkgo 2>/dev/null)
|
||||
if [ -z "$ginkgo_command" ]; then
|
||||
# Check if ginkgo is available
|
||||
if ! command -v ginkgo >/dev/null 2>&1; then
|
||||
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
|
||||
|
||||
debug=false
|
||||
|
@ -17,7 +17,7 @@ make -f ./Makefile.cross-compiles
|
||||
rm -rf ./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'
|
||||
extra_all='_ hf'
|
||||
|
||||
|
@ -194,7 +194,7 @@ func UnmarshalClientConfFromIni(source any) (ClientCommonConf, error) {
|
||||
}
|
||||
|
||||
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
||||
common.ClientConfig.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
|
||||
common.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
|
||||
|
||||
return common, nil
|
||||
}
|
||||
@ -229,10 +229,7 @@ func LoadAllProxyConfsFromIni(
|
||||
startProxy[s] = struct{}{}
|
||||
}
|
||||
|
||||
startAll := true
|
||||
if len(startProxy) > 0 {
|
||||
startAll = false
|
||||
}
|
||||
startAll := len(startProxy) == 0
|
||||
|
||||
// Build template sections from range section And append to ini.File.
|
||||
rangeSections := make([]*ini.Section, 0)
|
||||
|
@ -26,20 +26,20 @@ import (
|
||||
func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConfig {
|
||||
out := &v1.ClientCommonConfig{}
|
||||
out.User = conf.User
|
||||
out.Auth.Method = v1.AuthMethod(conf.ClientConfig.AuthenticationMethod)
|
||||
out.Auth.Token = conf.ClientConfig.Token
|
||||
if conf.ClientConfig.AuthenticateHeartBeats {
|
||||
out.Auth.Method = v1.AuthMethod(conf.AuthenticationMethod)
|
||||
out.Auth.Token = conf.Token
|
||||
if conf.AuthenticateHeartBeats {
|
||||
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.OIDC.ClientID = conf.ClientConfig.OidcClientID
|
||||
out.Auth.OIDC.ClientSecret = conf.ClientConfig.OidcClientSecret
|
||||
out.Auth.OIDC.Audience = conf.ClientConfig.OidcAudience
|
||||
out.Auth.OIDC.Scope = conf.ClientConfig.OidcScope
|
||||
out.Auth.OIDC.TokenEndpointURL = conf.ClientConfig.OidcTokenEndpointURL
|
||||
out.Auth.OIDC.AdditionalEndpointParams = conf.ClientConfig.OidcAdditionalEndpointParams
|
||||
out.Auth.OIDC.ClientID = conf.OidcClientID
|
||||
out.Auth.OIDC.ClientSecret = conf.OidcClientSecret
|
||||
out.Auth.OIDC.Audience = conf.OidcAudience
|
||||
out.Auth.OIDC.Scope = conf.OidcScope
|
||||
out.Auth.OIDC.TokenEndpointURL = conf.OidcTokenEndpointURL
|
||||
out.Auth.OIDC.AdditionalEndpointParams = conf.OidcAdditionalEndpointParams
|
||||
|
||||
out.ServerAddr = conf.ServerAddr
|
||||
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.TLS.Enable = lo.ToPtr(conf.TLSEnable)
|
||||
out.Transport.TLS.DisableCustomTLSFirstByte = lo.ToPtr(conf.DisableCustomTLSFirstByte)
|
||||
out.Transport.TLS.TLSConfig.CertFile = conf.TLSCertFile
|
||||
out.Transport.TLS.TLSConfig.KeyFile = conf.TLSKeyFile
|
||||
out.Transport.TLS.TLSConfig.TrustedCaFile = conf.TLSTrustedCaFile
|
||||
out.Transport.TLS.TLSConfig.ServerName = conf.TLSServerName
|
||||
out.Transport.TLS.CertFile = conf.TLSCertFile
|
||||
out.Transport.TLS.KeyFile = conf.TLSKeyFile
|
||||
out.Transport.TLS.TrustedCaFile = conf.TLSTrustedCaFile
|
||||
out.Transport.TLS.ServerName = conf.TLSServerName
|
||||
|
||||
out.Log.To = conf.LogFile
|
||||
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 {
|
||||
out := &v1.ServerConfig{}
|
||||
out.Auth.Method = v1.AuthMethod(conf.ServerConfig.AuthenticationMethod)
|
||||
out.Auth.Token = conf.ServerConfig.Token
|
||||
if conf.ServerConfig.AuthenticateHeartBeats {
|
||||
out.Auth.Method = v1.AuthMethod(conf.AuthenticationMethod)
|
||||
out.Auth.Token = conf.Token
|
||||
if conf.AuthenticateHeartBeats {
|
||||
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.OIDC.Audience = conf.ServerConfig.OidcAudience
|
||||
out.Auth.OIDC.Issuer = conf.ServerConfig.OidcIssuer
|
||||
out.Auth.OIDC.SkipExpiryCheck = conf.ServerConfig.OidcSkipExpiryCheck
|
||||
out.Auth.OIDC.SkipIssuerCheck = conf.ServerConfig.OidcSkipIssuerCheck
|
||||
out.Auth.OIDC.Audience = conf.OidcAudience
|
||||
out.Auth.OIDC.Issuer = conf.OidcIssuer
|
||||
out.Auth.OIDC.SkipExpiryCheck = conf.OidcSkipExpiryCheck
|
||||
out.Auth.OIDC.SkipIssuerCheck = conf.OidcSkipIssuerCheck
|
||||
|
||||
out.BindAddr = conf.BindAddr
|
||||
out.BindPort = conf.BindPort
|
||||
|
@ -206,7 +206,7 @@ func (cfg *BaseProxyConf) decorate(_ string, name string, section *ini.Section)
|
||||
}
|
||||
|
||||
// plugin_xxx
|
||||
cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
|
||||
cfg.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -111,6 +111,33 @@ func LoadConfigureFromFile(path string, c any, strict bool) error {
|
||||
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.
|
||||
// Now it supports json, yaml and toml format.
|
||||
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)
|
||||
}
|
||||
// It wasn't JSON. Unmarshal as YAML.
|
||||
|
||||
// Handle YAML content
|
||||
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)
|
||||
}
|
||||
|
||||
@ -182,7 +212,9 @@ func LoadServerConfig(path string, strict bool) (*v1.ServerConfig, bool, error)
|
||||
}
|
||||
}
|
||||
if svrCfg != nil {
|
||||
svrCfg.Complete()
|
||||
if err := svrCfg.Complete(); err != nil {
|
||||
return nil, isLegacyFormat, err
|
||||
}
|
||||
}
|
||||
return svrCfg, isLegacyFormat, nil
|
||||
}
|
||||
@ -250,7 +282,9 @@ func LoadClientConfig(path string, strict bool) (
|
||||
}
|
||||
|
||||
if cliCfg != nil {
|
||||
cliCfg.Complete()
|
||||
if err := cliCfg.Complete(); err != nil {
|
||||
return nil, nil, nil, isLegacyFormat, err
|
||||
}
|
||||
}
|
||||
for _, c := range proxyCfgs {
|
||||
c.Complete(cliCfg.User)
|
||||
|
@ -187,3 +187,122 @@ unixPath = "/tmp/uds.sock"
|
||||
err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
|
||||
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)
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@ -77,18 +79,21 @@ type ClientCommonConfig struct {
|
||||
IncludeConfigFiles []string `json:"includes,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ClientCommonConfig) Complete() {
|
||||
func (c *ClientCommonConfig) Complete() error {
|
||||
c.ServerAddr = util.EmptyOr(c.ServerAddr, "0.0.0.0")
|
||||
c.ServerPort = util.EmptyOr(c.ServerPort, 7000)
|
||||
c.LoginFailExit = util.EmptyOr(c.LoginFailExit, lo.ToPtr(true))
|
||||
c.NatHoleSTUNServer = util.EmptyOr(c.NatHoleSTUNServer, "stun.easyvoip.com:3478")
|
||||
|
||||
c.Auth.Complete()
|
||||
if err := c.Auth.Complete(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Log.Complete()
|
||||
c.Transport.Complete()
|
||||
c.WebServer.Complete()
|
||||
|
||||
c.UDPPacketSize = util.EmptyOr(c.UDPPacketSize, 1500)
|
||||
return nil
|
||||
}
|
||||
|
||||
type ClientTransportConfig struct {
|
||||
@ -184,12 +189,27 @@ type AuthClientConfig struct {
|
||||
// Token specifies the authorization token used to create keys to be sent
|
||||
// to the server. The server must have a matching token for authorization
|
||||
// to succeed. By default, this value is "".
|
||||
Token string `json:"token,omitempty"`
|
||||
OIDC AuthOIDCClientConfig `json:"oidc,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
// TokenSource specifies a dynamic source for the authorization token.
|
||||
// This is mutually exclusive with Token field.
|
||||
TokenSource *ValueSource `json:"tokenSource,omitempty"`
|
||||
OIDC AuthOIDCClientConfig `json:"oidc,omitempty"`
|
||||
}
|
||||
|
||||
func (c *AuthClientConfig) Complete() {
|
||||
func (c *AuthClientConfig) Complete() error {
|
||||
c.Method = util.EmptyOr(c.Method, "token")
|
||||
|
||||
// Resolve tokenSource during configuration loading
|
||||
if c.Method == AuthMethodToken && c.TokenSource != nil {
|
||||
token, err := c.TokenSource.Resolve(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve auth.tokenSource: %w", err)
|
||||
}
|
||||
// Move the resolved token to the Token field and clear TokenSource
|
||||
c.Token = token
|
||||
c.TokenSource = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AuthOIDCClientConfig struct {
|
||||
|
@ -15,6 +15,8 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@ -24,7 +26,8 @@ import (
|
||||
func TestClientConfigComplete(t *testing.T) {
|
||||
require := require.New(t)
|
||||
c := &ClientConfig{}
|
||||
c.Complete()
|
||||
err := c.Complete()
|
||||
require.NoError(err)
|
||||
|
||||
require.EqualValues("token", c.Auth.Method)
|
||||
require.Equal(true, lo.FromPtr(c.Transport.TCPMux))
|
||||
@ -33,3 +36,70 @@ func TestClientConfigComplete(t *testing.T) {
|
||||
require.Equal(true, lo.FromPtr(c.Transport.TLS.DisableCustomTLSFirstByte))
|
||||
require.NotEmpty(c.NatHoleSTUNServer)
|
||||
}
|
||||
|
||||
func TestAuthClientConfig_Complete(t *testing.T) {
|
||||
// Create a temporary file for testing
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "test_token")
|
||||
testContent := "client-token-value"
|
||||
err := os.WriteFile(testFile, []byte(testContent), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config AuthClientConfig
|
||||
expectToken string
|
||||
expectPanic bool
|
||||
}{
|
||||
{
|
||||
name: "tokenSource resolved to token",
|
||||
config: AuthClientConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: testFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectToken: testContent,
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "direct token unchanged",
|
||||
config: AuthClientConfig{
|
||||
Method: AuthMethodToken,
|
||||
Token: "direct-token",
|
||||
},
|
||||
expectToken: "direct-token",
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "invalid tokenSource should panic",
|
||||
config: AuthClientConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: "/non/existent/file",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.expectPanic {
|
||||
err := tt.config.Complete()
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
err := tt.config.Complete()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectToken, tt.config.Token)
|
||||
require.Nil(t, tt.config.TokenSource, "TokenSource should be cleared after resolution")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ func (c *ProxyBaseConfig) Complete(namePrefix string) {
|
||||
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
|
||||
|
||||
if c.Plugin.ClientPluginOptions != nil {
|
||||
c.Plugin.ClientPluginOptions.Complete()
|
||||
c.Plugin.Complete()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,9 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
@ -98,8 +101,10 @@ type ServerConfig struct {
|
||||
HTTPPlugins []HTTPPluginOptions `json:"httpPlugins,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ServerConfig) Complete() {
|
||||
c.Auth.Complete()
|
||||
func (c *ServerConfig) Complete() error {
|
||||
if err := c.Auth.Complete(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Log.Complete()
|
||||
c.Transport.Complete()
|
||||
c.WebServer.Complete()
|
||||
@ -120,17 +125,31 @@ func (c *ServerConfig) Complete() {
|
||||
c.UserConnTimeout = util.EmptyOr(c.UserConnTimeout, 10)
|
||||
c.UDPPacketSize = util.EmptyOr(c.UDPPacketSize, 1500)
|
||||
c.NatHoleAnalysisDataReserveHours = util.EmptyOr(c.NatHoleAnalysisDataReserveHours, 7*24)
|
||||
return nil
|
||||
}
|
||||
|
||||
type AuthServerConfig struct {
|
||||
Method AuthMethod `json:"method,omitempty"`
|
||||
AdditionalScopes []AuthScope `json:"additionalScopes,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
TokenSource *ValueSource `json:"tokenSource,omitempty"`
|
||||
OIDC AuthOIDCServerConfig `json:"oidc,omitempty"`
|
||||
}
|
||||
|
||||
func (c *AuthServerConfig) Complete() {
|
||||
func (c *AuthServerConfig) Complete() error {
|
||||
c.Method = util.EmptyOr(c.Method, "token")
|
||||
|
||||
// Resolve tokenSource during configuration loading
|
||||
if c.Method == AuthMethodToken && c.TokenSource != nil {
|
||||
token, err := c.TokenSource.Resolve(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve auth.tokenSource: %w", err)
|
||||
}
|
||||
// Move the resolved token to the Token field and clear TokenSource
|
||||
c.Token = token
|
||||
c.TokenSource = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AuthOIDCServerConfig struct {
|
||||
|
@ -15,6 +15,8 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@ -24,9 +26,77 @@ import (
|
||||
func TestServerConfigComplete(t *testing.T) {
|
||||
require := require.New(t)
|
||||
c := &ServerConfig{}
|
||||
c.Complete()
|
||||
err := c.Complete()
|
||||
require.NoError(err)
|
||||
|
||||
require.EqualValues("token", c.Auth.Method)
|
||||
require.Equal(true, lo.FromPtr(c.Transport.TCPMux))
|
||||
require.Equal(true, lo.FromPtr(c.DetailedErrorsToClient))
|
||||
}
|
||||
|
||||
func TestAuthServerConfig_Complete(t *testing.T) {
|
||||
// Create a temporary file for testing
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "test_token")
|
||||
testContent := "file-token-value"
|
||||
err := os.WriteFile(testFile, []byte(testContent), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config AuthServerConfig
|
||||
expectToken string
|
||||
expectPanic bool
|
||||
}{
|
||||
{
|
||||
name: "tokenSource resolved to token",
|
||||
config: AuthServerConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: testFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectToken: testContent,
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "direct token unchanged",
|
||||
config: AuthServerConfig{
|
||||
Method: AuthMethodToken,
|
||||
Token: "direct-token",
|
||||
},
|
||||
expectToken: "direct-token",
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "invalid tokenSource should panic",
|
||||
config: AuthServerConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: "/non/existent/file",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.expectPanic {
|
||||
err := tt.config.Complete()
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
err := tt.config.Complete()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectToken, tt.config.Token)
|
||||
require.Nil(t, tt.config.TokenSource, "TokenSource should be cleared after resolution")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,18 @@ func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
||||
errs = AppendError(errs, fmt.Errorf("invalid auth additional scopes, optional values are %v", SupportedAuthAdditionalScopes))
|
||||
}
|
||||
|
||||
// Validate token/tokenSource mutual exclusivity
|
||||
if c.Auth.Token != "" && c.Auth.TokenSource != nil {
|
||||
errs = AppendError(errs, fmt.Errorf("cannot specify both auth.token and auth.tokenSource"))
|
||||
}
|
||||
|
||||
// Validate tokenSource if specified
|
||||
if c.Auth.TokenSource != nil {
|
||||
if err := c.Auth.TokenSource.Validate(); err != nil {
|
||||
errs = AppendError(errs, fmt.Errorf("invalid auth.tokenSource: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateLogConfig(&c.Log); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
|
@ -35,6 +35,18 @@ func ValidateServerConfig(c *v1.ServerConfig) (Warning, error) {
|
||||
errs = AppendError(errs, fmt.Errorf("invalid auth additional scopes, optional values are %v", SupportedAuthAdditionalScopes))
|
||||
}
|
||||
|
||||
// Validate token/tokenSource mutual exclusivity
|
||||
if c.Auth.Token != "" && c.Auth.TokenSource != nil {
|
||||
errs = AppendError(errs, fmt.Errorf("cannot specify both auth.token and auth.tokenSource"))
|
||||
}
|
||||
|
||||
// Validate tokenSource if specified
|
||||
if c.Auth.TokenSource != nil {
|
||||
if err := c.Auth.TokenSource.Validate(); err != nil {
|
||||
errs = AppendError(errs, fmt.Errorf("invalid auth.tokenSource: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateLogConfig(&c.Log); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
|
93
pkg/config/v1/value_source.go
Normal file
93
pkg/config/v1/value_source.go
Normal file
@ -0,0 +1,93 @@
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValueSource provides a way to dynamically resolve configuration values
|
||||
// from various sources like files, environment variables, or external services.
|
||||
type ValueSource struct {
|
||||
Type string `json:"type"`
|
||||
File *FileSource `json:"file,omitempty"`
|
||||
}
|
||||
|
||||
// FileSource specifies how to load a value from a file.
|
||||
type FileSource struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// Validate validates the ValueSource configuration.
|
||||
func (v *ValueSource) Validate() error {
|
||||
if v == nil {
|
||||
return errors.New("valueSource cannot be nil")
|
||||
}
|
||||
|
||||
switch v.Type {
|
||||
case "file":
|
||||
if v.File == nil {
|
||||
return errors.New("file configuration is required when type is 'file'")
|
||||
}
|
||||
return v.File.Validate()
|
||||
default:
|
||||
return fmt.Errorf("unsupported value source type: %s (only 'file' is supported)", v.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve resolves the value from the configured source.
|
||||
func (v *ValueSource) Resolve(ctx context.Context) (string, error) {
|
||||
if err := v.Validate(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch v.Type {
|
||||
case "file":
|
||||
return v.File.Resolve(ctx)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported value source type: %s", v.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the FileSource configuration.
|
||||
func (f *FileSource) Validate() error {
|
||||
if f == nil {
|
||||
return errors.New("fileSource cannot be nil")
|
||||
}
|
||||
|
||||
if f.Path == "" {
|
||||
return errors.New("file path cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolve reads and returns the content from the specified file.
|
||||
func (f *FileSource) Resolve(_ context.Context) (string, error) {
|
||||
if err := f.Validate(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(f.Path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read file %s: %v", f.Path, err)
|
||||
}
|
||||
|
||||
// Trim whitespace, which is important for file-based tokens
|
||||
return strings.TrimSpace(string(content)), nil
|
||||
}
|
246
pkg/config/v1/value_source_test.go
Normal file
246
pkg/config/v1/value_source_test.go
Normal file
@ -0,0 +1,246 @@
|
||||
// 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 v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValueSource_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
vs *ValueSource
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "nil valueSource",
|
||||
vs: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unsupported type",
|
||||
vs: &ValueSource{
|
||||
Type: "unsupported",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "file type without file config",
|
||||
vs: &ValueSource{
|
||||
Type: "file",
|
||||
File: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid file type with absolute path",
|
||||
vs: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: "/tmp/test",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid file type with relative path",
|
||||
vs: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: "configs/token",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.vs.Validate()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValueSource.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSource_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fs *FileSource
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "nil fileSource",
|
||||
fs: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty path",
|
||||
fs: &FileSource{
|
||||
Path: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative path (allowed)",
|
||||
fs: &FileSource{
|
||||
Path: "relative/path",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "absolute path",
|
||||
fs: &FileSource{
|
||||
Path: "/absolute/path",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.fs.Validate()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("FileSource.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSource_Resolve(t *testing.T) {
|
||||
// Create a temporary file for testing
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "test_token")
|
||||
testContent := "test-token-value\n\t "
|
||||
expectedContent := "test-token-value"
|
||||
|
||||
err := os.WriteFile(testFile, []byte(testContent), 0o600)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fs *FileSource
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid file path",
|
||||
fs: &FileSource{
|
||||
Path: testFile,
|
||||
},
|
||||
want: expectedContent,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "non-existent file",
|
||||
fs: &FileSource{
|
||||
Path: "/non/existent/file",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "path traversal attempt (should fail validation)",
|
||||
fs: &FileSource{
|
||||
Path: "../../../etc/passwd",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.fs.Resolve(context.Background())
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("FileSource.Resolve() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("FileSource.Resolve() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSource_Resolve(t *testing.T) {
|
||||
// Create a temporary file for testing
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "test_token")
|
||||
testContent := "test-token-value"
|
||||
|
||||
err := os.WriteFile(testFile, []byte(testContent), 0o600)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
vs *ValueSource
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid file type",
|
||||
vs: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: testFile,
|
||||
},
|
||||
},
|
||||
want: testContent,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "unsupported type",
|
||||
vs: &ValueSource{
|
||||
Type: "unsupported",
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "file type with path traversal",
|
||||
vs: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: "../../../etc/passwd",
|
||||
},
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.vs.Resolve(ctx)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValueSource.Resolve() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ValueSource.Resolve() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -109,7 +109,7 @@ func (m *serverMetrics) NewProxy(name string, proxyType string) {
|
||||
m.info.ProxyTypeCounts[proxyType] = counter
|
||||
|
||||
proxyStats, ok := m.info.ProxyStatistics[name]
|
||||
if !(ok && proxyStats.ProxyType == proxyType) {
|
||||
if !ok || proxyStats.ProxyType != proxyType {
|
||||
proxyStats = &ProxyStatistics{
|
||||
Name: name,
|
||||
ProxyType: proxyType,
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/fatedier/golib/pool"
|
||||
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
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
|
||||
udpConnMap := make(map[string]*net.UDPConn)
|
||||
|
||||
@ -110,6 +111,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<-
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
udpConn, ok := udpConnMap[udpMsg.RemoteAddr.String()]
|
||||
if !ok {
|
||||
@ -122,6 +124,18 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<-
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
udpConn.Close()
|
||||
|
@ -3,16 +3,16 @@ package udp
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUdpPacket(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
buf := []byte("hello world")
|
||||
udpMsg := NewUDPPacket(buf, nil, nil)
|
||||
|
||||
newBuf, err := GetContent(udpMsg)
|
||||
assert.NoError(err)
|
||||
assert.EqualValues(buf, newBuf)
|
||||
require.NoError(err)
|
||||
require.EqualValues(buf, newBuf)
|
||||
}
|
||||
|
@ -105,7 +105,10 @@ func (s *TunnelServer) Run() error {
|
||||
s.writeToClient(err.Error())
|
||||
return fmt.Errorf("parse flags from ssh client error: %v", err)
|
||||
}
|
||||
clientCfg.Complete()
|
||||
if err := clientCfg.Complete(); err != nil {
|
||||
s.writeToClient(fmt.Sprintf("failed to complete client config: %v", err))
|
||||
return fmt.Errorf("complete client config error: %v", err)
|
||||
}
|
||||
if sshConn.Permissions != nil {
|
||||
clientCfg.User = util.EmptyOr(sshConn.Permissions.Extensions["user"], clientCfg.User)
|
||||
}
|
||||
|
@ -3,21 +3,21 @@ package metric
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
c := NewCounter()
|
||||
c.Inc(10)
|
||||
assert.EqualValues(10, c.Count())
|
||||
require.EqualValues(10, c.Count())
|
||||
|
||||
c.Dec(5)
|
||||
assert.EqualValues(5, c.Count())
|
||||
require.EqualValues(5, c.Count())
|
||||
|
||||
cTmp := c.Snapshot()
|
||||
assert.EqualValues(5, cTmp.Count())
|
||||
require.EqualValues(5, cTmp.Count())
|
||||
|
||||
c.Clear()
|
||||
assert.EqualValues(0, c.Count())
|
||||
require.EqualValues(0, c.Count())
|
||||
}
|
||||
|
@ -3,25 +3,25 @@ package metric
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDateCounter(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
dc := NewDateCounter(3)
|
||||
dc.Inc(10)
|
||||
assert.EqualValues(10, dc.TodayCount())
|
||||
require.EqualValues(10, dc.TodayCount())
|
||||
|
||||
dc.Dec(5)
|
||||
assert.EqualValues(5, dc.TodayCount())
|
||||
require.EqualValues(5, dc.TodayCount())
|
||||
|
||||
counts := dc.GetLastDaysCount(3)
|
||||
assert.EqualValues(3, len(counts))
|
||||
assert.EqualValues(5, counts[0])
|
||||
assert.EqualValues(0, counts[1])
|
||||
assert.EqualValues(0, counts[2])
|
||||
require.EqualValues(3, len(counts))
|
||||
require.EqualValues(5, counts[0])
|
||||
require.EqualValues(0, counts[1])
|
||||
require.EqualValues(0, counts[2])
|
||||
|
||||
dcTmp := dc.Snapshot()
|
||||
assert.EqualValues(5, dcTmp.TodayCount())
|
||||
require.EqualValues(5, dcTmp.TodayCount())
|
||||
}
|
||||
|
@ -197,11 +197,11 @@ func (statsConn *StatsConn) Close() (err error) {
|
||||
}
|
||||
|
||||
type wrapQuicStream struct {
|
||||
quic.Stream
|
||||
c quic.Connection
|
||||
*quic.Stream
|
||||
c *quic.Conn
|
||||
}
|
||||
|
||||
func QuicStreamToNetConn(s quic.Stream, c quic.Connection) net.Conn {
|
||||
func QuicStreamToNetConn(s *quic.Stream, c *quic.Conn) net.Conn {
|
||||
return &wrapQuicStream{
|
||||
Stream: s,
|
||||
c: c,
|
||||
@ -223,7 +223,7 @@ func (conn *wrapQuicStream) RemoteAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (conn *wrapQuicStream) Close() error {
|
||||
conn.Stream.CancelRead(0)
|
||||
conn.CancelRead(0)
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRandId(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
id, err := RandID()
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
t.Log(id)
|
||||
assert.Equal(16, len(id))
|
||||
require.Equal(16, len(id))
|
||||
}
|
||||
|
||||
func TestGetAuthKey(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
key := GetAuthKey("1234", 1488720000)
|
||||
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
|
||||
require.Equal("6df41a43725f0c770fd56379e12acf8c", key)
|
||||
}
|
||||
|
||||
func TestParseRangeNumbers(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
numbers, err := ParseRangeNumbers("2-5")
|
||||
if assert.NoError(err) {
|
||||
assert.Equal([]int64{2, 3, 4, 5}, numbers)
|
||||
}
|
||||
require.NoError(err)
|
||||
require.Equal([]int64{2, 3, 4, 5}, numbers)
|
||||
|
||||
numbers, err = ParseRangeNumbers("1")
|
||||
if assert.NoError(err) {
|
||||
assert.Equal([]int64{1}, numbers)
|
||||
}
|
||||
require.NoError(err)
|
||||
require.Equal([]int64{1}, numbers)
|
||||
|
||||
numbers, err = ParseRangeNumbers("3-5,8")
|
||||
if assert.NoError(err) {
|
||||
assert.Equal([]int64{3, 4, 5, 8}, numbers)
|
||||
}
|
||||
require.NoError(err)
|
||||
require.Equal([]int64{3, 4, 5, 8}, numbers)
|
||||
|
||||
numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ")
|
||||
if assert.NoError(err) {
|
||||
assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers)
|
||||
}
|
||||
require.NoError(err)
|
||||
require.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers)
|
||||
|
||||
_, err = ParseRangeNumbers("3-a")
|
||||
assert.Error(err)
|
||||
require.Error(err)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
package version
|
||||
|
||||
var version = "0.62.1"
|
||||
var version = "0.63.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
@ -225,11 +225,7 @@ func (rp *HTTPReverseProxy) getVhost(domain, location, routeByHTTPUser string) (
|
||||
// *.example.com
|
||||
// *.com
|
||||
domainSplit := strings.Split(domain, ".")
|
||||
for {
|
||||
if len(domainSplit) < 3 {
|
||||
break
|
||||
}
|
||||
|
||||
for len(domainSplit) >= 3 {
|
||||
domainSplit[0] = "*"
|
||||
domain = strings.Join(domainSplit, ".")
|
||||
vr, ok = findRouter(domain, location, routeByHTTPUser)
|
||||
|
@ -169,11 +169,7 @@ func (v *Muxer) getListener(name, path, httpUser string) (*Listener, bool) {
|
||||
}
|
||||
|
||||
domainSplit := strings.Split(name, ".")
|
||||
for {
|
||||
if len(domainSplit) < 3 {
|
||||
break
|
||||
}
|
||||
|
||||
for len(domainSplit) >= 3 {
|
||||
domainSplit[0] = "*"
|
||||
name = strings.Join(domainSplit, ".")
|
||||
|
||||
|
@ -37,7 +37,9 @@ type Client struct {
|
||||
|
||||
func NewClient(options ClientOptions) (*Client, error) {
|
||||
if options.Common != nil {
|
||||
options.Common.Complete()
|
||||
if err := options.Common.Complete(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ln := netpkg.NewInternalListener()
|
||||
|
@ -550,7 +550,7 @@ func (svr *Service) HandleQUICListener(l *quic.Listener) {
|
||||
return
|
||||
}
|
||||
// Start a new goroutine to handle connection.
|
||||
go func(ctx context.Context, frpConn quic.Connection) {
|
||||
go func(ctx context.Context, frpConn *quic.Conn) {
|
||||
for {
|
||||
stream, err := frpConn.AcceptStream(context.Background())
|
||||
if err != nil {
|
||||
|
@ -24,12 +24,14 @@ type generalTestConfigures struct {
|
||||
}
|
||||
|
||||
func renderBindPortConfig(protocol string) string {
|
||||
if protocol == "kcp" {
|
||||
switch protocol {
|
||||
case "kcp":
|
||||
return fmt.Sprintf(`kcp_bind_port = {{ .%s }}`, consts.PortServerName)
|
||||
} else if protocol == "quic" {
|
||||
case "quic":
|
||||
return fmt.Sprintf(`quic_bind_port = {{ .%s }}`, consts.PortServerName)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
|
||||
|
@ -223,7 +223,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.PingContent)
|
||||
record = content.Ping.PrivilegeKey
|
||||
record = content.PrivilegeKey
|
||||
ret.Unchange = true
|
||||
return &ret
|
||||
}
|
||||
@ -273,7 +273,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.NewWorkConnContent)
|
||||
record = content.NewWorkConn.RunID
|
||||
record = content.RunID
|
||||
ret.Unchange = true
|
||||
return &ret
|
||||
}
|
||||
|
@ -24,12 +24,14 @@ type generalTestConfigures struct {
|
||||
}
|
||||
|
||||
func renderBindPortConfig(protocol string) string {
|
||||
if protocol == "kcp" {
|
||||
switch protocol {
|
||||
case "kcp":
|
||||
return fmt.Sprintf(`kcpBindPort = {{ .%s }}`, consts.PortServerName)
|
||||
} else if protocol == "quic" {
|
||||
case "quic":
|
||||
return fmt.Sprintf(`quicBindPort = {{ .%s }}`, consts.PortServerName)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
|
||||
|
217
test/e2e/v1/basic/token_source.go
Normal file
217
test/e2e/v1/basic/token_source.go
Normal file
@ -0,0 +1,217 @@
|
||||
// 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 basic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/port"
|
||||
)
|
||||
|
||||
var _ = ginkgo.Describe("[Feature: TokenSource]", func() {
|
||||
f := framework.NewDefaultFramework()
|
||||
|
||||
ginkgo.Describe("File-based token loading", func() {
|
||||
ginkgo.It("should work with file tokenSource", func() {
|
||||
// Create a temporary token file
|
||||
tmpDir := f.TempDirectory
|
||||
tokenFile := filepath.Join(tmpDir, "test_token")
|
||||
tokenContent := "test-token-123"
|
||||
|
||||
err := os.WriteFile(tokenFile, []byte(tokenContent), 0o600)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
portName := port.GenName("TCP")
|
||||
|
||||
// Server config with tokenSource
|
||||
serverConf += fmt.Sprintf(`
|
||||
auth.tokenSource.type = "file"
|
||||
auth.tokenSource.file.path = "%s"
|
||||
`, tokenFile)
|
||||
|
||||
// Client config with matching token
|
||||
clientConf += fmt.Sprintf(`
|
||||
auth.token = "%s"
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = {{ .%s }}
|
||||
remotePort = {{ .%s }}
|
||||
`, tokenContent, framework.TCPEchoServerPort, portName)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).PortName(portName).Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("should work with client tokenSource", func() {
|
||||
// Create a temporary token file
|
||||
tmpDir := f.TempDirectory
|
||||
tokenFile := filepath.Join(tmpDir, "client_token")
|
||||
tokenContent := "client-token-456"
|
||||
|
||||
err := os.WriteFile(tokenFile, []byte(tokenContent), 0o600)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
portName := port.GenName("TCP")
|
||||
|
||||
// Server config with matching token
|
||||
serverConf += fmt.Sprintf(`
|
||||
auth.token = "%s"
|
||||
`, tokenContent)
|
||||
|
||||
// Client config with tokenSource
|
||||
clientConf += fmt.Sprintf(`
|
||||
auth.tokenSource.type = "file"
|
||||
auth.tokenSource.file.path = "%s"
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = {{ .%s }}
|
||||
remotePort = {{ .%s }}
|
||||
`, tokenFile, framework.TCPEchoServerPort, portName)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).PortName(portName).Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("should work with both server and client tokenSource", func() {
|
||||
// Create temporary token files
|
||||
tmpDir := f.TempDirectory
|
||||
serverTokenFile := filepath.Join(tmpDir, "server_token")
|
||||
clientTokenFile := filepath.Join(tmpDir, "client_token")
|
||||
tokenContent := "shared-token-789"
|
||||
|
||||
err := os.WriteFile(serverTokenFile, []byte(tokenContent), 0o600)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
err = os.WriteFile(clientTokenFile, []byte(tokenContent), 0o600)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
portName := port.GenName("TCP")
|
||||
|
||||
// Server config with tokenSource
|
||||
serverConf += fmt.Sprintf(`
|
||||
auth.tokenSource.type = "file"
|
||||
auth.tokenSource.file.path = "%s"
|
||||
`, serverTokenFile)
|
||||
|
||||
// Client config with tokenSource
|
||||
clientConf += fmt.Sprintf(`
|
||||
auth.tokenSource.type = "file"
|
||||
auth.tokenSource.file.path = "%s"
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = {{ .%s }}
|
||||
remotePort = {{ .%s }}
|
||||
`, clientTokenFile, framework.TCPEchoServerPort, portName)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).PortName(portName).Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("should fail with mismatched tokens", func() {
|
||||
// Create temporary token files with different content
|
||||
tmpDir := f.TempDirectory
|
||||
serverTokenFile := filepath.Join(tmpDir, "server_token")
|
||||
clientTokenFile := filepath.Join(tmpDir, "client_token")
|
||||
|
||||
err := os.WriteFile(serverTokenFile, []byte("server-token"), 0o600)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
err = os.WriteFile(clientTokenFile, []byte("client-token"), 0o600)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
portName := port.GenName("TCP")
|
||||
|
||||
// Server config with tokenSource
|
||||
serverConf += fmt.Sprintf(`
|
||||
auth.tokenSource.type = "file"
|
||||
auth.tokenSource.file.path = "%s"
|
||||
`, serverTokenFile)
|
||||
|
||||
// Client config with different tokenSource
|
||||
clientConf += fmt.Sprintf(`
|
||||
auth.tokenSource.type = "file"
|
||||
auth.tokenSource.file.path = "%s"
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = {{ .%s }}
|
||||
remotePort = {{ .%s }}
|
||||
`, clientTokenFile, framework.TCPEchoServerPort, portName)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
// This should fail due to token mismatch - the client should not be able to connect
|
||||
// We expect the request to fail because the proxy tunnel is not established
|
||||
framework.NewRequestExpect(f).PortName(portName).ExpectError(true).Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("should fail with non-existent token file", func() {
|
||||
// This test verifies that server fails to start when tokenSource points to non-existent file
|
||||
// We'll verify this by checking that the configuration loading itself fails
|
||||
|
||||
// Create a config that references a non-existent file
|
||||
tmpDir := f.TempDirectory
|
||||
nonExistentFile := filepath.Join(tmpDir, "non_existent_token")
|
||||
|
||||
serverConf := consts.DefaultServerConfig
|
||||
|
||||
// Server config with non-existent tokenSource file
|
||||
serverConf += fmt.Sprintf(`
|
||||
auth.tokenSource.type = "file"
|
||||
auth.tokenSource.file.path = "%s"
|
||||
`, nonExistentFile)
|
||||
|
||||
// The test expectation is that this will fail during the RunProcesses call
|
||||
// because the server cannot load the configuration due to missing token file
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Expected: server should fail to start due to missing file
|
||||
ginkgo.By(fmt.Sprintf("Server correctly failed to start: %v", r))
|
||||
}
|
||||
}()
|
||||
|
||||
// This should cause a panic or error during server startup
|
||||
f.RunProcesses([]string{serverConf}, []string{})
|
||||
})
|
||||
})
|
||||
})
|
@ -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() {
|
||||
vhostHTTPPort := f.AllocPort()
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
|
@ -232,7 +232,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.PingContent)
|
||||
record = content.Ping.PrivilegeKey
|
||||
record = content.PrivilegeKey
|
||||
ret.Unchange = true
|
||||
return &ret
|
||||
}
|
||||
@ -284,7 +284,7 @@ var _ = ginkgo.Describe("[Feature: Server-Plugins]", func() {
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.NewWorkConnContent)
|
||||
record = content.NewWorkConn.RunID
|
||||
record = content.RunID
|
||||
ret.Unchange = true
|
||||
return &ret
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user