Compare commits

..

1 Commits

Author SHA1 Message Date
fatedier
de2ae03af2 fix ini configuration default values 2024-05-30 10:26:00 +08:00
131 changed files with 755 additions and 3416 deletions

View File

@@ -2,7 +2,7 @@ version: 2
jobs: jobs:
go-version-latest: go-version-latest:
docker: docker:
- image: cimg/go:1.23-node - image: cimg/go:1.22-node
resource_class: large resource_class: large
steps: steps:
- checkout - checkout

2
.github/FUNDING.yml vendored
View File

@@ -1,4 +1,4 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [fatedier] github: [fatedier]
custom: ["https://afdian.com/a/fatedier"] custom: ["https://afdian.net/a/fatedier"]

View File

@@ -17,13 +17,13 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
with: with:
go-version: '1.23' go-version: '1.22'
cache: false cache: false
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v8 uses: golangci/golangci-lint-action@v4
with: with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: v2.1 version: v1.57
# Optional: golangci-lint command line arguments. # Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0 # args: --issues-exit-code=0
@@ -34,3 +34,9 @@ jobs:
# Optional: if set to true then the all caching functionality will be complete disabled, # Optional: if set to true then the all caching functionality will be complete disabled,
# takes precedence over all other caching options. # takes precedence over all other caching options.
# skip-cache: true # skip-cache: true
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
# skip-pkg-cache: true
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
# skip-build-cache: true

View File

@@ -15,7 +15,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: '1.23' go-version: '1.22'
- name: Make All - name: Make All
run: | run: |

View File

@@ -1,4 +1,4 @@
name: "Close stale issues and PRs" name: "Close stale issues"
on: on:
schedule: schedule:
- cron: "20 0 * * *" - cron: "20 0 * * *"
@@ -21,14 +21,14 @@ jobs:
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v9
with: with:
stale-issue-message: 'Issues go stale after 14d of inactivity. Stale issues rot after an additional 3d of inactivity and eventually close.' stale-issue-message: 'Issues go stale after 21d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
stale-pr-message: "PRs go stale after 14d of inactivity. Stale PRs rot after an additional 3d of inactivity and eventually close." stale-pr-message: "PRs go stale after 21d of inactivity. Stale PRs rot after an additional 7d of inactivity and eventually close."
stale-issue-label: 'lifecycle/stale' stale-issue-label: 'lifecycle/stale'
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned' exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
stale-pr-label: 'lifecycle/stale' stale-pr-label: 'lifecycle/stale'
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned' exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
days-before-stale: 14 days-before-stale: 21
days-before-close: 3 days-before-close: 7
debug-only: ${{ github.event.inputs.debug-only }} debug-only: ${{ github.event.inputs.debug-only }}
exempt-all-pr-milestones: true exempt-all-pr-milestones: true
exempt-all-pr-assignees: true exempt-all-pr-assignees: true

3
.gitignore vendored
View File

@@ -39,6 +39,3 @@ client.key
# Cache # Cache
*.swp *.swp
# AI
CLAUDE.md

View File

@@ -1,112 +1,136 @@
version: "2" service:
golangci-lint-version: 1.57.x # use the fixed version to not introduce new linters unexpectedly
run: run:
concurrency: 4 concurrency: 4
timeout: 20m # timeout for analysis, e.g. 30s, 5m, default is 1m
deadline: 20m
build-tags: build-tags:
- integ - integ
- integfuzz - integfuzz
linters: linters:
default: none disable-all: true
enable: enable:
- asciicheck - unused
- copyloopvar
- errcheck - errcheck
- exportloopref
- gocritic - gocritic
- gosec - gofumpt
- goimports
- revive
- gosimple
- govet - govet
- ineffassign - ineffassign
- lll - lll
- makezero
- misspell - misspell
- prealloc
- predeclared
- revive
- staticcheck - staticcheck
- stylecheck
- typecheck
- unconvert - unconvert
- unparam - 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 - gci
- gofumpt - gosec
- goimports - asciicheck
settings: - prealloc
gci: - predeclared
sections: - makezero
- standard fast: false
- default
- prefix(github.com/fatedier/frp/) linters-settings:
exclusions: errcheck:
generated: lax # report about not checking of errors in type assetions: `a := b.(MyStruct)`;
paths: # default is false: such cases aren't reported by default.
- .*\.pb\.go check-type-assertions: false
- .*\.gen\.go
- genfiles$ # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
- vendor$ # default is false: such cases aren't reported by default.
- bin$ check-blank: false
- third_party$ govet:
- builtin$ # report about shadowed variables
- examples$ check-shadowing: false
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
- G501
issues: issues:
max-issues-per-linter: 0 # 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-same-issues: 0 max-same-issues: 0

View File

@@ -2,7 +2,7 @@ export PATH := $(PATH):`go env GOPATH`/bin
export GO111MODULE=on export GO111MODULE=on
LDFLAGS := -s -w LDFLAGS := -s -w
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 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 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 android:arm64
all: build all: build

View File

@@ -7,25 +7,15 @@
[README](README.md) | [中文文档](README_zh.md) [README](README.md) | [中文文档](README_zh.md)
## Sponsors
frp is an open source project with its ongoing development made possible entirely by the support of our awesome sponsors. If you'd like to join them, please consider [sponsoring frp's development](https://github.com/sponsors/fatedier).
<h3 align="center">Gold Sponsors</h3> <h3 align="center">Gold Sponsors</h3>
<!--gold sponsors start--> <!--gold sponsors start-->
<p align="center"> <p align="center">
<a href="https://jb.gg/frp" target="_blank"> <a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg"> <img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
</a> </a>
</p> <a>&nbsp</a>
<p align="center">
<a href="https://github.com/daytonaio/daytona" target="_blank"> <a href="https://github.com/daytonaio/daytona" target="_blank">
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png"> <img width="360px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
</a>
</p>
<p align="center">
<a href="https://github.com/beclab/Olares" target="_blank">
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_olares.jpeg">
</a> </a>
</p> </p>
<!--gold sponsors end--> <!--gold sponsors end-->
@@ -92,12 +82,7 @@ frp also offers a P2P connect mode.
* [Client Plugins](#client-plugins) * [Client Plugins](#client-plugins)
* [Server Manage Plugins](#server-manage-plugins) * [Server Manage Plugins](#server-manage-plugins)
* [SSH Tunnel Gateway](#ssh-tunnel-gateway) * [SSH Tunnel Gateway](#ssh-tunnel-gateway)
* [Virtual Network (VirtualNet)](#virtual-network-virtualnet) * [Releated Projects](#releated-projects)
* [Feature Gates](#feature-gates)
* [Available Feature Gates](#available-feature-gates)
* [Enabling Feature Gates](#enabling-feature-gates)
* [Feature Lifecycle](#feature-lifecycle)
* [Related Projects](#related-projects)
* [Contributing](#contributing) * [Contributing](#contributing)
* [Donation](#donation) * [Donation](#donation)
* [GitHub Sponsors](#github-sponsors) * [GitHub Sponsors](#github-sponsors)
@@ -1025,7 +1010,7 @@ You can get user's real IP from HTTP request headers `X-Forwarded-For`.
#### Proxy Protocol #### Proxy Protocol
frp supports Proxy Protocol to send user's real IP to local services. frp supports Proxy Protocol to send user's real IP to local services. It support all types except UDP.
Here is an example for https service: Here is an example for https service:
@@ -1260,40 +1245,7 @@ frpc tcp --proxy_name "test-tcp" --local_ip 127.0.0.1 --local_port 8080 --remote
Please refer to this [document](/doc/ssh_tunnel_gateway.md) for more information. Please refer to this [document](/doc/ssh_tunnel_gateway.md) for more information.
### Virtual Network (VirtualNet) ## Releated Projects
*Alpha feature added in v0.62.0*
The VirtualNet feature enables frp to create and manage virtual network connections between clients and visitors through a TUN interface. This allows for IP-level routing between machines, extending frp beyond simple port forwarding to support full network connectivity.
For detailed information about configuration and usage, please refer to the [VirtualNet documentation](/doc/virtual_net.md).
## Feature Gates
frp supports feature gates to enable or disable experimental features. This allows users to try out new features before they're considered stable.
### Available Feature Gates
| Name | Stage | Default | Description |
|------|-------|---------|-------------|
| VirtualNet | ALPHA | false | Virtual network capabilities for frp |
### Enabling Feature Gates
To enable an experimental feature, add the feature gate to your configuration:
```toml
featureGates = { VirtualNet = true }
```
### Feature Lifecycle
Features typically go through three stages:
1. **ALPHA**: Disabled by default, may be unstable
2. **BETA**: May be enabled by default, more stable but still evolving
3. **GA (Generally Available)**: Enabled by default, ready for production use
## Related Projects
* [gofrp/plugin](https://github.com/gofrp/plugin) - A repository for frp plugins that contains a variety of plugins implemented based on the frp extension mechanism, meeting the customization needs of different scenarios. * [gofrp/plugin](https://github.com/gofrp/plugin) - A repository for frp plugins that contains a variety of plugins implemented based on the frp extension mechanism, meeting the customization needs of different scenarios.
* [gofrp/tiny-frpc](https://github.com/gofrp/tiny-frpc) - A lightweight version of the frp client (around 3.5MB at minimum) implemented using the ssh protocol, supporting some of the most commonly used features, suitable for devices with limited resources. * [gofrp/tiny-frpc](https://github.com/gofrp/tiny-frpc) - A lightweight version of the frp client (around 3.5MB at minimum) implemented using the ssh protocol, supporting some of the most commonly used features, suitable for devices with limited resources.

View File

@@ -9,25 +9,15 @@
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议,且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议,且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
## Sponsors
frp 是一个完全开源的项目,我们的开发工作完全依靠赞助者们的支持。如果你愿意加入他们的行列,请考虑 [赞助 frp 的开发](https://github.com/sponsors/fatedier)。
<h3 align="center">Gold Sponsors</h3> <h3 align="center">Gold Sponsors</h3>
<!--gold sponsors start--> <!--gold sponsors start-->
<p align="center"> <p align="center">
<a href="https://jb.gg/frp" target="_blank"> <a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg"> <img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
</a> </a>
</p> <a>&nbsp</a>
<p align="center">
<a href="https://github.com/daytonaio/daytona" target="_blank"> <a href="https://github.com/daytonaio/daytona" target="_blank">
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png"> <img width="360px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
</a>
</p>
<p align="center">
<a href="https://github.com/beclab/Olares" target="_blank">
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_olares.jpeg">
</a> </a>
</p> </p>
<!--gold sponsors end--> <!--gold sponsors end-->
@@ -99,7 +89,7 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。 您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
国内用户可以通过 [爱发电](https://afdian.com/a/fatedier) 赞助我们。 国内用户可以通过 [爱发电](https://afdian.net/a/fatedier) 赞助我们。
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。 企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。

View File

@@ -1,4 +1,9 @@
## Features ### Fixes
* Support for YAML merge functionality (anchors and references with dot-prefixed fields) in strict configuration mode without requiring `--strict-config=false` parameter. * Fixed an issue where HTTP/2 was not enabled for https2http and https2https plugins.
* Support for proxy protocol in UDP proxies to preserve real client IP addresses. * Fixed the issue where the default values of INI configuration parameters are inconsistent with other configuration formats.
### Changes
* Updated the default value of `transport.tcpMuxKeepaliveInterval` from 60 to 30.
* On the Android platform, the Google DNS server is used only when the default DNS server cannot be obtained.

View File

@@ -165,9 +165,9 @@ func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
res StatusResp = make(map[string][]ProxyStatusResp) res StatusResp = make(map[string][]ProxyStatusResp)
) )
log.Infof("http request [/api/status]") log.Infof("Http request [/api/status]")
defer func() { defer func() {
log.Infof("http response [/api/status]") log.Infof("Http response [/api/status]")
buf, _ = json.Marshal(&res) buf, _ = json.Marshal(&res)
_, _ = w.Write(buf) _, _ = w.Write(buf)
}() }()
@@ -198,9 +198,9 @@ func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) { func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
res := GeneralResponse{Code: 200} res := GeneralResponse{Code: 200}
log.Infof("http get request [/api/config]") log.Infof("Http get request [/api/config]")
defer func() { defer func() {
log.Infof("http get response [/api/config], code [%d]", res.Code) log.Infof("Http get response [/api/config], code [%d]", res.Code)
w.WriteHeader(res.Code) w.WriteHeader(res.Code)
if len(res.Msg) > 0 { if len(res.Msg) > 0 {
_, _ = w.Write([]byte(res.Msg)) _, _ = w.Write([]byte(res.Msg))
@@ -228,9 +228,9 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) { func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200} res := GeneralResponse{Code: 200}
log.Infof("http put request [/api/config]") log.Infof("Http put request [/api/config]")
defer func() { defer func() {
log.Infof("http put response [/api/config], code [%d]", res.Code) log.Infof("Http put response [/api/config], code [%d]", res.Code)
w.WriteHeader(res.Code) w.WriteHeader(res.Code)
if len(res.Msg) > 0 { if len(res.Msg) > 0 {
_, _ = w.Write([]byte(res.Msg)) _, _ = w.Write([]byte(res.Msg))

View File

@@ -29,7 +29,6 @@ import (
netpkg "github.com/fatedier/frp/pkg/util/net" netpkg "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/wait" "github.com/fatedier/frp/pkg/util/wait"
"github.com/fatedier/frp/pkg/util/xlog" "github.com/fatedier/frp/pkg/util/xlog"
"github.com/fatedier/frp/pkg/vnet"
) )
type SessionContext struct { type SessionContext struct {
@@ -47,8 +46,6 @@ type SessionContext struct {
AuthSetter auth.Setter AuthSetter auth.Setter
// Connector is used to create new connections, which could be real TCP connections or virtual streams. // Connector is used to create new connections, which could be real TCP connections or virtual streams.
Connector Connector Connector Connector
// Virtual net controller
VnetController *vnet.Controller
} }
type Control struct { type Control struct {
@@ -102,9 +99,8 @@ func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, erro
ctl.registerMsgHandlers() ctl.registerMsgHandlers()
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher.SendChannel()) ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher.SendChannel())
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, ctl.msgTransporter, sessionCtx.VnetController) ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, ctl.msgTransporter)
ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common, ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common, ctl.connectServer, ctl.msgTransporter)
ctl.connectServer, ctl.msgTransporter, sessionCtx.VnetController)
return ctl, nil return ctl, nil
} }
@@ -189,7 +185,7 @@ func (ctl *Control) handlePong(m msg.Message) {
inMsg := m.(*msg.Pong) inMsg := m.(*msg.Pong)
if inMsg.Error != "" { if inMsg.Error != "" {
xl.Errorf("pong message contains error: %s", inMsg.Error) xl.Errorf("Pong message contains error: %s", inMsg.Error)
ctl.closeSession() ctl.closeSession()
return return
} }
@@ -234,7 +230,7 @@ func (ctl *Control) registerMsgHandlers() {
ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong) ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
} }
// heartbeatWorker sends heartbeat to server and check heartbeat timeout. // headerWorker sends heartbeat to server and check heartbeat timeout.
func (ctl *Control) heartbeatWorker() { func (ctl *Control) heartbeatWorker() {
xl := ctl.xl xl := ctl.xl

View File

@@ -8,7 +8,7 @@ import (
var ErrPayloadType = errors.New("error payload type") var ErrPayloadType = errors.New("error payload type")
type Handler func(payload any) error type Handler func(payload interface{}) error
type StartProxyPayload struct { type StartProxyPayload struct {
NewProxyMsg *msg.NewProxy NewProxyMsg *msg.NewProxy

View File

@@ -20,11 +20,13 @@ import (
"net" "net"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
libio "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
libnet "github.com/fatedier/golib/net" libnet "github.com/fatedier/golib/net"
pp "github.com/pires/go-proxyproto"
"golang.org/x/time/rate" "golang.org/x/time/rate"
"github.com/fatedier/frp/pkg/config/types" "github.com/fatedier/frp/pkg/config/types"
@@ -33,9 +35,7 @@ import (
plugin "github.com/fatedier/frp/pkg/plugin/client" plugin "github.com/fatedier/frp/pkg/plugin/client"
"github.com/fatedier/frp/pkg/transport" "github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/limit" "github.com/fatedier/frp/pkg/util/limit"
netpkg "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/xlog" "github.com/fatedier/frp/pkg/util/xlog"
"github.com/fatedier/frp/pkg/vnet"
) )
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{} var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{}
@@ -58,7 +58,6 @@ func NewProxy(
pxyConf v1.ProxyConfigurer, pxyConf v1.ProxyConfigurer,
clientCfg *v1.ClientCommonConfig, clientCfg *v1.ClientCommonConfig,
msgTransporter transport.MessageTransporter, msgTransporter transport.MessageTransporter,
vnetController *vnet.Controller,
) (pxy Proxy) { ) (pxy Proxy) {
var limiter *rate.Limiter var limiter *rate.Limiter
limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes() limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes()
@@ -71,7 +70,6 @@ func NewProxy(
clientCfg: clientCfg, clientCfg: clientCfg,
limiter: limiter, limiter: limiter,
msgTransporter: msgTransporter, msgTransporter: msgTransporter,
vnetController: vnetController,
xl: xlog.FromContextSafe(ctx), xl: xlog.FromContextSafe(ctx),
ctx: ctx, ctx: ctx,
} }
@@ -87,7 +85,6 @@ type BaseProxy struct {
baseCfg *v1.ProxyBaseConfig baseCfg *v1.ProxyBaseConfig
clientCfg *v1.ClientCommonConfig clientCfg *v1.ClientCommonConfig
msgTransporter transport.MessageTransporter msgTransporter transport.MessageTransporter
vnetController *vnet.Controller
limiter *rate.Limiter limiter *rate.Limiter
// proxyPlugin is used to handle connections instead of dialing to local service. // proxyPlugin is used to handle connections instead of dialing to local service.
// It's only validate for TCP protocol now. // It's only validate for TCP protocol now.
@@ -101,10 +98,7 @@ type BaseProxy struct {
func (pxy *BaseProxy) Run() error { func (pxy *BaseProxy) Run() error {
if pxy.baseCfg.Plugin.Type != "" { if pxy.baseCfg.Plugin.Type != "" {
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, plugin.PluginContext{ p, err := plugin.Create(pxy.baseCfg.Plugin.Type, pxy.baseCfg.Plugin.ClientPluginOptions)
Name: pxy.baseCfg.Name,
VnetController: pxy.vnetController,
}, pxy.baseCfg.Plugin.ClientPluginOptions)
if err != nil { if err != nil {
return err return err
} }
@@ -163,29 +157,42 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
} }
// check if we need to send proxy protocol info // check if we need to send proxy protocol info
var connInfo plugin.ConnectionInfo var extraInfo plugin.ExtraInfo
if m.SrcAddr != "" && m.SrcPort != 0 { if m.SrcAddr != "" && m.SrcPort != 0 {
if m.DstAddr == "" { if m.DstAddr == "" {
m.DstAddr = "127.0.0.1" m.DstAddr = "127.0.0.1"
} }
srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort)))) srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort)))) dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
connInfo.SrcAddr = srcAddr extraInfo.SrcAddr = srcAddr
connInfo.DstAddr = dstAddr extraInfo.DstAddr = dstAddr
} }
if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 { if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
// Use the common proxy protocol builder function h := &pp.Header{
header := netpkg.BuildProxyProtocolHeaderStruct(connInfo.SrcAddr, connInfo.DstAddr, baseCfg.Transport.ProxyProtocolVersion) Command: pp.PROXY,
connInfo.ProxyProtocolHeader = header SourceAddr: extraInfo.SrcAddr,
DestinationAddr: extraInfo.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
}
extraInfo.ProxyProtocolHeader = h
} }
connInfo.Conn = remote
connInfo.UnderlyingConn = workConn
if pxy.proxyPlugin != nil { if pxy.proxyPlugin != nil {
// if plugin is set, let plugin handle connection first // if plugin is set, let plugin handle connection first
xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name()) xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name())
pxy.proxyPlugin.Handle(pxy.ctx, &connInfo) pxy.proxyPlugin.Handle(remote, workConn, &extraInfo)
xl.Debugf("handle by plugin finished") xl.Debugf("handle by plugin finished")
return return
} }
@@ -203,8 +210,8 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
xl.Debugf("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(), xl.Debugf("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String()) localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
if connInfo.ProxyProtocolHeader != nil { if extraInfo.ProxyProtocolHeader != nil {
if _, err := connInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil { if _, err := extraInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
workConn.Close() workConn.Close()
xl.Errorf("write proxy protocol header to local conn error: %v", err) xl.Errorf("write proxy protocol header to local conn error: %v", err)
return return

View File

@@ -28,14 +28,12 @@ import (
"github.com/fatedier/frp/pkg/msg" "github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/transport" "github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/xlog" "github.com/fatedier/frp/pkg/util/xlog"
"github.com/fatedier/frp/pkg/vnet"
) )
type Manager struct { type Manager struct {
proxies map[string]*Wrapper proxies map[string]*Wrapper
msgTransporter transport.MessageTransporter msgTransporter transport.MessageTransporter
inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
vnetController *vnet.Controller
closed bool closed bool
mu sync.RWMutex mu sync.RWMutex
@@ -49,12 +47,10 @@ func NewManager(
ctx context.Context, ctx context.Context,
clientCfg *v1.ClientCommonConfig, clientCfg *v1.ClientCommonConfig,
msgTransporter transport.MessageTransporter, msgTransporter transport.MessageTransporter,
vnetController *vnet.Controller,
) *Manager { ) *Manager {
return &Manager{ return &Manager{
proxies: make(map[string]*Wrapper), proxies: make(map[string]*Wrapper),
msgTransporter: msgTransporter, msgTransporter: msgTransporter,
vnetController: vnetController,
closed: false, closed: false,
clientCfg: clientCfg, clientCfg: clientCfg,
ctx: ctx, ctx: ctx,
@@ -100,7 +96,7 @@ func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWo
} }
} }
func (pm *Manager) HandleEvent(payload any) error { func (pm *Manager) HandleEvent(payload interface{}) error {
var m msg.Message var m msg.Message
switch e := payload.(type) { switch e := payload.(type) {
case *event.StartProxyPayload: case *event.StartProxyPayload:
@@ -163,7 +159,7 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
for _, cfg := range proxyCfgs { for _, cfg := range proxyCfgs {
name := cfg.GetBaseConfig().Name name := cfg.GetBaseConfig().Name
if _, ok := pm.proxies[name]; !ok { if _, ok := pm.proxies[name]; !ok {
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter, pm.vnetController) pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter)
if pm.inWorkConnCallback != nil { if pm.inWorkConnCallback != nil {
pxy.SetInWorkConnCallback(pm.inWorkConnCallback) pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
} }

View File

@@ -31,7 +31,6 @@ import (
"github.com/fatedier/frp/pkg/msg" "github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/transport" "github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/xlog" "github.com/fatedier/frp/pkg/util/xlog"
"github.com/fatedier/frp/pkg/vnet"
) )
const ( const (
@@ -74,8 +73,6 @@ type Wrapper struct {
handler event.Handler handler event.Handler
msgTransporter transport.MessageTransporter msgTransporter transport.MessageTransporter
// vnet controller
vnetController *vnet.Controller
health uint32 health uint32
lastSendStartMsg time.Time lastSendStartMsg time.Time
@@ -94,7 +91,6 @@ func NewWrapper(
clientCfg *v1.ClientCommonConfig, clientCfg *v1.ClientCommonConfig,
eventHandler event.Handler, eventHandler event.Handler,
msgTransporter transport.MessageTransporter, msgTransporter transport.MessageTransporter,
vnetController *vnet.Controller,
) *Wrapper { ) *Wrapper {
baseInfo := cfg.GetBaseConfig() baseInfo := cfg.GetBaseConfig()
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name) xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name)
@@ -109,7 +105,6 @@ func NewWrapper(
healthNotifyCh: make(chan struct{}), healthNotifyCh: make(chan struct{}),
handler: eventHandler, handler: eventHandler,
msgTransporter: msgTransporter, msgTransporter: msgTransporter,
vnetController: vnetController,
xl: xl, xl: xl,
ctx: xlog.NewContext(ctx, xl), ctx: xlog.NewContext(ctx, xl),
} }
@@ -122,7 +117,7 @@ func NewWrapper(
xl.Tracef("enable health check monitor") xl.Tracef("enable health check monitor")
} }
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter, pw.vnetController) pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter)
return pw return pw
} }
@@ -142,7 +137,7 @@ func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
pw.Phase = ProxyPhaseStartErr pw.Phase = ProxyPhaseStartErr
pw.Err = respErr pw.Err = respErr
pw.lastStartErr = time.Now() pw.lastStartErr = time.Now()
return fmt.Errorf("%s", pw.Err) return fmt.Errorf(pw.Err)
} }
if err := pw.pxy.Run(); err != nil { if err := pw.pxy.Run(); err != nil {

View File

@@ -205,5 +205,5 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
go workConnReaderFn(workConn, readCh) go workConnReaderFn(workConn, readCh)
go heartbeatFn(sendCh) go heartbeatFn(sendCh)
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize), pxy.cfg.Transport.ProxyProtocolVersion) udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
} }

View File

@@ -171,7 +171,5 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
go workConnSenderFn(pxy.workConn, pxy.sendCh) go workConnSenderFn(pxy.workConn, pxy.sendCh)
go workConnReaderFn(pxy.workConn, pxy.readCh) go workConnReaderFn(pxy.workConn, pxy.readCh)
go heartbeatFn(pxy.sendCh) go heartbeatFn(pxy.sendCh)
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
// Call Forwarder with proxy protocol version (empty string means no proxy protocol)
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize), pxy.cfg.Transport.ProxyProtocolVersion)
} }

View File

@@ -37,7 +37,6 @@ import (
"github.com/fatedier/frp/pkg/util/version" "github.com/fatedier/frp/pkg/util/version"
"github.com/fatedier/frp/pkg/util/wait" "github.com/fatedier/frp/pkg/util/wait"
"github.com/fatedier/frp/pkg/util/xlog" "github.com/fatedier/frp/pkg/util/xlog"
"github.com/fatedier/frp/pkg/vnet"
) )
func init() { func init() {
@@ -111,8 +110,6 @@ type Service struct {
// web server for admin UI and apis // web server for admin UI and apis
webServer *httppkg.Server webServer *httppkg.Server
vnetController *vnet.Controller
cfgMu sync.RWMutex cfgMu sync.RWMutex
common *v1.ClientCommonConfig common *v1.ClientCommonConfig
proxyCfgs []v1.ProxyConfigurer proxyCfgs []v1.ProxyConfigurer
@@ -159,9 +156,6 @@ func NewService(options ServiceOptions) (*Service, error) {
if webServer != nil { if webServer != nil {
webServer.RouteRegister(s.registerRouteHandlers) webServer.RouteRegister(s.registerRouteHandlers)
} }
if options.Common.VirtualNet.Address != "" {
s.vnetController = vnet.NewController(options.Common.VirtualNet)
}
return s, nil return s, nil
} }
@@ -175,28 +169,6 @@ func (svr *Service) Run(ctx context.Context) error {
netpkg.SetDefaultDNSAddress(svr.common.DNSServer) netpkg.SetDefaultDNSAddress(svr.common.DNSServer)
} }
if svr.vnetController != nil {
if err := svr.vnetController.Init(); err != nil {
log.Errorf("init virtual network controller error: %v", err)
return err
}
go func() {
log.Infof("virtual network controller start...")
if err := svr.vnetController.Run(); err != nil {
log.Warnf("virtual network controller exit with error: %v", err)
}
}()
}
if svr.webServer != nil {
go func() {
log.Infof("admin server listen on %s", svr.webServer.Address())
if err := svr.webServer.Run(); err != nil {
log.Warnf("admin server exit with error: %v", err)
}
}()
}
// first login to frps // first login to frps
svr.loopLoginUntilSuccess(10*time.Second, lo.FromPtr(svr.common.LoginFailExit)) svr.loopLoginUntilSuccess(10*time.Second, lo.FromPtr(svr.common.LoginFailExit))
if svr.ctl == nil { if svr.ctl == nil {
@@ -207,6 +179,14 @@ func (svr *Service) Run(ctx context.Context) error {
go svr.keepControllerWorking() go svr.keepControllerWorking()
if svr.webServer != nil {
go func() {
log.Infof("admin server listen on %s", svr.webServer.Address())
if err := svr.webServer.Run(); err != nil {
log.Warnf("admin server exit with error: %v", err)
}
}()
}
<-svr.ctx.Done() <-svr.ctx.Done()
svr.stop() svr.stop()
return nil return nil
@@ -325,22 +305,22 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
proxyCfgs := svr.proxyCfgs proxyCfgs := svr.proxyCfgs
visitorCfgs := svr.visitorCfgs visitorCfgs := svr.visitorCfgs
svr.cfgMu.RUnlock() svr.cfgMu.RUnlock()
connEncrypted := true
connEncrypted := svr.clientSpec == nil || svr.clientSpec.Type != "ssh-tunnel" if svr.clientSpec != nil && svr.clientSpec.Type == "ssh-tunnel" {
connEncrypted = false
}
sessionCtx := &SessionContext{ sessionCtx := &SessionContext{
Common: svr.common, Common: svr.common,
RunID: svr.runID, RunID: svr.runID,
Conn: conn, Conn: conn,
ConnEncrypted: connEncrypted, ConnEncrypted: connEncrypted,
AuthSetter: svr.authSetter, AuthSetter: svr.authSetter,
Connector: connector, Connector: connector,
VnetController: svr.vnetController,
} }
ctl, err := NewControl(svr.ctx, sessionCtx) ctl, err := NewControl(svr.ctx, sessionCtx)
if err != nil { if err != nil {
conn.Close() conn.Close()
xl.Errorf("new control error: %v", err) xl.Errorf("NewControl error: %v", err)
return false, err return false, err
} }
ctl.SetInWorkConnCallback(svr.handleWorkConnCb) ctl.SetInWorkConnCallback(svr.handleWorkConnCb)

View File

@@ -44,10 +44,6 @@ func (sv *STCPVisitor) Run() (err error) {
} }
go sv.internalConnWorker() go sv.internalConnWorker()
if sv.plugin != nil {
sv.plugin.Start()
}
return return
} }

View File

@@ -20,11 +20,9 @@ import (
"sync" "sync"
v1 "github.com/fatedier/frp/pkg/config/v1" v1 "github.com/fatedier/frp/pkg/config/v1"
plugin "github.com/fatedier/frp/pkg/plugin/visitor"
"github.com/fatedier/frp/pkg/transport" "github.com/fatedier/frp/pkg/transport"
netpkg "github.com/fatedier/frp/pkg/util/net" netpkg "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/xlog" "github.com/fatedier/frp/pkg/util/xlog"
"github.com/fatedier/frp/pkg/vnet"
) )
// Helper wraps some functions for visitor to use. // Helper wraps some functions for visitor to use.
@@ -36,8 +34,6 @@ type Helper interface {
// MsgTransporter returns the message transporter that is used to send and receive messages // MsgTransporter returns the message transporter that is used to send and receive messages
// to the frp server through the controller. // to the frp server through the controller.
MsgTransporter() transport.MessageTransporter MsgTransporter() transport.MessageTransporter
// VNetController returns the vnet controller that is used to manage the virtual network.
VNetController() *vnet.Controller
// RunID returns the run id of current controller. // RunID returns the run id of current controller.
RunID() string RunID() string
} }
@@ -54,34 +50,14 @@ func NewVisitor(
cfg v1.VisitorConfigurer, cfg v1.VisitorConfigurer,
clientCfg *v1.ClientCommonConfig, clientCfg *v1.ClientCommonConfig,
helper Helper, helper Helper,
) (Visitor, error) { ) (visitor Visitor) {
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name) xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name)
ctx = xlog.NewContext(ctx, xl)
var visitor Visitor
baseVisitor := BaseVisitor{ baseVisitor := BaseVisitor{
clientCfg: clientCfg, clientCfg: clientCfg,
helper: helper, helper: helper,
ctx: ctx, ctx: xlog.NewContext(ctx, xl),
internalLn: netpkg.NewInternalListener(), internalLn: netpkg.NewInternalListener(),
} }
if cfg.GetBaseConfig().Plugin.Type != "" {
p, err := plugin.Create(
cfg.GetBaseConfig().Plugin.Type,
plugin.PluginContext{
Name: cfg.GetBaseConfig().Name,
Ctx: ctx,
VnetController: helper.VNetController(),
HandleConn: func(conn net.Conn) {
_ = baseVisitor.AcceptConn(conn)
},
},
cfg.GetBaseConfig().Plugin.VisitorPluginOptions,
)
if err != nil {
return nil, err
}
baseVisitor.plugin = p
}
switch cfg := cfg.(type) { switch cfg := cfg.(type) {
case *v1.STCPVisitorConfig: case *v1.STCPVisitorConfig:
visitor = &STCPVisitor{ visitor = &STCPVisitor{
@@ -101,7 +77,7 @@ func NewVisitor(
checkCloseCh: make(chan struct{}), checkCloseCh: make(chan struct{}),
} }
} }
return visitor, nil return
} }
type BaseVisitor struct { type BaseVisitor struct {
@@ -109,7 +85,6 @@ type BaseVisitor struct {
helper Helper helper Helper
l net.Listener l net.Listener
internalLn *netpkg.InternalListener internalLn *netpkg.InternalListener
plugin plugin.Plugin
mu sync.RWMutex mu sync.RWMutex
ctx context.Context ctx context.Context
@@ -126,7 +101,4 @@ func (v *BaseVisitor) Close() {
if v.internalLn != nil { if v.internalLn != nil {
v.internalLn.Close() v.internalLn.Close()
} }
if v.plugin != nil {
v.plugin.Close()
}
} }

View File

@@ -27,7 +27,6 @@ import (
v1 "github.com/fatedier/frp/pkg/config/v1" v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/transport" "github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/xlog" "github.com/fatedier/frp/pkg/util/xlog"
"github.com/fatedier/frp/pkg/vnet"
) )
type Manager struct { type Manager struct {
@@ -51,7 +50,6 @@ func NewManager(
clientCfg *v1.ClientCommonConfig, clientCfg *v1.ClientCommonConfig,
connectServer func() (net.Conn, error), connectServer func() (net.Conn, error),
msgTransporter transport.MessageTransporter, msgTransporter transport.MessageTransporter,
vnetController *vnet.Controller,
) *Manager { ) *Manager {
m := &Manager{ m := &Manager{
clientCfg: clientCfg, clientCfg: clientCfg,
@@ -64,7 +62,6 @@ func NewManager(
m.helper = &visitorHelperImpl{ m.helper = &visitorHelperImpl{
connectServerFn: connectServer, connectServerFn: connectServer,
msgTransporter: msgTransporter, msgTransporter: msgTransporter,
vnetController: vnetController,
transferConnFn: m.TransferConn, transferConnFn: m.TransferConn,
runID: runID, runID: runID,
} }
@@ -115,11 +112,7 @@ func (vm *Manager) Close() {
func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) { func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
xl := xlog.FromContextSafe(vm.ctx) xl := xlog.FromContextSafe(vm.ctx)
name := cfg.GetBaseConfig().Name name := cfg.GetBaseConfig().Name
visitor, err := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper) visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
if err != nil {
xl.Warnf("new visitor error: %v", err)
return
}
err = visitor.Run() err = visitor.Run()
if err != nil { if err != nil {
xl.Warnf("start error: %v", err) xl.Warnf("start error: %v", err)
@@ -194,7 +187,6 @@ func (vm *Manager) TransferConn(name string, conn net.Conn) error {
type visitorHelperImpl struct { type visitorHelperImpl struct {
connectServerFn func() (net.Conn, error) connectServerFn func() (net.Conn, error)
msgTransporter transport.MessageTransporter msgTransporter transport.MessageTransporter
vnetController *vnet.Controller
transferConnFn func(name string, conn net.Conn) error transferConnFn func(name string, conn net.Conn) error
runID string runID string
} }
@@ -211,10 +203,6 @@ func (v *visitorHelperImpl) MsgTransporter() transport.MessageTransporter {
return v.msgTransporter return v.msgTransporter
} }
func (v *visitorHelperImpl) VNetController() *vnet.Controller {
return v.vnetController
}
func (v *visitorHelperImpl) RunID() string { func (v *visitorHelperImpl) RunID() string {
return v.runID return v.runID
} }

View File

@@ -73,10 +73,6 @@ func (sv *XTCPVisitor) Run() (err error) {
sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour) sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour)
go sv.keepTunnelOpenWorker() go sv.keepTunnelOpenWorker()
} }
if sv.plugin != nil {
sv.plugin.Start()
}
return return
} }
@@ -161,9 +157,9 @@ func (sv *XTCPVisitor) keepTunnelOpenWorker() {
func (sv *XTCPVisitor) handleConn(userConn net.Conn) { func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
xl := xlog.FromContextSafe(sv.ctx) xl := xlog.FromContextSafe(sv.ctx)
isConnTransfered := false isConnTrasfered := false
defer func() { defer func() {
if !isConnTransfered { if !isConnTrasfered {
userConn.Close() userConn.Close()
} }
}() }()
@@ -191,7 +187,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
xl.Errorf("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err) xl.Errorf("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err)
return return
} }
isConnTransfered = true isConnTrasfered = true
return return
} }

View File

@@ -15,11 +15,9 @@
package sub package sub
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"time"
"github.com/rodaine/table" "github.com/rodaine/table"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -29,24 +27,24 @@ import (
clientsdk "github.com/fatedier/frp/pkg/sdk/client" clientsdk "github.com/fatedier/frp/pkg/sdk/client"
) )
var adminAPITimeout = 30 * time.Second
func init() { func init() {
commands := []struct { rootCmd.AddCommand(NewAdminCommand(
name string "reload",
description string "Hot-Reload frpc configuration",
handler func(*v1.ClientCommonConfig) error ReloadHandler,
}{ ))
{"reload", "Hot-Reload frpc configuration", ReloadHandler},
{"status", "Overview of all proxies status", StatusHandler},
{"stop", "Stop the running frpc", StopHandler},
}
for _, cmdConfig := range commands { rootCmd.AddCommand(NewAdminCommand(
cmd := NewAdminCommand(cmdConfig.name, cmdConfig.description, cmdConfig.handler) "status",
cmd.Flags().DurationVar(&adminAPITimeout, "api-timeout", adminAPITimeout, "Timeout for admin API calls") "Overview of all proxies status",
rootCmd.AddCommand(cmd) StatusHandler,
} ))
rootCmd.AddCommand(NewAdminCommand(
"stop",
"Stop the running frpc",
StopHandler,
))
} }
func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command { func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command {
@@ -75,9 +73,7 @@ func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) er
func ReloadHandler(clientCfg *v1.ClientCommonConfig) error { func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port) client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password) client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout) if err := client.Reload(strictConfigMode); err != nil {
defer cancel()
if err := client.Reload(ctx, strictConfigMode); err != nil {
return err return err
} }
fmt.Println("reload success") fmt.Println("reload success")
@@ -87,9 +83,7 @@ func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
func StatusHandler(clientCfg *v1.ClientCommonConfig) error { func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port) client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password) client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout) res, err := client.GetAllProxyStatus()
defer cancel()
res, err := client.GetAllProxyStatus(ctx)
if err != nil { if err != nil {
return err return err
} }
@@ -115,9 +109,7 @@ func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
func StopHandler(clientCfg *v1.ClientCommonConfig) error { func StopHandler(clientCfg *v1.ClientCommonConfig) error {
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port) client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password) client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout) if err := client.Stop(); err != nil {
defer cancel()
if err := client.Stop(ctx); err != nil {
return err return err
} }
fmt.Println("stop success") fmt.Println("stop success")

View File

@@ -31,7 +31,6 @@ import (
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
v1 "github.com/fatedier/frp/pkg/config/v1" v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/config/v1/validation" "github.com/fatedier/frp/pkg/config/v1/validation"
"github.com/fatedier/frp/pkg/featuregate"
"github.com/fatedier/frp/pkg/util/log" "github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/pkg/util/version" "github.com/fatedier/frp/pkg/util/version"
) )
@@ -121,12 +120,6 @@ func runClient(cfgFilePath string) error {
"please use yaml/json/toml format instead!\n") "please use yaml/json/toml format instead!\n")
} }
if len(cfg.FeatureGates) > 0 {
if err := featuregate.SetFromMap(cfg.FeatureGates); err != nil {
return err
}
}
warning, err := validation.ValidateAllClientConfig(cfg, proxyCfgs, visitorCfgs) warning, err := validation.ValidateAllClientConfig(cfg, proxyCfgs, visitorCfgs)
if warning != nil { if warning != nil {
fmt.Printf("WARNING: %v\n", warning) fmt.Printf("WARNING: %v\n", warning)

View File

@@ -129,15 +129,6 @@ transport.tls.enable = true
# It affects the udp and sudp proxy. # It affects the udp and sudp proxy.
udpPacketSize = 1500 udpPacketSize = 1500
# Feature gates allows you to enable or disable experimental features
# Format is a map of feature names to boolean values
# You can enable specific features:
#featureGates = { VirtualNet = true }
# VirtualNet settings for experimental virtual network capabilities
# The virtual network feature requires enabling the VirtualNet feature gate above
# virtualNet.address = "100.86.1.1/24"
# Additional metadatas for client. # Additional metadatas for client.
metadatas.var1 = "abc" metadatas.var1 = "abc"
metadatas.var2 = "123" metadatas.var2 = "123"
@@ -324,26 +315,6 @@ localAddr = "127.0.0.1:443"
hostHeaderRewrite = "127.0.0.1" hostHeaderRewrite = "127.0.0.1"
requestHeaders.set.x-from-where = "frp" requestHeaders.set.x-from-where = "frp"
[[proxies]]
name = "plugin_http2http"
type = "tcp"
remotePort = 6007
[proxies.plugin]
type = "http2http"
localAddr = "127.0.0.1:80"
hostHeaderRewrite = "127.0.0.1"
requestHeaders.set.x-from-where = "frp"
[[proxies]]
name = "plugin_tls2raw"
type = "tcp"
remotePort = 6008
[proxies.plugin]
type = "tls2raw"
localAddr = "127.0.0.1:80"
crtPath = "./server.crt"
keyPath = "./server.key"
[[proxies]] [[proxies]]
name = "secret_tcp" name = "secret_tcp"
# If the type is secret tcp, remotePort is useless # If the type is secret tcp, remotePort is useless
@@ -367,13 +338,6 @@ localPort = 22
# Otherwise, visitors from same user can connect. '*' means allow all users. # Otherwise, visitors from same user can connect. '*' means allow all users.
allowUsers = ["user1", "user2"] allowUsers = ["user1", "user2"]
[[proxies]]
name = "vnet-server"
type = "stcp"
secretKey = "your-secret-key"
[proxies.plugin]
type = "virtual_net"
# frpc role visitor -> frps -> frpc role server # frpc role visitor -> frps -> frpc role server
[[visitors]] [[visitors]]
name = "secret_tcp_visitor" name = "secret_tcp_visitor"
@@ -405,13 +369,3 @@ maxRetriesAnHour = 8
minRetryInterval = 90 minRetryInterval = 90
# fallbackTo = "stcp_visitor" # fallbackTo = "stcp_visitor"
# fallbackTimeoutMs = 500 # fallbackTimeoutMs = 500
[[visitors]]
name = "vnet-visitor"
type = "stcp"
serverName = "vnet-server"
secretKey = "your-secret-key"
bindPort = -1
[visitors.plugin]
type = "virtual_net"
destinationIP = "100.86.0.1"

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 56 KiB

BIN
doc/pic/sponsor_doppler.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

BIN
doc/pic/sponsor_nango.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -121,7 +121,7 @@ Create new proxy
// http and https only // http and https only
"custom_domains": []<string>, "custom_domains": []<string>,
"subdomain": <string>, "subdomain": <string>,
"locations": []<string>, "locations": <string>,
"http_user": <string>, "http_user": <string>,
"http_pwd": <string>, "http_pwd": <string>,
"host_header_rewrite": <string>, "host_header_rewrite": <string>,

View File

@@ -1,73 +0,0 @@
# Virtual Network (VirtualNet)
*Alpha feature added in v0.62.0*
The VirtualNet feature enables frp to create and manage virtual network connections between clients and visitors through a TUN interface. This allows for IP-level routing between machines, extending frp beyond simple port forwarding to support full network connectivity.
> **Note**: VirtualNet is an Alpha stage feature and is currently unstable. Its configuration methods and functionality may be adjusted and changed at any time in subsequent versions. Do not use this feature in production environments; it is only recommended for testing and evaluation purposes.
## Enabling VirtualNet
Since VirtualNet is currently an alpha feature, you need to enable it with feature gates in your configuration:
```toml
# frpc.toml
featureGates = { VirtualNet = true }
```
## Basic Configuration
To use the virtual network capabilities:
1. First, configure your frpc with a virtual network address:
```toml
# frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000
featureGates = { VirtualNet = true }
# Configure the virtual network interface
virtualNet.address = "100.86.0.1/24"
```
2. For client proxies, use the `virtual_net` plugin:
```toml
# frpc.toml (server side)
[[proxies]]
name = "vnet-server"
type = "stcp"
secretKey = "your-secret-key"
[proxies.plugin]
type = "virtual_net"
```
3. For visitor connections, configure the `virtual_net` visitor plugin:
```toml
# frpc.toml (client side)
serverAddr = "x.x.x.x"
serverPort = 7000
featureGates = { VirtualNet = true }
# Configure the virtual network interface
virtualNet.address = "100.86.0.2/24"
[[visitors]]
name = "vnet-visitor"
type = "stcp"
serverName = "vnet-server"
secretKey = "your-secret-key"
bindPort = -1
[visitors.plugin]
type = "virtual_net"
destinationIP = "100.86.0.1"
```
## Requirements and Limitations
- **Permissions**: Creating a TUN interface requires elevated permissions (root/admin)
- **Platform Support**: Currently supported on Linux and macOS
- **Default Status**: As an alpha feature, VirtualNet is disabled by default
- **Configuration**: A valid IP/CIDR must be provided for each endpoint in the virtual network

View File

@@ -1,4 +1,4 @@
FROM golang:1.23 AS building FROM golang:1.22 AS building
COPY . /building COPY . /building
WORKDIR /building WORKDIR /building
@@ -7,8 +7,6 @@ RUN make frpc
FROM alpine:3 FROM alpine:3
RUN apk add --no-cache tzdata
COPY --from=building /building/bin/frpc /usr/bin/frpc COPY --from=building /building/bin/frpc /usr/bin/frpc
ENTRYPOINT ["/usr/bin/frpc"] ENTRYPOINT ["/usr/bin/frpc"]

View File

@@ -1,4 +1,4 @@
FROM golang:1.23 AS building FROM golang:1.22 AS building
COPY . /building COPY . /building
WORKDIR /building WORKDIR /building
@@ -7,8 +7,6 @@ RUN make frps
FROM alpine:3 FROM alpine:3
RUN apk add --no-cache tzdata
COPY --from=building /building/bin/frps /usr/bin/frps COPY --from=building /building/bin/frps /usr/bin/frps
ENTRYPOINT ["/usr/bin/frps"] ENTRYPOINT ["/usr/bin/frps"]

66
go.mod
View File

@@ -1,37 +1,34 @@
module github.com/fatedier/frp module github.com/fatedier/frp
go 1.23.0 go 1.22
require ( require (
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-oidc/v3 v3.10.0
github.com/fatedier/golib v0.5.1 github.com/fatedier/golib v0.5.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/hashicorp/yamux v0.1.1 github.com/hashicorp/yamux v0.1.1
github.com/onsi/ginkgo/v2 v2.23.4 github.com/onsi/ginkgo/v2 v2.17.1
github.com/onsi/gomega v1.36.3 github.com/onsi/gomega v1.32.0
github.com/pelletier/go-toml/v2 v2.2.0 github.com/pelletier/go-toml/v2 v2.2.0
github.com/pion/stun/v2 v2.0.0 github.com/pion/stun/v2 v2.0.0
github.com/pires/go-proxyproto v0.7.0 github.com/pires/go-proxyproto v0.7.0
github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_golang v1.19.0
github.com/quic-go/quic-go v0.48.2 github.com/quic-go/quic-go v0.42.0
github.com/rodaine/table v1.2.0 github.com/rodaine/table v1.2.0
github.com/samber/lo v1.47.0 github.com/samber/lo v1.39.0
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.9.0
github.com/tidwall/gjson v1.17.1 github.com/tidwall/gjson v1.17.1
github.com/vishvananda/netlink v1.3.0 github.com/xtaci/kcp-go/v5 v5.6.8
github.com/xtaci/kcp-go/v5 v5.6.13 golang.org/x/crypto v0.22.0
golang.org/x/crypto v0.37.0 golang.org/x/net v0.24.0
golang.org/x/net v0.39.0 golang.org/x/oauth2 v0.16.0
golang.org/x/oauth2 v0.28.0 golang.org/x/sync v0.6.0
golang.org/x/sync v0.13.0
golang.org/x/time v0.5.0 golang.org/x/time v0.5.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
k8s.io/apimachinery v0.28.8 k8s.io/apimachinery v0.28.8
k8s.io/client-go v0.28.8 k8s.io/client-go v0.28.8
@@ -42,15 +39,17 @@ require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.0 // indirect github.com/klauspost/reedsolomon v1.12.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/logging v0.2.2 // indirect github.com/pion/logging v0.2.2 // indirect
github.com/pion/transport/v2 v2.2.1 // indirect github.com/pion/transport/v2 v2.2.1 // indirect
@@ -60,21 +59,20 @@ require (
github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect
github.com/templexxx/cpu v0.1.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/templexxx/xorsimd v0.4.3 // indirect github.com/templexxx/cpu v0.1.0 // indirect
github.com/templexxx/xorsimd v0.4.2 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/vishvananda/netns v0.0.4 // indirect go.uber.org/mock v0.4.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
go.uber.org/mock v0.5.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect golang.org/x/sys v0.19.0 // indirect
golang.org/x/mod v0.24.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/tools v0.17.0 // indirect
golang.org/x/text v0.24.0 // indirect google.golang.org/appengine v1.6.8 // indirect
golang.org/x/tools v0.31.0 // indirect google.golang.org/protobuf v1.33.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect

151
go.sum
View File

@@ -9,27 +9,31 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M= github.com/fatedier/golib v0.5.0 h1:hNcH7hgfIFqVWbP+YojCCAj4eO94pPf4dEF8lmq2jWs=
github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ= github.com/fatedier/golib v0.5.0/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo= github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -41,25 +45,28 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
@@ -72,10 +79,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU= github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
@@ -94,10 +101,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
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= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
@@ -105,19 +110,17 @@ 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/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 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 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.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA= github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
github.com/rodaine/table v1.2.0/go.mod h1:wejb/q/Yd4T/SVmBSRMr7GCq3KlcZp3gyNYdLSBhkaE= github.com/rodaine/table v1.2.0/go.mod h1:wejb/q/Yd4T/SVmBSRMr7GCq3KlcZp3gyNYdLSBhkaE=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -126,17 +129,17 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI= github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI=
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI=
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@@ -145,37 +148,31 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= github.com/xtaci/kcp-go/v5 v5.6.8 h1:jlI/0jAyjoOjT/SaGB58s4bQMJiNS41A2RKzR6TMWeI=
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/xtaci/kcp-go/v5 v5.6.8/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtaci/kcp-go/v5 v5.6.13 h1:FEjtz9+D4p8t2x4WjciGt/jsIuhlWjjgPCCWjrVR4Hk=
github.com/xtaci/kcp-go/v5 v5.6.13/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -189,50 +186,50 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -243,16 +240,14 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -265,8 +260,10 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -277,8 +274,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/apimachinery v0.28.8 h1:hi/nrxHwk4QLV+W/SHve1bypTE59HCDorLY1stBIxKQ= k8s.io/apimachinery v0.28.8 h1:hi/nrxHwk4QLV+W/SHve1bypTE59HCDorLY1stBIxKQ=

View File

@@ -3,10 +3,10 @@
SCRIPT=$(readlink -f "$0") SCRIPT=$(readlink -f "$0")
ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd) ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
# Check if ginkgo is available ginkgo_command=$(which ginkgo 2>/dev/null)
if ! command -v ginkgo >/dev/null 2>&1; then if [ -z "$ginkgo_command" ]; then
echo "ginkgo not found, try to install..." echo "ginkgo not found, try to install..."
go install github.com/onsi/ginkgo/v2/ginkgo@v2.23.4 go install github.com/onsi/ginkgo/v2/ginkgo@v2.17.1
fi fi
debug=false debug=false

View File

@@ -17,8 +17,8 @@ make -f ./Makefile.cross-compiles
rm -rf ./release/packages rm -rf ./release/packages
mkdir -p ./release/packages mkdir -p ./release/packages
os_all='linux windows darwin freebsd openbsd android' os_all='linux windows darwin freebsd android'
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64 loong64' arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64'
extra_all='_ hf' extra_all='_ hf'
cd ./release cd ./release

View File

@@ -50,8 +50,7 @@ func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
case v1.AuthMethodToken: case v1.AuthMethodToken:
authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token) authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
case v1.AuthMethodOIDC: case v1.AuthMethodOIDC:
tokenVerifier := NewTokenVerifier(cfg.OIDC) authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, cfg.OIDC)
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, tokenVerifier)
} }
return authVerifier return authVerifier
} }

View File

@@ -87,18 +87,14 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e
return err return err
} }
type TokenVerifier interface {
Verify(context.Context, string) (*oidc.IDToken, error)
}
type OidcAuthConsumer struct { type OidcAuthConsumer struct {
additionalAuthScopes []v1.AuthScope additionalAuthScopes []v1.AuthScope
verifier TokenVerifier verifier *oidc.IDTokenVerifier
subjectsFromLogin []string subjectFromLogin string
} }
func NewTokenVerifier(cfg v1.AuthOIDCServerConfig) TokenVerifier { func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCServerConfig) *OidcAuthConsumer {
provider, err := oidc.NewProvider(context.Background(), cfg.Issuer) provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -109,14 +105,9 @@ func NewTokenVerifier(cfg v1.AuthOIDCServerConfig) TokenVerifier {
SkipExpiryCheck: cfg.SkipExpiryCheck, SkipExpiryCheck: cfg.SkipExpiryCheck,
SkipIssuerCheck: cfg.SkipIssuerCheck, SkipIssuerCheck: cfg.SkipIssuerCheck,
} }
return provider.Verifier(&verifierConf)
}
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, verifier TokenVerifier) *OidcAuthConsumer {
return &OidcAuthConsumer{ return &OidcAuthConsumer{
additionalAuthScopes: additionalAuthScopes, additionalAuthScopes: additionalAuthScopes,
verifier: verifier, verifier: provider.Verifier(&verifierConf),
subjectsFromLogin: []string{},
} }
} }
@@ -125,9 +116,7 @@ func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) {
if err != nil { if err != nil {
return fmt.Errorf("invalid OIDC token in login: %v", err) return fmt.Errorf("invalid OIDC token in login: %v", err)
} }
if !slices.Contains(auth.subjectsFromLogin, token.Subject) { auth.subjectFromLogin = token.Subject
auth.subjectsFromLogin = append(auth.subjectsFromLogin, token.Subject)
}
return nil return nil
} }
@@ -136,11 +125,11 @@ func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err err
if err != nil { if err != nil {
return fmt.Errorf("invalid OIDC token in ping: %v", err) return fmt.Errorf("invalid OIDC token in ping: %v", err)
} }
if !slices.Contains(auth.subjectsFromLogin, token.Subject) { if token.Subject != auth.subjectFromLogin {
return fmt.Errorf("received different OIDC subject in login and ping. "+ return fmt.Errorf("received different OIDC subject in login and ping. "+
"original subjects: %s, "+ "original subject: %s, "+
"new subject: %s", "new subject: %s",
auth.subjectsFromLogin, token.Subject) auth.subjectFromLogin, token.Subject)
} }
return nil return nil
} }

View File

@@ -1,64 +0,0 @@
package auth_test
import (
"context"
"testing"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/stretchr/testify/require"
"github.com/fatedier/frp/pkg/auth"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg"
)
type mockTokenVerifier struct{}
func (m *mockTokenVerifier) Verify(ctx context.Context, subject string) (*oidc.IDToken, error) {
return &oidc.IDToken{
Subject: subject,
}, nil
}
func TestPingWithEmptySubjectFromLoginFails(t *testing.T) {
r := require.New(t)
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
err := consumer.VerifyPing(&msg.Ping{
PrivilegeKey: "ping-without-login",
Timestamp: time.Now().UnixMilli(),
})
r.Error(err)
r.Contains(err.Error(), "received different OIDC subject in login and ping")
}
func TestPingAfterLoginWithNewSubjectSucceeds(t *testing.T) {
r := require.New(t)
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
err := consumer.VerifyLogin(&msg.Login{
PrivilegeKey: "ping-after-login",
})
r.NoError(err)
err = consumer.VerifyPing(&msg.Ping{
PrivilegeKey: "ping-after-login",
Timestamp: time.Now().UnixMilli(),
})
r.NoError(err)
}
func TestPingAfterLoginWithDifferentSubjectFails(t *testing.T) {
r := require.New(t)
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
err := consumer.VerifyLogin(&msg.Login{
PrivilegeKey: "login-with-first-subject",
})
r.NoError(err)
err = consumer.VerifyPing(&msg.Ping{
PrivilegeKey: "ping-with-different-subject",
Timestamp: time.Now().UnixMilli(),
})
r.Error(err)
r.Contains(err.Error(), "received different OIDC subject in login and ping")
}

View File

@@ -106,8 +106,6 @@ func registerProxyBaseConfigFlags(cmd *cobra.Command, c *v1.ProxyBaseConfig, opt
} }
cmd.Flags().StringVarP(&c.Name, "proxy_name", "n", "", "proxy name") cmd.Flags().StringVarP(&c.Name, "proxy_name", "n", "", "proxy name")
cmd.Flags().StringToStringVarP(&c.Metadatas, "metadatas", "", nil, "metadata key-value pairs (e.g., key1=value1,key2=value2)")
cmd.Flags().StringToStringVarP(&c.Annotations, "annotations", "", nil, "annotation key-value pairs (e.g., key1=value1,key2=value2)")
if !options.sshMode { if !options.sshMode {
cmd.Flags().StringVarP(&c.LocalIP, "local_ip", "i", "127.0.0.1", "local ip") cmd.Flags().StringVarP(&c.LocalIP, "local_ip", "i", "127.0.0.1", "local ip")
@@ -142,7 +140,6 @@ func registerVisitorBaseConfigFlags(cmd *cobra.Command, c *v1.VisitorBaseConfig,
cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression") cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression")
cmd.Flags().StringVarP(&c.SecretKey, "sk", "", "", "secret key") cmd.Flags().StringVarP(&c.SecretKey, "sk", "", "", "secret key")
cmd.Flags().StringVarP(&c.ServerName, "server_name", "", "", "server name") cmd.Flags().StringVarP(&c.ServerName, "server_name", "", "", "server name")
cmd.Flags().StringVarP(&c.ServerUser, "server-user", "", "", "server user")
cmd.Flags().StringVarP(&c.BindAddr, "bind_addr", "", "", "bind addr") cmd.Flags().StringVarP(&c.BindAddr, "bind_addr", "", "", "bind addr")
cmd.Flags().IntVarP(&c.BindPort, "bind_port", "", 0, "bind port") cmd.Flags().IntVarP(&c.BindPort, "bind_port", "", 0, "bind port")
} }
@@ -229,7 +226,6 @@ func RegisterServerConfigFlags(cmd *cobra.Command, c *v1.ServerConfig, opts ...R
cmd.PersistentFlags().StringVarP(&c.BindAddr, "bind_addr", "", "0.0.0.0", "bind address") cmd.PersistentFlags().StringVarP(&c.BindAddr, "bind_addr", "", "0.0.0.0", "bind address")
cmd.PersistentFlags().IntVarP(&c.BindPort, "bind_port", "p", 7000, "bind port") cmd.PersistentFlags().IntVarP(&c.BindPort, "bind_port", "p", 7000, "bind port")
cmd.PersistentFlags().IntVarP(&c.KCPBindPort, "kcp_bind_port", "", 0, "kcp bind udp port") cmd.PersistentFlags().IntVarP(&c.KCPBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
cmd.PersistentFlags().IntVarP(&c.QUICBindPort, "quic_bind_port", "", 0, "quic bind udp port")
cmd.PersistentFlags().StringVarP(&c.ProxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address") cmd.PersistentFlags().StringVarP(&c.ProxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
cmd.PersistentFlags().IntVarP(&c.VhostHTTPPort, "vhost_http_port", "", 0, "vhost http port") cmd.PersistentFlags().IntVarP(&c.VhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
cmd.PersistentFlags().IntVarP(&c.VhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port") cmd.PersistentFlags().IntVarP(&c.VhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")

View File

@@ -170,7 +170,7 @@ type ClientCommonConf struct {
} }
// Supported sources including: string(file path), []byte, Reader interface. // Supported sources including: string(file path), []byte, Reader interface.
func UnmarshalClientConfFromIni(source any) (ClientCommonConf, error) { func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
f, err := ini.LoadSources(ini.LoadOptions{ f, err := ini.LoadSources(ini.LoadOptions{
Insensitive: false, Insensitive: false,
InsensitiveSections: false, InsensitiveSections: false,
@@ -194,7 +194,7 @@ func UnmarshalClientConfFromIni(source any) (ClientCommonConf, error) {
} }
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_") common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
common.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_") common.ClientConfig.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
return common, nil return common, nil
} }
@@ -203,7 +203,7 @@ func UnmarshalClientConfFromIni(source any) (ClientCommonConf, error) {
// otherwise just start proxies in startProxy map // otherwise just start proxies in startProxy map
func LoadAllProxyConfsFromIni( func LoadAllProxyConfsFromIni(
prefix string, prefix string,
source any, source interface{},
start []string, start []string,
) (map[string]ProxyConf, map[string]VisitorConf, error) { ) (map[string]ProxyConf, map[string]VisitorConf, error) {
f, err := ini.LoadSources(ini.LoadOptions{ f, err := ini.LoadSources(ini.LoadOptions{
@@ -229,7 +229,10 @@ func LoadAllProxyConfsFromIni(
startProxy[s] = struct{}{} startProxy[s] = struct{}{}
} }
startAll := len(startProxy) == 0 startAll := true
if len(startProxy) > 0 {
startAll = false
}
// Build template sections from range section And append to ini.File. // Build template sections from range section And append to ini.File.
rangeSections := make([]*ini.Section, 0) rangeSections := make([]*ini.Section, 0)

View File

@@ -26,20 +26,20 @@ import (
func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConfig { func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConfig {
out := &v1.ClientCommonConfig{} out := &v1.ClientCommonConfig{}
out.User = conf.User out.User = conf.User
out.Auth.Method = v1.AuthMethod(conf.AuthenticationMethod) out.Auth.Method = v1.AuthMethod(conf.ClientConfig.AuthenticationMethod)
out.Auth.Token = conf.Token out.Auth.Token = conf.ClientConfig.Token
if conf.AuthenticateHeartBeats { if conf.ClientConfig.AuthenticateHeartBeats {
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats) out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
} }
if conf.AuthenticateNewWorkConns { if conf.ClientConfig.AuthenticateNewWorkConns {
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns) out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
} }
out.Auth.OIDC.ClientID = conf.OidcClientID out.Auth.OIDC.ClientID = conf.ClientConfig.OidcClientID
out.Auth.OIDC.ClientSecret = conf.OidcClientSecret out.Auth.OIDC.ClientSecret = conf.ClientConfig.OidcClientSecret
out.Auth.OIDC.Audience = conf.OidcAudience out.Auth.OIDC.Audience = conf.ClientConfig.OidcAudience
out.Auth.OIDC.Scope = conf.OidcScope out.Auth.OIDC.Scope = conf.ClientConfig.OidcScope
out.Auth.OIDC.TokenEndpointURL = conf.OidcTokenEndpointURL out.Auth.OIDC.TokenEndpointURL = conf.ClientConfig.OidcTokenEndpointURL
out.Auth.OIDC.AdditionalEndpointParams = conf.OidcAdditionalEndpointParams out.Auth.OIDC.AdditionalEndpointParams = conf.ClientConfig.OidcAdditionalEndpointParams
out.ServerAddr = conf.ServerAddr out.ServerAddr = conf.ServerAddr
out.ServerPort = conf.ServerPort out.ServerPort = conf.ServerPort
@@ -59,10 +59,10 @@ func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConf
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
out.Transport.TLS.Enable = lo.ToPtr(conf.TLSEnable) out.Transport.TLS.Enable = lo.ToPtr(conf.TLSEnable)
out.Transport.TLS.DisableCustomTLSFirstByte = lo.ToPtr(conf.DisableCustomTLSFirstByte) out.Transport.TLS.DisableCustomTLSFirstByte = lo.ToPtr(conf.DisableCustomTLSFirstByte)
out.Transport.TLS.CertFile = conf.TLSCertFile out.Transport.TLS.TLSConfig.CertFile = conf.TLSCertFile
out.Transport.TLS.KeyFile = conf.TLSKeyFile out.Transport.TLS.TLSConfig.KeyFile = conf.TLSKeyFile
out.Transport.TLS.TrustedCaFile = conf.TLSTrustedCaFile out.Transport.TLS.TLSConfig.TrustedCaFile = conf.TLSTrustedCaFile
out.Transport.TLS.ServerName = conf.TLSServerName out.Transport.TLS.TLSConfig.ServerName = conf.TLSServerName
out.Log.To = conf.LogFile out.Log.To = conf.LogFile
out.Log.Level = conf.LogLevel out.Log.Level = conf.LogLevel
@@ -87,18 +87,18 @@ func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConf
func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig { func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig {
out := &v1.ServerConfig{} out := &v1.ServerConfig{}
out.Auth.Method = v1.AuthMethod(conf.AuthenticationMethod) out.Auth.Method = v1.AuthMethod(conf.ServerConfig.AuthenticationMethod)
out.Auth.Token = conf.Token out.Auth.Token = conf.ServerConfig.Token
if conf.AuthenticateHeartBeats { if conf.ServerConfig.AuthenticateHeartBeats {
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats) out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
} }
if conf.AuthenticateNewWorkConns { if conf.ServerConfig.AuthenticateNewWorkConns {
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns) out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
} }
out.Auth.OIDC.Audience = conf.OidcAudience out.Auth.OIDC.Audience = conf.ServerConfig.OidcAudience
out.Auth.OIDC.Issuer = conf.OidcIssuer out.Auth.OIDC.Issuer = conf.ServerConfig.OidcIssuer
out.Auth.OIDC.SkipExpiryCheck = conf.OidcSkipExpiryCheck out.Auth.OIDC.SkipExpiryCheck = conf.ServerConfig.OidcSkipExpiryCheck
out.Auth.OIDC.SkipIssuerCheck = conf.OidcSkipIssuerCheck out.Auth.OIDC.SkipIssuerCheck = conf.ServerConfig.OidcSkipIssuerCheck
out.BindAddr = conf.BindAddr out.BindAddr = conf.BindAddr
out.BindPort = conf.BindPort out.BindPort = conf.BindPort

View File

@@ -206,7 +206,7 @@ func (cfg *BaseProxyConf) decorate(_ string, name string, section *ini.Section)
} }
// plugin_xxx // plugin_xxx
cfg.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_") cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
return nil return nil
} }

View File

@@ -217,7 +217,7 @@ func GetDefaultServerConf() ServerCommonConf {
} }
} }
func UnmarshalServerConfFromIni(source any) (ServerCommonConf, error) { func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
f, err := ini.LoadSources(ini.LoadOptions{ f, err := ini.LoadSources(ini.LoadOptions{
Insensitive: false, Insensitive: false,
InsensitiveSections: false, InsensitiveSections: false,

View File

@@ -18,10 +18,10 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"html/template"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"text/template"
toml "github.com/pelletier/go-toml/v2" toml "github.com/pelletier/go-toml/v2"
"github.com/samber/lo" "github.com/samber/lo"
@@ -111,33 +111,6 @@ func LoadConfigureFromFile(path string, c any, strict bool) error {
return LoadConfigure(content, c, strict) return LoadConfigure(content, c, strict)
} }
// parseYAMLWithDotFieldsHandling parses YAML with dot-prefixed fields handling
// This function handles both cases efficiently: with or without dot fields
func parseYAMLWithDotFieldsHandling(content []byte, target any) error {
var temp any
if err := yaml.Unmarshal(content, &temp); err != nil {
return err
}
// Remove dot fields if it's a map
if tempMap, ok := temp.(map[string]any); ok {
for key := range tempMap {
if strings.HasPrefix(key, ".") {
delete(tempMap, key)
}
}
}
// Convert to JSON and decode with strict validation
jsonBytes, err := json.Marshal(temp)
if err != nil {
return err
}
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
decoder.DisallowUnknownFields()
return decoder.Decode(target)
}
// LoadConfigure loads configuration from bytes and unmarshal into c. // LoadConfigure loads configuration from bytes and unmarshal into c.
// Now it supports json, yaml and toml format. // Now it supports json, yaml and toml format.
func LoadConfigure(b []byte, c any, strict bool) error { func LoadConfigure(b []byte, c any, strict bool) error {
@@ -145,7 +118,7 @@ func LoadConfigure(b []byte, c any, strict bool) error {
defer v1.DisallowUnknownFieldsMu.Unlock() defer v1.DisallowUnknownFieldsMu.Unlock()
v1.DisallowUnknownFields = strict v1.DisallowUnknownFields = strict
var tomlObj any var tomlObj interface{}
// Try to unmarshal as TOML first; swallow errors from that (assume it's not valid TOML). // Try to unmarshal as TOML first; swallow errors from that (assume it's not valid TOML).
if err := toml.Unmarshal(b, &tomlObj); err == nil { if err := toml.Unmarshal(b, &tomlObj); err == nil {
b, err = json.Marshal(&tomlObj) b, err = json.Marshal(&tomlObj)
@@ -161,13 +134,10 @@ func LoadConfigure(b []byte, c any, strict bool) error {
} }
return decoder.Decode(c) return decoder.Decode(c)
} }
// It wasn't JSON. Unmarshal as YAML.
// Handle YAML content
if strict { if strict {
// In strict mode, always use our custom handler to support YAML merge return yaml.UnmarshalStrict(b, c)
return parseYAMLWithDotFieldsHandling(b, c)
} }
// Non-strict mode, parse normally
return yaml.Unmarshal(b, c) return yaml.Unmarshal(b, c)
} }

View File

@@ -112,29 +112,6 @@ func TestLoadServerConfigStrictMode(t *testing.T) {
} }
} }
func TestRenderWithTemplate(t *testing.T) {
tests := []struct {
name string
content string
want string
}{
{"toml", tomlServerContent, tomlServerContent},
{"yaml", yamlServerContent, yamlServerContent},
{"json", jsonServerContent, jsonServerContent},
{"template numeric", `key = {{ 123 }}`, "key = 123"},
{"template string", `key = {{ "xyz" }}`, "key = xyz"},
{"template quote", `key = {{ printf "%q" "with space" }}`, `key = "with space"`},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
got, err := RenderWithTemplate([]byte(test.content), nil)
require.NoError(err)
require.EqualValues(test.want, string(got))
})
}
}
func TestCustomStructStrictMode(t *testing.T) { func TestCustomStructStrictMode(t *testing.T) {
require := require.New(t) require := require.New(t)
@@ -187,122 +164,3 @@ unixPath = "/tmp/uds.sock"
err = LoadConfigure([]byte(pluginStr), &clientCfg, true) err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
require.Error(err) require.Error(err)
} }
// TestYAMLMergeInStrictMode tests that YAML merge functionality works
// even in strict mode by properly handling dot-prefixed fields
func TestYAMLMergeInStrictMode(t *testing.T) {
require := require.New(t)
yamlContent := `
serverAddr: "127.0.0.1"
serverPort: 7000
.common: &common
type: stcp
secretKey: "test-secret"
localIP: 127.0.0.1
transport:
useEncryption: true
useCompression: true
proxies:
- name: ssh
localPort: 22
<<: *common
- name: web
localPort: 80
<<: *common
`
clientCfg := v1.ClientConfig{}
// This should work in strict mode
err := LoadConfigure([]byte(yamlContent), &clientCfg, true)
require.NoError(err)
// Verify the merge worked correctly
require.Equal("127.0.0.1", clientCfg.ServerAddr)
require.Equal(7000, clientCfg.ServerPort)
require.Len(clientCfg.Proxies, 2)
// Check first proxy
sshProxy := clientCfg.Proxies[0].ProxyConfigurer
require.Equal("ssh", sshProxy.GetBaseConfig().Name)
require.Equal("stcp", sshProxy.GetBaseConfig().Type)
// Check second proxy
webProxy := clientCfg.Proxies[1].ProxyConfigurer
require.Equal("web", webProxy.GetBaseConfig().Name)
require.Equal("stcp", webProxy.GetBaseConfig().Type)
}
// TestOptimizedYAMLProcessing tests the optimization logic for YAML processing
func TestOptimizedYAMLProcessing(t *testing.T) {
require := require.New(t)
yamlWithDotFields := []byte(`
serverAddr: "127.0.0.1"
.common: &common
type: stcp
proxies:
- name: test
<<: *common
`)
yamlWithoutDotFields := []byte(`
serverAddr: "127.0.0.1"
proxies:
- name: test
type: tcp
localPort: 22
`)
// Test that YAML without dot fields works in strict mode
clientCfg := v1.ClientConfig{}
err := LoadConfigure(yamlWithoutDotFields, &clientCfg, true)
require.NoError(err)
require.Equal("127.0.0.1", clientCfg.ServerAddr)
require.Len(clientCfg.Proxies, 1)
require.Equal("test", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Name)
// Test that YAML with dot fields still works in strict mode
err = LoadConfigure(yamlWithDotFields, &clientCfg, true)
require.NoError(err)
require.Equal("127.0.0.1", clientCfg.ServerAddr)
require.Len(clientCfg.Proxies, 1)
require.Equal("test", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Name)
require.Equal("stcp", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Type)
}
// TestYAMLEdgeCases tests edge cases for YAML parsing, including non-map types
func TestYAMLEdgeCases(t *testing.T) {
require := require.New(t)
// Test array at root (should fail for frp config)
arrayYAML := []byte(`
- item1
- item2
`)
clientCfg := v1.ClientConfig{}
err := LoadConfigure(arrayYAML, &clientCfg, true)
require.Error(err) // Should fail because ClientConfig expects an object
// Test scalar at root (should fail for frp config)
scalarYAML := []byte(`"just a string"`)
err = LoadConfigure(scalarYAML, &clientCfg, true)
require.Error(err) // Should fail because ClientConfig expects an object
// Test empty object (should work)
emptyYAML := []byte(`{}`)
err = LoadConfigure(emptyYAML, &clientCfg, true)
require.NoError(err)
// Test nested structure without dots (should work)
nestedYAML := []byte(`
serverAddr: "127.0.0.1"
serverPort: 7000
`)
err = LoadConfigure(nestedYAML, &clientCfg, true)
require.NoError(err)
require.Equal("127.0.0.1", clientCfg.ServerAddr)
require.Equal(7000, clientCfg.ServerPort)
}

View File

@@ -159,18 +159,18 @@ func NewPortsRangeSliceFromString(str string) ([]PortsRange, error) {
out = append(out, PortsRange{Single: int(singleNum)}) out = append(out, PortsRange{Single: int(singleNum)})
case 2: case 2:
// range numbers // range numbers
minNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64) min, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("range number is invalid, %v", err) return nil, fmt.Errorf("range number is invalid, %v", err)
} }
maxNum, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64) max, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("range number is invalid, %v", err) return nil, fmt.Errorf("range number is invalid, %v", err)
} }
if maxNum < minNum { if max < min {
return nil, fmt.Errorf("range number is invalid") return nil, fmt.Errorf("range number is invalid")
} }
out = append(out, PortsRange{Start: int(minNum), End: int(maxNum)}) out = append(out, PortsRange{Start: int(min), End: int(max)})
default: default:
return nil, fmt.Errorf("range number is invalid") return nil, fmt.Errorf("range number is invalid")
} }

View File

@@ -58,14 +58,9 @@ type ClientCommonConfig struct {
// set. // set.
Start []string `json:"start,omitempty"` Start []string `json:"start,omitempty"`
Log LogConfig `json:"log,omitempty"` Log LogConfig `json:"log,omitempty"`
WebServer WebServerConfig `json:"webServer,omitempty"` WebServer WebServerConfig `json:"webServer,omitempty"`
Transport ClientTransportConfig `json:"transport,omitempty"` Transport ClientTransportConfig `json:"transport,omitempty"`
VirtualNet VirtualNetConfig `json:"virtualNet,omitempty"`
// FeatureGates specifies a set of feature gates to enable or disable.
// This can be used to enable alpha/beta features or disable default features.
FeatureGates map[string]bool `json:"featureGates,omitempty"`
// UDPPacketSize specifies the udp packet size // UDPPacketSize specifies the udp packet size
// By default, this value is 1500 // By default, this value is 1500
@@ -209,7 +204,3 @@ type AuthOIDCClientConfig struct {
// this field will be transfer to map[string][]string in OIDC token generator. // this field will be transfer to map[string][]string in OIDC token generator.
AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"` AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"`
} }
type VirtualNetConfig struct {
Address string `json:"address,omitempty"`
}

View File

@@ -20,41 +20,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"reflect" "reflect"
"github.com/samber/lo"
"github.com/fatedier/frp/pkg/util/util"
) )
const ( type ClientPluginOptions interface{}
PluginHTTP2HTTPS = "http2https"
PluginHTTPProxy = "http_proxy"
PluginHTTPS2HTTP = "https2http"
PluginHTTPS2HTTPS = "https2https"
PluginHTTP2HTTP = "http2http"
PluginSocks5 = "socks5"
PluginStaticFile = "static_file"
PluginUnixDomainSocket = "unix_domain_socket"
PluginTLS2Raw = "tls2raw"
PluginVirtualNet = "virtual_net"
)
var clientPluginOptionsTypeMap = map[string]reflect.Type{
PluginHTTP2HTTPS: reflect.TypeOf(HTTP2HTTPSPluginOptions{}),
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
PluginHTTP2HTTP: reflect.TypeOf(HTTP2HTTPPluginOptions{}),
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
PluginTLS2Raw: reflect.TypeOf(TLS2RawPluginOptions{}),
PluginVirtualNet: reflect.TypeOf(VirtualNetPluginOptions{}),
}
type ClientPluginOptions interface {
Complete()
}
type TypedClientPluginOptions struct { type TypedClientPluginOptions struct {
Type string `json:"type"` Type string `json:"type"`
@@ -100,6 +68,26 @@ func (c *TypedClientPluginOptions) MarshalJSON() ([]byte, error) {
return json.Marshal(c.ClientPluginOptions) return json.Marshal(c.ClientPluginOptions)
} }
const (
PluginHTTP2HTTPS = "http2https"
PluginHTTPProxy = "http_proxy"
PluginHTTPS2HTTP = "https2http"
PluginHTTPS2HTTPS = "https2https"
PluginSocks5 = "socks5"
PluginStaticFile = "static_file"
PluginUnixDomainSocket = "unix_domain_socket"
)
var clientPluginOptionsTypeMap = map[string]reflect.Type{
PluginHTTP2HTTPS: reflect.TypeOf(HTTP2HTTPSPluginOptions{}),
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
}
type HTTP2HTTPSPluginOptions struct { type HTTP2HTTPSPluginOptions struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
LocalAddr string `json:"localAddr,omitempty"` LocalAddr string `json:"localAddr,omitempty"`
@@ -107,61 +95,36 @@ type HTTP2HTTPSPluginOptions struct {
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"` RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
} }
func (o *HTTP2HTTPSPluginOptions) Complete() {}
type HTTPProxyPluginOptions struct { type HTTPProxyPluginOptions struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
HTTPUser string `json:"httpUser,omitempty"` HTTPUser string `json:"httpUser,omitempty"`
HTTPPassword string `json:"httpPassword,omitempty"` HTTPPassword string `json:"httpPassword,omitempty"`
} }
func (o *HTTPProxyPluginOptions) Complete() {}
type HTTPS2HTTPPluginOptions struct { type HTTPS2HTTPPluginOptions struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
LocalAddr string `json:"localAddr,omitempty"` LocalAddr string `json:"localAddr,omitempty"`
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"` HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"` RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
CrtPath string `json:"crtPath,omitempty"` CrtPath string `json:"crtPath,omitempty"`
KeyPath string `json:"keyPath,omitempty"` KeyPath string `json:"keyPath,omitempty"`
} }
func (o *HTTPS2HTTPPluginOptions) Complete() {
o.EnableHTTP2 = util.EmptyOr(o.EnableHTTP2, lo.ToPtr(true))
}
type HTTPS2HTTPSPluginOptions struct { type HTTPS2HTTPSPluginOptions struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
LocalAddr string `json:"localAddr,omitempty"` LocalAddr string `json:"localAddr,omitempty"`
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"` HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"` RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
CrtPath string `json:"crtPath,omitempty"` CrtPath string `json:"crtPath,omitempty"`
KeyPath string `json:"keyPath,omitempty"` KeyPath string `json:"keyPath,omitempty"`
} }
func (o *HTTPS2HTTPSPluginOptions) Complete() {
o.EnableHTTP2 = util.EmptyOr(o.EnableHTTP2, lo.ToPtr(true))
}
type HTTP2HTTPPluginOptions struct {
Type string `json:"type,omitempty"`
LocalAddr string `json:"localAddr,omitempty"`
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
}
func (o *HTTP2HTTPPluginOptions) Complete() {}
type Socks5PluginOptions struct { type Socks5PluginOptions struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
} }
func (o *Socks5PluginOptions) Complete() {}
type StaticFilePluginOptions struct { type StaticFilePluginOptions struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
LocalPath string `json:"localPath,omitempty"` LocalPath string `json:"localPath,omitempty"`
@@ -170,26 +133,7 @@ type StaticFilePluginOptions struct {
HTTPPassword string `json:"httpPassword,omitempty"` HTTPPassword string `json:"httpPassword,omitempty"`
} }
func (o *StaticFilePluginOptions) Complete() {}
type UnixDomainSocketPluginOptions struct { type UnixDomainSocketPluginOptions struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
UnixPath string `json:"unixPath,omitempty"` UnixPath string `json:"unixPath,omitempty"`
} }
func (o *UnixDomainSocketPluginOptions) Complete() {}
type TLS2RawPluginOptions struct {
Type string `json:"type,omitempty"`
LocalAddr string `json:"localAddr,omitempty"`
CrtPath string `json:"crtPath,omitempty"`
KeyPath string `json:"keyPath,omitempty"`
}
func (o *TLS2RawPluginOptions) Complete() {}
type VirtualNetPluginOptions struct {
Type string `json:"type,omitempty"`
}
func (o *VirtualNetPluginOptions) Complete() {}

View File

@@ -127,10 +127,6 @@ func (c *ProxyBaseConfig) Complete(namePrefix string) {
c.Name = lo.Ternary(namePrefix == "", "", namePrefix+".") + c.Name c.Name = lo.Ternary(namePrefix == "", "", namePrefix+".") + c.Name
c.LocalIP = util.EmptyOr(c.LocalIP, "127.0.0.1") c.LocalIP = util.EmptyOr(c.LocalIP, "127.0.0.1")
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient) c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
if c.Plugin.ClientPluginOptions != nil {
c.Plugin.Complete()
}
} }
func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) { func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {

View File

@@ -23,7 +23,6 @@ import (
"github.com/samber/lo" "github.com/samber/lo"
v1 "github.com/fatedier/frp/pkg/config/v1" v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/featuregate"
) )
func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) { func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
@@ -31,13 +30,6 @@ func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
warnings Warning warnings Warning
errs error errs error
) )
// validate feature gates
if c.VirtualNet.Address != "" {
if !featuregate.Enabled(featuregate.VirtualNet) {
return warnings, fmt.Errorf("VirtualNet feature is not enabled; enable it by setting the appropriate feature gate flag")
}
}
if !slices.Contains(SupportedAuthMethods, c.Auth.Method) { if !slices.Contains(SupportedAuthMethods, c.Auth.Method) {
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods)) errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
} }

View File

@@ -32,8 +32,6 @@ func ValidateClientPluginOptions(c v1.ClientPluginOptions) error {
return validateStaticFilePluginOptions(v) return validateStaticFilePluginOptions(v)
case *v1.UnixDomainSocketPluginOptions: case *v1.UnixDomainSocketPluginOptions:
return validateUnixDomainSocketPluginOptions(v) return validateUnixDomainSocketPluginOptions(v)
case *v1.TLS2RawPluginOptions:
return validateTLS2RawPluginOptions(v)
} }
return nil return nil
} }
@@ -72,10 +70,3 @@ func validateUnixDomainSocketPluginOptions(c *v1.UnixDomainSocketPluginOptions)
} }
return nil return nil
} }
func validateTLS2RawPluginOptions(c *v1.TLS2RawPluginOptions) error {
if c.LocalAddr == "" {
return errors.New("localAddr is required")
}
return nil
}

View File

@@ -44,9 +44,6 @@ type VisitorBaseConfig struct {
// It can be less than 0, it means don't bind to the port and only receive connections redirected from // It can be less than 0, it means don't bind to the port and only receive connections redirected from
// other visitors. (This is not supported for SUDP now) // other visitors. (This is not supported for SUDP now)
BindPort int `json:"bindPort,omitempty"` BindPort int `json:"bindPort,omitempty"`
// Plugin specifies what plugin should be used.
Plugin TypedVisitorPluginOptions `json:"plugin,omitempty"`
} }
func (c *VisitorBaseConfig) GetBaseConfig() *VisitorBaseConfig { func (c *VisitorBaseConfig) GetBaseConfig() *VisitorBaseConfig {

View File

@@ -1,86 +0,0 @@
// 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 (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
)
const (
VisitorPluginVirtualNet = "virtual_net"
)
var visitorPluginOptionsTypeMap = map[string]reflect.Type{
VisitorPluginVirtualNet: reflect.TypeOf(VirtualNetVisitorPluginOptions{}),
}
type VisitorPluginOptions interface {
Complete()
}
type TypedVisitorPluginOptions struct {
Type string `json:"type"`
VisitorPluginOptions
}
func (c *TypedVisitorPluginOptions) UnmarshalJSON(b []byte) error {
if len(b) == 4 && string(b) == "null" {
return nil
}
typeStruct := struct {
Type string `json:"type"`
}{}
if err := json.Unmarshal(b, &typeStruct); err != nil {
return err
}
c.Type = typeStruct.Type
if c.Type == "" {
return errors.New("visitor plugin type is empty")
}
v, ok := visitorPluginOptionsTypeMap[typeStruct.Type]
if !ok {
return fmt.Errorf("unknown visitor plugin type: %s", typeStruct.Type)
}
options := reflect.New(v).Interface().(VisitorPluginOptions)
decoder := json.NewDecoder(bytes.NewBuffer(b))
if DisallowUnknownFields {
decoder.DisallowUnknownFields()
}
if err := decoder.Decode(options); err != nil {
return fmt.Errorf("unmarshal VisitorPluginOptions error: %v", err)
}
c.VisitorPluginOptions = options
return nil
}
func (c *TypedVisitorPluginOptions) MarshalJSON() ([]byte, error) {
return json.Marshal(c.VisitorPluginOptions)
}
type VirtualNetVisitorPluginOptions struct {
Type string `json:"type"`
DestinationIP string `json:"destinationIP"`
}
func (o *VirtualNetVisitorPluginOptions) Complete() {}

View File

@@ -1,219 +0,0 @@
// 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 featuregate
import (
"fmt"
"sort"
"strings"
"sync"
"sync/atomic"
)
// Feature represents a feature gate name
type Feature string
// FeatureStage represents the maturity level of a feature
type FeatureStage string
const (
// Alpha means the feature is experimental and disabled by default
Alpha FeatureStage = "ALPHA"
// Beta means the feature is more stable but still might change and is disabled by default
Beta FeatureStage = "BETA"
// GA means the feature is generally available and enabled by default
GA FeatureStage = ""
)
// FeatureSpec describes a feature and its properties
type FeatureSpec struct {
// Default is the default enablement state for the feature
Default bool
// LockToDefault indicates the feature cannot be changed from its default
LockToDefault bool
// Stage indicates the maturity level of the feature
Stage FeatureStage
}
// Define all available features here
var (
VirtualNet = Feature("VirtualNet")
)
// defaultFeatures defines default features with their specifications
var defaultFeatures = map[Feature]FeatureSpec{
// Actual features
VirtualNet: {Default: false, Stage: Alpha},
}
// FeatureGate indicates whether a given feature is enabled or not
type FeatureGate interface {
// Enabled returns true if the key is enabled
Enabled(key Feature) bool
// KnownFeatures returns a slice of strings describing the known features
KnownFeatures() []string
}
// MutableFeatureGate allows for dynamic feature gate configuration
type MutableFeatureGate interface {
FeatureGate
// SetFromMap sets feature gate values from a map[string]bool
SetFromMap(m map[string]bool) error
// Add adds features to the feature gate
Add(features map[Feature]FeatureSpec) error
// String returns a string representing the feature gate configuration
String() string
}
// featureGate implements the FeatureGate and MutableFeatureGate interfaces
type featureGate struct {
// lock guards writes to known, enabled, and reads/writes of closed
lock sync.Mutex
// known holds a map[Feature]FeatureSpec
known atomic.Value
// enabled holds a map[Feature]bool
enabled atomic.Value
// closed is set to true once the feature gates are considered immutable
closed bool
}
// NewFeatureGate creates a new feature gate with the default features
func NewFeatureGate() MutableFeatureGate {
known := map[Feature]FeatureSpec{}
for k, v := range defaultFeatures {
known[k] = v
}
f := &featureGate{}
f.known.Store(known)
f.enabled.Store(map[Feature]bool{})
return f
}
// SetFromMap sets feature gate values from a map[string]bool
func (f *featureGate) SetFromMap(m map[string]bool) error {
f.lock.Lock()
defer f.lock.Unlock()
// Copy existing state
known := map[Feature]FeatureSpec{}
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
known[k] = v
}
enabled := map[Feature]bool{}
for k, v := range f.enabled.Load().(map[Feature]bool) {
enabled[k] = v
}
// Apply the new settings
for k, v := range m {
k := Feature(k)
featureSpec, ok := known[k]
if !ok {
return fmt.Errorf("unrecognized feature gate: %s", k)
}
if featureSpec.LockToDefault && featureSpec.Default != v {
return fmt.Errorf("cannot set feature gate %v to %v, feature is locked to %v", k, v, featureSpec.Default)
}
enabled[k] = v
}
// Persist the changes
f.known.Store(known)
f.enabled.Store(enabled)
return nil
}
// Add adds features to the feature gate
func (f *featureGate) Add(features map[Feature]FeatureSpec) error {
f.lock.Lock()
defer f.lock.Unlock()
if f.closed {
return fmt.Errorf("cannot add feature gates after the feature gate is closed")
}
// Copy existing state
known := map[Feature]FeatureSpec{}
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
known[k] = v
}
// Add new features
for name, spec := range features {
if existingSpec, found := known[name]; found {
if existingSpec == spec {
continue
}
return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec)
}
known[name] = spec
}
// Persist changes
f.known.Store(known)
return nil
}
// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,..."
func (f *featureGate) String() string {
pairs := []string{}
for k, v := range f.enabled.Load().(map[Feature]bool) {
pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
}
sort.Strings(pairs)
return strings.Join(pairs, ",")
}
// Enabled returns true if the key is enabled
func (f *featureGate) Enabled(key Feature) bool {
if v, ok := f.enabled.Load().(map[Feature]bool)[key]; ok {
return v
}
if v, ok := f.known.Load().(map[Feature]FeatureSpec)[key]; ok {
return v.Default
}
return false
}
// KnownFeatures returns a slice of strings describing the FeatureGate's known features
// GA features are hidden from the list
func (f *featureGate) KnownFeatures() []string {
knownFeatures := f.known.Load().(map[Feature]FeatureSpec)
known := make([]string, 0, len(knownFeatures))
for k, v := range knownFeatures {
if v.Stage == GA {
continue
}
known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, v.Stage, v.Default))
}
sort.Strings(known)
return known
}
// Default feature gates instance
var DefaultFeatureGates = NewFeatureGate()
// Enabled checks if a feature is enabled in the default feature gates
func Enabled(name Feature) bool {
return DefaultFeatureGates.Enabled(name)
}
// SetFromMap sets feature gate values from a map in the default feature gates
func SetFromMap(featureMap map[string]bool) error {
return DefaultFeatureGates.SetFromMap(featureMap)
}

View File

@@ -109,7 +109,7 @@ func (m *serverMetrics) NewProxy(name string, proxyType string) {
m.info.ProxyTypeCounts[proxyType] = counter m.info.ProxyTypeCounts[proxyType] = counter
proxyStats, ok := m.info.ProxyStatistics[name] proxyStats, ok := m.info.ProxyStatistics[name]
if !ok || proxyStats.ProxyType != proxyType { if !(ok && proxyStats.ProxyType == proxyType) {
proxyStats = &ProxyStatistics{ proxyStats = &ProxyStatistics{
Name: name, Name: name,
ProxyType: proxyType, ProxyType: proxyType,

View File

@@ -39,6 +39,6 @@ func ReadMsgInto(c io.Reader, msg Message) (err error) {
return msgCtl.ReadMsgInto(c, msg) return msgCtl.ReadMsgInto(c, msg)
} }
func WriteMsg(c io.Writer, msg any) (err error) { func WriteMsg(c io.Writer, msg interface{}) (err error) {
return msgCtl.WriteMsg(c, msg) return msgCtl.WriteMsg(c, msg)
} }

View File

@@ -40,7 +40,7 @@ const (
TypeNatHoleReport = '6' TypeNatHoleReport = '6'
) )
var msgTypeMap = map[byte]any{ var msgTypeMap = map[byte]interface{}{
TypeLogin: Login{}, TypeLogin: Login{},
TypeLoginResp: LoginResp{}, TypeLoginResp: LoginResp{},
TypeNewProxy: NewProxy{}, TypeNewProxy: NewProxy{},

View File

@@ -371,8 +371,8 @@ func getRangePorts(addrs []string, difference, maxNumber int) []msg.PortsRange {
return nil return nil
} }
addr, isLast := lo.Last(addrs) addr, err := lo.Last(addrs)
if !isLast { if err != nil {
return nil return nil
} }
var ports []msg.PortsRange var ports []msg.PortsRange

View File

@@ -78,9 +78,9 @@ func ListAllLocalIPs() ([]net.IP, error) {
return ips, nil return ips, nil
} }
func ListLocalIPsForNatHole(maxItems int) ([]string, error) { func ListLocalIPsForNatHole(max int) ([]string, error) {
if maxItems <= 0 { if max <= 0 {
return nil, fmt.Errorf("maxItems must be greater than 0") return nil, fmt.Errorf("max must be greater than 0")
} }
ips, err := ListAllLocalIPs() ips, err := ListAllLocalIPs()
@@ -88,9 +88,9 @@ func ListLocalIPsForNatHole(maxItems int) ([]string, error) {
return nil, err return nil, err
} }
filtered := make([]string, 0, maxItems) filtered := make([]string, 0, max)
for _, ip := range ips { for _, ip := range ips {
if len(filtered) >= maxItems { if len(filtered) >= max {
break break
} }

View File

@@ -1,92 +0,0 @@
// Copyright 2024 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !frps
package client
import (
"context"
stdlog "log"
"net/http"
"net/http/httputil"
"github.com/fatedier/golib/pool"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/util/log"
netpkg "github.com/fatedier/frp/pkg/util/net"
)
func init() {
Register(v1.PluginHTTP2HTTP, NewHTTP2HTTPPlugin)
}
type HTTP2HTTPPlugin struct {
opts *v1.HTTP2HTTPPluginOptions
l *Listener
s *http.Server
}
func NewHTTP2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.HTTP2HTTPPluginOptions)
listener := NewProxyListener()
p := &HTTP2HTTPPlugin{
opts: opts,
l: listener,
}
rp := &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) {
req := r.Out
req.URL.Scheme = "http"
req.URL.Host = p.opts.LocalAddr
if p.opts.HostHeaderRewrite != "" {
req.Host = p.opts.HostHeaderRewrite
}
for k, v := range p.opts.RequestHeaders.Set {
req.Header.Set(k, v)
}
},
BufferPool: pool.NewBuffer(32 * 1024),
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
}
p.s = &http.Server{
Handler: rp,
ReadHeaderTimeout: 0,
}
go func() {
_ = p.s.Serve(listener)
}()
return p, nil
}
func (p *HTTP2HTTPPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
_ = p.l.PutConn(wrapConn)
}
func (p *HTTP2HTTPPlugin) Name() string {
return v1.PluginHTTP2HTTP
}
func (p *HTTP2HTTPPlugin) Close() error {
return p.s.Close()
}

View File

@@ -14,12 +14,13 @@
//go:build !frps //go:build !frps
package client package plugin
import ( import (
"context"
"crypto/tls" "crypto/tls"
"io"
stdlog "log" stdlog "log"
"net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
@@ -41,7 +42,7 @@ type HTTP2HTTPSPlugin struct {
s *http.Server s *http.Server
} }
func NewHTTP2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) { func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.HTTP2HTTPSPluginOptions) opts := options.(*v1.HTTP2HTTPSPluginOptions)
listener := NewProxyListener() listener := NewProxyListener()
@@ -87,8 +88,8 @@ func NewHTTP2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugi
return p, nil return p, nil
} }
func (p *HTTP2HTTPSPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) { func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn) wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
_ = p.l.PutConn(wrapConn) _ = p.l.PutConn(wrapConn)
} }

View File

@@ -14,11 +14,10 @@
//go:build !frps //go:build !frps
package client package plugin
import ( import (
"bufio" "bufio"
"context"
"encoding/base64" "encoding/base64"
"io" "io"
"net" "net"
@@ -45,7 +44,7 @@ type HTTPProxy struct {
s *http.Server s *http.Server
} }
func NewHTTPProxyPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) { func NewHTTPProxyPlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.HTTPProxyPluginOptions) opts := options.(*v1.HTTPProxyPluginOptions)
listener := NewProxyListener() listener := NewProxyListener()
@@ -69,8 +68,8 @@ func (hp *HTTPProxy) Name() string {
return v1.PluginHTTPProxy return v1.PluginHTTPProxy
} }
func (hp *HTTPProxy) Handle(_ context.Context, connInfo *ConnectionInfo) { func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn) wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
sc, rd := libnet.NewSharedConn(wrapConn) sc, rd := libnet.NewSharedConn(wrapConn)
firstBytes := make([]byte, 7) firstBytes := make([]byte, 7)

View File

@@ -14,23 +14,22 @@
//go:build !frps //go:build !frps
package client package plugin
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io"
stdlog "log" stdlog "log"
"net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"time" "time"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
"github.com/samber/lo"
v1 "github.com/fatedier/frp/pkg/config/v1" v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/transport" "github.com/fatedier/frp/pkg/transport"
httppkg "github.com/fatedier/frp/pkg/util/http"
"github.com/fatedier/frp/pkg/util/log" "github.com/fatedier/frp/pkg/util/log"
netpkg "github.com/fatedier/frp/pkg/util/net" netpkg "github.com/fatedier/frp/pkg/util/net"
) )
@@ -46,7 +45,7 @@ type HTTPS2HTTPPlugin struct {
s *http.Server s *http.Server
} }
func NewHTTPS2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) { func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.HTTPS2HTTPPluginOptions) opts := options.(*v1.HTTPS2HTTPPluginOptions)
listener := NewProxyListener() listener := NewProxyListener()
@@ -72,31 +71,26 @@ func NewHTTPS2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugi
BufferPool: pool.NewBuffer(32 * 1024), BufferPool: pool.NewBuffer(32 * 1024),
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0), ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
} }
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.TLS != nil {
tlsServerName, _ := httppkg.CanonicalHost(r.TLS.ServerName)
host, _ := httppkg.CanonicalHost(r.Host)
if tlsServerName != "" && tlsServerName != host {
w.WriteHeader(http.StatusMisdirectedRequest)
return
}
}
rp.ServeHTTP(w, r)
})
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "") var (
tlsConfig *tls.Config
err error
)
if opts.CrtPath != "" || opts.KeyPath != "" {
tlsConfig, err = p.genTLSConfig()
} else {
tlsConfig, err = transport.NewServerTLSConfig("", "", "")
tlsConfig.InsecureSkipVerify = true
}
if err != nil { if err != nil {
return nil, fmt.Errorf("gen TLS config error: %v", err) return nil, fmt.Errorf("gen TLS config error: %v", err)
} }
p.s = &http.Server{ p.s = &http.Server{
Handler: handler, Handler: rp,
ReadHeaderTimeout: 60 * time.Second, ReadHeaderTimeout: 60 * time.Second,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
} }
if !lo.FromPtr(opts.EnableHTTP2) {
p.s.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
}
go func() { go func() {
_ = p.s.ServeTLS(listener, "", "") _ = p.s.ServeTLS(listener, "", "")
@@ -104,10 +98,20 @@ func NewHTTPS2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugi
return p, nil return p, nil
} }
func (p *HTTPS2HTTPPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) { func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn) cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
if connInfo.SrcAddr != nil { if err != nil {
wrapConn.SetRemoteAddr(connInfo.SrcAddr) return nil, err
}
config := &tls.Config{Certificates: []tls.Certificate{cert}}
return config, nil
}
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
if extra.SrcAddr != nil {
wrapConn.SetRemoteAddr(extra.SrcAddr)
} }
_ = p.l.PutConn(wrapConn) _ = p.l.PutConn(wrapConn)
} }

View File

@@ -14,23 +14,22 @@
//go:build !frps //go:build !frps
package client package plugin
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io"
stdlog "log" stdlog "log"
"net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"time" "time"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
"github.com/samber/lo"
v1 "github.com/fatedier/frp/pkg/config/v1" v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/transport" "github.com/fatedier/frp/pkg/transport"
httppkg "github.com/fatedier/frp/pkg/util/http"
"github.com/fatedier/frp/pkg/util/log" "github.com/fatedier/frp/pkg/util/log"
netpkg "github.com/fatedier/frp/pkg/util/net" netpkg "github.com/fatedier/frp/pkg/util/net"
) )
@@ -46,7 +45,7 @@ type HTTPS2HTTPSPlugin struct {
s *http.Server s *http.Server
} }
func NewHTTPS2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) { func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.HTTPS2HTTPSPluginOptions) opts := options.(*v1.HTTPS2HTTPSPluginOptions)
listener := NewProxyListener() listener := NewProxyListener()
@@ -78,31 +77,26 @@ func NewHTTPS2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plug
BufferPool: pool.NewBuffer(32 * 1024), BufferPool: pool.NewBuffer(32 * 1024),
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0), ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
} }
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.TLS != nil {
tlsServerName, _ := httppkg.CanonicalHost(r.TLS.ServerName)
host, _ := httppkg.CanonicalHost(r.Host)
if tlsServerName != "" && tlsServerName != host {
w.WriteHeader(http.StatusMisdirectedRequest)
return
}
}
rp.ServeHTTP(w, r)
})
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "") var (
tlsConfig *tls.Config
err error
)
if opts.CrtPath != "" || opts.KeyPath != "" {
tlsConfig, err = p.genTLSConfig()
} else {
tlsConfig, err = transport.NewServerTLSConfig("", "", "")
tlsConfig.InsecureSkipVerify = true
}
if err != nil { if err != nil {
return nil, fmt.Errorf("gen TLS config error: %v", err) return nil, fmt.Errorf("gen TLS config error: %v", err)
} }
p.s = &http.Server{ p.s = &http.Server{
Handler: handler, Handler: rp,
ReadHeaderTimeout: 60 * time.Second, ReadHeaderTimeout: 60 * time.Second,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
} }
if !lo.FromPtr(opts.EnableHTTP2) {
p.s.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
}
go func() { go func() {
_ = p.s.ServeTLS(listener, "", "") _ = p.s.ServeTLS(listener, "", "")
@@ -110,10 +104,20 @@ func NewHTTPS2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plug
return p, nil return p, nil
} }
func (p *HTTPS2HTTPSPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) { func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) {
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn) cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
if connInfo.SrcAddr != nil { if err != nil {
wrapConn.SetRemoteAddr(connInfo.SrcAddr) return nil, err
}
config := &tls.Config{Certificates: []tls.Certificate{cert}}
return config, nil
}
func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
if extra.SrcAddr != nil {
wrapConn.SetRemoteAddr(extra.SrcAddr)
} }
_ = p.l.PutConn(wrapConn) _ = p.l.PutConn(wrapConn)
} }

View File

@@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package client package plugin
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net" "net"
@@ -25,18 +24,13 @@ import (
pp "github.com/pires/go-proxyproto" pp "github.com/pires/go-proxyproto"
v1 "github.com/fatedier/frp/pkg/config/v1" v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/vnet"
) )
type PluginContext struct {
Name string
VnetController *vnet.Controller
}
// Creators is used for create plugins to handle connections. // Creators is used for create plugins to handle connections.
var creators = make(map[string]CreatorFn) var creators = make(map[string]CreatorFn)
type CreatorFn func(pluginCtx PluginContext, options v1.ClientPluginOptions) (Plugin, error) // params has prefix "plugin_"
type CreatorFn func(options v1.ClientPluginOptions) (Plugin, error)
func Register(name string, fn CreatorFn) { func Register(name string, fn CreatorFn) {
if _, exist := creators[name]; exist { if _, exist := creators[name]; exist {
@@ -45,19 +39,16 @@ func Register(name string, fn CreatorFn) {
creators[name] = fn creators[name] = fn
} }
func Create(pluginName string, pluginCtx PluginContext, options v1.ClientPluginOptions) (p Plugin, err error) { func Create(name string, options v1.ClientPluginOptions) (p Plugin, err error) {
if fn, ok := creators[pluginName]; ok { if fn, ok := creators[name]; ok {
p, err = fn(pluginCtx, options) p, err = fn(options)
} else { } else {
err = fmt.Errorf("plugin [%s] is not registered", pluginName) err = fmt.Errorf("plugin [%s] is not registered", name)
} }
return return
} }
type ConnectionInfo struct { type ExtraInfo struct {
Conn io.ReadWriteCloser
UnderlyingConn net.Conn
ProxyProtocolHeader *pp.Header ProxyProtocolHeader *pp.Header
SrcAddr net.Addr SrcAddr net.Addr
DstAddr net.Addr DstAddr net.Addr
@@ -66,7 +57,7 @@ type ConnectionInfo struct {
type Plugin interface { type Plugin interface {
Name() string Name() string
Handle(ctx context.Context, connInfo *ConnectionInfo) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo)
Close() error Close() error
} }

View File

@@ -14,12 +14,12 @@
//go:build !frps //go:build !frps
package client package plugin
import ( import (
"context"
"io" "io"
"log" "log"
"net"
gosocks5 "github.com/armon/go-socks5" gosocks5 "github.com/armon/go-socks5"
@@ -35,7 +35,7 @@ type Socks5Plugin struct {
Server *gosocks5.Server Server *gosocks5.Server
} }
func NewSocks5Plugin(_ PluginContext, options v1.ClientPluginOptions) (p Plugin, err error) { func NewSocks5Plugin(options v1.ClientPluginOptions) (p Plugin, err error) {
opts := options.(*v1.Socks5PluginOptions) opts := options.(*v1.Socks5PluginOptions)
cfg := &gosocks5.Config{ cfg := &gosocks5.Config{
@@ -50,9 +50,9 @@ func NewSocks5Plugin(_ PluginContext, options v1.ClientPluginOptions) (p Plugin,
return return
} }
func (sp *Socks5Plugin) Handle(_ context.Context, connInfo *ConnectionInfo) { func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
defer connInfo.Conn.Close() defer conn.Close()
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn) wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
_ = sp.Server.ServeConn(wrapConn) _ = sp.Server.ServeConn(wrapConn)
} }

View File

@@ -14,10 +14,11 @@
//go:build !frps //go:build !frps
package client package plugin
import ( import (
"context" "io"
"net"
"net/http" "net/http"
"time" "time"
@@ -38,7 +39,7 @@ type StaticFilePlugin struct {
s *http.Server s *http.Server
} }
func NewStaticFilePlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) { func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.StaticFilePluginOptions) opts := options.(*v1.StaticFilePluginOptions)
listener := NewProxyListener() listener := NewProxyListener()
@@ -68,8 +69,8 @@ func NewStaticFilePlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugi
return sp, nil return sp, nil
} }
func (sp *StaticFilePlugin) Handle(_ context.Context, connInfo *ConnectionInfo) { func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn) wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
_ = sp.l.PutConn(wrapConn) _ = sp.l.PutConn(wrapConn)
} }

View File

@@ -1,82 +0,0 @@
// Copyright 2024 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !frps
package client
import (
"context"
"crypto/tls"
"net"
libio "github.com/fatedier/golib/io"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/transport"
netpkg "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/xlog"
)
func init() {
Register(v1.PluginTLS2Raw, NewTLS2RawPlugin)
}
type TLS2RawPlugin struct {
opts *v1.TLS2RawPluginOptions
tlsConfig *tls.Config
}
func NewTLS2RawPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.TLS2RawPluginOptions)
p := &TLS2RawPlugin{
opts: opts,
}
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
if err != nil {
return nil, err
}
p.tlsConfig = tlsConfig
return p, nil
}
func (p *TLS2RawPlugin) Handle(ctx context.Context, connInfo *ConnectionInfo) {
xl := xlog.FromContextSafe(ctx)
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
tlsConn := tls.Server(wrapConn, p.tlsConfig)
if err := tlsConn.Handshake(); err != nil {
xl.Warnf("tls handshake error: %v", err)
return
}
rawConn, err := net.Dial("tcp", p.opts.LocalAddr)
if err != nil {
xl.Warnf("dial to local addr error: %v", err)
return
}
libio.Join(tlsConn, rawConn)
}
func (p *TLS2RawPlugin) Name() string {
return v1.PluginTLS2Raw
}
func (p *TLS2RawPlugin) Close() error {
return nil
}

View File

@@ -14,16 +14,15 @@
//go:build !frps //go:build !frps
package client package plugin
import ( import (
"context" "io"
"net" "net"
libio "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
v1 "github.com/fatedier/frp/pkg/config/v1" v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/util/xlog"
) )
func init() { func init() {
@@ -34,7 +33,7 @@ type UnixDomainSocketPlugin struct {
UnixAddr *net.UnixAddr UnixAddr *net.UnixAddr
} }
func NewUnixDomainSocketPlugin(_ PluginContext, options v1.ClientPluginOptions) (p Plugin, err error) { func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err error) {
opts := options.(*v1.UnixDomainSocketPluginOptions) opts := options.(*v1.UnixDomainSocketPluginOptions)
unixAddr, errRet := net.ResolveUnixAddr("unix", opts.UnixPath) unixAddr, errRet := net.ResolveUnixAddr("unix", opts.UnixPath)
@@ -49,20 +48,18 @@ func NewUnixDomainSocketPlugin(_ PluginContext, options v1.ClientPluginOptions)
return return
} }
func (uds *UnixDomainSocketPlugin) Handle(ctx context.Context, connInfo *ConnectionInfo) { func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, _ net.Conn, extra *ExtraInfo) {
xl := xlog.FromContextSafe(ctx)
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr) localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
if err != nil { if err != nil {
xl.Warnf("dial to uds %s error: %v", uds.UnixAddr, err)
return return
} }
if connInfo.ProxyProtocolHeader != nil { if extra.ProxyProtocolHeader != nil {
if _, err := connInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil { if _, err := extra.ProxyProtocolHeader.WriteTo(localConn); err != nil {
return return
} }
} }
libio.Join(localConn, connInfo.Conn) libio.Join(localConn, conn)
} }
func (uds *UnixDomainSocketPlugin) Name() string { func (uds *UnixDomainSocketPlugin) Name() string {

View File

@@ -1,92 +0,0 @@
// 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.
//go:build !frps
package client
import (
"context"
"io"
"sync"
v1 "github.com/fatedier/frp/pkg/config/v1"
)
func init() {
Register(v1.PluginVirtualNet, NewVirtualNetPlugin)
}
type VirtualNetPlugin struct {
pluginCtx PluginContext
opts *v1.VirtualNetPluginOptions
mu sync.Mutex
conns map[io.ReadWriteCloser]struct{}
}
func NewVirtualNetPlugin(pluginCtx PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.VirtualNetPluginOptions)
p := &VirtualNetPlugin{
pluginCtx: pluginCtx,
opts: opts,
}
return p, nil
}
func (p *VirtualNetPlugin) Handle(ctx context.Context, connInfo *ConnectionInfo) {
// Verify if virtual network controller is available
if p.pluginCtx.VnetController == nil {
return
}
// Add the connection before starting the read loop to avoid race condition
// where RemoveConn might be called before the connection is added.
p.mu.Lock()
if p.conns == nil {
p.conns = make(map[io.ReadWriteCloser]struct{})
}
p.conns[connInfo.Conn] = struct{}{}
p.mu.Unlock()
// Register the connection with the controller and pass the cleanup function
p.pluginCtx.VnetController.StartServerConnReadLoop(ctx, connInfo.Conn, func() {
p.RemoveConn(connInfo.Conn)
})
}
func (p *VirtualNetPlugin) RemoveConn(conn io.ReadWriteCloser) {
p.mu.Lock()
defer p.mu.Unlock()
// Check if the map exists, as Close might have set it to nil concurrently
if p.conns != nil {
delete(p.conns, conn)
}
}
func (p *VirtualNetPlugin) Name() string {
return v1.PluginVirtualNet
}
func (p *VirtualNetPlugin) Close() error {
p.mu.Lock()
defer p.mu.Unlock()
// Close any remaining connections
for conn := range p.conns {
_ = conn.Close()
}
p.conns = nil
return nil
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package server package plugin
import ( import (
"bytes" "bytes"
@@ -72,7 +72,7 @@ func (p *httpPlugin) IsSupport(op string) bool {
return false return false
} }
func (p *httpPlugin) Handle(ctx context.Context, op string, content any) (*Response, any, error) { func (p *httpPlugin) Handle(ctx context.Context, op string, content interface{}) (*Response, interface{}, error) {
r := &Request{ r := &Request{
Version: APIVersion, Version: APIVersion,
Op: op, Op: op,

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package server package plugin
import ( import (
"context" "context"
@@ -75,7 +75,7 @@ func (m *Manager) Login(content *LoginContent) (*LoginContent, error) {
Reject: false, Reject: false,
Unchange: true, Unchange: true,
} }
retContent any retContent interface{}
err error err error
) )
reqid, _ := util.RandID() reqid, _ := util.RandID()
@@ -109,7 +109,7 @@ func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
Reject: false, Reject: false,
Unchange: true, Unchange: true,
} }
retContent any retContent interface{}
err error err error
) )
reqid, _ := util.RandID() reqid, _ := util.RandID()
@@ -168,7 +168,7 @@ func (m *Manager) Ping(content *PingContent) (*PingContent, error) {
Reject: false, Reject: false,
Unchange: true, Unchange: true,
} }
retContent any retContent interface{}
err error err error
) )
reqid, _ := util.RandID() reqid, _ := util.RandID()
@@ -202,7 +202,7 @@ func (m *Manager) NewWorkConn(content *NewWorkConnContent) (*NewWorkConnContent,
Reject: false, Reject: false,
Unchange: true, Unchange: true,
} }
retContent any retContent interface{}
err error err error
) )
reqid, _ := util.RandID() reqid, _ := util.RandID()
@@ -236,7 +236,7 @@ func (m *Manager) NewUserConn(content *NewUserConnContent) (*NewUserConnContent,
Reject: false, Reject: false,
Unchange: true, Unchange: true,
} }
retContent any retContent interface{}
err error err error
) )
reqid, _ := util.RandID() reqid, _ := util.RandID()

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package server package plugin
import ( import (
"context" "context"
@@ -32,5 +32,5 @@ const (
type Plugin interface { type Plugin interface {
Name() string Name() string
IsSupport(op string) bool IsSupport(op string) bool
Handle(ctx context.Context, op string, content any) (res *Response, retContent any, err error) Handle(ctx context.Context, op string, content interface{}) (res *Response, retContent interface{}, err error)
} }

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package server package plugin
import ( import (
"context" "context"

View File

@@ -12,23 +12,23 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package server package plugin
import ( import (
"github.com/fatedier/frp/pkg/msg" "github.com/fatedier/frp/pkg/msg"
) )
type Request struct { type Request struct {
Version string `json:"version"` Version string `json:"version"`
Op string `json:"op"` Op string `json:"op"`
Content any `json:"content"` Content interface{} `json:"content"`
} }
type Response struct { type Response struct {
Reject bool `json:"reject"` Reject bool `json:"reject"`
RejectReason string `json:"reject_reason"` RejectReason string `json:"reject_reason"`
Unchange bool `json:"unchange"` Unchange bool `json:"unchange"`
Content any `json:"content"` Content interface{} `json:"content"`
} }
type LoginContent struct { type LoginContent struct {

View File

@@ -1,58 +0,0 @@
// 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 visitor
import (
"context"
"fmt"
"net"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/vnet"
)
type PluginContext struct {
Name string
Ctx context.Context
VnetController *vnet.Controller
HandleConn func(net.Conn)
}
// Creators is used for create plugins to handle connections.
var creators = make(map[string]CreatorFn)
type CreatorFn func(pluginCtx PluginContext, options v1.VisitorPluginOptions) (Plugin, error)
func Register(name string, fn CreatorFn) {
if _, exist := creators[name]; exist {
panic(fmt.Sprintf("plugin [%s] is already registered", name))
}
creators[name] = fn
}
func Create(pluginName string, pluginCtx PluginContext, options v1.VisitorPluginOptions) (p Plugin, err error) {
if fn, ok := creators[pluginName]; ok {
p, err = fn(pluginCtx, options)
} else {
err = fmt.Errorf("plugin [%s] is not registered", pluginName)
}
return
}
type Plugin interface {
Name() string
Start()
Close() error
}

View File

@@ -1,192 +0,0 @@
// 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.
//go:build !frps
package visitor
import (
"context"
"errors"
"fmt"
"net"
"sync"
"time"
v1 "github.com/fatedier/frp/pkg/config/v1"
netutil "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/xlog"
)
func init() {
Register(v1.VisitorPluginVirtualNet, NewVirtualNetPlugin)
}
type VirtualNetPlugin struct {
pluginCtx PluginContext
routes []net.IPNet
mu sync.Mutex
controllerConn net.Conn
closeSignal chan struct{}
ctx context.Context
cancel context.CancelFunc
}
func NewVirtualNetPlugin(pluginCtx PluginContext, options v1.VisitorPluginOptions) (Plugin, error) {
opts := options.(*v1.VirtualNetVisitorPluginOptions)
p := &VirtualNetPlugin{
pluginCtx: pluginCtx,
routes: make([]net.IPNet, 0),
}
p.ctx, p.cancel = context.WithCancel(pluginCtx.Ctx)
if opts.DestinationIP == "" {
return nil, errors.New("destinationIP is required")
}
// Parse DestinationIP and create a host route.
ip := net.ParseIP(opts.DestinationIP)
if ip == nil {
return nil, fmt.Errorf("invalid destination IP address [%s]", opts.DestinationIP)
}
var mask net.IPMask
if ip.To4() != nil {
mask = net.CIDRMask(32, 32) // /32 for IPv4
} else {
mask = net.CIDRMask(128, 128) // /128 for IPv6
}
p.routes = append(p.routes, net.IPNet{IP: ip, Mask: mask})
return p, nil
}
func (p *VirtualNetPlugin) Name() string {
return v1.VisitorPluginVirtualNet
}
func (p *VirtualNetPlugin) Start() {
xl := xlog.FromContextSafe(p.pluginCtx.Ctx)
if p.pluginCtx.VnetController == nil {
return
}
routeStr := "unknown"
if len(p.routes) > 0 {
routeStr = p.routes[0].String()
}
xl.Infof("starting VirtualNetPlugin for visitor [%s], attempting to register routes for %s", p.pluginCtx.Name, routeStr)
go p.run()
}
func (p *VirtualNetPlugin) run() {
xl := xlog.FromContextSafe(p.ctx)
reconnectDelay := 10 * time.Second
for {
currentCloseSignal := make(chan struct{})
p.mu.Lock()
p.closeSignal = currentCloseSignal
p.mu.Unlock()
select {
case <-p.ctx.Done():
xl.Infof("VirtualNetPlugin run loop for visitor [%s] stopping (context cancelled before pipe creation).", p.pluginCtx.Name)
p.cleanupControllerConn(xl)
return
default:
}
controllerConn, pluginConn := net.Pipe()
p.mu.Lock()
p.controllerConn = controllerConn
p.mu.Unlock()
pluginNotifyConn := netutil.WrapCloseNotifyConn(pluginConn, func() {
close(currentCloseSignal) // Signal the run loop on close.
})
xl.Infof("attempting to register client route for visitor [%s]", p.pluginCtx.Name)
p.pluginCtx.VnetController.RegisterClientRoute(p.ctx, p.pluginCtx.Name, p.routes, controllerConn)
xl.Infof("successfully registered client route for visitor [%s]. Starting connection handler with CloseNotifyConn.", p.pluginCtx.Name)
// Pass the CloseNotifyConn to HandleConn.
// HandleConn is responsible for calling Close() on pluginNotifyConn.
p.pluginCtx.HandleConn(pluginNotifyConn)
// Wait for context cancellation or connection close.
select {
case <-p.ctx.Done():
xl.Infof("VirtualNetPlugin run loop stopping for visitor [%s] (context cancelled while waiting).", p.pluginCtx.Name)
p.cleanupControllerConn(xl)
return
case <-currentCloseSignal:
xl.Infof("detected connection closed via CloseNotifyConn for visitor [%s].", p.pluginCtx.Name)
// HandleConn closed the plugin side. Close the controller side.
p.cleanupControllerConn(xl)
xl.Infof("waiting %v before attempting reconnection for visitor [%s]...", reconnectDelay, p.pluginCtx.Name)
select {
case <-time.After(reconnectDelay):
case <-p.ctx.Done():
xl.Infof("VirtualNetPlugin reconnection delay interrupted for visitor [%s]", p.pluginCtx.Name)
return
}
}
xl.Infof("re-establishing virtual connection for visitor [%s]...", p.pluginCtx.Name)
}
}
// cleanupControllerConn closes the current controllerConn (if it exists) under lock.
func (p *VirtualNetPlugin) cleanupControllerConn(xl *xlog.Logger) {
p.mu.Lock()
defer p.mu.Unlock()
if p.controllerConn != nil {
xl.Debugf("cleaning up controllerConn for visitor [%s]", p.pluginCtx.Name)
p.controllerConn.Close()
p.controllerConn = nil
}
p.closeSignal = nil
}
// Close initiates the plugin shutdown.
func (p *VirtualNetPlugin) Close() error {
xl := xlog.FromContextSafe(p.pluginCtx.Ctx)
xl.Infof("closing VirtualNetPlugin for visitor [%s]", p.pluginCtx.Name)
// Signal the run loop goroutine to stop.
p.cancel()
// Unregister the route from the controller.
if p.pluginCtx.VnetController != nil {
p.pluginCtx.VnetController.UnregisterClientRoute(p.pluginCtx.Name)
xl.Infof("unregistered client route for visitor [%s]", p.pluginCtx.Name)
}
// Explicitly close the controller side of the pipe.
// This ensures the pipe is broken even if the run loop is stuck or HandleConn hasn't closed its end.
p.cleanupControllerConn(xl)
xl.Infof("finished cleaning up connections during close for visitor [%s]", p.pluginCtx.Name)
return nil
}

View File

@@ -24,7 +24,6 @@ import (
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
"github.com/fatedier/frp/pkg/msg" "github.com/fatedier/frp/pkg/msg"
netpkg "github.com/fatedier/frp/pkg/util/net"
) )
func NewUDPPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UDPPacket { func NewUDPPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UDPPacket {
@@ -70,7 +69,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, proxyProtocolVersion string) { func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<- msg.Message, bufSize int) {
var mu sync.RWMutex var mu sync.RWMutex
udpConnMap := make(map[string]*net.UDPConn) udpConnMap := make(map[string]*net.UDPConn)
@@ -111,7 +110,6 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<-
if err != nil { if err != nil {
continue continue
} }
mu.Lock() mu.Lock()
udpConn, ok := udpConnMap[udpMsg.RemoteAddr.String()] udpConn, ok := udpConnMap[udpMsg.RemoteAddr.String()]
if !ok { if !ok {
@@ -124,18 +122,6 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<-
} }
mu.Unlock() mu.Unlock()
// Add proxy protocol header if configured
if proxyProtocolVersion != "" && udpMsg.RemoteAddr != nil {
ppBuf, err := netpkg.BuildProxyProtocolHeader(udpMsg.RemoteAddr, dstAddr, proxyProtocolVersion)
if err == nil {
// Prepend proxy protocol header to the UDP payload
finalBuf := make([]byte, len(ppBuf)+len(buf))
copy(finalBuf, ppBuf)
copy(finalBuf[len(ppBuf):], buf)
buf = finalBuf
}
}
_, err = udpConn.Write(buf) _, err = udpConn.Write(buf)
if err != nil { if err != nil {
udpConn.Close() udpConn.Close()

View File

@@ -3,16 +3,16 @@ package udp
import ( import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/assert"
) )
func TestUdpPacket(t *testing.T) { func TestUdpPacket(t *testing.T) {
require := require.New(t) assert := assert.New(t)
buf := []byte("hello world") buf := []byte("hello world")
udpMsg := NewUDPPacket(buf, nil, nil) udpMsg := NewUDPPacket(buf, nil, nil)
newBuf, err := GetContent(udpMsg) newBuf, err := GetContent(udpMsg)
require.NoError(err) assert.NoError(err)
require.EqualValues(buf, newBuf) assert.EqualValues(buf, newBuf)
} }

View File

@@ -1,7 +1,6 @@
package client package client
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@@ -32,8 +31,8 @@ func (c *Client) SetAuth(user, pwd string) {
c.authPwd = pwd c.authPwd = pwd
} }
func (c *Client) GetProxyStatus(ctx context.Context, name string) (*client.ProxyStatusResp, error) { func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/status", nil) req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -55,8 +54,8 @@ func (c *Client) GetProxyStatus(ctx context.Context, name string) (*client.Proxy
return nil, fmt.Errorf("no proxy status found") return nil, fmt.Errorf("no proxy status found")
} }
func (c *Client) GetAllProxyStatus(ctx context.Context) (client.StatusResp, error) { func (c *Client) GetAllProxyStatus() (client.StatusResp, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/status", nil) req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -71,7 +70,7 @@ func (c *Client) GetAllProxyStatus(ctx context.Context) (client.StatusResp, erro
return allStatus, nil return allStatus, nil
} }
func (c *Client) Reload(ctx context.Context, strictMode bool) error { func (c *Client) Reload(strictMode bool) error {
v := url.Values{} v := url.Values{}
if strictMode { if strictMode {
v.Set("strictConfig", "true") v.Set("strictConfig", "true")
@@ -80,7 +79,7 @@ func (c *Client) Reload(ctx context.Context, strictMode bool) error {
if len(v) > 0 { if len(v) > 0 {
queryStr = "?" + v.Encode() queryStr = "?" + v.Encode()
} }
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/reload"+queryStr, nil) req, err := http.NewRequest("GET", "http://"+c.address+"/api/reload"+queryStr, nil)
if err != nil { if err != nil {
return err return err
} }
@@ -88,8 +87,8 @@ func (c *Client) Reload(ctx context.Context, strictMode bool) error {
return err return err
} }
func (c *Client) Stop(ctx context.Context) error { func (c *Client) Stop() error {
req, err := http.NewRequestWithContext(ctx, "POST", "http://"+c.address+"/api/stop", nil) req, err := http.NewRequest("POST", "http://"+c.address+"/api/stop", nil)
if err != nil { if err != nil {
return err return err
} }
@@ -97,16 +96,16 @@ func (c *Client) Stop(ctx context.Context) error {
return err return err
} }
func (c *Client) GetConfig(ctx context.Context) (string, error) { func (c *Client) GetConfig() (string, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/config", nil) req, err := http.NewRequest("GET", "http://"+c.address+"/api/config", nil)
if err != nil { if err != nil {
return "", err return "", err
} }
return c.do(req) return c.do(req)
} }
func (c *Client) UpdateConfig(ctx context.Context, content string) error { func (c *Client) UpdateConfig(content string) error {
req, err := http.NewRequestWithContext(ctx, "PUT", "http://"+c.address+"/api/config", strings.NewReader(content)) req, err := http.NewRequest("PUT", "http://"+c.address+"/api/config", strings.NewReader(content))
if err != nil { if err != nil {
return err return err
} }

View File

@@ -112,10 +112,6 @@ func (g *Gateway) Run() {
} }
} }
func (g *Gateway) Close() error {
return g.ln.Close()
}
func (g *Gateway) handleConn(conn net.Conn) { func (g *Gateway) handleConn(conn net.Conn) {
defer conn.Close() defer conn.Close()

View File

@@ -67,27 +67,27 @@ func InitLogger(logPath string, levelStr string, maxDays int, disableLogColor bo
Logger = Logger.WithOptions(options...) Logger = Logger.WithOptions(options...)
} }
func Errorf(format string, v ...any) { func Errorf(format string, v ...interface{}) {
Logger.Errorf(format, v...) Logger.Errorf(format, v...)
} }
func Warnf(format string, v ...any) { func Warnf(format string, v ...interface{}) {
Logger.Warnf(format, v...) Logger.Warnf(format, v...)
} }
func Infof(format string, v ...any) { func Infof(format string, v ...interface{}) {
Logger.Infof(format, v...) Logger.Infof(format, v...)
} }
func Debugf(format string, v ...any) { func Debugf(format string, v ...interface{}) {
Logger.Debugf(format, v...) Logger.Debugf(format, v...)
} }
func Tracef(format string, v ...any) { func Tracef(format string, v ...interface{}) {
Logger.Tracef(format, v...) Logger.Tracef(format, v...)
} }
func Logf(level log.Level, offset int, format string, v ...any) { func Logf(level log.Level, offset int, format string, v ...interface{}) {
Logger.Logf(level, offset, format, v...) Logger.Logf(level, offset, format, v...)
} }

View File

@@ -3,21 +3,21 @@ package metric
import ( import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/assert"
) )
func TestCounter(t *testing.T) { func TestCounter(t *testing.T) {
require := require.New(t) assert := assert.New(t)
c := NewCounter() c := NewCounter()
c.Inc(10) c.Inc(10)
require.EqualValues(10, c.Count()) assert.EqualValues(10, c.Count())
c.Dec(5) c.Dec(5)
require.EqualValues(5, c.Count()) assert.EqualValues(5, c.Count())
cTmp := c.Snapshot() cTmp := c.Snapshot()
require.EqualValues(5, cTmp.Count()) assert.EqualValues(5, cTmp.Count())
c.Clear() c.Clear()
require.EqualValues(0, c.Count()) assert.EqualValues(0, c.Count())
} }

View File

@@ -3,25 +3,25 @@ package metric
import ( import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/assert"
) )
func TestDateCounter(t *testing.T) { func TestDateCounter(t *testing.T) {
require := require.New(t) assert := assert.New(t)
dc := NewDateCounter(3) dc := NewDateCounter(3)
dc.Inc(10) dc.Inc(10)
require.EqualValues(10, dc.TodayCount()) assert.EqualValues(10, dc.TodayCount())
dc.Dec(5) dc.Dec(5)
require.EqualValues(5, dc.TodayCount()) assert.EqualValues(5, dc.TodayCount())
counts := dc.GetLastDaysCount(3) counts := dc.GetLastDaysCount(3)
require.EqualValues(3, len(counts)) assert.EqualValues(3, len(counts))
require.EqualValues(5, counts[0]) assert.EqualValues(5, counts[0])
require.EqualValues(0, counts[1]) assert.EqualValues(0, counts[1])
require.EqualValues(0, counts[2]) assert.EqualValues(0, counts[2])
dcTmp := dc.Snapshot() dcTmp := dc.Snapshot()
require.EqualValues(5, dcTmp.TodayCount()) assert.EqualValues(5, dcTmp.TodayCount())
} }

View File

@@ -223,7 +223,7 @@ func (conn *wrapQuicStream) RemoteAddr() net.Addr {
} }
func (conn *wrapQuicStream) Close() error { func (conn *wrapQuicStream) Close() error {
conn.CancelRead(0) conn.Stream.CancelRead(0)
return conn.Stream.Close() return conn.Stream.Close()
} }

View File

@@ -1,45 +0,0 @@
// 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
}

View File

@@ -1,178 +0,0 @@
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)
}
}

View File

@@ -85,21 +85,21 @@ func ParseRangeNumbers(rangeStr string) (numbers []int64, err error) {
numbers = append(numbers, singleNum) numbers = append(numbers, singleNum)
case 2: case 2:
// range numbers // range numbers
minValue, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64) min, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
if errRet != nil { if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet) err = fmt.Errorf("range number is invalid, %v", errRet)
return return
} }
maxValue, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64) max, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
if errRet != nil { if errRet != nil {
err = fmt.Errorf("range number is invalid, %v", errRet) err = fmt.Errorf("range number is invalid, %v", errRet)
return return
} }
if maxValue < minValue { if max < min {
err = fmt.Errorf("range number is invalid") err = fmt.Errorf("range number is invalid")
return return
} }
for i := minValue; i <= maxValue; i++ { for i := min; i <= max; i++ {
numbers = append(numbers, i) numbers = append(numbers, i)
} }
default: default:
@@ -118,13 +118,13 @@ func GenerateResponseErrorString(summary string, err error, detailed bool) strin
} }
func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Duration { func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Duration {
minValue := int64(minRatio * 1000.0) min := int64(minRatio * 1000.0)
maxValue := int64(maxRatio * 1000.0) max := int64(maxRatio * 1000.0)
var n int64 var n int64
if maxValue <= minValue { if max <= min {
n = minValue n = min
} else { } else {
n = mathrand.Int64N(maxValue-minValue) + minValue n = mathrand.Int64N(max-min) + min
} }
d := duration * time.Duration(n) / time.Duration(1000) d := duration * time.Duration(n) / time.Duration(1000)
time.Sleep(d) time.Sleep(d)

View File

@@ -3,41 +3,45 @@ package util
import ( import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/assert"
) )
func TestRandId(t *testing.T) { func TestRandId(t *testing.T) {
require := require.New(t) assert := assert.New(t)
id, err := RandID() id, err := RandID()
require.NoError(err) assert.NoError(err)
t.Log(id) t.Log(id)
require.Equal(16, len(id)) assert.Equal(16, len(id))
} }
func TestGetAuthKey(t *testing.T) { func TestGetAuthKey(t *testing.T) {
require := require.New(t) assert := assert.New(t)
key := GetAuthKey("1234", 1488720000) key := GetAuthKey("1234", 1488720000)
require.Equal("6df41a43725f0c770fd56379e12acf8c", key) assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
} }
func TestParseRangeNumbers(t *testing.T) { func TestParseRangeNumbers(t *testing.T) {
require := require.New(t) assert := assert.New(t)
numbers, err := ParseRangeNumbers("2-5") numbers, err := ParseRangeNumbers("2-5")
require.NoError(err) if assert.NoError(err) {
require.Equal([]int64{2, 3, 4, 5}, numbers) assert.Equal([]int64{2, 3, 4, 5}, numbers)
}
numbers, err = ParseRangeNumbers("1") numbers, err = ParseRangeNumbers("1")
require.NoError(err) if assert.NoError(err) {
require.Equal([]int64{1}, numbers) assert.Equal([]int64{1}, numbers)
}
numbers, err = ParseRangeNumbers("3-5,8") numbers, err = ParseRangeNumbers("3-5,8")
require.NoError(err) if assert.NoError(err) {
require.Equal([]int64{3, 4, 5, 8}, numbers) assert.Equal([]int64{3, 4, 5, 8}, numbers)
}
numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ") numbers, err = ParseRangeNumbers(" 3-5,8, 10-12 ")
require.NoError(err) if assert.NoError(err) {
require.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers) assert.Equal([]int64{3, 4, 5, 8, 10, 11, 12}, numbers)
}
_, err = ParseRangeNumbers("3-a") _, err = ParseRangeNumbers("3-a")
require.Error(err) assert.Error(err)
} }

View File

@@ -14,7 +14,7 @@
package version package version
var version = "0.63.0" var version = "0.58.1"
func Full() string { func Full() string {
return version return version

View File

@@ -29,8 +29,6 @@ import (
libio "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
"github.com/fatedier/golib/pool" "github.com/fatedier/golib/pool"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
httppkg "github.com/fatedier/frp/pkg/util/http" httppkg "github.com/fatedier/frp/pkg/util/http"
"github.com/fatedier/frp/pkg/util/log" "github.com/fatedier/frp/pkg/util/log"
@@ -43,7 +41,7 @@ type HTTPReverseProxyOptions struct {
} }
type HTTPReverseProxy struct { type HTTPReverseProxy struct {
proxy http.Handler proxy *httputil.ReverseProxy
vhostRouter *Routers vhostRouter *Routers
responseHeaderTimeout time.Duration responseHeaderTimeout time.Duration
@@ -140,7 +138,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
_, _ = rw.Write(getNotFoundPageContent()) _, _ = rw.Write(getNotFoundPageContent())
}, },
} }
rp.proxy = h2c.NewHandler(proxy, &http2.Server{}) rp.proxy = proxy
return rp return rp
} }
@@ -162,7 +160,7 @@ func (rp *HTTPReverseProxy) UnRegister(routeCfg RouteConfig) {
func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser string) *RouteConfig { func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser string) *RouteConfig {
vr, ok := rp.getVhost(domain, location, routeByHTTPUser) vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
if ok { if ok {
log.Debugf("get new http request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser) log.Debugf("get new HTTP request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser)
return vr.payload.(*RouteConfig) return vr.payload.(*RouteConfig)
} }
return nil return nil
@@ -225,7 +223,11 @@ func (rp *HTTPReverseProxy) getVhost(domain, location, routeByHTTPUser string) (
// *.example.com // *.example.com
// *.com // *.com
domainSplit := strings.Split(domain, ".") domainSplit := strings.Split(domain, ".")
for len(domainSplit) >= 3 { for {
if len(domainSplit) < 3 {
break
}
domainSplit[0] = "*" domainSplit[0] = "*"
domain = strings.Join(domainSplit, ".") domain = strings.Join(domainSplit, ".")
vr, ok = findRouter(domain, location, routeByHTTPUser) vr, ok = findRouter(domain, location, routeByHTTPUser)

View File

@@ -24,7 +24,7 @@ type Router struct {
httpUser string httpUser string
// store any object here // store any object here
payload any payload interface{}
} }
func NewRouters() *Routers { func NewRouters() *Routers {
@@ -33,7 +33,7 @@ func NewRouters() *Routers {
} }
} }
func (r *Routers) Add(domain, location, httpUser string, payload any) error { func (r *Routers) Add(domain, location, httpUser string, payload interface{}) error {
domain = strings.ToLower(domain) domain = strings.ToLower(domain)
r.mutex.Lock() r.mutex.Lock()

View File

@@ -100,10 +100,6 @@ func (v *Muxer) SetRewriteHostFunc(f hostRewriteFunc) *Muxer {
return v return v
} }
func (v *Muxer) Close() error {
return v.listener.Close()
}
type ChooseEndpointFunc func() (string, error) type ChooseEndpointFunc func() (string, error)
type CreateConnFunc func(remoteAddr string) (net.Conn, error) type CreateConnFunc func(remoteAddr string) (net.Conn, error)
@@ -169,7 +165,11 @@ func (v *Muxer) getListener(name, path, httpUser string) (*Listener, bool) {
} }
domainSplit := strings.Split(name, ".") domainSplit := strings.Split(name, ".")
for len(domainSplit) >= 3 { for {
if len(domainSplit) < 3 {
break
}
domainSplit[0] = "*" domainSplit[0] = "*"
name = strings.Join(domainSplit, ".") name = strings.Join(domainSplit, ".")
@@ -271,7 +271,7 @@ func (l *Listener) Accept() (net.Conn, error) {
xl := xlog.FromContextSafe(l.ctx) xl := xlog.FromContextSafe(l.ctx)
conn, ok := <-l.accept conn, ok := <-l.accept
if !ok { if !ok {
return nil, fmt.Errorf("listener closed") return nil, fmt.Errorf("Listener closed")
} }
// if rewriteHost func is exist // if rewriteHost func is exist

Some files were not shown because too many files have changed in this diff Show More