mirror of
https://github.com/fatedier/frp.git
synced 2025-07-29 09:18:11 +00:00
Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b41d8f8e40 | ||
|
3c8d648ddc | ||
|
27f66baf54 | ||
|
c5a8f6ef4a | ||
|
e208043323 | ||
|
a78814a2e9 | ||
|
31b44c1feb | ||
|
773169e0c4 | ||
|
9757a351c6 | ||
|
1e8db66743 | ||
|
e0dd947e6a | ||
|
8b86e1473c | ||
|
b8d3ace113 | ||
|
450b8393bc | ||
|
27db6217ec | ||
|
6542dcd4ed | ||
|
092e5d3f94 | ||
|
01fed8d1a9 | ||
|
2a7aa69890 | ||
|
f47d8ab97f | ||
|
bb912d6c37 | ||
|
c73096f2bf | ||
|
0358113948 | ||
|
8593eff752 | ||
|
dff56cb0ca | ||
|
4383756fd4 | ||
|
6ba849fc75 | ||
|
9d5638cae6 | ||
|
62352c7ba5 | ||
|
4bbec09d57 | ||
|
f7a06cbe61 | ||
|
3a08c2aeb0 | ||
|
b14192a8d3 | ||
|
2466e65f43 | ||
|
2855ac71e3 | ||
|
fe4ca1b54e | ||
|
edd7cf8967 | ||
|
ccfe8c97f4 | ||
|
03c8d7bf96 | ||
|
2dcdb24cc4 | ||
|
d47e138bc9 | ||
|
f1fb2d721a | ||
|
ae73ec2fed | ||
|
e8045194cd | ||
|
69cc422edf |
@@ -2,7 +2,7 @@ version: 2
|
|||||||
jobs:
|
jobs:
|
||||||
go-version-latest:
|
go-version-latest:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.22-node
|
- image: cimg/go:1.23-node
|
||||||
resource_class: large
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [fatedier]
|
github: [fatedier]
|
||||||
custom: ["https://afdian.net/a/fatedier"]
|
custom: ["https://afdian.com/a/fatedier"]
|
||||||
|
4
.github/workflows/golangci-lint.yml
vendored
4
.github/workflows/golangci-lint.yml
vendored
@@ -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.22'
|
go-version: '1.23'
|
||||||
cache: false
|
cache: false
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v4
|
uses: golangci/golangci-lint-action@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: v1.57
|
version: v1.61
|
||||||
|
|
||||||
# Optional: golangci-lint command line arguments.
|
# Optional: golangci-lint command line arguments.
|
||||||
# args: --issues-exit-code=0
|
# args: --issues-exit-code=0
|
||||||
|
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
@@ -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.22'
|
go-version: '1.23'
|
||||||
|
|
||||||
- name: Make All
|
- name: Make All
|
||||||
run: |
|
run: |
|
||||||
|
8
.github/workflows/stale.yml
vendored
8
.github/workflows/stale.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
stale-issue-message: 'Issues go stale after 21d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
|
stale-issue-message: 'Issues go stale after 14d of inactivity. Stale issues 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-pr-message: "PRs go stale after 14d of inactivity. Stale PRs rot after an additional 3d 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: 21
|
days-before-stale: 14
|
||||||
days-before-close: 7
|
days-before-close: 3
|
||||||
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
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
service:
|
service:
|
||||||
golangci-lint-version: 1.57.x # use the fixed version to not introduce new linters unexpectedly
|
golangci-lint-version: 1.61.x # use the fixed version to not introduce new linters unexpectedly
|
||||||
|
|
||||||
run:
|
run:
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||||
deadline: 20m
|
timeout: 20m
|
||||||
build-tags:
|
build-tags:
|
||||||
- integ
|
- integ
|
||||||
- integfuzz
|
- integfuzz
|
||||||
@@ -14,7 +14,7 @@ linters:
|
|||||||
enable:
|
enable:
|
||||||
- unused
|
- unused
|
||||||
- errcheck
|
- errcheck
|
||||||
- exportloopref
|
- copyloopvar
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofumpt
|
- gofumpt
|
||||||
- goimports
|
- goimports
|
||||||
@@ -48,7 +48,8 @@ linters-settings:
|
|||||||
check-blank: false
|
check-blank: false
|
||||||
govet:
|
govet:
|
||||||
# report about shadowed variables
|
# report about shadowed variables
|
||||||
check-shadowing: false
|
disable:
|
||||||
|
- shadow
|
||||||
maligned:
|
maligned:
|
||||||
# print struct with more effective memory layout or not, false by default
|
# print struct with more effective memory layout or not, false by default
|
||||||
suggest-new: true
|
suggest-new: true
|
||||||
@@ -88,7 +89,9 @@ linters-settings:
|
|||||||
excludes:
|
excludes:
|
||||||
- G401
|
- G401
|
||||||
- G402
|
- G402
|
||||||
|
- G404
|
||||||
- G501
|
- G501
|
||||||
|
- G115 # integer overflow conversion
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
# List of regexps of issue texts to exclude, empty list by default.
|
# List of regexps of issue texts to exclude, empty list by default.
|
||||||
|
@@ -2,7 +2,7 @@ export PATH := $(PATH):`go env GOPATH`/bin
|
|||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
LDFLAGS := -s -w
|
LDFLAGS := -s -w
|
||||||
|
|
||||||
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm:7 linux:arm:5 linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 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 linux:loong64 android:arm64
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
|
59
README.md
59
README.md
@@ -7,16 +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://lokal.so/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
<a href="https://jb.gg/frp" target="_blank">
|
||||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_lokal.png">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
|
||||||
<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_workos.png">
|
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -25,8 +24,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/beclab/terminus" target="_blank">
|
<a href="https://github.com/beclab/Olares" target="_blank">
|
||||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_terminusos.jpeg">
|
<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-->
|
||||||
@@ -93,7 +92,12 @@ 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)
|
||||||
* [Releated Projects](#releated-projects)
|
* [Virtual Network (VirtualNet)](#virtual-network-virtualnet)
|
||||||
|
* [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)
|
||||||
@@ -1256,7 +1260,40 @@ 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.
|
||||||
|
|
||||||
## Releated Projects
|
### 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
19
README_zh.md
19
README_zh.md
@@ -9,16 +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://lokal.so/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
<a href="https://jb.gg/frp" target="_blank">
|
||||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_lokal.png">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
|
||||||
<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_workos.png">
|
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -27,8 +26,8 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/beclab/terminus" target="_blank">
|
<a href="https://github.com/beclab/Olares" target="_blank">
|
||||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_terminusos.jpeg">
|
<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-->
|
||||||
@@ -100,7 +99,7 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
|||||||
|
|
||||||
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
||||||
|
|
||||||
国内用户可以通过 [爱发电](https://afdian.net/a/fatedier) 赞助我们。
|
国内用户可以通过 [爱发电](https://afdian.com/a/fatedier) 赞助我们。
|
||||||
|
|
||||||
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
||||||
|
|
||||||
|
@@ -1,8 +1,3 @@
|
|||||||
### Features
|
### Bug Fixes
|
||||||
|
|
||||||
* Added a new plugin "http2http" which allows forwarding HTTP requests to another HTTP server, supporting options like local address binding, host header rewrite, and custom request headers.
|
* **VirtualNet:** Resolved various issues related to connection handling, TUN device management, and stability in the virtual network feature.
|
||||||
* Added `enableHTTP2` option to control whether to enable HTTP/2 in plugin https2http and https2https, default is true.
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
* Plugin https2http & https2https: return 421 `Misdirected Request` if host not match sni.
|
|
@@ -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))
|
||||||
|
@@ -29,6 +29,7 @@ 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 {
|
||||||
@@ -46,6 +47,8 @@ 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 {
|
||||||
@@ -99,8 +102,9 @@ 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)
|
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, ctl.msgTransporter, sessionCtx.VnetController)
|
||||||
ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common, ctl.connectServer, ctl.msgTransporter)
|
ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common,
|
||||||
|
ctl.connectServer, ctl.msgTransporter, sessionCtx.VnetController)
|
||||||
return ctl, nil
|
return ctl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +189,7 @@ func (ctl *Control) handlePong(m msg.Message) {
|
|||||||
inMsg := m.(*msg.Pong)
|
inMsg := m.(*msg.Pong)
|
||||||
|
|
||||||
if inMsg.Error != "" {
|
if inMsg.Error != "" {
|
||||||
xl.Errorf("Pong message contains error: %s", inMsg.Error)
|
xl.Errorf("pong message contains error: %s", inMsg.Error)
|
||||||
ctl.closeSession()
|
ctl.closeSession()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -230,7 +234,7 @@ func (ctl *Control) registerMsgHandlers() {
|
|||||||
ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
|
ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
|
||||||
}
|
}
|
||||||
|
|
||||||
// headerWorker sends heartbeat to server and check heartbeat timeout.
|
// heartbeatWorker sends heartbeat to server and check heartbeat timeout.
|
||||||
func (ctl *Control) heartbeatWorker() {
|
func (ctl *Control) heartbeatWorker() {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
var ErrPayloadType = errors.New("error payload type")
|
var ErrPayloadType = errors.New("error payload type")
|
||||||
|
|
||||||
type Handler func(payload interface{}) error
|
type Handler func(payload any) error
|
||||||
|
|
||||||
type StartProxyPayload struct {
|
type StartProxyPayload struct {
|
||||||
NewProxyMsg *msg.NewProxy
|
NewProxyMsg *msg.NewProxy
|
||||||
|
@@ -36,6 +36,7 @@ import (
|
|||||||
"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"
|
||||||
"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,6 +59,7 @@ 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()
|
||||||
@@ -70,6 +72,7 @@ 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,
|
||||||
}
|
}
|
||||||
@@ -85,6 +88,7 @@ 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.
|
||||||
@@ -98,7 +102,10 @@ 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, pxy.baseCfg.Plugin.ClientPluginOptions)
|
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, plugin.PluginContext{
|
||||||
|
Name: pxy.baseCfg.Name,
|
||||||
|
VnetController: pxy.vnetController,
|
||||||
|
}, pxy.baseCfg.Plugin.ClientPluginOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -157,22 +164,22 @@ 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 extraInfo plugin.ExtraInfo
|
var connInfo plugin.ConnectionInfo
|
||||||
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))))
|
||||||
extraInfo.SrcAddr = srcAddr
|
connInfo.SrcAddr = srcAddr
|
||||||
extraInfo.DstAddr = dstAddr
|
connInfo.DstAddr = dstAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
|
if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
h := &pp.Header{
|
h := &pp.Header{
|
||||||
Command: pp.PROXY,
|
Command: pp.PROXY,
|
||||||
SourceAddr: extraInfo.SrcAddr,
|
SourceAddr: connInfo.SrcAddr,
|
||||||
DestinationAddr: extraInfo.DstAddr,
|
DestinationAddr: connInfo.DstAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(m.SrcAddr, ".") {
|
if strings.Contains(m.SrcAddr, ".") {
|
||||||
@@ -186,13 +193,15 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
|
} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
|
||||||
h.Version = 2
|
h.Version = 2
|
||||||
}
|
}
|
||||||
extraInfo.ProxyProtocolHeader = h
|
connInfo.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(remote, workConn, &extraInfo)
|
pxy.proxyPlugin.Handle(pxy.ctx, &connInfo)
|
||||||
xl.Debugf("handle by plugin finished")
|
xl.Debugf("handle by plugin finished")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -210,8 +219,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 extraInfo.ProxyProtocolHeader != nil {
|
if connInfo.ProxyProtocolHeader != nil {
|
||||||
if _, err := extraInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
if _, err := connInfo.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
|
||||||
|
@@ -28,12 +28,14 @@ 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
|
||||||
@@ -47,10 +49,12 @@ 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,
|
||||||
@@ -96,7 +100,7 @@ func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *Manager) HandleEvent(payload interface{}) error {
|
func (pm *Manager) HandleEvent(payload any) error {
|
||||||
var m msg.Message
|
var m msg.Message
|
||||||
switch e := payload.(type) {
|
switch e := payload.(type) {
|
||||||
case *event.StartProxyPayload:
|
case *event.StartProxyPayload:
|
||||||
@@ -159,7 +163,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)
|
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter, pm.vnetController)
|
||||||
if pm.inWorkConnCallback != nil {
|
if pm.inWorkConnCallback != nil {
|
||||||
pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
|
pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,7 @@ 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 (
|
||||||
@@ -73,6 +74,8 @@ 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
|
||||||
@@ -91,6 +94,7 @@ 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)
|
||||||
@@ -105,6 +109,7 @@ 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),
|
||||||
}
|
}
|
||||||
@@ -117,7 +122,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.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter, pw.vnetController)
|
||||||
return pw
|
return pw
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +142,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(pw.Err)
|
return fmt.Errorf("%s", pw.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pw.pxy.Run(); err != nil {
|
if err := pw.pxy.Run(); err != nil {
|
||||||
|
@@ -37,6 +37,7 @@ 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() {
|
||||||
@@ -110,6 +111,8 @@ 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
|
||||||
@@ -156,6 +159,9 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +175,28 @@ 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 {
|
||||||
@@ -179,14 +207,6 @@ 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
|
||||||
@@ -310,17 +330,18 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
|||||||
connEncrypted = false
|
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("NewControl error: %v", err)
|
xl.Errorf("new control error: %v", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
ctl.SetInWorkConnCallback(svr.handleWorkConnCb)
|
ctl.SetInWorkConnCallback(svr.handleWorkConnCb)
|
||||||
|
@@ -44,6 +44,10 @@ func (sv *STCPVisitor) Run() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go sv.internalConnWorker()
|
go sv.internalConnWorker()
|
||||||
|
|
||||||
|
if sv.plugin != nil {
|
||||||
|
sv.plugin.Start()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,9 +20,11 @@ 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.
|
||||||
@@ -34,6 +36,8 @@ 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
|
||||||
}
|
}
|
||||||
@@ -50,14 +54,34 @@ func NewVisitor(
|
|||||||
cfg v1.VisitorConfigurer,
|
cfg v1.VisitorConfigurer,
|
||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
helper Helper,
|
helper Helper,
|
||||||
) (visitor Visitor) {
|
) (Visitor, error) {
|
||||||
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: xlog.NewContext(ctx, xl),
|
ctx: ctx,
|
||||||
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{
|
||||||
@@ -77,7 +101,7 @@ func NewVisitor(
|
|||||||
checkCloseCh: make(chan struct{}),
|
checkCloseCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return visitor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseVisitor struct {
|
type BaseVisitor struct {
|
||||||
@@ -85,6 +109,7 @@ 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
|
||||||
@@ -101,4 +126,7 @@ 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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ 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 {
|
||||||
@@ -50,6 +51,7 @@ 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,
|
||||||
@@ -62,6 +64,7 @@ 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,
|
||||||
}
|
}
|
||||||
@@ -112,7 +115,11 @@ 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 := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
|
visitor, err := 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)
|
||||||
@@ -187,6 +194,7 @@ 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
|
||||||
}
|
}
|
||||||
@@ -203,6 +211,10 @@ 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
|
||||||
}
|
}
|
||||||
|
@@ -73,6 +73,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,9 +161,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)
|
||||||
isConnTrasfered := false
|
isConnTransfered := false
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isConnTrasfered {
|
if !isConnTransfered {
|
||||||
userConn.Close()
|
userConn.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -187,7 +191,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
|
||||||
}
|
}
|
||||||
isConnTrasfered = true
|
isConnTransfered = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,9 +15,11 @@
|
|||||||
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"
|
||||||
@@ -27,24 +29,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() {
|
||||||
rootCmd.AddCommand(NewAdminCommand(
|
commands := []struct {
|
||||||
"reload",
|
name string
|
||||||
"Hot-Reload frpc configuration",
|
description string
|
||||||
ReloadHandler,
|
handler func(*v1.ClientCommonConfig) error
|
||||||
))
|
}{
|
||||||
|
{"reload", "Hot-Reload frpc configuration", ReloadHandler},
|
||||||
|
{"status", "Overview of all proxies status", StatusHandler},
|
||||||
|
{"stop", "Stop the running frpc", StopHandler},
|
||||||
|
}
|
||||||
|
|
||||||
rootCmd.AddCommand(NewAdminCommand(
|
for _, cmdConfig := range commands {
|
||||||
"status",
|
cmd := NewAdminCommand(cmdConfig.name, cmdConfig.description, cmdConfig.handler)
|
||||||
"Overview of all proxies status",
|
cmd.Flags().DurationVar(&adminAPITimeout, "api-timeout", adminAPITimeout, "Timeout for admin API calls")
|
||||||
StatusHandler,
|
rootCmd.AddCommand(cmd)
|
||||||
))
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -73,7 +75,9 @@ 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)
|
||||||
if err := client.Reload(strictConfigMode); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := client.Reload(ctx, strictConfigMode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("reload success")
|
fmt.Println("reload success")
|
||||||
@@ -83,7 +87,9 @@ 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)
|
||||||
res, err := client.GetAllProxyStatus()
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
res, err := client.GetAllProxyStatus(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -109,7 +115,9 @@ 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)
|
||||||
if err := client.Stop(); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := client.Stop(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("stop success")
|
fmt.Println("stop success")
|
||||||
|
@@ -31,6 +31,7 @@ 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"
|
||||||
)
|
)
|
||||||
@@ -120,6 +121,12 @@ 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)
|
||||||
|
@@ -129,6 +129,15 @@ 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"
|
||||||
@@ -325,6 +334,16 @@ localAddr = "127.0.0.1:80"
|
|||||||
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_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
|
||||||
@@ -348,6 +367,13 @@ 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"
|
||||||
@@ -379,3 +405,13 @@ 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"
|
||||||
|
BIN
doc/pic/sponsor_jetbrains.jpg
Normal file
BIN
doc/pic/sponsor_jetbrains.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
BIN
doc/pic/sponsor_olares.jpeg
Normal file
BIN
doc/pic/sponsor_olares.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
Before Width: | Height: | Size: 31 KiB |
73
doc/virtual_net.md
Normal file
73
doc/virtual_net.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# 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
|
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22 AS building
|
FROM golang:1.23 AS building
|
||||||
|
|
||||||
COPY . /building
|
COPY . /building
|
||||||
WORKDIR /building
|
WORKDIR /building
|
||||||
@@ -7,6 +7,8 @@ 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"]
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22 AS building
|
FROM golang:1.23 AS building
|
||||||
|
|
||||||
COPY . /building
|
COPY . /building
|
||||||
WORKDIR /building
|
WORKDIR /building
|
||||||
@@ -7,6 +7,8 @@ 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"]
|
||||||
|
62
go.mod
62
go.mod
@@ -1,34 +1,37 @@
|
|||||||
module github.com/fatedier/frp
|
module github.com/fatedier/frp
|
||||||
|
|
||||||
go 1.22
|
go 1.23.0
|
||||||
|
|
||||||
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.10.0
|
github.com/coreos/go-oidc/v3 v3.14.1
|
||||||
github.com/fatedier/golib v0.5.0
|
github.com/fatedier/golib v0.5.1
|
||||||
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.17.1
|
github.com/onsi/ginkgo/v2 v2.22.0
|
||||||
github.com/onsi/gomega v1.32.0
|
github.com/onsi/gomega v1.34.2
|
||||||
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.0
|
github.com/prometheus/client_golang v1.19.1
|
||||||
github.com/quic-go/quic-go v0.42.0
|
github.com/quic-go/quic-go v0.48.2
|
||||||
github.com/rodaine/table v1.2.0
|
github.com/rodaine/table v1.2.0
|
||||||
github.com/samber/lo v1.39.0
|
github.com/samber/lo v1.47.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.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/tidwall/gjson v1.17.1
|
github.com/tidwall/gjson v1.17.1
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.8
|
github.com/vishvananda/netlink v1.3.0
|
||||||
golang.org/x/crypto v0.22.0
|
github.com/xtaci/kcp-go/v5 v5.6.13
|
||||||
golang.org/x/net v0.24.0
|
golang.org/x/crypto v0.37.0
|
||||||
golang.org/x/oauth2 v0.16.0
|
golang.org/x/net v0.39.0
|
||||||
golang.org/x/sync v0.6.0
|
golang.org/x/oauth2 v0.28.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
|
||||||
@@ -39,13 +42,12 @@ 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.1 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // 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.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
github.com/google/pprof v0.0.0-20241206021119-61a79c692802 // 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
|
||||||
@@ -59,20 +61,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/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/templexxx/cpu v0.1.1 // indirect
|
||||||
github.com/templexxx/cpu v0.1.0 // indirect
|
github.com/templexxx/xorsimd v0.4.3 // 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
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
github.com/vishvananda/netns v0.0.4 // 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.22.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.28.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.34.1 // 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
|
||||||
|
143
go.sum
143
go.sum
@@ -9,13 +9,10 @@ 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.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
|
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
|
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/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=
|
||||||
@@ -24,16 +21,16 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.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.0 h1:hNcH7hgfIFqVWbP+YojCCAj4eO94pPf4dEF8lmq2jWs=
|
github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M=
|
||||||
github.com/fatedier/golib v0.5.0/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
|
github.com/fatedier/golib v0.5.1/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.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
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/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
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/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
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=
|
||||||
@@ -45,28 +42,24 @@ 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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
github.com/google/pprof v0.0.0-20241206021119-61a79c692802 h1:US08AXzP0bLurpzFUV3Poa9ZijrRdd1zAIOVtoHEiS8=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20241206021119-61a79c692802/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
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=
|
||||||
@@ -79,10 +72,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
|
||||||
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||||
github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
|
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
||||||
github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
|
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
||||||
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=
|
||||||
@@ -101,8 +94,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||||
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/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=
|
||||||
@@ -110,17 +103,19 @@ 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.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||||
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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
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.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||||
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||||
|
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=
|
||||||
@@ -129,17 +124,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/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI=
|
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
|
||||||
github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI=
|
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||||
|
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=
|
||||||
@@ -148,31 +143,35 @@ 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/xtaci/kcp-go/v5 v5.6.8 h1:jlI/0jAyjoOjT/SaGB58s4bQMJiNS41A2RKzR6TMWeI=
|
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.8/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM=
|
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||||
|
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/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
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=
|
||||||
@@ -186,50 +185,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.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
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.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
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.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||||
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
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=
|
||||||
@@ -240,14 +239,16 @@ 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.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||||
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=
|
||||||
@@ -260,10 +261,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
|||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
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=
|
||||||
@@ -274,6 +273,8 @@ 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=
|
||||||
|
@@ -18,7 +18,7 @@ rm -rf ./release/packages
|
|||||||
mkdir -p ./release/packages
|
mkdir -p ./release/packages
|
||||||
|
|
||||||
os_all='linux windows darwin freebsd android'
|
os_all='linux windows darwin freebsd android'
|
||||||
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64'
|
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64 loong64'
|
||||||
extra_all='_ hf'
|
extra_all='_ hf'
|
||||||
|
|
||||||
cd ./release
|
cd ./release
|
||||||
|
@@ -50,7 +50,8 @@ 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:
|
||||||
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, cfg.OIDC)
|
tokenVerifier := NewTokenVerifier(cfg.OIDC)
|
||||||
|
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, tokenVerifier)
|
||||||
}
|
}
|
||||||
return authVerifier
|
return authVerifier
|
||||||
}
|
}
|
||||||
|
@@ -87,14 +87,18 @@ 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 *oidc.IDTokenVerifier
|
verifier TokenVerifier
|
||||||
subjectFromLogin string
|
subjectsFromLogin []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCServerConfig) *OidcAuthConsumer {
|
func NewTokenVerifier(cfg v1.AuthOIDCServerConfig) TokenVerifier {
|
||||||
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)
|
||||||
@@ -105,9 +109,14 @@ func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCSer
|
|||||||
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: provider.Verifier(&verifierConf),
|
verifier: verifier,
|
||||||
|
subjectsFromLogin: []string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +125,9 @@ 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)
|
||||||
}
|
}
|
||||||
auth.subjectFromLogin = token.Subject
|
if !slices.Contains(auth.subjectsFromLogin, token.Subject) {
|
||||||
|
auth.subjectsFromLogin = append(auth.subjectsFromLogin, token.Subject)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,11 +136,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 token.Subject != auth.subjectFromLogin {
|
if !slices.Contains(auth.subjectsFromLogin, token.Subject) {
|
||||||
return fmt.Errorf("received different OIDC subject in login and ping. "+
|
return fmt.Errorf("received different OIDC subject in login and ping. "+
|
||||||
"original subject: %s, "+
|
"original subjects: %s, "+
|
||||||
"new subject: %s",
|
"new subject: %s",
|
||||||
auth.subjectFromLogin, token.Subject)
|
auth.subjectsFromLogin, token.Subject)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
64
pkg/auth/oidc_test.go
Normal file
64
pkg/auth/oidc_test.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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")
|
||||||
|
}
|
@@ -106,6 +106,8 @@ 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")
|
||||||
@@ -140,6 +142,7 @@ 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")
|
||||||
}
|
}
|
||||||
@@ -226,6 +229,7 @@ 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")
|
||||||
|
@@ -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 interface{}) (ClientCommonConf, error) {
|
func UnmarshalClientConfFromIni(source any) (ClientCommonConf, error) {
|
||||||
f, err := ini.LoadSources(ini.LoadOptions{
|
f, err := ini.LoadSources(ini.LoadOptions{
|
||||||
Insensitive: false,
|
Insensitive: false,
|
||||||
InsensitiveSections: false,
|
InsensitiveSections: false,
|
||||||
@@ -203,7 +203,7 @@ func UnmarshalClientConfFromIni(source interface{}) (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 interface{},
|
source any,
|
||||||
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{
|
||||||
|
@@ -217,7 +217,7 @@ func GetDefaultServerConf() ServerCommonConf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
func UnmarshalServerConfFromIni(source any) (ServerCommonConf, error) {
|
||||||
f, err := ini.LoadSources(ini.LoadOptions{
|
f, err := ini.LoadSources(ini.LoadOptions{
|
||||||
Insensitive: false,
|
Insensitive: false,
|
||||||
InsensitiveSections: false,
|
InsensitiveSections: false,
|
||||||
|
@@ -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"
|
||||||
@@ -118,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 interface{}
|
var tomlObj any
|
||||||
// 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)
|
||||||
|
@@ -112,6 +112,29 @@ 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)
|
||||||
|
|
||||||
|
@@ -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
|
||||||
min, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
minNum, 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)
|
||||||
}
|
}
|
||||||
max, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
maxNum, 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 max < min {
|
if maxNum < minNum {
|
||||||
return nil, fmt.Errorf("range number is invalid")
|
return nil, fmt.Errorf("range number is invalid")
|
||||||
}
|
}
|
||||||
out = append(out, PortsRange{Start: int(min), End: int(max)})
|
out = append(out, PortsRange{Start: int(minNum), End: int(maxNum)})
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("range number is invalid")
|
return nil, fmt.Errorf("range number is invalid")
|
||||||
}
|
}
|
||||||
|
@@ -58,9 +58,14 @@ 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
|
||||||
@@ -204,3 +209,7 @@ 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"`
|
||||||
|
}
|
||||||
|
@@ -26,6 +26,32 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
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 {
|
type ClientPluginOptions interface {
|
||||||
Complete()
|
Complete()
|
||||||
}
|
}
|
||||||
@@ -74,28 +100,6 @@ 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"
|
|
||||||
PluginHTTP2HTTP = "http2http"
|
|
||||||
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{}),
|
|
||||||
PluginHTTP2HTTP: reflect.TypeOf(HTTP2HTTPPluginOptions{}),
|
|
||||||
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"`
|
||||||
@@ -174,3 +178,18 @@ type UnixDomainSocketPluginOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *UnixDomainSocketPluginOptions) Complete() {}
|
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() {}
|
@@ -23,6 +23,7 @@ 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) {
|
||||||
@@ -30,6 +31,13 @@ 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))
|
||||||
}
|
}
|
||||||
|
@@ -32,6 +32,8 @@ 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
|
||||||
}
|
}
|
||||||
@@ -70,3 +72,10 @@ 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
|
||||||
|
}
|
||||||
|
@@ -44,6 +44,9 @@ 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 {
|
||||||
|
86
pkg/config/v1/visitor_plugin.go
Normal file
86
pkg/config/v1/visitor_plugin.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// 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() {}
|
219
pkg/featuregate/feature_gate.go
Normal file
219
pkg/featuregate/feature_gate.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
@@ -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 interface{}) (err error) {
|
func WriteMsg(c io.Writer, msg any) (err error) {
|
||||||
return msgCtl.WriteMsg(c, msg)
|
return msgCtl.WriteMsg(c, msg)
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,7 @@ const (
|
|||||||
TypeNatHoleReport = '6'
|
TypeNatHoleReport = '6'
|
||||||
)
|
)
|
||||||
|
|
||||||
var msgTypeMap = map[byte]interface{}{
|
var msgTypeMap = map[byte]any{
|
||||||
TypeLogin: Login{},
|
TypeLogin: Login{},
|
||||||
TypeLoginResp: LoginResp{},
|
TypeLoginResp: LoginResp{},
|
||||||
TypeNewProxy: NewProxy{},
|
TypeNewProxy: NewProxy{},
|
||||||
|
@@ -371,8 +371,8 @@ func getRangePorts(addrs []string, difference, maxNumber int) []msg.PortsRange {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := lo.Last(addrs)
|
addr, isLast := lo.Last(addrs)
|
||||||
if err != nil {
|
if !isLast {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var ports []msg.PortsRange
|
var ports []msg.PortsRange
|
||||||
|
@@ -78,9 +78,9 @@ func ListAllLocalIPs() ([]net.IP, error) {
|
|||||||
return ips, nil
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListLocalIPsForNatHole(max int) ([]string, error) {
|
func ListLocalIPsForNatHole(maxItems int) ([]string, error) {
|
||||||
if max <= 0 {
|
if maxItems <= 0 {
|
||||||
return nil, fmt.Errorf("max must be greater than 0")
|
return nil, fmt.Errorf("maxItems must be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, err := ListAllLocalIPs()
|
ips, err := ListAllLocalIPs()
|
||||||
@@ -88,9 +88,9 @@ func ListLocalIPsForNatHole(max int) ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered := make([]string, 0, max)
|
filtered := make([]string, 0, maxItems)
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
if len(filtered) >= max {
|
if len(filtered) >= maxItems {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,12 +12,13 @@
|
|||||||
// 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 plugin
|
//go:build !frps
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"context"
|
||||||
stdlog "log"
|
stdlog "log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ type HTTP2HTTPPlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTP2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTP2HTTPPluginOptions)
|
opts := options.(*v1.HTTP2HTTPPluginOptions)
|
||||||
|
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
@@ -77,8 +78,8 @@ func NewHTTP2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTP2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (p *HTTP2HTTPPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
_ = p.l.PutConn(wrapConn)
|
_ = p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,13 +14,12 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
|
||||||
stdlog "log"
|
stdlog "log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ type HTTP2HTTPSPlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTP2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTP2HTTPSPluginOptions)
|
opts := options.(*v1.HTTP2HTTPSPluginOptions)
|
||||||
|
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
@@ -88,8 +87,8 @@ func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (p *HTTP2HTTPSPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
_ = p.l.PutConn(wrapConn)
|
_ = p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,10 +14,11 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -44,7 +45,7 @@ type HTTPProxy struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPProxyPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTPProxyPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTPProxyPluginOptions)
|
opts := options.(*v1.HTTPProxyPluginOptions)
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
|
|
||||||
@@ -68,8 +69,8 @@ func (hp *HTTPProxy) Name() string {
|
|||||||
return v1.PluginHTTPProxy
|
return v1.PluginHTTPProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (hp *HTTPProxy) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
|
|
||||||
sc, rd := libnet.NewSharedConn(wrapConn)
|
sc, rd := libnet.NewSharedConn(wrapConn)
|
||||||
firstBytes := make([]byte, 7)
|
firstBytes := make([]byte, 7)
|
||||||
|
@@ -14,14 +14,13 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
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"
|
||||||
@@ -47,7 +46,7 @@ type HTTPS2HTTPPlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTPS2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTPS2HTTPPluginOptions)
|
opts := options.(*v1.HTTPS2HTTPPluginOptions)
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
|
|
||||||
@@ -85,16 +84,7 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
rp.ServeHTTP(w, r)
|
rp.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|
||||||
var (
|
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -114,20 +104,10 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
func (p *HTTPS2HTTPPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
if err != nil {
|
if connInfo.SrcAddr != nil {
|
||||||
return nil, err
|
wrapConn.SetRemoteAddr(connInfo.SrcAddr)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@@ -14,14 +14,13 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
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"
|
||||||
@@ -47,7 +46,7 @@ type HTTPS2HTTPSPlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTPS2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTPS2HTTPSPluginOptions)
|
opts := options.(*v1.HTTPS2HTTPSPluginOptions)
|
||||||
|
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
@@ -91,16 +90,7 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
rp.ServeHTTP(w, r)
|
rp.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|
||||||
var (
|
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -120,20 +110,10 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) {
|
func (p *HTTPS2HTTPSPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
if err != nil {
|
if connInfo.SrcAddr != nil {
|
||||||
return nil, err
|
wrapConn.SetRemoteAddr(connInfo.SrcAddr)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@@ -12,9 +12,10 @@
|
|||||||
// 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 plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -24,13 +25,18 @@ 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)
|
||||||
|
|
||||||
// params has prefix "plugin_"
|
type CreatorFn func(pluginCtx PluginContext, options v1.ClientPluginOptions) (Plugin, error)
|
||||||
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 {
|
||||||
@@ -39,16 +45,19 @@ func Register(name string, fn CreatorFn) {
|
|||||||
creators[name] = fn
|
creators[name] = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
func Create(name string, options v1.ClientPluginOptions) (p Plugin, err error) {
|
func Create(pluginName string, pluginCtx PluginContext, options v1.ClientPluginOptions) (p Plugin, err error) {
|
||||||
if fn, ok := creators[name]; ok {
|
if fn, ok := creators[pluginName]; ok {
|
||||||
p, err = fn(options)
|
p, err = fn(pluginCtx, options)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("plugin [%s] is not registered", name)
|
err = fmt.Errorf("plugin [%s] is not registered", pluginName)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtraInfo struct {
|
type ConnectionInfo 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
|
||||||
@@ -57,7 +66,7 @@ type ExtraInfo struct {
|
|||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo)
|
Handle(ctx context.Context, connInfo *ConnectionInfo)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,12 +14,12 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
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(options v1.ClientPluginOptions) (p Plugin, err error) {
|
func NewSocks5Plugin(_ PluginContext, 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(options v1.ClientPluginOptions) (p Plugin, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (sp *Socks5Plugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
defer conn.Close()
|
defer connInfo.Conn.Close()
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
_ = sp.Server.ServeConn(wrapConn)
|
_ = sp.Server.ServeConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,11 +14,10 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"context"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ type StaticFilePlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewStaticFilePlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.StaticFilePluginOptions)
|
opts := options.(*v1.StaticFilePluginOptions)
|
||||||
|
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
@@ -69,8 +68,8 @@ func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return sp, nil
|
return sp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (sp *StaticFilePlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
_ = sp.l.PutConn(wrapConn)
|
_ = sp.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
82
pkg/plugin/client/tls2raw.go
Normal file
82
pkg/plugin/client/tls2raw.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// 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
|
||||||
|
}
|
@@ -14,15 +14,16 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"context"
|
||||||
"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() {
|
||||||
@@ -33,7 +34,7 @@ type UnixDomainSocketPlugin struct {
|
|||||||
UnixAddr *net.UnixAddr
|
UnixAddr *net.UnixAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err error) {
|
func NewUnixDomainSocketPlugin(_ PluginContext, 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)
|
||||||
@@ -48,18 +49,20 @@ func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, _ net.Conn, extra *ExtraInfo) {
|
func (uds *UnixDomainSocketPlugin) Handle(ctx context.Context, connInfo *ConnectionInfo) {
|
||||||
|
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 extra.ProxyProtocolHeader != nil {
|
if connInfo.ProxyProtocolHeader != nil {
|
||||||
if _, err := extra.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
if _, err := connInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
libio.Join(localConn, conn)
|
libio.Join(localConn, connInfo.Conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uds *UnixDomainSocketPlugin) Name() string {
|
func (uds *UnixDomainSocketPlugin) Name() string {
|
||||||
|
92
pkg/plugin/client/virtual_net.go
Normal file
92
pkg/plugin/client/virtual_net.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
// 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
|
||||||
|
}
|
@@ -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 plugin
|
package server
|
||||||
|
|
||||||
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 interface{}) (*Response, interface{}, error) {
|
func (p *httpPlugin) Handle(ctx context.Context, op string, content any) (*Response, any, error) {
|
||||||
r := &Request{
|
r := &Request{
|
||||||
Version: APIVersion,
|
Version: APIVersion,
|
||||||
Op: op,
|
Op: op,
|
||||||
|
@@ -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 plugin
|
package server
|
||||||
|
|
||||||
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 interface{}
|
retContent any
|
||||||
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 interface{}
|
retContent any
|
||||||
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 interface{}
|
retContent any
|
||||||
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 interface{}
|
retContent any
|
||||||
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 interface{}
|
retContent any
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
reqid, _ := util.RandID()
|
reqid, _ := util.RandID()
|
||||||
|
@@ -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 plugin
|
package server
|
||||||
|
|
||||||
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 interface{}) (res *Response, retContent interface{}, err error)
|
Handle(ctx context.Context, op string, content any) (res *Response, retContent any, err error)
|
||||||
}
|
}
|
||||||
|
@@ -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 plugin
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@@ -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 plugin
|
package server
|
||||||
|
|
||||||
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 interface{} `json:"content"`
|
Content any `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 interface{} `json:"content"`
|
Content any `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginContent struct {
|
type LoginContent struct {
|
||||||
|
58
pkg/plugin/visitor/plugin.go
Normal file
58
pkg/plugin/visitor/plugin.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// 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
|
||||||
|
}
|
192
pkg/plugin/visitor/virtual_net.go
Normal file
192
pkg/plugin/visitor/virtual_net.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
// 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
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -31,8 +32,8 @@ func (c *Client) SetAuth(user, pwd string) {
|
|||||||
c.authPwd = pwd
|
c.authPwd = pwd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
|
func (c *Client) GetProxyStatus(ctx context.Context, name string) (*client.ProxyStatusResp, error) {
|
||||||
req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/status", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -54,8 +55,8 @@ func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
|
|||||||
return nil, fmt.Errorf("no proxy status found")
|
return nil, fmt.Errorf("no proxy status found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetAllProxyStatus() (client.StatusResp, error) {
|
func (c *Client) GetAllProxyStatus(ctx context.Context) (client.StatusResp, error) {
|
||||||
req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/status", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -70,7 +71,7 @@ func (c *Client) GetAllProxyStatus() (client.StatusResp, error) {
|
|||||||
return allStatus, nil
|
return allStatus, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Reload(strictMode bool) error {
|
func (c *Client) Reload(ctx context.Context, strictMode bool) error {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
if strictMode {
|
if strictMode {
|
||||||
v.Set("strictConfig", "true")
|
v.Set("strictConfig", "true")
|
||||||
@@ -79,7 +80,7 @@ func (c *Client) Reload(strictMode bool) error {
|
|||||||
if len(v) > 0 {
|
if len(v) > 0 {
|
||||||
queryStr = "?" + v.Encode()
|
queryStr = "?" + v.Encode()
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("GET", "http://"+c.address+"/api/reload"+queryStr, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/reload"+queryStr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -87,8 +88,8 @@ func (c *Client) Reload(strictMode bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Stop() error {
|
func (c *Client) Stop(ctx context.Context) error {
|
||||||
req, err := http.NewRequest("POST", "http://"+c.address+"/api/stop", nil)
|
req, err := http.NewRequestWithContext(ctx, "POST", "http://"+c.address+"/api/stop", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -96,16 +97,16 @@ func (c *Client) Stop() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetConfig() (string, error) {
|
func (c *Client) GetConfig(ctx context.Context) (string, error) {
|
||||||
req, err := http.NewRequest("GET", "http://"+c.address+"/api/config", nil)
|
req, err := http.NewRequestWithContext(ctx, "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(content string) error {
|
func (c *Client) UpdateConfig(ctx context.Context, content string) error {
|
||||||
req, err := http.NewRequest("PUT", "http://"+c.address+"/api/config", strings.NewReader(content))
|
req, err := http.NewRequestWithContext(ctx, "PUT", "http://"+c.address+"/api/config", strings.NewReader(content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -112,6 +112,10 @@ 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()
|
||||||
|
|
||||||
|
@@ -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 ...interface{}) {
|
func Errorf(format string, v ...any) {
|
||||||
Logger.Errorf(format, v...)
|
Logger.Errorf(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warnf(format string, v ...interface{}) {
|
func Warnf(format string, v ...any) {
|
||||||
Logger.Warnf(format, v...)
|
Logger.Warnf(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Infof(format string, v ...interface{}) {
|
func Infof(format string, v ...any) {
|
||||||
Logger.Infof(format, v...)
|
Logger.Infof(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debugf(format string, v ...interface{}) {
|
func Debugf(format string, v ...any) {
|
||||||
Logger.Debugf(format, v...)
|
Logger.Debugf(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Tracef(format string, v ...interface{}) {
|
func Tracef(format string, v ...any) {
|
||||||
Logger.Tracef(format, v...)
|
Logger.Tracef(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Logf(level log.Level, offset int, format string, v ...interface{}) {
|
func Logf(level log.Level, offset int, format string, v ...any) {
|
||||||
Logger.Logf(level, offset, format, v...)
|
Logger.Logf(level, offset, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
min, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
minValue, 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
|
||||||
}
|
}
|
||||||
max, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
maxValue, 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 max < min {
|
if maxValue < minValue {
|
||||||
err = fmt.Errorf("range number is invalid")
|
err = fmt.Errorf("range number is invalid")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i := min; i <= max; i++ {
|
for i := minValue; i <= maxValue; 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 {
|
||||||
min := int64(minRatio * 1000.0)
|
minValue := int64(minRatio * 1000.0)
|
||||||
max := int64(maxRatio * 1000.0)
|
maxValue := int64(maxRatio * 1000.0)
|
||||||
var n int64
|
var n int64
|
||||||
if max <= min {
|
if maxValue <= minValue {
|
||||||
n = min
|
n = minValue
|
||||||
} else {
|
} else {
|
||||||
n = mathrand.Int64N(max-min) + min
|
n = mathrand.Int64N(maxValue-minValue) + minValue
|
||||||
}
|
}
|
||||||
d := duration * time.Duration(n) / time.Duration(1000)
|
d := duration * time.Duration(n) / time.Duration(1000)
|
||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
var version = "0.59.0"
|
var version = "0.62.1"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
@@ -29,6 +29,8 @@ 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"
|
||||||
@@ -41,7 +43,7 @@ type HTTPReverseProxyOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HTTPReverseProxy struct {
|
type HTTPReverseProxy struct {
|
||||||
proxy *httputil.ReverseProxy
|
proxy http.Handler
|
||||||
vhostRouter *Routers
|
vhostRouter *Routers
|
||||||
|
|
||||||
responseHeaderTimeout time.Duration
|
responseHeaderTimeout time.Duration
|
||||||
@@ -138,7 +140,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
|||||||
_, _ = rw.Write(getNotFoundPageContent())
|
_, _ = rw.Write(getNotFoundPageContent())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
rp.proxy = proxy
|
rp.proxy = h2c.NewHandler(proxy, &http2.Server{})
|
||||||
return rp
|
return rp
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +162,7 @@ func (rp *HTTPReverseProxy) UnRegister(routeCfg RouteConfig) {
|
|||||||
func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser string) *RouteConfig {
|
func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser string) *RouteConfig {
|
||||||
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
|
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
|
||||||
if ok {
|
if ok {
|
||||||
log.Debugf("get new HTTP request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser)
|
log.Debugf("get new http request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser)
|
||||||
return vr.payload.(*RouteConfig)
|
return vr.payload.(*RouteConfig)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@@ -24,7 +24,7 @@ type Router struct {
|
|||||||
httpUser string
|
httpUser string
|
||||||
|
|
||||||
// store any object here
|
// store any object here
|
||||||
payload interface{}
|
payload any
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouters() *Routers {
|
func NewRouters() *Routers {
|
||||||
@@ -33,7 +33,7 @@ func NewRouters() *Routers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Routers) Add(domain, location, httpUser string, payload interface{}) error {
|
func (r *Routers) Add(domain, location, httpUser string, payload any) error {
|
||||||
domain = strings.ToLower(domain)
|
domain = strings.ToLower(domain)
|
||||||
|
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
|
@@ -100,6 +100,10 @@ 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)
|
||||||
@@ -271,7 +275,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
|
||||||
|
@@ -94,22 +94,22 @@ func (l *Logger) Spawn() *Logger {
|
|||||||
return nl
|
return nl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Errorf(format string, v ...interface{}) {
|
func (l *Logger) Errorf(format string, v ...any) {
|
||||||
log.Logger.Errorf(l.prefixString+format, v...)
|
log.Logger.Errorf(l.prefixString+format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Warnf(format string, v ...interface{}) {
|
func (l *Logger) Warnf(format string, v ...any) {
|
||||||
log.Logger.Warnf(l.prefixString+format, v...)
|
log.Logger.Warnf(l.prefixString+format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Infof(format string, v ...interface{}) {
|
func (l *Logger) Infof(format string, v ...any) {
|
||||||
log.Logger.Infof(l.prefixString+format, v...)
|
log.Logger.Infof(l.prefixString+format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
func (l *Logger) Debugf(format string, v ...any) {
|
||||||
log.Logger.Debugf(l.prefixString+format, v...)
|
log.Logger.Debugf(l.prefixString+format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Logger) Tracef(format string, v ...interface{}) {
|
func (l *Logger) Tracef(format string, v ...any) {
|
||||||
log.Logger.Tracef(l.prefixString+format, v...)
|
log.Logger.Tracef(l.prefixString+format, v...)
|
||||||
}
|
}
|
||||||
|
386
pkg/vnet/controller.go
Normal file
386
pkg/vnet/controller.go
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
// 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 vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/pool"
|
||||||
|
"github.com/songgao/water/waterutil"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxPacketSize = 1420
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
addr string
|
||||||
|
|
||||||
|
tun io.ReadWriteCloser
|
||||||
|
clientRouter *clientRouter // Route based on destination IP (client mode)
|
||||||
|
serverRouter *serverRouter // Route based on source IP (server mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(cfg v1.VirtualNetConfig) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
addr: cfg.Address,
|
||||||
|
clientRouter: newClientRouter(),
|
||||||
|
serverRouter: newServerRouter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Init() error {
|
||||||
|
tunDevice, err := OpenTun(context.Background(), c.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.tun = tunDevice
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Run() error {
|
||||||
|
conn := c.tun
|
||||||
|
|
||||||
|
for {
|
||||||
|
buf := pool.GetBuf(maxPacketSize)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
pool.PutBuf(buf)
|
||||||
|
log.Warnf("vnet read from tun error: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.handlePacket(buf[:n])
|
||||||
|
pool.PutBuf(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePacket processes a single packet. The caller is responsible for managing the buffer.
|
||||||
|
func (c *Controller) handlePacket(buf []byte) {
|
||||||
|
log.Tracef("vnet read from tun [%d]: %s", len(buf), base64.StdEncoding.EncodeToString(buf))
|
||||||
|
|
||||||
|
var src, dst net.IP
|
||||||
|
switch {
|
||||||
|
case waterutil.IsIPv4(buf):
|
||||||
|
header, err := ipv4.ParseHeader(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("parse ipv4 header error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src = header.Src
|
||||||
|
dst = header.Dst
|
||||||
|
log.Tracef("%s >> %s %d/%-4d %-4x %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.Len, header.TotalLen, header.ID, header.Flags)
|
||||||
|
case waterutil.IsIPv6(buf):
|
||||||
|
header, err := ipv6.ParseHeader(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("parse ipv6 header error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src = header.Src
|
||||||
|
dst = header.Dst
|
||||||
|
log.Tracef("%s >> %s %d %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.PayloadLen, header.TrafficClass)
|
||||||
|
default:
|
||||||
|
log.Tracef("unknown packet, discarded(%d)", len(buf))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetConn, err := c.clientRouter.findConn(dst)
|
||||||
|
if err == nil {
|
||||||
|
if err := WriteMessage(targetConn, buf); err != nil {
|
||||||
|
log.Warnf("write to client target conn error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetConn, err = c.serverRouter.findConnBySrc(dst)
|
||||||
|
if err == nil {
|
||||||
|
if err := WriteMessage(targetConn, buf); err != nil {
|
||||||
|
log.Warnf("write to server target conn error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("no route found for packet from %s to %s", src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Stop() error {
|
||||||
|
return c.tun.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client connection read loop
|
||||||
|
func (c *Controller) readLoopClient(ctx context.Context, conn io.ReadWriteCloser) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
defer func() {
|
||||||
|
// Remove the route when read loop ends (connection closed)
|
||||||
|
c.clientRouter.removeConnRoute(conn)
|
||||||
|
conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
data, err := ReadMessage(conn)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("client read error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case waterutil.IsIPv4(data):
|
||||||
|
header, err := ipv4.ParseHeader(data)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("parse ipv4 header error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
xl.Tracef("%s >> %s %d/%-4d %-4x %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.Len, header.TotalLen, header.ID, header.Flags)
|
||||||
|
case waterutil.IsIPv6(data):
|
||||||
|
header, err := ipv6.ParseHeader(data)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("parse ipv6 header error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
xl.Tracef("%s >> %s %d %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.PayloadLen, header.TrafficClass)
|
||||||
|
default:
|
||||||
|
xl.Tracef("unknown packet, discarded(%d)", len(data))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Tracef("vnet write to tun (client) [%d]: %s", len(data), base64.StdEncoding.EncodeToString(data))
|
||||||
|
_, err = c.tun.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("client write tun error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server connection read loop
|
||||||
|
func (c *Controller) readLoopServer(ctx context.Context, conn io.ReadWriteCloser, onClose func()) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
defer func() {
|
||||||
|
// Clean up all IP mappings associated with this connection when it closes
|
||||||
|
c.serverRouter.cleanupConnIPs(conn)
|
||||||
|
// Call the provided callback upon closure
|
||||||
|
if onClose != nil {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
data, err := ReadMessage(conn)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("server read error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register source IP to connection mapping
|
||||||
|
if waterutil.IsIPv4(data) || waterutil.IsIPv6(data) {
|
||||||
|
var src net.IP
|
||||||
|
if waterutil.IsIPv4(data) {
|
||||||
|
header, err := ipv4.ParseHeader(data)
|
||||||
|
if err == nil {
|
||||||
|
src = header.Src
|
||||||
|
c.serverRouter.registerSrcIP(src, conn)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header, err := ipv6.ParseHeader(data)
|
||||||
|
if err == nil {
|
||||||
|
src = header.Src
|
||||||
|
c.serverRouter.registerSrcIP(src, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Tracef("vnet write to tun (server) [%d]: %s", len(data), base64.StdEncoding.EncodeToString(data))
|
||||||
|
_, err = c.tun.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("server write tun error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterClientRoute registers a client route (based on destination IP CIDR)
|
||||||
|
// and starts the read loop
|
||||||
|
func (c *Controller) RegisterClientRoute(ctx context.Context, name string, routes []net.IPNet, conn io.ReadWriteCloser) {
|
||||||
|
c.clientRouter.addRoute(name, routes, conn)
|
||||||
|
go c.readLoopClient(ctx, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterClientRoute Remove client route from routing table
|
||||||
|
func (c *Controller) UnregisterClientRoute(name string) {
|
||||||
|
c.clientRouter.delRoute(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartServerConnReadLoop starts the read loop for a server connection
|
||||||
|
// (dynamically associates with source IPs)
|
||||||
|
func (c *Controller) StartServerConnReadLoop(ctx context.Context, conn io.ReadWriteCloser, onClose func()) {
|
||||||
|
go c.readLoopServer(ctx, conn, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRoutes Convert route strings to IPNet objects
|
||||||
|
func ParseRoutes(routeStrings []string) ([]net.IPNet, error) {
|
||||||
|
routes := make([]net.IPNet, 0, len(routeStrings))
|
||||||
|
for _, r := range routeStrings {
|
||||||
|
_, ipNet, err := net.ParseCIDR(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse route %s error: %v", r, err)
|
||||||
|
}
|
||||||
|
routes = append(routes, *ipNet)
|
||||||
|
}
|
||||||
|
return routes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client router (based on destination IP routing)
|
||||||
|
type clientRouter struct {
|
||||||
|
routes map[string]*routeElement
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClientRouter() *clientRouter {
|
||||||
|
return &clientRouter{
|
||||||
|
routes: make(map[string]*routeElement),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *clientRouter) addRoute(name string, routes []net.IPNet, conn io.ReadWriteCloser) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
r.routes[name] = &routeElement{
|
||||||
|
name: name,
|
||||||
|
routes: routes,
|
||||||
|
conn: conn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *clientRouter) findConn(dst net.IP) (io.Writer, error) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
for _, re := range r.routes {
|
||||||
|
for _, route := range re.routes {
|
||||||
|
if route.Contains(dst) {
|
||||||
|
return re.conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no route found for destination %s", dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *clientRouter) delRoute(name string) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
delete(r.routes, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *clientRouter) removeConnRoute(conn io.Writer) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
for name, re := range r.routes {
|
||||||
|
if re.conn == conn {
|
||||||
|
delete(r.routes, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server router (based solely on source IP routing)
|
||||||
|
type serverRouter struct {
|
||||||
|
srcIPConns map[string]io.Writer // Source IP string to connection mapping
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newServerRouter() *serverRouter {
|
||||||
|
return &serverRouter{
|
||||||
|
srcIPConns: make(map[string]io.Writer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *serverRouter) findConnBySrc(src net.IP) (io.Writer, error) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
conn, exists := r.srcIPConns[src.String()]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("no route found for source %s", src)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *serverRouter) registerSrcIP(src net.IP, conn io.Writer) {
|
||||||
|
key := src.String()
|
||||||
|
|
||||||
|
r.mu.RLock()
|
||||||
|
existingConn, ok := r.srcIPConns[key]
|
||||||
|
r.mu.RUnlock()
|
||||||
|
|
||||||
|
// If the entry exists and the connection is the same, no need to do anything.
|
||||||
|
if ok && existingConn == conn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire write lock to update the map.
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
// Double-check after acquiring the write lock to handle potential race conditions.
|
||||||
|
existingConn, ok = r.srcIPConns[key]
|
||||||
|
if ok && existingConn == conn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.srcIPConns[key] = conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupConnIPs removes all IP mappings associated with the specified connection
|
||||||
|
func (r *serverRouter) cleanupConnIPs(conn io.Writer) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
// Find and delete all IP mappings pointing to this connection
|
||||||
|
for ip, mappedConn := range r.srcIPConns {
|
||||||
|
if mappedConn == conn {
|
||||||
|
delete(r.srcIPConns, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type routeElement struct {
|
||||||
|
name string
|
||||||
|
routes []net.IPNet
|
||||||
|
conn io.ReadWriteCloser
|
||||||
|
}
|
81
pkg/vnet/message.go
Normal file
81
pkg/vnet/message.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// 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 vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Maximum message size
|
||||||
|
const (
|
||||||
|
maxMessageSize = 1024 * 1024 // 1MB
|
||||||
|
)
|
||||||
|
|
||||||
|
// Format: [length(4 bytes)][data(length bytes)]
|
||||||
|
|
||||||
|
// ReadMessage reads a framed message from the reader
|
||||||
|
func ReadMessage(r io.Reader) ([]byte, error) {
|
||||||
|
// Read length (4 bytes)
|
||||||
|
var length uint32
|
||||||
|
err := binary.Read(r, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read message length error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check length to prevent DoS
|
||||||
|
if length == 0 {
|
||||||
|
return nil, fmt.Errorf("message length is 0")
|
||||||
|
}
|
||||||
|
if length > maxMessageSize {
|
||||||
|
return nil, fmt.Errorf("message too large: %d > %d", length, maxMessageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read message data
|
||||||
|
data := make([]byte, length)
|
||||||
|
_, err = io.ReadFull(r, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read message data error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMessage writes a framed message to the writer
|
||||||
|
func WriteMessage(w io.Writer, data []byte) error {
|
||||||
|
// Get data length
|
||||||
|
length := uint32(len(data))
|
||||||
|
if length == 0 {
|
||||||
|
return fmt.Errorf("message data length is 0")
|
||||||
|
}
|
||||||
|
if length > maxMessageSize {
|
||||||
|
return fmt.Errorf("message too large: %d > %d", length, maxMessageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write length
|
||||||
|
err := binary.Write(w, binary.LittleEndian, length)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write message length error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write message data
|
||||||
|
_, err = w.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write message data error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
109
pkg/vnet/tun.go
Normal file
109
pkg/vnet/tun.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// 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 vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/pool"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
offset = 16
|
||||||
|
defaultPacketSize = 1420
|
||||||
|
)
|
||||||
|
|
||||||
|
type TunDevice interface {
|
||||||
|
io.ReadWriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenTun(ctx context.Context, addr string) (TunDevice, error) {
|
||||||
|
td, err := openTun(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mtu, err := td.MTU()
|
||||||
|
if err != nil {
|
||||||
|
mtu = defaultPacketSize
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferSize := max(mtu, defaultPacketSize)
|
||||||
|
batchSize := td.BatchSize()
|
||||||
|
|
||||||
|
device := &tunDeviceWrapper{
|
||||||
|
dev: td,
|
||||||
|
bufferSize: bufferSize,
|
||||||
|
readBuffers: make([][]byte, batchSize),
|
||||||
|
sizeBuffer: make([]int, batchSize),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range device.readBuffers {
|
||||||
|
device.readBuffers[i] = make([]byte, offset+bufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return device, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tunDeviceWrapper struct {
|
||||||
|
dev tun.Device
|
||||||
|
bufferSize int
|
||||||
|
readBuffers [][]byte
|
||||||
|
packetBuffers [][]byte
|
||||||
|
sizeBuffer []int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tunDeviceWrapper) Read(p []byte) (int, error) {
|
||||||
|
if len(d.packetBuffers) > 0 {
|
||||||
|
n := copy(p, d.packetBuffers[0])
|
||||||
|
d.packetBuffers = d.packetBuffers[1:]
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := d.dev.Read(d.readBuffers, d.sizeBuffer, offset)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range n {
|
||||||
|
if d.sizeBuffer[i] <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d.packetBuffers = append(d.packetBuffers, d.readBuffers[i][offset:offset+d.sizeBuffer[i]])
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSize := copy(p, d.packetBuffers[0])
|
||||||
|
d.packetBuffers = d.packetBuffers[1:]
|
||||||
|
|
||||||
|
return dataSize, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tunDeviceWrapper) Write(p []byte) (int, error) {
|
||||||
|
buf := pool.GetBuf(offset + d.bufferSize)
|
||||||
|
defer pool.PutBuf(buf)
|
||||||
|
|
||||||
|
n := copy(buf[offset:], p)
|
||||||
|
_, err := d.dev.Write([][]byte{buf[:offset+n]}, offset)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tunDeviceWrapper) Close() error {
|
||||||
|
return d.dev.Close()
|
||||||
|
}
|
85
pkg/vnet/tun_darwin.go
Normal file
85
pkg/vnet/tun_darwin.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// 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 vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultTunName = "utun"
|
||||||
|
defaultMTU = 1420
|
||||||
|
)
|
||||||
|
|
||||||
|
func openTun(_ context.Context, addr string) (tun.Device, error) {
|
||||||
|
dev, err := tun.CreateTUN(defaultTunName, defaultMTU)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name, err := dev.Name()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, ipNet, err := net.ParseCIDR(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate a peer IP for the point-to-point tunnel
|
||||||
|
peerIP := generatePeerIP(ip)
|
||||||
|
|
||||||
|
// Configure the interface with proper point-to-point addressing
|
||||||
|
if err = exec.Command("ifconfig", name, "inet", ip.String(), peerIP.String(), "mtu", fmt.Sprint(defaultMTU), "up").Run(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add default route for the tunnel subnet
|
||||||
|
routes := []net.IPNet{*ipNet}
|
||||||
|
if err = addRoutes(name, routes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generatePeerIP creates a peer IP for the point-to-point tunnel
|
||||||
|
// by incrementing the last octet of the IP
|
||||||
|
func generatePeerIP(ip net.IP) net.IP {
|
||||||
|
// Make a copy to avoid modifying the original
|
||||||
|
peerIP := make(net.IP, len(ip))
|
||||||
|
copy(peerIP, ip)
|
||||||
|
|
||||||
|
// Increment the last octet
|
||||||
|
peerIP[len(peerIP)-1]++
|
||||||
|
|
||||||
|
return peerIP
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRoutes configures system routes for the TUN interface
|
||||||
|
func addRoutes(ifName string, routes []net.IPNet) error {
|
||||||
|
for _, route := range routes {
|
||||||
|
routeStr := route.String()
|
||||||
|
if err := exec.Command("route", "add", "-net", routeStr, "-interface", ifName).Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
131
pkg/vnet/tun_linux.go
Normal file
131
pkg/vnet/tun_linux.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// 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 vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseTunName = "utun"
|
||||||
|
defaultMTU = 1420
|
||||||
|
)
|
||||||
|
|
||||||
|
func openTun(_ context.Context, addr string) (tun.Device, error) {
|
||||||
|
name, err := findNextTunName(baseTunName)
|
||||||
|
if err != nil {
|
||||||
|
name = getFallbackTunName(baseTunName, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tunDevice, err := tun.CreateTUN(name, defaultMTU)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create TUN device '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualName, err := tunDevice.Name()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ifn, err := net.InterfaceByName(actualName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(actualName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, cidr, err := net.ParseCIDR(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := netlink.AddrAdd(link, &netlink.Addr{
|
||||||
|
IPNet: &net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: cidr.Mask,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkSetUp(link); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = addRoutes(ifn, cidr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tunDevice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNextTunName(basename string) (string, error) {
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get network interfaces: %w", err)
|
||||||
|
}
|
||||||
|
maxSuffix := -1
|
||||||
|
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
name := iface.Name
|
||||||
|
if strings.HasPrefix(name, basename) {
|
||||||
|
suffix := name[len(basename):]
|
||||||
|
if suffix == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
numSuffix, err := strconv.Atoi(suffix)
|
||||||
|
if err == nil && numSuffix > maxSuffix {
|
||||||
|
maxSuffix = numSuffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSuffix := maxSuffix + 1
|
||||||
|
name := fmt.Sprintf("%s%d", basename, nextSuffix)
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRoutes(ifn *net.Interface, cidr *net.IPNet) error {
|
||||||
|
r := netlink.Route{
|
||||||
|
Dst: cidr,
|
||||||
|
LinkIndex: ifn.Index,
|
||||||
|
}
|
||||||
|
if err := netlink.RouteReplace(&r); err != nil {
|
||||||
|
return fmt.Errorf("add route to %v error: %v", r.Dst, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFallbackTunName generates a deterministic fallback TUN device name
|
||||||
|
// based on the base name and the provided address string using a hash.
|
||||||
|
func getFallbackTunName(baseName, addr string) string {
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write([]byte(addr))
|
||||||
|
hashBytes := hasher.Sum(nil)
|
||||||
|
// Use first 4 bytes -> 8 hex chars for brevity, respecting IFNAMSIZ limit.
|
||||||
|
shortHash := hex.EncodeToString(hashBytes[:4])
|
||||||
|
return fmt.Sprintf("%s%s", baseName, shortHash)
|
||||||
|
}
|
29
pkg/vnet/tun_unsupported.go
Normal file
29
pkg/vnet/tun_unsupported.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// 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 !darwin && !linux
|
||||||
|
|
||||||
|
package vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openTun(_ context.Context, _ string) (tun.Device, error) {
|
||||||
|
return nil, fmt.Errorf("virtual net is not supported on this platform (%s/%s)", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
@@ -224,7 +224,7 @@ func (ctl *Control) Close() error {
|
|||||||
|
|
||||||
func (ctl *Control) Replaced(newCtl *Control) {
|
func (ctl *Control) Replaced(newCtl *Control) {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
xl.Infof("Replaced by client [%s]", newCtl.runID)
|
xl.Infof("replaced by client [%s]", newCtl.runID)
|
||||||
ctl.runID = ""
|
ctl.runID = ""
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
}
|
}
|
||||||
|
@@ -59,3 +59,13 @@ type ResourceController struct {
|
|||||||
// All server manager plugin
|
// All server manager plugin
|
||||||
PluginManager *plugin.Manager
|
PluginManager *plugin.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rc *ResourceController) Close() error {
|
||||||
|
if rc.VhostHTTPSMuxer != nil {
|
||||||
|
rc.VhostHTTPSMuxer.Close()
|
||||||
|
}
|
||||||
|
if rc.TCPMuxHTTPConnectMuxer != nil {
|
||||||
|
rc.TCPMuxHTTPConnectMuxer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -97,14 +97,14 @@ func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
|
|||||||
func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Infof("Http request: [%s]", r.URL.Path)
|
log.Infof("http request: [%s]", r.URL.Path)
|
||||||
serverStats := mem.StatsCollector.GetServer()
|
serverStats := mem.StatsCollector.GetServer()
|
||||||
svrResp := serverInfoResp{
|
svrResp := serverInfoResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
@@ -196,15 +196,15 @@ func getConfByType(proxyType string) any {
|
|||||||
|
|
||||||
// Get proxy info.
|
// Get proxy info.
|
||||||
type ProxyStatsInfo struct {
|
type ProxyStatsInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Conf interface{} `json:"conf"`
|
Conf any `json:"conf"`
|
||||||
ClientVersion string `json:"clientVersion,omitempty"`
|
ClientVersion string `json:"clientVersion,omitempty"`
|
||||||
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
||||||
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
||||||
CurConns int64 `json:"curConns"`
|
CurConns int64 `json:"curConns"`
|
||||||
LastStartTime string `json:"lastStartTime"`
|
LastStartTime string `json:"lastStartTime"`
|
||||||
LastCloseTime string `json:"lastCloseTime"`
|
LastCloseTime string `json:"lastCloseTime"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetProxyInfoResp struct {
|
type GetProxyInfoResp struct {
|
||||||
@@ -218,13 +218,13 @@ func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
|
|||||||
proxyType := params["type"]
|
proxyType := params["type"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
log.Infof("Http request: [%s]", r.URL.Path)
|
log.Infof("http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
proxyInfoResp := GetProxyInfoResp{}
|
proxyInfoResp := GetProxyInfoResp{}
|
||||||
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
||||||
@@ -272,14 +272,14 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
|
|||||||
|
|
||||||
// Get proxy info by name.
|
// Get proxy info by name.
|
||||||
type GetProxyStatsResp struct {
|
type GetProxyStatsResp struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Conf interface{} `json:"conf"`
|
Conf any `json:"conf"`
|
||||||
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
||||||
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
||||||
CurConns int64 `json:"curConns"`
|
CurConns int64 `json:"curConns"`
|
||||||
LastStartTime string `json:"lastStartTime"`
|
LastStartTime string `json:"lastStartTime"`
|
||||||
LastCloseTime string `json:"lastCloseTime"`
|
LastCloseTime string `json:"lastCloseTime"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// /api/proxy/:type/:name
|
// /api/proxy/:type/:name
|
||||||
@@ -290,13 +290,13 @@ func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request
|
|||||||
name := params["name"]
|
name := params["name"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
log.Infof("Http request: [%s]", r.URL.Path)
|
log.Infof("http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
var proxyStatsResp GetProxyStatsResp
|
var proxyStatsResp GetProxyStatsResp
|
||||||
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
||||||
@@ -358,13 +358,13 @@ func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
|||||||
name := params["name"]
|
name := params["name"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
log.Infof("Http request: [%s]", r.URL.Path)
|
log.Infof("http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
trafficResp := GetProxyTrafficResp{}
|
trafficResp := GetProxyTrafficResp{}
|
||||||
trafficResp.Name = name
|
trafficResp.Name = name
|
||||||
@@ -386,9 +386,9 @@ func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
log.Infof("Http request: [%s]", r.URL.Path)
|
log.Infof("http request: [%s]", r.URL.Path)
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
w.WriteHeader(res.Code)
|
w.WriteHeader(res.Code)
|
||||||
if len(res.Msg) > 0 {
|
if len(res.Msg) > 0 {
|
||||||
_, _ = w.Write([]byte(res.Msg))
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
|
@@ -137,17 +137,17 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn,
|
|||||||
dstAddr string
|
dstAddr string
|
||||||
srcPortStr string
|
srcPortStr string
|
||||||
dstPortStr string
|
dstPortStr string
|
||||||
srcPort int
|
srcPort uint64
|
||||||
dstPort int
|
dstPort uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
if src != nil {
|
if src != nil {
|
||||||
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
|
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
|
||||||
srcPort, _ = strconv.Atoi(srcPortStr)
|
srcPort, _ = strconv.ParseUint(srcPortStr, 10, 16)
|
||||||
}
|
}
|
||||||
if dst != nil {
|
if dst != nil {
|
||||||
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
|
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
|
||||||
dstPort, _ = strconv.Atoi(dstPortStr)
|
dstPort, _ = strconv.ParseUint(dstPortStr, 10, 16)
|
||||||
}
|
}
|
||||||
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
||||||
ProxyName: pxy.GetName(),
|
ProxyName: pxy.GetName(),
|
||||||
@@ -190,8 +190,8 @@ func (pxy *BaseProxy) startCommonTCPListenersHandler() {
|
|||||||
} else {
|
} else {
|
||||||
tempDelay *= 2
|
tempDelay *= 2
|
||||||
}
|
}
|
||||||
if max := 1 * time.Second; tempDelay > max {
|
if maxTime := 1 * time.Second; tempDelay > maxTime {
|
||||||
tempDelay = max
|
tempDelay = maxTime
|
||||||
}
|
}
|
||||||
xl.Infof("met temporary error: %s, sleep for %s ...", err, tempDelay)
|
xl.Infof("met temporary error: %s, sleep for %s ...", err, tempDelay)
|
||||||
time.Sleep(tempDelay)
|
time.Sleep(tempDelay)
|
||||||
|
@@ -17,8 +17,7 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync"
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
@@ -32,7 +31,8 @@ type XTCPProxy struct {
|
|||||||
*BaseProxy
|
*BaseProxy
|
||||||
cfg *v1.XTCPProxyConfig
|
cfg *v1.XTCPProxyConfig
|
||||||
|
|
||||||
closeCh chan struct{}
|
closeCh chan struct{}
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXTCPProxy(baseProxy *BaseProxy) Proxy {
|
func NewXTCPProxy(baseProxy *BaseProxy) Proxy {
|
||||||
@@ -43,6 +43,7 @@ func NewXTCPProxy(baseProxy *BaseProxy) Proxy {
|
|||||||
return &XTCPProxy{
|
return &XTCPProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: baseProxy,
|
||||||
cfg: unwrapped,
|
cfg: unwrapped,
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +88,9 @@ func (pxy *XTCPProxy) Run() (remoteAddr string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *XTCPProxy) Close() {
|
func (pxy *XTCPProxy) Close() {
|
||||||
pxy.BaseProxy.Close()
|
pxy.closeOnce.Do(func() {
|
||||||
pxy.rc.NatHoleController.CloseClient(pxy.GetName())
|
pxy.BaseProxy.Close()
|
||||||
_ = errors.PanicToError(func() {
|
pxy.rc.NatHoleController.CloseClient(pxy.GetName())
|
||||||
close(pxy.closeCh)
|
close(pxy.closeCh)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -386,24 +386,30 @@ func (svr *Service) Run(ctx context.Context) {
|
|||||||
func (svr *Service) Close() error {
|
func (svr *Service) Close() error {
|
||||||
if svr.kcpListener != nil {
|
if svr.kcpListener != nil {
|
||||||
svr.kcpListener.Close()
|
svr.kcpListener.Close()
|
||||||
svr.kcpListener = nil
|
|
||||||
}
|
}
|
||||||
if svr.quicListener != nil {
|
if svr.quicListener != nil {
|
||||||
svr.quicListener.Close()
|
svr.quicListener.Close()
|
||||||
svr.quicListener = nil
|
|
||||||
}
|
}
|
||||||
if svr.websocketListener != nil {
|
if svr.websocketListener != nil {
|
||||||
svr.websocketListener.Close()
|
svr.websocketListener.Close()
|
||||||
svr.websocketListener = nil
|
|
||||||
}
|
}
|
||||||
if svr.tlsListener != nil {
|
if svr.tlsListener != nil {
|
||||||
svr.tlsListener.Close()
|
svr.tlsListener.Close()
|
||||||
svr.tlsConfig = nil
|
}
|
||||||
|
if svr.sshTunnelListener != nil {
|
||||||
|
svr.sshTunnelListener.Close()
|
||||||
}
|
}
|
||||||
if svr.listener != nil {
|
if svr.listener != nil {
|
||||||
svr.listener.Close()
|
svr.listener.Close()
|
||||||
svr.listener = nil
|
|
||||||
}
|
}
|
||||||
|
if svr.webServer != nil {
|
||||||
|
svr.webServer.Close()
|
||||||
|
}
|
||||||
|
if svr.sshTunnelGateway != nil {
|
||||||
|
svr.sshTunnelGateway.Close()
|
||||||
|
}
|
||||||
|
svr.rc.Close()
|
||||||
|
svr.muxer.Close()
|
||||||
svr.ctlManager.Close()
|
svr.ctlManager.Close()
|
||||||
if svr.cancel != nil {
|
if svr.cancel != nil {
|
||||||
svr.cancel()
|
svr.cancel()
|
||||||
@@ -421,7 +427,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna
|
|||||||
|
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
_ = conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
||||||
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
||||||
log.Tracef("Failed to read message: %v", err)
|
log.Tracef("failed to read message: %v", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -469,7 +475,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Warnf("Error message type for the new connection [%s]", conn.RemoteAddr().String())
|
log.Warnf("error message type for the new connection [%s]", conn.RemoteAddr().String())
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -482,7 +488,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) {
|
|||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Listener for incoming connections from client closed")
|
log.Warnf("listener for incoming connections from client closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// inject xlog object into net.Conn context
|
// inject xlog object into net.Conn context
|
||||||
@@ -498,7 +504,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) {
|
|||||||
var isTLS, custom bool
|
var isTLS, custom bool
|
||||||
c, isTLS, custom, err = netpkg.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, forceTLS, connReadTimeout)
|
c, isTLS, custom, err = netpkg.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, forceTLS, connReadTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
|
log.Warnf("checkAndEnableTLSServerConnWithTimeout error: %v", err)
|
||||||
originConn.Close()
|
originConn.Close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -514,7 +520,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) {
|
|||||||
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
||||||
session, err := fmux.Server(frpConn, fmuxCfg)
|
session, err := fmux.Server(frpConn, fmuxCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to create mux connection: %v", err)
|
log.Warnf("failed to create mux connection: %v", err)
|
||||||
frpConn.Close()
|
frpConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -522,7 +528,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) {
|
|||||||
for {
|
for {
|
||||||
stream, err := session.AcceptStream()
|
stream, err := session.AcceptStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Accept new mux stream error: %v", err)
|
log.Debugf("accept new mux stream error: %v", err)
|
||||||
session.Close()
|
session.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -540,7 +546,7 @@ func (svr *Service) HandleQUICListener(l *quic.Listener) {
|
|||||||
for {
|
for {
|
||||||
c, err := l.Accept(context.Background())
|
c, err := l.Accept(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("QUICListener for incoming connections from client closed")
|
log.Warnf("quic listener for incoming connections from client closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Start a new goroutine to handle connection.
|
// Start a new goroutine to handle connection.
|
||||||
@@ -548,7 +554,7 @@ func (svr *Service) HandleQUICListener(l *quic.Listener) {
|
|||||||
for {
|
for {
|
||||||
stream, err := frpConn.AcceptStream(context.Background())
|
stream, err := frpConn.AcceptStream(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Accept new quic mux stream error: %v", err)
|
log.Debugf("accept new quic mux stream error: %v", err)
|
||||||
_ = frpConn.CloseWithError(0, "")
|
_ = frpConn.CloseWithError(0, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -614,7 +620,7 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn)
|
|||||||
xl := netpkg.NewLogFromConn(workConn)
|
xl := netpkg.NewLogFromConn(workConn)
|
||||||
ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
|
ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
|
||||||
if !exist {
|
if !exist {
|
||||||
xl.Warnf("No client control found for run id [%s]", newMsg.RunID)
|
xl.Warnf("no client control found for run id [%s]", newMsg.RunID)
|
||||||
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
|
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
|
||||||
}
|
}
|
||||||
// server plugin hook
|
// server plugin hook
|
||||||
|
@@ -38,7 +38,7 @@ func RunE2ETests(t *testing.T) {
|
|||||||
// Randomize specs as well as suites
|
// Randomize specs as well as suites
|
||||||
suiteConfig.RandomizeAllSpecs = true
|
suiteConfig.RandomizeAllSpecs = true
|
||||||
|
|
||||||
log.Infof("Starting e2e run %q on Ginkgo node %d of total %d",
|
log.Infof("starting e2e run %q on Ginkgo node %d of total %d",
|
||||||
framework.RunID, suiteConfig.ParallelProcess, suiteConfig.ParallelTotal)
|
framework.RunID, suiteConfig.ParallelProcess, suiteConfig.ParallelTotal)
|
||||||
ginkgo.RunSpecs(t, "frp e2e suite", suiteConfig, reporterConfig)
|
ginkgo.RunSpecs(t, "frp e2e suite", suiteConfig, reporterConfig)
|
||||||
}
|
}
|
||||||
|
@@ -5,75 +5,75 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ExpectEqual expects the specified two are the same, otherwise an exception raises
|
// ExpectEqual expects the specified two are the same, otherwise an exception raises
|
||||||
func ExpectEqual(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectEqual(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.Equal(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.Equal(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectEqualValues expects the specified two are the same, it not strict about type
|
// ExpectEqualValues expects the specified two are the same, it not strict about type
|
||||||
func ExpectEqualValues(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectEqualValues(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.BeEquivalentTo(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.BeEquivalentTo(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectEqualValuesWithOffset(offset int, actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectEqualValuesWithOffset(offset int, actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1+offset, actual).To(gomega.BeEquivalentTo(extra), explain...)
|
gomega.ExpectWithOffset(1+offset, actual).To(gomega.BeEquivalentTo(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectNotEqual expects the specified two are not the same, otherwise an exception raises
|
// ExpectNotEqual expects the specified two are not the same, otherwise an exception raises
|
||||||
func ExpectNotEqual(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectNotEqual(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).NotTo(gomega.Equal(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).NotTo(gomega.Equal(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectError expects an error happens, otherwise an exception raises
|
// ExpectError expects an error happens, otherwise an exception raises
|
||||||
func ExpectError(err error, explain ...interface{}) {
|
func ExpectError(err error, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...)
|
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectErrorWithOffset(offset int, err error, explain ...interface{}) {
|
func ExpectErrorWithOffset(offset int, err error, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1+offset, err).To(gomega.HaveOccurred(), explain...)
|
gomega.ExpectWithOffset(1+offset, err).To(gomega.HaveOccurred(), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectNoError checks if "err" is set, and if so, fails assertion while logging the error.
|
// ExpectNoError checks if "err" is set, and if so, fails assertion while logging the error.
|
||||||
func ExpectNoError(err error, explain ...interface{}) {
|
func ExpectNoError(err error, explain ...any) {
|
||||||
ExpectNoErrorWithOffset(1, err, explain...)
|
ExpectNoErrorWithOffset(1, err, explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectNoErrorWithOffset checks if "err" is set, and if so, fails assertion while logging the error at "offset" levels above its caller
|
// ExpectNoErrorWithOffset checks if "err" is set, and if so, fails assertion while logging the error at "offset" levels above its caller
|
||||||
// (for example, for call chain f -> g -> ExpectNoErrorWithOffset(1, ...) error would be logged for "f").
|
// (for example, for call chain f -> g -> ExpectNoErrorWithOffset(1, ...) error would be logged for "f").
|
||||||
func ExpectNoErrorWithOffset(offset int, err error, explain ...interface{}) {
|
func ExpectNoErrorWithOffset(offset int, err error, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1+offset, err).NotTo(gomega.HaveOccurred(), explain...)
|
gomega.ExpectWithOffset(1+offset, err).NotTo(gomega.HaveOccurred(), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectContainSubstring(actual, substr string, explain ...interface{}) {
|
func ExpectContainSubstring(actual, substr string, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.ContainSubstring(substr), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.ContainSubstring(substr), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectConsistOf expects actual contains precisely the extra elements. The ordering of the elements does not matter.
|
// ExpectConsistOf expects actual contains precisely the extra elements. The ordering of the elements does not matter.
|
||||||
func ExpectConsistOf(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectConsistOf(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectContainElements(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectContainElements(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.ContainElements(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.ContainElements(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectNotContainElements(actual interface{}, extra interface{}, explain ...interface{}) {
|
func ExpectNotContainElements(actual any, extra any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).NotTo(gomega.ContainElements(extra), explain...)
|
gomega.ExpectWithOffset(1, actual).NotTo(gomega.ContainElements(extra), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectHaveKey expects the actual map has the key in the keyset
|
// ExpectHaveKey expects the actual map has the key in the keyset
|
||||||
func ExpectHaveKey(actual interface{}, key interface{}, explain ...interface{}) {
|
func ExpectHaveKey(actual any, key any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.HaveKey(key), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.HaveKey(key), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpectEmpty expects actual is empty
|
// ExpectEmpty expects actual is empty
|
||||||
func ExpectEmpty(actual interface{}, explain ...interface{}) {
|
func ExpectEmpty(actual any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).To(gomega.BeEmpty(), explain...)
|
gomega.ExpectWithOffset(1, actual).To(gomega.BeEmpty(), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectTrue(actual interface{}, explain ...interface{}) {
|
func ExpectTrue(actual any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1, actual).Should(gomega.BeTrue(), explain...)
|
gomega.ExpectWithOffset(1, actual).Should(gomega.BeTrue(), explain...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExpectTrueWithOffset(offset int, actual interface{}, explain ...interface{}) {
|
func ExpectTrueWithOffset(offset int, actual any, explain ...any) {
|
||||||
gomega.ExpectWithOffset(1+offset, actual).Should(gomega.BeTrue(), explain...)
|
gomega.ExpectWithOffset(1+offset, actual).Should(gomega.BeTrue(), explain...)
|
||||||
}
|
}
|
||||||
|
@@ -11,18 +11,18 @@ func nowStamp() string {
|
|||||||
return time.Now().Format(time.StampMilli)
|
return time.Now().Format(time.StampMilli)
|
||||||
}
|
}
|
||||||
|
|
||||||
func log(level string, format string, args ...interface{}) {
|
func log(level string, format string, args ...any) {
|
||||||
fmt.Fprintf(ginkgo.GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...)
|
fmt.Fprintf(ginkgo.GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logf logs the info.
|
// Logf logs the info.
|
||||||
func Logf(format string, args ...interface{}) {
|
func Logf(format string, args ...any) {
|
||||||
log("INFO", format, args...)
|
log("INFO", format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failf logs the fail info, including a stack trace starts with its direct caller
|
// Failf logs the fail info, including a stack trace starts with its direct caller
|
||||||
// (for example, for call chain f -> g -> Failf("foo", ...) error would be logged for "g").
|
// (for example, for call chain f -> g -> Failf("foo", ...) error would be logged for "g").
|
||||||
func Failf(format string, args ...interface{}) {
|
func Failf(format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
skip := 1
|
skip := 1
|
||||||
ginkgo.Fail(msg, skip)
|
ginkgo.Fail(msg, skip)
|
||||||
|
@@ -67,8 +67,8 @@ func (m *MockServers) Close() {
|
|||||||
os.Remove(m.udsEchoServer.BindAddr())
|
os.Remove(m.udsEchoServer.BindAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockServers) GetTemplateParams() map[string]interface{} {
|
func (m *MockServers) GetTemplateParams() map[string]any {
|
||||||
ret := make(map[string]interface{})
|
ret := make(map[string]any)
|
||||||
ret[TCPEchoServerPort] = m.tcpEchoServer.BindPort()
|
ret[TCPEchoServerPort] = m.tcpEchoServer.BindPort()
|
||||||
ret[UDPEchoServerPort] = m.udpEchoServer.BindPort()
|
ret[UDPEchoServerPort] = m.udpEchoServer.BindPort()
|
||||||
ret[UDSEchoServerAddr] = m.udsEchoServer.BindAddr()
|
ret[UDSEchoServerAddr] = m.udsEchoServer.BindAddr()
|
||||||
@@ -76,7 +76,7 @@ func (m *MockServers) GetTemplateParams() map[string]interface{} {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockServers) GetParam(key string) interface{} {
|
func (m *MockServers) GetParam(key string) any {
|
||||||
params := m.GetTemplateParams()
|
params := m.GetTemplateParams()
|
||||||
if v, ok := params[key]; ok {
|
if v, ok := params[key]; ok {
|
||||||
return v
|
return v
|
||||||
|
@@ -20,7 +20,7 @@ func ExpectResponseCode(code int) EnsureFunc {
|
|||||||
if resp.Code == code {
|
if resp.Code == code {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
flog.Warnf("Expect code %d, but got %d", code, resp.Code)
|
flog.Warnf("expect code %d, but got %d", code, resp.Code)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ type RequestExpect struct {
|
|||||||
f *Framework
|
f *Framework
|
||||||
expectResp []byte
|
expectResp []byte
|
||||||
expectError bool
|
expectError bool
|
||||||
explain []interface{}
|
explain []any
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequestExpect(f *Framework) *RequestExpect {
|
func NewRequestExpect(f *Framework) *RequestExpect {
|
||||||
@@ -51,7 +51,7 @@ func NewRequestExpect(f *Framework) *RequestExpect {
|
|||||||
f: f,
|
f: f,
|
||||||
expectResp: []byte(consts.TestString),
|
expectResp: []byte(consts.TestString),
|
||||||
expectError: false,
|
expectError: false,
|
||||||
explain: make([]interface{}, 0),
|
explain: make([]any, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ func (e *RequestExpect) ExpectError(expectErr bool) *RequestExpect {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RequestExpect) Explain(explain ...interface{}) *RequestExpect {
|
func (e *RequestExpect) Explain(explain ...any) *RequestExpect {
|
||||||
e.explain = explain
|
e.explain = explain
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
@@ -111,14 +111,14 @@ func (e *RequestExpect) Ensure(fns ...EnsureFunc) {
|
|||||||
|
|
||||||
if len(fns) == 0 {
|
if len(fns) == 0 {
|
||||||
if !bytes.Equal(e.expectResp, ret.Content) {
|
if !bytes.Equal(e.expectResp, ret.Content) {
|
||||||
flog.Tracef("Response info: %+v", ret)
|
flog.Tracef("response info: %+v", ret)
|
||||||
}
|
}
|
||||||
ExpectEqualValuesWithOffset(1, string(ret.Content), string(e.expectResp), e.explain...)
|
ExpectEqualValuesWithOffset(1, string(ret.Content), string(e.expectResp), e.explain...)
|
||||||
} else {
|
} else {
|
||||||
for _, fn := range fns {
|
for _, fn := range fns {
|
||||||
ok := fn(ret)
|
ok := fn(ret)
|
||||||
if !ok {
|
if !ok {
|
||||||
flog.Tracef("Response info: %+v", ret)
|
flog.Tracef("response info: %+v", ret)
|
||||||
}
|
}
|
||||||
ExpectTrueWithOffset(1, ok, e.explain...)
|
ExpectTrueWithOffset(1, ok, e.explain...)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -54,7 +55,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
|
|||||||
framework.NewRequestExpect(f).Port(p3Port).Ensure()
|
framework.NewRequestExpect(f).Port(p3Port).Ensure()
|
||||||
|
|
||||||
client := f.APIClientForFrpc(adminPort)
|
client := f.APIClientForFrpc(adminPort)
|
||||||
conf, err := client.GetConfig()
|
conf, err := client.GetConfig(context.Background())
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
newP2Port := f.AllocPort()
|
newP2Port := f.AllocPort()
|
||||||
@@ -65,10 +66,10 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
|
|||||||
newClientConf = newClientConf[:p3Index]
|
newClientConf = newClientConf[:p3Index]
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.UpdateConfig(newClientConf)
|
err = client.UpdateConfig(context.Background(), newClientConf)
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
err = client.Reload(true)
|
err = client.Reload(context.Background(), true)
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
|
|||||||
framework.NewRequestExpect(f).Port(testPort).Ensure()
|
framework.NewRequestExpect(f).Port(testPort).Ensure()
|
||||||
|
|
||||||
client := f.APIClientForFrpc(adminPort)
|
client := f.APIClientForFrpc(adminPort)
|
||||||
err := client.Stop()
|
err := client.Stop(context.Background())
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -101,7 +102,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
|
|||||||
client := f.APIClientForFrpc(adminPort)
|
client := f.APIClientForFrpc(adminPort)
|
||||||
|
|
||||||
// tcp random port
|
// tcp random port
|
||||||
status, err := client.GetProxyStatus("tcp")
|
status, err := client.GetProxyStatus(context.Background(), "tcp")
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
_, portStr, err := net.SplitHostPort(status.RemoteAddr)
|
_, portStr, err := net.SplitHostPort(status.RemoteAddr)
|
||||||
@@ -112,7 +113,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
|
|||||||
framework.NewRequestExpect(f).Port(port).Ensure()
|
framework.NewRequestExpect(f).Port(port).Ensure()
|
||||||
|
|
||||||
// udp random port
|
// udp random port
|
||||||
status, err = client.GetProxyStatus("udp")
|
status, err = client.GetProxyStatus(context.Background(), "udp")
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
_, portStr, err = net.SplitHostPort(status.RemoteAddr)
|
_, portStr, err = net.SplitHostPort(status.RemoteAddr)
|
||||||
|
@@ -93,7 +93,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
|
|||||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
framework.NewRequestExpect(f).Port(remotePort).Ensure(func(resp *request.Response) bool {
|
framework.NewRequestExpect(f).Port(remotePort).Ensure(func(resp *request.Response) bool {
|
||||||
log.Tracef("ProxyProtocol get SourceAddr: %s", string(resp.Content))
|
log.Tracef("proxy protocol get SourceAddr: %s", string(resp.Content))
|
||||||
addr, err := net.ResolveTCPAddr("tcp", string(resp.Content))
|
addr, err := net.ResolveTCPAddr("tcp", string(resp.Content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@@ -142,7 +142,7 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
|
|||||||
r.HTTP().HTTPHost("normal.example.com")
|
r.HTTP().HTTPHost("normal.example.com")
|
||||||
}).Ensure(framework.ExpectResponseCode(404))
|
}).Ensure(framework.ExpectResponseCode(404))
|
||||||
|
|
||||||
log.Tracef("ProxyProtocol get SourceAddr: %s", srcAddrRecord)
|
log.Tracef("proxy protocol get SourceAddr: %s", srcAddrRecord)
|
||||||
addr, err := net.ResolveTCPAddr("tcp", srcAddrRecord)
|
addr, err := net.ResolveTCPAddr("tcp", srcAddrRecord)
|
||||||
framework.ExpectNoError(err, srcAddrRecord)
|
framework.ExpectNoError(err, srcAddrRecord)
|
||||||
framework.ExpectEqualValues("127.0.0.1", addr.IP.String())
|
framework.ExpectEqualValues("127.0.0.1", addr.IP.String())
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user