mirror of
https://github.com/fatedier/frp.git
synced 2025-11-14 13:27:50 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44985f574d | ||
|
|
a6478aeac8 | ||
|
|
806b55c292 | ||
|
|
c9ca9353cf | ||
|
|
496b1f1078 | ||
|
|
9cb0726ebc | ||
|
|
31190c703d | ||
|
|
1452facf77 | ||
|
|
6d4d8e616d | ||
|
|
a7ad967231 | ||
|
|
31fa3f021a | ||
|
|
01a0d557ef | ||
|
|
b9c24e9b69 | ||
|
|
df12cc2b9d | ||
|
|
7cc67e852e | ||
|
|
307d1bfa3f | ||
|
|
5eb8f3db03 | ||
|
|
2d3af8a108 | ||
|
|
3ae1a4f45a | ||
|
|
21d8e674f0 | ||
|
|
5e70d5bee0 | ||
|
|
5c8ea51eb5 | ||
|
|
bae0b4d7c0 | ||
|
|
74255f711e | ||
|
|
7cd02f5bd8 | ||
|
|
c95311d1a0 | ||
|
|
885b029fcf | ||
|
|
466d69eae0 | ||
|
|
f1454e91f5 | ||
|
|
7c8cbeb250 | ||
|
|
e9e12cf888 | ||
|
|
6430afcfa5 | ||
|
|
3235addaaa | ||
|
|
46ff40543a | ||
|
|
4fd6301577 | ||
|
|
efcc028a3d | ||
|
|
90861b6821 | ||
|
|
8f105adbca | ||
|
|
53626b370c | ||
|
|
b1789afbab | ||
|
|
88c7e8bf7c | ||
|
|
fc4e787fe2 | ||
|
|
4c4d5f0d0d | ||
|
|
801e8c6742 |
@@ -2,7 +2,7 @@ version: 2
|
|||||||
jobs:
|
jobs:
|
||||||
go-version-latest:
|
go-version-latest:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.20-node
|
- image: cimg/go:1.21-node
|
||||||
resource_class: large
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
- run: make alltest
|
- run: make alltest
|
||||||
go-version-last:
|
go-version-last:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.19-node
|
- image: cimg/go:1.20-node
|
||||||
resource_class: large
|
resource_class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
|||||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,3 +1,4 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [fatedier]
|
github: [fatedier]
|
||||||
|
custom: ["https://afdian.net/a/fatedier"]
|
||||||
|
|||||||
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@@ -4,7 +4,3 @@ copilot:summary
|
|||||||
|
|
||||||
### WHY
|
### WHY
|
||||||
<!-- author to complete -->
|
<!-- author to complete -->
|
||||||
|
|
||||||
### Walkthrough
|
|
||||||
|
|
||||||
copilot:walkthrough
|
|
||||||
|
|||||||
4
.github/workflows/build-and-push-image.yml
vendored
4
.github/workflows/build-and-push-image.yml
vendored
@@ -61,7 +61,7 @@ jobs:
|
|||||||
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build and push frpc
|
- name: Build and push frpc
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./dockerfiles/Dockerfile-for-frpc
|
file: ./dockerfiles/Dockerfile-for-frpc
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
${{ env.TAG_FRPC_GPR }}
|
${{ env.TAG_FRPC_GPR }}
|
||||||
|
|
||||||
- name: Build and push frps
|
- name: Build and push frps
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./dockerfiles/Dockerfile-for-frps
|
file: ./dockerfiles/Dockerfile-for-frps
|
||||||
|
|||||||
6
.github/workflows/golangci-lint.yml
vendored
6
.github/workflows/golangci-lint.yml
vendored
@@ -14,15 +14,15 @@ jobs:
|
|||||||
name: lint
|
name: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: '1.21'
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
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.51
|
version: v1.53
|
||||||
|
|
||||||
# Optional: golangci-lint command line arguments.
|
# Optional: golangci-lint command line arguments.
|
||||||
# args: --issues-exit-code=0
|
# args: --issues-exit-code=0
|
||||||
|
|||||||
8
.github/workflows/goreleaser.yml
vendored
8
.github/workflows/goreleaser.yml
vendored
@@ -13,18 +13,18 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '1.20'
|
go-version: '1.21'
|
||||||
|
|
||||||
- name: Make All
|
- name: Make All
|
||||||
run: |
|
run: |
|
||||||
./package.sh
|
./package.sh
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v3
|
uses: goreleaser/goreleaser-action@v4
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --rm-dist --release-notes=./Release.md
|
args: release --clean --release-notes=./Release.md
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
pull-requests: write # for actions/stale to close stale PRs
|
pull-requests: write # for actions/stale to close stale PRs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v6
|
- uses: actions/stale@v8
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
|
stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
|
||||||
|
|||||||
@@ -120,16 +120,18 @@ issues:
|
|||||||
# - composite literal uses unkeyed fields
|
# - composite literal uses unkeyed fields
|
||||||
|
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
# Exclude some linters from running on test files.
|
# Exclude some linters from running on test files.
|
||||||
- path: _test\.go$|^tests/|^samples/
|
- path: _test\.go$|^tests/|^samples/
|
||||||
linters:
|
linters:
|
||||||
- errcheck
|
- errcheck
|
||||||
- maligned
|
- maligned
|
||||||
|
- linters:
|
||||||
# keep it until we only support go1.20
|
- revive
|
||||||
- linters:
|
- stylecheck
|
||||||
- staticcheck
|
text: "use underscores in Go names"
|
||||||
text: "SA1019: rand.Seed has been deprecated"
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: "unused-parameter"
|
||||||
|
|
||||||
# Independently from option `exclude` we use default exclude patterns,
|
# Independently from option `exclude` we use default exclude patterns,
|
||||||
# it can be disabled by this option. To list all
|
# it can be disabled by this option. To list all
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export PATH := $(GOPATH)/bin:$(PATH)
|
|||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
LDFLAGS := -s -w
|
LDFLAGS := -s -w
|
||||||
|
|
||||||
os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64
|
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -19,8 +19,6 @@ app:
|
|||||||
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_$${target_suffix} ./cmd/frps;\
|
env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_$${target_suffix} ./cmd/frps;\
|
||||||
echo "Build $${os}-$${arch} done";\
|
echo "Build $${os}-$${arch} done";\
|
||||||
)
|
)
|
||||||
@mv ./release/frpc_windows_386 ./release/frpc_windows_386.exe
|
|
||||||
@mv ./release/frps_windows_386 ./release/frps_windows_386.exe
|
|
||||||
@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
|
@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
|
||||||
@mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe
|
@mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe
|
||||||
@mv ./release/frpc_windows_arm64 ./release/frpc_windows_arm64.exe
|
@mv ./release/frpc_windows_arm64 ./release/frpc_windows_arm64.exe
|
||||||
|
|||||||
41
README_zh.md
41
README_zh.md
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
[README](README.md) | [中文文档](README_zh.md)
|
[README](README.md) | [中文文档](README_zh.md)
|
||||||
|
|
||||||
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议,且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
||||||
|
|
||||||
<h3 align="center">Gold Sponsors</h3>
|
<h3 align="center">Gold Sponsors</h3>
|
||||||
<!--gold sponsors start-->
|
<!--gold sponsors start-->
|
||||||
@@ -20,12 +20,13 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
|
|||||||
|
|
||||||
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
|
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
|
||||||
|
|
||||||
* 客户端服务端通信支持 TCP、KCP 以及 Websocket 等多种协议。
|
* 客户端服务端通信支持 TCP、QUIC、KCP 以及 Websocket 等多种协议。
|
||||||
* 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间。
|
* 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间,降低请求延迟。
|
||||||
* 代理组间的负载均衡。
|
* 代理组间的负载均衡。
|
||||||
* 端口复用,多个服务通过同一个服务端端口暴露。
|
* 端口复用,多个服务通过同一个服务端端口暴露。
|
||||||
* 多个原生支持的客户端插件(静态文件查看,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。
|
* 支持 P2P 通信,流量不经过服务器中转,充分利用带宽资源。
|
||||||
* 高度扩展性的服务端插件系统,方便结合自身需求进行功能扩展。
|
* 多个原生支持的客户端插件(静态文件查看,HTTPS/HTTP 协议转换,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。
|
||||||
|
* 高度扩展性的服务端插件系统,易于结合自身需求进行功能扩展。
|
||||||
* 服务端和客户端 UI 页面。
|
* 服务端和客户端 UI 页面。
|
||||||
|
|
||||||
## 开发状态
|
## 开发状态
|
||||||
@@ -34,13 +35,25 @@ frp 目前已被很多公司广泛用于测试、生产环境。
|
|||||||
|
|
||||||
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||||
|
|
||||||
我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续一段时间。
|
我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续较长的一段时间。
|
||||||
|
|
||||||
现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。
|
现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。
|
||||||
|
|
||||||
|
### 关于 v2 的一些说明
|
||||||
|
|
||||||
|
v2 版本的复杂度和难度比我们预期的要高得多。我只能利用零散的时间进行开发,而且由于上下文经常被打断,效率极低。由于这种情况可能会持续一段时间,我们仍然会在当前版本上进行一些优化和迭代,直到我们有更多空闲时间来推进大版本的重构,或者也有可能放弃一次性的重构,而是采用渐进的方式在当前版本上逐步做一些可能会导致不兼容的修改。
|
||||||
|
|
||||||
|
v2 的构想是基于我多年在云原生领域,特别是在 K8s 和 ServiceMesh 方面的工作经验和思考。它的核心是一个现代化的四层和七层代理,类似于 envoy。这个代理本身高度可扩展,不仅可以用于实现内网穿透的功能,还可以应用于更多领域。在这个高度可扩展的内核基础上,我们将实现 frp v1 中的所有功能,并且能够以一种更加优雅的方式实现原先架构中无法实现或不易实现的功能。同时,我们将保持高效的开发和迭代能力。
|
||||||
|
|
||||||
|
除此之外,我希望 frp 本身也成为一个高度可扩展的系统和平台,就像我们可以基于 K8s 提供一系列扩展能力一样。在 K8s 上,我们可以根据企业需求进行定制化开发,例如使用 CRD、controller 模式、webhook、CSI 和 CNI 等。在 frp v1 中,我们引入了服务端插件的概念,实现了一些简单的扩展性。但是,它实际上依赖于简单的 HTTP 协议,并且需要用户自己启动独立的进程和管理。这种方式远远不够灵活和方便,而且现实世界的需求千差万别,我们不能期望一个由少数人维护的非营利性开源项目能够满足所有人的需求。
|
||||||
|
|
||||||
|
最后,我们意识到像配置管理、权限验证、证书管理和管理 API 等模块的当前设计并不够现代化。尽管我们可能在 v1 版本中进行一些优化,但确保兼容性是一个令人头疼的问题,需要投入大量精力来解决。
|
||||||
|
|
||||||
|
非常感谢您对 frp 的支持。
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。
|
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org)。
|
||||||
|
|
||||||
## 为 frp 做贡献
|
## 为 frp 做贡献
|
||||||
|
|
||||||
@@ -55,26 +68,26 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
|||||||
|
|
||||||
**提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
|
**提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
|
||||||
|
|
||||||
## 捐助
|
## 赞助
|
||||||
|
|
||||||
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
||||||
|
|
||||||
### GitHub Sponsors
|
### Sponsors
|
||||||
|
|
||||||
|
长期赞助可以帮助我们保持项目的持续发展。
|
||||||
|
|
||||||
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
||||||
|
|
||||||
|
国内用户可以通过 [爱发电](https://afdian.net/a/fatedier) 赞助我们。
|
||||||
|
|
||||||
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
||||||
|
|
||||||
### 知识星球
|
### 知识星球
|
||||||
|
|
||||||
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
如果您想了解更多 frp 相关技术以及更新详解,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 支付宝扫码捐赠
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 微信支付捐赠
|
### 微信支付捐赠
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
17
Release.md
17
Release.md
@@ -1,18 +1,3 @@
|
|||||||
## Notes
|
|
||||||
|
|
||||||
**For enhanced security, the default values for `tls_enable` and `disable_custom_tls_first_byte` have been set to true.**
|
|
||||||
|
|
||||||
If you wish to revert to the previous default values, you need to manually set the values of these two parameters to false.
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Added support for `allow_users` in stcp, sudp, xtcp. By default, only the same user is allowed to access. Use `*` to allow access from any user. The visitor configuration now supports `server_user` to connect to proxies of other users.
|
|
||||||
* Added fallback support to a specified alternative visitor when xtcp connection fails.
|
|
||||||
|
|
||||||
### Improvements
|
|
||||||
|
|
||||||
* Increased the default value of `MaxStreamWindowSize` for yamux to 6MB, improving traffic forwarding rate in high-latency scenarios.
|
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
* Fixed an issue where having proxies with the same name would cause previously working proxies to become ineffective in `xtcp`.
|
* `admin_user` is not effective in the INI configuration.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>frps dashboard</title>
|
<title>frps dashboard</title>
|
||||||
<script type="module" crossorigin src="./index-ea3edf22.js"></script>
|
<script type="module" crossorigin src="./index-c322b7dd.js"></script>
|
||||||
<link rel="stylesheet" href="./index-1e0c7400.css">
|
<link rel="stylesheet" href="./index-1e0c7400.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func (svr *Service) RunAdminServer(address string) (err error) {
|
|||||||
router.HandleFunc("/healthz", svr.healthz)
|
router.HandleFunc("/healthz", svr.healthz)
|
||||||
|
|
||||||
// debug
|
// debug
|
||||||
if svr.cfg.PprofEnable {
|
if svr.cfg.WebServer.PprofEnable {
|
||||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||||
@@ -47,11 +47,12 @@ func (svr *Service) RunAdminServer(address string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
subRouter := router.NewRoute().Subrouter()
|
subRouter := router.NewRoute().Subrouter()
|
||||||
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
user, passwd := svr.cfg.WebServer.User, svr.cfg.WebServer.Password
|
||||||
subRouter.Use(utilnet.NewHTTPAuthMiddleware(user, passwd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
subRouter.Use(utilnet.NewHTTPAuthMiddleware(user, passwd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||||
|
|
||||||
// api, see admin_api.go
|
// api, see admin_api.go
|
||||||
subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||||
|
subRouter.HandleFunc("/api/stop", svr.apiStop).Methods("POST")
|
||||||
subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||||
subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||||
subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,12 +40,12 @@ type GeneralResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// /healthz
|
// /healthz
|
||||||
func (svr *Service) healthz(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/reload
|
// GET /api/reload
|
||||||
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiReload(w http.ResponseWriter, _ *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
log.Info("api request [/api/reload]")
|
log.Info("api request [/api/reload]")
|
||||||
@@ -55,15 +57,21 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
_, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(svr.cfgFile)
|
cliCfg, pxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(svr.cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if _, err := validation.ValidateAllClientConfig(cliCfg, pxyCfgs, visitorCfgs); err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err = svr.ReloadConf(pxyCfgs, visitorCfgs); err != nil {
|
if err := svr.ReloadConf(pxyCfgs, visitorCfgs); err != nil {
|
||||||
res.Code = 500
|
res.Code = 500
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||||
@@ -72,6 +80,22 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Info("success reload conf")
|
log.Info("success reload conf")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /api/stop
|
||||||
|
func (svr *Service) apiStop(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
|
log.Info("api request [/api/stop]")
|
||||||
|
defer func() {
|
||||||
|
log.Info("api response [/api/stop], code [%d]", res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
_, _ = w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go svr.GracefulClose(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
type StatusResp map[string][]ProxyStatusResp
|
type StatusResp map[string][]ProxyStatusResp
|
||||||
|
|
||||||
type ProxyStatusResp struct {
|
type ProxyStatusResp struct {
|
||||||
@@ -95,7 +119,7 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
|||||||
if baseCfg.LocalPort != 0 {
|
if baseCfg.LocalPort != 0 {
|
||||||
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
|
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
|
||||||
}
|
}
|
||||||
psr.Plugin = baseCfg.Plugin
|
psr.Plugin = baseCfg.Plugin.Type
|
||||||
|
|
||||||
if status.Err == "" {
|
if status.Err == "" {
|
||||||
psr.RemoteAddr = status.RemoteAddr
|
psr.RemoteAddr = status.RemoteAddr
|
||||||
@@ -106,8 +130,8 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
|||||||
return psr
|
return psr
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/status
|
// GET /api/status
|
||||||
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
|
||||||
var (
|
var (
|
||||||
buf []byte
|
buf []byte
|
||||||
res StatusResp = make(map[string][]ProxyStatusResp)
|
res StatusResp = make(map[string][]ProxyStatusResp)
|
||||||
@@ -135,8 +159,8 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/config
|
// GET /api/config
|
||||||
func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
||||||
res := GeneralResponse{Code: 200}
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
log.Info("Http get request [/api/config]")
|
log.Info("Http get request [/api/config]")
|
||||||
@@ -155,27 +179,17 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := config.GetRenderedConfFromFile(svr.cfgFile)
|
content, err := os.ReadFile(svr.cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 400
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Warn("load frpc config file error: %s", res.Msg)
|
log.Warn("load frpc config file error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
res.Msg = string(content)
|
||||||
rows := strings.Split(string(content), "\n")
|
|
||||||
newRows := make([]string, 0, len(rows))
|
|
||||||
for _, row := range rows {
|
|
||||||
row = strings.TrimSpace(row)
|
|
||||||
if strings.HasPrefix(row, "token") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newRows = append(newRows, row)
|
|
||||||
}
|
|
||||||
res.Msg = strings.Join(newRows, "\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT api/config
|
// PUT /api/config
|
||||||
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}
|
||||||
|
|
||||||
@@ -204,49 +218,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get token from origin content
|
if err := os.WriteFile(svr.cfgFile, body, 0o644); err != nil {
|
||||||
token := ""
|
|
||||||
b, err := os.ReadFile(svr.cfgFile)
|
|
||||||
if err != nil {
|
|
||||||
res.Code = 400
|
|
||||||
res.Msg = err.Error()
|
|
||||||
log.Warn("load frpc config file error: %s", res.Msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
content := string(b)
|
|
||||||
|
|
||||||
for _, row := range strings.Split(content, "\n") {
|
|
||||||
row = strings.TrimSpace(row)
|
|
||||||
if strings.HasPrefix(row, "token") {
|
|
||||||
token = row
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpRows := make([]string, 0)
|
|
||||||
for _, row := range strings.Split(string(body), "\n") {
|
|
||||||
row = strings.TrimSpace(row)
|
|
||||||
if strings.HasPrefix(row, "token") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tmpRows = append(tmpRows, row)
|
|
||||||
}
|
|
||||||
|
|
||||||
newRows := make([]string, 0)
|
|
||||||
if token != "" {
|
|
||||||
for _, row := range tmpRows {
|
|
||||||
newRows = append(newRows, row)
|
|
||||||
if strings.HasPrefix(row, "[common]") {
|
|
||||||
newRows = append(newRows, token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newRows = tmpRows
|
|
||||||
}
|
|
||||||
content = strings.Join(newRows, "\n")
|
|
||||||
|
|
||||||
err = os.WriteFile(svr.cfgFile, []byte(content), 0o644)
|
|
||||||
if err != nil {
|
|
||||||
res.Code = 500
|
res.Code = 500
|
||||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||||
log.Warn("%s", res.Msg)
|
log.Warn("%s", res.Msg)
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ import (
|
|||||||
|
|
||||||
"github.com/fatedier/golib/control/shutdown"
|
"github.com/fatedier/golib/control/shutdown"
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"github.com/fatedier/frp/client/visitor"
|
"github.com/fatedier/frp/client/visitor"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"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"
|
||||||
@@ -43,7 +44,7 @@ type Control struct {
|
|||||||
runID string
|
runID string
|
||||||
|
|
||||||
// manage all proxies
|
// manage all proxies
|
||||||
pxyCfgs map[string]config.ProxyConf
|
pxyCfgs []v1.ProxyConfigurer
|
||||||
pm *proxy.Manager
|
pm *proxy.Manager
|
||||||
|
|
||||||
// manage all visitors
|
// manage all visitors
|
||||||
@@ -69,7 +70,7 @@ type Control struct {
|
|||||||
lastPong time.Time
|
lastPong time.Time
|
||||||
|
|
||||||
// The client configuration
|
// The client configuration
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg *v1.ClientCommonConfig
|
||||||
|
|
||||||
readerShutdown *shutdown.Shutdown
|
readerShutdown *shutdown.Shutdown
|
||||||
writerShutdown *shutdown.Shutdown
|
writerShutdown *shutdown.Shutdown
|
||||||
@@ -83,9 +84,9 @@ type Control struct {
|
|||||||
|
|
||||||
func NewControl(
|
func NewControl(
|
||||||
ctx context.Context, runID string, conn net.Conn, cm *ConnectionManager,
|
ctx context.Context, runID string, conn net.Conn, cm *ConnectionManager,
|
||||||
clientCfg config.ClientCommonConf,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
pxyCfgs map[string]config.ProxyConf,
|
pxyCfgs []v1.ProxyConfigurer,
|
||||||
visitorCfgs map[string]config.VisitorConf,
|
visitorCfgs []v1.VisitorConfigurer,
|
||||||
authSetter auth.Setter,
|
authSetter auth.Setter,
|
||||||
) *Control {
|
) *Control {
|
||||||
// new xlog instance
|
// new xlog instance
|
||||||
@@ -124,7 +125,7 @@ func (ctl *Control) Run() {
|
|||||||
go ctl.vm.Run()
|
go ctl.vm.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
func (ctl *Control) HandleReqWorkConn(_ *msg.ReqWorkConn) {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
workConn, err := ctl.connectServer()
|
workConn, err := ctl.connectServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -220,7 +221,7 @@ func (ctl *Control) reader() {
|
|||||||
defer ctl.readerShutdown.Done()
|
defer ctl.readerShutdown.Done()
|
||||||
defer close(ctl.closedCh)
|
defer close(ctl.closedCh)
|
||||||
|
|
||||||
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
|
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Auth.Token))
|
||||||
for {
|
for {
|
||||||
m, err := msg.ReadMsg(encReader)
|
m, err := msg.ReadMsg(encReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -240,7 +241,7 @@ func (ctl *Control) reader() {
|
|||||||
func (ctl *Control) writer() {
|
func (ctl *Control) writer() {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
defer ctl.writerShutdown.Done()
|
defer ctl.writerShutdown.Done()
|
||||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
|
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Auth.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("crypto new writer error: %v", err)
|
xl.Error("crypto new writer error: %v", err)
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
@@ -274,15 +275,16 @@ func (ctl *Control) msgHandler() {
|
|||||||
var hbSendCh <-chan time.Time
|
var hbSendCh <-chan time.Time
|
||||||
// TODO(fatedier): disable heartbeat if TCPMux is enabled.
|
// TODO(fatedier): disable heartbeat if TCPMux is enabled.
|
||||||
// Just keep it here to keep compatible with old version frps.
|
// Just keep it here to keep compatible with old version frps.
|
||||||
if ctl.clientCfg.HeartbeatInterval > 0 {
|
if ctl.clientCfg.Transport.HeartbeatInterval > 0 {
|
||||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
|
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.Transport.HeartbeatInterval) * time.Second)
|
||||||
defer hbSend.Stop()
|
defer hbSend.Stop()
|
||||||
hbSendCh = hbSend.C
|
hbSendCh = hbSend.C
|
||||||
}
|
}
|
||||||
|
|
||||||
var hbCheckCh <-chan time.Time
|
var hbCheckCh <-chan time.Time
|
||||||
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
|
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
|
||||||
if ctl.clientCfg.HeartbeatInterval > 0 && ctl.clientCfg.HeartbeatTimeout > 0 && !ctl.clientCfg.TCPMux {
|
if ctl.clientCfg.Transport.HeartbeatInterval > 0 && ctl.clientCfg.Transport.HeartbeatTimeout > 0 &&
|
||||||
|
!lo.FromPtr(ctl.clientCfg.Transport.TCPMux) {
|
||||||
hbCheck := time.NewTicker(time.Second)
|
hbCheck := time.NewTicker(time.Second)
|
||||||
defer hbCheck.Stop()
|
defer hbCheck.Stop()
|
||||||
hbCheckCh = hbCheck.C
|
hbCheckCh = hbCheck.C
|
||||||
@@ -301,7 +303,7 @@ func (ctl *Control) msgHandler() {
|
|||||||
}
|
}
|
||||||
ctl.sendCh <- pingMsg
|
ctl.sendCh <- pingMsg
|
||||||
case <-hbCheckCh:
|
case <-hbCheckCh:
|
||||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second {
|
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.Transport.HeartbeatTimeout)*time.Second {
|
||||||
xl.Warn("heartbeat timeout")
|
xl.Warn("heartbeat timeout")
|
||||||
// let reader() stop
|
// let reader() stop
|
||||||
ctl.conn.Close()
|
ctl.conn.Close()
|
||||||
@@ -354,7 +356,7 @@ func (ctl *Control) worker() {
|
|||||||
ctl.cm.Close()
|
ctl.cm.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
|
func (ctl *Control) ReloadConf(pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
|
||||||
ctl.vm.Reload(visitorCfgs)
|
ctl.vm.Reload(visitorCfgs)
|
||||||
ctl.pm.Reload(pxyCfgs)
|
ctl.pm.Reload(pxyCfgs)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,26 +51,33 @@ type Monitor struct {
|
|||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMonitor(ctx context.Context, checkType string,
|
func NewMonitor(ctx context.Context, cfg v1.HealthCheckConfig, addr string,
|
||||||
intervalS int, timeoutS int, maxFailedTimes int,
|
|
||||||
addr string, url string,
|
|
||||||
statusNormalFn func(), statusFailedFn func(),
|
statusNormalFn func(), statusFailedFn func(),
|
||||||
) *Monitor {
|
) *Monitor {
|
||||||
if intervalS <= 0 {
|
if cfg.IntervalSeconds <= 0 {
|
||||||
intervalS = 10
|
cfg.IntervalSeconds = 10
|
||||||
}
|
}
|
||||||
if timeoutS <= 0 {
|
if cfg.TimeoutSeconds <= 0 {
|
||||||
timeoutS = 3
|
cfg.TimeoutSeconds = 3
|
||||||
}
|
}
|
||||||
if maxFailedTimes <= 0 {
|
if cfg.MaxFailed <= 0 {
|
||||||
maxFailedTimes = 1
|
cfg.MaxFailed = 1
|
||||||
}
|
}
|
||||||
newctx, cancel := context.WithCancel(ctx)
|
newctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
var url string
|
||||||
|
if cfg.Type == "http" && cfg.Path != "" {
|
||||||
|
s := "http://" + addr
|
||||||
|
if !strings.HasPrefix(cfg.Path, "/") {
|
||||||
|
s += "/"
|
||||||
|
}
|
||||||
|
url = s + cfg.Path
|
||||||
|
}
|
||||||
return &Monitor{
|
return &Monitor{
|
||||||
checkType: checkType,
|
checkType: cfg.Type,
|
||||||
interval: time.Duration(intervalS) * time.Second,
|
interval: time.Duration(cfg.IntervalSeconds) * time.Second,
|
||||||
timeout: time.Duration(timeoutS) * time.Second,
|
timeout: time.Duration(cfg.TimeoutSeconds) * time.Second,
|
||||||
maxFailedTimes: maxFailedTimes,
|
maxFailedTimes: cfg.MaxFailed,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
url: url,
|
url: url,
|
||||||
statusOK: false,
|
statusOK: false,
|
||||||
|
|||||||
@@ -17,16 +17,16 @@ package proxy
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
pxyConfs := []config.ProxyConf{
|
pxyConfs := []v1.ProxyConfigurer{
|
||||||
&config.TCPProxyConf{},
|
&v1.TCPProxyConfig{},
|
||||||
&config.HTTPProxyConf{},
|
&v1.HTTPProxyConfig{},
|
||||||
&config.HTTPSProxyConf{},
|
&v1.HTTPSProxyConfig{},
|
||||||
&config.STCPProxyConf{},
|
&v1.STCPProxyConfig{},
|
||||||
&config.TCPMuxProxyConf{},
|
&v1.TCPMuxProxyConfig{},
|
||||||
}
|
}
|
||||||
for _, cfg := range pxyConfs {
|
for _, cfg := range pxyConfs {
|
||||||
RegisterProxyFactory(reflect.TypeOf(cfg), NewGeneralTCPProxy)
|
RegisterProxyFactory(reflect.TypeOf(cfg), NewGeneralTCPProxy)
|
||||||
@@ -40,7 +40,7 @@ type GeneralTCPProxy struct {
|
|||||||
*BaseProxy
|
*BaseProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGeneralTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
|
func NewGeneralTCPProxy(baseProxy *BaseProxy, _ v1.ProxyConfigurer) Proxy {
|
||||||
return &GeneralTCPProxy{
|
return &GeneralTCPProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: baseProxy,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -30,7 +29,8 @@ import (
|
|||||||
pp "github.com/pires/go-proxyproto"
|
pp "github.com/pires/go-proxyproto"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
@@ -38,9 +38,9 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, config.ProxyConf) Proxy{}
|
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{}
|
||||||
|
|
||||||
func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, config.ProxyConf) Proxy) {
|
func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, v1.ProxyConfigurer) Proxy) {
|
||||||
proxyFactoryRegistry[proxyConfType] = factory
|
proxyFactoryRegistry[proxyConfType] = factory
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,23 +56,23 @@ type Proxy interface {
|
|||||||
|
|
||||||
func NewProxy(
|
func NewProxy(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
pxyConf config.ProxyConf,
|
pxyConf v1.ProxyConfigurer,
|
||||||
clientCfg config.ClientCommonConf,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
) (pxy Proxy) {
|
) (pxy Proxy) {
|
||||||
var limiter *rate.Limiter
|
var limiter *rate.Limiter
|
||||||
limitBytes := pxyConf.GetBaseConfig().BandwidthLimit.Bytes()
|
limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes()
|
||||||
if limitBytes > 0 && pxyConf.GetBaseConfig().BandwidthLimitMode == config.BandwidthLimitModeClient {
|
if limitBytes > 0 && pxyConf.GetBaseConfig().Transport.BandwidthLimitMode == types.BandwidthLimitModeClient {
|
||||||
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
baseProxy := BaseProxy{
|
baseProxy := BaseProxy{
|
||||||
baseProxyConfig: pxyConf.GetBaseConfig(),
|
baseCfg: pxyConf.GetBaseConfig(),
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
limiter: limiter,
|
limiter: limiter,
|
||||||
msgTransporter: msgTransporter,
|
msgTransporter: msgTransporter,
|
||||||
xl: xlog.FromContextSafe(ctx),
|
xl: xlog.FromContextSafe(ctx),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
|
|
||||||
factory := proxyFactoryRegistry[reflect.TypeOf(pxyConf)]
|
factory := proxyFactoryRegistry[reflect.TypeOf(pxyConf)]
|
||||||
@@ -83,10 +83,10 @@ func NewProxy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BaseProxy struct {
|
type BaseProxy struct {
|
||||||
baseProxyConfig *config.BaseProxyConf
|
baseCfg *v1.ProxyBaseConfig
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg *v1.ClientCommonConfig
|
||||||
msgTransporter transport.MessageTransporter
|
msgTransporter transport.MessageTransporter
|
||||||
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.
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -97,8 +97,8 @@ type BaseProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) Run() error {
|
func (pxy *BaseProxy) Run() error {
|
||||||
if pxy.baseProxyConfig.Plugin != "" {
|
if pxy.baseCfg.Plugin.Type != "" {
|
||||||
p, err := plugin.Create(pxy.baseProxyConfig.Plugin, pxy.baseProxyConfig.PluginParams)
|
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, pxy.baseCfg.Plugin.ClientPluginOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -114,13 +114,13 @@ func (pxy *BaseProxy) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
pxy.HandleTCPWorkConnection(conn, m, []byte(pxy.clientCfg.Token))
|
pxy.HandleTCPWorkConnection(conn, m, []byte(pxy.clientCfg.Auth.Token))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common handler for tcp work connections.
|
// Common handler for tcp work connections.
|
||||||
func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWorkConn, encKey []byte) {
|
func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWorkConn, encKey []byte) {
|
||||||
xl := pxy.xl
|
xl := pxy.xl
|
||||||
baseConfig := pxy.baseProxyConfig
|
baseCfg := pxy.baseCfg
|
||||||
var (
|
var (
|
||||||
remote io.ReadWriteCloser
|
remote io.ReadWriteCloser
|
||||||
err error
|
err error
|
||||||
@@ -133,8 +133,8 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
}
|
}
|
||||||
|
|
||||||
xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
|
xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
|
||||||
baseConfig.UseEncryption, baseConfig.UseCompression)
|
baseCfg.Transport.UseEncryption, baseCfg.Transport.UseCompression)
|
||||||
if baseConfig.UseEncryption {
|
if baseCfg.Transport.UseEncryption {
|
||||||
remote, err = libio.WithEncryption(remote, encKey)
|
remote, err = libio.WithEncryption(remote, encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
@@ -142,13 +142,14 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if baseConfig.UseCompression {
|
var compressionResourceRecycleFn func()
|
||||||
remote = libio.WithCompression(remote)
|
if baseCfg.Transport.UseCompression {
|
||||||
|
remote, compressionResourceRecycleFn = libio.WithCompressionFromPool(remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we need to send proxy protocol info
|
// check if we need to send proxy protocol info
|
||||||
var extraInfo []byte
|
var extraInfo plugin.ExtraInfo
|
||||||
if baseConfig.ProxyProtocolVersion != "" {
|
if baseCfg.Transport.ProxyProtocolVersion != "" {
|
||||||
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"
|
||||||
@@ -167,43 +168,41 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
h.TransportProtocol = pp.TCPv6
|
h.TransportProtocol = pp.TCPv6
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseConfig.ProxyProtocolVersion == "v1" {
|
if baseCfg.Transport.ProxyProtocolVersion == "v1" {
|
||||||
h.Version = 1
|
h.Version = 1
|
||||||
} else if baseConfig.ProxyProtocolVersion == "v2" {
|
} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
|
||||||
h.Version = 2
|
h.Version = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewBuffer(nil)
|
extraInfo.ProxyProtocolHeader = h
|
||||||
_, _ = h.WriteTo(buf)
|
|
||||||
extraInfo = buf.Bytes()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.Debug("handle by plugin: %s", pxy.proxyPlugin.Name())
|
xl.Debug("handle by plugin: %s", pxy.proxyPlugin.Name())
|
||||||
pxy.proxyPlugin.Handle(remote, workConn, extraInfo)
|
pxy.proxyPlugin.Handle(remote, workConn, &extraInfo)
|
||||||
xl.Debug("handle by plugin finished")
|
xl.Debug("handle by plugin finished")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
localConn, err := libdial.Dial(
|
localConn, err := libdial.Dial(
|
||||||
net.JoinHostPort(baseConfig.LocalIP, strconv.Itoa(baseConfig.LocalPort)),
|
net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort)),
|
||||||
libdial.WithTimeout(10*time.Second),
|
libdial.WithTimeout(10*time.Second),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
xl.Error("connect to local service [%s:%d] error: %v", baseConfig.LocalIP, baseConfig.LocalPort, err)
|
xl.Error("connect to local service [%s:%d] error: %v", baseCfg.LocalIP, baseCfg.LocalPort, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
xl.Debug("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 len(extraInfo) > 0 {
|
if extraInfo.ProxyProtocolHeader != nil {
|
||||||
if _, err := localConn.Write(extraInfo); err != nil {
|
if _, err := extraInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
xl.Error("write extraInfo to local conn error: %v", err)
|
xl.Error("write proxy protocol header to local conn error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,4 +212,7 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
xl.Trace("join connections errors: %v", errs)
|
xl.Trace("join connections errors: %v", errs)
|
||||||
}
|
}
|
||||||
|
if compressionResourceRecycleFn != nil {
|
||||||
|
compressionResourceRecycleFn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/event"
|
"github.com/fatedier/frp/client/event"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"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"
|
||||||
@@ -35,14 +37,14 @@ type Manager struct {
|
|||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg *v1.ClientCommonConfig
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(
|
func NewManager(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
clientCfg config.ClientCommonConf,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
) *Manager {
|
) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
@@ -113,15 +115,18 @@ func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
|||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
func (pm *Manager) Reload(pxyCfgs []v1.ProxyConfigurer) {
|
||||||
xl := xlog.FromContextSafe(pm.ctx)
|
xl := xlog.FromContextSafe(pm.ctx)
|
||||||
|
pxyCfgsMap := lo.KeyBy(pxyCfgs, func(c v1.ProxyConfigurer) string {
|
||||||
|
return c.GetBaseConfig().Name
|
||||||
|
})
|
||||||
pm.mu.Lock()
|
pm.mu.Lock()
|
||||||
defer pm.mu.Unlock()
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
delPxyNames := make([]string, 0)
|
delPxyNames := make([]string, 0)
|
||||||
for name, pxy := range pm.proxies {
|
for name, pxy := range pm.proxies {
|
||||||
del := false
|
del := false
|
||||||
cfg, ok := pxyCfgs[name]
|
cfg, ok := pxyCfgsMap[name]
|
||||||
if !ok || !reflect.DeepEqual(pxy.Cfg, cfg) {
|
if !ok || !reflect.DeepEqual(pxy.Cfg, cfg) {
|
||||||
del = true
|
del = true
|
||||||
}
|
}
|
||||||
@@ -137,7 +142,8 @@ func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addPxyNames := make([]string, 0)
|
addPxyNames := make([]string, 0)
|
||||||
for name, cfg := range pxyCfgs {
|
for _, cfg := range pxyCfgs {
|
||||||
|
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.proxies[name] = pxy
|
pm.proxies[name] = pxy
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -26,7 +27,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fatedier/frp/client/event"
|
"github.com/fatedier/frp/client/event"
|
||||||
"github.com/fatedier/frp/client/health"
|
"github.com/fatedier/frp/client/health"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"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"
|
||||||
@@ -48,11 +49,11 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type WorkingStatus struct {
|
type WorkingStatus struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Phase string `json:"status"`
|
Phase string `json:"status"`
|
||||||
Err string `json:"err"`
|
Err string `json:"err"`
|
||||||
Cfg config.ProxyConf `json:"cfg"`
|
Cfg v1.ProxyConfigurer `json:"cfg"`
|
||||||
|
|
||||||
// Got from server.
|
// Got from server.
|
||||||
RemoteAddr string `json:"remote_addr"`
|
RemoteAddr string `json:"remote_addr"`
|
||||||
@@ -86,17 +87,17 @@ type Wrapper struct {
|
|||||||
|
|
||||||
func NewWrapper(
|
func NewWrapper(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg config.ProxyConf,
|
cfg v1.ProxyConfigurer,
|
||||||
clientCfg config.ClientCommonConf,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
eventHandler event.Handler,
|
eventHandler event.Handler,
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
) *Wrapper {
|
) *Wrapper {
|
||||||
baseInfo := cfg.GetBaseConfig()
|
baseInfo := cfg.GetBaseConfig()
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name)
|
||||||
pw := &Wrapper{
|
pw := &Wrapper{
|
||||||
WorkingStatus: WorkingStatus{
|
WorkingStatus: WorkingStatus{
|
||||||
Name: baseInfo.ProxyName,
|
Name: baseInfo.Name,
|
||||||
Type: baseInfo.ProxyType,
|
Type: baseInfo.Type,
|
||||||
Phase: ProxyPhaseNew,
|
Phase: ProxyPhaseNew,
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
},
|
},
|
||||||
@@ -108,11 +109,11 @@ func NewWrapper(
|
|||||||
ctx: xlog.NewContext(ctx, xl),
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseInfo.HealthCheckType != "" {
|
if baseInfo.HealthCheck.Type != "" && baseInfo.LocalPort > 0 {
|
||||||
pw.health = 1 // means failed
|
pw.health = 1 // means failed
|
||||||
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
|
addr := net.JoinHostPort(baseInfo.LocalIP, strconv.Itoa(baseInfo.LocalPort))
|
||||||
baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr,
|
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheck, addr,
|
||||||
baseInfo.HealthCheckURL, pw.statusNormalCallback, pw.statusFailedCallback)
|
pw.statusNormalCallback, pw.statusFailedCallback)
|
||||||
xl.Trace("enable health check monitor")
|
xl.Trace("enable health check monitor")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
libio "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/proto/udp"
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
"github.com/fatedier/frp/pkg/util/limit"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
@@ -33,21 +33,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterProxyFactory(reflect.TypeOf(&config.SUDPProxyConf{}), NewSUDPProxy)
|
RegisterProxyFactory(reflect.TypeOf(&v1.SUDPProxyConfig{}), NewSUDPProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SUDPProxy struct {
|
type SUDPProxy struct {
|
||||||
*BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.SUDPProxyConf
|
cfg *v1.SUDPProxyConfig
|
||||||
|
|
||||||
localAddr *net.UDPAddr
|
localAddr *net.UDPAddr
|
||||||
|
|
||||||
closeCh chan struct{}
|
closeCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSUDPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
|
func NewSUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
|
||||||
unwrapped, ok := cfg.(*config.SUDPProxyConf)
|
unwrapped, ok := cfg.(*v1.SUDPProxyConfig)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ func (pxy *SUDPProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||||
xl := pxy.xl
|
xl := pxy.xl
|
||||||
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
||||||
|
|
||||||
@@ -88,15 +88,15 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|||||||
return conn.Close()
|
return conn.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if pxy.cfg.UseEncryption {
|
if pxy.cfg.Transport.UseEncryption {
|
||||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if pxy.cfg.UseCompression {
|
if pxy.cfg.Transport.UseCompression {
|
||||||
rwc = libio.WithCompression(rwc)
|
rwc = libio.WithCompression(rwc)
|
||||||
}
|
}
|
||||||
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
|
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
libio "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/proto/udp"
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
"github.com/fatedier/frp/pkg/util/limit"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
@@ -32,13 +32,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterProxyFactory(reflect.TypeOf(&config.UDPProxyConf{}), NewUDPProxy)
|
RegisterProxyFactory(reflect.TypeOf(&v1.UDPProxyConfig{}), NewUDPProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
type UDPProxy struct {
|
type UDPProxy struct {
|
||||||
*BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.UDPProxyConf
|
cfg *v1.UDPProxyConfig
|
||||||
|
|
||||||
localAddr *net.UDPAddr
|
localAddr *net.UDPAddr
|
||||||
readCh chan *msg.UDPPacket
|
readCh chan *msg.UDPPacket
|
||||||
@@ -49,8 +49,8 @@ type UDPProxy struct {
|
|||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUDPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
|
func NewUDPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
|
||||||
unwrapped, ok := cfg.(*config.UDPProxyConf)
|
unwrapped, ok := cfg.(*v1.UDPProxyConfig)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ func (pxy *UDPProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||||
xl := pxy.xl
|
xl := pxy.xl
|
||||||
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||||
// close resources releated with old workConn
|
// close resources releated with old workConn
|
||||||
@@ -99,15 +99,15 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|||||||
return conn.Close()
|
return conn.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if pxy.cfg.UseEncryption {
|
if pxy.cfg.Transport.UseEncryption {
|
||||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if pxy.cfg.UseCompression {
|
if pxy.cfg.Transport.UseCompression {
|
||||||
rwc = libio.WithCompression(rwc)
|
rwc = libio.WithCompression(rwc)
|
||||||
}
|
}
|
||||||
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
|
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/nathole"
|
"github.com/fatedier/frp/pkg/nathole"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
@@ -31,17 +31,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterProxyFactory(reflect.TypeOf(&config.XTCPProxyConf{}), NewXTCPProxy)
|
RegisterProxyFactory(reflect.TypeOf(&v1.XTCPProxyConfig{}), NewXTCPProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
type XTCPProxy struct {
|
type XTCPProxy struct {
|
||||||
*BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.XTCPProxyConf
|
cfg *v1.XTCPProxyConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
|
func NewXTCPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
|
||||||
unwrapped, ok := cfg.(*config.XTCPProxyConf)
|
unwrapped, ok := cfg.(*v1.XTCPProxyConfig)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
|
|||||||
transactionID := nathole.NewTransactionID()
|
transactionID := nathole.NewTransactionID()
|
||||||
natHoleClientMsg := &msg.NatHoleClient{
|
natHoleClientMsg := &msg.NatHoleClient{
|
||||||
TransactionID: transactionID,
|
TransactionID: transactionID,
|
||||||
ProxyName: pxy.cfg.ProxyName,
|
ProxyName: pxy.cfg.Name,
|
||||||
Sid: natHoleSidMsg.Sid,
|
Sid: natHoleSidMsg.Sid,
|
||||||
MappedAddrs: prepareResult.Addrs,
|
MappedAddrs: prepareResult.Addrs,
|
||||||
AssistedAddrs: prepareResult.AssistedAddrs,
|
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||||
@@ -93,7 +93,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
|
|||||||
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||||
|
|
||||||
listenConn := prepareResult.ListenConn
|
listenConn := prepareResult.ListenConn
|
||||||
newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Sk))
|
newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Secretkey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
listenConn.Close()
|
listenConn.Close()
|
||||||
xl.Warn("make hole error: %v", err)
|
xl.Warn("make hole error: %v", err)
|
||||||
@@ -154,7 +154,7 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s
|
|||||||
xl.Error("accept connection error: %v", err)
|
xl.Error("accept connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Sk))
|
go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Secretkey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,9 +170,9 @@ func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, star
|
|||||||
tlsConfig.NextProtos = []string{"frp"}
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
quicListener, err := quic.Listen(listenConn, tlsConfig,
|
quicListener, err := quic.Listen(listenConn, tlsConfig,
|
||||||
&quic.Config{
|
&quic.Config{
|
||||||
MaxIdleTimeout: time.Duration(pxy.clientCfg.QUICMaxIdleTimeout) * time.Second,
|
MaxIdleTimeout: time.Duration(pxy.clientCfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||||
MaxIncomingStreams: int64(pxy.clientCfg.QUICMaxIncomingStreams),
|
MaxIncomingStreams: int64(pxy.clientCfg.Transport.QUIC.MaxIncomingStreams),
|
||||||
KeepAlivePeriod: time.Duration(pxy.clientCfg.QUICKeepalivePeriod) * time.Second,
|
KeepAlivePeriod: time.Duration(pxy.clientCfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -192,6 +192,6 @@ func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, star
|
|||||||
_ = c.CloseWithError(0, "")
|
_ = c.CloseWithError(0, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go pxy.HandleTCPWorkConnection(utilnet.QuicStreamToNetConn(stream, c), startWorkConnMsg, []byte(pxy.cfg.Sk))
|
go pxy.HandleTCPWorkConnection(utilnet.QuicStreamToNetConn(stream, c), startWorkConnMsg, []byte(pxy.cfg.Secretkey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -32,10 +31,11 @@ import (
|
|||||||
libdial "github.com/fatedier/golib/net/dial"
|
libdial "github.com/fatedier/golib/net/dial"
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
quic "github.com/quic-go/quic-go"
|
quic "github.com/quic-go/quic-go"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"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/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
@@ -47,8 +47,6 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
crypto.DefaultSalt = "frp"
|
crypto.DefaultSalt = "frp"
|
||||||
// TODO: remove this when we drop support for go1.19
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service is a client service.
|
// Service is a client service.
|
||||||
@@ -63,9 +61,9 @@ type Service struct {
|
|||||||
// Sets authentication based on selected method
|
// Sets authentication based on selected method
|
||||||
authSetter auth.Setter
|
authSetter auth.Setter
|
||||||
|
|
||||||
cfg config.ClientCommonConf
|
cfg *v1.ClientCommonConfig
|
||||||
pxyCfgs map[string]config.ProxyConf
|
pxyCfgs []v1.ProxyConfigurer
|
||||||
visitorCfgs map[string]config.VisitorConf
|
visitorCfgs []v1.VisitorConfigurer
|
||||||
cfgMu sync.RWMutex
|
cfgMu sync.RWMutex
|
||||||
|
|
||||||
// The configuration file used to initialize this client, or an empty
|
// The configuration file used to initialize this client, or an empty
|
||||||
@@ -81,21 +79,19 @@ type Service struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
cfg config.ClientCommonConf,
|
cfg *v1.ClientCommonConfig,
|
||||||
pxyCfgs map[string]config.ProxyConf,
|
pxyCfgs []v1.ProxyConfigurer,
|
||||||
visitorCfgs map[string]config.VisitorConf,
|
visitorCfgs []v1.VisitorConfigurer,
|
||||||
cfgFile string,
|
cfgFile string,
|
||||||
) (svr *Service, err error) {
|
) (svr *Service, err error) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
authSetter: auth.NewAuthSetter(cfg.ClientConfig),
|
authSetter: auth.NewAuthSetter(cfg.Auth),
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
cfgFile: cfgFile,
|
cfgFile: cfgFile,
|
||||||
pxyCfgs: pxyCfgs,
|
pxyCfgs: pxyCfgs,
|
||||||
visitorCfgs: visitorCfgs,
|
visitorCfgs: visitorCfgs,
|
||||||
|
ctx: context.Background(),
|
||||||
exit: 0,
|
exit: 0,
|
||||||
ctx: xlog.NewContext(ctx, xlog.New()),
|
|
||||||
cancel: cancel,
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -106,7 +102,11 @@ func (svr *Service) GetController() *Control {
|
|||||||
return svr.ctl
|
return svr.ctl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) Run() error {
|
func (svr *Service) Run(ctx context.Context) error {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
svr.ctx = xlog.NewContext(ctx, xlog.New())
|
||||||
|
svr.cancel = cancel
|
||||||
|
|
||||||
xl := xlog.FromContextSafe(svr.ctx)
|
xl := xlog.FromContextSafe(svr.ctx)
|
||||||
|
|
||||||
// set custom DNSServer
|
// set custom DNSServer
|
||||||
@@ -132,10 +132,10 @@ func (svr *Service) Run() error {
|
|||||||
|
|
||||||
// if login_fail_exit is true, just exit this program
|
// if login_fail_exit is true, just exit this program
|
||||||
// otherwise sleep a while and try again to connect to server
|
// otherwise sleep a while and try again to connect to server
|
||||||
if svr.cfg.LoginFailExit {
|
if lo.FromPtr(svr.cfg.LoginFailExit) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
util.RandomSleep(10*time.Second, 0.9, 1.1)
|
util.RandomSleep(5*time.Second, 0.9, 1.1)
|
||||||
} else {
|
} else {
|
||||||
// login success
|
// login success
|
||||||
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||||
@@ -149,18 +149,22 @@ func (svr *Service) Run() error {
|
|||||||
|
|
||||||
go svr.keepControllerWorking()
|
go svr.keepControllerWorking()
|
||||||
|
|
||||||
if svr.cfg.AdminPort != 0 {
|
if svr.cfg.WebServer.Port != 0 {
|
||||||
// Init admin server assets
|
// Init admin server assets
|
||||||
assets.Load(svr.cfg.AssetsDir)
|
assets.Load(svr.cfg.WebServer.AssetsDir)
|
||||||
|
|
||||||
address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort))
|
address := net.JoinHostPort(svr.cfg.WebServer.Addr, strconv.Itoa(svr.cfg.WebServer.Port))
|
||||||
err := svr.RunAdminServer(address)
|
err := svr.RunAdminServer(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("run admin server error: %v", err)
|
log.Warn("run admin server error: %v", err)
|
||||||
}
|
}
|
||||||
log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
|
log.Info("admin server listen on %s:%d", svr.cfg.WebServer.Addr, svr.cfg.WebServer.Port)
|
||||||
}
|
}
|
||||||
<-svr.ctx.Done()
|
<-svr.ctx.Done()
|
||||||
|
// service context may not be canceled by svr.Close(), we should call it here to release resources
|
||||||
|
if atomic.LoadUint32(&svr.exit) == 0 {
|
||||||
|
svr.Close()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +186,7 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// the first three retry with no delay
|
// the first three attempts with a low delay
|
||||||
if reconnectCounts > 3 {
|
if reconnectCounts > 3 {
|
||||||
util.RandomSleep(reconnectDelay, 0.9, 1.1)
|
util.RandomSleep(reconnectDelay, 0.9, 1.1)
|
||||||
xl.Info("wait %v to reconnect", reconnectDelay)
|
xl.Info("wait %v to reconnect", reconnectDelay)
|
||||||
@@ -238,7 +242,7 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
// session: if it's not nil, using tcp mux
|
// session: if it's not nil, using tcp mux
|
||||||
func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
|
func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
|
||||||
xl := xlog.FromContextSafe(svr.ctx)
|
xl := xlog.FromContextSafe(svr.ctx)
|
||||||
cm = NewConnectionManager(svr.ctx, &svr.cfg)
|
cm = NewConnectionManager(svr.ctx, svr.cfg)
|
||||||
|
|
||||||
if err = cm.OpenConnection(); err != nil {
|
if err = cm.OpenConnection(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -258,12 +262,12 @@ func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
|
|||||||
loginMsg := &msg.Login{
|
loginMsg := &msg.Login{
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
PoolCount: svr.cfg.PoolCount,
|
PoolCount: svr.cfg.Transport.PoolCount,
|
||||||
User: svr.cfg.User,
|
User: svr.cfg.User,
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
RunID: svr.runID,
|
RunID: svr.runID,
|
||||||
Metas: svr.cfg.Metas,
|
Metas: svr.cfg.Metadatas,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add auth
|
// Add auth
|
||||||
@@ -296,7 +300,7 @@ func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
|
func (svr *Service) ReloadConf(pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
|
||||||
svr.cfgMu.Lock()
|
svr.cfgMu.Lock()
|
||||||
svr.pxyCfgs = pxyCfgs
|
svr.pxyCfgs = pxyCfgs
|
||||||
svr.visitorCfgs = visitorCfgs
|
svr.visitorCfgs = visitorCfgs
|
||||||
@@ -322,21 +326,24 @@ func (svr *Service) GracefulClose(d time.Duration) {
|
|||||||
svr.ctlMu.RLock()
|
svr.ctlMu.RLock()
|
||||||
if svr.ctl != nil {
|
if svr.ctl != nil {
|
||||||
svr.ctl.GracefulClose(d)
|
svr.ctl.GracefulClose(d)
|
||||||
|
svr.ctl = nil
|
||||||
}
|
}
|
||||||
svr.ctlMu.RUnlock()
|
svr.ctlMu.RUnlock()
|
||||||
|
|
||||||
svr.cancel()
|
if svr.cancel != nil {
|
||||||
|
svr.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionManager struct {
|
type ConnectionManager struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cfg *config.ClientCommonConf
|
cfg *v1.ClientCommonConfig
|
||||||
|
|
||||||
muxSession *fmux.Session
|
muxSession *fmux.Session
|
||||||
quicConn quic.Connection
|
quicConn quic.Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnectionManager(ctx context.Context, cfg *config.ClientCommonConf) *ConnectionManager {
|
func NewConnectionManager(ctx context.Context, cfg *v1.ClientCommonConfig) *ConnectionManager {
|
||||||
return &ConnectionManager{
|
return &ConnectionManager{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
@@ -347,18 +354,18 @@ func (cm *ConnectionManager) OpenConnection() error {
|
|||||||
xl := xlog.FromContextSafe(cm.ctx)
|
xl := xlog.FromContextSafe(cm.ctx)
|
||||||
|
|
||||||
// special for quic
|
// special for quic
|
||||||
if strings.EqualFold(cm.cfg.Protocol, "quic") {
|
if strings.EqualFold(cm.cfg.Transport.Protocol, "quic") {
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
var err error
|
var err error
|
||||||
sn := cm.cfg.TLSServerName
|
sn := cm.cfg.Transport.TLS.ServerName
|
||||||
if sn == "" {
|
if sn == "" {
|
||||||
sn = cm.cfg.ServerAddr
|
sn = cm.cfg.ServerAddr
|
||||||
}
|
}
|
||||||
if cm.cfg.TLSEnable {
|
if lo.FromPtr(cm.cfg.Transport.TLS.Enable) {
|
||||||
tlsConfig, err = transport.NewClientTLSConfig(
|
tlsConfig, err = transport.NewClientTLSConfig(
|
||||||
cm.cfg.TLSCertFile,
|
cm.cfg.Transport.TLS.CertFile,
|
||||||
cm.cfg.TLSKeyFile,
|
cm.cfg.Transport.TLS.KeyFile,
|
||||||
cm.cfg.TLSTrustedCaFile,
|
cm.cfg.Transport.TLS.TrustedCaFile,
|
||||||
sn)
|
sn)
|
||||||
} else {
|
} else {
|
||||||
tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
|
tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
|
||||||
@@ -369,13 +376,13 @@ func (cm *ConnectionManager) OpenConnection() error {
|
|||||||
}
|
}
|
||||||
tlsConfig.NextProtos = []string{"frp"}
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
|
|
||||||
conn, err := quic.DialAddrContext(
|
conn, err := quic.DialAddr(
|
||||||
cm.ctx,
|
cm.ctx,
|
||||||
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
||||||
tlsConfig, &quic.Config{
|
tlsConfig, &quic.Config{
|
||||||
MaxIdleTimeout: time.Duration(cm.cfg.QUICMaxIdleTimeout) * time.Second,
|
MaxIdleTimeout: time.Duration(cm.cfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||||
MaxIncomingStreams: int64(cm.cfg.QUICMaxIncomingStreams),
|
MaxIncomingStreams: int64(cm.cfg.Transport.QUIC.MaxIncomingStreams),
|
||||||
KeepAlivePeriod: time.Duration(cm.cfg.QUICKeepalivePeriod) * time.Second,
|
KeepAlivePeriod: time.Duration(cm.cfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -384,7 +391,7 @@ func (cm *ConnectionManager) OpenConnection() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cm.cfg.TCPMux {
|
if !lo.FromPtr(cm.cfg.Transport.TCPMux) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +401,7 @@ func (cm *ConnectionManager) OpenConnection() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
fmuxCfg.KeepAliveInterval = time.Duration(cm.cfg.TCPMuxKeepaliveInterval) * time.Second
|
fmuxCfg.KeepAliveInterval = time.Duration(cm.cfg.Transport.TCPMuxKeepaliveInterval) * time.Second
|
||||||
fmuxCfg.LogOutput = io.Discard
|
fmuxCfg.LogOutput = io.Discard
|
||||||
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
||||||
session, err := fmux.Client(conn, fmuxCfg)
|
session, err := fmux.Client(conn, fmuxCfg)
|
||||||
@@ -427,16 +434,20 @@ func (cm *ConnectionManager) realConnect() (net.Conn, error) {
|
|||||||
xl := xlog.FromContextSafe(cm.ctx)
|
xl := xlog.FromContextSafe(cm.ctx)
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
var err error
|
var err error
|
||||||
if cm.cfg.TLSEnable {
|
tlsEnable := lo.FromPtr(cm.cfg.Transport.TLS.Enable)
|
||||||
sn := cm.cfg.TLSServerName
|
if cm.cfg.Transport.Protocol == "wss" {
|
||||||
|
tlsEnable = true
|
||||||
|
}
|
||||||
|
if tlsEnable {
|
||||||
|
sn := cm.cfg.Transport.TLS.ServerName
|
||||||
if sn == "" {
|
if sn == "" {
|
||||||
sn = cm.cfg.ServerAddr
|
sn = cm.cfg.ServerAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig, err = transport.NewClientTLSConfig(
|
tlsConfig, err = transport.NewClientTLSConfig(
|
||||||
cm.cfg.TLSCertFile,
|
cm.cfg.Transport.TLS.CertFile,
|
||||||
cm.cfg.TLSKeyFile,
|
cm.cfg.Transport.TLS.KeyFile,
|
||||||
cm.cfg.TLSTrustedCaFile,
|
cm.cfg.Transport.TLS.TrustedCaFile,
|
||||||
sn)
|
sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
xl.Warn("fail to build tls configuration, err: %v", err)
|
||||||
@@ -444,30 +455,42 @@ func (cm *ConnectionManager) realConnect() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyType, addr, auth, err := libdial.ParseProxyURL(cm.cfg.HTTPProxy)
|
proxyType, addr, auth, err := libdial.ParseProxyURL(cm.cfg.Transport.ProxyURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("fail to parse proxy url")
|
xl.Error("fail to parse proxy url")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dialOptions := []libdial.DialOption{}
|
dialOptions := []libdial.DialOption{}
|
||||||
protocol := cm.cfg.Protocol
|
protocol := cm.cfg.Transport.Protocol
|
||||||
if protocol == "websocket" {
|
switch protocol {
|
||||||
|
case "websocket":
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: utilnet.DialHookWebsocket()}))
|
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: utilnet.DialHookWebsocket(protocol, "")}))
|
||||||
|
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{
|
||||||
|
Hook: utilnet.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(cm.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
||||||
|
}))
|
||||||
|
dialOptions = append(dialOptions, libdial.WithTLSConfig(tlsConfig))
|
||||||
|
case "wss":
|
||||||
|
protocol = "tcp"
|
||||||
|
dialOptions = append(dialOptions, libdial.WithTLSConfigAndPriority(100, tlsConfig))
|
||||||
|
// Make sure that if it is wss, the websocket hook is executed after the tls hook.
|
||||||
|
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: utilnet.DialHookWebsocket(protocol, tlsConfig.ServerName), Priority: 110}))
|
||||||
|
default:
|
||||||
|
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{
|
||||||
|
Hook: utilnet.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(cm.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
||||||
|
}))
|
||||||
|
dialOptions = append(dialOptions, libdial.WithTLSConfig(tlsConfig))
|
||||||
}
|
}
|
||||||
if cm.cfg.ConnectServerLocalIP != "" {
|
|
||||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(cm.cfg.ConnectServerLocalIP))
|
if cm.cfg.Transport.ConnectServerLocalIP != "" {
|
||||||
|
dialOptions = append(dialOptions, libdial.WithLocalAddr(cm.cfg.Transport.ConnectServerLocalIP))
|
||||||
}
|
}
|
||||||
dialOptions = append(dialOptions,
|
dialOptions = append(dialOptions,
|
||||||
libdial.WithProtocol(protocol),
|
libdial.WithProtocol(protocol),
|
||||||
libdial.WithTimeout(time.Duration(cm.cfg.DialServerTimeout)*time.Second),
|
libdial.WithTimeout(time.Duration(cm.cfg.Transport.DialServerTimeout)*time.Second),
|
||||||
libdial.WithKeepAlive(time.Duration(cm.cfg.DialServerKeepAlive)*time.Second),
|
libdial.WithKeepAlive(time.Duration(cm.cfg.Transport.DialServerKeepAlive)*time.Second),
|
||||||
libdial.WithProxy(proxyType, addr),
|
libdial.WithProxy(proxyType, addr),
|
||||||
libdial.WithProxyAuth(auth),
|
libdial.WithProxyAuth(auth),
|
||||||
libdial.WithTLSConfig(tlsConfig),
|
|
||||||
libdial.WithAfterHook(libdial.AfterHook{
|
|
||||||
Hook: utilnet.DialHookCustomTLSHeadByte(tlsConfig != nil, cm.cfg.DisableCustomTLSFirstByte),
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
conn, err := libdial.DialContext(
|
conn, err := libdial.DialContext(
|
||||||
cm.ctx,
|
cm.ctx,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
|
|
||||||
libio "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
type STCPVisitor struct {
|
type STCPVisitor struct {
|
||||||
*BaseVisitor
|
*BaseVisitor
|
||||||
|
|
||||||
cfg *config.STCPVisitorConf
|
cfg *v1.STCPVisitorConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *STCPVisitor) Run() (err error) {
|
func (sv *STCPVisitor) Run() (err error) {
|
||||||
@@ -90,10 +90,10 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
RunID: sv.helper.RunID(),
|
RunID: sv.helper.RunID(),
|
||||||
ProxyName: sv.cfg.ServerName,
|
ProxyName: sv.cfg.ServerName,
|
||||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
|
||||||
Timestamp: now,
|
Timestamp: now,
|
||||||
UseEncryption: sv.cfg.UseEncryption,
|
UseEncryption: sv.cfg.Transport.UseEncryption,
|
||||||
UseCompression: sv.cfg.UseCompression,
|
UseCompression: sv.cfg.Transport.UseCompression,
|
||||||
}
|
}
|
||||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -117,16 +117,18 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
|
|
||||||
var remote io.ReadWriteCloser
|
var remote io.ReadWriteCloser
|
||||||
remote = visitorConn
|
remote = visitorConn
|
||||||
if sv.cfg.UseEncryption {
|
if sv.cfg.Transport.UseEncryption {
|
||||||
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.Sk))
|
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sv.cfg.UseCompression {
|
if sv.cfg.Transport.UseCompression {
|
||||||
remote = libio.WithCompression(remote)
|
var recycleFn func()
|
||||||
|
remote, recycleFn = libio.WithCompressionFromPool(remote)
|
||||||
|
defer recycleFn()
|
||||||
}
|
}
|
||||||
|
|
||||||
libio.Join(userConn, remote)
|
libio.Join(userConn, remote)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
libio "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/proto/udp"
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
@@ -42,7 +42,7 @@ type SUDPVisitor struct {
|
|||||||
readCh chan *msg.UDPPacket
|
readCh chan *msg.UDPPacket
|
||||||
sendCh chan *msg.UDPPacket
|
sendCh chan *msg.UDPPacket
|
||||||
|
|
||||||
cfg *config.SUDPVisitorConf
|
cfg *v1.SUDPVisitorConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// SUDP Run start listen a udp port
|
// SUDP Run start listen a udp port
|
||||||
@@ -208,10 +208,10 @@ func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
|||||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
RunID: sv.helper.RunID(),
|
RunID: sv.helper.RunID(),
|
||||||
ProxyName: sv.cfg.ServerName,
|
ProxyName: sv.cfg.ServerName,
|
||||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
|
||||||
Timestamp: now,
|
Timestamp: now,
|
||||||
UseEncryption: sv.cfg.UseEncryption,
|
UseEncryption: sv.cfg.Transport.UseEncryption,
|
||||||
UseCompression: sv.cfg.UseCompression,
|
UseCompression: sv.cfg.Transport.UseCompression,
|
||||||
}
|
}
|
||||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -232,14 +232,14 @@ func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
|||||||
|
|
||||||
var remote io.ReadWriteCloser
|
var remote io.ReadWriteCloser
|
||||||
remote = visitorConn
|
remote = visitorConn
|
||||||
if sv.cfg.UseEncryption {
|
if sv.cfg.Transport.UseEncryption {
|
||||||
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.Sk))
|
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sv.cfg.UseCompression {
|
if sv.cfg.Transport.UseCompression {
|
||||||
remote = libio.WithCompression(remote)
|
remote = libio.WithCompression(remote)
|
||||||
}
|
}
|
||||||
return utilnet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
return utilnet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
@@ -47,11 +47,11 @@ type Visitor interface {
|
|||||||
|
|
||||||
func NewVisitor(
|
func NewVisitor(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg config.VisitorConf,
|
cfg v1.VisitorConfigurer,
|
||||||
clientCfg config.ClientCommonConf,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
helper Helper,
|
helper Helper,
|
||||||
) (visitor Visitor) {
|
) (visitor Visitor) {
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().ProxyName)
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name)
|
||||||
baseVisitor := BaseVisitor{
|
baseVisitor := BaseVisitor{
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
helper: helper,
|
helper: helper,
|
||||||
@@ -59,18 +59,18 @@ func NewVisitor(
|
|||||||
internalLn: utilnet.NewInternalListener(),
|
internalLn: utilnet.NewInternalListener(),
|
||||||
}
|
}
|
||||||
switch cfg := cfg.(type) {
|
switch cfg := cfg.(type) {
|
||||||
case *config.STCPVisitorConf:
|
case *v1.STCPVisitorConfig:
|
||||||
visitor = &STCPVisitor{
|
visitor = &STCPVisitor{
|
||||||
BaseVisitor: &baseVisitor,
|
BaseVisitor: &baseVisitor,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.XTCPVisitorConf:
|
case *v1.XTCPVisitorConfig:
|
||||||
visitor = &XTCPVisitor{
|
visitor = &XTCPVisitor{
|
||||||
BaseVisitor: &baseVisitor,
|
BaseVisitor: &baseVisitor,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
startTunnelCh: make(chan struct{}),
|
startTunnelCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
case *config.SUDPVisitorConf:
|
case *v1.SUDPVisitorConfig:
|
||||||
visitor = &SUDPVisitor{
|
visitor = &SUDPVisitor{
|
||||||
BaseVisitor: &baseVisitor,
|
BaseVisitor: &baseVisitor,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
@@ -81,7 +81,7 @@ func NewVisitor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BaseVisitor struct {
|
type BaseVisitor struct {
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg *v1.ClientCommonConfig
|
||||||
helper Helper
|
helper Helper
|
||||||
l net.Listener
|
l net.Listener
|
||||||
internalLn *utilnet.InternalListener
|
internalLn *utilnet.InternalListener
|
||||||
|
|||||||
@@ -22,14 +22,16 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg *v1.ClientCommonConfig
|
||||||
cfgs map[string]config.VisitorConf
|
cfgs map[string]v1.VisitorConfigurer
|
||||||
visitors map[string]Visitor
|
visitors map[string]Visitor
|
||||||
helper Helper
|
helper Helper
|
||||||
|
|
||||||
@@ -44,13 +46,13 @@ type Manager struct {
|
|||||||
func NewManager(
|
func NewManager(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
runID string,
|
runID string,
|
||||||
clientCfg config.ClientCommonConf,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
connectServer func() (net.Conn, error),
|
connectServer func() (net.Conn, error),
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
) *Manager {
|
) *Manager {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
cfgs: make(map[string]config.VisitorConf),
|
cfgs: make(map[string]v1.VisitorConfigurer),
|
||||||
visitors: make(map[string]Visitor),
|
visitors: make(map[string]Visitor),
|
||||||
checkInterval: 10 * time.Second,
|
checkInterval: 10 * time.Second,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -79,7 +81,7 @@ func (vm *Manager) Run() {
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
for _, cfg := range vm.cfgs {
|
for _, cfg := range vm.cfgs {
|
||||||
name := cfg.GetBaseConfig().ProxyName
|
name := cfg.GetBaseConfig().Name
|
||||||
if _, exist := vm.visitors[name]; !exist {
|
if _, exist := vm.visitors[name]; !exist {
|
||||||
xl.Info("try to start visitor [%s]", name)
|
xl.Info("try to start visitor [%s]", name)
|
||||||
_ = vm.startVisitor(cfg)
|
_ = vm.startVisitor(cfg)
|
||||||
@@ -104,9 +106,9 @@ func (vm *Manager) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hold lock before calling this function.
|
// Hold lock before calling this function.
|
||||||
func (vm *Manager) startVisitor(cfg config.VisitorConf) (err error) {
|
func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
name := cfg.GetBaseConfig().ProxyName
|
name := cfg.GetBaseConfig().Name
|
||||||
visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
|
visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
|
||||||
err = visitor.Run()
|
err = visitor.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -118,15 +120,18 @@ func (vm *Manager) startVisitor(cfg config.VisitorConf) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *Manager) Reload(cfgs map[string]config.VisitorConf) {
|
func (vm *Manager) Reload(cfgs []v1.VisitorConfigurer) {
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
|
cfgsMap := lo.KeyBy(cfgs, func(c v1.VisitorConfigurer) string {
|
||||||
|
return c.GetBaseConfig().Name
|
||||||
|
})
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
defer vm.mu.Unlock()
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
delNames := make([]string, 0)
|
delNames := make([]string, 0)
|
||||||
for name, oldCfg := range vm.cfgs {
|
for name, oldCfg := range vm.cfgs {
|
||||||
del := false
|
del := false
|
||||||
cfg, ok := cfgs[name]
|
cfg, ok := cfgsMap[name]
|
||||||
if !ok || !reflect.DeepEqual(oldCfg, cfg) {
|
if !ok || !reflect.DeepEqual(oldCfg, cfg) {
|
||||||
del = true
|
del = true
|
||||||
}
|
}
|
||||||
@@ -145,7 +150,8 @@ func (vm *Manager) Reload(cfgs map[string]config.VisitorConf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addNames := make([]string, 0)
|
addNames := make([]string, 0)
|
||||||
for name, cfg := range cfgs {
|
for _, cfg := range cfgs {
|
||||||
|
name := cfg.GetBaseConfig().Name
|
||||||
if _, ok := vm.cfgs[name]; !ok {
|
if _, ok := vm.cfgs[name]; !ok {
|
||||||
vm.cfgs[name] = cfg
|
vm.cfgs[name] = cfg
|
||||||
addNames = append(addNames, name)
|
addNames = append(addNames, name)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import (
|
|||||||
quic "github.com/quic-go/quic-go"
|
quic "github.com/quic-go/quic-go"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/nathole"
|
"github.com/fatedier/frp/pkg/nathole"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
@@ -47,7 +47,7 @@ type XTCPVisitor struct {
|
|||||||
retryLimiter *rate.Limiter
|
retryLimiter *rate.Limiter
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
cfg *config.XTCPVisitorConf
|
cfg *v1.XTCPVisitorConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *XTCPVisitor) Run() (err error) {
|
func (sv *XTCPVisitor) Run() (err error) {
|
||||||
@@ -56,7 +56,7 @@ func (sv *XTCPVisitor) Run() (err error) {
|
|||||||
if sv.cfg.Protocol == "kcp" {
|
if sv.cfg.Protocol == "kcp" {
|
||||||
sv.session = NewKCPTunnelSession()
|
sv.session = NewKCPTunnelSession()
|
||||||
} else {
|
} else {
|
||||||
sv.session = NewQUICTunnelSession(&sv.clientCfg)
|
sv.session = NewQUICTunnelSession(sv.clientCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sv.cfg.BindPort > 0 {
|
if sv.cfg.BindPort > 0 {
|
||||||
@@ -192,15 +192,17 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var muxConnRWCloser io.ReadWriteCloser = tunnelConn
|
var muxConnRWCloser io.ReadWriteCloser = tunnelConn
|
||||||
if sv.cfg.UseEncryption {
|
if sv.cfg.Transport.UseEncryption {
|
||||||
muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
|
muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.SecretKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sv.cfg.UseCompression {
|
if sv.cfg.Transport.UseCompression {
|
||||||
muxConnRWCloser = libio.WithCompression(muxConnRWCloser)
|
var recycleFn func()
|
||||||
|
muxConnRWCloser, recycleFn = libio.WithCompressionFromPool(muxConnRWCloser)
|
||||||
|
defer recycleFn()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, errs := libio.Join(userConn, muxConnRWCloser)
|
_, _, errs := libio.Join(userConn, muxConnRWCloser)
|
||||||
@@ -290,7 +292,7 @@ func (sv *XTCPVisitor) makeNatHole() {
|
|||||||
TransactionID: transactionID,
|
TransactionID: transactionID,
|
||||||
ProxyName: sv.cfg.ServerName,
|
ProxyName: sv.cfg.ServerName,
|
||||||
Protocol: sv.cfg.Protocol,
|
Protocol: sv.cfg.Protocol,
|
||||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
SignKey: util.GetAuthKey(sv.cfg.SecretKey, now),
|
||||||
Timestamp: now,
|
Timestamp: now,
|
||||||
MappedAddrs: prepareResult.Addrs,
|
MappedAddrs: prepareResult.Addrs,
|
||||||
AssistedAddrs: prepareResult.AssistedAddrs,
|
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||||
@@ -308,7 +310,7 @@ func (sv *XTCPVisitor) makeNatHole() {
|
|||||||
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||||
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||||
|
|
||||||
newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.Sk))
|
newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.SecretKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
listenConn.Close()
|
listenConn.Close()
|
||||||
xl.Warn("make hole error: %v", err)
|
xl.Warn("make hole error: %v", err)
|
||||||
@@ -368,7 +370,7 @@ func (ks *KCPTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks *KCPTunnelSession) OpenConn(ctx context.Context) (net.Conn, error) {
|
func (ks *KCPTunnelSession) OpenConn(_ context.Context) (net.Conn, error) {
|
||||||
ks.mu.RLock()
|
ks.mu.RLock()
|
||||||
defer ks.mu.RUnlock()
|
defer ks.mu.RUnlock()
|
||||||
session := ks.session
|
session := ks.session
|
||||||
@@ -396,10 +398,10 @@ type QUICTunnelSession struct {
|
|||||||
listenConn *net.UDPConn
|
listenConn *net.UDPConn
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
clientCfg *config.ClientCommonConf
|
clientCfg *v1.ClientCommonConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQUICTunnelSession(clientCfg *config.ClientCommonConf) TunnelSession {
|
func NewQUICTunnelSession(clientCfg *v1.ClientCommonConfig) TunnelSession {
|
||||||
return &QUICTunnelSession{
|
return &QUICTunnelSession{
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
}
|
}
|
||||||
@@ -411,11 +413,11 @@ func (qs *QUICTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) e
|
|||||||
return fmt.Errorf("create tls config error: %v", err)
|
return fmt.Errorf("create tls config error: %v", err)
|
||||||
}
|
}
|
||||||
tlsConfig.NextProtos = []string{"frp"}
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
quicConn, err := quic.Dial(listenConn, raddr, raddr.String(), tlsConfig,
|
quicConn, err := quic.Dial(context.Background(), listenConn, raddr, tlsConfig,
|
||||||
&quic.Config{
|
&quic.Config{
|
||||||
MaxIdleTimeout: time.Duration(qs.clientCfg.QUICMaxIdleTimeout) * time.Second,
|
MaxIdleTimeout: time.Duration(qs.clientCfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||||
MaxIncomingStreams: int64(qs.clientCfg.QUICMaxIncomingStreams),
|
MaxIncomingStreams: int64(qs.clientCfg.Transport.QUIC.MaxIncomingStreams),
|
||||||
KeepAlivePeriod: time.Duration(qs.clientCfg.QUICKeepalivePeriod) * time.Second,
|
KeepAlivePeriod: time.Duration(qs.clientCfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dial quic error: %v", err)
|
return fmt.Errorf("dial quic error: %v", err)
|
||||||
|
|||||||
117
cmd/frpc/sub/admin.go
Normal file
117
cmd/frpc/sub/admin.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// Copyright 2023 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 sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rodaine/table"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
clientsdk "github.com/fatedier/frp/pkg/sdk/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(NewAdminCommand(
|
||||||
|
"reload",
|
||||||
|
"Hot-Reload frpc configuration",
|
||||||
|
ReloadHandler,
|
||||||
|
))
|
||||||
|
|
||||||
|
rootCmd.AddCommand(NewAdminCommand(
|
||||||
|
"status",
|
||||||
|
"Overview of all proxies status",
|
||||||
|
StatusHandler,
|
||||||
|
))
|
||||||
|
|
||||||
|
rootCmd.AddCommand(NewAdminCommand(
|
||||||
|
"stop",
|
||||||
|
"Stop the running frpc",
|
||||||
|
StopHandler,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: name,
|
||||||
|
Short: short,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
cfg, _, _, _, err := config.LoadClientConfig(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if cfg.WebServer.Port <= 0 {
|
||||||
|
fmt.Println("web server port should be set if you want to use this feature")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := handler(cfg); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
|
if err := client.Reload(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("reload success")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
|
res, err := client.GetAllProxyStatus()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Proxy Status...\n\n")
|
||||||
|
for _, typ := range proxyTypes {
|
||||||
|
arrs := res[string(typ)]
|
||||||
|
if len(arrs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(strings.ToUpper(string(typ)))
|
||||||
|
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||||
|
for _, ps := range arrs {
|
||||||
|
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||||
|
}
|
||||||
|
tbl.Print()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
|
if err := client.Stop(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("stop success")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
125
cmd/frpc/sub/flags.go
Normal file
125
cmd/frpc/sub/flags.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
// Copyright 2023 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 sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BandwidthQuantityFlag struct {
|
||||||
|
V *types.BandwidthQuantity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *BandwidthQuantityFlag) Set(s string) error {
|
||||||
|
return f.V.UnmarshalString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *BandwidthQuantityFlag) String() string {
|
||||||
|
return f.V.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *BandwidthQuantityFlag) Type() string {
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterProxyFlags(cmd *cobra.Command, c v1.ProxyConfigurer) {
|
||||||
|
registerProxyBaseConfigFlags(cmd, c.GetBaseConfig())
|
||||||
|
switch cc := c.(type) {
|
||||||
|
case *v1.TCPProxyConfig:
|
||||||
|
cmd.Flags().IntVarP(&cc.RemotePort, "remote_port", "r", 0, "remote port")
|
||||||
|
case *v1.UDPProxyConfig:
|
||||||
|
cmd.Flags().IntVarP(&cc.RemotePort, "remote_port", "r", 0, "remote port")
|
||||||
|
case *v1.HTTPProxyConfig:
|
||||||
|
registerProxyDomainConfigFlags(cmd, &cc.DomainConfig)
|
||||||
|
cmd.Flags().StringSliceVarP(&cc.Locations, "locations", "", []string{}, "locations")
|
||||||
|
cmd.Flags().StringVarP(&cc.HTTPUser, "http_user", "", "", "http auth user")
|
||||||
|
cmd.Flags().StringVarP(&cc.HTTPPassword, "http_pwd", "", "", "http auth password")
|
||||||
|
cmd.Flags().StringVarP(&cc.HostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
|
||||||
|
case *v1.HTTPSProxyConfig:
|
||||||
|
registerProxyDomainConfigFlags(cmd, &cc.DomainConfig)
|
||||||
|
case *v1.TCPMuxProxyConfig:
|
||||||
|
registerProxyDomainConfigFlags(cmd, &cc.DomainConfig)
|
||||||
|
cmd.Flags().StringVarP(&cc.Multiplexer, "mux", "", "", "multiplexer")
|
||||||
|
case *v1.STCPProxyConfig:
|
||||||
|
cmd.Flags().StringVarP(&cc.Secretkey, "sk", "", "", "secret key")
|
||||||
|
case *v1.SUDPProxyConfig:
|
||||||
|
cmd.Flags().StringVarP(&cc.Secretkey, "sk", "", "", "secret key")
|
||||||
|
case *v1.XTCPProxyConfig:
|
||||||
|
cmd.Flags().StringVarP(&cc.Secretkey, "sk", "", "", "secret key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerProxyBaseConfigFlags(cmd *cobra.Command, c *v1.ProxyBaseConfig) {
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVarP(&c.Name, "proxy_name", "n", "", "proxy name")
|
||||||
|
cmd.Flags().StringVarP(&c.LocalIP, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
|
cmd.Flags().IntVarP(&c.LocalPort, "local_port", "l", 0, "local port")
|
||||||
|
cmd.Flags().BoolVarP(&c.Transport.UseEncryption, "ue", "", false, "use encryption")
|
||||||
|
cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression")
|
||||||
|
cmd.Flags().StringVarP(&c.Transport.BandwidthLimitMode, "bandwidth_limit_mode", "", types.BandwidthLimitModeClient, "bandwidth limit mode")
|
||||||
|
cmd.Flags().VarP(&BandwidthQuantityFlag{V: &c.Transport.BandwidthLimit}, "bandwidth_limit", "", "bandwidth limit (e.g. 100KB or 1MB)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerProxyDomainConfigFlags(cmd *cobra.Command, c *v1.DomainConfig) {
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd.Flags().StringSliceVarP(&c.CustomDomains, "custom_domain", "d", []string{}, "custom domains")
|
||||||
|
cmd.Flags().StringVarP(&c.SubDomain, "sd", "", "", "sub domain")
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterVisitorFlags(cmd *cobra.Command, c v1.VisitorConfigurer) {
|
||||||
|
registerVisitorBaseConfigFlags(cmd, c.GetBaseConfig())
|
||||||
|
|
||||||
|
// add visitor flags if exist
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerVisitorBaseConfigFlags(cmd *cobra.Command, c *v1.VisitorBaseConfig) {
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVarP(&c.Name, "visitor_name", "n", "", "visitor name")
|
||||||
|
cmd.Flags().BoolVarP(&c.Transport.UseEncryption, "ue", "", false, "use encryption")
|
||||||
|
cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression")
|
||||||
|
cmd.Flags().StringVarP(&c.SecretKey, "sk", "", "", "secret key")
|
||||||
|
cmd.Flags().StringVarP(&c.ServerName, "server_name", "", "", "server name")
|
||||||
|
cmd.Flags().StringVarP(&c.BindAddr, "bind_addr", "", "", "bind addr")
|
||||||
|
cmd.Flags().IntVarP(&c.BindPort, "bind_port", "", 0, "bind port")
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterClientCommonConfigFlags(cmd *cobra.Command, c *v1.ClientCommonConfig) {
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.ServerAddr, "server_addr", "s", "127.0.0.1", "frp server's address")
|
||||||
|
cmd.PersistentFlags().IntVarP(&c.ServerPort, "server_port", "P", 7000, "frp server's port")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.User, "user", "u", "", "user")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.Transport.Protocol, "protocol", "p", "tcp",
|
||||||
|
fmt.Sprintf("optional values are %v", validation.SupportedTransportProtocols))
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.Auth.Token, "token", "t", "", "auth token")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.Log.Level, "log_level", "", "info", "log level")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.Log.To, "log_file", "", "console", "console or file path")
|
||||||
|
cmd.PersistentFlags().Int64VarP(&c.Log.MaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
cmd.PersistentFlags().BoolVarP(&c.Log.DisablePrintColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.Transport.TLS.ServerName, "tls_server_name", "", "", "specify the custom server name of tls certificate")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.DNSServer, "dns_server", "", "", "specify dns server instead of using system default one")
|
||||||
|
|
||||||
|
c.Transport.TLS.Enable = cmd.PersistentFlags().BoolP("tls_enable", "", true, "enable frpc tls")
|
||||||
|
}
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(httpCmd)
|
|
||||||
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
httpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&locations, "locations", "", "", "locations")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&httpUser, "http_user", "", "", "http auth user")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&httpPwd, "http_pwd", "", "", "http auth password")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
|
|
||||||
httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
|
||||||
httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
|
||||||
httpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(httpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpCmd = &cobra.Command{
|
|
||||||
Use: "http",
|
|
||||||
Short: "Run frpc with a single http proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.HTTPProxyConf{}
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.HTTPProxy
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
|
||||||
cfg.SubDomain = subDomain
|
|
||||||
cfg.Locations = strings.Split(locations, ",")
|
|
||||||
cfg.HTTPUser = httpUser
|
|
||||||
cfg.HTTPPwd = httpPwd
|
|
||||||
cfg.HostHeaderRewrite = hostHeaderRewrite
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
|
||||||
|
|
||||||
err = cfg.ValidateForClient()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := map[string]config.ProxyConf{
|
|
||||||
cfg.ProxyName: cfg,
|
|
||||||
}
|
|
||||||
err = startService(clientCfg, proxyConfs, nil, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(httpsCmd)
|
|
||||||
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
|
||||||
httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
|
||||||
httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
|
||||||
httpsCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(httpsCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpsCmd = &cobra.Command{
|
|
||||||
Use: "https",
|
|
||||||
Short: "Run frpc with a single https proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.HTTPSProxyConf{}
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.HTTPSProxy
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
|
||||||
cfg.SubDomain = subDomain
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
|
||||||
|
|
||||||
err = cfg.ValidateForClient()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := map[string]config.ProxyConf{
|
|
||||||
cfg.ProxyName: cfg,
|
|
||||||
}
|
|
||||||
err = startService(clientCfg, proxyConfs, nil, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/nathole"
|
"github.com/fatedier/frp/pkg/nathole"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,8 +31,6 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterCommonFlags(natholeCmd)
|
|
||||||
|
|
||||||
rootCmd.AddCommand(natholeCmd)
|
rootCmd.AddCommand(natholeCmd)
|
||||||
natholeCmd.AddCommand(natholeDiscoveryCmd)
|
natholeCmd.AddCommand(natholeDiscoveryCmd)
|
||||||
|
|
||||||
@@ -49,9 +48,9 @@ var natholeDiscoveryCmd = &cobra.Command{
|
|||||||
Short: "Discover nathole information from stun server",
|
Short: "Discover nathole information from stun server",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// ignore error here, because we can use command line pameters
|
// ignore error here, because we can use command line pameters
|
||||||
cfg, _, _, err := config.ParseClientConfig(cfgFile)
|
cfg, _, _, _, err := config.LoadClientConfig(cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cfg = config.GetDefaultClientConf()
|
cfg = &v1.ClientCommonConfig{}
|
||||||
}
|
}
|
||||||
if natHoleSTUNServer != "" {
|
if natHoleSTUNServer != "" {
|
||||||
cfg.NatHoleSTUNServer = natHoleSTUNServer
|
cfg.NatHoleSTUNServer = natHoleSTUNServer
|
||||||
@@ -89,7 +88,7 @@ var natholeDiscoveryCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateForNatHoleDiscovery(cfg config.ClientCommonConf) error {
|
func validateForNatHoleDiscovery(cfg *v1.ClientCommonConfig) error {
|
||||||
if cfg.NatHoleSTUNServer == "" {
|
if cfg.NatHoleSTUNServer == "" {
|
||||||
return fmt.Errorf("nat_hole_stun_server can not be empty")
|
return fmt.Errorf("nat_hole_stun_server can not be empty")
|
||||||
}
|
}
|
||||||
|
|||||||
120
cmd/frpc/sub/proxy.go
Normal file
120
cmd/frpc/sub/proxy.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2023 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 sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
var proxyTypes = []v1.ProxyType{
|
||||||
|
v1.ProxyTypeTCP,
|
||||||
|
v1.ProxyTypeUDP,
|
||||||
|
v1.ProxyTypeTCPMUX,
|
||||||
|
v1.ProxyTypeHTTP,
|
||||||
|
v1.ProxyTypeHTTPS,
|
||||||
|
v1.ProxyTypeSTCP,
|
||||||
|
v1.ProxyTypeSUDP,
|
||||||
|
v1.ProxyTypeXTCP,
|
||||||
|
}
|
||||||
|
|
||||||
|
var visitorTypes = []v1.VisitorType{
|
||||||
|
v1.VisitorTypeSTCP,
|
||||||
|
v1.VisitorTypeSUDP,
|
||||||
|
v1.VisitorTypeXTCP,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for _, typ := range proxyTypes {
|
||||||
|
c := v1.NewProxyConfigurerByType(typ)
|
||||||
|
if c == nil {
|
||||||
|
panic("proxy type: " + typ + " not support")
|
||||||
|
}
|
||||||
|
clientCfg := v1.ClientCommonConfig{}
|
||||||
|
cmd := NewProxyCommand(string(typ), c, &clientCfg)
|
||||||
|
RegisterClientCommonConfigFlags(cmd, &clientCfg)
|
||||||
|
RegisterProxyFlags(cmd, c)
|
||||||
|
|
||||||
|
// add sub command for visitor
|
||||||
|
if lo.Contains(visitorTypes, v1.VisitorType(typ)) {
|
||||||
|
vc := v1.NewVisitorConfigurerByType(v1.VisitorType(typ))
|
||||||
|
if vc == nil {
|
||||||
|
panic("visitor type: " + typ + " not support")
|
||||||
|
}
|
||||||
|
visitorCmd := NewVisitorCommand(string(typ), vc, &clientCfg)
|
||||||
|
RegisterVisitorFlags(visitorCmd, vc)
|
||||||
|
cmd.AddCommand(visitorCmd)
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.ClientCommonConfig) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: name,
|
||||||
|
Short: fmt.Sprintf("Run frpc with a single %s proxy", name),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
clientCfg.Complete()
|
||||||
|
if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Complete(clientCfg.User)
|
||||||
|
c.GetBaseConfig().Type = name
|
||||||
|
if err := validation.ValidateProxyConfigurerForClient(c); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err := startService(clientCfg, []v1.ProxyConfigurer{c}, nil, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitorCommand(name string, c v1.VisitorConfigurer, clientCfg *v1.ClientCommonConfig) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "visitor",
|
||||||
|
Short: fmt.Sprintf("Run frpc with a single %s visitor", name),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
clientCfg.Complete()
|
||||||
|
if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Complete(clientCfg)
|
||||||
|
c.GetBaseConfig().Type = name
|
||||||
|
if err := validation.ValidateVisitorConfigurer(c); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err := startService(clientCfg, nil, []v1.VisitorConfigurer{c}, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(reloadCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var reloadCmd = &cobra.Command{
|
|
||||||
Use: "reload",
|
|
||||||
Short: "Hot-Reload frpc configuration",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
cfg, _, _, err := config.ParseClientConfig(cfgFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = reload(cfg)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("frpc reload error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("reload success\n")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func reload(clientCfg config.ClientCommonConf) error {
|
|
||||||
if clientCfg.AdminPort == 0 {
|
|
||||||
return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://"+
|
|
||||||
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
|
||||||
clientCfg.AdminPwd))
|
|
||||||
|
|
||||||
req.Header.Add("Authorization", authStr)
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode == 200 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
|
||||||
}
|
|
||||||
@@ -15,13 +15,12 @@
|
|||||||
package sub
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -29,54 +28,17 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
"github.com/fatedier/frp/client"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
CfgFileTypeIni = iota
|
|
||||||
CfgFileTypeCmd
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfgFile string
|
cfgFile string
|
||||||
cfgDir string
|
cfgDir string
|
||||||
showVersion bool
|
showVersion bool
|
||||||
|
|
||||||
serverAddr string
|
|
||||||
user string
|
|
||||||
protocol string
|
|
||||||
token string
|
|
||||||
logLevel string
|
|
||||||
logFile string
|
|
||||||
logMaxDays int
|
|
||||||
disableLogColor bool
|
|
||||||
dnsServer string
|
|
||||||
|
|
||||||
proxyName string
|
|
||||||
localIP string
|
|
||||||
localPort int
|
|
||||||
remotePort int
|
|
||||||
useEncryption bool
|
|
||||||
useCompression bool
|
|
||||||
bandwidthLimit string
|
|
||||||
bandwidthLimitMode string
|
|
||||||
customDomains string
|
|
||||||
subDomain string
|
|
||||||
httpUser string
|
|
||||||
httpPwd string
|
|
||||||
locations string
|
|
||||||
hostHeaderRewrite string
|
|
||||||
role string
|
|
||||||
sk string
|
|
||||||
multiplexer string
|
|
||||||
serverName string
|
|
||||||
bindAddr string
|
|
||||||
bindPort int
|
|
||||||
|
|
||||||
tlsEnable bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -85,19 +47,6 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterCommonFlags(cmd *cobra.Command) {
|
|
||||||
cmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
|
||||||
cmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
|
||||||
cmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
|
||||||
cmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
|
||||||
cmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
|
||||||
cmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
|
||||||
cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
|
||||||
cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
|
||||||
cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", true, "enable frpc tls")
|
|
||||||
cmd.PersistentFlags().StringVarP(&dnsServer, "dns_server", "", "", "specify dns server instead of using system default one")
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "frpc",
|
Use: "frpc",
|
||||||
Short: "frpc is the client of frp (https://github.com/fatedier/frp)",
|
Short: "frpc is the client of frp (https://github.com/fatedier/frp)",
|
||||||
@@ -117,6 +66,7 @@ var rootCmd = &cobra.Command{
|
|||||||
// Do not show command usage here.
|
// Do not show command usage here.
|
||||||
err := runClient(cfgFile)
|
err := runClient(cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -150,89 +100,56 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSignal(svr *client.Service, doneCh chan struct{}) {
|
func handleTermSignal(svr *client.Service) {
|
||||||
ch := make(chan os.Signal, 1)
|
ch := make(chan os.Signal, 1)
|
||||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-ch
|
<-ch
|
||||||
svr.GracefulClose(500 * time.Millisecond)
|
svr.GracefulClose(500 * time.Millisecond)
|
||||||
close(doneCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
|
||||||
cfg = config.GetDefaultClientConf()
|
|
||||||
|
|
||||||
ipStr, portStr, err := net.SplitHostPort(serverAddr)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("invalid server_addr: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.ServerAddr = ipStr
|
|
||||||
cfg.ServerPort, err = strconv.Atoi(portStr)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("invalid server_addr: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.User = user
|
|
||||||
cfg.Protocol = protocol
|
|
||||||
cfg.LogLevel = logLevel
|
|
||||||
cfg.LogFile = logFile
|
|
||||||
cfg.LogMaxDays = int64(logMaxDays)
|
|
||||||
cfg.DisableLogColor = disableLogColor
|
|
||||||
cfg.DNSServer = dnsServer
|
|
||||||
|
|
||||||
// Only token authentication is supported in cmd mode
|
|
||||||
cfg.ClientConfig = auth.GetDefaultClientConf()
|
|
||||||
cfg.Token = token
|
|
||||||
cfg.TLSEnable = tlsEnable
|
|
||||||
|
|
||||||
cfg.Complete()
|
|
||||||
if err = cfg.Validate(); err != nil {
|
|
||||||
err = fmt.Errorf("parse config error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runClient(cfgFilePath string) error {
|
func runClient(cfgFilePath string) error {
|
||||||
cfg, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(cfgFilePath)
|
cfg, pxyCfgs, visitorCfgs, isLegacyFormat, err := config.LoadClientConfig(cfgFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if isLegacyFormat {
|
||||||
|
fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " +
|
||||||
|
"please use yaml/json/toml format instead!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
warning, err := validation.ValidateAllClientConfig(cfg, pxyCfgs, visitorCfgs)
|
||||||
|
if warning != nil {
|
||||||
|
fmt.Printf("WARNING: %v\n", warning)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startService(
|
func startService(
|
||||||
cfg config.ClientCommonConf,
|
cfg *v1.ClientCommonConfig,
|
||||||
pxyCfgs map[string]config.ProxyConf,
|
pxyCfgs []v1.ProxyConfigurer,
|
||||||
visitorCfgs map[string]config.VisitorConf,
|
visitorCfgs []v1.VisitorConfigurer,
|
||||||
cfgFile string,
|
cfgFile string,
|
||||||
) (err error) {
|
) error {
|
||||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
log.InitLog(cfg.Log.To, cfg.Log.Level, cfg.Log.MaxDays, cfg.Log.DisablePrintColor)
|
||||||
cfg.LogMaxDays, cfg.DisableLogColor)
|
|
||||||
|
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
log.Info("start frpc service for config file [%s]", cfgFile)
|
log.Info("start frpc service for config file [%s]", cfgFile)
|
||||||
defer log.Info("frpc service for config file [%s] stopped", cfgFile)
|
defer log.Info("frpc service for config file [%s] stopped", cfgFile)
|
||||||
}
|
}
|
||||||
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
svr, err := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||||
if errRet != nil {
|
if err != nil {
|
||||||
err = errRet
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closedDoneCh := make(chan struct{})
|
shouldGracefulClose := cfg.Transport.Protocol == "kcp" || cfg.Transport.Protocol == "quic"
|
||||||
shouldGracefulClose := cfg.Protocol == "kcp" || cfg.Protocol == "quic"
|
|
||||||
// Capture the exit signal if we use kcp or quic.
|
// Capture the exit signal if we use kcp or quic.
|
||||||
if shouldGracefulClose {
|
if shouldGracefulClose {
|
||||||
go handleSignal(svr, closedDoneCh)
|
go handleTermSignal(svr)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = svr.Run()
|
_ = svr.Run(context.Background())
|
||||||
if err == nil && shouldGracefulClose {
|
return nil
|
||||||
<-closedDoneCh
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rodaine/table"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(statusCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusCmd = &cobra.Command{
|
|
||||||
Use: "status",
|
|
||||||
Short: "Overview of all proxies status",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
cfg, _, _, err := config.ParseClientConfig(cfgFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = status(cfg); err != nil {
|
|
||||||
fmt.Printf("frpc get status error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func status(clientCfg config.ClientCommonConf) error {
|
|
||||||
if clientCfg.AdminPort == 0 {
|
|
||||||
return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://"+
|
|
||||||
clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
|
|
||||||
clientCfg.AdminPwd))
|
|
||||||
|
|
||||||
req.Header.Add("Authorization", authStr)
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res := make(client.StatusResp)
|
|
||||||
err = json.Unmarshal(body, &res)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Proxy Status...")
|
|
||||||
types := []string{"tcp", "udp", "tcpmux", "http", "https", "stcp", "sudp", "xtcp"}
|
|
||||||
for _, pxyType := range types {
|
|
||||||
arrs := res[pxyType]
|
|
||||||
if len(arrs) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(strings.ToUpper(pxyType))
|
|
||||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
|
||||||
for _, ps := range arrs {
|
|
||||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
|
||||||
}
|
|
||||||
tbl.Print()
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(stcpCmd)
|
|
||||||
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
stcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
|
||||||
stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
|
||||||
stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
|
||||||
stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
|
||||||
stcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(stcpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var stcpCmd = &cobra.Command{
|
|
||||||
Use: "stcp",
|
|
||||||
Short: "Run frpc with a single stcp proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := make(map[string]config.ProxyConf)
|
|
||||||
visitorConfs := make(map[string]config.VisitorConf)
|
|
||||||
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
switch role {
|
|
||||||
case "server":
|
|
||||||
cfg := &config.STCPProxyConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.STCPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
|
||||||
err = cfg.ValidateForClient()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
proxyConfs[cfg.ProxyName] = cfg
|
|
||||||
case "visitor":
|
|
||||||
cfg := &config.STCPVisitorConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.STCPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.ServerName = serverName
|
|
||||||
cfg.BindAddr = bindAddr
|
|
||||||
cfg.BindPort = bindPort
|
|
||||||
err = cfg.Validate()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
visitorConfs[cfg.ProxyName] = cfg
|
|
||||||
default:
|
|
||||||
fmt.Println("invalid role")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(sudpCmd)
|
|
||||||
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
sudpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
|
||||||
sudpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
|
||||||
sudpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
|
||||||
sudpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
|
||||||
sudpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(sudpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sudpCmd = &cobra.Command{
|
|
||||||
Use: "sudp",
|
|
||||||
Short: "Run frpc with a single sudp proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := make(map[string]config.ProxyConf)
|
|
||||||
visitorConfs := make(map[string]config.VisitorConf)
|
|
||||||
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
switch role {
|
|
||||||
case "server":
|
|
||||||
cfg := &config.SUDPProxyConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.SUDPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
|
||||||
err = cfg.ValidateForClient()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
proxyConfs[cfg.ProxyName] = cfg
|
|
||||||
case "visitor":
|
|
||||||
cfg := &config.SUDPVisitorConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.SUDPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.ServerName = serverName
|
|
||||||
cfg.BindAddr = bindAddr
|
|
||||||
cfg.BindPort = bindPort
|
|
||||||
err = cfg.Validate()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
visitorConfs[cfg.ProxyName] = cfg
|
|
||||||
default:
|
|
||||||
fmt.Println("invalid role")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
|
||||||
if err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(tcpCmd)
|
|
||||||
|
|
||||||
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
tcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
|
|
||||||
tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
|
||||||
tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
|
||||||
tcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
|
||||||
tcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(tcpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tcpCmd = &cobra.Command{
|
|
||||||
Use: "tcp",
|
|
||||||
Short: "Run frpc with a single tcp proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.TCPProxyConf{}
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.TCPProxy
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.RemotePort = remotePort
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
|
||||||
|
|
||||||
err = cfg.ValidateForClient()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := map[string]config.ProxyConf{
|
|
||||||
cfg.ProxyName: cfg,
|
|
||||||
}
|
|
||||||
err = startService(clientCfg, proxyConfs, nil, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
// Copyright 2020 guylewin, guy@lewin.co.il
|
|
||||||
//
|
|
||||||
// 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(tcpMuxCmd)
|
|
||||||
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
tcpMuxCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer")
|
|
||||||
tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
|
||||||
tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
|
||||||
tcpMuxCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(tcpMuxCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tcpMuxCmd = &cobra.Command{
|
|
||||||
Use: "tcpmux",
|
|
||||||
Short: "Run frpc with a single tcpmux proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.TCPMuxProxyConf{}
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.TCPMuxProxy
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.CustomDomains = strings.Split(customDomains, ",")
|
|
||||||
cfg.SubDomain = subDomain
|
|
||||||
cfg.Multiplexer = multiplexer
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
|
||||||
|
|
||||||
err = cfg.ValidateForClient()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := map[string]config.ProxyConf{
|
|
||||||
cfg.ProxyName: cfg,
|
|
||||||
}
|
|
||||||
err = startService(clientCfg, proxyConfs, nil, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(udpCmd)
|
|
||||||
|
|
||||||
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
udpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
|
|
||||||
udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
|
||||||
udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
|
||||||
udpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
|
||||||
udpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(udpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var udpCmd = &cobra.Command{
|
|
||||||
Use: "udp",
|
|
||||||
Short: "Run frpc with a single udp proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.UDPProxyConf{}
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.UDPProxy
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.RemotePort = remotePort
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
|
||||||
|
|
||||||
err = cfg.ValidateForClient()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := map[string]config.ProxyConf{
|
|
||||||
cfg.ProxyName: cfg,
|
|
||||||
}
|
|
||||||
err = startService(clientCfg, proxyConfs, nil, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -31,7 +32,20 @@ var verifyCmd = &cobra.Command{
|
|||||||
Use: "verify",
|
Use: "verify",
|
||||||
Short: "Verify that the configures is valid",
|
Short: "Verify that the configures is valid",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
_, _, _, err := config.ParseClientConfig(cfgFile)
|
if cfgFile == "" {
|
||||||
|
fmt.Println("frpc: the configuration file is not specified")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cliCfg, pxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
warning, err := validation.ValidateAllClientConfig(cliCfg, pxyCfgs, visitorCfgs)
|
||||||
|
if warning != nil {
|
||||||
|
fmt.Printf("WARNING: %v\n", warning)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// 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 sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterCommonFlags(xtcpCmd)
|
|
||||||
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
|
|
||||||
xtcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
|
||||||
xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
|
||||||
xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
|
||||||
xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
|
|
||||||
xtcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
|
|
||||||
|
|
||||||
rootCmd.AddCommand(xtcpCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var xtcpCmd = &cobra.Command{
|
|
||||||
Use: "xtcp",
|
|
||||||
Short: "Run frpc with a single xtcp proxy",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
clientCfg, err := parseClientCommonCfgFromCmd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyConfs := make(map[string]config.ProxyConf)
|
|
||||||
visitorConfs := make(map[string]config.VisitorConf)
|
|
||||||
|
|
||||||
var prefix string
|
|
||||||
if user != "" {
|
|
||||||
prefix = user + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
switch role {
|
|
||||||
case "server":
|
|
||||||
cfg := &config.XTCPProxyConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.XTCPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.LocalIP = localIP
|
|
||||||
cfg.LocalPort = localPort
|
|
||||||
cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
|
||||||
err = cfg.ValidateForClient()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
proxyConfs[cfg.ProxyName] = cfg
|
|
||||||
case "visitor":
|
|
||||||
cfg := &config.XTCPVisitorConf{}
|
|
||||||
cfg.ProxyName = prefix + proxyName
|
|
||||||
cfg.ProxyType = consts.XTCPProxy
|
|
||||||
cfg.UseEncryption = useEncryption
|
|
||||||
cfg.UseCompression = useCompression
|
|
||||||
cfg.Role = role
|
|
||||||
cfg.Sk = sk
|
|
||||||
cfg.ServerName = serverName
|
|
||||||
cfg.BindAddr = bindAddr
|
|
||||||
cfg.BindPort = bindPort
|
|
||||||
err = cfg.Validate()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
visitorConfs[cfg.ProxyName] = cfg
|
|
||||||
default:
|
|
||||||
fmt.Println("invalid role")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
110
cmd/frps/flags.go
Normal file
110
cmd/frps/flags.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2023 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PortsRangeSliceFlag struct {
|
||||||
|
V *[]types.PortsRange
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *PortsRangeSliceFlag) String() string {
|
||||||
|
if f.V == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return types.PortsRangeSlice(*f.V).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *PortsRangeSliceFlag) Set(s string) error {
|
||||||
|
slice, err := types.NewPortsRangeSliceFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*f.V = slice
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *PortsRangeSliceFlag) Type() string {
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoolFuncFlag struct {
|
||||||
|
TrueFunc func()
|
||||||
|
FalseFunc func()
|
||||||
|
|
||||||
|
v bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *BoolFuncFlag) String() string {
|
||||||
|
return strconv.FormatBool(f.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *BoolFuncFlag) Set(s string) error {
|
||||||
|
f.v = strconv.FormatBool(f.v) == "true"
|
||||||
|
|
||||||
|
if !f.v {
|
||||||
|
if f.FalseFunc != nil {
|
||||||
|
f.FalseFunc()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.TrueFunc != nil {
|
||||||
|
f.TrueFunc()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *BoolFuncFlag) Type() string {
|
||||||
|
return "bool"
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterServerConfigFlags(cmd *cobra.Command, c *v1.ServerConfig) {
|
||||||
|
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.KCPBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
||||||
|
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.VhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")
|
||||||
|
cmd.PersistentFlags().Int64VarP(&c.VhostHTTPTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.WebServer.Addr, "dashboard_addr", "", "0.0.0.0", "dashboard address")
|
||||||
|
cmd.PersistentFlags().IntVarP(&c.WebServer.Port, "dashboard_port", "", 0, "dashboard port")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.WebServer.User, "dashboard_user", "", "admin", "dashboard user")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.WebServer.Password, "dashboard_pwd", "", "admin", "dashboard password")
|
||||||
|
cmd.PersistentFlags().BoolVarP(&c.EnablePrometheus, "enable_prometheus", "", false, "enable prometheus dashboard")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.Log.To, "log_file", "", "console", "log file")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.Log.Level, "log_level", "", "info", "log level")
|
||||||
|
cmd.PersistentFlags().Int64VarP(&c.Log.MaxDays, "log_max_days", "", 3, "log max days")
|
||||||
|
cmd.PersistentFlags().BoolVarP(&c.Log.DisablePrintColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.Auth.Token, "token", "t", "", "auth token")
|
||||||
|
cmd.PersistentFlags().StringVarP(&c.SubDomainHost, "subdomain_host", "", "", "subdomain host")
|
||||||
|
cmd.PersistentFlags().VarP(&PortsRangeSliceFlag{V: &c.AllowPorts}, "allow_ports", "", "allow ports")
|
||||||
|
cmd.PersistentFlags().Int64VarP(&c.MaxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
|
||||||
|
cmd.PersistentFlags().BoolVarP(&c.Transport.TLS.Force, "tls_only", "", false, "frps tls only")
|
||||||
|
|
||||||
|
webServerTLS := v1.TLSConfig{}
|
||||||
|
cmd.PersistentFlags().StringVarP(&webServerTLS.CertFile, "dashboard_tls_cert_file", "", "", "dashboard tls cert file")
|
||||||
|
cmd.PersistentFlags().StringVarP(&webServerTLS.KeyFile, "dashboard_tls_key_file", "", "", "dashboard tls key file")
|
||||||
|
cmd.PersistentFlags().VarP(&BoolFuncFlag{
|
||||||
|
TrueFunc: func() { c.WebServer.TLS = &webServerTLS },
|
||||||
|
}, "dashboard_tls_mode", "", "if enable dashboard tls mode")
|
||||||
|
}
|
||||||
@@ -15,9 +15,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
|
|
||||||
_ "github.com/fatedier/frp/assets/frps"
|
_ "github.com/fatedier/frp/assets/frps"
|
||||||
@@ -26,8 +23,5 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
crypto.DefaultSalt = "frp"
|
crypto.DefaultSalt = "frp"
|
||||||
// TODO: remove this when we drop support for go1.19
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
Execute()
|
Execute()
|
||||||
}
|
}
|
||||||
|
|||||||
163
cmd/frps/root.go
163
cmd/frps/root.go
@@ -15,83 +15,32 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
|
||||||
"github.com/fatedier/frp/pkg/util/version"
|
"github.com/fatedier/frp/pkg/util/version"
|
||||||
"github.com/fatedier/frp/server"
|
"github.com/fatedier/frp/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
CfgFileTypeIni = iota
|
|
||||||
CfgFileTypeCmd
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfgFile string
|
cfgFile string
|
||||||
showVersion bool
|
showVersion bool
|
||||||
|
|
||||||
bindAddr string
|
serverCfg v1.ServerConfig
|
||||||
bindPort int
|
|
||||||
kcpBindPort int
|
|
||||||
proxyBindAddr string
|
|
||||||
vhostHTTPPort int
|
|
||||||
vhostHTTPSPort int
|
|
||||||
vhostHTTPTimeout int64
|
|
||||||
dashboardAddr string
|
|
||||||
dashboardPort int
|
|
||||||
dashboardUser string
|
|
||||||
dashboardPwd string
|
|
||||||
enablePrometheus bool
|
|
||||||
logFile string
|
|
||||||
logLevel string
|
|
||||||
logMaxDays int64
|
|
||||||
disableLogColor bool
|
|
||||||
token string
|
|
||||||
subDomainHost string
|
|
||||||
allowPorts string
|
|
||||||
maxPortsPerClient int64
|
|
||||||
tlsOnly bool
|
|
||||||
dashboardTLSMode bool
|
|
||||||
dashboardTLSCertFile string
|
|
||||||
dashboardTLSKeyFile string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps")
|
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
RegisterServerConfigFlags(rootCmd, &serverCfg)
|
||||||
rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port")
|
|
||||||
rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
|
||||||
rootCmd.PersistentFlags().IntVarP(&vhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
|
|
||||||
rootCmd.PersistentFlags().IntVarP(&vhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")
|
|
||||||
rootCmd.PersistentFlags().Int64VarP(&vhostHTTPTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dashboard address")
|
|
||||||
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&enablePrometheus, "enable_prometheus", "", false, "enable prometheus dashboard")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
|
||||||
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
|
||||||
rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&tlsOnly, "tls_only", "", false, "frps tls only")
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&dashboardTLSMode, "dashboard_tls_mode", "", false, "dashboard tls mode")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&dashboardTLSCertFile, "dashboard_tls_cert_file", "", "", "dashboard tls cert file")
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&dashboardTLSKeyFile, "dashboard_tls_key_file", "", "", "dashboard tls key file")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@@ -103,27 +52,39 @@ var rootCmd = &cobra.Command{
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg config.ServerCommonConf
|
var (
|
||||||
var err error
|
svrCfg *v1.ServerConfig
|
||||||
|
isLegacyFormat bool
|
||||||
|
err error
|
||||||
|
)
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
var content []byte
|
svrCfg, isLegacyFormat, err = config.LoadServerConfig(cfgFile)
|
||||||
content, err = config.GetRenderedConfFromFile(cfgFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if isLegacyFormat {
|
||||||
|
fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " +
|
||||||
|
"please use yaml/json/toml format instead!\n")
|
||||||
}
|
}
|
||||||
cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
|
|
||||||
} else {
|
} else {
|
||||||
cfg, err = parseServerCommonCfg(CfgFileTypeCmd, nil)
|
serverCfg.Complete()
|
||||||
}
|
svrCfg = &serverCfg
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = runServer(cfg)
|
warning, err := validation.ValidateServerConfig(svrCfg)
|
||||||
|
if warning != nil {
|
||||||
|
fmt.Printf("WARNING: %v\n", warning)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := runServer(svrCfg); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -134,70 +95,8 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseServerCommonCfg(fileType int, source []byte) (cfg config.ServerCommonConf, err error) {
|
func runServer(cfg *v1.ServerConfig) (err error) {
|
||||||
if fileType == CfgFileTypeIni {
|
log.InitLog(cfg.Log.To, cfg.Log.Level, cfg.Log.MaxDays, cfg.Log.DisablePrintColor)
|
||||||
cfg, err = config.UnmarshalServerConfFromIni(source)
|
|
||||||
} else if fileType == CfgFileTypeCmd {
|
|
||||||
cfg, err = parseServerCommonCfgFromCmd()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cfg.Complete()
|
|
||||||
err = cfg.Validate()
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("parse config error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
|
||||||
cfg = config.GetDefaultServerConf()
|
|
||||||
|
|
||||||
cfg.BindAddr = bindAddr
|
|
||||||
cfg.BindPort = bindPort
|
|
||||||
cfg.KCPBindPort = kcpBindPort
|
|
||||||
cfg.ProxyBindAddr = proxyBindAddr
|
|
||||||
cfg.VhostHTTPPort = vhostHTTPPort
|
|
||||||
cfg.VhostHTTPSPort = vhostHTTPSPort
|
|
||||||
cfg.VhostHTTPTimeout = vhostHTTPTimeout
|
|
||||||
cfg.DashboardAddr = dashboardAddr
|
|
||||||
cfg.DashboardPort = dashboardPort
|
|
||||||
cfg.DashboardUser = dashboardUser
|
|
||||||
cfg.DashboardPwd = dashboardPwd
|
|
||||||
cfg.EnablePrometheus = enablePrometheus
|
|
||||||
cfg.DashboardTLSCertFile = dashboardTLSCertFile
|
|
||||||
cfg.DashboardTLSKeyFile = dashboardTLSKeyFile
|
|
||||||
cfg.DashboardTLSMode = dashboardTLSMode
|
|
||||||
cfg.LogFile = logFile
|
|
||||||
cfg.LogLevel = logLevel
|
|
||||||
cfg.LogMaxDays = logMaxDays
|
|
||||||
cfg.SubDomainHost = subDomainHost
|
|
||||||
cfg.TLSOnly = tlsOnly
|
|
||||||
|
|
||||||
// Only token authentication is supported in cmd mode
|
|
||||||
cfg.ServerConfig = auth.GetDefaultServerConf()
|
|
||||||
cfg.Token = token
|
|
||||||
if len(allowPorts) > 0 {
|
|
||||||
// e.g. 1000-2000,2001,2002,3000-4000
|
|
||||||
ports, errRet := util.ParseRangeNumbers(allowPorts)
|
|
||||||
if errRet != nil {
|
|
||||||
err = fmt.Errorf("parse conf error: allow_ports: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, port := range ports {
|
|
||||||
cfg.AllowPorts[int(port)] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cfg.MaxPortsPerClient = maxPortsPerClient
|
|
||||||
cfg.DisableLogColor = disableLogColor
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func runServer(cfg config.ServerCommonConf) (err error) {
|
|
||||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
|
|
||||||
|
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
log.Info("frps uses config file: %s", cfgFile)
|
log.Info("frps uses config file: %s", cfgFile)
|
||||||
@@ -210,6 +109,6 @@ func runServer(cfg config.ServerCommonConf) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info("frps started successfully")
|
log.Info("frps started successfully")
|
||||||
svr.Run()
|
svr.Run(context.Background())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -32,21 +33,23 @@ var verifyCmd = &cobra.Command{
|
|||||||
Short: "Verify that the configures is valid",
|
Short: "Verify that the configures is valid",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if cfgFile == "" {
|
if cfgFile == "" {
|
||||||
fmt.Println("no config file is specified")
|
fmt.Println("frps: the configuration file is not specified")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
iniContent, err := config.GetRenderedConfFromFile(cfgFile)
|
svrCfg, _, err := config.LoadServerConfig(cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = parseServerCommonCfg(CfgFileTypeIni, iniContent)
|
warning, err := validation.ValidateServerConfig(svrCfg)
|
||||||
|
if warning != nil {
|
||||||
|
fmt.Printf("WARNING: %v\n", warning)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile)
|
fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
[common]
|
|
||||||
server_addr = 127.0.0.1
|
|
||||||
server_port = 7000
|
|
||||||
|
|
||||||
[ssh]
|
|
||||||
type = tcp
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 22
|
|
||||||
remote_port = 6000
|
|
||||||
9
conf/frpc.toml
Normal file
9
conf/frpc.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
serverAddr = "127.0.0.1"
|
||||||
|
serverPort = 7000
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "test-tcp"
|
||||||
|
type = "tcp"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 22
|
||||||
|
remotePort = 6000
|
||||||
361
conf/frpc_full_example.toml
Normal file
361
conf/frpc_full_example.toml
Normal file
@@ -0,0 +1,361 @@
|
|||||||
|
# This configuration file is for reference only. Please do not use this configuration directly to run the program as it may have various issues.
|
||||||
|
|
||||||
|
# your proxy name will be changed to {user}.{proxy}
|
||||||
|
user = "your_name"
|
||||||
|
|
||||||
|
# A literal address or host name for IPv6 must be enclosed
|
||||||
|
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||||
|
# For single serverAddr field, no need square brackets, like serverAddr = "::".
|
||||||
|
serverAddr = "0.0.0.0"
|
||||||
|
serverPort = 7000
|
||||||
|
|
||||||
|
# STUN server to help penetrate NAT hole.
|
||||||
|
# natHoleStunServer = "stun.easyvoip.com:3478"
|
||||||
|
|
||||||
|
# Decide if exit program when first login failed, otherwise continuous relogin to frps
|
||||||
|
# default is true
|
||||||
|
loginFailExit = true
|
||||||
|
|
||||||
|
# console or real logFile path like ./frpc.log
|
||||||
|
log.to = "./frpc.log"
|
||||||
|
# trace, debug, info, warn, error
|
||||||
|
log.level = "info"
|
||||||
|
log.maxDays = 3
|
||||||
|
# disable log colors when log.to is console, default is false
|
||||||
|
log.disablePrintColor = false
|
||||||
|
|
||||||
|
auth.method = "token"
|
||||||
|
# auth.additionalScopes specifies additional scopes to include authentication information.
|
||||||
|
# Optional values are HeartBeats, NewWorkConns.
|
||||||
|
# auth.additionalScopes = ["HeartBeats", "NewWorkConns"]
|
||||||
|
|
||||||
|
# auth token
|
||||||
|
auth.token = "12345678"
|
||||||
|
|
||||||
|
# oidc.clientID specifies the client ID to use to get a token in OIDC authentication.
|
||||||
|
# auth.oidc.clientID = ""
|
||||||
|
# oidc.clientSecret specifies the client secret to use to get a token in OIDC authentication.
|
||||||
|
# auth.oidc.clientSecret = ""
|
||||||
|
# oidc.audience specifies the audience of the token in OIDC authentication.
|
||||||
|
# auth.oidc.audience = ""
|
||||||
|
# oidc.scope specifies the permisssions of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
# auth.oidc.scope = ""
|
||||||
|
# oidc.tokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||||
|
# It will be used to get an OIDC token.
|
||||||
|
# auth.oidc.tokenEndpointURL = ""
|
||||||
|
|
||||||
|
# oidc.additionalEndpointParams specifies additional parameters to be sent to the OIDC Token Endpoint.
|
||||||
|
# For example, if you want to specify the "audience" parameter, you can set as follow.
|
||||||
|
# frp will add "audience=<value>" "var1=<value>" to the additional parameters.
|
||||||
|
# auth.oidc.additionalEndpointParams.audience = "https://dev.auth.com/api/v2/"
|
||||||
|
# auth.oidc.additionalEndpointParams.var1 = "foobar"
|
||||||
|
|
||||||
|
# Set admin address for control frpc's action by http api such as reload
|
||||||
|
webServer.addr = "127.0.0.1"
|
||||||
|
webServer.port = 7400
|
||||||
|
webServer.user = "admin"
|
||||||
|
webServer.password = "admin"
|
||||||
|
# Admin assets directory. By default, these assets are bundled with frpc.
|
||||||
|
# webServer.assetsDir = "./static"
|
||||||
|
|
||||||
|
# Enable golang pprof handlers in admin listener.
|
||||||
|
webServer.pprofEnable = false
|
||||||
|
|
||||||
|
# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
|
||||||
|
# transport.dialServerTimeout = 10
|
||||||
|
|
||||||
|
# dialServerKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
|
# If negative, keep-alive probes are disabled.
|
||||||
|
# transport.dialServerKeepalive = 7200
|
||||||
|
|
||||||
|
# connections will be established in advance, default value is zero
|
||||||
|
transport.poolCount = 5
|
||||||
|
|
||||||
|
# If tcp stream multiplexing is used, default is true, it must be same with frps
|
||||||
|
# transport.tcpMux = true
|
||||||
|
|
||||||
|
# Specify keep alive interval for tcp mux.
|
||||||
|
# only valid if tcpMux is enabled.
|
||||||
|
# transport.tcpMuxKeepaliveInterval = 60
|
||||||
|
|
||||||
|
# Communication protocol used to connect to server
|
||||||
|
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
||||||
|
transport.protocol = "tcp"
|
||||||
|
|
||||||
|
# set client binding ip when connect server, default is empty.
|
||||||
|
# only when protocol = tcp or websocket, the value will be used.
|
||||||
|
transport.connectServerLocalIP = "0.0.0.0"
|
||||||
|
|
||||||
|
# if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set proxyURL here or in global environment variables
|
||||||
|
# it only works when protocol is tcp
|
||||||
|
# transport.proxyURL = "http://user:passwd@192.168.1.128:8080"
|
||||||
|
# transport.proxyURL = "socks5://user:passwd@192.168.1.128:1080"
|
||||||
|
# transport.proxyURL = "ntlm://user:passwd@192.168.1.128:2080"
|
||||||
|
|
||||||
|
# quic protocol options
|
||||||
|
# transport.quic.keepalivePeriod = 10
|
||||||
|
# transport.quic.maxIdleTimeout = 30
|
||||||
|
# transport.quic.maxIncomingStreams = 100000
|
||||||
|
|
||||||
|
# If tls.enable is true, frpc will connect frps by tls.
|
||||||
|
# Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
|
||||||
|
transport.tls.enable = true
|
||||||
|
|
||||||
|
# transport.tls.certFile = "client.crt"
|
||||||
|
# transport.tls.keyFile = "client.key"
|
||||||
|
# transport.tls.trustedCaFile = "ca.crt"
|
||||||
|
# transport.tls.serverName = "example.com"
|
||||||
|
|
||||||
|
# If the disableCustomTLSFirstByte is set to false, frpc will establish a connection with frps using the
|
||||||
|
# first custom byte when tls is enabled.
|
||||||
|
# Since v0.50.0, the default value has been changed to true, and the first custom byte is disabled by default.
|
||||||
|
# transport.tls.disableCustomTLSFirstByte = true
|
||||||
|
|
||||||
|
# Heartbeat configure, it's not recommended to modify the default value.
|
||||||
|
# The default value of heartbeatInterval is 10 and heartbeatTimeout is 90. Set negative value
|
||||||
|
# to disable it.
|
||||||
|
# transport.heartbeatInterval = 30
|
||||||
|
# transport.heartbeatTimeout = 90
|
||||||
|
|
||||||
|
# Specify a dns server, so frpc will use this instead of default one
|
||||||
|
# dnsServer = "8.8.8.8"
|
||||||
|
|
||||||
|
# Proxy names you want to start.
|
||||||
|
# Default is empty, means all proxies.
|
||||||
|
# start = ["ssh", "dns"]
|
||||||
|
|
||||||
|
# Specify udp packet size, unit is byte. If not set, the default value is 1500.
|
||||||
|
# This parameter should be same between client and server.
|
||||||
|
# It affects the udp and sudp proxy.
|
||||||
|
udpPacketSize = 1500
|
||||||
|
|
||||||
|
# Additional metadatas for client.
|
||||||
|
metadatas.var1 = "abc"
|
||||||
|
metadatas.var2 = "123"
|
||||||
|
|
||||||
|
# Include other config files for proxies.
|
||||||
|
# includes = ["./confd/*.ini"]
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
# 'ssh' is the unique proxy name
|
||||||
|
# If global user is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
|
||||||
|
name = "ssh"
|
||||||
|
type = "tcp"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 22
|
||||||
|
# Limit bandwidth for this proxy, unit is KB and MB
|
||||||
|
transport.bandwidthLimit = "1MB"
|
||||||
|
# Where to limit bandwidth, can be 'client' or 'server', default is 'client'
|
||||||
|
transport.bandwidthLimitMode = "client"
|
||||||
|
# If true, traffic of this proxy will be encrypted, default is false
|
||||||
|
transport.useEncryption = false
|
||||||
|
# If true, traffic will be compressed
|
||||||
|
transport.useCompression = false
|
||||||
|
# Remote port listen by frps
|
||||||
|
remotePort = 6001
|
||||||
|
# frps will load balancing connections for proxies in same group
|
||||||
|
loadBalancer.group = "test_group"
|
||||||
|
# group should have same group key
|
||||||
|
loadBalancer.groupKey = "123456"
|
||||||
|
# Enable health check for the backend service, it supports 'tcp' and 'http' now.
|
||||||
|
# frpc will connect local service's port to detect it's healthy status
|
||||||
|
healthCheck.type = "tcp"
|
||||||
|
# Health check connection timeout
|
||||||
|
healthCheck.timeoutSeconds = 3
|
||||||
|
# If continuous failed in 3 times, the proxy will be removed from frps
|
||||||
|
healthCheck.maxFailed = 3
|
||||||
|
# every 10 seconds will do a health check
|
||||||
|
healthCheck.intervalSeconds = 10
|
||||||
|
# additional meta info for each proxy
|
||||||
|
metadatas.var1 = "abc"
|
||||||
|
metadatas.var2 = "123"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "ssh_random"
|
||||||
|
type = "tcp"
|
||||||
|
localIP = "192.168.31.100"
|
||||||
|
localPort = 22
|
||||||
|
# If remotePort is 0, frps will assign a random port for you
|
||||||
|
remotePort = 0
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "dns"
|
||||||
|
type = "udp"
|
||||||
|
localIP = "114.114.114.114"
|
||||||
|
localPort = 53
|
||||||
|
remotePort = 6002
|
||||||
|
|
||||||
|
# Resolve your domain names to [serverAddr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
|
||||||
|
[[proxies]]
|
||||||
|
name = "web01"
|
||||||
|
type = "http"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 80
|
||||||
|
# http username and password are safety certification for http protocol
|
||||||
|
# if not set, you can access this customDomains without certification
|
||||||
|
httpUser = "admin"
|
||||||
|
httpPassword = "admin"
|
||||||
|
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://web01.frps.com
|
||||||
|
subdomain = "web01"
|
||||||
|
customDomains = ["web01.yourdomain.com"]
|
||||||
|
# locations is only available for http type
|
||||||
|
locations = ["/", "/pic"]
|
||||||
|
# route requests to this service if http basic auto user is abc
|
||||||
|
# routeByHTTPUser = abc
|
||||||
|
hostHeaderRewrite = "example.com"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
healthCheck.type = "http"
|
||||||
|
# frpc will send a GET http request '/status' to local http service
|
||||||
|
# http service is alive when it return 2xx http response code
|
||||||
|
healthCheck.path = "/status"
|
||||||
|
healthCheck.intervalSeconds = 10
|
||||||
|
healthCheck.maxFailed = 3
|
||||||
|
healthCheck.timeoutSeconds = 3
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "web02"
|
||||||
|
type = "https"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 8000
|
||||||
|
subdomain = "web02"
|
||||||
|
customDomains = ["web02.yourdomain.com"]
|
||||||
|
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||||
|
# v1 or v2 or empty
|
||||||
|
transport.proxyProtocolVersion = "v2"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "tcpmuxhttpconnect"
|
||||||
|
type = "tcpmux"
|
||||||
|
multiplexer = "httpconnect"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 10701
|
||||||
|
customDomains = ["tunnel1"]
|
||||||
|
# routeByHTTPUser = "user1"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_unix_domain_socket"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6003
|
||||||
|
# if plugin is defined, localIP and localPort is useless
|
||||||
|
# plugin will handle connections got from frps
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "unix_domain_socket"
|
||||||
|
unixPath = "/var/run/docker.sock"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_http_proxy"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6004
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "http_proxy"
|
||||||
|
httpUser = "abc"
|
||||||
|
httpPassword = "abc"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_socks5"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6005
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "socks5"
|
||||||
|
username = "abc"
|
||||||
|
password = "abc"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_static_file"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6006
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "static_file"
|
||||||
|
localPath = "/var/www/blog"
|
||||||
|
stripPrefix = "static"
|
||||||
|
httpUser = "abc"
|
||||||
|
httpPassword = "abc"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_https2http"
|
||||||
|
type = "https"
|
||||||
|
customDomains = ["test.yourdomain.com"]
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "https2http"
|
||||||
|
localAddr = "127.0.0.1:80"
|
||||||
|
crtPath = "./server.crt"
|
||||||
|
keyPath = "./server.key"
|
||||||
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_https2https"
|
||||||
|
type = "https"
|
||||||
|
customDomains = ["test.yourdomain.com"]
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "https2https"
|
||||||
|
localAddr = "127.0.0.1:443"
|
||||||
|
crtPath = "./server.crt"
|
||||||
|
keyPath = "./server.key"
|
||||||
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_http2https"
|
||||||
|
type = "http"
|
||||||
|
customDomains = ["test.yourdomain.com"]
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "http2https"
|
||||||
|
localAddr = "127.0.0.1:443"
|
||||||
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "secret_tcp"
|
||||||
|
# If the type is secret tcp, remotePort is useless
|
||||||
|
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
||||||
|
type = "stcp"
|
||||||
|
# secretKey is used for authentication for visitors
|
||||||
|
secretKey = "abcdefg"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 22
|
||||||
|
# If not empty, only visitors from specified users can connect.
|
||||||
|
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||||
|
allowUsers = ["*"]
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "p2p_tcp"
|
||||||
|
type = "xtcp"
|
||||||
|
secretKey = "abcdefg"
|
||||||
|
localIP = "127.0.0.1"
|
||||||
|
localPort = 22
|
||||||
|
# If not empty, only visitors from specified users can connect.
|
||||||
|
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||||
|
allowUsers = ["user1", "user2"]
|
||||||
|
|
||||||
|
# frpc role visitor -> frps -> frpc role server
|
||||||
|
[[visitors]]
|
||||||
|
name = "secret_tcp_visitor"
|
||||||
|
type = "stcp"
|
||||||
|
# the server name you want to visitor
|
||||||
|
serverName = "secret_tcp"
|
||||||
|
secretKey = "abcdefg"
|
||||||
|
# connect this address to visitor stcp server
|
||||||
|
bindAddr = "127.0.0.1"
|
||||||
|
# bindPort 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)
|
||||||
|
bindPort = 9000
|
||||||
|
|
||||||
|
[[visitors]]
|
||||||
|
name = "p2p_tcp_visitor"
|
||||||
|
type = "xtcp"
|
||||||
|
# if the server user is not set, it defaults to the current user
|
||||||
|
serverUser = "user1"
|
||||||
|
serverName = "p2p_tcp"
|
||||||
|
secretKey = "abcdefg"
|
||||||
|
bindAddr = "127.0.0.1"
|
||||||
|
# bindPort 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)
|
||||||
|
bindPort = 9001
|
||||||
|
# when automatic tunnel persistence is required, set it to true
|
||||||
|
keepTunnelOpen = false
|
||||||
|
# effective when keepTunnelOpen is set to true, the number of attempts to punch through per hour
|
||||||
|
maxRetriesAnHour = 8
|
||||||
|
minRetryInterval = 90
|
||||||
|
# fallbackTo = "stcp_visitor"
|
||||||
|
# fallbackTimeoutMs = 500
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
[common]
|
|
||||||
bind_port = 7000
|
|
||||||
1
conf/frps.toml
Normal file
1
conf/frps.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bindPort = 7000
|
||||||
156
conf/frps_full_example.toml
Normal file
156
conf/frps_full_example.toml
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
# This configuration file is for reference only. Please do not use this configuration directly to run the program as it may have various issues.
|
||||||
|
|
||||||
|
# A literal address or host name for IPv6 must be enclosed
|
||||||
|
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||||
|
# For single "bindAddr" field, no need square brackets, like `bindAddr = "::"`.
|
||||||
|
bindAddr = "0.0.0.0"
|
||||||
|
bindPort = 7000
|
||||||
|
|
||||||
|
# udp port used for kcp protocol, it can be same with 'bindPort'.
|
||||||
|
# if not set, kcp is disabled in frps.
|
||||||
|
kcpBindPort = 7000
|
||||||
|
|
||||||
|
# udp port used for quic protocol.
|
||||||
|
# if not set, quic is disabled in frps.
|
||||||
|
# quicBindPort = 7002
|
||||||
|
|
||||||
|
# Specify which address proxy will listen for, default value is same with bindAddr
|
||||||
|
# proxyBindAddr = "127.0.0.1"
|
||||||
|
|
||||||
|
# quic protocol options
|
||||||
|
# transport.quic.keepalivePeriod = 10
|
||||||
|
# transport.quic.maxIdleTimeout = 30
|
||||||
|
# transport.quic.maxIncomingStreams = 100000
|
||||||
|
|
||||||
|
# Heartbeat configure, it's not recommended to modify the default value
|
||||||
|
# The default value of heartbeatTimeout is 90. Set negative value to disable it.
|
||||||
|
# transport.heartbeatTimeout = 90
|
||||||
|
|
||||||
|
# Pool count in each proxy will keep no more than maxPoolCount.
|
||||||
|
transport.maxPoolCount = 5
|
||||||
|
|
||||||
|
# If tcp stream multiplexing is used, default is true
|
||||||
|
# transport.tcpMux = true
|
||||||
|
|
||||||
|
# Specify keep alive interval for tcp mux.
|
||||||
|
# only valid if tcpMux is true.
|
||||||
|
# transport.tcpMuxKeepaliveInterval = 60
|
||||||
|
|
||||||
|
# tcpKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
|
# If negative, keep-alive probes are disabled.
|
||||||
|
# transport.tcpKeepalive = 7200
|
||||||
|
|
||||||
|
# transport.tls.force specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
||||||
|
tls.force = false
|
||||||
|
|
||||||
|
# transport.tls.certFile = "server.crt"
|
||||||
|
# transport.tls.keyFile = "server.key"
|
||||||
|
# transport.tls.trustedCaFile = "ca.crt"
|
||||||
|
|
||||||
|
# If you want to support virtual host, you must set the http port for listening (optional)
|
||||||
|
# Note: http port and https port can be same with bindPort
|
||||||
|
vhostHTTPPort = 80
|
||||||
|
vhostHTTPSPort = 443
|
||||||
|
|
||||||
|
# Response header timeout(seconds) for vhost http server, default is 60s
|
||||||
|
# vhostHTTPTimeout = 60
|
||||||
|
|
||||||
|
# tcpmuxHTTPConnectPort specifies the port that the server listens for TCP
|
||||||
|
# HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||||
|
# requests on one single port. If it's not - it will listen on this value for
|
||||||
|
# HTTP CONNECT requests. By default, this value is 0.
|
||||||
|
# tcpmuxHTTPConnectPort = 1337
|
||||||
|
|
||||||
|
# If tcpmuxPassthrough is true, frps won't do any update on traffic.
|
||||||
|
# tcpmuxPassthrough = false
|
||||||
|
|
||||||
|
# Configure the web server to enable the dashboard for frps.
|
||||||
|
# dashboard is available only if webServer.port is set.
|
||||||
|
webServer.addr = "127.0.0.1"
|
||||||
|
webServer.port = 7500
|
||||||
|
webServer.user = "admin"
|
||||||
|
webServer.password = "admin"
|
||||||
|
# webServer.tls.certFile = "server.crt"
|
||||||
|
# webServer.tls.keyFile = "server.key"
|
||||||
|
# dashboard assets directory(only for debug mode)
|
||||||
|
# webServer.assetsDir = "./static"
|
||||||
|
|
||||||
|
# Enable golang pprof handlers in dashboard listener.
|
||||||
|
# Dashboard port must be set first
|
||||||
|
webServer.pprofEnable = false
|
||||||
|
|
||||||
|
# enablePrometheus will export prometheus metrics on webServer in /metrics api.
|
||||||
|
enablePrometheus = true
|
||||||
|
|
||||||
|
# console or real logFile path like ./frps.log
|
||||||
|
log.to = "./frps.log"
|
||||||
|
# trace, debug, info, warn, error
|
||||||
|
log.level = "info"
|
||||||
|
log.maxDays = 3
|
||||||
|
# disable log colors when log.to is console, default is false
|
||||||
|
log.disablePrintColor = false
|
||||||
|
|
||||||
|
# DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true.
|
||||||
|
detailedErrorsToClient = true
|
||||||
|
|
||||||
|
# auth.method specifies what authentication method to use authenticate frpc with frps.
|
||||||
|
# If "token" is specified - token will be read into login message.
|
||||||
|
# If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token".
|
||||||
|
auth.method = "token"
|
||||||
|
|
||||||
|
# auth.additionalScopes specifies additional scopes to include authentication information.
|
||||||
|
# Optional values are HeartBeats, NewWorkConns.
|
||||||
|
# auth.additionalScopes = ["HeartBeats", "NewWorkConns"]
|
||||||
|
|
||||||
|
# auth token
|
||||||
|
auth.token = "12345678"
|
||||||
|
|
||||||
|
# oidc issuer specifies the issuer to verify OIDC tokens with.
|
||||||
|
auth.oidc.issuer = ""
|
||||||
|
# oidc audience specifies the audience OIDC tokens should contain when validated.
|
||||||
|
auth.oidc.audience = ""
|
||||||
|
# oidc skipExpiryCheck specifies whether to skip checking if the OIDC token is expired.
|
||||||
|
auth.oidc.skipExpiryCheck = false
|
||||||
|
# oidc skipIssuerCheck specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer.
|
||||||
|
auth.oidc.skipIssuerCheck = false
|
||||||
|
|
||||||
|
# userConnTimeout specifies the maximum time to wait for a work connection.
|
||||||
|
# userConnTimeout = 10
|
||||||
|
|
||||||
|
# Only allow frpc to bind ports you list. By default, there won't be any limit.
|
||||||
|
allowPorts = [
|
||||||
|
{ start = 2000, end = 3000 },
|
||||||
|
{ single = 3001 },
|
||||||
|
{ single = 3003 },
|
||||||
|
{ start = 4000, end = 50000 }
|
||||||
|
]
|
||||||
|
|
||||||
|
# Max ports can be used for each client, default value is 0 means no limit
|
||||||
|
maxPortsPerClient = 0
|
||||||
|
|
||||||
|
# If subDomainHost is not empty, you can set subdomain when type is http or https in frpc's configure file
|
||||||
|
# When subdomain is est, the host used by routing is test.frps.com
|
||||||
|
subDomainHost = "frps.com"
|
||||||
|
|
||||||
|
# custom 404 page for HTTP requests
|
||||||
|
# custom404Page = "/path/to/404.html"
|
||||||
|
|
||||||
|
# specify udp packet size, unit is byte. If not set, the default value is 1500.
|
||||||
|
# This parameter should be same between client and server.
|
||||||
|
# It affects the udp and sudp proxy.
|
||||||
|
udpPacketSize = 1500
|
||||||
|
|
||||||
|
# Retention time for NAT hole punching strategy data.
|
||||||
|
natholeAnalysisDataReserveHours = 168
|
||||||
|
|
||||||
|
[[httpPlugins]]
|
||||||
|
name = "user-manager"
|
||||||
|
addr = "127.0.0.1:9000"
|
||||||
|
path = "/handler"
|
||||||
|
ops = ["Login"]
|
||||||
|
|
||||||
|
[[httpPlugins]]
|
||||||
|
name = "port-manager"
|
||||||
|
addr = "127.0.0.1:9001"
|
||||||
|
path = "/handler"
|
||||||
|
ops = ["NewProxy"]
|
||||||
@@ -95,7 +95,7 @@ user = your_name
|
|||||||
login_fail_exit = true
|
login_fail_exit = true
|
||||||
|
|
||||||
# communication protocol used to connect to server
|
# communication protocol used to connect to server
|
||||||
# supports tcp, kcp, quic and websocket now, default is tcp
|
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
||||||
protocol = tcp
|
protocol = tcp
|
||||||
|
|
||||||
# set client binding ip when connect server, default is empty.
|
# set client binding ip when connect server, default is empty.
|
||||||
@@ -216,49 +216,50 @@ New user connection received from proxy (support `tcp`, `stcp`, `https` and `tcp
|
|||||||
|
|
||||||
### Server Plugin Configuration
|
### Server Plugin Configuration
|
||||||
|
|
||||||
```ini
|
```toml
|
||||||
# frps.ini
|
# frps.toml
|
||||||
[common]
|
bindPort = 7000
|
||||||
bind_port = 7000
|
|
||||||
|
|
||||||
[plugin.user-manager]
|
[[httpPlugins]]
|
||||||
addr = 127.0.0.1:9000
|
name = "user-manager"
|
||||||
path = /handler
|
addr = "127.0.0.1:9000"
|
||||||
ops = Login
|
path = "/handler"
|
||||||
|
ops = ["Login"]
|
||||||
|
|
||||||
[plugin.port-manager]
|
[[httpPlugins]]
|
||||||
addr = 127.0.0.1:9001
|
name = "port-manager"
|
||||||
path = /handler
|
addr = "127.0.0.1:9001"
|
||||||
ops = NewProxy
|
path = "/handler"
|
||||||
|
ops = ["NewProxy"]
|
||||||
```
|
```
|
||||||
|
|
||||||
- addr: the address where the external RPC service listens. Defaults to http. For https, specify the schema: `addr = https://127.0.0.1:9001`.
|
- addr: the address where the external RPC service listens. Defaults to http. For https, specify the schema: `addr = "https://127.0.0.1:9001"`.
|
||||||
- path: http request url path for the POST request.
|
- path: http request url path for the POST request.
|
||||||
- ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...).
|
- ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...).
|
||||||
- tls_verify: When the schema is https, we verify by default. Set this value to false if you want to skip verification.
|
- tlsVerify: When the schema is https, we verify by default. Set this value to false if you want to skip verification.
|
||||||
|
|
||||||
### Metadata
|
### Metadata
|
||||||
|
|
||||||
Metadata will be sent to the server plugin in each RPC request.
|
Metadata will be sent to the server plugin in each RPC request.
|
||||||
|
|
||||||
There are 2 types of metadata entries - 1 under `[common]` and the other under each proxy configuration.
|
There are 2 types of metadata entries - global one and the other under each proxy configuration.
|
||||||
Metadata entries under `[common]` will be sent in `Login` under the key `metas`, and in any other RPC request under `user.metas`.
|
Global metadata entries will be sent in `Login` under the key `metas`, and in any other RPC request under `user.metas`.
|
||||||
Metadata entries under each proxy configuration will be sent in `NewProxy` op only, under `metas`.
|
Metadata entries under each proxy configuration will be sent in `NewProxy` op only, under `metas`.
|
||||||
|
|
||||||
Metadata entries start with `meta_`. This is an example of metadata entries in `[common]` and under the proxy named `[ssh]`:
|
This is an example of metadata entries:
|
||||||
|
|
||||||
```
|
```toml
|
||||||
# frpc.ini
|
# frpc.toml
|
||||||
[common]
|
serverAddr = "127.0.0.1"
|
||||||
server_addr = 127.0.0.1
|
serverPort = 7000
|
||||||
server_port = 7000
|
user = "fake"
|
||||||
user = fake
|
metadatas.token = "fake"
|
||||||
meta_token = fake
|
metadatas.version = "1.0.0"
|
||||||
meta_version = 1.0.0
|
|
||||||
|
|
||||||
[ssh]
|
[[proxies]]
|
||||||
type = tcp
|
name = "ssh"
|
||||||
local_port = 22
|
type = "tcp"
|
||||||
remote_port = 6000
|
localPort = 22
|
||||||
meta_id = 123
|
remotePort = 6000
|
||||||
|
metadatas.id = "123"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.20 AS building
|
FROM golang:1.21 AS building
|
||||||
|
|
||||||
COPY . /building
|
COPY . /building
|
||||||
WORKDIR /building
|
WORKDIR /building
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.20 AS building
|
FROM golang:1.21 AS building
|
||||||
|
|
||||||
COPY . /building
|
COPY . /building
|
||||||
WORKDIR /building
|
WORKDIR /building
|
||||||
|
|||||||
90
go.mod
90
go.mod
@@ -4,74 +4,80 @@ go 1.20
|
|||||||
|
|
||||||
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.4.0
|
github.com/coreos/go-oidc/v3 v3.6.0
|
||||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||||
github.com/fatedier/golib v0.1.1-0.20230320133937-a7edcc8c793d
|
github.com/fatedier/golib v0.1.1-0.20230725122706-dcbaee8eef40
|
||||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
||||||
github.com/go-playground/validator/v10 v10.11.0
|
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
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.8.3
|
github.com/onsi/ginkgo/v2 v2.11.0
|
||||||
github.com/onsi/gomega v1.27.0
|
github.com/onsi/gomega v1.27.8
|
||||||
github.com/pion/stun v0.4.0
|
github.com/pelletier/go-toml/v2 v2.1.0
|
||||||
github.com/pires/go-proxyproto v0.6.2
|
github.com/pion/stun v0.6.1
|
||||||
github.com/prometheus/client_golang v1.13.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
github.com/quic-go/quic-go v0.34.0
|
github.com/prometheus/client_golang v1.16.0
|
||||||
github.com/rodaine/table v1.0.1
|
github.com/quic-go/quic-go v0.37.4
|
||||||
|
github.com/rodaine/table v1.1.0
|
||||||
github.com/samber/lo v1.38.1
|
github.com/samber/lo v1.38.1
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.4
|
||||||
golang.org/x/net v0.7.0
|
golang.org/x/net v0.12.0
|
||||||
golang.org/x/oauth2 v0.3.0
|
golang.org/x/oauth2 v0.10.0
|
||||||
golang.org/x/sync v0.1.0
|
golang.org/x/sync v0.3.0
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
|
golang.org/x/time v0.3.0
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
k8s.io/apimachinery v0.26.1
|
k8s.io/apimachinery v0.27.4
|
||||||
k8s.io/client-go v0.26.1
|
k8s.io/client-go v0.27.4
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect
|
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // 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-logr/logr v1.2.3 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-logr/logr v1.2.4 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/golang/snappy v0.0.3 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
|
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
|
||||||
github.com/klauspost/reedsolomon v1.9.15 // indirect
|
github.com/klauspost/reedsolomon v1.9.15 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/pion/transport/v2 v2.0.0 // indirect
|
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||||
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
|
github.com/pion/transport/v2 v2.2.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.10.1 // indirect
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
golang.org/x/crypto v0.4.0 // indirect
|
golang.org/x/crypto v0.11.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.5.0 // indirect
|
golang.org/x/sys v0.10.0 // indirect
|
||||||
golang.org/x/text v0.7.0 // indirect
|
golang.org/x/text v0.11.0 // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.9.3 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.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-20221107191617-1a15be271d1d // indirect
|
k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(fatedier): Temporary use the modified version, update to the official version after merging into the official repository.
|
||||||
|
replace github.com/hashicorp/yamux => github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
|
|||||||
ginkgo_command=$(which ginkgo 2>/dev/null)
|
ginkgo_command=$(which ginkgo 2>/dev/null)
|
||||||
if [ -z "$ginkgo_command" ]; then
|
if [ -z "$ginkgo_command" ]; then
|
||||||
echo "ginkgo not found, try to install..."
|
echo "ginkgo not found, try to install..."
|
||||||
go install github.com/onsi/ginkgo/v2/ginkgo@v2.8.3
|
go install github.com/onsi/ginkgo/v2/ginkgo@v2.11.0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
debug=false
|
debug=false
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ for os in $os_all; do
|
|||||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||||
fi
|
fi
|
||||||
cp ../LICENSE ${frp_path}
|
cp ../LICENSE ${frp_path}
|
||||||
cp -rf ../conf/* ${frp_path}
|
cp -f ../conf/frpc.toml ${frp_path}
|
||||||
|
cp -f ../conf/frps.toml ${frp_path}
|
||||||
|
|
||||||
# packages
|
# packages
|
||||||
cd ./packages
|
cd ./packages
|
||||||
|
|||||||
@@ -17,76 +17,25 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaseConfig struct {
|
|
||||||
// AuthenticationMethod specifies what authentication method to use to
|
|
||||||
// authenticate frpc with frps. If "token" is specified - token will be
|
|
||||||
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
|
|
||||||
// token will be issued using OIDC settings. By default, this value is "token".
|
|
||||||
AuthenticationMethod string `ini:"authentication_method" json:"authentication_method"`
|
|
||||||
// AuthenticateHeartBeats specifies whether to include authentication token in
|
|
||||||
// heartbeats sent to frps. By default, this value is false.
|
|
||||||
AuthenticateHeartBeats bool `ini:"authenticate_heartbeats" json:"authenticate_heartbeats"`
|
|
||||||
// AuthenticateNewWorkConns specifies whether to include authentication token in
|
|
||||||
// new work connections sent to frps. By default, this value is false.
|
|
||||||
AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns" json:"authenticate_new_work_conns"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefaultBaseConf() BaseConfig {
|
|
||||||
return BaseConfig{
|
|
||||||
AuthenticationMethod: "token",
|
|
||||||
AuthenticateHeartBeats: false,
|
|
||||||
AuthenticateNewWorkConns: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientConfig struct {
|
|
||||||
BaseConfig `ini:",extends"`
|
|
||||||
OidcClientConfig `ini:",extends"`
|
|
||||||
TokenConfig `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDefaultClientConf() ClientConfig {
|
|
||||||
return ClientConfig{
|
|
||||||
BaseConfig: getDefaultBaseConf(),
|
|
||||||
OidcClientConfig: getDefaultOidcClientConf(),
|
|
||||||
TokenConfig: getDefaultTokenConf(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerConfig struct {
|
|
||||||
BaseConfig `ini:",extends"`
|
|
||||||
OidcServerConfig `ini:",extends"`
|
|
||||||
TokenConfig `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDefaultServerConf() ServerConfig {
|
|
||||||
return ServerConfig{
|
|
||||||
BaseConfig: getDefaultBaseConf(),
|
|
||||||
OidcServerConfig: getDefaultOidcServerConf(),
|
|
||||||
TokenConfig: getDefaultTokenConf(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Setter interface {
|
type Setter interface {
|
||||||
SetLogin(*msg.Login) error
|
SetLogin(*msg.Login) error
|
||||||
SetPing(*msg.Ping) error
|
SetPing(*msg.Ping) error
|
||||||
SetNewWorkConn(*msg.NewWorkConn) error
|
SetNewWorkConn(*msg.NewWorkConn) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthSetter(cfg ClientConfig) (authProvider Setter) {
|
func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter) {
|
||||||
switch cfg.AuthenticationMethod {
|
switch cfg.Method {
|
||||||
case consts.TokenAuthMethod:
|
case v1.AuthMethodToken:
|
||||||
authProvider = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig)
|
authProvider = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
|
||||||
case consts.OidcAuthMethod:
|
case v1.AuthMethodOIDC:
|
||||||
authProvider = NewOidcAuthSetter(cfg.BaseConfig, cfg.OidcClientConfig)
|
authProvider = NewOidcAuthSetter(cfg.AdditionalScopes, cfg.OIDC)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("wrong authentication method: '%s'", cfg.AuthenticationMethod))
|
panic(fmt.Sprintf("wrong method: '%s'", cfg.Method))
|
||||||
}
|
}
|
||||||
|
|
||||||
return authProvider
|
return authProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,13 +45,12 @@ type Verifier interface {
|
|||||||
VerifyNewWorkConn(*msg.NewWorkConn) error
|
VerifyNewWorkConn(*msg.NewWorkConn) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthVerifier(cfg ServerConfig) (authVerifier Verifier) {
|
func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
|
||||||
switch cfg.AuthenticationMethod {
|
switch cfg.Method {
|
||||||
case consts.TokenAuthMethod:
|
case v1.AuthMethodToken:
|
||||||
authVerifier = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig)
|
authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
|
||||||
case consts.OidcAuthMethod:
|
case v1.AuthMethodOIDC:
|
||||||
authVerifier = NewOidcAuthVerifier(cfg.BaseConfig, cfg.OidcServerConfig)
|
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, cfg.OIDC)
|
||||||
}
|
}
|
||||||
|
|
||||||
return authVerifier
|
return authVerifier
|
||||||
}
|
}
|
||||||
|
|||||||
145
pkg/auth/legacy/legacy.go
Normal file
145
pkg/auth/legacy/legacy.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright 2023 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 legacy
|
||||||
|
|
||||||
|
type BaseConfig struct {
|
||||||
|
// AuthenticationMethod specifies what authentication method to use to
|
||||||
|
// authenticate frpc with frps. If "token" is specified - token will be
|
||||||
|
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
|
||||||
|
// token will be issued using OIDC settings. By default, this value is "token".
|
||||||
|
AuthenticationMethod string `ini:"authentication_method" json:"authentication_method"`
|
||||||
|
// AuthenticateHeartBeats specifies whether to include authentication token in
|
||||||
|
// heartbeats sent to frps. By default, this value is false.
|
||||||
|
AuthenticateHeartBeats bool `ini:"authenticate_heartbeats" json:"authenticate_heartbeats"`
|
||||||
|
// AuthenticateNewWorkConns specifies whether to include authentication token in
|
||||||
|
// new work connections sent to frps. By default, this value is false.
|
||||||
|
AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns" json:"authenticate_new_work_conns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultBaseConf() BaseConfig {
|
||||||
|
return BaseConfig{
|
||||||
|
AuthenticationMethod: "token",
|
||||||
|
AuthenticateHeartBeats: false,
|
||||||
|
AuthenticateNewWorkConns: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientConfig struct {
|
||||||
|
BaseConfig `ini:",extends"`
|
||||||
|
OidcClientConfig `ini:",extends"`
|
||||||
|
TokenConfig `ini:",extends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultClientConf() ClientConfig {
|
||||||
|
return ClientConfig{
|
||||||
|
BaseConfig: getDefaultBaseConf(),
|
||||||
|
OidcClientConfig: getDefaultOidcClientConf(),
|
||||||
|
TokenConfig: getDefaultTokenConf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
BaseConfig `ini:",extends"`
|
||||||
|
OidcServerConfig `ini:",extends"`
|
||||||
|
TokenConfig `ini:",extends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultServerConf() ServerConfig {
|
||||||
|
return ServerConfig{
|
||||||
|
BaseConfig: getDefaultBaseConf(),
|
||||||
|
OidcServerConfig: getDefaultOidcServerConf(),
|
||||||
|
TokenConfig: getDefaultTokenConf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcClientConfig struct {
|
||||||
|
// OidcClientID specifies the client ID to use to get a token in OIDC
|
||||||
|
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||||
|
// is "".
|
||||||
|
OidcClientID string `ini:"oidc_client_id" json:"oidc_client_id"`
|
||||||
|
// OidcClientSecret specifies the client secret to use to get a token in OIDC
|
||||||
|
// authentication if AuthenticationMethod == "oidc". By default, this value
|
||||||
|
// is "".
|
||||||
|
OidcClientSecret string `ini:"oidc_client_secret" json:"oidc_client_secret"`
|
||||||
|
// OidcAudience specifies the audience of the token in OIDC authentication
|
||||||
|
// if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
||||||
|
// OidcScope specifies the scope of the token in OIDC authentication
|
||||||
|
// if AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
OidcScope string `ini:"oidc_scope" json:"oidc_scope"`
|
||||||
|
// OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||||
|
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
||||||
|
// By default, this value is "".
|
||||||
|
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
|
||||||
|
|
||||||
|
// OidcAdditionalEndpointParams specifies additional parameters to be sent
|
||||||
|
// this field will be transfer to map[string][]string in OIDC token generator
|
||||||
|
// The field will be set by prefix "oidc_additional_"
|
||||||
|
OidcAdditionalEndpointParams map[string]string `ini:"-" json:"oidc_additional_endpoint_params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultOidcClientConf() OidcClientConfig {
|
||||||
|
return OidcClientConfig{
|
||||||
|
OidcClientID: "",
|
||||||
|
OidcClientSecret: "",
|
||||||
|
OidcAudience: "",
|
||||||
|
OidcScope: "",
|
||||||
|
OidcTokenEndpointURL: "",
|
||||||
|
OidcAdditionalEndpointParams: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OidcServerConfig struct {
|
||||||
|
// OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer
|
||||||
|
// will be used to load public keys to verify signature and will be compared
|
||||||
|
// with the issuer claim in the OIDC token. It will be used if
|
||||||
|
// AuthenticationMethod == "oidc". By default, this value is "".
|
||||||
|
OidcIssuer string `ini:"oidc_issuer" json:"oidc_issuer"`
|
||||||
|
// OidcAudience specifies the audience OIDC tokens should contain when validated.
|
||||||
|
// If this value is empty, audience ("client ID") verification will be skipped.
|
||||||
|
// It will be used when AuthenticationMethod == "oidc". By default, this
|
||||||
|
// value is "".
|
||||||
|
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
||||||
|
// OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is
|
||||||
|
// expired. It will be used when AuthenticationMethod == "oidc". By default, this
|
||||||
|
// value is false.
|
||||||
|
OidcSkipExpiryCheck bool `ini:"oidc_skip_expiry_check" json:"oidc_skip_expiry_check"`
|
||||||
|
// OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's
|
||||||
|
// issuer claim matches the issuer specified in OidcIssuer. It will be used when
|
||||||
|
// AuthenticationMethod == "oidc". By default, this value is false.
|
||||||
|
OidcSkipIssuerCheck bool `ini:"oidc_skip_issuer_check" json:"oidc_skip_issuer_check"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultOidcServerConf() OidcServerConfig {
|
||||||
|
return OidcServerConfig{
|
||||||
|
OidcIssuer: "",
|
||||||
|
OidcAudience: "",
|
||||||
|
OidcSkipExpiryCheck: false,
|
||||||
|
OidcSkipIssuerCheck: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenConfig struct {
|
||||||
|
// Token specifies the authorization token used to create keys to be sent
|
||||||
|
// to the server. The server must have a matching token for authorization
|
||||||
|
// to succeed. By default, this value is "".
|
||||||
|
Token string `ini:"token" json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultTokenConf() TokenConfig {
|
||||||
|
return TokenConfig{
|
||||||
|
Token: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
117
pkg/auth/oidc.go
117
pkg/auth/oidc.go
@@ -19,105 +19,40 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/samber/lo"
|
||||||
"golang.org/x/oauth2/clientcredentials"
|
"golang.org/x/oauth2/clientcredentials"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OidcClientConfig struct {
|
|
||||||
// OidcClientID specifies the client ID to use to get a token in OIDC
|
|
||||||
// authentication if AuthenticationMethod == "oidc". By default, this value
|
|
||||||
// is "".
|
|
||||||
OidcClientID string `ini:"oidc_client_id" json:"oidc_client_id"`
|
|
||||||
// OidcClientSecret specifies the client secret to use to get a token in OIDC
|
|
||||||
// authentication if AuthenticationMethod == "oidc". By default, this value
|
|
||||||
// is "".
|
|
||||||
OidcClientSecret string `ini:"oidc_client_secret" json:"oidc_client_secret"`
|
|
||||||
// OidcAudience specifies the audience of the token in OIDC authentication
|
|
||||||
// if AuthenticationMethod == "oidc". By default, this value is "".
|
|
||||||
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
|
||||||
// OidcScope specifies the scope of the token in OIDC authentication
|
|
||||||
// if AuthenticationMethod == "oidc". By default, this value is "".
|
|
||||||
OidcScope string `ini:"oidc_scope" json:"oidc_scope"`
|
|
||||||
// OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
|
||||||
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
|
||||||
// By default, this value is "".
|
|
||||||
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
|
|
||||||
|
|
||||||
// OidcAdditionalEndpointParams specifies additional parameters to be sent
|
|
||||||
// this field will be transfer to map[string][]string in OIDC token generator
|
|
||||||
// The field will be set by prefix "oidc_additional_"
|
|
||||||
OidcAdditionalEndpointParams map[string]string `ini:"-" json:"oidc_additional_endpoint_params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefaultOidcClientConf() OidcClientConfig {
|
|
||||||
return OidcClientConfig{
|
|
||||||
OidcClientID: "",
|
|
||||||
OidcClientSecret: "",
|
|
||||||
OidcAudience: "",
|
|
||||||
OidcScope: "",
|
|
||||||
OidcTokenEndpointURL: "",
|
|
||||||
OidcAdditionalEndpointParams: make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type OidcServerConfig struct {
|
|
||||||
// OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer
|
|
||||||
// will be used to load public keys to verify signature and will be compared
|
|
||||||
// with the issuer claim in the OIDC token. It will be used if
|
|
||||||
// AuthenticationMethod == "oidc". By default, this value is "".
|
|
||||||
OidcIssuer string `ini:"oidc_issuer" json:"oidc_issuer"`
|
|
||||||
// OidcAudience specifies the audience OIDC tokens should contain when validated.
|
|
||||||
// If this value is empty, audience ("client ID") verification will be skipped.
|
|
||||||
// It will be used when AuthenticationMethod == "oidc". By default, this
|
|
||||||
// value is "".
|
|
||||||
OidcAudience string `ini:"oidc_audience" json:"oidc_audience"`
|
|
||||||
// OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is
|
|
||||||
// expired. It will be used when AuthenticationMethod == "oidc". By default, this
|
|
||||||
// value is false.
|
|
||||||
OidcSkipExpiryCheck bool `ini:"oidc_skip_expiry_check" json:"oidc_skip_expiry_check"`
|
|
||||||
// OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's
|
|
||||||
// issuer claim matches the issuer specified in OidcIssuer. It will be used when
|
|
||||||
// AuthenticationMethod == "oidc". By default, this value is false.
|
|
||||||
OidcSkipIssuerCheck bool `ini:"oidc_skip_issuer_check" json:"oidc_skip_issuer_check"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefaultOidcServerConf() OidcServerConfig {
|
|
||||||
return OidcServerConfig{
|
|
||||||
OidcIssuer: "",
|
|
||||||
OidcAudience: "",
|
|
||||||
OidcSkipExpiryCheck: false,
|
|
||||||
OidcSkipIssuerCheck: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type OidcAuthProvider struct {
|
type OidcAuthProvider struct {
|
||||||
BaseConfig
|
additionalAuthScopes []v1.AuthScope
|
||||||
|
|
||||||
tokenGenerator *clientcredentials.Config
|
tokenGenerator *clientcredentials.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider {
|
func NewOidcAuthSetter(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCClientConfig) *OidcAuthProvider {
|
||||||
eps := make(map[string][]string)
|
eps := make(map[string][]string)
|
||||||
for k, v := range cfg.OidcAdditionalEndpointParams {
|
for k, v := range cfg.AdditionalEndpointParams {
|
||||||
eps[k] = []string{v}
|
eps[k] = []string{v}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.OidcAudience != "" {
|
if cfg.Audience != "" {
|
||||||
eps["audience"] = []string{cfg.OidcAudience}
|
eps["audience"] = []string{cfg.Audience}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenGenerator := &clientcredentials.Config{
|
tokenGenerator := &clientcredentials.Config{
|
||||||
ClientID: cfg.OidcClientID,
|
ClientID: cfg.ClientID,
|
||||||
ClientSecret: cfg.OidcClientSecret,
|
ClientSecret: cfg.ClientSecret,
|
||||||
Scopes: []string{cfg.OidcScope},
|
Scopes: []string{cfg.Scope},
|
||||||
TokenURL: cfg.OidcTokenEndpointURL,
|
TokenURL: cfg.TokenEndpointURL,
|
||||||
EndpointParams: eps,
|
EndpointParams: eps,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &OidcAuthProvider{
|
return &OidcAuthProvider{
|
||||||
BaseConfig: baseCfg,
|
additionalAuthScopes: additionalAuthScopes,
|
||||||
tokenGenerator: tokenGenerator,
|
tokenGenerator: tokenGenerator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +70,7 @@ func (auth *OidcAuthProvider) SetLogin(loginMsg *msg.Login) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
|
func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
|
||||||
if !auth.AuthenticateHeartBeats {
|
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +79,7 @@ func (auth *OidcAuthProvider) SetPing(pingMsg *msg.Ping) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
||||||
if !auth.AuthenticateNewWorkConns {
|
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,26 +88,26 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OidcAuthConsumer struct {
|
type OidcAuthConsumer struct {
|
||||||
BaseConfig
|
additionalAuthScopes []v1.AuthScope
|
||||||
|
|
||||||
verifier *oidc.IDTokenVerifier
|
verifier *oidc.IDTokenVerifier
|
||||||
subjectFromLogin string
|
subjectFromLogin string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOidcAuthVerifier(baseCfg BaseConfig, cfg OidcServerConfig) *OidcAuthConsumer {
|
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCServerConfig) *OidcAuthConsumer {
|
||||||
provider, err := oidc.NewProvider(context.Background(), cfg.OidcIssuer)
|
provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
verifierConf := oidc.Config{
|
verifierConf := oidc.Config{
|
||||||
ClientID: cfg.OidcAudience,
|
ClientID: cfg.Audience,
|
||||||
SkipClientIDCheck: cfg.OidcAudience == "",
|
SkipClientIDCheck: cfg.Audience == "",
|
||||||
SkipExpiryCheck: cfg.OidcSkipExpiryCheck,
|
SkipExpiryCheck: cfg.SkipExpiryCheck,
|
||||||
SkipIssuerCheck: cfg.OidcSkipIssuerCheck,
|
SkipIssuerCheck: cfg.SkipIssuerCheck,
|
||||||
}
|
}
|
||||||
return &OidcAuthConsumer{
|
return &OidcAuthConsumer{
|
||||||
BaseConfig: baseCfg,
|
additionalAuthScopes: additionalAuthScopes,
|
||||||
verifier: provider.Verifier(&verifierConf),
|
verifier: provider.Verifier(&verifierConf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +135,7 @@ func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
|
func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
|
||||||
if !auth.AuthenticateHeartBeats {
|
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +143,7 @@ func (auth *OidcAuthConsumer) VerifyPing(pingMsg *msg.Ping) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
func (auth *OidcAuthConsumer) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (err error) {
|
||||||
if !auth.AuthenticateNewWorkConns {
|
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,43 +18,32 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TokenConfig struct {
|
|
||||||
// Token specifies the authorization token used to create keys to be sent
|
|
||||||
// to the server. The server must have a matching token for authorization
|
|
||||||
// to succeed. By default, this value is "".
|
|
||||||
Token string `ini:"token" json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefaultTokenConf() TokenConfig {
|
|
||||||
return TokenConfig{
|
|
||||||
Token: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenAuthSetterVerifier struct {
|
type TokenAuthSetterVerifier struct {
|
||||||
BaseConfig
|
additionalAuthScopes []v1.AuthScope
|
||||||
|
token string
|
||||||
token string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTokenAuth(baseCfg BaseConfig, cfg TokenConfig) *TokenAuthSetterVerifier {
|
func NewTokenAuth(additionalAuthScopes []v1.AuthScope, token string) *TokenAuthSetterVerifier {
|
||||||
return &TokenAuthSetterVerifier{
|
return &TokenAuthSetterVerifier{
|
||||||
BaseConfig: baseCfg,
|
additionalAuthScopes: additionalAuthScopes,
|
||||||
token: cfg.Token,
|
token: token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) (err error) {
|
func (auth *TokenAuthSetterVerifier) SetLogin(loginMsg *msg.Login) error {
|
||||||
loginMsg.PrivilegeKey = util.GetAuthKey(auth.token, loginMsg.Timestamp)
|
loginMsg.PrivilegeKey = util.GetAuthKey(auth.token, loginMsg.Timestamp)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
||||||
if !auth.AuthenticateHeartBeats {
|
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +53,7 @@ func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
||||||
if !auth.AuthenticateNewWorkConns {
|
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +70,7 @@ func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
|
func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
|
||||||
if !auth.AuthenticateHeartBeats {
|
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +81,7 @@ func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
|
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
|
||||||
if !auth.AuthenticateNewWorkConns {
|
if !lo.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,680 +0,0 @@
|
|||||||
// Copyright 2020 The frp Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
testUser = "test"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testClientBytesWithFull = []byte(`
|
|
||||||
# [common] is integral section
|
|
||||||
[common]
|
|
||||||
server_addr = 0.0.0.9
|
|
||||||
server_port = 7009
|
|
||||||
http_proxy = http://user:passwd@192.168.1.128:8080
|
|
||||||
log_file = ./frpc.log9
|
|
||||||
log_way = file
|
|
||||||
log_level = info9
|
|
||||||
log_max_days = 39
|
|
||||||
disable_log_color = false
|
|
||||||
authenticate_heartbeats = false
|
|
||||||
authenticate_new_work_conns = false
|
|
||||||
token = 12345678
|
|
||||||
oidc_client_id = client-id
|
|
||||||
oidc_client_secret = client-secret
|
|
||||||
oidc_audience = audience
|
|
||||||
oidc_token_endpoint_url = endpoint_url
|
|
||||||
admin_addr = 127.0.0.9
|
|
||||||
admin_port = 7409
|
|
||||||
admin_user = admin9
|
|
||||||
admin_pwd = admin9
|
|
||||||
assets_dir = ./static9
|
|
||||||
pool_count = 59
|
|
||||||
tcp_mux
|
|
||||||
user = your_name
|
|
||||||
login_fail_exit
|
|
||||||
protocol = tcp
|
|
||||||
tls_enable = true
|
|
||||||
tls_cert_file = client.crt
|
|
||||||
tls_key_file = client.key
|
|
||||||
tls_trusted_ca_file = ca.crt
|
|
||||||
tls_server_name = example.com
|
|
||||||
dns_server = 8.8.8.9
|
|
||||||
start = ssh,dns
|
|
||||||
heartbeat_interval = 39
|
|
||||||
heartbeat_timeout = 99
|
|
||||||
meta_var1 = 123
|
|
||||||
meta_var2 = 234
|
|
||||||
udp_packet_size = 1509
|
|
||||||
|
|
||||||
# all proxy
|
|
||||||
[ssh]
|
|
||||||
type = tcp
|
|
||||||
local_ip = 127.0.0.9
|
|
||||||
local_port = 29
|
|
||||||
bandwidth_limit = 19MB
|
|
||||||
bandwidth_limit_mode = server
|
|
||||||
use_encryption
|
|
||||||
use_compression
|
|
||||||
remote_port = 6009
|
|
||||||
group = test_group
|
|
||||||
group_key = 123456
|
|
||||||
health_check_type = tcp
|
|
||||||
health_check_timeout_s = 3
|
|
||||||
health_check_max_failed = 3
|
|
||||||
health_check_interval_s = 19
|
|
||||||
meta_var1 = 123
|
|
||||||
meta_var2 = 234
|
|
||||||
|
|
||||||
[ssh_random]
|
|
||||||
type = tcp
|
|
||||||
local_ip = 127.0.0.9
|
|
||||||
local_port = 29
|
|
||||||
remote_port = 9
|
|
||||||
|
|
||||||
[range:tcp_port]
|
|
||||||
type = tcp
|
|
||||||
local_ip = 127.0.0.9
|
|
||||||
local_port = 6010-6011,6019
|
|
||||||
remote_port = 6010-6011,6019
|
|
||||||
use_encryption = false
|
|
||||||
use_compression = false
|
|
||||||
|
|
||||||
[dns]
|
|
||||||
type = udp
|
|
||||||
local_ip = 114.114.114.114
|
|
||||||
local_port = 59
|
|
||||||
remote_port = 6009
|
|
||||||
use_encryption
|
|
||||||
use_compression
|
|
||||||
|
|
||||||
[range:udp_port]
|
|
||||||
type = udp
|
|
||||||
local_ip = 114.114.114.114
|
|
||||||
local_port = 6000,6010-6011
|
|
||||||
remote_port = 6000,6010-6011
|
|
||||||
use_encryption
|
|
||||||
use_compression
|
|
||||||
|
|
||||||
[web01]
|
|
||||||
type = http
|
|
||||||
local_ip = 127.0.0.9
|
|
||||||
local_port = 89
|
|
||||||
use_encryption
|
|
||||||
use_compression
|
|
||||||
http_user = admin
|
|
||||||
http_pwd = admin
|
|
||||||
subdomain = web01
|
|
||||||
custom_domains = web02.yourdomain.com
|
|
||||||
locations = /,/pic
|
|
||||||
host_header_rewrite = example.com
|
|
||||||
header_X-From-Where = frp
|
|
||||||
health_check_type = http
|
|
||||||
health_check_url = /status
|
|
||||||
health_check_interval_s = 19
|
|
||||||
health_check_max_failed = 3
|
|
||||||
health_check_timeout_s = 3
|
|
||||||
|
|
||||||
[web02]
|
|
||||||
type = https
|
|
||||||
local_ip = 127.0.0.9
|
|
||||||
local_port = 8009
|
|
||||||
use_encryption
|
|
||||||
use_compression
|
|
||||||
subdomain = web01
|
|
||||||
custom_domains = web02.yourdomain.com
|
|
||||||
proxy_protocol_version = v2
|
|
||||||
|
|
||||||
[secret_tcp]
|
|
||||||
type = stcp
|
|
||||||
sk = abcdefg
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 22
|
|
||||||
use_encryption = false
|
|
||||||
use_compression = false
|
|
||||||
|
|
||||||
[p2p_tcp]
|
|
||||||
type = xtcp
|
|
||||||
sk = abcdefg
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 22
|
|
||||||
use_encryption = false
|
|
||||||
use_compression = false
|
|
||||||
|
|
||||||
[tcpmuxhttpconnect]
|
|
||||||
type = tcpmux
|
|
||||||
multiplexer = httpconnect
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 10701
|
|
||||||
custom_domains = tunnel1
|
|
||||||
|
|
||||||
[plugin_unix_domain_socket]
|
|
||||||
type = tcp
|
|
||||||
remote_port = 6003
|
|
||||||
plugin = unix_domain_socket
|
|
||||||
plugin_unix_path = /var/run/docker.sock
|
|
||||||
|
|
||||||
[plugin_http_proxy]
|
|
||||||
type = tcp
|
|
||||||
remote_port = 6004
|
|
||||||
plugin = http_proxy
|
|
||||||
plugin_http_user = abc
|
|
||||||
plugin_http_passwd = abc
|
|
||||||
|
|
||||||
[plugin_socks5]
|
|
||||||
type = tcp
|
|
||||||
remote_port = 6005
|
|
||||||
plugin = socks5
|
|
||||||
plugin_user = abc
|
|
||||||
plugin_passwd = abc
|
|
||||||
|
|
||||||
[plugin_static_file]
|
|
||||||
type = tcp
|
|
||||||
remote_port = 6006
|
|
||||||
plugin = static_file
|
|
||||||
plugin_local_path = /var/www/blog
|
|
||||||
plugin_strip_prefix = static
|
|
||||||
plugin_http_user = abc
|
|
||||||
plugin_http_passwd = abc
|
|
||||||
|
|
||||||
[plugin_https2http]
|
|
||||||
type = https
|
|
||||||
custom_domains = test.yourdomain.com
|
|
||||||
plugin = https2http
|
|
||||||
plugin_local_addr = 127.0.0.1:80
|
|
||||||
plugin_crt_path = ./server.crt
|
|
||||||
plugin_key_path = ./server.key
|
|
||||||
plugin_host_header_rewrite = 127.0.0.1
|
|
||||||
plugin_header_X-From-Where = frp
|
|
||||||
|
|
||||||
[plugin_http2https]
|
|
||||||
type = http
|
|
||||||
custom_domains = test.yourdomain.com
|
|
||||||
plugin = http2https
|
|
||||||
plugin_local_addr = 127.0.0.1:443
|
|
||||||
plugin_host_header_rewrite = 127.0.0.1
|
|
||||||
plugin_header_X-From-Where = frp
|
|
||||||
|
|
||||||
# visitor
|
|
||||||
[secret_tcp_visitor]
|
|
||||||
role = visitor
|
|
||||||
type = stcp
|
|
||||||
server_name = secret_tcp
|
|
||||||
sk = abcdefg
|
|
||||||
bind_addr = 127.0.0.1
|
|
||||||
bind_port = 9000
|
|
||||||
use_encryption = false
|
|
||||||
use_compression = false
|
|
||||||
|
|
||||||
[p2p_tcp_visitor]
|
|
||||||
role = visitor
|
|
||||||
type = xtcp
|
|
||||||
server_name = p2p_tcp
|
|
||||||
sk = abcdefg
|
|
||||||
bind_addr = 127.0.0.1
|
|
||||||
bind_port = 9001
|
|
||||||
use_encryption = false
|
|
||||||
use_compression = false
|
|
||||||
`)
|
|
||||||
|
|
||||||
func Test_LoadClientCommonConf(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
expected := ClientCommonConf{
|
|
||||||
ClientConfig: auth.ClientConfig{
|
|
||||||
BaseConfig: auth.BaseConfig{
|
|
||||||
AuthenticationMethod: "token",
|
|
||||||
AuthenticateHeartBeats: false,
|
|
||||||
AuthenticateNewWorkConns: false,
|
|
||||||
},
|
|
||||||
TokenConfig: auth.TokenConfig{
|
|
||||||
Token: "12345678",
|
|
||||||
},
|
|
||||||
OidcClientConfig: auth.OidcClientConfig{
|
|
||||||
OidcClientID: "client-id",
|
|
||||||
OidcClientSecret: "client-secret",
|
|
||||||
OidcAudience: "audience",
|
|
||||||
OidcTokenEndpointURL: "endpoint_url",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ServerAddr: "0.0.0.9",
|
|
||||||
ServerPort: 7009,
|
|
||||||
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
|
||||||
DialServerTimeout: 10,
|
|
||||||
DialServerKeepAlive: 7200,
|
|
||||||
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
|
|
||||||
LogFile: "./frpc.log9",
|
|
||||||
LogWay: "file",
|
|
||||||
LogLevel: "info9",
|
|
||||||
LogMaxDays: 39,
|
|
||||||
DisableLogColor: false,
|
|
||||||
AdminAddr: "127.0.0.9",
|
|
||||||
AdminPort: 7409,
|
|
||||||
AdminUser: "admin9",
|
|
||||||
AdminPwd: "admin9",
|
|
||||||
AssetsDir: "./static9",
|
|
||||||
PoolCount: 59,
|
|
||||||
TCPMux: true,
|
|
||||||
TCPMuxKeepaliveInterval: 60,
|
|
||||||
User: "your_name",
|
|
||||||
LoginFailExit: true,
|
|
||||||
Protocol: "tcp",
|
|
||||||
QUICKeepalivePeriod: 10,
|
|
||||||
QUICMaxIdleTimeout: 30,
|
|
||||||
QUICMaxIncomingStreams: 100000,
|
|
||||||
TLSEnable: true,
|
|
||||||
TLSCertFile: "client.crt",
|
|
||||||
TLSKeyFile: "client.key",
|
|
||||||
TLSTrustedCaFile: "ca.crt",
|
|
||||||
TLSServerName: "example.com",
|
|
||||||
DisableCustomTLSFirstByte: true,
|
|
||||||
DNSServer: "8.8.8.9",
|
|
||||||
Start: []string{"ssh", "dns"},
|
|
||||||
HeartbeatInterval: 39,
|
|
||||||
HeartbeatTimeout: 99,
|
|
||||||
Metas: map[string]string{
|
|
||||||
"var1": "123",
|
|
||||||
"var2": "234",
|
|
||||||
},
|
|
||||||
UDPPacketSize: 1509,
|
|
||||||
IncludeConfigFiles: []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
common, err := UnmarshalClientConfFromIni(testClientBytesWithFull)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.EqualValues(expected, common)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_LoadClientBasicConf(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
proxyExpected := map[string]ProxyConf{
|
|
||||||
testUser + ".ssh": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".ssh",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
UseCompression: true,
|
|
||||||
UseEncryption: true,
|
|
||||||
Group: "test_group",
|
|
||||||
GroupKey: "123456",
|
|
||||||
BandwidthLimit: MustBandwidthQuantity("19MB"),
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeServer,
|
|
||||||
Metas: map[string]string{
|
|
||||||
"var1": "123",
|
|
||||||
"var2": "234",
|
|
||||||
},
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 29,
|
|
||||||
},
|
|
||||||
HealthCheckConf: HealthCheckConf{
|
|
||||||
HealthCheckType: consts.TCPProxy,
|
|
||||||
HealthCheckTimeoutS: 3,
|
|
||||||
HealthCheckMaxFailed: 3,
|
|
||||||
HealthCheckIntervalS: 19,
|
|
||||||
HealthCheckAddr: "127.0.0.9:29",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RemotePort: 6009,
|
|
||||||
},
|
|
||||||
testUser + ".ssh_random": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".ssh_random",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 29,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 9,
|
|
||||||
},
|
|
||||||
testUser + ".tcp_port_0": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".tcp_port_0",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 6010,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6010,
|
|
||||||
},
|
|
||||||
testUser + ".tcp_port_1": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".tcp_port_1",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 6011,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6011,
|
|
||||||
},
|
|
||||||
testUser + ".tcp_port_2": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".tcp_port_2",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 6019,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6019,
|
|
||||||
},
|
|
||||||
testUser + ".dns": &UDPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".dns",
|
|
||||||
ProxyType: consts.UDPProxy,
|
|
||||||
UseEncryption: true,
|
|
||||||
UseCompression: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "114.114.114.114",
|
|
||||||
LocalPort: 59,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6009,
|
|
||||||
},
|
|
||||||
testUser + ".udp_port_0": &UDPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".udp_port_0",
|
|
||||||
ProxyType: consts.UDPProxy,
|
|
||||||
UseEncryption: true,
|
|
||||||
UseCompression: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "114.114.114.114",
|
|
||||||
LocalPort: 6000,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6000,
|
|
||||||
},
|
|
||||||
testUser + ".udp_port_1": &UDPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".udp_port_1",
|
|
||||||
ProxyType: consts.UDPProxy,
|
|
||||||
UseEncryption: true,
|
|
||||||
UseCompression: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "114.114.114.114",
|
|
||||||
LocalPort: 6010,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6010,
|
|
||||||
},
|
|
||||||
testUser + ".udp_port_2": &UDPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".udp_port_2",
|
|
||||||
ProxyType: consts.UDPProxy,
|
|
||||||
UseEncryption: true,
|
|
||||||
UseCompression: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "114.114.114.114",
|
|
||||||
LocalPort: 6011,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6011,
|
|
||||||
},
|
|
||||||
testUser + ".web01": &HTTPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".web01",
|
|
||||||
ProxyType: consts.HTTPProxy,
|
|
||||||
UseCompression: true,
|
|
||||||
UseEncryption: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 89,
|
|
||||||
},
|
|
||||||
HealthCheckConf: HealthCheckConf{
|
|
||||||
HealthCheckType: consts.HTTPProxy,
|
|
||||||
HealthCheckTimeoutS: 3,
|
|
||||||
HealthCheckMaxFailed: 3,
|
|
||||||
HealthCheckIntervalS: 19,
|
|
||||||
HealthCheckURL: "http://127.0.0.9:89/status",
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
DomainConf: DomainConf{
|
|
||||||
CustomDomains: []string{"web02.yourdomain.com"},
|
|
||||||
SubDomain: "web01",
|
|
||||||
},
|
|
||||||
Locations: []string{"/", "/pic"},
|
|
||||||
HTTPUser: "admin",
|
|
||||||
HTTPPwd: "admin",
|
|
||||||
HostHeaderRewrite: "example.com",
|
|
||||||
Headers: map[string]string{
|
|
||||||
"X-From-Where": "frp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testUser + ".web02": &HTTPSProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".web02",
|
|
||||||
ProxyType: consts.HTTPSProxy,
|
|
||||||
UseCompression: true,
|
|
||||||
UseEncryption: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 8009,
|
|
||||||
},
|
|
||||||
ProxyProtocolVersion: "v2",
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
DomainConf: DomainConf{
|
|
||||||
CustomDomains: []string{"web02.yourdomain.com"},
|
|
||||||
SubDomain: "web01",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testUser + ".secret_tcp": &STCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".secret_tcp",
|
|
||||||
ProxyType: consts.STCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
LocalPort: 22,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RoleServerCommonConf: RoleServerCommonConf{
|
|
||||||
Role: "server",
|
|
||||||
Sk: "abcdefg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testUser + ".p2p_tcp": &XTCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".p2p_tcp",
|
|
||||||
ProxyType: consts.XTCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
LocalPort: 22,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RoleServerCommonConf: RoleServerCommonConf{
|
|
||||||
Role: "server",
|
|
||||||
Sk: "abcdefg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testUser + ".tcpmuxhttpconnect": &TCPMuxProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".tcpmuxhttpconnect",
|
|
||||||
ProxyType: consts.TCPMuxProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
LocalPort: 10701,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
DomainConf: DomainConf{
|
|
||||||
CustomDomains: []string{"tunnel1"},
|
|
||||||
SubDomain: "",
|
|
||||||
},
|
|
||||||
Multiplexer: "httpconnect",
|
|
||||||
},
|
|
||||||
testUser + ".plugin_unix_domain_socket": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".plugin_unix_domain_socket",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
Plugin: "unix_domain_socket",
|
|
||||||
PluginParams: map[string]string{
|
|
||||||
"plugin_unix_path": "/var/run/docker.sock",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6003,
|
|
||||||
},
|
|
||||||
testUser + ".plugin_http_proxy": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".plugin_http_proxy",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
Plugin: "http_proxy",
|
|
||||||
PluginParams: map[string]string{
|
|
||||||
"plugin_http_user": "abc",
|
|
||||||
"plugin_http_passwd": "abc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6004,
|
|
||||||
},
|
|
||||||
testUser + ".plugin_socks5": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".plugin_socks5",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
Plugin: "socks5",
|
|
||||||
PluginParams: map[string]string{
|
|
||||||
"plugin_user": "abc",
|
|
||||||
"plugin_passwd": "abc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6005,
|
|
||||||
},
|
|
||||||
testUser + ".plugin_static_file": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".plugin_static_file",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
Plugin: "static_file",
|
|
||||||
PluginParams: map[string]string{
|
|
||||||
"plugin_local_path": "/var/www/blog",
|
|
||||||
"plugin_strip_prefix": "static",
|
|
||||||
"plugin_http_user": "abc",
|
|
||||||
"plugin_http_passwd": "abc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6006,
|
|
||||||
},
|
|
||||||
testUser + ".plugin_https2http": &HTTPSProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".plugin_https2http",
|
|
||||||
ProxyType: consts.HTTPSProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
Plugin: "https2http",
|
|
||||||
PluginParams: map[string]string{
|
|
||||||
"plugin_local_addr": "127.0.0.1:80",
|
|
||||||
"plugin_crt_path": "./server.crt",
|
|
||||||
"plugin_key_path": "./server.key",
|
|
||||||
"plugin_host_header_rewrite": "127.0.0.1",
|
|
||||||
"plugin_header_X-From-Where": "frp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
DomainConf: DomainConf{
|
|
||||||
CustomDomains: []string{"test.yourdomain.com"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testUser + ".plugin_http2https": &HTTPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testUser + ".plugin_http2https",
|
|
||||||
ProxyType: consts.HTTPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
Plugin: "http2https",
|
|
||||||
PluginParams: map[string]string{
|
|
||||||
"plugin_local_addr": "127.0.0.1:443",
|
|
||||||
"plugin_host_header_rewrite": "127.0.0.1",
|
|
||||||
"plugin_header_X-From-Where": "frp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
DomainConf: DomainConf{
|
|
||||||
CustomDomains: []string{"test.yourdomain.com"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
visitorExpected := map[string]VisitorConf{
|
|
||||||
testUser + ".secret_tcp_visitor": &STCPVisitorConf{
|
|
||||||
BaseVisitorConf: BaseVisitorConf{
|
|
||||||
ProxyName: testUser + ".secret_tcp_visitor",
|
|
||||||
ProxyType: consts.STCPProxy,
|
|
||||||
Role: "visitor",
|
|
||||||
Sk: "abcdefg",
|
|
||||||
ServerName: testVisitorPrefix + "secret_tcp",
|
|
||||||
BindAddr: "127.0.0.1",
|
|
||||||
BindPort: 9000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testUser + ".p2p_tcp_visitor": &XTCPVisitorConf{
|
|
||||||
BaseVisitorConf: BaseVisitorConf{
|
|
||||||
ProxyName: testUser + ".p2p_tcp_visitor",
|
|
||||||
ProxyType: consts.XTCPProxy,
|
|
||||||
Role: "visitor",
|
|
||||||
Sk: "abcdefg",
|
|
||||||
ServerName: testProxyPrefix + "p2p_tcp",
|
|
||||||
BindAddr: "127.0.0.1",
|
|
||||||
BindPort: 9001,
|
|
||||||
},
|
|
||||||
Protocol: "quic",
|
|
||||||
MaxRetriesAnHour: 8,
|
|
||||||
MinRetryInterval: 90,
|
|
||||||
FallbackTimeoutMs: 1000,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyActual, visitorActual, err := LoadAllProxyConfsFromIni(testUser, testClientBytesWithFull, nil)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(proxyExpected, proxyActual)
|
|
||||||
assert.Equal(visitorExpected, visitorActual)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020 The frp Authors
|
// Copyright 2023 The frp Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -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 config
|
package legacy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -20,17 +20,19 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
legacyauth "github.com/fatedier/frp/pkg/auth/legacy"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClientCommonConf contains information for a client service. It is
|
// ClientCommonConf is the configuration parsed from ini.
|
||||||
|
// It contains information for a client service. It is
|
||||||
// recommended to use GetDefaultClientConf instead of creating this object
|
// recommended to use GetDefaultClientConf instead of creating this object
|
||||||
// directly, so that all unspecified fields have reasonable default values.
|
// directly, so that all unspecified fields have reasonable default values.
|
||||||
type ClientCommonConf struct {
|
type ClientCommonConf struct {
|
||||||
auth.ClientConfig `ini:",extends"`
|
legacyauth.ClientConfig `ini:",extends"`
|
||||||
|
|
||||||
// ServerAddr specifies the address of the server to connect to. By
|
// ServerAddr specifies the address of the server to connect to. By
|
||||||
// default, this value is "0.0.0.0".
|
// default, this value is "0.0.0.0".
|
||||||
@@ -117,13 +119,13 @@ type ClientCommonConf struct {
|
|||||||
Start []string `ini:"start" json:"start"`
|
Start []string `ini:"start" json:"start"`
|
||||||
// Start map[string]struct{} `json:"start"`
|
// Start map[string]struct{} `json:"start"`
|
||||||
// Protocol specifies the protocol to use when interacting with the server.
|
// Protocol specifies the protocol to use when interacting with the server.
|
||||||
// Valid values are "tcp", "kcp", "quic" and "websocket". By default, this value
|
// Valid values are "tcp", "kcp", "quic", "websocket" and "wss". By default, this value
|
||||||
// is "tcp".
|
// is "tcp".
|
||||||
Protocol string `ini:"protocol" json:"protocol"`
|
Protocol string `ini:"protocol" json:"protocol"`
|
||||||
// QUIC protocol options
|
// QUIC protocol options
|
||||||
QUICKeepalivePeriod int `ini:"quic_keepalive_period" json:"quic_keepalive_period" validate:"gte=0"`
|
QUICKeepalivePeriod int `ini:"quic_keepalive_period" json:"quic_keepalive_period"`
|
||||||
QUICMaxIdleTimeout int `ini:"quic_max_idle_timeout" json:"quic_max_idle_timeout" validate:"gte=0"`
|
QUICMaxIdleTimeout int `ini:"quic_max_idle_timeout" json:"quic_max_idle_timeout"`
|
||||||
QUICMaxIncomingStreams int `ini:"quic_max_incoming_streams" json:"quic_max_incoming_streams" validate:"gte=0"`
|
QUICMaxIncomingStreams int `ini:"quic_max_incoming_streams" json:"quic_max_incoming_streams"`
|
||||||
// TLSEnable specifies whether or not TLS should be used when communicating
|
// TLSEnable specifies whether or not TLS should be used when communicating
|
||||||
// with the server. If "tls_cert_file" and "tls_key_file" are valid,
|
// with the server. If "tls_cert_file" and "tls_key_file" are valid,
|
||||||
// client will load the supplied tls configuration.
|
// client will load the supplied tls configuration.
|
||||||
@@ -167,85 +169,6 @@ type ClientCommonConf struct {
|
|||||||
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultClientConf returns a client configuration with default values.
|
|
||||||
func GetDefaultClientConf() ClientCommonConf {
|
|
||||||
return ClientCommonConf{
|
|
||||||
ClientConfig: auth.GetDefaultClientConf(),
|
|
||||||
ServerAddr: "0.0.0.0",
|
|
||||||
ServerPort: 7000,
|
|
||||||
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
|
||||||
DialServerTimeout: 10,
|
|
||||||
DialServerKeepAlive: 7200,
|
|
||||||
HTTPProxy: os.Getenv("http_proxy"),
|
|
||||||
LogFile: "console",
|
|
||||||
LogWay: "console",
|
|
||||||
LogLevel: "info",
|
|
||||||
LogMaxDays: 3,
|
|
||||||
AdminAddr: "127.0.0.1",
|
|
||||||
PoolCount: 1,
|
|
||||||
TCPMux: true,
|
|
||||||
TCPMuxKeepaliveInterval: 60,
|
|
||||||
LoginFailExit: true,
|
|
||||||
Start: make([]string, 0),
|
|
||||||
Protocol: "tcp",
|
|
||||||
QUICKeepalivePeriod: 10,
|
|
||||||
QUICMaxIdleTimeout: 30,
|
|
||||||
QUICMaxIncomingStreams: 100000,
|
|
||||||
TLSEnable: true,
|
|
||||||
DisableCustomTLSFirstByte: true,
|
|
||||||
HeartbeatInterval: 30,
|
|
||||||
HeartbeatTimeout: 90,
|
|
||||||
Metas: make(map[string]string),
|
|
||||||
UDPPacketSize: 1500,
|
|
||||||
IncludeConfigFiles: make([]string, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *ClientCommonConf) Complete() {
|
|
||||||
if cfg.LogFile == "console" {
|
|
||||||
cfg.LogWay = "console"
|
|
||||||
} else {
|
|
||||||
cfg.LogWay = "file"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *ClientCommonConf) Validate() error {
|
|
||||||
if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
|
|
||||||
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
|
||||||
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.TLSEnable {
|
|
||||||
if cfg.TLSCertFile != "" {
|
|
||||||
fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.TLSKeyFile != "" {
|
|
||||||
fmt.Println("WARNING! tls_key_file is invalid when tls_enable is false")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.TLSTrustedCaFile != "" {
|
|
||||||
fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Protocol != "tcp" && cfg.Protocol != "kcp" && cfg.Protocol != "websocket" && cfg.Protocol != "quic" {
|
|
||||||
return fmt.Errorf("invalid protocol")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range cfg.IncludeConfigFiles {
|
|
||||||
absDir, err := filepath.Abs(filepath.Dir(f))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("include: parse directory of %s failed: %v", f, absDir)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("include: directory of %s not exist", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 interface{}) (ClientCommonConf, error) {
|
||||||
f, err := ini.LoadSources(ini.LoadOptions{
|
f, err := ini.LoadSources(ini.LoadOptions{
|
||||||
@@ -420,3 +343,74 @@ func copySection(source, target *ini.Section) {
|
|||||||
_, _ = target.NewKey(key, value)
|
_, _ = target.NewKey(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultClientConf returns a client configuration with default values.
|
||||||
|
func GetDefaultClientConf() ClientCommonConf {
|
||||||
|
return ClientCommonConf{
|
||||||
|
ClientConfig: legacyauth.GetDefaultClientConf(),
|
||||||
|
ServerAddr: "0.0.0.0",
|
||||||
|
ServerPort: 7000,
|
||||||
|
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
||||||
|
DialServerTimeout: 10,
|
||||||
|
DialServerKeepAlive: 7200,
|
||||||
|
HTTPProxy: os.Getenv("http_proxy"),
|
||||||
|
LogFile: "console",
|
||||||
|
LogWay: "console",
|
||||||
|
LogLevel: "info",
|
||||||
|
LogMaxDays: 3,
|
||||||
|
AdminAddr: "127.0.0.1",
|
||||||
|
PoolCount: 1,
|
||||||
|
TCPMux: true,
|
||||||
|
TCPMuxKeepaliveInterval: 60,
|
||||||
|
LoginFailExit: true,
|
||||||
|
Start: make([]string, 0),
|
||||||
|
Protocol: "tcp",
|
||||||
|
QUICKeepalivePeriod: 10,
|
||||||
|
QUICMaxIdleTimeout: 30,
|
||||||
|
QUICMaxIncomingStreams: 100000,
|
||||||
|
TLSEnable: true,
|
||||||
|
DisableCustomTLSFirstByte: true,
|
||||||
|
HeartbeatInterval: 30,
|
||||||
|
HeartbeatTimeout: 90,
|
||||||
|
Metas: make(map[string]string),
|
||||||
|
UDPPacketSize: 1500,
|
||||||
|
IncludeConfigFiles: make([]string, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *ClientCommonConf) Validate() error {
|
||||||
|
if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
|
||||||
|
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||||
|
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.TLSEnable {
|
||||||
|
if cfg.TLSCertFile != "" {
|
||||||
|
fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.TLSKeyFile != "" {
|
||||||
|
fmt.Println("WARNING! tls_key_file is invalid when tls_enable is false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.TLSTrustedCaFile != "" {
|
||||||
|
fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lo.Contains([]string{"tcp", "kcp", "quic", "websocket", "wss"}, cfg.Protocol) {
|
||||||
|
return fmt.Errorf("invalid protocol")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range cfg.IncludeConfigFiles {
|
||||||
|
absDir, err := filepath.Abs(filepath.Dir(f))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("include: parse directory of %s failed: %v", f, err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("include: directory of %s not exist", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
352
pkg/config/legacy/conversion.go
Normal file
352
pkg/config/legacy/conversion.go
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
// Copyright 2023 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 legacy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConfig {
|
||||||
|
out := &v1.ClientCommonConfig{}
|
||||||
|
out.User = conf.User
|
||||||
|
out.Auth.Method = v1.AuthMethod(conf.ClientConfig.AuthenticationMethod)
|
||||||
|
out.Auth.Token = conf.ClientConfig.Token
|
||||||
|
if conf.ClientConfig.AuthenticateHeartBeats {
|
||||||
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
|
||||||
|
}
|
||||||
|
if conf.ClientConfig.AuthenticateNewWorkConns {
|
||||||
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
|
||||||
|
}
|
||||||
|
out.Auth.OIDC.ClientID = conf.ClientConfig.OidcClientID
|
||||||
|
out.Auth.OIDC.ClientSecret = conf.ClientConfig.OidcClientSecret
|
||||||
|
out.Auth.OIDC.Audience = conf.ClientConfig.OidcAudience
|
||||||
|
out.Auth.OIDC.Scope = conf.ClientConfig.OidcScope
|
||||||
|
out.Auth.OIDC.TokenEndpointURL = conf.ClientConfig.OidcTokenEndpointURL
|
||||||
|
out.Auth.OIDC.AdditionalEndpointParams = conf.ClientConfig.OidcAdditionalEndpointParams
|
||||||
|
|
||||||
|
out.ServerAddr = conf.ServerAddr
|
||||||
|
out.ServerPort = conf.ServerPort
|
||||||
|
out.NatHoleSTUNServer = conf.NatHoleSTUNServer
|
||||||
|
out.Transport.DialServerTimeout = conf.DialServerTimeout
|
||||||
|
out.Transport.DialServerKeepAlive = conf.DialServerKeepAlive
|
||||||
|
out.Transport.ConnectServerLocalIP = conf.ConnectServerLocalIP
|
||||||
|
out.Transport.ProxyURL = conf.HTTPProxy
|
||||||
|
out.Transport.PoolCount = conf.PoolCount
|
||||||
|
out.Transport.TCPMux = lo.ToPtr(conf.TCPMux)
|
||||||
|
out.Transport.TCPMuxKeepaliveInterval = conf.TCPMuxKeepaliveInterval
|
||||||
|
out.Transport.Protocol = conf.Protocol
|
||||||
|
out.Transport.HeartbeatInterval = conf.HeartbeatInterval
|
||||||
|
out.Transport.HeartbeatTimeout = conf.HeartbeatTimeout
|
||||||
|
out.Transport.QUIC.KeepalivePeriod = conf.QUICKeepalivePeriod
|
||||||
|
out.Transport.QUIC.MaxIdleTimeout = conf.QUICMaxIdleTimeout
|
||||||
|
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
|
||||||
|
out.Transport.TLS.Enable = lo.ToPtr(conf.TLSEnable)
|
||||||
|
out.Transport.TLS.DisableCustomTLSFirstByte = lo.ToPtr(conf.DisableCustomTLSFirstByte)
|
||||||
|
out.Transport.TLS.TLSConfig.CertFile = conf.TLSCertFile
|
||||||
|
out.Transport.TLS.TLSConfig.KeyFile = conf.TLSKeyFile
|
||||||
|
out.Transport.TLS.TLSConfig.TrustedCaFile = conf.TLSTrustedCaFile
|
||||||
|
out.Transport.TLS.TLSConfig.ServerName = conf.TLSServerName
|
||||||
|
|
||||||
|
out.Log.To = conf.LogFile
|
||||||
|
out.Log.Level = conf.LogLevel
|
||||||
|
out.Log.MaxDays = conf.LogMaxDays
|
||||||
|
out.Log.DisablePrintColor = conf.DisableLogColor
|
||||||
|
|
||||||
|
out.WebServer.Addr = conf.AdminAddr
|
||||||
|
out.WebServer.Port = conf.AdminPort
|
||||||
|
out.WebServer.User = conf.AdminUser
|
||||||
|
out.WebServer.Password = conf.AdminPwd
|
||||||
|
out.WebServer.AssetsDir = conf.AssetsDir
|
||||||
|
out.WebServer.PprofEnable = conf.PprofEnable
|
||||||
|
|
||||||
|
out.DNSServer = conf.DNSServer
|
||||||
|
out.LoginFailExit = lo.ToPtr(conf.LoginFailExit)
|
||||||
|
out.Start = conf.Start
|
||||||
|
out.UDPPacketSize = conf.UDPPacketSize
|
||||||
|
out.Metadatas = conf.Metas
|
||||||
|
out.IncludeConfigFiles = conf.IncludeConfigFiles
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig {
|
||||||
|
out := &v1.ServerConfig{}
|
||||||
|
out.Auth.Method = v1.AuthMethod(conf.ServerConfig.AuthenticationMethod)
|
||||||
|
out.Auth.Token = conf.ServerConfig.Token
|
||||||
|
if conf.ServerConfig.AuthenticateHeartBeats {
|
||||||
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
|
||||||
|
}
|
||||||
|
if conf.ServerConfig.AuthenticateNewWorkConns {
|
||||||
|
out.Auth.AdditionalScopes = append(out.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
|
||||||
|
}
|
||||||
|
out.Auth.OIDC.Audience = conf.ServerConfig.OidcAudience
|
||||||
|
out.Auth.OIDC.Issuer = conf.ServerConfig.OidcIssuer
|
||||||
|
out.Auth.OIDC.SkipExpiryCheck = conf.ServerConfig.OidcSkipExpiryCheck
|
||||||
|
out.Auth.OIDC.SkipIssuerCheck = conf.ServerConfig.OidcSkipIssuerCheck
|
||||||
|
|
||||||
|
out.BindAddr = conf.BindAddr
|
||||||
|
out.BindPort = conf.BindPort
|
||||||
|
out.KCPBindPort = conf.KCPBindPort
|
||||||
|
out.QUICBindPort = conf.QUICBindPort
|
||||||
|
out.Transport.QUIC.KeepalivePeriod = conf.QUICKeepalivePeriod
|
||||||
|
out.Transport.QUIC.MaxIdleTimeout = conf.QUICMaxIdleTimeout
|
||||||
|
out.Transport.QUIC.MaxIncomingStreams = conf.QUICMaxIncomingStreams
|
||||||
|
|
||||||
|
out.ProxyBindAddr = conf.ProxyBindAddr
|
||||||
|
out.VhostHTTPPort = conf.VhostHTTPPort
|
||||||
|
out.VhostHTTPSPort = conf.VhostHTTPSPort
|
||||||
|
out.TCPMuxHTTPConnectPort = conf.TCPMuxHTTPConnectPort
|
||||||
|
out.TCPMuxPassthrough = conf.TCPMuxPassthrough
|
||||||
|
out.VhostHTTPTimeout = conf.VhostHTTPTimeout
|
||||||
|
|
||||||
|
out.WebServer.Addr = conf.DashboardAddr
|
||||||
|
out.WebServer.Port = conf.DashboardPort
|
||||||
|
out.WebServer.User = conf.DashboardUser
|
||||||
|
out.WebServer.Password = conf.DashboardPwd
|
||||||
|
out.WebServer.AssetsDir = conf.AssetsDir
|
||||||
|
if conf.DashboardTLSMode {
|
||||||
|
out.WebServer.TLS = &v1.TLSConfig{}
|
||||||
|
out.WebServer.TLS.CertFile = conf.DashboardTLSCertFile
|
||||||
|
out.WebServer.TLS.KeyFile = conf.DashboardTLSKeyFile
|
||||||
|
out.WebServer.PprofEnable = conf.PprofEnable
|
||||||
|
}
|
||||||
|
|
||||||
|
out.EnablePrometheus = conf.EnablePrometheus
|
||||||
|
|
||||||
|
out.Log.To = conf.LogFile
|
||||||
|
out.Log.Level = conf.LogLevel
|
||||||
|
out.Log.MaxDays = conf.LogMaxDays
|
||||||
|
out.Log.DisablePrintColor = conf.DisableLogColor
|
||||||
|
|
||||||
|
out.DetailedErrorsToClient = lo.ToPtr(conf.DetailedErrorsToClient)
|
||||||
|
out.SubDomainHost = conf.SubDomainHost
|
||||||
|
out.Custom404Page = conf.Custom404Page
|
||||||
|
out.UserConnTimeout = conf.UserConnTimeout
|
||||||
|
out.UDPPacketSize = conf.UDPPacketSize
|
||||||
|
out.NatHoleAnalysisDataReserveHours = conf.NatHoleAnalysisDataReserveHours
|
||||||
|
|
||||||
|
out.Transport.TCPMux = lo.ToPtr(conf.TCPMux)
|
||||||
|
out.Transport.TCPMuxKeepaliveInterval = conf.TCPMuxKeepaliveInterval
|
||||||
|
out.Transport.TCPKeepAlive = conf.TCPKeepAlive
|
||||||
|
out.Transport.MaxPoolCount = conf.MaxPoolCount
|
||||||
|
out.Transport.HeartbeatTimeout = conf.HeartbeatTimeout
|
||||||
|
|
||||||
|
out.Transport.TLS.Force = conf.TLSOnly
|
||||||
|
out.Transport.TLS.CertFile = conf.TLSCertFile
|
||||||
|
out.Transport.TLS.KeyFile = conf.TLSKeyFile
|
||||||
|
out.Transport.TLS.TrustedCaFile = conf.TLSTrustedCaFile
|
||||||
|
|
||||||
|
out.MaxPortsPerClient = conf.MaxPortsPerClient
|
||||||
|
|
||||||
|
for _, v := range conf.HTTPPlugins {
|
||||||
|
out.HTTPPlugins = append(out.HTTPPlugins, v1.HTTPPluginOptions{
|
||||||
|
Name: v.Name,
|
||||||
|
Addr: v.Addr,
|
||||||
|
Path: v.Path,
|
||||||
|
Ops: v.Ops,
|
||||||
|
TLSVerify: v.TLSVerify,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
out.AllowPorts, _ = types.NewPortsRangeSliceFromString(conf.AllowPortsStr)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformHeadersFromPluginParams(params map[string]string) v1.HeaderOperations {
|
||||||
|
out := v1.HeaderOperations{}
|
||||||
|
for k, v := range params {
|
||||||
|
if !strings.HasPrefix(k, "plugin_header_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
|
||||||
|
out.Set[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func Convert_ProxyConf_To_v1_Base(conf ProxyConf) *v1.ProxyBaseConfig {
|
||||||
|
out := &v1.ProxyBaseConfig{}
|
||||||
|
base := conf.GetBaseConfig()
|
||||||
|
|
||||||
|
out.Name = base.ProxyName
|
||||||
|
out.Type = base.ProxyType
|
||||||
|
out.Metadatas = base.Metas
|
||||||
|
|
||||||
|
out.Transport.UseEncryption = base.UseEncryption
|
||||||
|
out.Transport.UseCompression = base.UseCompression
|
||||||
|
out.Transport.BandwidthLimit = base.BandwidthLimit
|
||||||
|
out.Transport.BandwidthLimitMode = base.BandwidthLimitMode
|
||||||
|
out.Transport.ProxyProtocolVersion = base.ProxyProtocolVersion
|
||||||
|
|
||||||
|
out.LoadBalancer.Group = base.Group
|
||||||
|
out.LoadBalancer.GroupKey = base.GroupKey
|
||||||
|
|
||||||
|
out.HealthCheck.Type = base.HealthCheckType
|
||||||
|
out.HealthCheck.TimeoutSeconds = base.HealthCheckTimeoutS
|
||||||
|
out.HealthCheck.MaxFailed = base.HealthCheckMaxFailed
|
||||||
|
out.HealthCheck.IntervalSeconds = base.HealthCheckIntervalS
|
||||||
|
out.HealthCheck.Path = base.HealthCheckURL
|
||||||
|
|
||||||
|
out.LocalIP = base.LocalIP
|
||||||
|
out.LocalPort = base.LocalPort
|
||||||
|
|
||||||
|
switch base.Plugin {
|
||||||
|
case "http2https":
|
||||||
|
out.Plugin.ClientPluginOptions = &v1.HTTP2HTTPSPluginOptions{
|
||||||
|
LocalAddr: base.PluginParams["plugin_local_addr"],
|
||||||
|
HostHeaderRewrite: base.PluginParams["plugin_host_header_rewrite"],
|
||||||
|
RequestHeaders: transformHeadersFromPluginParams(base.PluginParams),
|
||||||
|
}
|
||||||
|
case "http_proxy":
|
||||||
|
out.Plugin.ClientPluginOptions = &v1.HTTPProxyPluginOptions{
|
||||||
|
HTTPUser: base.PluginParams["plugin_http_user"],
|
||||||
|
HTTPPassword: base.PluginParams["plugin_http_passwd"],
|
||||||
|
}
|
||||||
|
case "https2http":
|
||||||
|
out.Plugin.ClientPluginOptions = &v1.HTTPS2HTTPPluginOptions{
|
||||||
|
LocalAddr: base.PluginParams["plugin_local_addr"],
|
||||||
|
HostHeaderRewrite: base.PluginParams["plugin_host_header_rewrite"],
|
||||||
|
RequestHeaders: transformHeadersFromPluginParams(base.PluginParams),
|
||||||
|
CrtPath: base.PluginParams["plugin_crt_path"],
|
||||||
|
KeyPath: base.PluginParams["plugin_key_path"],
|
||||||
|
}
|
||||||
|
case "https2https":
|
||||||
|
out.Plugin.ClientPluginOptions = &v1.HTTPS2HTTPSPluginOptions{
|
||||||
|
LocalAddr: base.PluginParams["plugin_local_addr"],
|
||||||
|
HostHeaderRewrite: base.PluginParams["plugin_host_header_rewrite"],
|
||||||
|
RequestHeaders: transformHeadersFromPluginParams(base.PluginParams),
|
||||||
|
CrtPath: base.PluginParams["plugin_crt_path"],
|
||||||
|
KeyPath: base.PluginParams["plugin_key_path"],
|
||||||
|
}
|
||||||
|
case "socks5":
|
||||||
|
out.Plugin.ClientPluginOptions = &v1.Socks5PluginOptions{
|
||||||
|
Username: base.PluginParams["plugin_user"],
|
||||||
|
Password: base.PluginParams["plugin_passwd"],
|
||||||
|
}
|
||||||
|
case "static_file":
|
||||||
|
out.Plugin.ClientPluginOptions = &v1.StaticFilePluginOptions{
|
||||||
|
LocalPath: base.PluginParams["plugin_local_path"],
|
||||||
|
StripPrefix: base.PluginParams["plugin_strip_prefix"],
|
||||||
|
HTTPUser: base.PluginParams["plugin_http_user"],
|
||||||
|
HTTPPassword: base.PluginParams["plugin_http_passwd"],
|
||||||
|
}
|
||||||
|
case "unix_domain_socket":
|
||||||
|
out.Plugin.ClientPluginOptions = &v1.UnixDomainSocketPluginOptions{
|
||||||
|
UnixPath: base.PluginParams["plugin_unix_path"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.Plugin.Type = base.Plugin
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func Convert_ProxyConf_To_v1(conf ProxyConf) v1.ProxyConfigurer {
|
||||||
|
outBase := Convert_ProxyConf_To_v1_Base(conf)
|
||||||
|
var out v1.ProxyConfigurer
|
||||||
|
switch v := conf.(type) {
|
||||||
|
case *TCPProxyConf:
|
||||||
|
c := &v1.TCPProxyConfig{ProxyBaseConfig: *outBase}
|
||||||
|
c.RemotePort = v.RemotePort
|
||||||
|
out = c
|
||||||
|
case *UDPProxyConf:
|
||||||
|
c := &v1.UDPProxyConfig{ProxyBaseConfig: *outBase}
|
||||||
|
c.RemotePort = v.RemotePort
|
||||||
|
out = c
|
||||||
|
case *HTTPProxyConf:
|
||||||
|
c := &v1.HTTPProxyConfig{ProxyBaseConfig: *outBase}
|
||||||
|
c.CustomDomains = v.CustomDomains
|
||||||
|
c.SubDomain = v.SubDomain
|
||||||
|
c.Locations = v.Locations
|
||||||
|
c.HTTPUser = v.HTTPUser
|
||||||
|
c.HTTPPassword = v.HTTPPwd
|
||||||
|
c.HostHeaderRewrite = v.HostHeaderRewrite
|
||||||
|
c.RequestHeaders.Set = v.Headers
|
||||||
|
c.RouteByHTTPUser = v.RouteByHTTPUser
|
||||||
|
out = c
|
||||||
|
case *HTTPSProxyConf:
|
||||||
|
c := &v1.HTTPSProxyConfig{ProxyBaseConfig: *outBase}
|
||||||
|
c.CustomDomains = v.CustomDomains
|
||||||
|
c.SubDomain = v.SubDomain
|
||||||
|
out = c
|
||||||
|
case *TCPMuxProxyConf:
|
||||||
|
c := &v1.TCPMuxProxyConfig{ProxyBaseConfig: *outBase}
|
||||||
|
c.CustomDomains = v.CustomDomains
|
||||||
|
c.SubDomain = v.SubDomain
|
||||||
|
c.HTTPUser = v.HTTPUser
|
||||||
|
c.HTTPPassword = v.HTTPPwd
|
||||||
|
c.RouteByHTTPUser = v.RouteByHTTPUser
|
||||||
|
c.Multiplexer = v.Multiplexer
|
||||||
|
out = c
|
||||||
|
case *STCPProxyConf:
|
||||||
|
c := &v1.STCPProxyConfig{ProxyBaseConfig: *outBase}
|
||||||
|
c.Secretkey = v.Sk
|
||||||
|
c.AllowUsers = v.AllowUsers
|
||||||
|
out = c
|
||||||
|
case *SUDPProxyConf:
|
||||||
|
c := &v1.SUDPProxyConfig{ProxyBaseConfig: *outBase}
|
||||||
|
c.Secretkey = v.Sk
|
||||||
|
c.AllowUsers = v.AllowUsers
|
||||||
|
out = c
|
||||||
|
case *XTCPProxyConf:
|
||||||
|
c := &v1.XTCPProxyConfig{ProxyBaseConfig: *outBase}
|
||||||
|
c.Secretkey = v.Sk
|
||||||
|
c.AllowUsers = v.AllowUsers
|
||||||
|
out = c
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func Convert_VisitorConf_To_v1_Base(conf VisitorConf) *v1.VisitorBaseConfig {
|
||||||
|
out := &v1.VisitorBaseConfig{}
|
||||||
|
base := conf.GetBaseConfig()
|
||||||
|
|
||||||
|
out.Name = base.ProxyName
|
||||||
|
out.Type = base.ProxyType
|
||||||
|
out.Transport.UseEncryption = base.UseEncryption
|
||||||
|
out.Transport.UseCompression = base.UseCompression
|
||||||
|
out.SecretKey = base.Sk
|
||||||
|
out.ServerUser = base.ServerUser
|
||||||
|
out.ServerName = base.ServerName
|
||||||
|
out.BindAddr = base.BindAddr
|
||||||
|
out.BindPort = base.BindPort
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func Convert_VisitorConf_To_v1(conf VisitorConf) v1.VisitorConfigurer {
|
||||||
|
outBase := Convert_VisitorConf_To_v1_Base(conf)
|
||||||
|
var out v1.VisitorConfigurer
|
||||||
|
switch v := conf.(type) {
|
||||||
|
case *STCPVisitorConf:
|
||||||
|
c := &v1.STCPVisitorConfig{VisitorBaseConfig: *outBase}
|
||||||
|
out = c
|
||||||
|
case *SUDPVisitorConf:
|
||||||
|
c := &v1.SUDPVisitorConfig{VisitorBaseConfig: *outBase}
|
||||||
|
out = c
|
||||||
|
case *XTCPVisitorConf:
|
||||||
|
c := &v1.XTCPVisitorConfig{VisitorBaseConfig: *outBase}
|
||||||
|
c.Protocol = v.Protocol
|
||||||
|
c.KeepTunnelOpen = v.KeepTunnelOpen
|
||||||
|
c.MaxRetriesAnHour = v.MaxRetriesAnHour
|
||||||
|
c.MinRetryInterval = v.MinRetryInterval
|
||||||
|
c.FallbackTo = v.FallbackTo
|
||||||
|
c.FallbackTimeoutMs = v.FallbackTimeoutMs
|
||||||
|
out = c
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -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 config
|
package legacy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -40,7 +40,6 @@ func ParseClientConfig(filePath string) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.Complete()
|
|
||||||
if err = cfg.Validate(); err != nil {
|
if err = cfg.Validate(); err != nil {
|
||||||
err = fmt.Errorf("parse config error: %v", err)
|
err = fmt.Errorf("parse config error: %v", err)
|
||||||
return
|
return
|
||||||
387
pkg/config/legacy/proxy.go
Normal file
387
pkg/config/legacy/proxy.go
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
// Copyright 2023 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 legacy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProxyTypeTCP ProxyType = "tcp"
|
||||||
|
ProxyTypeUDP ProxyType = "udp"
|
||||||
|
ProxyTypeTCPMUX ProxyType = "tcpmux"
|
||||||
|
ProxyTypeHTTP ProxyType = "http"
|
||||||
|
ProxyTypeHTTPS ProxyType = "https"
|
||||||
|
ProxyTypeSTCP ProxyType = "stcp"
|
||||||
|
ProxyTypeXTCP ProxyType = "xtcp"
|
||||||
|
ProxyTypeSUDP ProxyType = "sudp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Proxy
|
||||||
|
var (
|
||||||
|
proxyConfTypeMap = map[ProxyType]reflect.Type{
|
||||||
|
ProxyTypeTCP: reflect.TypeOf(TCPProxyConf{}),
|
||||||
|
ProxyTypeUDP: reflect.TypeOf(UDPProxyConf{}),
|
||||||
|
ProxyTypeTCPMUX: reflect.TypeOf(TCPMuxProxyConf{}),
|
||||||
|
ProxyTypeHTTP: reflect.TypeOf(HTTPProxyConf{}),
|
||||||
|
ProxyTypeHTTPS: reflect.TypeOf(HTTPSProxyConf{}),
|
||||||
|
ProxyTypeSTCP: reflect.TypeOf(STCPProxyConf{}),
|
||||||
|
ProxyTypeXTCP: reflect.TypeOf(XTCPProxyConf{}),
|
||||||
|
ProxyTypeSUDP: reflect.TypeOf(SUDPProxyConf{}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyConf interface {
|
||||||
|
// GetBaseConfig returns the BaseProxyConf for this config.
|
||||||
|
GetBaseConfig() *BaseProxyConf
|
||||||
|
// UnmarshalFromIni unmarshals a ini.Section into this config. This function
|
||||||
|
// will be called on the frpc side.
|
||||||
|
UnmarshalFromIni(string, string, *ini.Section) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfByType(proxyType ProxyType) ProxyConf {
|
||||||
|
v, ok := proxyConfTypeMap[proxyType]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cfg := reflect.New(v).Interface().(ProxyConf)
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxy Conf Loader
|
||||||
|
// DefaultProxyConf creates a empty ProxyConf object by proxyType.
|
||||||
|
// If proxyType doesn't exist, return nil.
|
||||||
|
func DefaultProxyConf(proxyType ProxyType) ProxyConf {
|
||||||
|
return NewConfByType(proxyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxy loaded from ini
|
||||||
|
func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf, error) {
|
||||||
|
// section.Key: if key not exists, section will set it with default value.
|
||||||
|
proxyType := ProxyType(section.Key("type").String())
|
||||||
|
if proxyType == "" {
|
||||||
|
proxyType = ProxyTypeTCP
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := DefaultProxyConf(proxyType)
|
||||||
|
if conf == nil {
|
||||||
|
return nil, fmt.Errorf("invalid type [%s]", proxyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalSvrConf configures what location the client will to, or what
|
||||||
|
// plugin will be used.
|
||||||
|
type LocalSvrConf struct {
|
||||||
|
// LocalIP specifies the IP address or host name to to.
|
||||||
|
LocalIP string `ini:"local_ip" json:"local_ip"`
|
||||||
|
// LocalPort specifies the port to to.
|
||||||
|
LocalPort int `ini:"local_port" json:"local_port"`
|
||||||
|
|
||||||
|
// Plugin specifies what plugin should be used for ng. If this value
|
||||||
|
// is set, the LocalIp and LocalPort values will be ignored. By default,
|
||||||
|
// this value is "".
|
||||||
|
Plugin string `ini:"plugin" json:"plugin"`
|
||||||
|
// PluginParams specify parameters to be passed to the plugin, if one is
|
||||||
|
// being used. By default, this value is an empty map.
|
||||||
|
PluginParams map[string]string `ini:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheckConf configures health checking. This can be useful for load
|
||||||
|
// balancing purposes to detect and remove proxies to failing services.
|
||||||
|
type HealthCheckConf struct {
|
||||||
|
// HealthCheckType specifies what protocol to use for health checking.
|
||||||
|
// Valid values include "tcp", "http", and "". If this value is "", health
|
||||||
|
// checking will not be performed. By default, this value is "".
|
||||||
|
//
|
||||||
|
// If the type is "tcp", a connection will be attempted to the target
|
||||||
|
// server. If a connection cannot be established, the health check fails.
|
||||||
|
//
|
||||||
|
// If the type is "http", a GET request will be made to the endpoint
|
||||||
|
// specified by HealthCheckURL. If the response is not a 200, the health
|
||||||
|
// check fails.
|
||||||
|
HealthCheckType string `ini:"health_check_type" json:"health_check_type"` // tcp | http
|
||||||
|
// HealthCheckTimeoutS specifies the number of seconds to wait for a health
|
||||||
|
// check attempt to connect. If the timeout is reached, this counts as a
|
||||||
|
// health check failure. By default, this value is 3.
|
||||||
|
HealthCheckTimeoutS int `ini:"health_check_timeout_s" json:"health_check_timeout_s"`
|
||||||
|
// HealthCheckMaxFailed specifies the number of allowed failures before the
|
||||||
|
// is stopped. By default, this value is 1.
|
||||||
|
HealthCheckMaxFailed int `ini:"health_check_max_failed" json:"health_check_max_failed"`
|
||||||
|
// HealthCheckIntervalS specifies the time in seconds between health
|
||||||
|
// checks. By default, this value is 10.
|
||||||
|
HealthCheckIntervalS int `ini:"health_check_interval_s" json:"health_check_interval_s"`
|
||||||
|
// HealthCheckURL specifies the address to send health checks to if the
|
||||||
|
// health check type is "http".
|
||||||
|
HealthCheckURL string `ini:"health_check_url" json:"health_check_url"`
|
||||||
|
// HealthCheckAddr specifies the address to connect to if the health check
|
||||||
|
// type is "tcp".
|
||||||
|
HealthCheckAddr string `ini:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseProxyConf provides configuration info that is common to all types.
|
||||||
|
type BaseProxyConf struct {
|
||||||
|
// ProxyName is the name of this
|
||||||
|
ProxyName string `ini:"name" json:"name"`
|
||||||
|
// ProxyType specifies the type of this Valid values include "tcp",
|
||||||
|
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
|
||||||
|
// "tcp".
|
||||||
|
ProxyType string `ini:"type" json:"type"`
|
||||||
|
|
||||||
|
// UseEncryption controls whether or not communication with the server will
|
||||||
|
// be encrypted. Encryption is done using the tokens supplied in the server
|
||||||
|
// and client configuration. By default, this value is false.
|
||||||
|
UseEncryption bool `ini:"use_encryption" json:"use_encryption"`
|
||||||
|
// UseCompression controls whether or not communication with the server
|
||||||
|
// will be compressed. By default, this value is false.
|
||||||
|
UseCompression bool `ini:"use_compression" json:"use_compression"`
|
||||||
|
// Group specifies which group the is a part of. The server will use
|
||||||
|
// this information to load balance proxies in the same group. If the value
|
||||||
|
// is "", this will not be in a group. By default, this value is "".
|
||||||
|
Group string `ini:"group" json:"group"`
|
||||||
|
// GroupKey specifies a group key, which should be the same among proxies
|
||||||
|
// of the same group. By default, this value is "".
|
||||||
|
GroupKey string `ini:"group_key" json:"group_key"`
|
||||||
|
|
||||||
|
// ProxyProtocolVersion specifies which protocol version to use. Valid
|
||||||
|
// values include "v1", "v2", and "". If the value is "", a protocol
|
||||||
|
// version will be automatically selected. By default, this value is "".
|
||||||
|
ProxyProtocolVersion string `ini:"proxy_protocol_version" json:"proxy_protocol_version"`
|
||||||
|
|
||||||
|
// BandwidthLimit limit the bandwidth
|
||||||
|
// 0 means no limit
|
||||||
|
BandwidthLimit types.BandwidthQuantity `ini:"bandwidth_limit" json:"bandwidth_limit"`
|
||||||
|
// BandwidthLimitMode specifies whether to limit the bandwidth on the
|
||||||
|
// client or server side. Valid values include "client" and "server".
|
||||||
|
// By default, this value is "client".
|
||||||
|
BandwidthLimitMode string `ini:"bandwidth_limit_mode" json:"bandwidth_limit_mode"`
|
||||||
|
|
||||||
|
// meta info for each proxy
|
||||||
|
Metas map[string]string `ini:"-" json:"metas"`
|
||||||
|
|
||||||
|
LocalSvrConf `ini:",extends"`
|
||||||
|
HealthCheckConf `ini:",extends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base
|
||||||
|
func (cfg *BaseProxyConf) GetBaseConfig() *BaseProxyConf {
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseProxyConf apply custom logic changes.
|
||||||
|
func (cfg *BaseProxyConf) decorate(_ string, name string, section *ini.Section) error {
|
||||||
|
cfg.ProxyName = name
|
||||||
|
// metas_xxx
|
||||||
|
cfg.Metas = GetMapWithoutPrefix(section.KeysHash(), "meta_")
|
||||||
|
|
||||||
|
// bandwidth_limit
|
||||||
|
if bandwidth, err := section.GetKey("bandwidth_limit"); err == nil {
|
||||||
|
cfg.BandwidthLimit, err = types.NewBandwidthQuantity(bandwidth.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// plugin_xxx
|
||||||
|
cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DomainConf struct {
|
||||||
|
CustomDomains []string `ini:"custom_domains" json:"custom_domains"`
|
||||||
|
SubDomain string `ini:"subdomain" json:"subdomain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoleServerCommonConf struct {
|
||||||
|
Role string `ini:"role" json:"role"`
|
||||||
|
Sk string `ini:"sk" json:"sk"`
|
||||||
|
AllowUsers []string `ini:"allow_users" json:"allow_users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP
|
||||||
|
type HTTPProxyConf struct {
|
||||||
|
BaseProxyConf `ini:",extends"`
|
||||||
|
DomainConf `ini:",extends"`
|
||||||
|
|
||||||
|
Locations []string `ini:"locations" json:"locations"`
|
||||||
|
HTTPUser string `ini:"http_user" json:"http_user"`
|
||||||
|
HTTPPwd string `ini:"http_pwd" json:"http_pwd"`
|
||||||
|
HostHeaderRewrite string `ini:"host_header_rewrite" json:"host_header_rewrite"`
|
||||||
|
Headers map[string]string `ini:"-" json:"headers"`
|
||||||
|
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom logic unmarshal if exists
|
||||||
|
cfg.Headers = GetMapWithoutPrefix(section.KeysHash(), "header_")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPS
|
||||||
|
type HTTPSProxyConf struct {
|
||||||
|
BaseProxyConf `ini:",extends"`
|
||||||
|
DomainConf `ini:",extends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom logic unmarshal if exists
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP
|
||||||
|
type TCPProxyConf struct {
|
||||||
|
BaseProxyConf `ini:",extends"`
|
||||||
|
RemotePort int `ini:"remote_port" json:"remote_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom logic unmarshal if exists
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDP
|
||||||
|
type UDPProxyConf struct {
|
||||||
|
BaseProxyConf `ini:",extends"`
|
||||||
|
|
||||||
|
RemotePort int `ini:"remote_port" json:"remote_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom logic unmarshal if exists
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCPMux
|
||||||
|
type TCPMuxProxyConf struct {
|
||||||
|
BaseProxyConf `ini:",extends"`
|
||||||
|
DomainConf `ini:",extends"`
|
||||||
|
HTTPUser string `ini:"http_user" json:"http_user,omitempty"`
|
||||||
|
HTTPPwd string `ini:"http_pwd" json:"http_pwd,omitempty"`
|
||||||
|
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
||||||
|
|
||||||
|
Multiplexer string `ini:"multiplexer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom logic unmarshal if exists
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// STCP
|
||||||
|
type STCPProxyConf struct {
|
||||||
|
BaseProxyConf `ini:",extends"`
|
||||||
|
RoleServerCommonConf `ini:",extends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom logic unmarshal if exists
|
||||||
|
if cfg.Role == "" {
|
||||||
|
cfg.Role = "server"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XTCP
|
||||||
|
type XTCPProxyConf struct {
|
||||||
|
BaseProxyConf `ini:",extends"`
|
||||||
|
RoleServerCommonConf `ini:",extends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom logic unmarshal if exists
|
||||||
|
if cfg.Role == "" {
|
||||||
|
cfg.Role = "server"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SUDP
|
||||||
|
type SUDPProxyConf struct {
|
||||||
|
BaseProxyConf `ini:",extends"`
|
||||||
|
RoleServerCommonConf `ini:",extends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom logic unmarshal if exists
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, section *ini.Section) error {
|
||||||
|
err := section.MapTo(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cfg.GetBaseConfig().decorate(prefix, name, section)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020 The frp Authors
|
// Copyright 2023 The frp Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,60 +12,64 @@
|
|||||||
// 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 config
|
package legacy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
legacyauth "github.com/fatedier/frp/pkg/auth/legacy"
|
||||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type HTTPPluginOptions struct {
|
||||||
|
Name string `ini:"name"`
|
||||||
|
Addr string `ini:"addr"`
|
||||||
|
Path string `ini:"path"`
|
||||||
|
Ops []string `ini:"ops"`
|
||||||
|
TLSVerify bool `ini:"tlsVerify"`
|
||||||
|
}
|
||||||
|
|
||||||
// ServerCommonConf contains information for a server service. It is
|
// ServerCommonConf contains information for a server service. It is
|
||||||
// recommended to use GetDefaultServerConf instead of creating this object
|
// recommended to use GetDefaultServerConf instead of creating this object
|
||||||
// directly, so that all unspecified fields have reasonable default values.
|
// directly, so that all unspecified fields have reasonable default values.
|
||||||
type ServerCommonConf struct {
|
type ServerCommonConf struct {
|
||||||
auth.ServerConfig `ini:",extends"`
|
legacyauth.ServerConfig `ini:",extends"`
|
||||||
|
|
||||||
// BindAddr specifies the address that the server binds to. By default,
|
// BindAddr specifies the address that the server binds to. By default,
|
||||||
// this value is "0.0.0.0".
|
// this value is "0.0.0.0".
|
||||||
BindAddr string `ini:"bind_addr" json:"bind_addr"`
|
BindAddr string `ini:"bind_addr" json:"bind_addr"`
|
||||||
// BindPort specifies the port that the server listens on. By default, this
|
// BindPort specifies the port that the server listens on. By default, this
|
||||||
// value is 7000.
|
// value is 7000.
|
||||||
BindPort int `ini:"bind_port" json:"bind_port" validate:"gte=0,lte=65535"`
|
BindPort int `ini:"bind_port" json:"bind_port"`
|
||||||
// KCPBindPort specifies the KCP port that the server listens on. If this
|
// KCPBindPort specifies the KCP port that the server listens on. If this
|
||||||
// value is 0, the server will not listen for KCP connections. By default,
|
// value is 0, the server will not listen for KCP connections. By default,
|
||||||
// this value is 0.
|
// this value is 0.
|
||||||
KCPBindPort int `ini:"kcp_bind_port" json:"kcp_bind_port" validate:"gte=0,lte=65535"`
|
KCPBindPort int `ini:"kcp_bind_port" json:"kcp_bind_port"`
|
||||||
// QUICBindPort specifies the QUIC port that the server listens on.
|
// QUICBindPort specifies the QUIC port that the server listens on.
|
||||||
// Set this value to 0 will disable this feature.
|
// Set this value to 0 will disable this feature.
|
||||||
// By default, the value is 0.
|
// By default, the value is 0.
|
||||||
QUICBindPort int `ini:"quic_bind_port" json:"quic_bind_port" validate:"gte=0,lte=65535"`
|
QUICBindPort int `ini:"quic_bind_port" json:"quic_bind_port"`
|
||||||
// QUIC protocol options
|
// QUIC protocol options
|
||||||
QUICKeepalivePeriod int `ini:"quic_keepalive_period" json:"quic_keepalive_period" validate:"gte=0"`
|
QUICKeepalivePeriod int `ini:"quic_keepalive_period" json:"quic_keepalive_period"`
|
||||||
QUICMaxIdleTimeout int `ini:"quic_max_idle_timeout" json:"quic_max_idle_timeout" validate:"gte=0"`
|
QUICMaxIdleTimeout int `ini:"quic_max_idle_timeout" json:"quic_max_idle_timeout"`
|
||||||
QUICMaxIncomingStreams int `ini:"quic_max_incoming_streams" json:"quic_max_incoming_streams" validate:"gte=0"`
|
QUICMaxIncomingStreams int `ini:"quic_max_incoming_streams" json:"quic_max_incoming_streams"`
|
||||||
// ProxyBindAddr specifies the address that the proxy binds to. This value
|
// ProxyBindAddr specifies the address that the proxy binds to. This value
|
||||||
// may be the same as BindAddr.
|
// may be the same as BindAddr.
|
||||||
ProxyBindAddr string `ini:"proxy_bind_addr" json:"proxy_bind_addr"`
|
ProxyBindAddr string `ini:"proxy_bind_addr" json:"proxy_bind_addr"`
|
||||||
// VhostHTTPPort specifies the port that the server listens for HTTP Vhost
|
// VhostHTTPPort specifies the port that the server listens for HTTP Vhost
|
||||||
// requests. If this value is 0, the server will not listen for HTTP
|
// requests. If this value is 0, the server will not listen for HTTP
|
||||||
// requests. By default, this value is 0.
|
// requests. By default, this value is 0.
|
||||||
VhostHTTPPort int `ini:"vhost_http_port" json:"vhost_http_port" validate:"gte=0,lte=65535"`
|
VhostHTTPPort int `ini:"vhost_http_port" json:"vhost_http_port"`
|
||||||
// VhostHTTPSPort specifies the port that the server listens for HTTPS
|
// VhostHTTPSPort specifies the port that the server listens for HTTPS
|
||||||
// Vhost requests. If this value is 0, the server will not listen for HTTPS
|
// Vhost requests. If this value is 0, the server will not listen for HTTPS
|
||||||
// requests. By default, this value is 0.
|
// requests. By default, this value is 0.
|
||||||
VhostHTTPSPort int `ini:"vhost_https_port" json:"vhost_https_port" validate:"gte=0,lte=65535"`
|
VhostHTTPSPort int `ini:"vhost_https_port" json:"vhost_https_port"`
|
||||||
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
|
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
|
||||||
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||||
// requests on one single port. If it's not - it will listen on this value for
|
// requests on one single port. If it's not - it will listen on this value for
|
||||||
// HTTP CONNECT requests. By default, this value is 0.
|
// HTTP CONNECT requests. By default, this value is 0.
|
||||||
TCPMuxHTTPConnectPort int `ini:"tcpmux_httpconnect_port" json:"tcpmux_httpconnect_port" validate:"gte=0,lte=65535"`
|
TCPMuxHTTPConnectPort int `ini:"tcpmux_httpconnect_port" json:"tcpmux_httpconnect_port"`
|
||||||
// If TCPMuxPassthrough is true, frps won't do any update on traffic.
|
// If TCPMuxPassthrough is true, frps won't do any update on traffic.
|
||||||
TCPMuxPassthrough bool `ini:"tcpmux_passthrough" json:"tcpmux_passthrough"`
|
TCPMuxPassthrough bool `ini:"tcpmux_passthrough" json:"tcpmux_passthrough"`
|
||||||
// VhostHTTPTimeout specifies the response header timeout for the Vhost
|
// VhostHTTPTimeout specifies the response header timeout for the Vhost
|
||||||
@@ -77,7 +81,7 @@ type ServerCommonConf struct {
|
|||||||
// DashboardPort specifies the port that the dashboard listens on. If this
|
// DashboardPort specifies the port that the dashboard listens on. If this
|
||||||
// value is 0, the dashboard will not be started. By default, this value is
|
// value is 0, the dashboard will not be started. By default, this value is
|
||||||
// 0.
|
// 0.
|
||||||
DashboardPort int `ini:"dashboard_port" json:"dashboard_port" validate:"gte=0,lte=65535"`
|
DashboardPort int `ini:"dashboard_port" json:"dashboard_port"`
|
||||||
// DashboardTLSCertFile specifies the path of the cert file that the server will
|
// DashboardTLSCertFile specifies the path of the cert file that the server will
|
||||||
// load. If "dashboard_tls_cert_file", "dashboard_tls_key_file" are valid, the server will use this
|
// load. If "dashboard_tls_cert_file", "dashboard_tls_key_file" are valid, the server will use this
|
||||||
// supplied tls configuration.
|
// supplied tls configuration.
|
||||||
@@ -185,7 +189,7 @@ type ServerCommonConf struct {
|
|||||||
// connection. By default, this value is 10.
|
// connection. By default, this value is 10.
|
||||||
UserConnTimeout int64 `ini:"user_conn_timeout" json:"user_conn_timeout"`
|
UserConnTimeout int64 `ini:"user_conn_timeout" json:"user_conn_timeout"`
|
||||||
// HTTPPlugins specify the server plugins support HTTP protocol.
|
// HTTPPlugins specify the server plugins support HTTP protocol.
|
||||||
HTTPPlugins map[string]plugin.HTTPPluginOptions `ini:"-" json:"http_plugins"`
|
HTTPPlugins map[string]HTTPPluginOptions `ini:"-" json:"http_plugins"`
|
||||||
// UDPPacketSize specifies the UDP packet size
|
// UDPPacketSize specifies the UDP packet size
|
||||||
// By default, this value is 1500
|
// By default, this value is 1500
|
||||||
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
|
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
|
||||||
@@ -200,7 +204,7 @@ type ServerCommonConf struct {
|
|||||||
// defaults.
|
// defaults.
|
||||||
func GetDefaultServerConf() ServerCommonConf {
|
func GetDefaultServerConf() ServerCommonConf {
|
||||||
return ServerCommonConf{
|
return ServerCommonConf{
|
||||||
ServerConfig: auth.GetDefaultServerConf(),
|
ServerConfig: legacyauth.GetDefaultServerConf(),
|
||||||
BindAddr: "0.0.0.0",
|
BindAddr: "0.0.0.0",
|
||||||
BindPort: 7000,
|
BindPort: 7000,
|
||||||
QUICKeepalivePeriod: 10,
|
QUICKeepalivePeriod: 10,
|
||||||
@@ -221,7 +225,7 @@ func GetDefaultServerConf() ServerCommonConf {
|
|||||||
MaxPortsPerClient: 0,
|
MaxPortsPerClient: 0,
|
||||||
HeartbeatTimeout: 90,
|
HeartbeatTimeout: 90,
|
||||||
UserConnTimeout: 10,
|
UserConnTimeout: 10,
|
||||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
HTTPPlugins: make(map[string]HTTPPluginOptions),
|
||||||
UDPPacketSize: 1500,
|
UDPPacketSize: 1500,
|
||||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||||
}
|
}
|
||||||
@@ -253,18 +257,11 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
|||||||
// allow_ports
|
// allow_ports
|
||||||
allowPortStr := s.Key("allow_ports").String()
|
allowPortStr := s.Key("allow_ports").String()
|
||||||
if allowPortStr != "" {
|
if allowPortStr != "" {
|
||||||
allowPorts, err := util.ParseRangeNumbers(allowPortStr)
|
|
||||||
if err != nil {
|
|
||||||
return ServerCommonConf{}, fmt.Errorf("invalid allow_ports: %v", err)
|
|
||||||
}
|
|
||||||
for _, port := range allowPorts {
|
|
||||||
common.AllowPorts[int(port)] = struct{}{}
|
|
||||||
}
|
|
||||||
common.AllowPortsStr = allowPortStr
|
common.AllowPortsStr = allowPortStr
|
||||||
}
|
}
|
||||||
|
|
||||||
// plugin.xxx
|
// plugin.xxx
|
||||||
pluginOpts := make(map[string]plugin.HTTPPluginOptions)
|
pluginOpts := make(map[string]HTTPPluginOptions)
|
||||||
for _, section := range f.Sections() {
|
for _, section := range f.Sections() {
|
||||||
name := section.Name()
|
name := section.Name()
|
||||||
if !strings.HasPrefix(name, "plugin.") {
|
if !strings.HasPrefix(name, "plugin.") {
|
||||||
@@ -283,47 +280,10 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
|||||||
return common, nil
|
return common, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *ServerCommonConf) Complete() {
|
func loadHTTPPluginOpt(section *ini.Section) (*HTTPPluginOptions, error) {
|
||||||
if cfg.LogFile == "console" {
|
|
||||||
cfg.LogWay = "console"
|
|
||||||
} else {
|
|
||||||
cfg.LogWay = "file"
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.ProxyBindAddr == "" {
|
|
||||||
cfg.ProxyBindAddr = cfg.BindAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.TLSTrustedCaFile != "" {
|
|
||||||
cfg.TLSOnly = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *ServerCommonConf) Validate() error {
|
|
||||||
if !cfg.DashboardTLSMode {
|
|
||||||
if cfg.DashboardTLSCertFile != "" {
|
|
||||||
fmt.Println("WARNING! dashboard_tls_cert_file is invalid when dashboard_tls_mode is false")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.DashboardTLSKeyFile != "" {
|
|
||||||
fmt.Println("WARNING! dashboard_tls_key_file is invalid when dashboard_tls_mode is false")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if cfg.DashboardTLSCertFile == "" {
|
|
||||||
return fmt.Errorf("ERROR! dashboard_tls_cert_file must be specified when dashboard_tls_mode is true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.DashboardTLSKeyFile == "" {
|
|
||||||
return fmt.Errorf("ERROR! dashboard_tls_cert_file must be specified when dashboard_tls_mode is true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return validator.New().Struct(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadHTTPPluginOpt(section *ini.Section) (*plugin.HTTPPluginOptions, error) {
|
|
||||||
name := strings.TrimSpace(strings.TrimPrefix(section.Name(), "plugin."))
|
name := strings.TrimSpace(strings.TrimPrefix(section.Name(), "plugin."))
|
||||||
|
|
||||||
opt := new(plugin.HTTPPluginOptions)
|
opt := &HTTPPluginOptions{}
|
||||||
err := section.MapTo(opt)
|
err := section.MapTo(opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -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 config
|
package legacy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
@@ -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 config
|
package legacy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 fatedier, fatedier@gmail.com
|
// Copyright 2023 The frp Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,24 +12,29 @@
|
|||||||
// 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 config
|
package legacy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
type VisitorType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
VisitorTypeSTCP VisitorType = "stcp"
|
||||||
|
VisitorTypeXTCP VisitorType = "xtcp"
|
||||||
|
VisitorTypeSUDP VisitorType = "sudp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Visitor
|
// Visitor
|
||||||
var (
|
var (
|
||||||
visitorConfTypeMap = map[string]reflect.Type{
|
visitorConfTypeMap = map[VisitorType]reflect.Type{
|
||||||
consts.STCPProxy: reflect.TypeOf(STCPVisitorConf{}),
|
VisitorTypeSTCP: reflect.TypeOf(STCPVisitorConf{}),
|
||||||
consts.XTCPProxy: reflect.TypeOf(XTCPVisitorConf{}),
|
VisitorTypeXTCP: reflect.TypeOf(XTCPVisitorConf{}),
|
||||||
consts.SUDPProxy: reflect.TypeOf(SUDPVisitorConf{}),
|
VisitorTypeSUDP: reflect.TypeOf(SUDPVisitorConf{}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,8 +43,16 @@ type VisitorConf interface {
|
|||||||
GetBaseConfig() *BaseVisitorConf
|
GetBaseConfig() *BaseVisitorConf
|
||||||
// UnmarshalFromIni unmarshals config from ini.
|
// UnmarshalFromIni unmarshals config from ini.
|
||||||
UnmarshalFromIni(prefix string, name string, section *ini.Section) error
|
UnmarshalFromIni(prefix string, name string, section *ini.Section) error
|
||||||
// Validate validates config.
|
}
|
||||||
Validate() error
|
|
||||||
|
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
|
||||||
|
// If visitorType doesn't exist, return nil.
|
||||||
|
func DefaultVisitorConf(visitorType VisitorType) VisitorConf {
|
||||||
|
v, ok := visitorConfTypeMap[visitorType]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return reflect.New(v).Interface().(VisitorConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseVisitorConf struct {
|
type BaseVisitorConf struct {
|
||||||
@@ -59,96 +72,14 @@ type BaseVisitorConf struct {
|
|||||||
BindPort int `ini:"bind_port" json:"bind_port"`
|
BindPort int `ini:"bind_port" json:"bind_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SUDPVisitorConf struct {
|
|
||||||
BaseVisitorConf `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type STCPVisitorConf struct {
|
|
||||||
BaseVisitorConf `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type XTCPVisitorConf struct {
|
|
||||||
BaseVisitorConf `ini:",extends"`
|
|
||||||
|
|
||||||
Protocol string `ini:"protocol" json:"protocol,omitempty"`
|
|
||||||
KeepTunnelOpen bool `ini:"keep_tunnel_open" json:"keep_tunnel_open,omitempty"`
|
|
||||||
MaxRetriesAnHour int `ini:"max_retries_an_hour" json:"max_retries_an_hour,omitempty"`
|
|
||||||
MinRetryInterval int `ini:"min_retry_interval" json:"min_retry_interval,omitempty"`
|
|
||||||
FallbackTo string `ini:"fallback_to" json:"fallback_to,omitempty"`
|
|
||||||
FallbackTimeoutMs int `ini:"fallback_timeout_ms" json:"fallback_timeout_ms,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
|
|
||||||
// If visitorType doesn't exist, return nil.
|
|
||||||
func DefaultVisitorConf(visitorType string) VisitorConf {
|
|
||||||
v, ok := visitorConfTypeMap[visitorType]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return reflect.New(v).Interface().(VisitorConf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visitor loaded from ini
|
|
||||||
func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (VisitorConf, error) {
|
|
||||||
// section.Key: if key not exists, section will set it with default value.
|
|
||||||
visitorType := section.Key("type").String()
|
|
||||||
|
|
||||||
if visitorType == "" {
|
|
||||||
return nil, fmt.Errorf("type shouldn't be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := DefaultVisitorConf(visitorType)
|
|
||||||
if conf == nil {
|
|
||||||
return nil, fmt.Errorf("type [%s] error", visitorType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
|
|
||||||
return nil, fmt.Errorf("type [%s] error", visitorType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := conf.Validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base
|
// Base
|
||||||
func (cfg *BaseVisitorConf) GetBaseConfig() *BaseVisitorConf {
|
func (cfg *BaseVisitorConf) GetBaseConfig() *BaseVisitorConf {
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseVisitorConf) validate() (err error) {
|
func (cfg *BaseVisitorConf) unmarshalFromIni(_ string, name string, _ *ini.Section) error {
|
||||||
if cfg.Role != "visitor" {
|
|
||||||
err = fmt.Errorf("invalid role")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cfg.BindAddr == "" {
|
|
||||||
err = fmt.Errorf("bind_addr shouldn't be empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// BindPort can be less than 0, it means don't bind to the port and only receive connections redirected from
|
|
||||||
// other visitors
|
|
||||||
if cfg.BindPort == 0 {
|
|
||||||
err = fmt.Errorf("bind_port is required")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *BaseVisitorConf) unmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
|
||||||
_ = section
|
|
||||||
|
|
||||||
// Custom decoration after basic unmarshal:
|
// Custom decoration after basic unmarshal:
|
||||||
// proxy name
|
cfg.ProxyName = name
|
||||||
cfg.ProxyName = prefix + name
|
|
||||||
|
|
||||||
// server_name
|
|
||||||
if cfg.ServerUser == "" {
|
|
||||||
cfg.ServerName = prefix + cfg.ServerName
|
|
||||||
} else {
|
|
||||||
cfg.ServerName = cfg.ServerUser + "." + cfg.ServerName
|
|
||||||
}
|
|
||||||
|
|
||||||
// bind_addr
|
// bind_addr
|
||||||
if cfg.BindAddr == "" {
|
if cfg.BindAddr == "" {
|
||||||
@@ -170,8 +101,9 @@ func preVisitorUnmarshalFromIni(cfg VisitorConf, prefix string, name string, sec
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SUDP
|
type SUDPVisitorConf struct {
|
||||||
var _ VisitorConf = &SUDPVisitorConf{}
|
BaseVisitorConf `ini:",extends"`
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
@@ -184,19 +116,10 @@ func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *SUDPVisitorConf) Validate() (err error) {
|
type STCPVisitorConf struct {
|
||||||
if err = cfg.BaseVisitorConf.validate(); err != nil {
|
BaseVisitorConf `ini:",extends"`
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic validate, if exists
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// STCP
|
|
||||||
var _ VisitorConf = &STCPVisitorConf{}
|
|
||||||
|
|
||||||
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -208,19 +131,17 @@ func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *STCPVisitorConf) Validate() (err error) {
|
type XTCPVisitorConf struct {
|
||||||
if err = cfg.BaseVisitorConf.validate(); err != nil {
|
BaseVisitorConf `ini:",extends"`
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic validate, if exists
|
Protocol string `ini:"protocol" json:"protocol,omitempty"`
|
||||||
|
KeepTunnelOpen bool `ini:"keep_tunnel_open" json:"keep_tunnel_open,omitempty"`
|
||||||
return
|
MaxRetriesAnHour int `ini:"max_retries_an_hour" json:"max_retries_an_hour,omitempty"`
|
||||||
|
MinRetryInterval int `ini:"min_retry_interval" json:"min_retry_interval,omitempty"`
|
||||||
|
FallbackTo string `ini:"fallback_to" json:"fallback_to,omitempty"`
|
||||||
|
FallbackTimeoutMs int `ini:"fallback_timeout_ms" json:"fallback_timeout_ms,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// XTCP
|
|
||||||
var _ VisitorConf = &XTCPVisitorConf{}
|
|
||||||
|
|
||||||
func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -243,14 +164,22 @@ func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *XTCPVisitorConf) Validate() (err error) {
|
// Visitor loaded from ini
|
||||||
if err = cfg.BaseVisitorConf.validate(); err != nil {
|
func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (VisitorConf, error) {
|
||||||
return
|
// section.Key: if key not exists, section will set it with default value.
|
||||||
|
visitorType := VisitorType(section.Key("type").String())
|
||||||
|
|
||||||
|
if visitorType == "" {
|
||||||
|
return nil, fmt.Errorf("type shouldn't be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add custom logic validate, if exists
|
conf := DefaultVisitorConf(visitorType)
|
||||||
if !lo.Contains([]string{"", "kcp", "quic"}, cfg.Protocol) {
|
if conf == nil {
|
||||||
return fmt.Errorf("protocol should be 'kcp' or 'quic'")
|
return nil, fmt.Errorf("type [%s] error", visitorType)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return nil, fmt.Errorf("type [%s] error", visitorType)
|
||||||
|
}
|
||||||
|
return conf, nil
|
||||||
}
|
}
|
||||||
281
pkg/config/load.go
Normal file
281
pkg/config/load.go
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
toml "github.com/pelletier/go-toml/v2"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config/legacy"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var glbEnvs map[string]string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
glbEnvs = make(map[string]string)
|
||||||
|
envs := os.Environ()
|
||||||
|
for _, env := range envs {
|
||||||
|
pair := strings.SplitN(env, "=", 2)
|
||||||
|
if len(pair) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
glbEnvs[pair[0]] = pair[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Values struct {
|
||||||
|
Envs map[string]string // environment vars
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetValues() *Values {
|
||||||
|
return &Values{
|
||||||
|
Envs: glbEnvs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DetectLegacyINIFormat(content []byte) bool {
|
||||||
|
f, err := ini.Load(content)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, err := f.GetSection("common"); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func DetectLegacyINIFormatFromFile(path string) bool {
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return DetectLegacyINIFormat(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderWithTemplate(in []byte, values *Values) ([]byte, error) {
|
||||||
|
tmpl, err := template.New("frp").Parse(string(in))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := bytes.NewBufferString("")
|
||||||
|
if err := tmpl.Execute(buffer, values); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadFileContentWithTemplate(path string, values *Values) ([]byte, error) {
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return RenderWithTemplate(b, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfigureFromFile(path string, c any) error {
|
||||||
|
content, err := LoadFileContentWithTemplate(path, GetValues())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return LoadConfigure(content, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfigure loads configuration from bytes and unmarshal into c.
|
||||||
|
// Now it supports json, yaml and toml format.
|
||||||
|
func LoadConfigure(b []byte, c any) error {
|
||||||
|
var tomlObj interface{}
|
||||||
|
if err := toml.Unmarshal(b, &tomlObj); err == nil {
|
||||||
|
b, err = json.Marshal(&tomlObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(b), 4096)
|
||||||
|
return decoder.Decode(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyConfigurerFromMsg(m *msg.NewProxy, serverCfg *v1.ServerConfig) (v1.ProxyConfigurer, error) {
|
||||||
|
m.ProxyType = util.EmptyOr(m.ProxyType, string(v1.ProxyTypeTCP))
|
||||||
|
|
||||||
|
configurer := v1.NewProxyConfigurerByType(v1.ProxyType(m.ProxyType))
|
||||||
|
if configurer == nil {
|
||||||
|
return nil, fmt.Errorf("unknown proxy type: %s", m.ProxyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
configurer.UnmarshalFromMsg(m)
|
||||||
|
configurer.Complete("")
|
||||||
|
|
||||||
|
if err := validation.ValidateProxyConfigurerForServer(configurer, serverCfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return configurer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadServerConfig(path string) (*v1.ServerConfig, bool, error) {
|
||||||
|
var (
|
||||||
|
svrCfg *v1.ServerConfig
|
||||||
|
isLegacyFormat bool
|
||||||
|
)
|
||||||
|
// detect legacy ini format
|
||||||
|
if DetectLegacyINIFormatFromFile(path) {
|
||||||
|
content, err := legacy.GetRenderedConfFromFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
legacyCfg, err := legacy.UnmarshalServerConfFromIni(content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
svrCfg = legacy.Convert_ServerCommonConf_To_v1(&legacyCfg)
|
||||||
|
isLegacyFormat = true
|
||||||
|
} else {
|
||||||
|
svrCfg = &v1.ServerConfig{}
|
||||||
|
if err := LoadConfigureFromFile(path, svrCfg); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if svrCfg != nil {
|
||||||
|
svrCfg.Complete()
|
||||||
|
}
|
||||||
|
return svrCfg, isLegacyFormat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadClientConfig(path string) (
|
||||||
|
*v1.ClientCommonConfig,
|
||||||
|
[]v1.ProxyConfigurer,
|
||||||
|
[]v1.VisitorConfigurer,
|
||||||
|
bool, error,
|
||||||
|
) {
|
||||||
|
var (
|
||||||
|
cliCfg *v1.ClientCommonConfig
|
||||||
|
pxyCfgs = make([]v1.ProxyConfigurer, 0)
|
||||||
|
visitorCfgs = make([]v1.VisitorConfigurer, 0)
|
||||||
|
isLegacyFormat bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if DetectLegacyINIFormatFromFile(path) {
|
||||||
|
legacyCommon, legacyPxyCfgs, legacyVisitorCfgs, err := legacy.ParseClientConfig(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, true, err
|
||||||
|
}
|
||||||
|
cliCfg = legacy.Convert_ClientCommonConf_To_v1(&legacyCommon)
|
||||||
|
for _, c := range legacyPxyCfgs {
|
||||||
|
pxyCfgs = append(pxyCfgs, legacy.Convert_ProxyConf_To_v1(c))
|
||||||
|
}
|
||||||
|
for _, c := range legacyVisitorCfgs {
|
||||||
|
visitorCfgs = append(visitorCfgs, legacy.Convert_VisitorConf_To_v1(c))
|
||||||
|
}
|
||||||
|
isLegacyFormat = true
|
||||||
|
} else {
|
||||||
|
allCfg := v1.ClientConfig{}
|
||||||
|
if err := LoadConfigureFromFile(path, &allCfg); err != nil {
|
||||||
|
return nil, nil, nil, false, err
|
||||||
|
}
|
||||||
|
cliCfg = &allCfg.ClientCommonConfig
|
||||||
|
for _, c := range allCfg.Proxies {
|
||||||
|
pxyCfgs = append(pxyCfgs, c.ProxyConfigurer)
|
||||||
|
}
|
||||||
|
for _, c := range allCfg.Visitors {
|
||||||
|
visitorCfgs = append(visitorCfgs, c.VisitorConfigurer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load additional config from includes.
|
||||||
|
// legacy ini format alredy handle this in ParseClientConfig.
|
||||||
|
if len(cliCfg.IncludeConfigFiles) > 0 && !isLegacyFormat {
|
||||||
|
extPxyCfgs, extVisitorCfgs, err := LoadAdditionalClientConfigs(cliCfg.IncludeConfigFiles, isLegacyFormat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, isLegacyFormat, err
|
||||||
|
}
|
||||||
|
pxyCfgs = append(pxyCfgs, extPxyCfgs...)
|
||||||
|
visitorCfgs = append(visitorCfgs, extVisitorCfgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by start
|
||||||
|
if len(cliCfg.Start) > 0 {
|
||||||
|
startSet := sets.New(cliCfg.Start...)
|
||||||
|
pxyCfgs = lo.Filter(pxyCfgs, func(c v1.ProxyConfigurer, _ int) bool {
|
||||||
|
return startSet.Has(c.GetBaseConfig().Name)
|
||||||
|
})
|
||||||
|
visitorCfgs = lo.Filter(visitorCfgs, func(c v1.VisitorConfigurer, _ int) bool {
|
||||||
|
return startSet.Has(c.GetBaseConfig().Name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if cliCfg != nil {
|
||||||
|
cliCfg.Complete()
|
||||||
|
}
|
||||||
|
for _, c := range pxyCfgs {
|
||||||
|
c.Complete(cliCfg.User)
|
||||||
|
}
|
||||||
|
for _, c := range visitorCfgs {
|
||||||
|
c.Complete(cliCfg)
|
||||||
|
}
|
||||||
|
return cliCfg, pxyCfgs, visitorCfgs, isLegacyFormat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadAdditionalClientConfigs(paths []string, isLegacyFormat bool) ([]v1.ProxyConfigurer, []v1.VisitorConfigurer, error) {
|
||||||
|
pxyCfgs := make([]v1.ProxyConfigurer, 0)
|
||||||
|
visitorCfgs := make([]v1.VisitorConfigurer, 0)
|
||||||
|
for _, path := range paths {
|
||||||
|
absDir, err := filepath.Abs(filepath.Dir(path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
files, err := os.ReadDir(absDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
for _, fi := range files {
|
||||||
|
if fi.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
absFile := filepath.Join(absDir, fi.Name())
|
||||||
|
if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched {
|
||||||
|
// support yaml/json/toml
|
||||||
|
cfg := v1.ClientConfig{}
|
||||||
|
if err := LoadConfigureFromFile(absFile, &cfg); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("load additional config from %s error: %v", absFile, err)
|
||||||
|
}
|
||||||
|
for _, c := range cfg.Proxies {
|
||||||
|
pxyCfgs = append(pxyCfgs, c.ProxyConfigurer)
|
||||||
|
}
|
||||||
|
for _, c := range cfg.Visitors {
|
||||||
|
visitorCfgs = append(visitorCfgs, c.VisitorConfigurer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pxyCfgs, visitorCfgs, nil
|
||||||
|
}
|
||||||
45
pkg/config/load_test.go
Normal file
45
pkg/config/load_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadConfigure(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
content := `
|
||||||
|
bindAddr = "127.0.0.1"
|
||||||
|
kcpBindPort = 7000
|
||||||
|
quicBindPort = 7001
|
||||||
|
tcpmuxHTTPConnectPort = 7005
|
||||||
|
custom404Page = "/abc.html"
|
||||||
|
transport.tcpKeepalive = 10
|
||||||
|
`
|
||||||
|
|
||||||
|
svrCfg := v1.ServerConfig{}
|
||||||
|
err := LoadConfigure([]byte(content), &svrCfg)
|
||||||
|
require.NoError(err)
|
||||||
|
require.EqualValues("127.0.0.1", svrCfg.BindAddr)
|
||||||
|
require.EqualValues(7000, svrCfg.KCPBindPort)
|
||||||
|
require.EqualValues(7001, svrCfg.QUICBindPort)
|
||||||
|
require.EqualValues(7005, svrCfg.TCPMuxHTTPConnectPort)
|
||||||
|
require.EqualValues("/abc.html", svrCfg.Custom404Page)
|
||||||
|
require.EqualValues(10, svrCfg.Transport.TCPKeepAlive)
|
||||||
|
}
|
||||||
@@ -1,936 +0,0 @@
|
|||||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopkg.in/ini.v1"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Proxy
|
|
||||||
var (
|
|
||||||
proxyConfTypeMap = map[string]reflect.Type{
|
|
||||||
consts.TCPProxy: reflect.TypeOf(TCPProxyConf{}),
|
|
||||||
consts.TCPMuxProxy: reflect.TypeOf(TCPMuxProxyConf{}),
|
|
||||||
consts.UDPProxy: reflect.TypeOf(UDPProxyConf{}),
|
|
||||||
consts.HTTPProxy: reflect.TypeOf(HTTPProxyConf{}),
|
|
||||||
consts.HTTPSProxy: reflect.TypeOf(HTTPSProxyConf{}),
|
|
||||||
consts.STCPProxy: reflect.TypeOf(STCPProxyConf{}),
|
|
||||||
consts.XTCPProxy: reflect.TypeOf(XTCPProxyConf{}),
|
|
||||||
consts.SUDPProxy: reflect.TypeOf(SUDPProxyConf{}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewConfByType(proxyType string) ProxyConf {
|
|
||||||
v, ok := proxyConfTypeMap[proxyType]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cfg := reflect.New(v).Interface().(ProxyConf)
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyConf interface {
|
|
||||||
// GetBaseConfig returns the BaseProxyConf for this config.
|
|
||||||
GetBaseConfig() *BaseProxyConf
|
|
||||||
// SetDefaultValues sets the default values for this config.
|
|
||||||
SetDefaultValues()
|
|
||||||
// UnmarshalFromMsg unmarshals a msg.NewProxy message into this config.
|
|
||||||
// This function will be called on the frps side.
|
|
||||||
UnmarshalFromMsg(*msg.NewProxy)
|
|
||||||
// UnmarshalFromIni unmarshals a ini.Section into this config. This function
|
|
||||||
// will be called on the frpc side.
|
|
||||||
UnmarshalFromIni(string, string, *ini.Section) error
|
|
||||||
// MarshalToMsg marshals this config into a msg.NewProxy message. This
|
|
||||||
// function will be called on the frpc side.
|
|
||||||
MarshalToMsg(*msg.NewProxy)
|
|
||||||
// ValidateForClient checks that the config is valid for the frpc side.
|
|
||||||
ValidateForClient() error
|
|
||||||
// ValidateForServer checks that the config is valid for the frps side.
|
|
||||||
ValidateForServer(ServerCommonConf) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalSvrConf configures what location the client will to, or what
|
|
||||||
// plugin will be used.
|
|
||||||
type LocalSvrConf struct {
|
|
||||||
// LocalIP specifies the IP address or host name to to.
|
|
||||||
LocalIP string `ini:"local_ip" json:"local_ip"`
|
|
||||||
// LocalPort specifies the port to to.
|
|
||||||
LocalPort int `ini:"local_port" json:"local_port"`
|
|
||||||
|
|
||||||
// Plugin specifies what plugin should be used for ng. If this value
|
|
||||||
// is set, the LocalIp and LocalPort values will be ignored. By default,
|
|
||||||
// this value is "".
|
|
||||||
Plugin string `ini:"plugin" json:"plugin"`
|
|
||||||
// PluginParams specify parameters to be passed to the plugin, if one is
|
|
||||||
// being used. By default, this value is an empty map.
|
|
||||||
PluginParams map[string]string `ini:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealthCheckConf configures health checking. This can be useful for load
|
|
||||||
// balancing purposes to detect and remove proxies to failing services.
|
|
||||||
type HealthCheckConf struct {
|
|
||||||
// HealthCheckType specifies what protocol to use for health checking.
|
|
||||||
// Valid values include "tcp", "http", and "". If this value is "", health
|
|
||||||
// checking will not be performed. By default, this value is "".
|
|
||||||
//
|
|
||||||
// If the type is "tcp", a connection will be attempted to the target
|
|
||||||
// server. If a connection cannot be established, the health check fails.
|
|
||||||
//
|
|
||||||
// If the type is "http", a GET request will be made to the endpoint
|
|
||||||
// specified by HealthCheckURL. If the response is not a 200, the health
|
|
||||||
// check fails.
|
|
||||||
HealthCheckType string `ini:"health_check_type" json:"health_check_type"` // tcp | http
|
|
||||||
// HealthCheckTimeoutS specifies the number of seconds to wait for a health
|
|
||||||
// check attempt to connect. If the timeout is reached, this counts as a
|
|
||||||
// health check failure. By default, this value is 3.
|
|
||||||
HealthCheckTimeoutS int `ini:"health_check_timeout_s" json:"health_check_timeout_s"`
|
|
||||||
// HealthCheckMaxFailed specifies the number of allowed failures before the
|
|
||||||
// is stopped. By default, this value is 1.
|
|
||||||
HealthCheckMaxFailed int `ini:"health_check_max_failed" json:"health_check_max_failed"`
|
|
||||||
// HealthCheckIntervalS specifies the time in seconds between health
|
|
||||||
// checks. By default, this value is 10.
|
|
||||||
HealthCheckIntervalS int `ini:"health_check_interval_s" json:"health_check_interval_s"`
|
|
||||||
// HealthCheckURL specifies the address to send health checks to if the
|
|
||||||
// health check type is "http".
|
|
||||||
HealthCheckURL string `ini:"health_check_url" json:"health_check_url"`
|
|
||||||
// HealthCheckAddr specifies the address to connect to if the health check
|
|
||||||
// type is "tcp".
|
|
||||||
HealthCheckAddr string `ini:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BaseProxyConf provides configuration info that is common to all types.
|
|
||||||
type BaseProxyConf struct {
|
|
||||||
// ProxyName is the name of this
|
|
||||||
ProxyName string `ini:"name" json:"name"`
|
|
||||||
// ProxyType specifies the type of this Valid values include "tcp",
|
|
||||||
// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
|
|
||||||
// "tcp".
|
|
||||||
ProxyType string `ini:"type" json:"type"`
|
|
||||||
|
|
||||||
// UseEncryption controls whether or not communication with the server will
|
|
||||||
// be encrypted. Encryption is done using the tokens supplied in the server
|
|
||||||
// and client configuration. By default, this value is false.
|
|
||||||
UseEncryption bool `ini:"use_encryption" json:"use_encryption"`
|
|
||||||
// UseCompression controls whether or not communication with the server
|
|
||||||
// will be compressed. By default, this value is false.
|
|
||||||
UseCompression bool `ini:"use_compression" json:"use_compression"`
|
|
||||||
// Group specifies which group the is a part of. The server will use
|
|
||||||
// this information to load balance proxies in the same group. If the value
|
|
||||||
// is "", this will not be in a group. By default, this value is "".
|
|
||||||
Group string `ini:"group" json:"group"`
|
|
||||||
// GroupKey specifies a group key, which should be the same among proxies
|
|
||||||
// of the same group. By default, this value is "".
|
|
||||||
GroupKey string `ini:"group_key" json:"group_key"`
|
|
||||||
|
|
||||||
// ProxyProtocolVersion specifies which protocol version to use. Valid
|
|
||||||
// values include "v1", "v2", and "". If the value is "", a protocol
|
|
||||||
// version will be automatically selected. By default, this value is "".
|
|
||||||
ProxyProtocolVersion string `ini:"proxy_protocol_version" json:"proxy_protocol_version"`
|
|
||||||
|
|
||||||
// BandwidthLimit limit the bandwidth
|
|
||||||
// 0 means no limit
|
|
||||||
BandwidthLimit BandwidthQuantity `ini:"bandwidth_limit" json:"bandwidth_limit"`
|
|
||||||
// BandwidthLimitMode specifies whether to limit the bandwidth on the
|
|
||||||
// client or server side. Valid values include "client" and "server".
|
|
||||||
// By default, this value is "client".
|
|
||||||
BandwidthLimitMode string `ini:"bandwidth_limit_mode" json:"bandwidth_limit_mode"`
|
|
||||||
|
|
||||||
// meta info for each proxy
|
|
||||||
Metas map[string]string `ini:"-" json:"metas"`
|
|
||||||
|
|
||||||
LocalSvrConf `ini:",extends"`
|
|
||||||
HealthCheckConf `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DomainConf struct {
|
|
||||||
CustomDomains []string `ini:"custom_domains" json:"custom_domains"`
|
|
||||||
SubDomain string `ini:"subdomain" json:"subdomain"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RoleServerCommonConf struct {
|
|
||||||
Role string `ini:"role" json:"role"`
|
|
||||||
Sk string `ini:"sk" json:"sk"`
|
|
||||||
AllowUsers []string `ini:"allow_users" json:"allow_users"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *RoleServerCommonConf) setDefaultValues() {
|
|
||||||
cfg.Role = "server"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *RoleServerCommonConf) marshalToMsg(m *msg.NewProxy) {
|
|
||||||
m.Sk = cfg.Sk
|
|
||||||
m.AllowUsers = cfg.AllowUsers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *RoleServerCommonConf) unmarshalFromMsg(m *msg.NewProxy) {
|
|
||||||
cfg.Sk = m.Sk
|
|
||||||
cfg.AllowUsers = m.AllowUsers
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP
|
|
||||||
type HTTPProxyConf struct {
|
|
||||||
BaseProxyConf `ini:",extends"`
|
|
||||||
DomainConf `ini:",extends"`
|
|
||||||
|
|
||||||
Locations []string `ini:"locations" json:"locations"`
|
|
||||||
HTTPUser string `ini:"http_user" json:"http_user"`
|
|
||||||
HTTPPwd string `ini:"http_pwd" json:"http_pwd"`
|
|
||||||
HostHeaderRewrite string `ini:"host_header_rewrite" json:"host_header_rewrite"`
|
|
||||||
Headers map[string]string `ini:"-" json:"headers"`
|
|
||||||
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPS
|
|
||||||
type HTTPSProxyConf struct {
|
|
||||||
BaseProxyConf `ini:",extends"`
|
|
||||||
DomainConf `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCP
|
|
||||||
type TCPProxyConf struct {
|
|
||||||
BaseProxyConf `ini:",extends"`
|
|
||||||
RemotePort int `ini:"remote_port" json:"remote_port"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDP
|
|
||||||
type UDPProxyConf struct {
|
|
||||||
BaseProxyConf `ini:",extends"`
|
|
||||||
|
|
||||||
RemotePort int `ini:"remote_port" json:"remote_port"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCPMux
|
|
||||||
type TCPMuxProxyConf struct {
|
|
||||||
BaseProxyConf `ini:",extends"`
|
|
||||||
DomainConf `ini:",extends"`
|
|
||||||
HTTPUser string `ini:"http_user" json:"http_user,omitempty"`
|
|
||||||
HTTPPwd string `ini:"http_pwd" json:"http_pwd,omitempty"`
|
|
||||||
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
|
||||||
|
|
||||||
Multiplexer string `ini:"multiplexer"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// STCP
|
|
||||||
type STCPProxyConf struct {
|
|
||||||
BaseProxyConf `ini:",extends"`
|
|
||||||
RoleServerCommonConf `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// XTCP
|
|
||||||
type XTCPProxyConf struct {
|
|
||||||
BaseProxyConf `ini:",extends"`
|
|
||||||
RoleServerCommonConf `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SUDP
|
|
||||||
type SUDPProxyConf struct {
|
|
||||||
BaseProxyConf `ini:",extends"`
|
|
||||||
RoleServerCommonConf `ini:",extends"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxy Conf Loader
|
|
||||||
// DefaultProxyConf creates a empty ProxyConf object by proxyType.
|
|
||||||
// If proxyType doesn't exist, return nil.
|
|
||||||
func DefaultProxyConf(proxyType string) ProxyConf {
|
|
||||||
conf := NewConfByType(proxyType)
|
|
||||||
if conf != nil {
|
|
||||||
conf.SetDefaultValues()
|
|
||||||
}
|
|
||||||
return conf
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxy loaded from ini
|
|
||||||
func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf, error) {
|
|
||||||
// section.Key: if key not exists, section will set it with default value.
|
|
||||||
proxyType := section.Key("type").String()
|
|
||||||
if proxyType == "" {
|
|
||||||
proxyType = consts.TCPProxy
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := DefaultProxyConf(proxyType)
|
|
||||||
if conf == nil {
|
|
||||||
return nil, fmt.Errorf("invalid type [%s]", proxyType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := conf.ValidateForClient(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxy loaded from msg
|
|
||||||
func NewProxyConfFromMsg(m *msg.NewProxy, serverCfg ServerCommonConf) (ProxyConf, error) {
|
|
||||||
if m.ProxyType == "" {
|
|
||||||
m.ProxyType = consts.TCPProxy
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := DefaultProxyConf(m.ProxyType)
|
|
||||||
if conf == nil {
|
|
||||||
return nil, fmt.Errorf("proxy [%s] type [%s] error", m.ProxyName, m.ProxyType)
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.UnmarshalFromMsg(m)
|
|
||||||
|
|
||||||
err := conf.ValidateForServer(serverCfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base
|
|
||||||
func (cfg *BaseProxyConf) GetBaseConfig() *BaseProxyConf {
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) SetDefaultValues() {
|
|
||||||
cfg.LocalSvrConf = LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
}
|
|
||||||
cfg.BandwidthLimitMode = BandwidthLimitModeClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// BaseProxyConf apply custom logic changes.
|
|
||||||
func (cfg *BaseProxyConf) decorate(prefix string, name string, section *ini.Section) error {
|
|
||||||
// proxy_name
|
|
||||||
cfg.ProxyName = prefix + name
|
|
||||||
|
|
||||||
// metas_xxx
|
|
||||||
cfg.Metas = GetMapWithoutPrefix(section.KeysHash(), "meta_")
|
|
||||||
|
|
||||||
// bandwidth_limit
|
|
||||||
if bandwidth, err := section.GetKey("bandwidth_limit"); err == nil {
|
|
||||||
cfg.BandwidthLimit, err = NewBandwidthQuantity(bandwidth.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// plugin_xxx
|
|
||||||
cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_")
|
|
||||||
|
|
||||||
// custom logic code
|
|
||||||
if cfg.HealthCheckType == "tcp" && cfg.Plugin == "" {
|
|
||||||
cfg.HealthCheckAddr = cfg.LocalIP + fmt.Sprintf(":%d", cfg.LocalPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckURL != "" {
|
|
||||||
s := "http://" + net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
|
|
||||||
if !strings.HasPrefix(cfg.HealthCheckURL, "/") {
|
|
||||||
s += "/"
|
|
||||||
}
|
|
||||||
cfg.HealthCheckURL = s + cfg.HealthCheckURL
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) marshalToMsg(m *msg.NewProxy) {
|
|
||||||
m.ProxyName = cfg.ProxyName
|
|
||||||
m.ProxyType = cfg.ProxyType
|
|
||||||
m.UseEncryption = cfg.UseEncryption
|
|
||||||
m.UseCompression = cfg.UseCompression
|
|
||||||
m.BandwidthLimit = cfg.BandwidthLimit.String()
|
|
||||||
// leave it empty for default value to reduce traffic
|
|
||||||
if cfg.BandwidthLimitMode != "client" {
|
|
||||||
m.BandwidthLimitMode = cfg.BandwidthLimitMode
|
|
||||||
}
|
|
||||||
m.Group = cfg.Group
|
|
||||||
m.GroupKey = cfg.GroupKey
|
|
||||||
m.Metas = cfg.Metas
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) unmarshalFromMsg(m *msg.NewProxy) {
|
|
||||||
cfg.ProxyName = m.ProxyName
|
|
||||||
cfg.ProxyType = m.ProxyType
|
|
||||||
cfg.UseEncryption = m.UseEncryption
|
|
||||||
cfg.UseCompression = m.UseCompression
|
|
||||||
if m.BandwidthLimit != "" {
|
|
||||||
cfg.BandwidthLimit, _ = NewBandwidthQuantity(m.BandwidthLimit)
|
|
||||||
}
|
|
||||||
if m.BandwidthLimitMode != "" {
|
|
||||||
cfg.BandwidthLimitMode = m.BandwidthLimitMode
|
|
||||||
}
|
|
||||||
cfg.Group = m.Group
|
|
||||||
cfg.GroupKey = m.GroupKey
|
|
||||||
cfg.Metas = m.Metas
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) validateForClient() (err error) {
|
|
||||||
if cfg.ProxyProtocolVersion != "" {
|
|
||||||
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
|
|
||||||
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
|
|
||||||
return fmt.Errorf("bandwidth_limit_mode should be client or server")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = cfg.LocalSvrConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = cfg.HealthCheckConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) validateForServer() (err error) {
|
|
||||||
if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
|
|
||||||
return fmt.Errorf("bandwidth_limit_mode should be client or server")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainConf
|
|
||||||
func (cfg *DomainConf) check() (err error) {
|
|
||||||
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
|
|
||||||
err = fmt.Errorf("custom_domains and subdomain should set at least one of them")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *DomainConf) validateForClient() (err error) {
|
|
||||||
if err = cfg.check(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *DomainConf) validateForServer(serverCfg ServerCommonConf) (err error) {
|
|
||||||
if err = cfg.check(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, domain := range cfg.CustomDomains {
|
|
||||||
if serverCfg.SubDomainHost != "" && len(strings.Split(serverCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
|
||||||
if strings.Contains(domain, serverCfg.SubDomainHost) {
|
|
||||||
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, serverCfg.SubDomainHost)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.SubDomain != "" {
|
|
||||||
if serverCfg.SubDomainHost == "" {
|
|
||||||
return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
|
|
||||||
}
|
|
||||||
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
|
|
||||||
return fmt.Errorf("'.' and '*' is not supported in subdomain")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalSvrConf
|
|
||||||
func (cfg *LocalSvrConf) validateForClient() (err error) {
|
|
||||||
if cfg.Plugin == "" {
|
|
||||||
if cfg.LocalIP == "" {
|
|
||||||
err = fmt.Errorf("local ip or plugin is required")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cfg.LocalPort <= 0 {
|
|
||||||
err = fmt.Errorf("error local_port")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealthCheckConf
|
|
||||||
func (cfg *HealthCheckConf) validateForClient() error {
|
|
||||||
if cfg.HealthCheckType != "" && cfg.HealthCheckType != "tcp" && cfg.HealthCheckType != "http" {
|
|
||||||
return fmt.Errorf("unsupport health check type")
|
|
||||||
}
|
|
||||||
if cfg.HealthCheckType != "" {
|
|
||||||
if cfg.HealthCheckType == "http" && cfg.HealthCheckURL == "" {
|
|
||||||
return fmt.Errorf("health_check_url is required for health check type 'http'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, section *ini.Section) error {
|
|
||||||
err := section.MapTo(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cfg.GetBaseConfig().decorate(prefix, name, section)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCP
|
|
||||||
func (cfg *TCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
cfg.RemotePort = m.RemotePort
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *TCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.marshalToMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic marshal if exists
|
|
||||||
m.RemotePort = cfg.RemotePort
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *TCPProxyConf) ValidateForClient() (err error) {
|
|
||||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic check if exists
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *TCPProxyConf) ValidateForServer(serverCfg ServerCommonConf) error {
|
|
||||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCPMux
|
|
||||||
func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
cfg.CustomDomains = m.CustomDomains
|
|
||||||
cfg.SubDomain = m.SubDomain
|
|
||||||
cfg.Multiplexer = m.Multiplexer
|
|
||||||
cfg.HTTPUser = m.HTTPUser
|
|
||||||
cfg.HTTPPwd = m.HTTPPwd
|
|
||||||
cfg.RouteByHTTPUser = m.RouteByHTTPUser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *TCPMuxProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.marshalToMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic marshal if exists
|
|
||||||
m.CustomDomains = cfg.CustomDomains
|
|
||||||
m.SubDomain = cfg.SubDomain
|
|
||||||
m.Multiplexer = cfg.Multiplexer
|
|
||||||
m.HTTPUser = cfg.HTTPUser
|
|
||||||
m.HTTPPwd = cfg.HTTPPwd
|
|
||||||
m.RouteByHTTPUser = cfg.RouteByHTTPUser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *TCPMuxProxyConf) ValidateForClient() (err error) {
|
|
||||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic check if exists
|
|
||||||
if err = cfg.DomainConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
|
|
||||||
return fmt.Errorf("parse conf error: incorrect multiplexer [%s]", cfg.Multiplexer)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *TCPMuxProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
|
|
||||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
|
|
||||||
return fmt.Errorf("proxy [%s] incorrect multiplexer [%s]", cfg.ProxyName, cfg.Multiplexer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Multiplexer == consts.HTTPConnectTCPMultiplexer && serverCfg.TCPMuxHTTPConnectPort == 0 {
|
|
||||||
return fmt.Errorf("proxy [%s] type [tcpmux] with multiplexer [httpconnect] requires tcpmux_httpconnect_port configuration", cfg.ProxyName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
|
|
||||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDP
|
|
||||||
func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *UDPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
cfg.RemotePort = m.RemotePort
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *UDPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.marshalToMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic marshal if exists
|
|
||||||
m.RemotePort = cfg.RemotePort
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *UDPProxyConf) ValidateForClient() (err error) {
|
|
||||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic check if exists
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *UDPProxyConf) ValidateForServer(serverCfg ServerCommonConf) error {
|
|
||||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP
|
|
||||||
func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
cfg.Headers = GetMapWithoutPrefix(section.KeysHash(), "header_")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *HTTPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
cfg.CustomDomains = m.CustomDomains
|
|
||||||
cfg.SubDomain = m.SubDomain
|
|
||||||
cfg.Locations = m.Locations
|
|
||||||
cfg.HostHeaderRewrite = m.HostHeaderRewrite
|
|
||||||
cfg.HTTPUser = m.HTTPUser
|
|
||||||
cfg.HTTPPwd = m.HTTPPwd
|
|
||||||
cfg.Headers = m.Headers
|
|
||||||
cfg.RouteByHTTPUser = m.RouteByHTTPUser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *HTTPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.marshalToMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic marshal if exists
|
|
||||||
m.CustomDomains = cfg.CustomDomains
|
|
||||||
m.SubDomain = cfg.SubDomain
|
|
||||||
m.Locations = cfg.Locations
|
|
||||||
m.HostHeaderRewrite = cfg.HostHeaderRewrite
|
|
||||||
m.HTTPUser = cfg.HTTPUser
|
|
||||||
m.HTTPPwd = cfg.HTTPPwd
|
|
||||||
m.Headers = cfg.Headers
|
|
||||||
m.RouteByHTTPUser = cfg.RouteByHTTPUser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *HTTPProxyConf) ValidateForClient() (err error) {
|
|
||||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic check if exists
|
|
||||||
if err = cfg.DomainConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *HTTPProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
|
|
||||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverCfg.VhostHTTPPort == 0 {
|
|
||||||
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
|
|
||||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPS
|
|
||||||
func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *HTTPSProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
cfg.CustomDomains = m.CustomDomains
|
|
||||||
cfg.SubDomain = m.SubDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *HTTPSProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.marshalToMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic marshal if exists
|
|
||||||
m.CustomDomains = cfg.CustomDomains
|
|
||||||
m.SubDomain = cfg.SubDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *HTTPSProxyConf) ValidateForClient() (err error) {
|
|
||||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic check if exists
|
|
||||||
if err = cfg.DomainConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *HTTPSProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
|
|
||||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverCfg.VhostHTTPSPort == 0 {
|
|
||||||
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
|
|
||||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SUDP
|
|
||||||
func (cfg *SUDPProxyConf) SetDefaultValues() {
|
|
||||||
cfg.BaseProxyConf.SetDefaultValues()
|
|
||||||
cfg.RoleServerCommonConf.setDefaultValues()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only for role server.
|
|
||||||
func (cfg *SUDPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
cfg.RoleServerCommonConf.unmarshalFromMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *SUDPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.marshalToMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic marshal if exists
|
|
||||||
cfg.RoleServerCommonConf.marshalToMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *SUDPProxyConf) ValidateForClient() (err error) {
|
|
||||||
if err := cfg.BaseProxyConf.validateForClient(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic check if exists
|
|
||||||
if cfg.Role != "server" {
|
|
||||||
return fmt.Errorf("role should be 'server'")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *SUDPProxyConf) ValidateForServer(serverCfg ServerCommonConf) error {
|
|
||||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// STCP
|
|
||||||
func (cfg *STCPProxyConf) SetDefaultValues() {
|
|
||||||
cfg.BaseProxyConf.SetDefaultValues()
|
|
||||||
cfg.RoleServerCommonConf.setDefaultValues()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
if cfg.Role == "" {
|
|
||||||
cfg.Role = "server"
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only for role server.
|
|
||||||
func (cfg *STCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
cfg.RoleServerCommonConf.unmarshalFromMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *STCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.marshalToMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic marshal if exists
|
|
||||||
cfg.RoleServerCommonConf.marshalToMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *STCPProxyConf) ValidateForClient() (err error) {
|
|
||||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic check if exists
|
|
||||||
if cfg.Role != "server" {
|
|
||||||
return fmt.Errorf("role should be 'server'")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *STCPProxyConf) ValidateForServer(serverCfg ServerCommonConf) error {
|
|
||||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// XTCP
|
|
||||||
func (cfg *XTCPProxyConf) SetDefaultValues() {
|
|
||||||
cfg.BaseProxyConf.SetDefaultValues()
|
|
||||||
cfg.RoleServerCommonConf.setDefaultValues()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
if cfg.Role == "" {
|
|
||||||
cfg.Role = "server"
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only for role server.
|
|
||||||
func (cfg *XTCPProxyConf) UnmarshalFromMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.unmarshalFromMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
|
||||||
cfg.RoleServerCommonConf.unmarshalFromMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *XTCPProxyConf) MarshalToMsg(m *msg.NewProxy) {
|
|
||||||
cfg.BaseProxyConf.marshalToMsg(m)
|
|
||||||
|
|
||||||
// Add custom logic marshal if exists
|
|
||||||
cfg.RoleServerCommonConf.marshalToMsg(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *XTCPProxyConf) ValidateForClient() (err error) {
|
|
||||||
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic check if exists
|
|
||||||
if cfg.Role != "server" {
|
|
||||||
return fmt.Errorf("role should be 'server'")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *XTCPProxyConf) ValidateForServer(serverCfg ServerCommonConf) error {
|
|
||||||
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,478 +0,0 @@
|
|||||||
// Copyright 2020 The frp Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"gopkg.in/ini.v1"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
testLoadOptions = ini.LoadOptions{
|
|
||||||
Insensitive: false,
|
|
||||||
InsensitiveSections: false,
|
|
||||||
InsensitiveKeys: false,
|
|
||||||
IgnoreInlineComment: true,
|
|
||||||
AllowBooleanKeys: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
testProxyPrefix = "test."
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Proxy_Interface(t *testing.T) {
|
|
||||||
for name := range proxyConfTypeMap {
|
|
||||||
NewConfByType(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testcases := []struct {
|
|
||||||
sname string
|
|
||||||
source []byte
|
|
||||||
expected ProxyConf
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
sname: "ssh",
|
|
||||||
source: []byte(`
|
|
||||||
[ssh]
|
|
||||||
# tcp | udp | http | https | stcp | xtcp, default is tcp
|
|
||||||
type = tcp
|
|
||||||
local_ip = 127.0.0.9
|
|
||||||
local_port = 29
|
|
||||||
bandwidth_limit = 19MB
|
|
||||||
bandwidth_limit_mode = server
|
|
||||||
use_encryption
|
|
||||||
use_compression
|
|
||||||
remote_port = 6009
|
|
||||||
group = test_group
|
|
||||||
group_key = 123456
|
|
||||||
health_check_type = tcp
|
|
||||||
health_check_timeout_s = 3
|
|
||||||
health_check_max_failed = 3
|
|
||||||
health_check_interval_s = 19
|
|
||||||
meta_var1 = 123
|
|
||||||
meta_var2 = 234`),
|
|
||||||
expected: &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "ssh",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
UseCompression: true,
|
|
||||||
UseEncryption: true,
|
|
||||||
Group: "test_group",
|
|
||||||
GroupKey: "123456",
|
|
||||||
BandwidthLimit: MustBandwidthQuantity("19MB"),
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeServer,
|
|
||||||
Metas: map[string]string{
|
|
||||||
"var1": "123",
|
|
||||||
"var2": "234",
|
|
||||||
},
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 29,
|
|
||||||
},
|
|
||||||
HealthCheckConf: HealthCheckConf{
|
|
||||||
HealthCheckType: consts.TCPProxy,
|
|
||||||
HealthCheckTimeoutS: 3,
|
|
||||||
HealthCheckMaxFailed: 3,
|
|
||||||
HealthCheckIntervalS: 19,
|
|
||||||
HealthCheckAddr: "127.0.0.9:29",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RemotePort: 6009,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sname: "ssh_random",
|
|
||||||
source: []byte(`
|
|
||||||
[ssh_random]
|
|
||||||
type = tcp
|
|
||||||
local_ip = 127.0.0.9
|
|
||||||
local_port = 29
|
|
||||||
remote_port = 9
|
|
||||||
`),
|
|
||||||
expected: &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "ssh_random",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 29,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 9,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sname: "dns",
|
|
||||||
source: []byte(`
|
|
||||||
[dns]
|
|
||||||
type = udp
|
|
||||||
local_ip = 114.114.114.114
|
|
||||||
local_port = 59
|
|
||||||
remote_port = 6009
|
|
||||||
use_encryption
|
|
||||||
use_compression
|
|
||||||
`),
|
|
||||||
expected: &UDPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "dns",
|
|
||||||
ProxyType: consts.UDPProxy,
|
|
||||||
UseEncryption: true,
|
|
||||||
UseCompression: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "114.114.114.114",
|
|
||||||
LocalPort: 59,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6009,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sname: "web01",
|
|
||||||
source: []byte(`
|
|
||||||
[web01]
|
|
||||||
type = http
|
|
||||||
local_ip = 127.0.0.9
|
|
||||||
local_port = 89
|
|
||||||
use_encryption
|
|
||||||
use_compression
|
|
||||||
http_user = admin
|
|
||||||
http_pwd = admin
|
|
||||||
subdomain = web01
|
|
||||||
custom_domains = web02.yourdomain.com
|
|
||||||
locations = /,/pic
|
|
||||||
host_header_rewrite = example.com
|
|
||||||
header_X-From-Where = frp
|
|
||||||
health_check_type = http
|
|
||||||
health_check_url = /status
|
|
||||||
health_check_interval_s = 19
|
|
||||||
health_check_max_failed = 3
|
|
||||||
health_check_timeout_s = 3
|
|
||||||
`),
|
|
||||||
expected: &HTTPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "web01",
|
|
||||||
ProxyType: consts.HTTPProxy,
|
|
||||||
UseCompression: true,
|
|
||||||
UseEncryption: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 89,
|
|
||||||
},
|
|
||||||
HealthCheckConf: HealthCheckConf{
|
|
||||||
HealthCheckType: consts.HTTPProxy,
|
|
||||||
HealthCheckTimeoutS: 3,
|
|
||||||
HealthCheckMaxFailed: 3,
|
|
||||||
HealthCheckIntervalS: 19,
|
|
||||||
HealthCheckURL: "http://127.0.0.9:89/status",
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
DomainConf: DomainConf{
|
|
||||||
CustomDomains: []string{"web02.yourdomain.com"},
|
|
||||||
SubDomain: "web01",
|
|
||||||
},
|
|
||||||
Locations: []string{"/", "/pic"},
|
|
||||||
HTTPUser: "admin",
|
|
||||||
HTTPPwd: "admin",
|
|
||||||
HostHeaderRewrite: "example.com",
|
|
||||||
Headers: map[string]string{
|
|
||||||
"X-From-Where": "frp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sname: "web02",
|
|
||||||
source: []byte(`
|
|
||||||
[web02]
|
|
||||||
type = https
|
|
||||||
local_ip = 127.0.0.9
|
|
||||||
local_port = 8009
|
|
||||||
use_encryption
|
|
||||||
use_compression
|
|
||||||
subdomain = web01
|
|
||||||
custom_domains = web02.yourdomain.com
|
|
||||||
proxy_protocol_version = v2
|
|
||||||
`),
|
|
||||||
expected: &HTTPSProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "web02",
|
|
||||||
ProxyType: consts.HTTPSProxy,
|
|
||||||
UseCompression: true,
|
|
||||||
UseEncryption: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 8009,
|
|
||||||
},
|
|
||||||
ProxyProtocolVersion: "v2",
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
DomainConf: DomainConf{
|
|
||||||
CustomDomains: []string{"web02.yourdomain.com"},
|
|
||||||
SubDomain: "web01",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sname: "secret_tcp",
|
|
||||||
source: []byte(`
|
|
||||||
[secret_tcp]
|
|
||||||
type = stcp
|
|
||||||
sk = abcdefg
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 22
|
|
||||||
use_encryption = false
|
|
||||||
use_compression = false
|
|
||||||
`),
|
|
||||||
expected: &STCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "secret_tcp",
|
|
||||||
ProxyType: consts.STCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
LocalPort: 22,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RoleServerCommonConf: RoleServerCommonConf{
|
|
||||||
Role: "server",
|
|
||||||
Sk: "abcdefg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sname: "p2p_tcp",
|
|
||||||
source: []byte(`
|
|
||||||
[p2p_tcp]
|
|
||||||
type = xtcp
|
|
||||||
sk = abcdefg
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 22
|
|
||||||
use_encryption = false
|
|
||||||
use_compression = false
|
|
||||||
`),
|
|
||||||
expected: &XTCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "p2p_tcp",
|
|
||||||
ProxyType: consts.XTCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
LocalPort: 22,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RoleServerCommonConf: RoleServerCommonConf{
|
|
||||||
Role: "server",
|
|
||||||
Sk: "abcdefg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sname: "tcpmuxhttpconnect",
|
|
||||||
source: []byte(`
|
|
||||||
[tcpmuxhttpconnect]
|
|
||||||
type = tcpmux
|
|
||||||
multiplexer = httpconnect
|
|
||||||
local_ip = 127.0.0.1
|
|
||||||
local_port = 10701
|
|
||||||
custom_domains = tunnel1
|
|
||||||
`),
|
|
||||||
expected: &TCPMuxProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "tcpmuxhttpconnect",
|
|
||||||
ProxyType: consts.TCPMuxProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
LocalPort: 10701,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
DomainConf: DomainConf{
|
|
||||||
CustomDomains: []string{"tunnel1"},
|
|
||||||
SubDomain: "",
|
|
||||||
},
|
|
||||||
Multiplexer: "httpconnect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range testcases {
|
|
||||||
f, err := ini.LoadSources(testLoadOptions, c.source)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
proxyType := f.Section(c.sname).Key("type").String()
|
|
||||||
assert.NotEmpty(proxyType)
|
|
||||||
|
|
||||||
actual := DefaultProxyConf(proxyType)
|
|
||||||
assert.NotNil(actual)
|
|
||||||
|
|
||||||
err = actual.UnmarshalFromIni(testProxyPrefix, c.sname, f.Section(c.sname))
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(c.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testcases := []struct {
|
|
||||||
sname string
|
|
||||||
source []byte
|
|
||||||
expected map[string]ProxyConf
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
sname: "range:tcp_port",
|
|
||||||
source: []byte(`
|
|
||||||
[range:tcp_port]
|
|
||||||
type = tcp
|
|
||||||
local_ip = 127.0.0.9
|
|
||||||
local_port = 6010-6011,6019
|
|
||||||
remote_port = 6010-6011,6019
|
|
||||||
use_encryption = false
|
|
||||||
use_compression = false
|
|
||||||
`),
|
|
||||||
expected: map[string]ProxyConf{
|
|
||||||
"tcp_port_0": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "tcp_port_0",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 6010,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6010,
|
|
||||||
},
|
|
||||||
"tcp_port_1": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "tcp_port_1",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 6011,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6011,
|
|
||||||
},
|
|
||||||
"tcp_port_2": &TCPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "tcp_port_2",
|
|
||||||
ProxyType: consts.TCPProxy,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.9",
|
|
||||||
LocalPort: 6019,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6019,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sname: "range:udp_port",
|
|
||||||
source: []byte(`
|
|
||||||
[range:udp_port]
|
|
||||||
type = udp
|
|
||||||
local_ip = 114.114.114.114
|
|
||||||
local_port = 6000,6010-6011
|
|
||||||
remote_port = 6000,6010-6011
|
|
||||||
use_encryption
|
|
||||||
use_compression
|
|
||||||
`),
|
|
||||||
expected: map[string]ProxyConf{
|
|
||||||
"udp_port_0": &UDPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "udp_port_0",
|
|
||||||
ProxyType: consts.UDPProxy,
|
|
||||||
UseEncryption: true,
|
|
||||||
UseCompression: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "114.114.114.114",
|
|
||||||
LocalPort: 6000,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6000,
|
|
||||||
},
|
|
||||||
"udp_port_1": &UDPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "udp_port_1",
|
|
||||||
ProxyType: consts.UDPProxy,
|
|
||||||
UseEncryption: true,
|
|
||||||
UseCompression: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "114.114.114.114",
|
|
||||||
LocalPort: 6010,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6010,
|
|
||||||
},
|
|
||||||
"udp_port_2": &UDPProxyConf{
|
|
||||||
BaseProxyConf: BaseProxyConf{
|
|
||||||
ProxyName: testProxyPrefix + "udp_port_2",
|
|
||||||
ProxyType: consts.UDPProxy,
|
|
||||||
UseEncryption: true,
|
|
||||||
UseCompression: true,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "114.114.114.114",
|
|
||||||
LocalPort: 6011,
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
},
|
|
||||||
RemotePort: 6011,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range testcases {
|
|
||||||
|
|
||||||
f, err := ini.LoadSources(testLoadOptions, c.source)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
actual := make(map[string]ProxyConf)
|
|
||||||
s := f.Section(c.sname)
|
|
||||||
|
|
||||||
err = renderRangeProxyTemplates(f, s)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
f.DeleteSection(ini.DefaultSection)
|
|
||||||
f.DeleteSection(c.sname)
|
|
||||||
|
|
||||||
for _, section := range f.Sections() {
|
|
||||||
proxyType := section.Key("type").String()
|
|
||||||
newsname := section.Name()
|
|
||||||
|
|
||||||
tmp := DefaultProxyConf(proxyType)
|
|
||||||
err = tmp.UnmarshalFromIni(testProxyPrefix, newsname, section)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
actual[newsname] = tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(c.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
// Copyright 2020 The frp Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
|
||||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_LoadServerCommonConf(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testcases := []struct {
|
|
||||||
source []byte
|
|
||||||
expected ServerCommonConf
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
source: []byte(`
|
|
||||||
# [common] is integral section
|
|
||||||
[common]
|
|
||||||
bind_addr = 0.0.0.9
|
|
||||||
bind_port = 7009
|
|
||||||
kcp_bind_port = 7007
|
|
||||||
proxy_bind_addr = 127.0.0.9
|
|
||||||
vhost_http_port = 89
|
|
||||||
vhost_https_port = 449
|
|
||||||
vhost_http_timeout = 69
|
|
||||||
tcpmux_httpconnect_port = 1339
|
|
||||||
dashboard_addr = 0.0.0.9
|
|
||||||
dashboard_port = 7509
|
|
||||||
dashboard_user = admin9
|
|
||||||
dashboard_pwd = admin9
|
|
||||||
enable_prometheus
|
|
||||||
assets_dir = ./static9
|
|
||||||
log_file = ./frps.log9
|
|
||||||
log_way = file
|
|
||||||
log_level = info9
|
|
||||||
log_max_days = 39
|
|
||||||
disable_log_color = false
|
|
||||||
detailed_errors_to_client
|
|
||||||
authentication_method = token
|
|
||||||
authenticate_heartbeats = false
|
|
||||||
authenticate_new_work_conns = false
|
|
||||||
token = 123456789
|
|
||||||
oidc_issuer = test9
|
|
||||||
oidc_audience = test9
|
|
||||||
oidc_skip_expiry_check
|
|
||||||
oidc_skip_issuer_check
|
|
||||||
heartbeat_timeout = 99
|
|
||||||
user_conn_timeout = 9
|
|
||||||
allow_ports = 10-12,99
|
|
||||||
max_pool_count = 59
|
|
||||||
max_ports_per_client = 9
|
|
||||||
tls_only = false
|
|
||||||
tls_cert_file = server.crt
|
|
||||||
tls_key_file = server.key
|
|
||||||
tls_trusted_ca_file = ca.crt
|
|
||||||
subdomain_host = frps.com
|
|
||||||
tcp_mux
|
|
||||||
udp_packet_size = 1509
|
|
||||||
[plugin.user-manager]
|
|
||||||
addr = 127.0.0.1:9009
|
|
||||||
path = /handler
|
|
||||||
ops = Login
|
|
||||||
[plugin.port-manager]
|
|
||||||
addr = 127.0.0.1:9009
|
|
||||||
path = /handler
|
|
||||||
ops = NewProxy
|
|
||||||
tls_verify
|
|
||||||
`),
|
|
||||||
expected: ServerCommonConf{
|
|
||||||
ServerConfig: auth.ServerConfig{
|
|
||||||
BaseConfig: auth.BaseConfig{
|
|
||||||
AuthenticationMethod: "token",
|
|
||||||
AuthenticateHeartBeats: false,
|
|
||||||
AuthenticateNewWorkConns: false,
|
|
||||||
},
|
|
||||||
TokenConfig: auth.TokenConfig{
|
|
||||||
Token: "123456789",
|
|
||||||
},
|
|
||||||
OidcServerConfig: auth.OidcServerConfig{
|
|
||||||
OidcIssuer: "test9",
|
|
||||||
OidcAudience: "test9",
|
|
||||||
OidcSkipExpiryCheck: true,
|
|
||||||
OidcSkipIssuerCheck: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
BindAddr: "0.0.0.9",
|
|
||||||
BindPort: 7009,
|
|
||||||
KCPBindPort: 7007,
|
|
||||||
QUICKeepalivePeriod: 10,
|
|
||||||
QUICMaxIdleTimeout: 30,
|
|
||||||
QUICMaxIncomingStreams: 100000,
|
|
||||||
ProxyBindAddr: "127.0.0.9",
|
|
||||||
VhostHTTPPort: 89,
|
|
||||||
VhostHTTPSPort: 449,
|
|
||||||
VhostHTTPTimeout: 69,
|
|
||||||
TCPMuxHTTPConnectPort: 1339,
|
|
||||||
DashboardAddr: "0.0.0.9",
|
|
||||||
DashboardPort: 7509,
|
|
||||||
DashboardUser: "admin9",
|
|
||||||
DashboardPwd: "admin9",
|
|
||||||
EnablePrometheus: true,
|
|
||||||
AssetsDir: "./static9",
|
|
||||||
LogFile: "./frps.log9",
|
|
||||||
LogWay: "file",
|
|
||||||
LogLevel: "info9",
|
|
||||||
LogMaxDays: 39,
|
|
||||||
DisableLogColor: false,
|
|
||||||
DetailedErrorsToClient: true,
|
|
||||||
HeartbeatTimeout: 99,
|
|
||||||
UserConnTimeout: 9,
|
|
||||||
AllowPorts: map[int]struct{}{
|
|
||||||
10: {},
|
|
||||||
11: {},
|
|
||||||
12: {},
|
|
||||||
99: {},
|
|
||||||
},
|
|
||||||
AllowPortsStr: "10-12,99",
|
|
||||||
MaxPoolCount: 59,
|
|
||||||
MaxPortsPerClient: 9,
|
|
||||||
TLSOnly: true,
|
|
||||||
TLSCertFile: "server.crt",
|
|
||||||
TLSKeyFile: "server.key",
|
|
||||||
TLSTrustedCaFile: "ca.crt",
|
|
||||||
SubDomainHost: "frps.com",
|
|
||||||
TCPMux: true,
|
|
||||||
TCPMuxKeepaliveInterval: 60,
|
|
||||||
TCPKeepAlive: 7200,
|
|
||||||
UDPPacketSize: 1509,
|
|
||||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
|
||||||
|
|
||||||
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
|
||||||
"user-manager": {
|
|
||||||
Name: "user-manager",
|
|
||||||
Addr: "127.0.0.1:9009",
|
|
||||||
Path: "/handler",
|
|
||||||
Ops: []string{"Login"},
|
|
||||||
},
|
|
||||||
"port-manager": {
|
|
||||||
Name: "port-manager",
|
|
||||||
Addr: "127.0.0.1:9009",
|
|
||||||
Path: "/handler",
|
|
||||||
Ops: []string{"NewProxy"},
|
|
||||||
TLSVerify: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: []byte(`
|
|
||||||
# [common] is integral section
|
|
||||||
[common]
|
|
||||||
bind_addr = 0.0.0.9
|
|
||||||
bind_port = 7009
|
|
||||||
`),
|
|
||||||
expected: ServerCommonConf{
|
|
||||||
ServerConfig: auth.ServerConfig{
|
|
||||||
BaseConfig: auth.BaseConfig{
|
|
||||||
AuthenticationMethod: "token",
|
|
||||||
AuthenticateHeartBeats: false,
|
|
||||||
AuthenticateNewWorkConns: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
BindAddr: "0.0.0.9",
|
|
||||||
BindPort: 7009,
|
|
||||||
QUICKeepalivePeriod: 10,
|
|
||||||
QUICMaxIdleTimeout: 30,
|
|
||||||
QUICMaxIncomingStreams: 100000,
|
|
||||||
ProxyBindAddr: "0.0.0.9",
|
|
||||||
VhostHTTPTimeout: 60,
|
|
||||||
DashboardAddr: "0.0.0.0",
|
|
||||||
DashboardUser: "",
|
|
||||||
DashboardPwd: "",
|
|
||||||
EnablePrometheus: false,
|
|
||||||
LogFile: "console",
|
|
||||||
LogWay: "console",
|
|
||||||
LogLevel: "info",
|
|
||||||
LogMaxDays: 3,
|
|
||||||
DetailedErrorsToClient: true,
|
|
||||||
TCPMux: true,
|
|
||||||
TCPMuxKeepaliveInterval: 60,
|
|
||||||
TCPKeepAlive: 7200,
|
|
||||||
AllowPorts: make(map[int]struct{}),
|
|
||||||
MaxPoolCount: 5,
|
|
||||||
HeartbeatTimeout: 90,
|
|
||||||
UserConnTimeout: 10,
|
|
||||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
|
||||||
UDPPacketSize: 1500,
|
|
||||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range testcases {
|
|
||||||
actual, err := UnmarshalServerConfFromIni(c.source)
|
|
||||||
assert.NoError(err)
|
|
||||||
actual.Complete()
|
|
||||||
assert.Equal(c.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,11 +12,12 @@
|
|||||||
// 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 config
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -123,3 +124,65 @@ func (q *BandwidthQuantity) MarshalJSON() ([]byte, error) {
|
|||||||
func (q *BandwidthQuantity) Bytes() int64 {
|
func (q *BandwidthQuantity) Bytes() int64 {
|
||||||
return q.i
|
return q.i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PortsRange struct {
|
||||||
|
Start int `json:"start,omitempty"`
|
||||||
|
End int `json:"end,omitempty"`
|
||||||
|
Single int `json:"single,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortsRangeSlice []PortsRange
|
||||||
|
|
||||||
|
func (p PortsRangeSlice) String() string {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
strs := []string{}
|
||||||
|
for _, v := range p {
|
||||||
|
if v.Single > 0 {
|
||||||
|
strs = append(strs, strconv.Itoa(v.Single))
|
||||||
|
} else {
|
||||||
|
strs = append(strs, strconv.Itoa(v.Start)+"-"+strconv.Itoa(v.End))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(strs, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the format of str is like "1000-2000,3000,4000-5000"
|
||||||
|
func NewPortsRangeSliceFromString(str string) ([]PortsRange, error) {
|
||||||
|
str = strings.TrimSpace(str)
|
||||||
|
out := []PortsRange{}
|
||||||
|
numRanges := strings.Split(str, ",")
|
||||||
|
for _, numRangeStr := range numRanges {
|
||||||
|
// 1000-2000 or 2001
|
||||||
|
numArray := strings.Split(numRangeStr, "-")
|
||||||
|
// length: only 1 or 2 is correct
|
||||||
|
rangeType := len(numArray)
|
||||||
|
switch rangeType {
|
||||||
|
case 1:
|
||||||
|
// single number
|
||||||
|
singleNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("range number is invalid, %v", err)
|
||||||
|
}
|
||||||
|
out = append(out, PortsRange{Single: int(singleNum)})
|
||||||
|
case 2:
|
||||||
|
// range numbers
|
||||||
|
min, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("range number is invalid, %v", err)
|
||||||
|
}
|
||||||
|
max, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("range number is invalid, %v", err)
|
||||||
|
}
|
||||||
|
if max < min {
|
||||||
|
return nil, fmt.Errorf("range number is invalid")
|
||||||
|
}
|
||||||
|
out = append(out, PortsRange{Start: int(min), End: int(max)})
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("range number is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
@@ -12,13 +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 config
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Wrap struct {
|
type Wrap struct {
|
||||||
@@ -27,14 +27,46 @@ type Wrap struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBandwidthQuantity(t *testing.T) {
|
func TestBandwidthQuantity(t *testing.T) {
|
||||||
assert := assert.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
var w Wrap
|
var w Wrap
|
||||||
err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w)
|
err := json.Unmarshal([]byte(`{"b":"1KB","int":5}`), &w)
|
||||||
assert.NoError(err)
|
require.NoError(err)
|
||||||
assert.EqualValues(1*KB, w.B.Bytes())
|
require.EqualValues(1*KB, w.B.Bytes())
|
||||||
|
|
||||||
buf, err := json.Marshal(&w)
|
buf, err := json.Marshal(&w)
|
||||||
assert.NoError(err)
|
require.NoError(err)
|
||||||
assert.Equal(`{"b":"1KB","int":5}`, string(buf))
|
require.Equal(`{"b":"1KB","int":5}`, string(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPortsRangeSlice2String(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
ports := []PortsRange{
|
||||||
|
{
|
||||||
|
Start: 1000,
|
||||||
|
End: 2000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Single: 3000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
str := PortsRangeSlice(ports).String()
|
||||||
|
require.Equal("1000-2000,3000", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewPortsRangeSliceFromString(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
ports, err := NewPortsRangeSliceFromString("1000-2000,3000")
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal([]PortsRange{
|
||||||
|
{
|
||||||
|
Start: 1000,
|
||||||
|
End: 2000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Single: 3000,
|
||||||
|
},
|
||||||
|
}, ports)
|
||||||
}
|
}
|
||||||
19
pkg/config/v1/api.go
Normal file
19
pkg/config/v1/api.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2023 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
|
||||||
|
|
||||||
|
type APIMetadata struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
200
pkg/config/v1/client.go
Normal file
200
pkg/config/v1/client.go
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
// Copyright 2023 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 (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientConfig struct {
|
||||||
|
ClientCommonConfig
|
||||||
|
|
||||||
|
Proxies []TypedProxyConfig `json:"proxies,omitempty"`
|
||||||
|
Visitors []TypedVisitorConfig `json:"visitors,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientCommonConfig struct {
|
||||||
|
APIMetadata
|
||||||
|
|
||||||
|
Auth AuthClientConfig `json:"auth,omitempty"`
|
||||||
|
// User specifies a prefix for proxy names to distinguish them from other
|
||||||
|
// clients. If this value is not "", proxy names will automatically be
|
||||||
|
// changed to "{user}.{proxy_name}".
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
|
||||||
|
// ServerAddr specifies the address of the server to connect to. By
|
||||||
|
// default, this value is "0.0.0.0".
|
||||||
|
ServerAddr string `json:"serverAddr,omitempty"`
|
||||||
|
// ServerPort specifies the port to connect to the server on. By default,
|
||||||
|
// this value is 7000.
|
||||||
|
ServerPort int `json:"serverPort,omitempty"`
|
||||||
|
// STUN server to help penetrate NAT hole.
|
||||||
|
NatHoleSTUNServer string `json:"natHoleStunServer,omitempty"`
|
||||||
|
// DNSServer specifies a DNS server address for FRPC to use. If this value
|
||||||
|
// is "", the default DNS will be used.
|
||||||
|
DNSServer string `json:"dnsServer,omitempty"`
|
||||||
|
// LoginFailExit controls whether or not the client should exit after a
|
||||||
|
// failed login attempt. If false, the client will retry until a login
|
||||||
|
// attempt succeeds. By default, this value is true.
|
||||||
|
LoginFailExit *bool `json:"loginFailExit,omitempty"`
|
||||||
|
// Start specifies a set of enabled proxies by name. If this set is empty,
|
||||||
|
// all supplied proxies are enabled. By default, this value is an empty
|
||||||
|
// set.
|
||||||
|
Start []string `json:"start,omitempty"`
|
||||||
|
|
||||||
|
Log LogConfig `json:"log,omitempty"`
|
||||||
|
WebServer WebServerConfig `json:"webServer,omitempty"`
|
||||||
|
Transport ClientTransportConfig `json:"transport,omitempty"`
|
||||||
|
|
||||||
|
// UDPPacketSize specifies the udp packet size
|
||||||
|
// By default, this value is 1500
|
||||||
|
UDPPacketSize int64 `json:"udpPacketSize,omitempty"`
|
||||||
|
// Client metadata info
|
||||||
|
Metadatas map[string]string `json:"metadatas,omitempty"`
|
||||||
|
|
||||||
|
// Include other config files for proxies.
|
||||||
|
IncludeConfigFiles []string `json:"includes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientCommonConfig) Complete() {
|
||||||
|
c.ServerAddr = util.EmptyOr(c.ServerAddr, "0.0.0.0")
|
||||||
|
c.ServerPort = util.EmptyOr(c.ServerPort, 7000)
|
||||||
|
c.LoginFailExit = util.EmptyOr(c.LoginFailExit, lo.ToPtr(true))
|
||||||
|
c.NatHoleSTUNServer = util.EmptyOr(c.NatHoleSTUNServer, "stun.easyvoip.com:3478")
|
||||||
|
|
||||||
|
c.Auth.Complete()
|
||||||
|
c.Log.Complete()
|
||||||
|
c.Transport.Complete()
|
||||||
|
c.WebServer.Complete()
|
||||||
|
|
||||||
|
c.UDPPacketSize = util.EmptyOr(c.UDPPacketSize, 1500)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientTransportConfig struct {
|
||||||
|
// Protocol specifies the protocol to use when interacting with the server.
|
||||||
|
// Valid values are "tcp", "kcp", "quic", "websocket" and "wss". By default, this value
|
||||||
|
// is "tcp".
|
||||||
|
Protocol string `json:"protocol,omitempty"`
|
||||||
|
// The maximum amount of time a dial to server will wait for a connect to complete.
|
||||||
|
DialServerTimeout int64 `json:"dialServerTimeout,omitempty"`
|
||||||
|
// DialServerKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
|
// If negative, keep-alive probes are disabled.
|
||||||
|
DialServerKeepAlive int64 `json:"dialServerKeepalive,omitempty"`
|
||||||
|
// ConnectServerLocalIP specifies the address of the client bind when it connect to server.
|
||||||
|
// Note: This value only use in TCP/Websocket protocol. Not support in KCP protocol.
|
||||||
|
ConnectServerLocalIP string `json:"connectServerLocalIP,omitempty"`
|
||||||
|
// ProxyURL specifies a proxy address to connect to the server through. If
|
||||||
|
// this value is "", the server will be connected to directly. By default,
|
||||||
|
// this value is read from the "http_proxy" environment variable.
|
||||||
|
ProxyURL string `json:"proxyURL,omitempty"`
|
||||||
|
// PoolCount specifies the number of connections the client will make to
|
||||||
|
// the server in advance.
|
||||||
|
PoolCount int `json:"poolCount,omitempty"`
|
||||||
|
// TCPMux toggles TCP stream multiplexing. This allows multiple requests
|
||||||
|
// from a client to share a single TCP connection. If this value is true,
|
||||||
|
// the server must have TCP multiplexing enabled as well. By default, this
|
||||||
|
// value is true.
|
||||||
|
TCPMux *bool `json:"tcpMux,omitempty"`
|
||||||
|
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
|
||||||
|
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
|
||||||
|
TCPMuxKeepaliveInterval int64 `json:"tcpMuxKeepaliveInterval,omitempty"`
|
||||||
|
// QUIC protocol options.
|
||||||
|
QUIC QUICOptions `json:"quic,omitempty"`
|
||||||
|
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
||||||
|
// server, in seconds. It is not recommended to change this value. By
|
||||||
|
// default, this value is 30. Set negative value to disable it.
|
||||||
|
HeartbeatInterval int64 `json:"heartbeatInterval,omitempty"`
|
||||||
|
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||||
|
// before the connection is terminated, in seconds. It is not recommended
|
||||||
|
// to change this value. By default, this value is 90. Set negative value to disable it.
|
||||||
|
HeartbeatTimeout int64 `json:"heartbeatTimeout,omitempty"`
|
||||||
|
// TLS specifies TLS settings for the connection to the server.
|
||||||
|
TLS TLSClientConfig `json:"tls,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientTransportConfig) Complete() {
|
||||||
|
c.Protocol = util.EmptyOr(c.Protocol, "tcp")
|
||||||
|
c.DialServerTimeout = util.EmptyOr(c.DialServerTimeout, 10)
|
||||||
|
c.DialServerKeepAlive = util.EmptyOr(c.DialServerKeepAlive, 7200)
|
||||||
|
c.ProxyURL = util.EmptyOr(c.ProxyURL, os.Getenv("http_proxy"))
|
||||||
|
c.PoolCount = util.EmptyOr(c.PoolCount, 1)
|
||||||
|
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
||||||
|
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
|
||||||
|
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30)
|
||||||
|
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||||
|
c.QUIC.Complete()
|
||||||
|
c.TLS.Complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
type TLSClientConfig struct {
|
||||||
|
// TLSEnable specifies whether or not TLS should be used when communicating
|
||||||
|
// with the server. If "tls.certFile" and "tls.keyFile" are valid,
|
||||||
|
// client will load the supplied tls configuration.
|
||||||
|
// Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
|
||||||
|
Enable *bool `json:"enable,omitempty"`
|
||||||
|
// If DisableCustomTLSFirstByte is set to false, frpc will establish a connection with frps using the
|
||||||
|
// first custom byte when tls is enabled.
|
||||||
|
// Since v0.50.0, the default value has been changed to true, and the first custom byte is disabled by default.
|
||||||
|
DisableCustomTLSFirstByte *bool `json:"disableCustomTLSFirstByte,omitempty"`
|
||||||
|
|
||||||
|
TLSConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TLSClientConfig) Complete() {
|
||||||
|
c.Enable = util.EmptyOr(c.Enable, lo.ToPtr(true))
|
||||||
|
c.DisableCustomTLSFirstByte = util.EmptyOr(c.DisableCustomTLSFirstByte, lo.ToPtr(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthClientConfig struct {
|
||||||
|
// Method specifies what authentication method to use to
|
||||||
|
// authenticate frpc with frps. If "token" is specified - token will be
|
||||||
|
// read into login message. If "oidc" is specified - OIDC (Open ID Connect)
|
||||||
|
// token will be issued using OIDC settings. By default, this value is "token".
|
||||||
|
Method AuthMethod `json:"method,omitempty"`
|
||||||
|
// Specify whether to include auth info in additional scope.
|
||||||
|
// Current supported scopes are: "HeartBeats", "NewWorkConns".
|
||||||
|
AdditionalScopes []AuthScope `json:"additionalScopes,omitempty"`
|
||||||
|
// Token specifies the authorization token used to create keys to be sent
|
||||||
|
// to the server. The server must have a matching token for authorization
|
||||||
|
// to succeed. By default, this value is "".
|
||||||
|
Token string `json:"token,omitempty"`
|
||||||
|
OIDC AuthOIDCClientConfig `json:"oidc,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthClientConfig) Complete() {
|
||||||
|
c.Method = util.EmptyOr(c.Method, "token")
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthOIDCClientConfig struct {
|
||||||
|
// ClientID specifies the client ID to use to get a token in OIDC authentication.
|
||||||
|
ClientID string `json:"clientID,omitempty"`
|
||||||
|
// ClientSecret specifies the client secret to use to get a token in OIDC
|
||||||
|
// authentication.
|
||||||
|
ClientSecret string `json:"clientSecret,omitempty"`
|
||||||
|
// Audience specifies the audience of the token in OIDC authentication.
|
||||||
|
Audience string `json:"audience,omitempty"`
|
||||||
|
// Scope specifies the scope of the token in OIDC authentication.
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
// TokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
|
||||||
|
// It will be used to get an OIDC token.
|
||||||
|
TokenEndpointURL string `json:"tokenEndpointURL,omitempty"`
|
||||||
|
// AdditionalEndpointParams specifies additional parameters to be sent
|
||||||
|
// this field will be transfer to map[string][]string in OIDC token generator.
|
||||||
|
AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"`
|
||||||
|
}
|
||||||
35
pkg/config/v1/client_test.go
Normal file
35
pkg/config/v1/client_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2023 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClientConfigComplete(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
c := &ClientConfig{}
|
||||||
|
c.Complete()
|
||||||
|
|
||||||
|
require.EqualValues("token", c.Auth.Method)
|
||||||
|
require.Equal(true, lo.FromPtr(c.Transport.TCPMux))
|
||||||
|
require.Equal(true, lo.FromPtr(c.LoginFailExit))
|
||||||
|
require.Equal(true, lo.FromPtr(c.Transport.TLS.Enable))
|
||||||
|
require.Equal(true, lo.FromPtr(c.Transport.TLS.DisableCustomTLSFirstByte))
|
||||||
|
require.NotEmpty(c.NatHoleSTUNServer)
|
||||||
|
}
|
||||||
117
pkg/config/v1/common.go
Normal file
117
pkg/config/v1/common.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// Copyright 2023 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 (
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthScope string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AuthScopeHeartBeats AuthScope = "HeartBeats"
|
||||||
|
AuthScopeNewWorkConns AuthScope = "NewWorkConns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthMethod string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AuthMethodToken AuthMethod = "token"
|
||||||
|
AuthMethodOIDC AuthMethod = "oidc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QUIC protocol options
|
||||||
|
type QUICOptions struct {
|
||||||
|
KeepalivePeriod int `json:"keepalivePeriod,omitempty"`
|
||||||
|
MaxIdleTimeout int `json:"maxIdleTimeout,omitempty"`
|
||||||
|
MaxIncomingStreams int `json:"maxIncomingStreams,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *QUICOptions) Complete() {
|
||||||
|
c.KeepalivePeriod = util.EmptyOr(c.KeepalivePeriod, 10)
|
||||||
|
c.MaxIdleTimeout = util.EmptyOr(c.MaxIdleTimeout, 30)
|
||||||
|
c.MaxIncomingStreams = util.EmptyOr(c.MaxIncomingStreams, 100000)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebServerConfig struct {
|
||||||
|
// This is the network address to bind on for serving the web interface and API.
|
||||||
|
// By default, this value is "127.0.0.1".
|
||||||
|
Addr string `json:"addr,omitempty"`
|
||||||
|
// Port specifies the port for the web server to listen on. If this
|
||||||
|
// value is 0, the admin server will not be started.
|
||||||
|
Port int `json:"port,omitempty"`
|
||||||
|
// User specifies the username that the web server will use for login.
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
// Password specifies the password that the admin server will use for login.
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
// AssetsDir specifies the local directory that the admin server will load
|
||||||
|
// resources from. If this value is "", assets will be loaded from the
|
||||||
|
// bundled executable using embed package.
|
||||||
|
AssetsDir string `json:"assetsDir,omitempty"`
|
||||||
|
// Enable golang pprof handlers.
|
||||||
|
PprofEnable bool `json:"pprofEnable,omitempty"`
|
||||||
|
// Enable TLS if TLSConfig is not nil.
|
||||||
|
TLS *TLSConfig `json:"tls,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebServerConfig) Complete() {
|
||||||
|
c.Addr = util.EmptyOr(c.Addr, "127.0.0.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
type TLSConfig struct {
|
||||||
|
// CertPath specifies the path of the cert file that client will load.
|
||||||
|
CertFile string `json:"certFile,omitempty"`
|
||||||
|
// KeyPath specifies the path of the secret key file that client will load.
|
||||||
|
KeyFile string `json:"keyFile,omitempty"`
|
||||||
|
// TrustedCaFile specifies the path of the trusted ca file that will load.
|
||||||
|
TrustedCaFile string `json:"trustedCaFile,omitempty"`
|
||||||
|
// ServerName specifies the custom server name of tls certificate. By
|
||||||
|
// default, server name if same to ServerAddr.
|
||||||
|
ServerName string `json:"serverName,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogConfig struct {
|
||||||
|
// This is destination where frp should wirte the logs.
|
||||||
|
// If "console" is used, logs will be printed to stdout, otherwise,
|
||||||
|
// logs will be written to the specified file.
|
||||||
|
// By default, this value is "console".
|
||||||
|
To string `json:"to,omitempty"`
|
||||||
|
// Level specifies the minimum log level. Valid values are "trace",
|
||||||
|
// "debug", "info", "warn", and "error". By default, this value is "info".
|
||||||
|
Level string `json:"level,omitempty"`
|
||||||
|
// MaxDays specifies the maximum number of days to store log information
|
||||||
|
// before deletion.
|
||||||
|
MaxDays int64 `json:"maxDays"`
|
||||||
|
// DisablePrintColor disables log colors when log.to is "console".
|
||||||
|
DisablePrintColor bool `json:"disablePrintColor,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LogConfig) Complete() {
|
||||||
|
c.To = util.EmptyOr(c.To, "console")
|
||||||
|
c.Level = util.EmptyOr(c.Level, "info")
|
||||||
|
c.MaxDays = util.EmptyOr(c.MaxDays, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPPluginOptions struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Ops []string `json:"ops"`
|
||||||
|
TLSVerify bool `json:"tlsVerify,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeaderOperations struct {
|
||||||
|
Set map[string]string `json:"set,omitempty"`
|
||||||
|
}
|
||||||
120
pkg/config/v1/plugin.go
Normal file
120
pkg/config/v1/plugin.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2023 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientPluginOptions interface{}
|
||||||
|
|
||||||
|
type TypedClientPluginOptions struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ClientPluginOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypedClientPluginOptions) 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 nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := clientPluginOptionsTypeMap[typeStruct.Type]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown plugin type: %s", typeStruct.Type)
|
||||||
|
}
|
||||||
|
options := reflect.New(v).Interface().(ClientPluginOptions)
|
||||||
|
if err := json.Unmarshal(b, options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.ClientPluginOptions = options
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
PluginHTTP2HTTPS = "http2https"
|
||||||
|
PluginHTTPProxy = "http_proxy"
|
||||||
|
PluginHTTPS2HTTP = "https2http"
|
||||||
|
PluginHTTPS2HTTPS = "https2https"
|
||||||
|
PluginSocks5 = "socks5"
|
||||||
|
PluginStaticFile = "static_file"
|
||||||
|
PluginUnixDomainSocket = "unix_domain_socket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
||||||
|
PluginHTTP2HTTPS: reflect.TypeOf(HTTP2HTTPSPluginOptions{}),
|
||||||
|
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
|
||||||
|
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
|
||||||
|
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
|
||||||
|
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
||||||
|
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
||||||
|
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTP2HTTPSPluginOptions struct {
|
||||||
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPProxyPluginOptions struct {
|
||||||
|
HTTPUser string `json:"httpUser,omitempty"`
|
||||||
|
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPS2HTTPPluginOptions struct {
|
||||||
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPS2HTTPSPluginOptions struct {
|
||||||
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Socks5PluginOptions struct {
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StaticFilePluginOptions struct {
|
||||||
|
LocalPath string `json:"localPath,omitempty"`
|
||||||
|
StripPrefix string `json:"stripPrefix,omitempty"`
|
||||||
|
HTTPUser string `json:"httpUser,omitempty"`
|
||||||
|
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnixDomainSocketPluginOptions struct {
|
||||||
|
UnixPath string `json:"unixPath,omitempty"`
|
||||||
|
}
|
||||||
438
pkg/config/v1/proxy.go
Normal file
438
pkg/config/v1/proxy.go
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
// Copyright 2023 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyTransport struct {
|
||||||
|
// UseEncryption controls whether or not communication with the server will
|
||||||
|
// be encrypted. Encryption is done using the tokens supplied in the server
|
||||||
|
// and client configuration.
|
||||||
|
UseEncryption bool `json:"useEncryption,omitempty"`
|
||||||
|
// UseCompression controls whether or not communication with the server
|
||||||
|
// will be compressed.
|
||||||
|
UseCompression bool `json:"useCompression,omitempty"`
|
||||||
|
// BandwidthLimit limit the bandwidth
|
||||||
|
// 0 means no limit
|
||||||
|
BandwidthLimit types.BandwidthQuantity `json:"bandwidthLimit,omitempty"`
|
||||||
|
// BandwidthLimitMode specifies whether to limit the bandwidth on the
|
||||||
|
// client or server side. Valid values include "client" and "server".
|
||||||
|
// By default, this value is "client".
|
||||||
|
BandwidthLimitMode string `json:"bandwidthLimitMode,omitempty"`
|
||||||
|
// ProxyProtocolVersion specifies which protocol version to use. Valid
|
||||||
|
// values include "v1", "v2", and "". If the value is "", a protocol
|
||||||
|
// version will be automatically selected. By default, this value is "".
|
||||||
|
ProxyProtocolVersion string `json:"proxyProtocolVersion,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadBalancerConfig struct {
|
||||||
|
// Group specifies which group the is a part of. The server will use
|
||||||
|
// this information to load balance proxies in the same group. If the value
|
||||||
|
// is "", this will not be in a group.
|
||||||
|
Group string `json:"group"`
|
||||||
|
// GroupKey specifies a group key, which should be the same among proxies
|
||||||
|
// of the same group.
|
||||||
|
GroupKey string `json:"groupKey,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyBackend struct {
|
||||||
|
// LocalIP specifies the IP address or host name of the backend.
|
||||||
|
LocalIP string `json:"localIP,omitempty"`
|
||||||
|
// LocalPort specifies the port of the backend.
|
||||||
|
LocalPort int `json:"localPort,omitempty"`
|
||||||
|
|
||||||
|
// Plugin specifies what plugin should be used for handling connections. If this value
|
||||||
|
// is set, the LocalIP and LocalPort values will be ignored.
|
||||||
|
Plugin TypedClientPluginOptions `json:"plugin,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheckConfig configures health checking. This can be useful for load
|
||||||
|
// balancing purposes to detect and remove proxies to failing services.
|
||||||
|
type HealthCheckConfig struct {
|
||||||
|
// Type specifies what protocol to use for health checking.
|
||||||
|
// Valid values include "tcp", "http", and "". If this value is "", health
|
||||||
|
// checking will not be performed.
|
||||||
|
//
|
||||||
|
// If the type is "tcp", a connection will be attempted to the target
|
||||||
|
// server. If a connection cannot be established, the health check fails.
|
||||||
|
//
|
||||||
|
// If the type is "http", a GET request will be made to the endpoint
|
||||||
|
// specified by HealthCheckURL. If the response is not a 200, the health
|
||||||
|
// check fails.
|
||||||
|
Type string `json:"type"` // tcp | http
|
||||||
|
// TimeoutSeconds specifies the number of seconds to wait for a health
|
||||||
|
// check attempt to connect. If the timeout is reached, this counts as a
|
||||||
|
// health check failure. By default, this value is 3.
|
||||||
|
TimeoutSeconds int `json:"timeoutSeconds,omitempty"`
|
||||||
|
// MaxFailed specifies the number of allowed failures before the
|
||||||
|
// is stopped. By default, this value is 1.
|
||||||
|
MaxFailed int `json:"maxFailed,omitempty"`
|
||||||
|
// IntervalSeconds specifies the time in seconds between health
|
||||||
|
// checks. By default, this value is 10.
|
||||||
|
IntervalSeconds int `json:"intervalSeconds"`
|
||||||
|
// Path specifies the path to send health checks to if the
|
||||||
|
// health check type is "http".
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DomainConfig struct {
|
||||||
|
CustomDomains []string `json:"customDomains,omitempty"`
|
||||||
|
SubDomain string `json:"subdomain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyBaseConfig struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Transport ProxyTransport `json:"transport,omitempty"`
|
||||||
|
// metadata info for each proxy
|
||||||
|
Metadatas map[string]string `json:"metadatas,omitempty"`
|
||||||
|
LoadBalancer LoadBalancerConfig `json:"loadBalancer,omitempty"`
|
||||||
|
HealthCheck HealthCheckConfig `json:"healthCheck,omitempty"`
|
||||||
|
ProxyBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProxyBaseConfig) GetBaseConfig() *ProxyBaseConfig {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProxyBaseConfig) Complete(namePrefix string) {
|
||||||
|
c.Name = lo.Ternary(namePrefix == "", "", namePrefix+".") + c.Name
|
||||||
|
c.LocalIP = util.EmptyOr(c.LocalIP, "127.0.0.1")
|
||||||
|
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
|
m.ProxyName = c.Name
|
||||||
|
m.ProxyType = c.Type
|
||||||
|
m.UseEncryption = c.Transport.UseEncryption
|
||||||
|
m.UseCompression = c.Transport.UseCompression
|
||||||
|
m.BandwidthLimit = c.Transport.BandwidthLimit.String()
|
||||||
|
// leave it empty for default value to reduce traffic
|
||||||
|
if c.Transport.BandwidthLimitMode != "client" {
|
||||||
|
m.BandwidthLimitMode = c.Transport.BandwidthLimitMode
|
||||||
|
}
|
||||||
|
m.Group = c.LoadBalancer.Group
|
||||||
|
m.GroupKey = c.LoadBalancer.GroupKey
|
||||||
|
m.Metas = c.Metadatas
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProxyBaseConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
|
c.Name = m.ProxyName
|
||||||
|
c.Type = m.ProxyType
|
||||||
|
c.Transport.UseEncryption = m.UseEncryption
|
||||||
|
c.Transport.UseCompression = m.UseCompression
|
||||||
|
if m.BandwidthLimit != "" {
|
||||||
|
c.Transport.BandwidthLimit, _ = types.NewBandwidthQuantity(m.BandwidthLimit)
|
||||||
|
}
|
||||||
|
if m.BandwidthLimitMode != "" {
|
||||||
|
c.Transport.BandwidthLimitMode = m.BandwidthLimitMode
|
||||||
|
}
|
||||||
|
c.LoadBalancer.Group = m.Group
|
||||||
|
c.LoadBalancer.GroupKey = m.GroupKey
|
||||||
|
c.Metadatas = m.Metas
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypedProxyConfig struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ProxyConfigurer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypedProxyConfig) UnmarshalJSON(b []byte) error {
|
||||||
|
if len(b) == 4 && string(b) == "null" {
|
||||||
|
return errors.New("type is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
typeStruct := struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}{}
|
||||||
|
if err := json.Unmarshal(b, &typeStruct); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Type = typeStruct.Type
|
||||||
|
configurer := NewProxyConfigurerByType(ProxyType(typeStruct.Type))
|
||||||
|
if configurer == nil {
|
||||||
|
return fmt.Errorf("unknown proxy type: %s", typeStruct.Type)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(b, configurer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.ProxyConfigurer = configurer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyConfigurer interface {
|
||||||
|
Complete(namePrefix string)
|
||||||
|
GetBaseConfig() *ProxyBaseConfig
|
||||||
|
// MarshalToMsg marshals this config into a msg.NewProxy message. This
|
||||||
|
// function will be called on the frpc side.
|
||||||
|
MarshalToMsg(*msg.NewProxy)
|
||||||
|
// UnmarshalFromMsg unmarshals a msg.NewProxy message into this config.
|
||||||
|
// This function will be called on the frps side.
|
||||||
|
UnmarshalFromMsg(*msg.NewProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProxyTypeTCP ProxyType = "tcp"
|
||||||
|
ProxyTypeUDP ProxyType = "udp"
|
||||||
|
ProxyTypeTCPMUX ProxyType = "tcpmux"
|
||||||
|
ProxyTypeHTTP ProxyType = "http"
|
||||||
|
ProxyTypeHTTPS ProxyType = "https"
|
||||||
|
ProxyTypeSTCP ProxyType = "stcp"
|
||||||
|
ProxyTypeXTCP ProxyType = "xtcp"
|
||||||
|
ProxyTypeSUDP ProxyType = "sudp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var proxyConfigTypeMap = map[ProxyType]reflect.Type{
|
||||||
|
ProxyTypeTCP: reflect.TypeOf(TCPProxyConfig{}),
|
||||||
|
ProxyTypeUDP: reflect.TypeOf(UDPProxyConfig{}),
|
||||||
|
ProxyTypeHTTP: reflect.TypeOf(HTTPProxyConfig{}),
|
||||||
|
ProxyTypeHTTPS: reflect.TypeOf(HTTPSProxyConfig{}),
|
||||||
|
ProxyTypeTCPMUX: reflect.TypeOf(TCPMuxProxyConfig{}),
|
||||||
|
ProxyTypeSTCP: reflect.TypeOf(STCPProxyConfig{}),
|
||||||
|
ProxyTypeXTCP: reflect.TypeOf(XTCPProxyConfig{}),
|
||||||
|
ProxyTypeSUDP: reflect.TypeOf(SUDPProxyConfig{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyConfigurerByType(proxyType ProxyType) ProxyConfigurer {
|
||||||
|
v, ok := proxyConfigTypeMap[proxyType]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return reflect.New(v).Interface().(ProxyConfigurer)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ProxyConfigurer = &TCPProxyConfig{}
|
||||||
|
|
||||||
|
type TCPProxyConfig struct {
|
||||||
|
ProxyBaseConfig
|
||||||
|
|
||||||
|
RemotePort int `json:"remotePort,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||||
|
|
||||||
|
m.RemotePort = c.RemotePort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||||
|
|
||||||
|
c.RemotePort = m.RemotePort
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ProxyConfigurer = &UDPProxyConfig{}
|
||||||
|
|
||||||
|
type UDPProxyConfig struct {
|
||||||
|
ProxyBaseConfig
|
||||||
|
|
||||||
|
RemotePort int `json:"remotePort,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UDPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||||
|
|
||||||
|
m.RemotePort = c.RemotePort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UDPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||||
|
|
||||||
|
c.RemotePort = m.RemotePort
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ProxyConfigurer = &HTTPProxyConfig{}
|
||||||
|
|
||||||
|
type HTTPProxyConfig struct {
|
||||||
|
ProxyBaseConfig
|
||||||
|
DomainConfig
|
||||||
|
|
||||||
|
Locations []string `json:"locations,omitempty"`
|
||||||
|
HTTPUser string `json:"httpUser,omitempty"`
|
||||||
|
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||||
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
RouteByHTTPUser string `json:"routeByHTTPUser,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||||
|
|
||||||
|
m.CustomDomains = c.CustomDomains
|
||||||
|
m.SubDomain = c.SubDomain
|
||||||
|
m.Locations = c.Locations
|
||||||
|
m.HostHeaderRewrite = c.HostHeaderRewrite
|
||||||
|
m.HTTPUser = c.HTTPUser
|
||||||
|
m.HTTPPwd = c.HTTPPassword
|
||||||
|
m.Headers = c.RequestHeaders.Set
|
||||||
|
m.RouteByHTTPUser = c.RouteByHTTPUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||||
|
|
||||||
|
c.CustomDomains = m.CustomDomains
|
||||||
|
c.SubDomain = m.SubDomain
|
||||||
|
c.Locations = m.Locations
|
||||||
|
c.HostHeaderRewrite = m.HostHeaderRewrite
|
||||||
|
c.HTTPUser = m.HTTPUser
|
||||||
|
c.HTTPPassword = m.HTTPPwd
|
||||||
|
c.RequestHeaders.Set = m.Headers
|
||||||
|
c.RouteByHTTPUser = m.RouteByHTTPUser
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ProxyConfigurer = &HTTPSProxyConfig{}
|
||||||
|
|
||||||
|
type HTTPSProxyConfig struct {
|
||||||
|
ProxyBaseConfig
|
||||||
|
DomainConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPSProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||||
|
|
||||||
|
m.CustomDomains = c.CustomDomains
|
||||||
|
m.SubDomain = c.SubDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPSProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||||
|
|
||||||
|
c.CustomDomains = m.CustomDomains
|
||||||
|
c.SubDomain = m.SubDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
type TCPMultiplexerType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TCPMultiplexerHTTPConnect TCPMultiplexerType = "httpconnect"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ ProxyConfigurer = &TCPMuxProxyConfig{}
|
||||||
|
|
||||||
|
type TCPMuxProxyConfig struct {
|
||||||
|
ProxyBaseConfig
|
||||||
|
DomainConfig
|
||||||
|
|
||||||
|
HTTPUser string `json:"httpUser,omitempty"`
|
||||||
|
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||||
|
RouteByHTTPUser string `json:"routeByHTTPUser,omitempty"`
|
||||||
|
Multiplexer string `json:"multiplexer,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TCPMuxProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||||
|
|
||||||
|
m.CustomDomains = c.CustomDomains
|
||||||
|
m.SubDomain = c.SubDomain
|
||||||
|
m.Multiplexer = c.Multiplexer
|
||||||
|
m.HTTPUser = c.HTTPUser
|
||||||
|
m.HTTPPwd = c.HTTPPassword
|
||||||
|
m.RouteByHTTPUser = c.RouteByHTTPUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TCPMuxProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||||
|
|
||||||
|
c.CustomDomains = m.CustomDomains
|
||||||
|
c.SubDomain = m.SubDomain
|
||||||
|
c.Multiplexer = m.Multiplexer
|
||||||
|
c.HTTPUser = m.HTTPUser
|
||||||
|
c.HTTPPassword = m.HTTPPwd
|
||||||
|
c.RouteByHTTPUser = m.RouteByHTTPUser
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ProxyConfigurer = &STCPProxyConfig{}
|
||||||
|
|
||||||
|
type STCPProxyConfig struct {
|
||||||
|
ProxyBaseConfig
|
||||||
|
|
||||||
|
Secretkey string `json:"secretKey,omitempty"`
|
||||||
|
AllowUsers []string `json:"allowUsers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *STCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||||
|
|
||||||
|
m.Sk = c.Secretkey
|
||||||
|
m.AllowUsers = c.AllowUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *STCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||||
|
|
||||||
|
c.Secretkey = m.Sk
|
||||||
|
c.AllowUsers = m.AllowUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ProxyConfigurer = &XTCPProxyConfig{}
|
||||||
|
|
||||||
|
type XTCPProxyConfig struct {
|
||||||
|
ProxyBaseConfig
|
||||||
|
|
||||||
|
Secretkey string `json:"secretKey,omitempty"`
|
||||||
|
AllowUsers []string `json:"allowUsers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *XTCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||||
|
|
||||||
|
m.Sk = c.Secretkey
|
||||||
|
m.AllowUsers = c.AllowUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *XTCPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||||
|
|
||||||
|
c.Secretkey = m.Sk
|
||||||
|
c.AllowUsers = m.AllowUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ProxyConfigurer = &SUDPProxyConfig{}
|
||||||
|
|
||||||
|
type SUDPProxyConfig struct {
|
||||||
|
ProxyBaseConfig
|
||||||
|
|
||||||
|
Secretkey string `json:"secretKey,omitempty"`
|
||||||
|
AllowUsers []string `json:"allowUsers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SUDPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.MarshalToMsg(m)
|
||||||
|
|
||||||
|
m.Sk = c.Secretkey
|
||||||
|
m.AllowUsers = c.AllowUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SUDPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||||
|
c.ProxyBaseConfig.UnmarshalFromMsg(m)
|
||||||
|
|
||||||
|
c.Secretkey = m.Sk
|
||||||
|
c.AllowUsers = m.AllowUsers
|
||||||
|
}
|
||||||
49
pkg/config/v1/proxy_test.go
Normal file
49
pkg/config/v1/proxy_test.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2023 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnmarshalTypedProxyConfig(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
proxyConfigs := struct {
|
||||||
|
Proxies []TypedProxyConfig `json:"proxies,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
strs := `{
|
||||||
|
"proxies": [
|
||||||
|
{
|
||||||
|
"type": "tcp",
|
||||||
|
"localPort": 22,
|
||||||
|
"remotePort": 6000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"localPort": 80,
|
||||||
|
"customDomains": ["www.example.com"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
err := json.Unmarshal([]byte(strs), &proxyConfigs)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
require.IsType(&TCPProxyConfig{}, proxyConfigs.Proxies[0].ProxyConfigurer)
|
||||||
|
require.IsType(&HTTPProxyConfig{}, proxyConfigs.Proxies[1].ProxyConfigurer)
|
||||||
|
}
|
||||||
191
pkg/config/v1/server.go
Normal file
191
pkg/config/v1/server.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
// Copyright 2023 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 (
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config/types"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
APIMetadata
|
||||||
|
|
||||||
|
Auth AuthServerConfig `json:"auth,omitempty"`
|
||||||
|
// BindAddr specifies the address that the server binds to. By default,
|
||||||
|
// this value is "0.0.0.0".
|
||||||
|
BindAddr string `json:"bindAddr,omitempty"`
|
||||||
|
// BindPort specifies the port that the server listens on. By default, this
|
||||||
|
// value is 7000.
|
||||||
|
BindPort int `json:"bindPort,omitempty"`
|
||||||
|
// KCPBindPort specifies the KCP port that the server listens on. If this
|
||||||
|
// value is 0, the server will not listen for KCP connections.
|
||||||
|
KCPBindPort int `json:"kcpBindPort,omitempty"`
|
||||||
|
// QUICBindPort specifies the QUIC port that the server listens on.
|
||||||
|
// Set this value to 0 will disable this feature.
|
||||||
|
QUICBindPort int `json:"quicBindPort,omitempty"`
|
||||||
|
// ProxyBindAddr specifies the address that the proxy binds to. This value
|
||||||
|
// may be the same as BindAddr.
|
||||||
|
ProxyBindAddr string `json:"proxyBindAddr,omitempty"`
|
||||||
|
// VhostHTTPPort specifies the port that the server listens for HTTP Vhost
|
||||||
|
// requests. If this value is 0, the server will not listen for HTTP
|
||||||
|
// requests.
|
||||||
|
VhostHTTPPort int `json:"vhostHTTPPort,omitempty"`
|
||||||
|
// VhostHTTPTimeout specifies the response header timeout for the Vhost
|
||||||
|
// HTTP server, in seconds. By default, this value is 60.
|
||||||
|
VhostHTTPTimeout int64 `json:"vhostHTTPTimeout,omitempty"`
|
||||||
|
// VhostHTTPSPort specifies the port that the server listens for HTTPS
|
||||||
|
// Vhost requests. If this value is 0, the server will not listen for HTTPS
|
||||||
|
// requests.
|
||||||
|
VhostHTTPSPort int `json:"vhostHTTPSPort,omitempty"`
|
||||||
|
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
|
||||||
|
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||||
|
// requests on one single port. If it's not - it will listen on this value for
|
||||||
|
// HTTP CONNECT requests.
|
||||||
|
TCPMuxHTTPConnectPort int `json:"tcpmuxHTTPConnectPort,omitempty"`
|
||||||
|
// If TCPMuxPassthrough is true, frps won't do any update on traffic.
|
||||||
|
TCPMuxPassthrough bool `json:"tcpmuxPassthrough,omitempty"`
|
||||||
|
// SubDomainHost specifies the domain that will be attached to sub-domains
|
||||||
|
// requested by the client when using Vhost proxying. For example, if this
|
||||||
|
// value is set to "frps.com" and the client requested the subdomain
|
||||||
|
// "test", the resulting URL would be "test.frps.com".
|
||||||
|
SubDomainHost string `json:"subDomainHost,omitempty"`
|
||||||
|
// Custom404Page specifies a path to a custom 404 page to display. If this
|
||||||
|
// value is "", a default page will be displayed.
|
||||||
|
Custom404Page string `json:"custom404Page,omitempty"`
|
||||||
|
|
||||||
|
WebServer WebServerConfig `json:"webServer,omitempty"`
|
||||||
|
// EnablePrometheus will export prometheus metrics on webserver address
|
||||||
|
// in /metrics api.
|
||||||
|
EnablePrometheus bool `json:"enablePrometheus,omitempty"`
|
||||||
|
|
||||||
|
Log LogConfig `json:"log,omitempty"`
|
||||||
|
|
||||||
|
Transport ServerTransportConfig `json:"transport,omitempty"`
|
||||||
|
|
||||||
|
// DetailedErrorsToClient defines whether to send the specific error (with
|
||||||
|
// debug info) to frpc. By default, this value is true.
|
||||||
|
DetailedErrorsToClient *bool `json:"detailedErrorsToClient,omitempty"`
|
||||||
|
// MaxPortsPerClient specifies the maximum number of ports a single client
|
||||||
|
// may proxy to. If this value is 0, no limit will be applied.
|
||||||
|
MaxPortsPerClient int64 `json:"maxPortsPerClient,omitempty"`
|
||||||
|
// UserConnTimeout specifies the maximum time to wait for a work
|
||||||
|
// connection. By default, this value is 10.
|
||||||
|
UserConnTimeout int64 `json:"userConnTimeout,omitempty"`
|
||||||
|
// UDPPacketSize specifies the UDP packet size
|
||||||
|
// By default, this value is 1500
|
||||||
|
UDPPacketSize int64 `json:"udpPacketSize,omitempty"`
|
||||||
|
// NatHoleAnalysisDataReserveHours specifies the hours to reserve nat hole analysis data.
|
||||||
|
NatHoleAnalysisDataReserveHours int64 `json:"natholeAnalysisDataReserveHours,omitempty"`
|
||||||
|
|
||||||
|
AllowPorts []types.PortsRange `json:"allowPorts,omitempty"`
|
||||||
|
|
||||||
|
HTTPPlugins []HTTPPluginOptions `json:"httpPlugins,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConfig) Complete() {
|
||||||
|
c.Auth.Complete()
|
||||||
|
c.Log.Complete()
|
||||||
|
c.Transport.Complete()
|
||||||
|
c.WebServer.Complete()
|
||||||
|
|
||||||
|
c.BindAddr = util.EmptyOr(c.BindAddr, "0.0.0.0")
|
||||||
|
c.BindPort = util.EmptyOr(c.BindPort, 7000)
|
||||||
|
if c.ProxyBindAddr == "" {
|
||||||
|
c.ProxyBindAddr = c.BindAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.WebServer.Port > 0 {
|
||||||
|
c.WebServer.Addr = util.EmptyOr(c.WebServer.Addr, "0.0.0.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.VhostHTTPTimeout = util.EmptyOr(c.VhostHTTPTimeout, 60)
|
||||||
|
c.DetailedErrorsToClient = util.EmptyOr(c.DetailedErrorsToClient, lo.ToPtr(true))
|
||||||
|
c.UserConnTimeout = util.EmptyOr(c.UserConnTimeout, 10)
|
||||||
|
c.UDPPacketSize = util.EmptyOr(c.UDPPacketSize, 1500)
|
||||||
|
c.NatHoleAnalysisDataReserveHours = util.EmptyOr(c.NatHoleAnalysisDataReserveHours, 7*24)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthServerConfig struct {
|
||||||
|
Method AuthMethod `json:"method,omitempty"`
|
||||||
|
AdditionalScopes []AuthScope `json:"additionalScopes,omitempty"`
|
||||||
|
Token string `json:"token,omitempty"`
|
||||||
|
OIDC AuthOIDCServerConfig `json:"oidc,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthServerConfig) Complete() {
|
||||||
|
c.Method = util.EmptyOr(c.Method, "token")
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthOIDCServerConfig struct {
|
||||||
|
// Issuer specifies the issuer to verify OIDC tokens with. This issuer
|
||||||
|
// will be used to load public keys to verify signature and will be compared
|
||||||
|
// with the issuer claim in the OIDC token.
|
||||||
|
Issuer string `json:"issuer,omitempty"`
|
||||||
|
// Audience specifies the audience OIDC tokens should contain when validated.
|
||||||
|
// If this value is empty, audience ("client ID") verification will be skipped.
|
||||||
|
Audience string `json:"audience,omitempty"`
|
||||||
|
// SkipExpiryCheck specifies whether to skip checking if the OIDC token is
|
||||||
|
// expired.
|
||||||
|
SkipExpiryCheck bool `json:"skipExpiryCheck,omitempty"`
|
||||||
|
// SkipIssuerCheck specifies whether to skip checking if the OIDC token's
|
||||||
|
// issuer claim matches the issuer specified in OidcIssuer.
|
||||||
|
SkipIssuerCheck bool `json:"skipIssuerCheck,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerTransportConfig struct {
|
||||||
|
// TCPMux toggles TCP stream multiplexing. This allows multiple requests
|
||||||
|
// from a client to share a single TCP connection. By default, this value
|
||||||
|
// is true.
|
||||||
|
// $HideFromDoc
|
||||||
|
TCPMux *bool `json:"tcpMux,omitempty"`
|
||||||
|
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
|
||||||
|
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
|
||||||
|
TCPMuxKeepaliveInterval int64 `json:"tcpMuxKeepaliveInterval,omitempty"`
|
||||||
|
// TCPKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
|
// If negative, keep-alive probes are disabled.
|
||||||
|
TCPKeepAlive int64 `json:"tcpKeepalive,omitempty"`
|
||||||
|
// MaxPoolCount specifies the maximum pool size for each proxy. By default,
|
||||||
|
// this value is 5.
|
||||||
|
MaxPoolCount int64 `json:"maxPoolCount,omitempty"`
|
||||||
|
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||||
|
// before terminating the connection. It is not recommended to change this
|
||||||
|
// value. By default, this value is 90. Set negative value to disable it.
|
||||||
|
HeartbeatTimeout int64 `json:"heartbeatTimeout,omitempty"`
|
||||||
|
// QUIC options.
|
||||||
|
QUIC QUICOptions `json:"quic,omitempty"`
|
||||||
|
// TLS specifies TLS settings for the connection from the client.
|
||||||
|
TLS TLSServerConfig `json:"tls,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerTransportConfig) Complete() {
|
||||||
|
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
||||||
|
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
|
||||||
|
c.TCPKeepAlive = util.EmptyOr(c.TCPKeepAlive, 7200)
|
||||||
|
c.MaxPoolCount = util.EmptyOr(c.MaxPoolCount, 5)
|
||||||
|
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||||
|
c.QUIC.Complete()
|
||||||
|
if c.TLS.TrustedCaFile != "" {
|
||||||
|
c.TLS.Force = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TLSServerConfig struct {
|
||||||
|
// Force specifies whether to only accept TLS-encrypted connections.
|
||||||
|
Force bool `json:"force,omitempty"`
|
||||||
|
|
||||||
|
TLSConfig
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
// Copyright 2023 The frp Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,30 +12,21 @@
|
|||||||
// 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 consts
|
package v1
|
||||||
|
|
||||||
var (
|
import (
|
||||||
// proxy status
|
"testing"
|
||||||
Idle = "idle"
|
|
||||||
Working = "working"
|
|
||||||
Closed = "closed"
|
|
||||||
Online = "online"
|
|
||||||
Offline = "offline"
|
|
||||||
|
|
||||||
// proxy type
|
"github.com/samber/lo"
|
||||||
TCPProxy = "tcp"
|
"github.com/stretchr/testify/require"
|
||||||
UDPProxy = "udp"
|
|
||||||
TCPMuxProxy = "tcpmux"
|
|
||||||
HTTPProxy = "http"
|
|
||||||
HTTPSProxy = "https"
|
|
||||||
STCPProxy = "stcp"
|
|
||||||
XTCPProxy = "xtcp"
|
|
||||||
SUDPProxy = "sudp"
|
|
||||||
|
|
||||||
// authentication method
|
|
||||||
TokenAuthMethod = "token"
|
|
||||||
OidcAuthMethod = "oidc"
|
|
||||||
|
|
||||||
// TCP multiplexer
|
|
||||||
HTTPConnectTCPMultiplexer = "httpconnect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestServerConfigComplete(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
c := &ServerConfig{}
|
||||||
|
c.Complete()
|
||||||
|
|
||||||
|
require.EqualValues("token", c.Auth.Method)
|
||||||
|
require.Equal(true, lo.FromPtr(c.Transport.TCPMux))
|
||||||
|
require.Equal(true, lo.FromPtr(c.DetailedErrorsToClient))
|
||||||
|
}
|
||||||
105
pkg/config/v1/validation/client.go
Normal file
105
pkg/config/v1/validation/client.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2023 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 validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
||||||
|
var (
|
||||||
|
warnings Warning
|
||||||
|
errs error
|
||||||
|
)
|
||||||
|
if !lo.Contains(SupportedAuthMethods, c.Auth.Method) {
|
||||||
|
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
|
||||||
|
}
|
||||||
|
if !lo.Every(SupportedAuthAdditionalScopes, c.Auth.AdditionalScopes) {
|
||||||
|
errs = AppendError(errs, fmt.Errorf("invalid auth additional scopes, optional values are %v", SupportedAuthAdditionalScopes))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateLogConfig(&c.Log); err != nil {
|
||||||
|
errs = AppendError(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateWebServerConfig(&c.WebServer); err != nil {
|
||||||
|
errs = AppendError(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Transport.HeartbeatTimeout > 0 && c.Transport.HeartbeatInterval > 0 {
|
||||||
|
if c.Transport.HeartbeatTimeout < c.Transport.HeartbeatInterval {
|
||||||
|
errs = AppendError(errs, fmt.Errorf("invalid transport.heartbeatTimeout, heartbeat timeout should not less than heartbeat interval"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lo.FromPtr(c.Transport.TLS.Enable) {
|
||||||
|
checkTLSConfig := func(name string, value string) Warning {
|
||||||
|
if value != "" {
|
||||||
|
return fmt.Errorf("%s is invalid when transport.tls.enable is false", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings = AppendError(warnings, checkTLSConfig("transport.tls.certFile", c.Transport.TLS.CertFile))
|
||||||
|
warnings = AppendError(warnings, checkTLSConfig("transport.tls.keyFile", c.Transport.TLS.KeyFile))
|
||||||
|
warnings = AppendError(warnings, checkTLSConfig("transport.tls.trustedCaFile", c.Transport.TLS.TrustedCaFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lo.Contains(SupportedTransportProtocols, c.Transport.Protocol) {
|
||||||
|
errs = AppendError(errs, fmt.Errorf("invalid transport.protocol, optional values are %v", SupportedTransportProtocols))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range c.IncludeConfigFiles {
|
||||||
|
absDir, err := filepath.Abs(filepath.Dir(f))
|
||||||
|
if err != nil {
|
||||||
|
errs = AppendError(errs, fmt.Errorf("include: parse directory of %s failed: %v", f, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(absDir); os.IsNotExist(err) {
|
||||||
|
errs = AppendError(errs, fmt.Errorf("include: directory of %s not exist", f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return warnings, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateAllClientConfig(c *v1.ClientCommonConfig, pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) (Warning, error) {
|
||||||
|
var warnings Warning
|
||||||
|
if c != nil {
|
||||||
|
warning, err := ValidateClientCommonConfig(c)
|
||||||
|
warnings = AppendError(warnings, warning)
|
||||||
|
if err != nil {
|
||||||
|
return warnings, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range pxyCfgs {
|
||||||
|
if err := ValidateProxyConfigurerForClient(c); err != nil {
|
||||||
|
return warnings, fmt.Errorf("proxy %s: %v", c.GetBaseConfig().Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range visitorCfgs {
|
||||||
|
if err := ValidateVisitorConfigurer(c); err != nil {
|
||||||
|
return warnings, fmt.Errorf("visitor %s: %v", c.GetBaseConfig().Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return warnings, nil
|
||||||
|
}
|
||||||
51
pkg/config/v1/validation/common.go
Normal file
51
pkg/config/v1/validation/common.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2023 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 validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateWebServerConfig(c *v1.WebServerConfig) error {
|
||||||
|
if c.TLS != nil {
|
||||||
|
if c.TLS.CertFile == "" {
|
||||||
|
return fmt.Errorf("tls.certFile must be specified when tls is enabled")
|
||||||
|
}
|
||||||
|
if c.TLS.KeyFile == "" {
|
||||||
|
return fmt.Errorf("tls.keyFile must be specified when tls is enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidatePort(c.Port, "webServer.port")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatePort checks that the network port is in range
|
||||||
|
func ValidatePort(port int, fieldPath string) error {
|
||||||
|
if 0 <= port && port <= 65535 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s: port number %d must be in the range 0..65535", fieldPath, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateLogConfig(c *v1.LogConfig) error {
|
||||||
|
if !lo.Contains(SupportedLogLevels, c.Level) {
|
||||||
|
return fmt.Errorf("invalid log level, optional values are %v", SupportedLogLevels)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
72
pkg/config/v1/validation/plugin.go
Normal file
72
pkg/config/v1/validation/plugin.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2023 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 validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidateClientPluginOptions(c v1.ClientPluginOptions) error {
|
||||||
|
switch v := c.(type) {
|
||||||
|
case *v1.HTTP2HTTPSPluginOptions:
|
||||||
|
return validateHTTP2HTTPSPluginOptions(v)
|
||||||
|
case *v1.HTTPS2HTTPPluginOptions:
|
||||||
|
return validateHTTPS2HTTPPluginOptions(v)
|
||||||
|
case *v1.HTTPS2HTTPSPluginOptions:
|
||||||
|
return validateHTTPS2HTTPSPluginOptions(v)
|
||||||
|
case *v1.StaticFilePluginOptions:
|
||||||
|
return validateStaticFilePluginOptions(v)
|
||||||
|
case *v1.UnixDomainSocketPluginOptions:
|
||||||
|
return validateUnixDomainSocketPluginOptions(v)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHTTP2HTTPSPluginOptions(c *v1.HTTP2HTTPSPluginOptions) error {
|
||||||
|
if c.LocalAddr == "" {
|
||||||
|
return errors.New("localAddr is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHTTPS2HTTPPluginOptions(c *v1.HTTPS2HTTPPluginOptions) error {
|
||||||
|
if c.LocalAddr == "" {
|
||||||
|
return errors.New("localAddr is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHTTPS2HTTPSPluginOptions(c *v1.HTTPS2HTTPSPluginOptions) error {
|
||||||
|
if c.LocalAddr == "" {
|
||||||
|
return errors.New("localAddr is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateStaticFilePluginOptions(c *v1.StaticFilePluginOptions) error {
|
||||||
|
if c.LocalPath == "" {
|
||||||
|
return errors.New("localPath is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateUnixDomainSocketPluginOptions(c *v1.UnixDomainSocketPluginOptions) error {
|
||||||
|
if c.UnixPath == "" {
|
||||||
|
return errors.New("unixPath is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
233
pkg/config/v1/validation/proxy.go
Normal file
233
pkg/config/v1/validation/proxy.go
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
// Copyright 2023 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 validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateProxyBaseConfigForClient(c *v1.ProxyBaseConfig) error {
|
||||||
|
if c.Name == "" {
|
||||||
|
return errors.New("name should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lo.Contains([]string{"", "v1", "v2"}, c.Transport.ProxyProtocolVersion) {
|
||||||
|
return fmt.Errorf("not support proxy protocol version: %s", c.Transport.ProxyProtocolVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lo.Contains([]string{"client", "server"}, c.Transport.BandwidthLimitMode) {
|
||||||
|
return fmt.Errorf("bandwidth limit mode should be client or server")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Plugin.Type == "" {
|
||||||
|
if err := ValidatePort(c.LocalPort, "localPort"); err != nil {
|
||||||
|
return fmt.Errorf("localPort: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lo.Contains([]string{"", "tcp", "http"}, c.HealthCheck.Type) {
|
||||||
|
return fmt.Errorf("not support health check type: %s", c.HealthCheck.Type)
|
||||||
|
}
|
||||||
|
if c.HealthCheck.Type != "" {
|
||||||
|
if c.HealthCheck.Type == "http" &&
|
||||||
|
c.HealthCheck.Path == "" {
|
||||||
|
return fmt.Errorf("health check path should not be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Plugin.Type != "" {
|
||||||
|
if err := ValidateClientPluginOptions(c.Plugin.ClientPluginOptions); err != nil {
|
||||||
|
return fmt.Errorf("plugin %s: %v", c.Plugin.Type, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateProxyBaseConfigForServer(c *v1.ProxyBaseConfig, s *v1.ServerConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDomainConfigForClient(c *v1.DomainConfig) error {
|
||||||
|
if c.SubDomain == "" && len(c.CustomDomains) == 0 {
|
||||||
|
return errors.New("subdomain and custom domains should not be both empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDomainConfigForServer(c *v1.DomainConfig, s *v1.ServerConfig) error {
|
||||||
|
for _, domain := range c.CustomDomains {
|
||||||
|
if s.SubDomainHost != "" && len(strings.Split(s.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
|
||||||
|
if strings.Contains(domain, s.SubDomainHost) {
|
||||||
|
return fmt.Errorf("custom domain [%s] should not belong to subdomain host [%s]", domain, s.SubDomainHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SubDomain != "" {
|
||||||
|
if s.SubDomainHost == "" {
|
||||||
|
return errors.New("subdomain is not supported because this feature is not enabled in server")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(c.SubDomain, ".") || strings.Contains(c.SubDomain, "*") {
|
||||||
|
return errors.New("'.' and '*' are not supported in subdomain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateProxyConfigurerForClient(c v1.ProxyConfigurer) error {
|
||||||
|
base := c.GetBaseConfig()
|
||||||
|
if err := validateProxyBaseConfigForClient(base); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := c.(type) {
|
||||||
|
case *v1.TCPProxyConfig:
|
||||||
|
return validateTCPProxyConfigForClient(v)
|
||||||
|
case *v1.UDPProxyConfig:
|
||||||
|
return validateUDPProxyConfigForClient(v)
|
||||||
|
case *v1.TCPMuxProxyConfig:
|
||||||
|
return validateTCPMuxProxyConfigForClient(v)
|
||||||
|
case *v1.HTTPProxyConfig:
|
||||||
|
return validateHTTPProxyConfigForClient(v)
|
||||||
|
case *v1.HTTPSProxyConfig:
|
||||||
|
return validateHTTPSProxyConfigForClient(v)
|
||||||
|
case *v1.STCPProxyConfig:
|
||||||
|
return validateSTCPProxyConfigForClient(v)
|
||||||
|
case *v1.XTCPProxyConfig:
|
||||||
|
return validateXTCPProxyConfigForClient(v)
|
||||||
|
case *v1.SUDPProxyConfig:
|
||||||
|
return validateSUDPProxyConfigForClient(v)
|
||||||
|
}
|
||||||
|
return errors.New("unknown proxy config type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateTCPProxyConfigForClient(c *v1.TCPProxyConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateUDPProxyConfigForClient(c *v1.UDPProxyConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateTCPMuxProxyConfigForClient(c *v1.TCPMuxProxyConfig) error {
|
||||||
|
if err := validateDomainConfigForClient(&c.DomainConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lo.Contains([]string{string(v1.TCPMultiplexerHTTPConnect)}, c.Multiplexer) {
|
||||||
|
return fmt.Errorf("not support multiplexer: %s", c.Multiplexer)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHTTPProxyConfigForClient(c *v1.HTTPProxyConfig) error {
|
||||||
|
return validateDomainConfigForClient(&c.DomainConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHTTPSProxyConfigForClient(c *v1.HTTPSProxyConfig) error {
|
||||||
|
return validateDomainConfigForClient(&c.DomainConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSTCPProxyConfigForClient(c *v1.STCPProxyConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateXTCPProxyConfigForClient(c *v1.XTCPProxyConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSUDPProxyConfigForClient(c *v1.SUDPProxyConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateProxyConfigurerForServer(c v1.ProxyConfigurer, s *v1.ServerConfig) error {
|
||||||
|
base := c.GetBaseConfig()
|
||||||
|
if err := validateProxyBaseConfigForServer(base, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := c.(type) {
|
||||||
|
case *v1.TCPProxyConfig:
|
||||||
|
return validateTCPProxyConfigForServer(v, s)
|
||||||
|
case *v1.UDPProxyConfig:
|
||||||
|
return validateUDPProxyConfigForServer(v, s)
|
||||||
|
case *v1.TCPMuxProxyConfig:
|
||||||
|
return validateTCPMuxProxyConfigForServer(v, s)
|
||||||
|
case *v1.HTTPProxyConfig:
|
||||||
|
return validateHTTPProxyConfigForServer(v, s)
|
||||||
|
case *v1.HTTPSProxyConfig:
|
||||||
|
return validateHTTPSProxyConfigForServer(v, s)
|
||||||
|
case *v1.STCPProxyConfig:
|
||||||
|
return validateSTCPProxyConfigForServer(v, s)
|
||||||
|
case *v1.XTCPProxyConfig:
|
||||||
|
return validateXTCPProxyConfigForServer(v, s)
|
||||||
|
case *v1.SUDPProxyConfig:
|
||||||
|
return validateSUDPProxyConfigForServer(v, s)
|
||||||
|
default:
|
||||||
|
return errors.New("unknown proxy config type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateTCPProxyConfigForServer(c *v1.TCPProxyConfig, s *v1.ServerConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateUDPProxyConfigForServer(c *v1.UDPProxyConfig, s *v1.ServerConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateTCPMuxProxyConfigForServer(c *v1.TCPMuxProxyConfig, s *v1.ServerConfig) error {
|
||||||
|
if c.Multiplexer == string(v1.TCPMultiplexerHTTPConnect) &&
|
||||||
|
s.TCPMuxHTTPConnectPort == 0 {
|
||||||
|
return fmt.Errorf("tcpmux with multiplexer httpconnect not supported because this feature is not enabled in server")
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateDomainConfigForServer(&c.DomainConfig, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHTTPProxyConfigForServer(c *v1.HTTPProxyConfig, s *v1.ServerConfig) error {
|
||||||
|
if s.VhostHTTPPort == 0 {
|
||||||
|
return fmt.Errorf("type [http] not supported when vhost http port is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateDomainConfigForServer(&c.DomainConfig, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateHTTPSProxyConfigForServer(c *v1.HTTPSProxyConfig, s *v1.ServerConfig) error {
|
||||||
|
if s.VhostHTTPSPort == 0 {
|
||||||
|
return fmt.Errorf("type [https] not supported when vhost https port is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateDomainConfigForServer(&c.DomainConfig, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSTCPProxyConfigForServer(c *v1.STCPProxyConfig, s *v1.ServerConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateXTCPProxyConfigForServer(c *v1.XTCPProxyConfig, s *v1.ServerConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSUDPProxyConfigForServer(c *v1.SUDPProxyConfig, s *v1.ServerConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user