mirror of
https://github.com/fatedier/frp.git
synced 2025-07-27 07:35:07 +00:00
Compare commits
50 Commits
078ee4dbab
...
v0.52.0
Author | SHA1 | Date | |
---|---|---|---|
|
2d3af8a108 | ||
|
466d69eae0 | ||
|
7c8cbeb250 | ||
|
4fd6301577 | ||
|
53626b370c | ||
|
4fd800bc48 | ||
|
0d6d968fe8 | ||
|
8fb99ef7a9 | ||
|
88e74ff24d | ||
|
534dc99d55 | ||
|
595aba5a9b | ||
|
a4189ba474 | ||
|
9ec84f8143 | ||
|
8ab474cc97 | ||
|
a301046f3d | ||
|
8888610d83 | ||
|
fe5fb0326b | ||
|
eb1e19a821 | ||
|
10f2620131 | ||
|
ce677820c6 | ||
|
88fcc079e8 | ||
|
2dab5d0bca | ||
|
143750901e | ||
|
997d406ec2 | ||
|
cfd1a3128a | ||
|
57577ea044 | ||
|
c5c79e4148 | ||
|
55da58eca4 | ||
|
76a1efccd9 | ||
|
980f084ad1 | ||
|
3bf1eb8565 | ||
|
b2ae433e18 | ||
|
aa0a41ee4e | ||
|
1ea1530b36 | ||
|
e0c45a1aca | ||
|
813c45f5c2 | ||
|
aa74dc4646 | ||
|
2406ecdfea | ||
|
8668fef136 | ||
|
ea62bc5a34 | ||
|
23bb76397a | ||
|
487c8d7c29 | ||
|
f480160e2d | ||
|
30c246c488 | ||
|
75f3bce04d | ||
|
adc3adc13b | ||
|
e62d9a5242 | ||
|
134a46c00b | ||
|
ae08811636 | ||
|
6451583e60 |
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@@ -1,3 +1,6 @@
|
||||
### WHY
|
||||
### Summary
|
||||
|
||||
copilot:summary
|
||||
|
||||
### WHY
|
||||
<!-- author to complete -->
|
||||
|
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: v1.55
|
||||
version: v1.53
|
||||
|
||||
# Optional: golangci-lint command line arguments.
|
||||
# args: --issues-exit-code=0
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -33,9 +33,6 @@ lastversion/
|
||||
dist/
|
||||
.idea/
|
||||
.vscode/
|
||||
.autogen_ssh_key
|
||||
client.crt
|
||||
client.key
|
||||
|
||||
# Cache
|
||||
*.swp
|
||||
|
@@ -1,5 +1,5 @@
|
||||
service:
|
||||
golangci-lint-version: 1.55.x # use the fixed version to not introduce new linters unexpectedly
|
||||
golangci-lint-version: 1.51.x # use the fixed version to not introduce new linters unexpectedly
|
||||
|
||||
run:
|
||||
concurrency: 4
|
||||
@@ -132,9 +132,6 @@ issues:
|
||||
- linters:
|
||||
- revive
|
||||
text: "unused-parameter"
|
||||
- linters:
|
||||
- unparam
|
||||
text: "is always false"
|
||||
|
||||
# Independently from option `exclude` we use default exclude patterns,
|
||||
# it can be disabled by this option. To list all
|
||||
|
4
Makefile
4
Makefile
@@ -26,10 +26,10 @@ vet:
|
||||
go vet ./...
|
||||
|
||||
frps:
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -tags frps -o bin/frps ./cmd/frps
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
|
||||
|
||||
frpc:
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -tags frpc -o bin/frpc ./cmd/frpc
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frpc ./cmd/frpc
|
||||
|
||||
test: gotest
|
||||
|
||||
|
84
README.md
84
README.md
@@ -11,10 +11,6 @@
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
<a> </a>
|
||||
<a href="https://www.nango.dev?utm_source=github&utm_medium=oss-banner&utm_campaign=fatedier-frp" target="_blank">
|
||||
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_nango.png">
|
||||
</a>
|
||||
</p>
|
||||
<!--gold sponsors end-->
|
||||
|
||||
@@ -46,7 +42,7 @@ frp also offers a P2P connect mode.
|
||||
* [Using Environment Variables](#using-environment-variables)
|
||||
* [Split Configures Into Different Files](#split-configures-into-different-files)
|
||||
* [Server Dashboard](#server-dashboard)
|
||||
* [Client Admin UI](#client-admin-ui)
|
||||
* [Admin UI](#admin-ui)
|
||||
* [Monitor](#monitor)
|
||||
* [Prometheus](#prometheus)
|
||||
* [Authenticating the Client](#authenticating-the-client)
|
||||
@@ -75,10 +71,9 @@ frp also offers a P2P connect mode.
|
||||
* [Custom Subdomain Names](#custom-subdomain-names)
|
||||
* [URL Routing](#url-routing)
|
||||
* [TCP Port Multiplexing](#tcp-port-multiplexing)
|
||||
* [Connecting to frps via PROXY](#connecting-to-frps-via-proxy)
|
||||
* [Connecting to frps via HTTP PROXY](#connecting-to-frps-via-http-proxy)
|
||||
* [Client Plugins](#client-plugins)
|
||||
* [Server Manage Plugins](#server-manage-plugins)
|
||||
* [SSH Tunnel Gateway](#ssh-tunnel-gateway)
|
||||
* [Contributing](#contributing)
|
||||
* [Donation](#donation)
|
||||
* [GitHub Sponsors](#github-sponsors)
|
||||
@@ -96,6 +91,8 @@ We will transition from version 0 to version 1 at the appropriate time and will
|
||||
|
||||
### About V2
|
||||
|
||||
The overall situation is currently unfavorable, and there is significant pressure in both personal and professional aspects.
|
||||
|
||||
The complexity and difficulty of the v2 version are much higher than anticipated. I can only work on its development during fragmented time periods, and the constant interruptions disrupt productivity significantly. Given this situation, we will continue to optimize and iterate on the current version until we have more free time to proceed with the major version overhaul.
|
||||
|
||||
The concept behind v2 is based on my years of experience and reflection in the cloud-native domain, particularly in K8s and ServiceMesh. Its core is a modernized four-layer and seven-layer proxy, similar to envoy. This proxy itself is highly scalable, not only capable of implementing the functionality of intranet penetration but also applicable to various other domains. Building upon this highly scalable core, we aim to implement all the capabilities of frp v1 while also addressing the functionalities that were previously unachievable or difficult to implement in an elegant manner. Furthermore, we will maintain efficient development and iteration capabilities.
|
||||
@@ -221,7 +218,7 @@ Unfortunately, we cannot resolve a domain name to a local IP. However, we can us
|
||||
vhostHTTPPort = 8080
|
||||
```
|
||||
|
||||
If you want to configure an https proxy, you need to set up the `vhostHTTPSPort`.
|
||||
If you want to configure an https proxy, you need to set up the `vhost_https_port`.
|
||||
|
||||
2. Start `frps`:
|
||||
|
||||
@@ -340,7 +337,7 @@ Configure `frps` as described above, then:
|
||||
|
||||
### Enable HTTPS for a local HTTP(S) service
|
||||
|
||||
You may substitute `https2https` for the plugin, and point the `localAddr` to a HTTPS endpoint.
|
||||
You may substitute `https2https` for the plugin, and point the `plugin_local_addr` to a HTTPS endpoint.
|
||||
|
||||
1. Start `frpc` with the following configuration:
|
||||
|
||||
@@ -372,7 +369,7 @@ To mitigate risks associated with exposing certain services directly to the publ
|
||||
|
||||
Configure `frps` same as above.
|
||||
|
||||
1. Start `frpc` on machine B with the following config. This example is for exposing the SSH service (port 22), and note the `secretKey` field for the preshared key, and that the `remotePort` field is removed here:
|
||||
1. Start `frpc` on machine B with the following config. This example is for exposing the SSH service (port 22), and note the `sk` field for the preshared key, and that the `remote_port` field is removed here:
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
@@ -387,7 +384,7 @@ Configure `frps` same as above.
|
||||
localPort = 22
|
||||
```
|
||||
|
||||
2. Start another `frpc` (typically on another machine C) with the following config to access the SSH service with a security key (`secretKey` field):
|
||||
2. Start another `frpc` (typically on another machine C) with the following config to access the SSH service with a security key (`sk` field):
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
@@ -464,11 +461,9 @@ Read the full example configuration files to find out even more features not des
|
||||
|
||||
Examples use TOML format, but you can still use YAML or JSON.
|
||||
|
||||
These configuration files is for reference only. Please do not use this configuration directly to run the program as it may have various issues.
|
||||
[Full configuration file for frps (Server)](./conf/frps.toml)
|
||||
|
||||
[Full configuration file for frps (Server)](./conf/frps_full_example.toml)
|
||||
|
||||
[Full configuration file for frpc (Client)](./conf/frpc_full_example.toml)
|
||||
[Full configuration file for frpc (Client)](./conf/frpc.toml)
|
||||
|
||||
### Using Environment Variables
|
||||
|
||||
@@ -510,7 +505,6 @@ includes = ["./confd/*.toml"]
|
||||
|
||||
```toml
|
||||
# ./confd/test.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "ssh"
|
||||
type = "tcp"
|
||||
@@ -532,7 +526,7 @@ webServer.user = "admin"
|
||||
webServer.password = "admin"
|
||||
```
|
||||
|
||||
Then visit `http://[serverAddr]:7500` to see the dashboard, with username and password both being `admin`.
|
||||
Then visit `http://[server_addr]:7500` to see the dashboard, with username and password both being `admin`.
|
||||
|
||||
Additionally, you can use HTTPS port by using your domains wildcard or normal SSL certificate:
|
||||
|
||||
@@ -545,7 +539,7 @@ webServer.tls.certFile = "server.crt"
|
||||
webServer.tls.keyFile = "server.key"
|
||||
```
|
||||
|
||||
Then visit `https://[serverAddr]:7500` to see the dashboard in secure HTTPS connection, with username and password both being `admin`.
|
||||
Then visit `https://[server_addr]:7500` to see the dashboard in secure HTTPS connection, with username and password both being `admin`.
|
||||
|
||||

|
||||
|
||||
@@ -622,7 +616,6 @@ The features are off by default. You can turn on encryption and/or compression:
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "ssh"
|
||||
type = "tcp"
|
||||
@@ -768,8 +761,6 @@ allowPorts = [
|
||||
|
||||
`vhostHTTPPort` and `vhostHTTPSPort` in frps can use same port with `bindPort`. frps will detect the connection's protocol and handle it correspondingly.
|
||||
|
||||
What you need to pay attention to is that if you want to configure `vhostHTTPSPort` and `bindPort` to the same port, you need to first set `transport.tls.disableCustomTLSFirstByte` to false.
|
||||
|
||||
We would like to try to allow multiple proxies bind a same remote port with different protocols in the future.
|
||||
|
||||
### Bandwidth Limit
|
||||
@@ -778,7 +769,6 @@ We would like to try to allow multiple proxies bind a same remote port with diff
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "ssh"
|
||||
type = "tcp"
|
||||
@@ -844,7 +834,7 @@ Using QUIC in frp:
|
||||
quicBindPort = 7000
|
||||
```
|
||||
|
||||
The `quicBindPort` number can be the same number as `bindPort`, since `bindPort` field specifies a TCP port.
|
||||
The `quicBindPort` number can be the same number as `bind_port`, since `bind_port` field specifies a TCP port.
|
||||
|
||||
2. Configure `frpc.toml` to use QUIC to connect to frps:
|
||||
|
||||
@@ -884,7 +874,6 @@ This feature is only available for types `tcp`, `http`, `tcpmux` now.
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "test1"
|
||||
type = "tcp"
|
||||
@@ -920,7 +909,6 @@ With health check type **tcp**, the service port will be pinged (TCPing):
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "test1"
|
||||
type = "tcp"
|
||||
@@ -940,7 +928,6 @@ With health check type **http**, an HTTP request will be sent to the service and
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "web"
|
||||
type = "http"
|
||||
@@ -965,7 +952,6 @@ However, speaking of web servers and HTTP requests, your web server might rely o
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "web"
|
||||
type = "http"
|
||||
@@ -982,7 +968,6 @@ Similar to `Host`, You can override other HTTP request headers with proxy type `
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "web"
|
||||
type = "http"
|
||||
@@ -1010,7 +995,6 @@ Here is an example for https service:
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "web"
|
||||
type = "https"
|
||||
@@ -1033,7 +1017,6 @@ It can only be enabled when proxy type is http.
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "web"
|
||||
type = "http"
|
||||
@@ -1058,7 +1041,6 @@ Resolve `*.frps.com` to the frps server's IP. This is usually called a Wildcard
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "web"
|
||||
type = "http"
|
||||
@@ -1078,7 +1060,6 @@ frp supports forwarding HTTP requests to different backend web services by url r
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "web01"
|
||||
type = "http"
|
||||
@@ -1164,7 +1145,6 @@ Using plugin **http_proxy**:
|
||||
|
||||
```toml
|
||||
# frpc.toml
|
||||
|
||||
[[proxies]]
|
||||
name = "http_proxy"
|
||||
type = "tcp"
|
||||
@@ -1183,44 +1163,6 @@ Read the [document](/doc/server_plugin.md).
|
||||
|
||||
Find more plugins in [gofrp/plugin](https://github.com/gofrp/plugin).
|
||||
|
||||
### SSH Tunnel Gateway
|
||||
|
||||
*added in v0.53.0*
|
||||
|
||||
frp supports listening to an SSH port on the frps side and achieves TCP protocol proxying through the SSH -R protocol, without relying on frpc.
|
||||
|
||||
```toml
|
||||
# frps.toml
|
||||
sshTunnelGateway.bindPort = 2200
|
||||
```
|
||||
|
||||
When running `./frps -c frps.toml`, a private key file named `.autogen_ssh_key` will be automatically created in the current working directory. This generated private key file will be used by the SSH server in frps.
|
||||
|
||||
Executing the command
|
||||
|
||||
```bash
|
||||
ssh -R :80:127.0.0.1:8080 v0@{frp address} -p 2200 tcp --proxy_name "test-tcp" --remote_port 9090
|
||||
```
|
||||
|
||||
sets up a proxy on frps that forwards the local 8080 service to the port 9090.
|
||||
|
||||
```bash
|
||||
frp (via SSH) (Ctrl+C to quit)
|
||||
|
||||
User:
|
||||
ProxyName: test-tcp
|
||||
Type: tcp
|
||||
RemoteAddress: :9090
|
||||
```
|
||||
|
||||
This is equivalent to:
|
||||
|
||||
```bash
|
||||
frpc tcp --proxy_name "test-tcp" --local_ip 127.0.0.1 --local_port 8080 --remote_port 9090
|
||||
```
|
||||
|
||||
Please refer to this [document](/doc/ssh_tunnel_gateway.md) for more information.
|
||||
|
||||
## Contributing
|
||||
|
||||
Interested in getting involved? We would like to help you!
|
||||
|
12
README_zh.md
12
README_zh.md
@@ -13,10 +13,6 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
<a> </a>
|
||||
<a href="https://www.nango.dev?utm_source=github&utm_medium=oss-banner&utm_campaign=fatedier-frp" target="_blank">
|
||||
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_nango.png">
|
||||
</a>
|
||||
</p>
|
||||
<!--gold sponsors end-->
|
||||
|
||||
@@ -45,7 +41,9 @@ master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试
|
||||
|
||||
### 关于 v2 的一些说明
|
||||
|
||||
v2 版本的复杂度和难度比我们预期的要高得多。我只能利用零散的时间进行开发,而且由于上下文经常被打断,效率极低。由于这种情况可能会持续一段时间,我们仍然会在当前版本上进行一些优化和迭代,直到我们有更多空闲时间来推进大版本的重构,或者也有可能放弃一次性的重构,而是采用渐进的方式在当前版本上逐步做一些可能会导致不兼容的修改。
|
||||
当前整体形势不佳,面临的生活工作压力很大。
|
||||
|
||||
v2 版本的复杂度和难度比我们预期的要高得多。我只能利用零散的时间进行开发,而且由于上下文经常被打断,效率极低。由于这种情况可能会持续一段时间,我们仍然会在当前版本上进行一些优化和迭代,直到我们有更多空闲时间来推进大版本的重构。
|
||||
|
||||
v2 的构想是基于我多年在云原生领域,特别是在 K8s 和 ServiceMesh 方面的工作经验和思考。它的核心是一个现代化的四层和七层代理,类似于 envoy。这个代理本身高度可扩展,不仅可以用于实现内网穿透的功能,还可以应用于更多领域。在这个高度可扩展的内核基础上,我们将实现 frp v1 中的所有功能,并且能够以一种更加优雅的方式实现原先架构中无法实现或不易实现的功能。同时,我们将保持高效的开发和迭代能力。
|
||||
|
||||
@@ -57,7 +55,7 @@ v2 的构想是基于我多年在云原生领域,特别是在 K8s 和 ServiceM
|
||||
|
||||
## 文档
|
||||
|
||||
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org)。
|
||||
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。
|
||||
|
||||
## 为 frp 做贡献
|
||||
|
||||
@@ -88,7 +86,7 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
||||
|
||||
### 知识星球
|
||||
|
||||
如果您想了解更多 frp 相关技术以及更新详解,或者寻求任何 frp 使用方面的帮助,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||
如果您想了解更多 frp 相关技术以及更新详解,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||
|
||||

|
||||
|
||||
|
10
Release.md
10
Release.md
@@ -1,3 +1,9 @@
|
||||
### Deprecation Notices
|
||||
### Features
|
||||
|
||||
* Using an underscore in a flag name is deprecated and has been replaced by a hyphen. The underscore format will remain compatible for some time, until it is completely removed in a future version. For example, `--remote_port` is replaced with `--remote-port`.
|
||||
* Configuration: We now support TOML, YAML, and JSON for configuration. Please note that INI is deprecated and will be removed in future releases. New features will only be available in TOML, YAML, or JSON. Users wanting these new features should switch their configuration format accordingly. #2521
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Change the way to start the visitor through the command line from `frpc stcp --role=visitor xxx` to `frpc stcp visitor xxx`.
|
||||
* Modified the semantics of the `server_addr` in the command line, no longer including the port. Added the `server_port` parameter to configure the port.
|
||||
* No longer support range ports mapping in TOML/YAML/JSON.
|
||||
|
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>frps dashboard</title>
|
||||
<script type="module" crossorigin src="./index-c322b7dd.js"></script>
|
||||
<script type="module" crossorigin src="./index-ea3edf22.js"></script>
|
||||
<link rel="stylesheet" href="./index-1e0c7400.css">
|
||||
</head>
|
||||
|
||||
|
85
client/admin.go
Normal file
85
client/admin.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2017 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 client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerReadTimeout = 60 * time.Second
|
||||
httpServerWriteTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
func (svr *Service) RunAdminServer(address string) (err error) {
|
||||
// url router
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/healthz", svr.healthz)
|
||||
|
||||
// debug
|
||||
if svr.cfg.WebServer.PprofEnable {
|
||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||
}
|
||||
|
||||
subRouter := router.NewRoute().Subrouter()
|
||||
user, passwd := svr.cfg.WebServer.User, svr.cfg.WebServer.Password
|
||||
subRouter.Use(utilnet.NewHTTPAuthMiddleware(user, passwd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||
|
||||
// api, see admin_api.go
|
||||
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/config", svr.apiGetConfig).Methods("GET")
|
||||
subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||
|
||||
// view
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Addr: address,
|
||||
Handler: router,
|
||||
ReadTimeout: httpServerReadTimeout,
|
||||
WriteTimeout: httpServerWriteTimeout,
|
||||
}
|
||||
if address == "" {
|
||||
address = ":http"
|
||||
}
|
||||
ln, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
_ = server.Serve(ln)
|
||||
}()
|
||||
return
|
||||
}
|
@@ -31,9 +31,7 @@ import (
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
@@ -41,42 +39,14 @@ type GeneralResponse struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
|
||||
helper.Router.HandleFunc("/healthz", svr.healthz)
|
||||
subRouter := helper.Router.NewRoute().Subrouter()
|
||||
|
||||
subRouter.Use(helper.AuthMiddleware.Middleware)
|
||||
|
||||
// api, see admin_api.go
|
||||
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/config", svr.apiGetConfig).Methods("GET")
|
||||
subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||
|
||||
// view
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(
|
||||
netpkg.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(helper.AssetsFS))),
|
||||
).Methods("GET")
|
||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
}
|
||||
|
||||
// /healthz
|
||||
func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
||||
// 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}
|
||||
strictConfigMode := false
|
||||
strictStr := r.URL.Query().Get("strictConfig")
|
||||
if strictStr != "" {
|
||||
strictConfigMode, _ = strconv.ParseBool(strictStr)
|
||||
}
|
||||
|
||||
log.Info("api request [/api/reload]")
|
||||
defer func() {
|
||||
@@ -87,21 +57,21 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}()
|
||||
|
||||
cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(svr.configFilePath, strictConfigMode)
|
||||
cliCfg, pxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
if _, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs); err != nil {
|
||||
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.UpdateAllConfigurer(proxyCfgs, visitorCfgs); err != nil {
|
||||
if err := svr.ReloadConf(pxyCfgs, visitorCfgs); err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = err.Error()
|
||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||
@@ -174,16 +144,9 @@ func (svr *Service) apiStatus(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write(buf)
|
||||
}()
|
||||
|
||||
svr.ctlMu.RLock()
|
||||
ctl := svr.ctl
|
||||
svr.ctlMu.RUnlock()
|
||||
if ctl == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ps := ctl.pm.GetAllProxyStatus()
|
||||
ps := svr.ctl.pm.GetAllProxyStatus()
|
||||
for _, status := range ps {
|
||||
res[status.Type] = append(res[status.Type], NewProxyStatusResp(status, svr.common.ServerAddr))
|
||||
res[status.Type] = append(res[status.Type], NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
}
|
||||
|
||||
for _, arrs := range res {
|
||||
@@ -209,14 +172,14 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
}()
|
||||
|
||||
if svr.configFilePath == "" {
|
||||
if svr.cfgFile == "" {
|
||||
res.Code = 400
|
||||
res.Msg = "frpc has no config file path"
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(svr.configFilePath)
|
||||
content, err := os.ReadFile(svr.cfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
@@ -255,7 +218,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.WriteFile(svr.configFilePath, body, 0o644); err != nil {
|
||||
if err := os.WriteFile(svr.cfgFile, body, 0o644); err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||
log.Warn("%s", res.Msg)
|
||||
|
@@ -1,227 +0,0 @@
|
||||
// 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 client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
quic "github.com/quic-go/quic-go"
|
||||
"github.com/samber/lo"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
// Connector is an interface for establishing connections to the server.
|
||||
type Connector interface {
|
||||
Open() error
|
||||
Connect() (net.Conn, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// defaultConnectorImpl is the default implementation of Connector for normal frpc.
|
||||
type defaultConnectorImpl struct {
|
||||
ctx context.Context
|
||||
cfg *v1.ClientCommonConfig
|
||||
|
||||
muxSession *fmux.Session
|
||||
quicConn quic.Connection
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
func NewConnector(ctx context.Context, cfg *v1.ClientCommonConfig) Connector {
|
||||
return &defaultConnectorImpl{
|
||||
ctx: ctx,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Open opens an underlying connection to the server.
|
||||
// The underlying connection is either a TCP connection or a QUIC connection.
|
||||
// After the underlying connection is established, you can call Connect() to get a stream.
|
||||
// If TCPMux isn't enabled, the underlying connection is nil, you will get a new real TCP connection every time you call Connect().
|
||||
func (c *defaultConnectorImpl) Open() error {
|
||||
xl := xlog.FromContextSafe(c.ctx)
|
||||
|
||||
// special for quic
|
||||
if strings.EqualFold(c.cfg.Transport.Protocol, "quic") {
|
||||
var tlsConfig *tls.Config
|
||||
var err error
|
||||
sn := c.cfg.Transport.TLS.ServerName
|
||||
if sn == "" {
|
||||
sn = c.cfg.ServerAddr
|
||||
}
|
||||
if lo.FromPtr(c.cfg.Transport.TLS.Enable) {
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
c.cfg.Transport.TLS.CertFile,
|
||||
c.cfg.Transport.TLS.KeyFile,
|
||||
c.cfg.Transport.TLS.TrustedCaFile,
|
||||
sn)
|
||||
} else {
|
||||
tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
|
||||
}
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
||||
return err
|
||||
}
|
||||
tlsConfig.NextProtos = []string{"frp"}
|
||||
|
||||
conn, err := quic.DialAddr(
|
||||
c.ctx,
|
||||
net.JoinHostPort(c.cfg.ServerAddr, strconv.Itoa(c.cfg.ServerPort)),
|
||||
tlsConfig, &quic.Config{
|
||||
MaxIdleTimeout: time.Duration(c.cfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||
MaxIncomingStreams: int64(c.cfg.Transport.QUIC.MaxIncomingStreams),
|
||||
KeepAlivePeriod: time.Duration(c.cfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.quicConn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
if !lo.FromPtr(c.cfg.Transport.TCPMux) {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := c.realConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = time.Duration(c.cfg.Transport.TCPMuxKeepaliveInterval) * time.Second
|
||||
fmuxCfg.LogOutput = io.Discard
|
||||
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
||||
session, err := fmux.Client(conn, fmuxCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.muxSession = session
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect returns a stream from the underlying connection, or a new TCP connection if TCPMux isn't enabled.
|
||||
func (c *defaultConnectorImpl) Connect() (net.Conn, error) {
|
||||
if c.quicConn != nil {
|
||||
stream, err := c.quicConn.OpenStreamSync(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netpkg.QuicStreamToNetConn(stream, c.quicConn), nil
|
||||
} else if c.muxSession != nil {
|
||||
stream, err := c.muxSession.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
return c.realConnect()
|
||||
}
|
||||
|
||||
func (c *defaultConnectorImpl) realConnect() (net.Conn, error) {
|
||||
xl := xlog.FromContextSafe(c.ctx)
|
||||
var tlsConfig *tls.Config
|
||||
var err error
|
||||
tlsEnable := lo.FromPtr(c.cfg.Transport.TLS.Enable)
|
||||
if c.cfg.Transport.Protocol == "wss" {
|
||||
tlsEnable = true
|
||||
}
|
||||
if tlsEnable {
|
||||
sn := c.cfg.Transport.TLS.ServerName
|
||||
if sn == "" {
|
||||
sn = c.cfg.ServerAddr
|
||||
}
|
||||
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
c.cfg.Transport.TLS.CertFile,
|
||||
c.cfg.Transport.TLS.KeyFile,
|
||||
c.cfg.Transport.TLS.TrustedCaFile,
|
||||
sn)
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
proxyType, addr, auth, err := libdial.ParseProxyURL(c.cfg.Transport.ProxyURL)
|
||||
if err != nil {
|
||||
xl.Error("fail to parse proxy url")
|
||||
return nil, err
|
||||
}
|
||||
dialOptions := []libdial.DialOption{}
|
||||
protocol := c.cfg.Transport.Protocol
|
||||
switch protocol {
|
||||
case "websocket":
|
||||
protocol = "tcp"
|
||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: netpkg.DialHookWebsocket(protocol, "")}))
|
||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{
|
||||
Hook: netpkg.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(c.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: netpkg.DialHookWebsocket(protocol, tlsConfig.ServerName), Priority: 110}))
|
||||
default:
|
||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{
|
||||
Hook: netpkg.DialHookCustomTLSHeadByte(tlsConfig != nil, lo.FromPtr(c.cfg.Transport.TLS.DisableCustomTLSFirstByte)),
|
||||
}))
|
||||
dialOptions = append(dialOptions, libdial.WithTLSConfig(tlsConfig))
|
||||
}
|
||||
|
||||
if c.cfg.Transport.ConnectServerLocalIP != "" {
|
||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(c.cfg.Transport.ConnectServerLocalIP))
|
||||
}
|
||||
dialOptions = append(dialOptions,
|
||||
libdial.WithProtocol(protocol),
|
||||
libdial.WithTimeout(time.Duration(c.cfg.Transport.DialServerTimeout)*time.Second),
|
||||
libdial.WithKeepAlive(time.Duration(c.cfg.Transport.DialServerKeepAlive)*time.Second),
|
||||
libdial.WithProxy(proxyType, addr),
|
||||
libdial.WithProxyAuth(auth),
|
||||
)
|
||||
conn, err := libdial.DialContext(
|
||||
c.ctx,
|
||||
net.JoinHostPort(c.cfg.ServerAddr, strconv.Itoa(c.cfg.ServerPort)),
|
||||
dialOptions...,
|
||||
)
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (c *defaultConnectorImpl) Close() error {
|
||||
c.closeOnce.Do(func() {
|
||||
if c.quicConn != nil {
|
||||
_ = c.quicConn.CloseWithError(0, "")
|
||||
}
|
||||
if c.muxSession != nil {
|
||||
_ = c.muxSession.Close()
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
@@ -16,10 +16,13 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/control/shutdown"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
@@ -28,99 +31,101 @@ import (
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/wait"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
type SessionContext struct {
|
||||
// The client common configuration.
|
||||
Common *v1.ClientCommonConfig
|
||||
|
||||
// Unique ID obtained from frps.
|
||||
// It should be attached to the login message when reconnecting.
|
||||
RunID string
|
||||
// Underlying control connection. Once conn is closed, the msgDispatcher and the entire Control will exit.
|
||||
Conn net.Conn
|
||||
// Indicates whether the connection is encrypted.
|
||||
ConnEncrypted bool
|
||||
// Sets authentication based on selected method
|
||||
AuthSetter auth.Setter
|
||||
// Connector is used to create new connections, which could be real TCP connections or virtual streams.
|
||||
Connector Connector
|
||||
}
|
||||
|
||||
type Control struct {
|
||||
// service context
|
||||
ctx context.Context
|
||||
xl *xlog.Logger
|
||||
|
||||
// session context
|
||||
sessionCtx *SessionContext
|
||||
// Unique ID obtained from frps.
|
||||
// It should be attached to the login message when reconnecting.
|
||||
runID string
|
||||
|
||||
// manage all proxies
|
||||
pxyCfgs []v1.ProxyConfigurer
|
||||
pm *proxy.Manager
|
||||
|
||||
// manage all visitors
|
||||
vm *visitor.Manager
|
||||
|
||||
doneCh chan struct{}
|
||||
// control connection
|
||||
conn net.Conn
|
||||
|
||||
// of time.Time, last time got the Pong message
|
||||
lastPong atomic.Value
|
||||
cm *ConnectionManager
|
||||
|
||||
// put a message in this channel to send it over control connection to server
|
||||
sendCh chan (msg.Message)
|
||||
|
||||
// read from this channel to get the next message sent by server
|
||||
readCh chan (msg.Message)
|
||||
|
||||
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
|
||||
closedCh chan struct{}
|
||||
|
||||
closedDoneCh chan struct{}
|
||||
|
||||
// last time got the Pong message
|
||||
lastPong time.Time
|
||||
|
||||
// The client configuration
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
|
||||
readerShutdown *shutdown.Shutdown
|
||||
writerShutdown *shutdown.Shutdown
|
||||
msgHandlerShutdown *shutdown.Shutdown
|
||||
|
||||
// sets authentication based on selected method
|
||||
authSetter auth.Setter
|
||||
|
||||
// The role of msgTransporter is similar to HTTP2.
|
||||
// It allows multiple messages to be sent simultaneously on the same control connection.
|
||||
// The server's response messages will be dispatched to the corresponding waiting goroutines based on the laneKey and message type.
|
||||
msgTransporter transport.MessageTransporter
|
||||
|
||||
// msgDispatcher is a wrapper for control connection.
|
||||
// It provides a channel for sending messages, and you can register handlers to process messages based on their respective types.
|
||||
msgDispatcher *msg.Dispatcher
|
||||
}
|
||||
|
||||
func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, error) {
|
||||
func NewControl(
|
||||
ctx context.Context, runID string, conn net.Conn, cm *ConnectionManager,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
pxyCfgs []v1.ProxyConfigurer,
|
||||
visitorCfgs []v1.VisitorConfigurer,
|
||||
authSetter auth.Setter,
|
||||
) *Control {
|
||||
// new xlog instance
|
||||
ctl := &Control{
|
||||
ctx: ctx,
|
||||
xl: xlog.FromContextSafe(ctx),
|
||||
sessionCtx: sessionCtx,
|
||||
doneCh: make(chan struct{}),
|
||||
runID: runID,
|
||||
conn: conn,
|
||||
cm: cm,
|
||||
pxyCfgs: pxyCfgs,
|
||||
sendCh: make(chan msg.Message, 100),
|
||||
readCh: make(chan msg.Message, 100),
|
||||
closedCh: make(chan struct{}),
|
||||
closedDoneCh: make(chan struct{}),
|
||||
clientCfg: clientCfg,
|
||||
readerShutdown: shutdown.New(),
|
||||
writerShutdown: shutdown.New(),
|
||||
msgHandlerShutdown: shutdown.New(),
|
||||
authSetter: authSetter,
|
||||
}
|
||||
ctl.lastPong.Store(time.Now())
|
||||
ctl.msgTransporter = transport.NewMessageTransporter(ctl.sendCh)
|
||||
ctl.pm = proxy.NewManager(ctl.ctx, clientCfg, ctl.msgTransporter)
|
||||
|
||||
if sessionCtx.ConnEncrypted {
|
||||
cryptoRW, err := netpkg.NewCryptoReadWriter(sessionCtx.Conn, []byte(sessionCtx.Common.Auth.Token))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctl.msgDispatcher = msg.NewDispatcher(cryptoRW)
|
||||
} else {
|
||||
ctl.msgDispatcher = msg.NewDispatcher(sessionCtx.Conn)
|
||||
}
|
||||
ctl.registerMsgHandlers()
|
||||
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher.SendChannel())
|
||||
|
||||
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, ctl.msgTransporter)
|
||||
ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common, ctl.connectServer, ctl.msgTransporter)
|
||||
return ctl, nil
|
||||
ctl.vm = visitor.NewManager(ctl.ctx, ctl.runID, ctl.clientCfg, ctl.connectServer, ctl.msgTransporter)
|
||||
ctl.vm.Reload(visitorCfgs)
|
||||
return ctl
|
||||
}
|
||||
|
||||
func (ctl *Control) Run(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) {
|
||||
func (ctl *Control) Run() {
|
||||
go ctl.worker()
|
||||
|
||||
// start all proxies
|
||||
ctl.pm.UpdateAll(proxyCfgs)
|
||||
ctl.pm.Reload(ctl.pxyCfgs)
|
||||
|
||||
// start all visitors
|
||||
ctl.vm.UpdateAll(visitorCfgs)
|
||||
go ctl.vm.Run()
|
||||
}
|
||||
|
||||
func (ctl *Control) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
|
||||
ctl.pm.SetInWorkConnCallback(cb)
|
||||
}
|
||||
|
||||
func (ctl *Control) handleReqWorkConn(_ msg.Message) {
|
||||
func (ctl *Control) HandleReqWorkConn(_ *msg.ReqWorkConn) {
|
||||
xl := ctl.xl
|
||||
workConn, err := ctl.connectServer()
|
||||
if err != nil {
|
||||
@@ -129,11 +134,10 @@ func (ctl *Control) handleReqWorkConn(_ msg.Message) {
|
||||
}
|
||||
|
||||
m := &msg.NewWorkConn{
|
||||
RunID: ctl.sessionCtx.RunID,
|
||||
RunID: ctl.runID,
|
||||
}
|
||||
if err = ctl.sessionCtx.AuthSetter.SetNewWorkConn(m); err != nil {
|
||||
if err = ctl.authSetter.SetNewWorkConn(m); err != nil {
|
||||
xl.Warn("error during NewWorkConn authentication: %v", err)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
if err = msg.WriteMsg(workConn, m); err != nil {
|
||||
@@ -158,9 +162,8 @@ func (ctl *Control) handleReqWorkConn(_ msg.Message) {
|
||||
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
||||
}
|
||||
|
||||
func (ctl *Control) handleNewProxyResp(m msg.Message) {
|
||||
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||
xl := ctl.xl
|
||||
inMsg := m.(*msg.NewProxyResp)
|
||||
// Server will return NewProxyResp message to each NewProxy message.
|
||||
// Start a new proxy handler if no error got
|
||||
err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
|
||||
@@ -171,9 +174,8 @@ func (ctl *Control) handleNewProxyResp(m msg.Message) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) handleNatHoleResp(m msg.Message) {
|
||||
func (ctl *Control) HandleNatHoleResp(inMsg *msg.NatHoleResp) {
|
||||
xl := ctl.xl
|
||||
inMsg := m.(*msg.NatHoleResp)
|
||||
|
||||
// Dispatch the NatHoleResp message to the related proxy.
|
||||
ok := ctl.msgTransporter.DispatchWithType(inMsg, msg.TypeNameNatHoleResp, inMsg.TransactionID)
|
||||
@@ -182,25 +184,6 @@ func (ctl *Control) handleNatHoleResp(m msg.Message) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) handlePong(m msg.Message) {
|
||||
xl := ctl.xl
|
||||
inMsg := m.(*msg.Pong)
|
||||
|
||||
if inMsg.Error != "" {
|
||||
xl.Error("Pong message contains error: %s", inMsg.Error)
|
||||
ctl.closeSession()
|
||||
return
|
||||
}
|
||||
ctl.lastPong.Store(time.Now())
|
||||
xl.Debug("receive heartbeat from server")
|
||||
}
|
||||
|
||||
// closeSession closes the control connection.
|
||||
func (ctl *Control) closeSession() {
|
||||
ctl.sessionCtx.Conn.Close()
|
||||
ctl.sessionCtx.Connector.Close()
|
||||
}
|
||||
|
||||
func (ctl *Control) Close() error {
|
||||
return ctl.GracefulClose(0)
|
||||
}
|
||||
@@ -211,86 +194,170 @@ func (ctl *Control) GracefulClose(d time.Duration) error {
|
||||
|
||||
time.Sleep(d)
|
||||
|
||||
ctl.closeSession()
|
||||
ctl.conn.Close()
|
||||
ctl.cm.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Done returns a channel that will be closed after all resources are released
|
||||
func (ctl *Control) Done() <-chan struct{} {
|
||||
return ctl.doneCh
|
||||
// ClosedDoneCh returns a channel that will be closed after all resources are released
|
||||
func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
||||
return ctl.closedDoneCh
|
||||
}
|
||||
|
||||
// connectServer return a new connection to frps
|
||||
func (ctl *Control) connectServer() (net.Conn, error) {
|
||||
return ctl.sessionCtx.Connector.Connect()
|
||||
func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
||||
return ctl.cm.Connect()
|
||||
}
|
||||
|
||||
func (ctl *Control) registerMsgHandlers() {
|
||||
ctl.msgDispatcher.RegisterHandler(&msg.ReqWorkConn{}, msg.AsyncHandler(ctl.handleReqWorkConn))
|
||||
ctl.msgDispatcher.RegisterHandler(&msg.NewProxyResp{}, ctl.handleNewProxyResp)
|
||||
ctl.msgDispatcher.RegisterHandler(&msg.NatHoleResp{}, ctl.handleNatHoleResp)
|
||||
ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
|
||||
}
|
||||
|
||||
// headerWorker sends heartbeat to server and check heartbeat timeout.
|
||||
func (ctl *Control) heartbeatWorker() {
|
||||
// reader read all messages from frps and send to readCh
|
||||
func (ctl *Control) reader() {
|
||||
xl := ctl.xl
|
||||
|
||||
// TODO(fatedier): Change default value of HeartbeatInterval to -1 if tcpmux is enabled.
|
||||
// Users can still enable heartbeat feature by setting HeartbeatInterval to a positive value.
|
||||
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 {
|
||||
// send heartbeat to server
|
||||
sendHeartBeat := func() (bool, error) {
|
||||
xl.Debug("send heartbeat to server")
|
||||
pingMsg := &msg.Ping{}
|
||||
if err := ctl.sessionCtx.AuthSetter.SetPing(pingMsg); err != nil {
|
||||
xl.Warn("error during ping authentication: %v, skip sending ping message", err)
|
||||
return false, err
|
||||
}
|
||||
_ = ctl.msgDispatcher.Send(pingMsg)
|
||||
return false, nil
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
xl.Error("panic error: %v", err)
|
||||
xl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
defer ctl.readerShutdown.Done()
|
||||
defer close(ctl.closedCh)
|
||||
|
||||
go wait.BackoffUntil(sendHeartBeat,
|
||||
wait.NewFastBackoffManager(wait.FastBackoffOptions{
|
||||
Duration: time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatInterval) * time.Second,
|
||||
InitDurationIfFail: time.Second,
|
||||
Factor: 2.0,
|
||||
Jitter: 0.1,
|
||||
MaxDuration: time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatInterval) * time.Second,
|
||||
}),
|
||||
true, ctl.doneCh,
|
||||
)
|
||||
}
|
||||
|
||||
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
|
||||
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 &&
|
||||
!lo.FromPtr(ctl.sessionCtx.Common.Transport.TCPMux) {
|
||||
|
||||
go wait.Until(func() {
|
||||
if time.Since(ctl.lastPong.Load().(time.Time)) > time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatTimeout)*time.Second {
|
||||
xl.Warn("heartbeat timeout")
|
||||
ctl.closeSession()
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Auth.Token))
|
||||
for {
|
||||
m, err := msg.ReadMsg(encReader)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
xl.Debug("read from control connection EOF")
|
||||
return
|
||||
}
|
||||
}, time.Second, ctl.doneCh)
|
||||
xl.Warn("read error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
ctl.readCh <- m
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) worker() {
|
||||
go ctl.heartbeatWorker()
|
||||
go ctl.msgDispatcher.Run()
|
||||
// writer writes messages got from sendCh to frps
|
||||
func (ctl *Control) writer() {
|
||||
xl := ctl.xl
|
||||
defer ctl.writerShutdown.Done()
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Auth.Token))
|
||||
if err != nil {
|
||||
xl.Error("crypto new writer error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
for {
|
||||
m, ok := <-ctl.sendCh
|
||||
if !ok {
|
||||
xl.Info("control writer is closing")
|
||||
return
|
||||
}
|
||||
|
||||
<-ctl.msgDispatcher.Done()
|
||||
ctl.closeSession()
|
||||
if err := msg.WriteMsg(encWriter, m); err != nil {
|
||||
xl.Warn("write message to control connection error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// msgHandler handles all channel events and performs corresponding operations.
|
||||
func (ctl *Control) msgHandler() {
|
||||
xl := ctl.xl
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
xl.Error("panic error: %v", err)
|
||||
xl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
defer ctl.msgHandlerShutdown.Done()
|
||||
|
||||
var hbSendCh <-chan time.Time
|
||||
// TODO(fatedier): disable heartbeat if TCPMux is enabled.
|
||||
// Just keep it here to keep compatible with old version frps.
|
||||
if ctl.clientCfg.Transport.HeartbeatInterval > 0 {
|
||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.Transport.HeartbeatInterval) * time.Second)
|
||||
defer hbSend.Stop()
|
||||
hbSendCh = hbSend.C
|
||||
}
|
||||
|
||||
var hbCheckCh <-chan time.Time
|
||||
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
|
||||
if ctl.clientCfg.Transport.HeartbeatInterval > 0 && ctl.clientCfg.Transport.HeartbeatTimeout > 0 &&
|
||||
!lo.FromPtr(ctl.clientCfg.Transport.TCPMux) {
|
||||
hbCheck := time.NewTicker(time.Second)
|
||||
defer hbCheck.Stop()
|
||||
hbCheckCh = hbCheck.C
|
||||
}
|
||||
|
||||
ctl.lastPong = time.Now()
|
||||
for {
|
||||
select {
|
||||
case <-hbSendCh:
|
||||
// send heartbeat to server
|
||||
xl.Debug("send heartbeat to server")
|
||||
pingMsg := &msg.Ping{}
|
||||
if err := ctl.authSetter.SetPing(pingMsg); err != nil {
|
||||
xl.Warn("error during ping authentication: %v", err)
|
||||
return
|
||||
}
|
||||
ctl.sendCh <- pingMsg
|
||||
case <-hbCheckCh:
|
||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.Transport.HeartbeatTimeout)*time.Second {
|
||||
xl.Warn("heartbeat timeout")
|
||||
// let reader() stop
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
case rawMsg, ok := <-ctl.readCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.ReqWorkConn:
|
||||
go ctl.HandleReqWorkConn(m)
|
||||
case *msg.NewProxyResp:
|
||||
ctl.HandleNewProxyResp(m)
|
||||
case *msg.NatHoleResp:
|
||||
ctl.HandleNatHoleResp(m)
|
||||
case *msg.Pong:
|
||||
if m.Error != "" {
|
||||
xl.Error("Pong contains error: %s", m.Error)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
ctl.lastPong = time.Now()
|
||||
xl.Debug("receive heartbeat from server")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If controler is notified by closedCh, reader and writer and handler will exit
|
||||
func (ctl *Control) worker() {
|
||||
go ctl.msgHandler()
|
||||
go ctl.reader()
|
||||
go ctl.writer()
|
||||
|
||||
<-ctl.closedCh
|
||||
// close related channels and wait until other goroutines done
|
||||
close(ctl.readCh)
|
||||
ctl.readerShutdown.WaitDone()
|
||||
ctl.msgHandlerShutdown.WaitDone()
|
||||
|
||||
close(ctl.sendCh)
|
||||
ctl.writerShutdown.WaitDone()
|
||||
|
||||
ctl.pm.Close()
|
||||
ctl.vm.Close()
|
||||
close(ctl.doneCh)
|
||||
|
||||
close(ctl.closedDoneCh)
|
||||
ctl.cm.Close()
|
||||
}
|
||||
|
||||
func (ctl *Control) UpdateAllConfigurer(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
|
||||
ctl.vm.UpdateAll(visitorCfgs)
|
||||
ctl.pm.UpdateAll(proxyCfgs)
|
||||
func (ctl *Control) ReloadConf(pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
|
||||
ctl.vm.Reload(visitorCfgs)
|
||||
ctl.pm.Reload(pxyCfgs)
|
||||
return nil
|
||||
}
|
||||
|
@@ -47,9 +47,10 @@ func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, v
|
||||
// Proxy defines how to handle work connections for different proxy type.
|
||||
type Proxy interface {
|
||||
Run() error
|
||||
|
||||
// InWorkConn accept work connections registered to server.
|
||||
InWorkConn(net.Conn, *msg.StartWorkConn)
|
||||
SetInWorkConnCallback(func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) /* continue */ bool)
|
||||
|
||||
Close()
|
||||
}
|
||||
|
||||
@@ -89,7 +90,6 @@ type BaseProxy struct {
|
||||
// proxyPlugin is used to handle connections instead of dialing to local service.
|
||||
// It's only validate for TCP protocol now.
|
||||
proxyPlugin plugin.Plugin
|
||||
inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) /* continue */ bool
|
||||
|
||||
mu sync.RWMutex
|
||||
xl *xlog.Logger
|
||||
@@ -113,16 +113,7 @@ func (pxy *BaseProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
|
||||
pxy.inWorkConnCallback = cb
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
if pxy.inWorkConnCallback != nil {
|
||||
if !pxy.inWorkConnCallback(pxy.baseCfg, conn, m) {
|
||||
return
|
||||
}
|
||||
}
|
||||
pxy.HandleTCPWorkConnection(conn, m, []byte(pxy.clientCfg.Auth.Token))
|
||||
}
|
||||
|
||||
@@ -141,7 +132,7 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
||||
})
|
||||
}
|
||||
|
||||
xl.Trace("handle tcp work connection, useEncryption: %t, useCompression: %t",
|
||||
xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
|
||||
baseCfg.Transport.UseEncryption, baseCfg.Transport.UseCompression)
|
||||
if baseCfg.Transport.UseEncryption {
|
||||
remote, err = libio.WithEncryption(remote, encKey)
|
||||
|
@@ -33,7 +33,6 @@ import (
|
||||
type Manager struct {
|
||||
proxies map[string]*Wrapper
|
||||
msgTransporter transport.MessageTransporter
|
||||
inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
||||
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
@@ -72,10 +71,6 @@ func (pm *Manager) StartProxy(name string, remoteAddr string, serverRespErr stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *Manager) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
|
||||
pm.inWorkConnCallback = cb
|
||||
}
|
||||
|
||||
func (pm *Manager) Close() {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
@@ -120,18 +115,9 @@ func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
||||
return ps
|
||||
}
|
||||
|
||||
func (pm *Manager) GetProxyStatus(name string) (*WorkingStatus, bool) {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
if pxy, ok := pm.proxies[name]; ok {
|
||||
return pxy.GetStatus(), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
|
||||
func (pm *Manager) Reload(pxyCfgs []v1.ProxyConfigurer) {
|
||||
xl := xlog.FromContextSafe(pm.ctx)
|
||||
proxyCfgsMap := lo.KeyBy(proxyCfgs, func(c v1.ProxyConfigurer) string {
|
||||
pxyCfgsMap := lo.KeyBy(pxyCfgs, func(c v1.ProxyConfigurer) string {
|
||||
return c.GetBaseConfig().Name
|
||||
})
|
||||
pm.mu.Lock()
|
||||
@@ -140,7 +126,7 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
|
||||
delPxyNames := make([]string, 0)
|
||||
for name, pxy := range pm.proxies {
|
||||
del := false
|
||||
cfg, ok := proxyCfgsMap[name]
|
||||
cfg, ok := pxyCfgsMap[name]
|
||||
if !ok || !reflect.DeepEqual(pxy.Cfg, cfg) {
|
||||
del = true
|
||||
}
|
||||
@@ -156,13 +142,10 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
|
||||
}
|
||||
|
||||
addPxyNames := make([]string, 0)
|
||||
for _, cfg := range proxyCfgs {
|
||||
for _, cfg := range pxyCfgs {
|
||||
name := cfg.GetBaseConfig().Name
|
||||
if _, ok := pm.proxies[name]; !ok {
|
||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter)
|
||||
if pm.inWorkConnCallback != nil {
|
||||
pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
|
||||
}
|
||||
pm.proxies[name] = pxy
|
||||
addPxyNames = append(addPxyNames, name)
|
||||
|
||||
|
@@ -121,10 +121,6 @@ func NewWrapper(
|
||||
return pw
|
||||
}
|
||||
|
||||
func (pw *Wrapper) SetInWorkConnCallback(cb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool) {
|
||||
pw.pxy.SetInWorkConnCallback(cb)
|
||||
}
|
||||
|
||||
func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
|
||||
pw.mu.Lock()
|
||||
defer pw.mu.Unlock()
|
||||
|
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !frps
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
@@ -31,7 +29,7 @@ import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -101,7 +99,7 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
if pxy.cfg.Transport.UseCompression {
|
||||
rwc = libio.WithCompression(rwc)
|
||||
}
|
||||
conn = netpkg.WrapReadWriteCloserToConn(rwc, conn)
|
||||
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
|
||||
|
||||
workConn := conn
|
||||
readCh := make(chan *msg.UDPPacket, 1024)
|
||||
|
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !frps
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
@@ -30,7 +28,7 @@ import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -91,7 +89,7 @@ func (pxy *UDPProxy) Close() {
|
||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
// close resources related with old workConn
|
||||
// close resources releated with old workConn
|
||||
pxy.Close()
|
||||
|
||||
var rwc io.ReadWriteCloser = conn
|
||||
@@ -112,7 +110,7 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
if pxy.cfg.Transport.UseCompression {
|
||||
rwc = libio.WithCompression(rwc)
|
||||
}
|
||||
conn = netpkg.WrapReadWriteCloserToConn(rwc, conn)
|
||||
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
|
||||
|
||||
pxy.mu.Lock()
|
||||
pxy.workConn = conn
|
||||
|
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !frps
|
||||
|
||||
package proxy
|
||||
|
||||
import (
|
||||
@@ -29,7 +27,7 @@ import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/nathole"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -133,7 +131,7 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s
|
||||
}
|
||||
defer lConn.Close()
|
||||
|
||||
remote, err := netpkg.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||
remote, err := utilnet.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||
if err != nil {
|
||||
xl.Warn("create kcp connection from udp connection error: %v", err)
|
||||
return
|
||||
@@ -194,6 +192,6 @@ func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, star
|
||||
_ = c.CloseWithError(0, "")
|
||||
return
|
||||
}
|
||||
go pxy.HandleTCPWorkConnection(netpkg.QuicStreamToNetConn(stream, c), startWorkConnMsg, []byte(pxy.cfg.Secretkey))
|
||||
go pxy.HandleTCPWorkConnection(utilnet.QuicStreamToNetConn(stream, c), startWorkConnMsg, []byte(pxy.cfg.Secretkey))
|
||||
}
|
||||
}
|
||||
|
@@ -16,25 +16,32 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
quic "github.com/quic-go/quic-go"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/pkg/util/wait"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
@@ -42,197 +49,212 @@ func init() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
}
|
||||
|
||||
type cancelErr struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e cancelErr) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// ServiceOptions contains options for creating a new client service.
|
||||
type ServiceOptions struct {
|
||||
Common *v1.ClientCommonConfig
|
||||
ProxyCfgs []v1.ProxyConfigurer
|
||||
VisitorCfgs []v1.VisitorConfigurer
|
||||
|
||||
// ConfigFilePath is the path to the configuration file used to initialize.
|
||||
// If it is empty, it means that the configuration file is not used for initialization.
|
||||
// It may be initialized using command line parameters or called directly.
|
||||
ConfigFilePath string
|
||||
|
||||
// ClientSpec is the client specification that control the client behavior.
|
||||
ClientSpec *msg.ClientSpec
|
||||
|
||||
// ConnectorCreator is a function that creates a new connector to make connections to the server.
|
||||
// The Connector shields the underlying connection details, whether it is through TCP or QUIC connection,
|
||||
// and regardless of whether multiplexing is used.
|
||||
//
|
||||
// If it is not set, the default frpc connector will be used.
|
||||
// By using a custom Connector, it can be used to implement a VirtualClient, which connects to frps
|
||||
// through a pipe instead of a real physical connection.
|
||||
ConnectorCreator func(context.Context, *v1.ClientCommonConfig) Connector
|
||||
|
||||
// HandleWorkConnCb is a callback function that is called when a new work connection is created.
|
||||
//
|
||||
// If it is not set, the default frpc implementation will be used.
|
||||
HandleWorkConnCb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
||||
}
|
||||
|
||||
// setServiceOptionsDefault sets the default values for ServiceOptions.
|
||||
func setServiceOptionsDefault(options *ServiceOptions) {
|
||||
if options.Common != nil {
|
||||
options.Common.Complete()
|
||||
}
|
||||
if options.ConnectorCreator == nil {
|
||||
options.ConnectorCreator = NewConnector
|
||||
}
|
||||
}
|
||||
|
||||
// Service is the client service that connects to frps and provides proxy services.
|
||||
// Service is a client service.
|
||||
type Service struct {
|
||||
ctlMu sync.RWMutex
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
runID string
|
||||
|
||||
// manager control connection with server
|
||||
ctl *Control
|
||||
// Uniq id got from frps, it will be attached to loginMsg.
|
||||
runID string
|
||||
ctlMu sync.RWMutex
|
||||
|
||||
// Sets authentication based on selected method
|
||||
authSetter auth.Setter
|
||||
|
||||
// web server for admin UI and apis
|
||||
webServer *httppkg.Server
|
||||
|
||||
cfgMu sync.RWMutex
|
||||
common *v1.ClientCommonConfig
|
||||
proxyCfgs []v1.ProxyConfigurer
|
||||
cfg *v1.ClientCommonConfig
|
||||
pxyCfgs []v1.ProxyConfigurer
|
||||
visitorCfgs []v1.VisitorConfigurer
|
||||
clientSpec *msg.ClientSpec
|
||||
cfgMu sync.RWMutex
|
||||
|
||||
// The configuration file used to initialize this client, or an empty
|
||||
// string if no configuration file was used.
|
||||
configFilePath string
|
||||
cfgFile string
|
||||
|
||||
exit uint32 // 0 means not exit
|
||||
|
||||
// service context
|
||||
ctx context.Context
|
||||
// call cancel to stop service
|
||||
cancel context.CancelCauseFunc
|
||||
gracefulShutdownDuration time.Duration
|
||||
|
||||
connectorCreator func(context.Context, *v1.ClientCommonConfig) Connector
|
||||
handleWorkConnCb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewService(options ServiceOptions) (*Service, error) {
|
||||
setServiceOptionsDefault(&options)
|
||||
|
||||
var webServer *httppkg.Server
|
||||
if options.Common.WebServer.Port > 0 {
|
||||
ws, err := httppkg.NewServer(options.Common.WebServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webServer = ws
|
||||
}
|
||||
s := &Service{
|
||||
func NewService(
|
||||
cfg *v1.ClientCommonConfig,
|
||||
pxyCfgs []v1.ProxyConfigurer,
|
||||
visitorCfgs []v1.VisitorConfigurer,
|
||||
cfgFile string,
|
||||
) (svr *Service, err error) {
|
||||
svr = &Service{
|
||||
authSetter: auth.NewAuthSetter(cfg.Auth),
|
||||
cfg: cfg,
|
||||
cfgFile: cfgFile,
|
||||
pxyCfgs: pxyCfgs,
|
||||
visitorCfgs: visitorCfgs,
|
||||
ctx: context.Background(),
|
||||
authSetter: auth.NewAuthSetter(options.Common.Auth),
|
||||
webServer: webServer,
|
||||
common: options.Common,
|
||||
configFilePath: options.ConfigFilePath,
|
||||
proxyCfgs: options.ProxyCfgs,
|
||||
visitorCfgs: options.VisitorCfgs,
|
||||
clientSpec: options.ClientSpec,
|
||||
connectorCreator: options.ConnectorCreator,
|
||||
handleWorkConnCb: options.HandleWorkConnCb,
|
||||
exit: 0,
|
||||
}
|
||||
if webServer != nil {
|
||||
webServer.RouteRegister(s.registerRouteHandlers)
|
||||
}
|
||||
return s, nil
|
||||
return
|
||||
}
|
||||
|
||||
func (svr *Service) GetController() *Control {
|
||||
svr.ctlMu.RLock()
|
||||
defer svr.ctlMu.RUnlock()
|
||||
return svr.ctl
|
||||
}
|
||||
|
||||
func (svr *Service) Run(ctx context.Context) error {
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
svr.ctx = xlog.NewContext(ctx, xlog.FromContextSafe(ctx))
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
svr.ctx = xlog.NewContext(ctx, xlog.New())
|
||||
svr.cancel = cancel
|
||||
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
|
||||
// set custom DNSServer
|
||||
if svr.common.DNSServer != "" {
|
||||
netpkg.SetDefaultDNSAddress(svr.common.DNSServer)
|
||||
if svr.cfg.DNSServer != "" {
|
||||
dnsAddr := svr.cfg.DNSServer
|
||||
if _, _, err := net.SplitHostPort(dnsAddr); err != nil {
|
||||
dnsAddr = net.JoinHostPort(dnsAddr, "53")
|
||||
}
|
||||
// Change default dns server for frpc
|
||||
net.DefaultResolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return net.Dial("udp", dnsAddr)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// first login to frps
|
||||
svr.loopLoginUntilSuccess(10*time.Second, lo.FromPtr(svr.common.LoginFailExit))
|
||||
if svr.ctl == nil {
|
||||
cancelCause := cancelErr{}
|
||||
_ = errors.As(context.Cause(svr.ctx), &cancelCause)
|
||||
return fmt.Errorf("login to the server failed: %v. With loginFailExit enabled, no additional retries will be attempted", cancelCause.Err)
|
||||
// login to frps
|
||||
for {
|
||||
conn, cm, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warn("login to server failed: %v", err)
|
||||
|
||||
// if login_fail_exit is true, just exit this program
|
||||
// otherwise sleep a while and try again to connect to server
|
||||
if lo.FromPtr(svr.cfg.LoginFailExit) {
|
||||
return err
|
||||
}
|
||||
util.RandomSleep(5*time.Second, 0.9, 1.1)
|
||||
} else {
|
||||
// login success
|
||||
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
svr.ctl = ctl
|
||||
svr.ctlMu.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
go svr.keepControllerWorking()
|
||||
|
||||
if svr.webServer != nil {
|
||||
go func() {
|
||||
log.Info("admin server listen on %s", svr.webServer.Address())
|
||||
if err := svr.webServer.Run(); err != nil {
|
||||
log.Warn("admin server exit with error: %v", err)
|
||||
if svr.cfg.WebServer.Port != 0 {
|
||||
// Init admin server assets
|
||||
assets.Load(svr.cfg.WebServer.AssetsDir)
|
||||
|
||||
address := net.JoinHostPort(svr.cfg.WebServer.Addr, strconv.Itoa(svr.cfg.WebServer.Port))
|
||||
err := svr.RunAdminServer(address)
|
||||
if err != nil {
|
||||
log.Warn("run admin server error: %v", err)
|
||||
}
|
||||
}()
|
||||
log.Info("admin server listen on %s:%d", svr.cfg.WebServer.Addr, svr.cfg.WebServer.Port)
|
||||
}
|
||||
<-svr.ctx.Done()
|
||||
svr.stop()
|
||||
// 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
|
||||
}
|
||||
|
||||
func (svr *Service) keepControllerWorking() {
|
||||
<-svr.ctl.Done()
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
maxDelayTime := 20 * time.Second
|
||||
delayTime := time.Second
|
||||
|
||||
// There is a situation where the login is successful but due to certain reasons,
|
||||
// the control immediately exits. It is necessary to limit the frequency of reconnection in this case.
|
||||
// The interval for the first three retries in 1 minute will be very short, and then it will increase exponentially.
|
||||
// The maximum interval is 20 seconds.
|
||||
wait.BackoffUntil(func() (bool, error) {
|
||||
// loopLoginUntilSuccess is another layer of loop that will continuously attempt to
|
||||
// login to the server until successful.
|
||||
svr.loopLoginUntilSuccess(20*time.Second, false)
|
||||
if svr.ctl != nil {
|
||||
<-svr.ctl.Done()
|
||||
return false, errors.New("control is closed and try another loop")
|
||||
// if frpc reconnect frps, we need to limit retry times in 1min
|
||||
// current retry logic is sleep 0s, 0s, 0s, 1s, 2s, 4s, 8s, ...
|
||||
// when exceed 1min, we will reset delay and counts
|
||||
cutoffTime := time.Now().Add(time.Minute)
|
||||
reconnectDelay := time.Second
|
||||
reconnectCounts := 1
|
||||
|
||||
for {
|
||||
<-svr.ctl.ClosedDoneCh()
|
||||
if atomic.LoadUint32(&svr.exit) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// the first three attempts with a low delay
|
||||
if reconnectCounts > 3 {
|
||||
util.RandomSleep(reconnectDelay, 0.9, 1.1)
|
||||
xl.Info("wait %v to reconnect", reconnectDelay)
|
||||
reconnectDelay *= 2
|
||||
} else {
|
||||
util.RandomSleep(time.Second, 0, 0.5)
|
||||
}
|
||||
reconnectCounts++
|
||||
|
||||
now := time.Now()
|
||||
if now.After(cutoffTime) {
|
||||
// reset
|
||||
cutoffTime = now.Add(time.Minute)
|
||||
reconnectDelay = time.Second
|
||||
reconnectCounts = 1
|
||||
}
|
||||
|
||||
for {
|
||||
if atomic.LoadUint32(&svr.exit) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
xl.Info("try to reconnect to server...")
|
||||
conn, cm, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warn("reconnect to server error: %v, wait %v for another retry", err, delayTime)
|
||||
util.RandomSleep(delayTime, 0.9, 1.1)
|
||||
|
||||
delayTime *= 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
continue
|
||||
}
|
||||
// reconnect success, init delayTime
|
||||
delayTime = time.Second
|
||||
|
||||
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||
ctl.Run()
|
||||
svr.ctlMu.Lock()
|
||||
if svr.ctl != nil {
|
||||
svr.ctl.Close()
|
||||
}
|
||||
svr.ctl = ctl
|
||||
svr.ctlMu.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
// If the control is nil, it means that the login failed and the service is also closed.
|
||||
return false, nil
|
||||
}, wait.NewFastBackoffManager(
|
||||
wait.FastBackoffOptions{
|
||||
Duration: time.Second,
|
||||
Factor: 2,
|
||||
Jitter: 0.1,
|
||||
MaxDuration: 20 * time.Second,
|
||||
FastRetryCount: 3,
|
||||
FastRetryDelay: 200 * time.Millisecond,
|
||||
FastRetryWindow: time.Minute,
|
||||
FastRetryJitter: 0.5,
|
||||
},
|
||||
), true, svr.ctx.Done())
|
||||
}
|
||||
|
||||
// login creates a connection to frps and registers it self as a client
|
||||
// conn: control connection
|
||||
// session: if it's not nil, using tcp mux
|
||||
func (svr *Service) login() (conn net.Conn, connector Connector, err error) {
|
||||
func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
connector = svr.connectorCreator(svr.ctx, svr.common)
|
||||
if err = connector.Open(); err != nil {
|
||||
cm = NewConnectionManager(svr.ctx, svr.cfg)
|
||||
|
||||
if err = cm.OpenConnection(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
connector.Close()
|
||||
cm.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err = connector.Connect()
|
||||
conn, err = cm.Connect()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -240,15 +262,12 @@ func (svr *Service) login() (conn net.Conn, connector Connector, err error) {
|
||||
loginMsg := &msg.Login{
|
||||
Arch: runtime.GOARCH,
|
||||
Os: runtime.GOOS,
|
||||
PoolCount: svr.common.Transport.PoolCount,
|
||||
User: svr.common.User,
|
||||
PoolCount: svr.cfg.Transport.PoolCount,
|
||||
User: svr.cfg.User,
|
||||
Version: version.Full(),
|
||||
Timestamp: time.Now().Unix(),
|
||||
RunID: svr.runID,
|
||||
Metas: svr.common.Metadatas,
|
||||
}
|
||||
if svr.clientSpec != nil {
|
||||
loginMsg.ClientSpec = *svr.clientSpec
|
||||
Metas: svr.cfg.Metadatas,
|
||||
}
|
||||
|
||||
// Add auth
|
||||
@@ -274,74 +293,16 @@ func (svr *Service) login() (conn net.Conn, connector Connector, err error) {
|
||||
}
|
||||
|
||||
svr.runID = loginRespMsg.RunID
|
||||
xl.AddPrefix(xlog.LogPrefix{Name: "runID", Value: svr.runID})
|
||||
xl.ResetPrefixes()
|
||||
xl.AppendPrefix(svr.runID)
|
||||
|
||||
xl.Info("login to server success, get run id [%s]", loginRespMsg.RunID)
|
||||
return
|
||||
}
|
||||
|
||||
func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginExit bool) {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
|
||||
loginFunc := func() (bool, error) {
|
||||
xl.Info("try to connect to server...")
|
||||
conn, connector, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warn("connect to server error: %v", err)
|
||||
if firstLoginExit {
|
||||
svr.cancel(cancelErr{Err: err})
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
svr.cfgMu.RLock()
|
||||
proxyCfgs := svr.proxyCfgs
|
||||
visitorCfgs := svr.visitorCfgs
|
||||
svr.cfgMu.RUnlock()
|
||||
connEncrypted := true
|
||||
if svr.clientSpec != nil && svr.clientSpec.Type == "ssh-tunnel" {
|
||||
connEncrypted = false
|
||||
}
|
||||
sessionCtx := &SessionContext{
|
||||
Common: svr.common,
|
||||
RunID: svr.runID,
|
||||
Conn: conn,
|
||||
ConnEncrypted: connEncrypted,
|
||||
AuthSetter: svr.authSetter,
|
||||
Connector: connector,
|
||||
}
|
||||
ctl, err := NewControl(svr.ctx, sessionCtx)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Error("NewControl error: %v", err)
|
||||
return false, err
|
||||
}
|
||||
ctl.SetInWorkConnCallback(svr.handleWorkConnCb)
|
||||
|
||||
ctl.Run(proxyCfgs, visitorCfgs)
|
||||
// close and replace previous control
|
||||
svr.ctlMu.Lock()
|
||||
if svr.ctl != nil {
|
||||
svr.ctl.Close()
|
||||
}
|
||||
svr.ctl = ctl
|
||||
svr.ctlMu.Unlock()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// try to reconnect to server until success
|
||||
wait.BackoffUntil(loginFunc, wait.NewFastBackoffManager(
|
||||
wait.FastBackoffOptions{
|
||||
Duration: time.Second,
|
||||
Factor: 2,
|
||||
Jitter: 0.1,
|
||||
MaxDuration: maxInterval,
|
||||
}), true, svr.ctx.Done())
|
||||
}
|
||||
|
||||
func (svr *Service) UpdateAllConfigurer(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
|
||||
func (svr *Service) ReloadConf(pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error {
|
||||
svr.cfgMu.Lock()
|
||||
svr.proxyCfgs = proxyCfgs
|
||||
svr.pxyCfgs = pxyCfgs
|
||||
svr.visitorCfgs = visitorCfgs
|
||||
svr.cfgMu.Unlock()
|
||||
|
||||
@@ -350,7 +311,7 @@ func (svr *Service) UpdateAllConfigurer(proxyCfgs []v1.ProxyConfigurer, visitorC
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
if ctl != nil {
|
||||
return svr.ctl.UpdateAllConfigurer(proxyCfgs, visitorCfgs)
|
||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -360,31 +321,188 @@ func (svr *Service) Close() {
|
||||
}
|
||||
|
||||
func (svr *Service) GracefulClose(d time.Duration) {
|
||||
svr.gracefulShutdownDuration = d
|
||||
svr.cancel(nil)
|
||||
}
|
||||
atomic.StoreUint32(&svr.exit, 1)
|
||||
|
||||
func (svr *Service) stop() {
|
||||
svr.ctlMu.Lock()
|
||||
defer svr.ctlMu.Unlock()
|
||||
svr.ctlMu.RLock()
|
||||
if svr.ctl != nil {
|
||||
svr.ctl.GracefulClose(svr.gracefulShutdownDuration)
|
||||
svr.ctl.GracefulClose(d)
|
||||
svr.ctl = nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(fatedier): Use StatusExporter to provide query interfaces instead of directly using methods from the Service.
|
||||
func (svr *Service) GetProxyStatus(name string) (*proxy.WorkingStatus, error) {
|
||||
svr.ctlMu.RLock()
|
||||
ctl := svr.ctl
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
if ctl == nil {
|
||||
return nil, fmt.Errorf("control is not running")
|
||||
if svr.cancel != nil {
|
||||
svr.cancel()
|
||||
}
|
||||
ws, ok := ctl.pm.GetProxyStatus(name)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("proxy [%s] is not found", name)
|
||||
}
|
||||
return ws, nil
|
||||
}
|
||||
|
||||
type ConnectionManager struct {
|
||||
ctx context.Context
|
||||
cfg *v1.ClientCommonConfig
|
||||
|
||||
muxSession *fmux.Session
|
||||
quicConn quic.Connection
|
||||
}
|
||||
|
||||
func NewConnectionManager(ctx context.Context, cfg *v1.ClientCommonConfig) *ConnectionManager {
|
||||
return &ConnectionManager{
|
||||
ctx: ctx,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) OpenConnection() error {
|
||||
xl := xlog.FromContextSafe(cm.ctx)
|
||||
|
||||
// special for quic
|
||||
if strings.EqualFold(cm.cfg.Transport.Protocol, "quic") {
|
||||
var tlsConfig *tls.Config
|
||||
var err error
|
||||
sn := cm.cfg.Transport.TLS.ServerName
|
||||
if sn == "" {
|
||||
sn = cm.cfg.ServerAddr
|
||||
}
|
||||
if lo.FromPtr(cm.cfg.Transport.TLS.Enable) {
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
cm.cfg.Transport.TLS.CertFile,
|
||||
cm.cfg.Transport.TLS.KeyFile,
|
||||
cm.cfg.Transport.TLS.TrustedCaFile,
|
||||
sn)
|
||||
} else {
|
||||
tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
|
||||
}
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
||||
return err
|
||||
}
|
||||
tlsConfig.NextProtos = []string{"frp"}
|
||||
|
||||
conn, err := quic.DialAddr(
|
||||
cm.ctx,
|
||||
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
||||
tlsConfig, &quic.Config{
|
||||
MaxIdleTimeout: time.Duration(cm.cfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
||||
MaxIncomingStreams: int64(cm.cfg.Transport.QUIC.MaxIncomingStreams),
|
||||
KeepAlivePeriod: time.Duration(cm.cfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.quicConn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
if !lo.FromPtr(cm.cfg.Transport.TCPMux) {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := cm.realConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = time.Duration(cm.cfg.Transport.TCPMuxKeepaliveInterval) * time.Second
|
||||
fmuxCfg.LogOutput = io.Discard
|
||||
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
||||
session, err := fmux.Client(conn, fmuxCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.muxSession = session
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) Connect() (net.Conn, error) {
|
||||
if cm.quicConn != nil {
|
||||
stream, err := cm.quicConn.OpenStreamSync(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utilnet.QuicStreamToNetConn(stream, cm.quicConn), nil
|
||||
} else if cm.muxSession != nil {
|
||||
stream, err := cm.muxSession.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
return cm.realConnect()
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) realConnect() (net.Conn, error) {
|
||||
xl := xlog.FromContextSafe(cm.ctx)
|
||||
var tlsConfig *tls.Config
|
||||
var err error
|
||||
tlsEnable := lo.FromPtr(cm.cfg.Transport.TLS.Enable)
|
||||
if cm.cfg.Transport.Protocol == "wss" {
|
||||
tlsEnable = true
|
||||
}
|
||||
if tlsEnable {
|
||||
sn := cm.cfg.Transport.TLS.ServerName
|
||||
if sn == "" {
|
||||
sn = cm.cfg.ServerAddr
|
||||
}
|
||||
|
||||
tlsConfig, err = transport.NewClientTLSConfig(
|
||||
cm.cfg.Transport.TLS.CertFile,
|
||||
cm.cfg.Transport.TLS.KeyFile,
|
||||
cm.cfg.Transport.TLS.TrustedCaFile,
|
||||
sn)
|
||||
if err != nil {
|
||||
xl.Warn("fail to build tls configuration, err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
proxyType, addr, auth, err := libdial.ParseProxyURL(cm.cfg.Transport.ProxyURL)
|
||||
if err != nil {
|
||||
xl.Error("fail to parse proxy url")
|
||||
return nil, err
|
||||
}
|
||||
dialOptions := []libdial.DialOption{}
|
||||
protocol := cm.cfg.Transport.Protocol
|
||||
switch protocol {
|
||||
case "websocket":
|
||||
protocol = "tcp"
|
||||
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.WithTLSConfig(tlsConfig))
|
||||
}
|
||||
|
||||
if cm.cfg.Transport.ConnectServerLocalIP != "" {
|
||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(cm.cfg.Transport.ConnectServerLocalIP))
|
||||
}
|
||||
dialOptions = append(dialOptions,
|
||||
libdial.WithProtocol(protocol),
|
||||
libdial.WithTimeout(time.Duration(cm.cfg.Transport.DialServerTimeout)*time.Second),
|
||||
libdial.WithKeepAlive(time.Duration(cm.cfg.Transport.DialServerKeepAlive)*time.Second),
|
||||
libdial.WithProxy(proxyType, addr),
|
||||
libdial.WithProxyAuth(auth),
|
||||
)
|
||||
conn, err := libdial.DialContext(
|
||||
cm.ctx,
|
||||
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
||||
dialOptions...,
|
||||
)
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (cm *ConnectionManager) Close() error {
|
||||
if cm.quicConn != nil {
|
||||
_ = cm.quicConn.CloseWithError(0, "")
|
||||
}
|
||||
if cm.muxSession != nil {
|
||||
_ = cm.muxSession.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ import (
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
@@ -242,7 +242,7 @@ func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
||||
if sv.cfg.Transport.UseCompression {
|
||||
remote = libio.WithCompression(remote)
|
||||
}
|
||||
return netpkg.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
||||
return utilnet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) Close() {
|
||||
|
@@ -21,11 +21,11 @@ import (
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
// Helper wraps some functions for visitor to use.
|
||||
// Helper wrapps some functions for visitor to use.
|
||||
type Helper interface {
|
||||
// ConnectServer directly connects to the frp server.
|
||||
ConnectServer() (net.Conn, error)
|
||||
@@ -56,7 +56,7 @@ func NewVisitor(
|
||||
clientCfg: clientCfg,
|
||||
helper: helper,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
internalLn: netpkg.NewInternalListener(),
|
||||
internalLn: utilnet.NewInternalListener(),
|
||||
}
|
||||
switch cfg := cfg.(type) {
|
||||
case *v1.STCPVisitorConfig:
|
||||
@@ -84,7 +84,7 @@ type BaseVisitor struct {
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
helper Helper
|
||||
l net.Listener
|
||||
internalLn *netpkg.InternalListener
|
||||
internalLn *utilnet.InternalListener
|
||||
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
|
@@ -36,7 +36,6 @@ type Manager struct {
|
||||
helper Helper
|
||||
|
||||
checkInterval time.Duration
|
||||
keepVisitorsRunningOnce sync.Once
|
||||
|
||||
mu sync.RWMutex
|
||||
ctx context.Context
|
||||
@@ -68,9 +67,7 @@ func NewManager(
|
||||
return m
|
||||
}
|
||||
|
||||
// keepVisitorsRunning checks all visitors' status periodically, if some visitor is not running, start it.
|
||||
// It will only start after Reload is called and a new visitor is added.
|
||||
func (vm *Manager) keepVisitorsRunning() {
|
||||
func (vm *Manager) Run() {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
|
||||
ticker := time.NewTicker(vm.checkInterval)
|
||||
@@ -79,7 +76,7 @@ func (vm *Manager) keepVisitorsRunning() {
|
||||
for {
|
||||
select {
|
||||
case <-vm.stopCh:
|
||||
xl.Trace("gracefully shutdown visitor manager")
|
||||
xl.Info("gracefully shutdown visitor manager")
|
||||
return
|
||||
case <-ticker.C:
|
||||
vm.mu.Lock()
|
||||
@@ -123,14 +120,7 @@ func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (vm *Manager) UpdateAll(cfgs []v1.VisitorConfigurer) {
|
||||
if len(cfgs) > 0 {
|
||||
// Only start keepVisitorsRunning goroutine once and only when there is at least one visitor.
|
||||
vm.keepVisitorsRunningOnce.Do(func() {
|
||||
go vm.keepVisitorsRunning()
|
||||
})
|
||||
}
|
||||
|
||||
func (vm *Manager) Reload(cfgs []v1.VisitorConfigurer) {
|
||||
xl := xlog.FromContextSafe(vm.ctx)
|
||||
cfgsMap := lo.KeyBy(cfgs, func(c v1.VisitorConfigurer) string {
|
||||
return c.GetBaseConfig().Name
|
||||
|
@@ -33,7 +33,7 @@ import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/nathole"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
@@ -349,7 +349,7 @@ func (ks *KCPTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) er
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial udp error: %v", err)
|
||||
}
|
||||
remote, err := netpkg.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||
remote, err := utilnet.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("create kcp connection from udp connection error: %v", err)
|
||||
}
|
||||
@@ -440,7 +440,7 @@ func (qs *QUICTunnelSession) OpenConn(ctx context.Context) (net.Conn, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return netpkg.QuicStreamToNetConn(stream, session), nil
|
||||
return utilnet.QuicStreamToNetConn(stream, session), nil
|
||||
}
|
||||
|
||||
func (qs *QUICTunnelSession) Close() {
|
||||
|
@@ -52,7 +52,7 @@ func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) er
|
||||
Use: name,
|
||||
Short: short,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cfg, _, _, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
|
||||
cfg, _, _, _, err := config.LoadClientConfig(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -73,7 +73,7 @@ func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) er
|
||||
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(strictConfigMode); err != nil {
|
||||
if err := client.Reload(); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("reload success")
|
||||
|
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")
|
||||
}
|
@@ -48,10 +48,9 @@ var natholeDiscoveryCmd = &cobra.Command{
|
||||
Short: "Discover nathole information from stun server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// ignore error here, because we can use command line pameters
|
||||
cfg, _, _, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
|
||||
cfg, _, _, _, err := config.LoadClientConfig(cfgFile)
|
||||
if err != nil {
|
||||
cfg = &v1.ClientCommonConfig{}
|
||||
cfg.Complete()
|
||||
}
|
||||
if natHoleSTUNServer != "" {
|
||||
cfg.NatHoleSTUNServer = natHoleSTUNServer
|
||||
|
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
)
|
||||
@@ -51,8 +50,8 @@ func init() {
|
||||
}
|
||||
clientCfg := v1.ClientCommonConfig{}
|
||||
cmd := NewProxyCommand(string(typ), c, &clientCfg)
|
||||
config.RegisterClientCommonConfigFlags(cmd, &clientCfg)
|
||||
config.RegisterProxyFlags(cmd, c)
|
||||
RegisterClientCommonConfigFlags(cmd, &clientCfg)
|
||||
RegisterProxyFlags(cmd, c)
|
||||
|
||||
// add sub command for visitor
|
||||
if lo.Contains(visitorTypes, v1.VisitorType(typ)) {
|
||||
@@ -61,7 +60,7 @@ func init() {
|
||||
panic("visitor type: " + typ + " not support")
|
||||
}
|
||||
visitorCmd := NewVisitorCommand(string(typ), vc, &clientCfg)
|
||||
config.RegisterVisitorFlags(visitorCmd, vc)
|
||||
RegisterVisitorFlags(visitorCmd, vc)
|
||||
cmd.AddCommand(visitorCmd)
|
||||
}
|
||||
rootCmd.AddCommand(cmd)
|
||||
|
@@ -39,14 +39,12 @@ var (
|
||||
cfgFile string
|
||||
cfgDir string
|
||||
showVersion bool
|
||||
strictConfigMode bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", false, "strict config parsing mode, unknown fields will cause an error")
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -97,7 +95,6 @@ func runMultipleClients(cfgDir string) error {
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
rootCmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -111,7 +108,7 @@ func handleTermSignal(svr *client.Service) {
|
||||
}
|
||||
|
||||
func runClient(cfgFilePath string) error {
|
||||
cfg, proxyCfgs, visitorCfgs, isLegacyFormat, err := config.LoadClientConfig(cfgFilePath, strictConfigMode)
|
||||
cfg, pxyCfgs, visitorCfgs, isLegacyFormat, err := config.LoadClientConfig(cfgFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -120,19 +117,19 @@ func runClient(cfgFilePath string) error {
|
||||
"please use yaml/json/toml format instead!\n")
|
||||
}
|
||||
|
||||
warning, err := validation.ValidateAllClientConfig(cfg, proxyCfgs, visitorCfgs)
|
||||
warning, err := validation.ValidateAllClientConfig(cfg, pxyCfgs, visitorCfgs)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return startService(cfg, proxyCfgs, visitorCfgs, cfgFilePath)
|
||||
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
||||
}
|
||||
|
||||
func startService(
|
||||
cfg *v1.ClientCommonConfig,
|
||||
proxyCfgs []v1.ProxyConfigurer,
|
||||
pxyCfgs []v1.ProxyConfigurer,
|
||||
visitorCfgs []v1.VisitorConfigurer,
|
||||
cfgFile string,
|
||||
) error {
|
||||
@@ -142,12 +139,7 @@ func startService(
|
||||
log.Info("start frpc service for config file [%s]", cfgFile)
|
||||
defer log.Info("frpc service for config file [%s] stopped", cfgFile)
|
||||
}
|
||||
svr, err := client.NewService(client.ServiceOptions{
|
||||
Common: cfg,
|
||||
ProxyCfgs: proxyCfgs,
|
||||
VisitorCfgs: visitorCfgs,
|
||||
ConfigFilePath: cfgFile,
|
||||
})
|
||||
svr, err := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -157,5 +149,7 @@ func startService(
|
||||
if shouldGracefulClose {
|
||||
go handleTermSignal(svr)
|
||||
}
|
||||
return svr.Run(context.Background())
|
||||
|
||||
_ = svr.Run(context.Background())
|
||||
return nil
|
||||
}
|
||||
|
@@ -37,12 +37,12 @@ var verifyCmd = &cobra.Command{
|
||||
return nil
|
||||
}
|
||||
|
||||
cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile, strictConfigMode)
|
||||
cliCfg, pxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
warning, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs)
|
||||
warning, err := validation.ValidateAllClientConfig(cliCfg, pxyCfgs, visitorCfgs)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
|
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")
|
||||
}
|
@@ -32,7 +32,6 @@ import (
|
||||
var (
|
||||
cfgFile string
|
||||
showVersion bool
|
||||
strictConfigMode bool
|
||||
|
||||
serverCfg v1.ServerConfig
|
||||
)
|
||||
@@ -40,9 +39,8 @@ var (
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", false, "strict config parsing mode, unknown fields will cause error")
|
||||
|
||||
config.RegisterServerConfigFlags(rootCmd, &serverCfg)
|
||||
RegisterServerConfigFlags(rootCmd, &serverCfg)
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -60,7 +58,7 @@ var rootCmd = &cobra.Command{
|
||||
err error
|
||||
)
|
||||
if cfgFile != "" {
|
||||
svrCfg, isLegacyFormat, err = config.LoadServerConfig(cfgFile, strictConfigMode)
|
||||
svrCfg, isLegacyFormat, err = config.LoadServerConfig(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -92,7 +90,6 @@ var rootCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
rootCmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ var verifyCmd = &cobra.Command{
|
||||
fmt.Println("frps: the configuration file is not specified")
|
||||
return nil
|
||||
}
|
||||
svrCfg, _, err := config.LoadServerConfig(cfgFile, strictConfigMode)
|
||||
svrCfg, _, err := config.LoadServerConfig(cfgFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
357
conf/frpc.toml
357
conf/frpc.toml
@@ -1,9 +1,360 @@
|
||||
serverAddr = "127.0.0.1"
|
||||
# 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 heartbeat_interval is 10 and heartbeat_timeout 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]]
|
||||
name = "test-tcp"
|
||||
# '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
|
||||
remotePort = 6000
|
||||
# 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 remote_port 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 [server_addr] 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 custom_domains 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
|
||||
# route_by_http_user = abc
|
||||
hostHeaderRewrite = "example.com"
|
||||
# params with prefix "header_" will be used to update http request headers
|
||||
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, local_ip and local_port 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, remote_port 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 keep_tunnel_open is set to true, the number of attempts to punch through per hour
|
||||
maxRetriesAnHour = 8
|
||||
minRetryInterval = 90
|
||||
# fallbackTo = "stcp_visitor"
|
||||
# fallbackTimeoutMs = 500
|
||||
|
@@ -1,361 +0,0 @@
|
||||
# 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 permissions 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
|
153
conf/frps.toml
153
conf/frps.toml
@@ -1 +1,154 @@
|
||||
# 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 "bind_addr" field, no need square brackets, like "bind_addr = ::".
|
||||
bindAddr = "0.0.0.0"
|
||||
bindPort = 7000
|
||||
|
||||
# udp port used for kcp protocol, it can be same with 'bind_port'.
|
||||
# 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 bind_addr
|
||||
# proxy_bind_addr = "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 heartbeat_timeout 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 bind_port
|
||||
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 tcpmux_passthrough 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"]
|
||||
|
@@ -1,164 +0,0 @@
|
||||
# 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
|
||||
|
||||
# ssh tunnel gateway
|
||||
# If you want to enable this feature, the bindPort parameter is required, while others are optional.
|
||||
# By default, this feature is disabled. It will be enabled if bindPort is greater than 0.
|
||||
# sshTunnelGateway.bindPort = 2200
|
||||
# sshTunnelGateway.privateKeyFile = "/home/frp-user/.ssh/id_rsa"
|
||||
# sshTunnelGateway.autoGenPrivateKeyPath = ""
|
||||
# sshTunnelGateway.authorizedKeysFile = "/home/frp-user/.ssh/authorized_keys"
|
||||
|
||||
[[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"]
|
@@ -56,7 +56,7 @@ oidc_client_secret =
|
||||
# oidc_audience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_audience =
|
||||
|
||||
# oidc_scope specifies the permissions of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
# oidc_scope specifies the permisssions of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_scope =
|
||||
|
||||
# oidc_token_endpoint_url specifies the URL which implements OIDC Token Endpoint.
|
||||
|
BIN
doc/pic/donate-alipay.png
Normal file
BIN
doc/pic/donate-alipay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
doc/pic/sponsor_asocks.jpg
Normal file
BIN
doc/pic/sponsor_asocks.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
@@ -1,160 +0,0 @@
|
||||
### SSH Tunnel Gateway
|
||||
|
||||
*Added in v0.53.0*
|
||||
|
||||
### Concept
|
||||
|
||||
SSH supports reverse proxy capabilities [rfc](https://www.rfc-editor.org/rfc/rfc4254#page-16).
|
||||
|
||||
frp supports listening on an SSH port on the frps side to achieve TCP protocol proxying using the SSH -R protocol. This mode does not rely on frpc.
|
||||
|
||||
SSH reverse tunneling proxying and proxying SSH ports through frp are two different concepts. SSH reverse tunneling proxying is essentially a basic reverse proxying accomplished by connecting to frps via an SSH client when you don't want to use frpc.
|
||||
|
||||
```toml
|
||||
# frps.toml
|
||||
sshTunnelGateway.bindPort = 0
|
||||
sshTunnelGateway.privateKeyFile = ""
|
||||
sshTunnelGateway.autoGenPrivateKeyPath = ""
|
||||
sshTunnelGateway.authorizedKeysFile = ""
|
||||
```
|
||||
|
||||
| Field | Type | Description | Required |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| bindPort| int | The ssh server port that frps listens on.| Yes |
|
||||
| privateKeyFile | string | Default value is empty. The private key file used by the ssh server. If it is empty, frps will read the private key file under the autoGenPrivateKeyPath path. It can reuse the /home/user/.ssh/id_rsa file on the local machine, or a custom path can be specified.| No |
|
||||
| autoGenPrivateKeyPath | string |Default value is ./.autogen_ssh_key. If the file does not exist or its content is empty, frps will automatically generate RSA private key file content and store it in this file.|No|
|
||||
| authorizedKeysFile | string |Default value is empty. If it is empty, ssh client authentication is not authenticated. If it is not empty, it can implement ssh password-free login authentication. It can reuse the local /home/user/.ssh/authorized_keys file or a custom path can be specified.| No |
|
||||
|
||||
### Basic Usage
|
||||
|
||||
#### Server-side frps
|
||||
|
||||
Minimal configuration:
|
||||
|
||||
```toml
|
||||
sshTunnelGateway.bindPort = 2200
|
||||
```
|
||||
|
||||
Place the above configuration in frps.toml and run `./frps -c frps.toml`. It will listen on port 2200 and accept SSH reverse proxy requests.
|
||||
|
||||
Note:
|
||||
|
||||
1. When using the minimal configuration, a `.autogen_ssh_key` private key file will be automatically created in the current working directory. The SSH server of frps will use this private key file for encryption and decryption. Alternatively, you can reuse an existing private key file on your local machine, such as `/home/user/.ssh/id_rsa`.
|
||||
|
||||
2. When running frps in the minimal configuration mode, connecting to frps via SSH does not require authentication. It is strongly recommended to configure a token in frps and specify the token in the SSH command line.
|
||||
|
||||
#### Client-side SSH
|
||||
|
||||
The command format is:
|
||||
|
||||
```bash
|
||||
ssh -R :80:{local_ip:port} v0@{frps_address} -p {frps_ssh_listen_port} {tcp|http|https|stcp|tcpmux} --remote_port {real_remote_port} --proxy_name {proxy_name} --token {frp_token}
|
||||
```
|
||||
|
||||
1. `--proxy_name` is optional, and if left empty, a random one will be generated.
|
||||
2. The username for logging in to frps is always "v0" and currently has no significance, i.e., `v0@{frps_address}`.
|
||||
3. The server-side proxy listens on the port determined by `--remote_port`.
|
||||
4. `{tcp|http|https|stcp|tcpmux}` supports the complete command parameters, which can be obtained by using `--help`. For example: `ssh -R :80::8080 v0@127.0.0.1 -p 2200 http --help`.
|
||||
5. The token is optional, but for security reasons, it is strongly recommended to configure the token in frps.
|
||||
|
||||
#### TCP Proxy
|
||||
|
||||
```bash
|
||||
ssh -R :80:127.0.0.1:8080 v0@{frp_address} -p 2200 tcp --proxy_name "test-tcp" --remote_port 9090
|
||||
```
|
||||
|
||||
This sets up a proxy on frps that listens on port 9090 and proxies local service on port 8080.
|
||||
|
||||
```bash
|
||||
frp (via SSH) (Ctrl+C to quit)
|
||||
|
||||
User:
|
||||
ProxyName: test-tcp
|
||||
Type: tcp
|
||||
RemoteAddress: :9090
|
||||
```
|
||||
|
||||
Equivalent to:
|
||||
|
||||
```bash
|
||||
frpc tcp --proxy_name "test-tcp" --local_ip 127.0.0.1 --local_port 8080 --remote_port 9090
|
||||
```
|
||||
|
||||
More parameters can be obtained by executing `--help`.
|
||||
|
||||
#### HTTP Proxy
|
||||
|
||||
```bash
|
||||
ssh -R :80:127.0.0.1:8080 v0@{frp address} -p 2200 http --proxy_name "test-http" --custom_domain test-http.frps.com
|
||||
```
|
||||
|
||||
Equivalent to:
|
||||
```bash
|
||||
frpc http --proxy_name "test-http" --custom_domain test-http.frps.com
|
||||
```
|
||||
|
||||
You can access the HTTP service using the following command:
|
||||
|
||||
curl 'http://test-http.frps.com'
|
||||
|
||||
More parameters can be obtained by executing --help.
|
||||
|
||||
#### HTTPS/STCP/TCPMUX Proxy
|
||||
|
||||
To obtain the usage instructions, use the following command:
|
||||
|
||||
```bash
|
||||
ssh -R :80:127.0.0.1:8080 v0@{frp address} -p 2200 {https|stcp|tcpmux} --help
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
#### Reusing the id_rsa File on the Local Machine
|
||||
|
||||
```toml
|
||||
# frps.toml
|
||||
sshTunnelGateway.bindPort = 2200
|
||||
sshTunnelGateway.privateKeyFile = "/home/user/.ssh/id_rsa"
|
||||
```
|
||||
|
||||
During the SSH protocol handshake, public keys are exchanged for data encryption. Therefore, the SSH server on the frps side needs to specify a private key file, which can be reused from an existing file on the local machine. If the privateKeyFile field is empty, frps will automatically create an RSA private key file.
|
||||
|
||||
#### Specifying the Auto-Generated Private Key File Path
|
||||
|
||||
```toml
|
||||
# frps.toml
|
||||
sshTunnelGateway.bindPort = 2200
|
||||
sshTunnelGateway.autoGenPrivateKeyPath = "/var/frp/ssh-private-key-file"
|
||||
```
|
||||
|
||||
frps will automatically create a private key file and store it at the specified path.
|
||||
|
||||
Note: Changing the private key file in frps can cause SSH client login failures. If you need to log in successfully, you can delete the old records from the `/home/user/.ssh/known_hosts` file.
|
||||
|
||||
#### Using an Existing authorized_keys File for SSH Public Key Authentication
|
||||
|
||||
```toml
|
||||
# frps.toml
|
||||
sshTunnelGateway.bindPort = 2200
|
||||
sshTunnelGateway.authorizedKeysFile = "/home/user/.ssh/authorized_keys"
|
||||
```
|
||||
|
||||
The authorizedKeysFile is the file used for SSH public key authentication, which contains the public key information for users, with one key per line.
|
||||
|
||||
If authorizedKeysFile is empty, frps won't perform any authentication for SSH clients. Frps does not support SSH username and password authentication.
|
||||
|
||||
You can reuse an existing `authorized_keys` file on your local machine for client authentication.
|
||||
|
||||
Note: authorizedKeysFile is for user authentication during the SSH login phase, while the token is for frps authentication. These two authentication methods are independent. SSH authentication comes first, followed by frps token authentication. It is strongly recommended to enable at least one of them. If authorizedKeysFile is empty, it is highly recommended to enable token authentication in frps to avoid security risks.
|
||||
|
||||
#### Using a Custom authorized_keys File for SSH Public Key Authentication
|
||||
|
||||
```toml
|
||||
# frps.toml
|
||||
sshTunnelGateway.bindPort = 2200
|
||||
sshTunnelGateway.authorizedKeysFile = "/var/frps/custom_authorized_keys_file"
|
||||
```
|
||||
|
||||
Specify the path to a custom `authorized_keys` file.
|
||||
|
||||
Note that changes to the authorizedKeysFile file may result in SSH authentication failures. You may need to re-add the public key information to the authorizedKeysFile.
|
14
go.mod
14
go.mod
@@ -21,11 +21,9 @@ require (
|
||||
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/spf13/cobra v1.8.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/time v0.3.0
|
||||
@@ -39,7 +37,7 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
@@ -62,13 +60,15 @@ require (
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
|
27
go.sum
27
go.sum
@@ -16,7 +16,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o=
|
||||
github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -32,8 +32,8 @@ github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
|
||||
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
|
||||
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
@@ -128,8 +128,8 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -157,8 +157,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
@@ -183,8 +183,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
@@ -210,21 +210,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@@ -26,9 +26,5 @@ frpsPath=${ROOT}/bin/frps
|
||||
if [ "${FRPS_PATH}" ]; then
|
||||
frpsPath="${FRPS_PATH}"
|
||||
fi
|
||||
concurrency="16"
|
||||
if [ "${CONCURRENCY}" ]; then
|
||||
concurrency="${CONCURRENCY}"
|
||||
fi
|
||||
|
||||
ginkgo -nodes=${concurrency} --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}
|
||||
ginkgo -nodes=8 --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}
|
||||
|
@@ -46,8 +46,8 @@ for os in $os_all; do
|
||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||
fi
|
||||
cp ../LICENSE ${frp_path}
|
||||
cp -f ../conf/frpc.toml ${frp_path}
|
||||
cp -f ../conf/frps.toml ${frp_path}
|
||||
cp -rf ../conf/* ${frp_path}
|
||||
rm -rf ${frp_path}/legacy
|
||||
|
||||
# packages
|
||||
cd ./packages
|
||||
|
@@ -1,31 +0,0 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
)
|
||||
|
||||
var AlwaysPassVerifier = &alwaysPass{}
|
||||
|
||||
var _ Verifier = &alwaysPass{}
|
||||
|
||||
type alwaysPass struct{}
|
||||
|
||||
func (*alwaysPass) VerifyLogin(*msg.Login) error { return nil }
|
||||
|
||||
func (*alwaysPass) VerifyPing(*msg.Ping) error { return nil }
|
||||
|
||||
func (*alwaysPass) VerifyNewWorkConn(*msg.NewWorkConn) error { return nil }
|
@@ -1,254 +0,0 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
)
|
||||
|
||||
// WordSepNormalizeFunc changes all flags that contain "_" separators
|
||||
func WordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
if strings.Contains(name, "_") {
|
||||
return pflag.NormalizedName(strings.ReplaceAll(name, "_", "-"))
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
}
|
||||
|
||||
type RegisterFlagOption func(*registerFlagOptions)
|
||||
|
||||
type registerFlagOptions struct {
|
||||
sshMode bool
|
||||
}
|
||||
|
||||
func WithSSHMode() RegisterFlagOption {
|
||||
return func(o *registerFlagOptions) {
|
||||
o.sshMode = true
|
||||
}
|
||||
}
|
||||
|
||||
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, opts ...RegisterFlagOption) {
|
||||
registerProxyBaseConfigFlags(cmd, c.GetBaseConfig(), opts...)
|
||||
|
||||
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")
|
||||
cmd.Flags().StringVarP(&cc.HTTPUser, "http_user", "", "", "http auth user")
|
||||
cmd.Flags().StringVarP(&cc.HTTPPassword, "http_pwd", "", "", "http auth password")
|
||||
case *v1.STCPProxyConfig:
|
||||
cmd.Flags().StringVarP(&cc.Secretkey, "sk", "", "", "secret key")
|
||||
cmd.Flags().StringSliceVarP(&cc.AllowUsers, "allow_users", "", []string{}, "allow visitor users")
|
||||
case *v1.SUDPProxyConfig:
|
||||
cmd.Flags().StringVarP(&cc.Secretkey, "sk", "", "", "secret key")
|
||||
cmd.Flags().StringSliceVarP(&cc.AllowUsers, "allow_users", "", []string{}, "allow visitor users")
|
||||
case *v1.XTCPProxyConfig:
|
||||
cmd.Flags().StringVarP(&cc.Secretkey, "sk", "", "", "secret key")
|
||||
cmd.Flags().StringSliceVarP(&cc.AllowUsers, "allow_users", "", []string{}, "allow visitor users")
|
||||
}
|
||||
}
|
||||
|
||||
func registerProxyBaseConfigFlags(cmd *cobra.Command, c *v1.ProxyBaseConfig, opts ...RegisterFlagOption) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
options := ®isterFlagOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&c.Name, "proxy_name", "n", "", "proxy name")
|
||||
|
||||
if !options.sshMode {
|
||||
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, opts ...RegisterFlagOption) {
|
||||
registerVisitorBaseConfigFlags(cmd, c.GetBaseConfig(), opts...)
|
||||
|
||||
// add visitor flags if exist
|
||||
}
|
||||
|
||||
func registerVisitorBaseConfigFlags(cmd *cobra.Command, c *v1.VisitorBaseConfig, _ ...RegisterFlagOption) {
|
||||
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, opts ...RegisterFlagOption) {
|
||||
options := ®isterFlagOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
if !options.sshMode {
|
||||
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.Transport.Protocol, "protocol", "p", "tcp",
|
||||
fmt.Sprintf("optional values are %v", validation.SupportedTransportProtocols))
|
||||
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")
|
||||
}
|
||||
cmd.PersistentFlags().StringVarP(&c.User, "user", "u", "", "user")
|
||||
cmd.PersistentFlags().StringVarP(&c.Auth.Token, "token", "t", "", "auth token")
|
||||
}
|
||||
|
||||
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, opts ...RegisterFlagOption) {
|
||||
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")
|
||||
}
|
@@ -99,7 +99,7 @@ type ClientCommonConf struct {
|
||||
// the server must have TCP multiplexing enabled as well. By default, this
|
||||
// value is true.
|
||||
TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
|
||||
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multiplier.
|
||||
// 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 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"`
|
||||
// User specifies a prefix for proxy names to distinguish them from other
|
||||
|
@@ -71,7 +71,6 @@ func Convert_ClientCommonConf_To_v1(conf *ClientCommonConf) *v1.ClientCommonConf
|
||||
|
||||
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
|
||||
@@ -307,7 +306,6 @@ func Convert_ProxyConf_To_v1(conf ProxyConf) v1.ProxyConfigurer {
|
||||
c := &v1.XTCPProxyConfig{ProxyBaseConfig: *outBase}
|
||||
c.Secretkey = v.Sk
|
||||
c.AllowUsers = v.AllowUsers
|
||||
out = c
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
func ParseClientConfig(filePath string) (
|
||||
cfg ClientCommonConf,
|
||||
proxyCfgs map[string]ProxyConf,
|
||||
pxyCfgs map[string]ProxyConf,
|
||||
visitorCfgs map[string]VisitorConf,
|
||||
err error,
|
||||
) {
|
||||
@@ -56,7 +56,7 @@ func ParseClientConfig(filePath string) (
|
||||
configBuffer.Write(buf)
|
||||
|
||||
// Parse all proxy and visitor configs.
|
||||
proxyCfgs, visitorCfgs, err = LoadAllProxyConfsFromIni(cfg.User, configBuffer.Bytes(), cfg.Start)
|
||||
pxyCfgs, visitorCfgs, err = LoadAllProxyConfsFromIni(cfg.User, configBuffer.Bytes(), cfg.Start)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ type HTTPPluginOptions struct {
|
||||
Addr string `ini:"addr"`
|
||||
Path string `ini:"path"`
|
||||
Ops []string `ini:"ops"`
|
||||
TLSVerify bool `ini:"tlsVerify"`
|
||||
TLSVerify bool `ini:"tls_verify"`
|
||||
}
|
||||
|
||||
// ServerCommonConf contains information for a server service. It is
|
||||
@@ -139,7 +139,7 @@ type ServerCommonConf struct {
|
||||
// from a client to share a single TCP connection. By default, this value
|
||||
// is true.
|
||||
TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
|
||||
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multiplier.
|
||||
// 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 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"`
|
||||
// TCPKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
|
@@ -100,42 +100,26 @@ func LoadFileContentWithTemplate(path string, values *Values) ([]byte, error) {
|
||||
return RenderWithTemplate(b, values)
|
||||
}
|
||||
|
||||
func LoadConfigureFromFile(path string, c any, strict bool) error {
|
||||
func LoadConfigureFromFile(path string, c any) error {
|
||||
content, err := LoadFileContentWithTemplate(path, GetValues())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return LoadConfigure(content, c, strict)
|
||||
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, strict bool) error {
|
||||
v1.DisallowUnknownFieldsMu.Lock()
|
||||
defer v1.DisallowUnknownFieldsMu.Unlock()
|
||||
v1.DisallowUnknownFields = strict
|
||||
|
||||
func LoadConfigure(b []byte, c any) error {
|
||||
var tomlObj interface{}
|
||||
// Try to unmarshal as TOML first; swallow errors from that (assume it's not valid TOML).
|
||||
if err := toml.Unmarshal(b, &tomlObj); err == nil {
|
||||
b, err = json.Marshal(&tomlObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// If the buffer smells like JSON (first non-whitespace character is '{'), unmarshal as JSON directly.
|
||||
if yaml.IsJSONBuffer(b) {
|
||||
decoder := json.NewDecoder(bytes.NewBuffer(b))
|
||||
if strict {
|
||||
decoder.DisallowUnknownFields()
|
||||
}
|
||||
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(b), 4096)
|
||||
return decoder.Decode(c)
|
||||
}
|
||||
// It wasn't JSON. Unmarshal as YAML.
|
||||
if strict {
|
||||
return yaml.UnmarshalStrict(b, c)
|
||||
}
|
||||
return yaml.Unmarshal(b, c)
|
||||
}
|
||||
|
||||
func NewProxyConfigurerFromMsg(m *msg.NewProxy, serverCfg *v1.ServerConfig) (v1.ProxyConfigurer, error) {
|
||||
@@ -155,7 +139,7 @@ func NewProxyConfigurerFromMsg(m *msg.NewProxy, serverCfg *v1.ServerConfig) (v1.
|
||||
return configurer, nil
|
||||
}
|
||||
|
||||
func LoadServerConfig(path string, strict bool) (*v1.ServerConfig, bool, error) {
|
||||
func LoadServerConfig(path string) (*v1.ServerConfig, bool, error) {
|
||||
var (
|
||||
svrCfg *v1.ServerConfig
|
||||
isLegacyFormat bool
|
||||
@@ -174,7 +158,7 @@ func LoadServerConfig(path string, strict bool) (*v1.ServerConfig, bool, error)
|
||||
isLegacyFormat = true
|
||||
} else {
|
||||
svrCfg = &v1.ServerConfig{}
|
||||
if err := LoadConfigureFromFile(path, svrCfg, strict); err != nil {
|
||||
if err := LoadConfigureFromFile(path, svrCfg); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
@@ -184,7 +168,7 @@ func LoadServerConfig(path string, strict bool) (*v1.ServerConfig, bool, error)
|
||||
return svrCfg, isLegacyFormat, nil
|
||||
}
|
||||
|
||||
func LoadClientConfig(path string, strict bool) (
|
||||
func LoadClientConfig(path string) (
|
||||
*v1.ClientCommonConfig,
|
||||
[]v1.ProxyConfigurer,
|
||||
[]v1.VisitorConfigurer,
|
||||
@@ -192,19 +176,19 @@ func LoadClientConfig(path string, strict bool) (
|
||||
) {
|
||||
var (
|
||||
cliCfg *v1.ClientCommonConfig
|
||||
proxyCfgs = make([]v1.ProxyConfigurer, 0)
|
||||
pxyCfgs = make([]v1.ProxyConfigurer, 0)
|
||||
visitorCfgs = make([]v1.VisitorConfigurer, 0)
|
||||
isLegacyFormat bool
|
||||
)
|
||||
|
||||
if DetectLegacyINIFormatFromFile(path) {
|
||||
legacyCommon, legacyProxyCfgs, legacyVisitorCfgs, err := legacy.ParseClientConfig(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 legacyProxyCfgs {
|
||||
proxyCfgs = append(proxyCfgs, legacy.Convert_ProxyConf_To_v1(c))
|
||||
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))
|
||||
@@ -212,12 +196,12 @@ func LoadClientConfig(path string, strict bool) (
|
||||
isLegacyFormat = true
|
||||
} else {
|
||||
allCfg := v1.ClientConfig{}
|
||||
if err := LoadConfigureFromFile(path, &allCfg, strict); err != nil {
|
||||
if err := LoadConfigureFromFile(path, &allCfg); err != nil {
|
||||
return nil, nil, nil, false, err
|
||||
}
|
||||
cliCfg = &allCfg.ClientCommonConfig
|
||||
for _, c := range allCfg.Proxies {
|
||||
proxyCfgs = append(proxyCfgs, c.ProxyConfigurer)
|
||||
pxyCfgs = append(pxyCfgs, c.ProxyConfigurer)
|
||||
}
|
||||
for _, c := range allCfg.Visitors {
|
||||
visitorCfgs = append(visitorCfgs, c.VisitorConfigurer)
|
||||
@@ -225,20 +209,20 @@ func LoadClientConfig(path string, strict bool) (
|
||||
}
|
||||
|
||||
// Load additional config from includes.
|
||||
// legacy ini format already handle this in ParseClientConfig.
|
||||
// legacy ini format alredy handle this in ParseClientConfig.
|
||||
if len(cliCfg.IncludeConfigFiles) > 0 && !isLegacyFormat {
|
||||
extProxyCfgs, extVisitorCfgs, err := LoadAdditionalClientConfigs(cliCfg.IncludeConfigFiles, isLegacyFormat, strict)
|
||||
extPxyCfgs, extVisitorCfgs, err := LoadAdditionalClientConfigs(cliCfg.IncludeConfigFiles, isLegacyFormat)
|
||||
if err != nil {
|
||||
return nil, nil, nil, isLegacyFormat, err
|
||||
}
|
||||
proxyCfgs = append(proxyCfgs, extProxyCfgs...)
|
||||
pxyCfgs = append(pxyCfgs, extPxyCfgs...)
|
||||
visitorCfgs = append(visitorCfgs, extVisitorCfgs...)
|
||||
}
|
||||
|
||||
// Filter by start
|
||||
if len(cliCfg.Start) > 0 {
|
||||
startSet := sets.New(cliCfg.Start...)
|
||||
proxyCfgs = lo.Filter(proxyCfgs, func(c v1.ProxyConfigurer, _ int) bool {
|
||||
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 {
|
||||
@@ -249,17 +233,17 @@ func LoadClientConfig(path string, strict bool) (
|
||||
if cliCfg != nil {
|
||||
cliCfg.Complete()
|
||||
}
|
||||
for _, c := range proxyCfgs {
|
||||
for _, c := range pxyCfgs {
|
||||
c.Complete(cliCfg.User)
|
||||
}
|
||||
for _, c := range visitorCfgs {
|
||||
c.Complete(cliCfg)
|
||||
}
|
||||
return cliCfg, proxyCfgs, visitorCfgs, isLegacyFormat, nil
|
||||
return cliCfg, pxyCfgs, visitorCfgs, isLegacyFormat, nil
|
||||
}
|
||||
|
||||
func LoadAdditionalClientConfigs(paths []string, isLegacyFormat bool, strict bool) ([]v1.ProxyConfigurer, []v1.VisitorConfigurer, error) {
|
||||
proxyCfgs := make([]v1.ProxyConfigurer, 0)
|
||||
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))
|
||||
@@ -281,11 +265,11 @@ func LoadAdditionalClientConfigs(paths []string, isLegacyFormat bool, strict boo
|
||||
if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched {
|
||||
// support yaml/json/toml
|
||||
cfg := v1.ClientConfig{}
|
||||
if err := LoadConfigureFromFile(absFile, &cfg, strict); err != nil {
|
||||
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 {
|
||||
proxyCfgs = append(proxyCfgs, c.ProxyConfigurer)
|
||||
pxyCfgs = append(pxyCfgs, c.ProxyConfigurer)
|
||||
}
|
||||
for _, c := range cfg.Visitors {
|
||||
visitorCfgs = append(visitorCfgs, c.VisitorConfigurer)
|
||||
@@ -293,5 +277,5 @@ func LoadAdditionalClientConfigs(paths []string, isLegacyFormat bool, strict boo
|
||||
}
|
||||
}
|
||||
}
|
||||
return proxyCfgs, visitorCfgs, nil
|
||||
return pxyCfgs, visitorCfgs, nil
|
||||
}
|
||||
|
@@ -15,8 +15,6 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -24,7 +22,9 @@ import (
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
const tomlServerContent = `
|
||||
func TestLoadConfigure(t *testing.T) {
|
||||
require := require.New(t)
|
||||
content := `
|
||||
bindAddr = "127.0.0.1"
|
||||
kcpBindPort = 7000
|
||||
quicBindPort = 7001
|
||||
@@ -33,43 +33,8 @@ custom404Page = "/abc.html"
|
||||
transport.tcpKeepalive = 10
|
||||
`
|
||||
|
||||
const yamlServerContent = `
|
||||
bindAddr: 127.0.0.1
|
||||
kcpBindPort: 7000
|
||||
quicBindPort: 7001
|
||||
tcpmuxHTTPConnectPort: 7005
|
||||
custom404Page: /abc.html
|
||||
transport:
|
||||
tcpKeepalive: 10
|
||||
`
|
||||
|
||||
const jsonServerContent = `
|
||||
{
|
||||
"bindAddr": "127.0.0.1",
|
||||
"kcpBindPort": 7000,
|
||||
"quicBindPort": 7001,
|
||||
"tcpmuxHTTPConnectPort": 7005,
|
||||
"custom404Page": "/abc.html",
|
||||
"transport": {
|
||||
"tcpKeepalive": 10
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func TestLoadServerConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
}{
|
||||
{"toml", tomlServerContent},
|
||||
{"yaml", yamlServerContent},
|
||||
{"json", jsonServerContent},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
svrCfg := v1.ServerConfig{}
|
||||
err := LoadConfigure([]byte(test.content), &svrCfg, true)
|
||||
err := LoadConfigure([]byte(content), &svrCfg)
|
||||
require.NoError(err)
|
||||
require.EqualValues("127.0.0.1", svrCfg.BindAddr)
|
||||
require.EqualValues(7000, svrCfg.KCPBindPort)
|
||||
@@ -77,90 +42,4 @@ func TestLoadServerConfig(t *testing.T) {
|
||||
require.EqualValues(7005, svrCfg.TCPMuxHTTPConnectPort)
|
||||
require.EqualValues("/abc.html", svrCfg.Custom404Page)
|
||||
require.EqualValues(10, svrCfg.Transport.TCPKeepAlive)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test that loading in strict mode fails when the config is invalid.
|
||||
func TestLoadServerConfigStrictMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
}{
|
||||
{"toml", tomlServerContent},
|
||||
{"yaml", yamlServerContent},
|
||||
{"json", jsonServerContent},
|
||||
}
|
||||
|
||||
for _, strict := range []bool{false, true} {
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%s-strict-%t", test.name, strict), func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
// Break the content with an innocent typo
|
||||
brokenContent := strings.Replace(test.content, "bindAddr", "bindAdur", 1)
|
||||
svrCfg := v1.ServerConfig{}
|
||||
err := LoadConfigure([]byte(brokenContent), &svrCfg, strict)
|
||||
if strict {
|
||||
require.ErrorContains(err, "bindAdur")
|
||||
} else {
|
||||
require.NoError(err)
|
||||
// BindAddr didn't get parsed because of the typo.
|
||||
require.EqualValues("", svrCfg.BindAddr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomStructStrictMode(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
proxyStr := `
|
||||
serverPort = 7000
|
||||
|
||||
[[proxies]]
|
||||
name = "test"
|
||||
type = "tcp"
|
||||
remotePort = 6000
|
||||
`
|
||||
clientCfg := v1.ClientConfig{}
|
||||
err := LoadConfigure([]byte(proxyStr), &clientCfg, true)
|
||||
require.NoError(err)
|
||||
|
||||
proxyStr += `unknown = "unknown"`
|
||||
err = LoadConfigure([]byte(proxyStr), &clientCfg, true)
|
||||
require.Error(err)
|
||||
|
||||
visitorStr := `
|
||||
serverPort = 7000
|
||||
|
||||
[[visitors]]
|
||||
name = "test"
|
||||
type = "stcp"
|
||||
bindPort = 6000
|
||||
serverName = "server"
|
||||
`
|
||||
err = LoadConfigure([]byte(visitorStr), &clientCfg, true)
|
||||
require.NoError(err)
|
||||
|
||||
visitorStr += `unknown = "unknown"`
|
||||
err = LoadConfigure([]byte(visitorStr), &clientCfg, true)
|
||||
require.Error(err)
|
||||
|
||||
pluginStr := `
|
||||
serverPort = 7000
|
||||
|
||||
[[proxies]]
|
||||
name = "test"
|
||||
type = "tcp"
|
||||
remotePort = 6000
|
||||
[proxies.plugin]
|
||||
type = "unix_domain_socket"
|
||||
unixPath = "/tmp/uds.sock"
|
||||
`
|
||||
err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
|
||||
require.NoError(err)
|
||||
pluginStr += `unknown = "unknown"`
|
||||
err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
|
||||
require.Error(err)
|
||||
}
|
||||
|
@@ -76,7 +76,6 @@ 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()
|
||||
@@ -111,7 +110,7 @@ type ClientTransportConfig struct {
|
||||
// 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 multiplier.
|
||||
// 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.
|
||||
|
@@ -31,5 +31,4 @@ func TestClientConfigComplete(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
@@ -15,23 +15,9 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
// TODO(fatedier): Due to the current implementation issue of the go json library, the UnmarshalJSON method
|
||||
// of a custom struct cannot access the DisallowUnknownFields parameter of the parent decoder.
|
||||
// Here, a global variable is temporarily used to control whether unknown fields are allowed.
|
||||
// Once the v2 version is implemented by the community, we can switch to a standardized approach.
|
||||
//
|
||||
// https://github.com/golang/go/issues/41144
|
||||
// https://github.com/golang/go/discussions/63397
|
||||
var (
|
||||
DisallowUnknownFields = false
|
||||
DisallowUnknownFieldsMu sync.Mutex
|
||||
)
|
||||
|
||||
type AuthScope string
|
||||
|
||||
const (
|
||||
@@ -97,7 +83,7 @@ type TLSConfig struct {
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
// This is destination where frp should write the logs.
|
||||
// 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".
|
||||
@@ -123,7 +109,7 @@ type HTTPPluginOptions struct {
|
||||
Addr string `json:"addr"`
|
||||
Path string `json:"path"`
|
||||
Ops []string `json:"ops"`
|
||||
TLSVerify bool `json:"tlsVerify,omitempty"`
|
||||
TLSVerify bool `json:"tls_verify,omitempty"`
|
||||
}
|
||||
|
||||
type HeaderOperations struct {
|
||||
|
@@ -15,8 +15,8 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
@@ -30,7 +30,7 @@ type TypedClientPluginOptions struct {
|
||||
|
||||
func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 4 && string(b) == "null" {
|
||||
return nil
|
||||
return errors.New("type is required")
|
||||
}
|
||||
|
||||
typeStruct := struct {
|
||||
@@ -41,22 +41,13 @@ func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewBuffer(b))
|
||||
if DisallowUnknownFields {
|
||||
decoder.DisallowUnknownFields()
|
||||
}
|
||||
|
||||
if err := decoder.Decode(options); err != nil {
|
||||
if err := json.Unmarshal(b, options); err != nil {
|
||||
return err
|
||||
}
|
||||
c.ClientPluginOptions = options
|
||||
@@ -84,20 +75,17 @@ var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
||||
}
|
||||
|
||||
type HTTP2HTTPSPluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
LocalAddr string `json:"localAddr,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPProxyPluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
HTTPUser string `json:"httpUser,omitempty"`
|
||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPS2HTTPPluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
LocalAddr string `json:"localAddr,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
@@ -106,7 +94,6 @@ type HTTPS2HTTPPluginOptions struct {
|
||||
}
|
||||
|
||||
type HTTPS2HTTPSPluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
LocalAddr string `json:"localAddr,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||
@@ -115,13 +102,11 @@ type HTTPS2HTTPSPluginOptions struct {
|
||||
}
|
||||
|
||||
type Socks5PluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type StaticFilePluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
LocalPath string `json:"localPath,omitempty"`
|
||||
StripPrefix string `json:"stripPrefix,omitempty"`
|
||||
HTTPUser string `json:"httpUser,omitempty"`
|
||||
@@ -129,6 +114,5 @@ type StaticFilePluginOptions struct {
|
||||
}
|
||||
|
||||
type UnixDomainSocketPluginOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
UnixPath string `json:"unixPath,omitempty"`
|
||||
}
|
||||
|
@@ -15,7 +15,6 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -178,11 +177,7 @@ func (c *TypedProxyConfig) UnmarshalJSON(b []byte) error {
|
||||
if configurer == nil {
|
||||
return fmt.Errorf("unknown proxy type: %s", typeStruct.Type)
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewBuffer(b))
|
||||
if DisallowUnknownFields {
|
||||
decoder.DisallowUnknownFields()
|
||||
}
|
||||
if err := decoder.Decode(configurer); err != nil {
|
||||
if err := json.Unmarshal(b, configurer); err != nil {
|
||||
return err
|
||||
}
|
||||
c.ProxyConfigurer = configurer
|
||||
@@ -195,7 +190,7 @@ type ProxyConfigurer interface {
|
||||
// MarshalToMsg marshals this config into a msg.NewProxy message. This
|
||||
// function will be called on the frpc side.
|
||||
MarshalToMsg(*msg.NewProxy)
|
||||
// UnmarshalFromMsg unmarshal a msg.NewProxy message into this config.
|
||||
// UnmarshalFromMsg unmarshals a msg.NewProxy message into this config.
|
||||
// This function will be called on the frps side.
|
||||
UnmarshalFromMsg(*msg.NewProxy)
|
||||
}
|
||||
@@ -229,9 +224,7 @@ func NewProxyConfigurerByType(proxyType ProxyType) ProxyConfigurer {
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
pc := reflect.New(v).Interface().(ProxyConfigurer)
|
||||
pc.GetBaseConfig().Type = string(proxyType)
|
||||
return pc
|
||||
return reflect.New(v).Interface().(ProxyConfigurer)
|
||||
}
|
||||
|
||||
var _ ProxyConfigurer = &TCPProxyConfig{}
|
||||
|
@@ -67,8 +67,6 @@ type ServerConfig struct {
|
||||
// value is "", a default page will be displayed.
|
||||
Custom404Page string `json:"custom404Page,omitempty"`
|
||||
|
||||
SSHTunnelGateway SSHTunnelGateway `json:"sshTunnelGateway,omitempty"`
|
||||
|
||||
WebServer WebServerConfig `json:"webServer,omitempty"`
|
||||
// EnablePrometheus will export prometheus metrics on webserver address
|
||||
// in /metrics api.
|
||||
@@ -103,7 +101,6 @@ func (c *ServerConfig) Complete() {
|
||||
c.Log.Complete()
|
||||
c.Transport.Complete()
|
||||
c.WebServer.Complete()
|
||||
c.SSHTunnelGateway.Complete()
|
||||
|
||||
c.BindAddr = util.EmptyOr(c.BindAddr, "0.0.0.0")
|
||||
c.BindPort = util.EmptyOr(c.BindPort, 7000)
|
||||
@@ -153,9 +150,8 @@ 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 multiplier.
|
||||
// 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.
|
||||
@@ -192,14 +188,3 @@ type TLSServerConfig struct {
|
||||
|
||||
TLSConfig
|
||||
}
|
||||
|
||||
type SSHTunnelGateway struct {
|
||||
BindPort int `json:"bindPort,omitempty"`
|
||||
PrivateKeyFile string `json:"privateKeyFile,omitempty"`
|
||||
AutoGenPrivateKeyPath string `json:"autoGenPrivateKeyPath,omitempty"`
|
||||
AuthorizedKeysFile string `json:"authorizedKeysFile,omitempty"`
|
||||
}
|
||||
|
||||
func (c *SSHTunnelGateway) Complete() {
|
||||
c.AutoGenPrivateKeyPath = util.EmptyOr(c.AutoGenPrivateKeyPath, "./.autogen_ssh_key")
|
||||
}
|
||||
|
@@ -80,7 +80,7 @@ func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
func ValidateAllClientConfig(c *v1.ClientCommonConfig, proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) (Warning, error) {
|
||||
func ValidateAllClientConfig(c *v1.ClientCommonConfig, pxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) (Warning, error) {
|
||||
var warnings Warning
|
||||
if c != nil {
|
||||
warning, err := ValidateClientCommonConfig(c)
|
||||
@@ -90,7 +90,7 @@ func ValidateAllClientConfig(c *v1.ClientCommonConfig, proxyCfgs []v1.ProxyConfi
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range proxyCfgs {
|
||||
for _, c := range pxyCfgs {
|
||||
if err := ValidateProxyConfigurerForClient(c); err != nil {
|
||||
return warnings, fmt.Errorf("proxy %s: %v", c.GetBaseConfig().Name, err)
|
||||
}
|
||||
|
@@ -15,7 +15,6 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -109,11 +108,7 @@ func (c *TypedVisitorConfig) UnmarshalJSON(b []byte) error {
|
||||
if configurer == nil {
|
||||
return fmt.Errorf("unknown visitor type: %s", typeStruct.Type)
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewBuffer(b))
|
||||
if DisallowUnknownFields {
|
||||
decoder.DisallowUnknownFields()
|
||||
}
|
||||
if err := decoder.Decode(configurer); err != nil {
|
||||
if err := json.Unmarshal(b, configurer); err != nil {
|
||||
return err
|
||||
}
|
||||
c.VisitorConfigurer = configurer
|
||||
@@ -125,9 +120,7 @@ func NewVisitorConfigurerByType(t VisitorType) VisitorConfigurer {
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
vc := reflect.New(v).Interface().(VisitorConfigurer)
|
||||
vc.GetBaseConfig().Type = string(t)
|
||||
return vc
|
||||
return reflect.New(v).Interface().(VisitorConfigurer)
|
||||
}
|
||||
|
||||
var _ VisitorConfigurer = &STCPVisitorConfig{}
|
||||
|
@@ -1,17 +1,3 @@
|
||||
// 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 metrics
|
||||
|
||||
import (
|
||||
|
@@ -1,103 +0,0 @@
|
||||
// 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 msg
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func AsyncHandler(f func(Message)) func(Message) {
|
||||
return func(m Message) {
|
||||
go f(m)
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatcher is used to send messages to net.Conn or register handlers for messages read from net.Conn.
|
||||
type Dispatcher struct {
|
||||
rw io.ReadWriter
|
||||
|
||||
sendCh chan Message
|
||||
doneCh chan struct{}
|
||||
msgHandlers map[reflect.Type]func(Message)
|
||||
defaultHandler func(Message)
|
||||
}
|
||||
|
||||
func NewDispatcher(rw io.ReadWriter) *Dispatcher {
|
||||
return &Dispatcher{
|
||||
rw: rw,
|
||||
sendCh: make(chan Message, 100),
|
||||
doneCh: make(chan struct{}),
|
||||
msgHandlers: make(map[reflect.Type]func(Message)),
|
||||
}
|
||||
}
|
||||
|
||||
// Run will block until io.EOF or some error occurs.
|
||||
func (d *Dispatcher) Run() {
|
||||
go d.sendLoop()
|
||||
go d.readLoop()
|
||||
}
|
||||
|
||||
func (d *Dispatcher) sendLoop() {
|
||||
for {
|
||||
select {
|
||||
case <-d.doneCh:
|
||||
return
|
||||
case m := <-d.sendCh:
|
||||
_ = WriteMsg(d.rw, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) readLoop() {
|
||||
for {
|
||||
m, err := ReadMsg(d.rw)
|
||||
if err != nil {
|
||||
close(d.doneCh)
|
||||
return
|
||||
}
|
||||
|
||||
if handler, ok := d.msgHandlers[reflect.TypeOf(m)]; ok {
|
||||
handler(m)
|
||||
} else if d.defaultHandler != nil {
|
||||
d.defaultHandler(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) Send(m Message) error {
|
||||
select {
|
||||
case <-d.doneCh:
|
||||
return io.EOF
|
||||
case d.sendCh <- m:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) SendChannel() chan Message {
|
||||
return d.sendCh
|
||||
}
|
||||
|
||||
func (d *Dispatcher) RegisterHandler(msg Message, handler func(Message)) {
|
||||
d.msgHandlers[reflect.TypeOf(msg)] = handler
|
||||
}
|
||||
|
||||
func (d *Dispatcher) RegisterDefaultHandler(handler func(Message)) {
|
||||
d.defaultHandler = handler
|
||||
}
|
||||
|
||||
func (d *Dispatcher) Done() chan struct{} {
|
||||
return d.doneCh
|
||||
}
|
@@ -63,15 +63,6 @@ var msgTypeMap = map[byte]interface{}{
|
||||
|
||||
var TypeNameNatHoleResp = reflect.TypeOf(&NatHoleResp{}).Elem().Name()
|
||||
|
||||
type ClientSpec struct {
|
||||
// Due to the support of VirtualClient, frps needs to know the client type in order to
|
||||
// differentiate the processing logic.
|
||||
// Optional values: ssh-tunnel
|
||||
Type string `json:"type,omitempty"`
|
||||
// If the value is true, the client will not require authentication.
|
||||
AlwaysAuthPass bool `json:"always_auth_pass,omitempty"`
|
||||
}
|
||||
|
||||
// When frpc start, client send this message to login to server.
|
||||
type Login struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
@@ -84,9 +75,6 @@ type Login struct {
|
||||
RunID string `json:"run_id,omitempty"`
|
||||
Metas map[string]string `json:"metas,omitempty"`
|
||||
|
||||
// Currently only effective for VirtualClient.
|
||||
ClientSpec ClientSpec `json:"client_spec,omitempty"`
|
||||
|
||||
// Some global configures.
|
||||
PoolCount int `json:"pool_count,omitempty"`
|
||||
}
|
||||
|
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !frps
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
@@ -24,7 +22,7 @@ import (
|
||||
"net/http/httputil"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -79,7 +77,7 @@ func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
}
|
||||
|
||||
func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
_ = p.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
|
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !frps
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
@@ -29,7 +27,7 @@ import (
|
||||
libnet "github.com/fatedier/golib/net"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
@@ -68,7 +66,7 @@ func (hp *HTTPProxy) Name() string {
|
||||
}
|
||||
|
||||
func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
|
||||
sc, rd := libnet.NewSharedConn(wrapConn)
|
||||
firstBytes := make([]byte, 7)
|
||||
|
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !frps
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
@@ -26,7 +24,7 @@ import (
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -98,7 +96,7 @@ func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
_ = p.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
|
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !frps
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
@@ -26,7 +24,7 @@ import (
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -104,7 +102,7 @@ func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) {
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
_ = p.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
|
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !frps
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
@@ -24,7 +22,7 @@ import (
|
||||
gosocks5 "github.com/armon/go-socks5"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -52,7 +50,7 @@ func NewSocks5Plugin(options v1.ClientPluginOptions) (p Plugin, err error) {
|
||||
|
||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||
defer conn.Close()
|
||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
_ = sp.Server.ServeConn(wrapConn)
|
||||
}
|
||||
|
||||
|
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !frps
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
@@ -25,7 +23,7 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -57,8 +55,8 @@ func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
}
|
||||
|
||||
router := mux.NewRouter()
|
||||
router.Use(netpkg.NewHTTPAuthMiddleware(opts.HTTPUser, opts.HTTPPassword).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||
router.PathPrefix(prefix).Handler(netpkg.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(opts.LocalPath))))).Methods("GET")
|
||||
router.Use(utilnet.NewHTTPAuthMiddleware(opts.HTTPUser, opts.HTTPPassword).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||
router.PathPrefix(prefix).Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(opts.LocalPath))))).Methods("GET")
|
||||
sp.s = &http.Server{
|
||||
Handler: router,
|
||||
}
|
||||
@@ -69,7 +67,7 @@ func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||
}
|
||||
|
||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
_ = sp.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
|
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !frps
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
|
@@ -6,12 +6,11 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
@@ -70,16 +69,8 @@ func (c *Client) GetAllProxyStatus() (client.StatusResp, error) {
|
||||
return allStatus, nil
|
||||
}
|
||||
|
||||
func (c *Client) Reload(strictMode bool) error {
|
||||
v := url.Values{}
|
||||
if strictMode {
|
||||
v.Set("strictConfig", "true")
|
||||
}
|
||||
queryStr := ""
|
||||
if len(v) > 0 {
|
||||
queryStr = "?" + v.Encode()
|
||||
}
|
||||
req, err := http.NewRequest("GET", "http://"+c.address+"/api/reload"+queryStr, nil)
|
||||
func (c *Client) Reload() error {
|
||||
req, err := http.NewRequest("GET", "http://"+c.address+"/api/reload", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -115,7 +106,7 @@ func (c *Client) UpdateConfig(content string) error {
|
||||
|
||||
func (c *Client) setAuthHeader(req *http.Request) {
|
||||
if c.authUser != "" || c.authPwd != "" {
|
||||
req.Header.Set("Authorization", httppkg.BasicAuth(c.authUser, c.authPwd))
|
||||
req.Header.Set("Authorization", util.BasicAuth(c.authUser, c.authPwd))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,143 +0,0 @@
|
||||
// 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 ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
type Gateway struct {
|
||||
bindPort int
|
||||
ln net.Listener
|
||||
|
||||
peerServerListener *netpkg.InternalListener
|
||||
|
||||
sshConfig *ssh.ServerConfig
|
||||
}
|
||||
|
||||
func NewGateway(
|
||||
cfg v1.SSHTunnelGateway, bindAddr string,
|
||||
peerServerListener *netpkg.InternalListener,
|
||||
) (*Gateway, error) {
|
||||
sshConfig := &ssh.ServerConfig{}
|
||||
|
||||
// privateKey
|
||||
var (
|
||||
privateKeyBytes []byte
|
||||
err error
|
||||
)
|
||||
if cfg.PrivateKeyFile != "" {
|
||||
privateKeyBytes, err = os.ReadFile(cfg.PrivateKeyFile)
|
||||
} else {
|
||||
if cfg.AutoGenPrivateKeyPath != "" {
|
||||
privateKeyBytes, _ = os.ReadFile(cfg.AutoGenPrivateKeyPath)
|
||||
}
|
||||
if len(privateKeyBytes) == 0 {
|
||||
privateKeyBytes, err = transport.NewRandomPrivateKey()
|
||||
if err == nil && cfg.AutoGenPrivateKeyPath != "" {
|
||||
err = os.WriteFile(cfg.AutoGenPrivateKeyPath, privateKeyBytes, 0o600)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateKey, err := ssh.ParsePrivateKey(privateKeyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshConfig.AddHostKey(privateKey)
|
||||
|
||||
sshConfig.NoClientAuth = cfg.AuthorizedKeysFile == ""
|
||||
sshConfig.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
authorizedKeysMap, err := loadAuthorizedKeysFromFile(cfg.AuthorizedKeysFile)
|
||||
if err != nil {
|
||||
log.Error("load authorized keys file error: %v", err)
|
||||
return nil, fmt.Errorf("internal error")
|
||||
}
|
||||
|
||||
user, ok := authorizedKeysMap[string(key.Marshal())]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown public key for remoteAddr %q", conn.RemoteAddr())
|
||||
}
|
||||
return &ssh.Permissions{
|
||||
Extensions: map[string]string{
|
||||
"user": user,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", net.JoinHostPort(bindAddr, strconv.Itoa(cfg.BindPort)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Gateway{
|
||||
bindPort: cfg.BindPort,
|
||||
ln: ln,
|
||||
peerServerListener: peerServerListener,
|
||||
sshConfig: sshConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *Gateway) Run() {
|
||||
for {
|
||||
conn, err := g.ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go g.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gateway) handleConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
ts, err := NewTunnelServer(conn, g.sshConfig, g.peerServerListener)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := ts.Run(); err != nil {
|
||||
log.Error("ssh tunnel server run error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func loadAuthorizedKeysFromFile(path string) (map[string]string, error) {
|
||||
authorizedKeysMap := make(map[string]string) // value is username
|
||||
authorizedKeysBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for len(authorizedKeysBytes) > 0 {
|
||||
pubKey, comment, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authorizedKeysMap[string(pubKey.Marshal())] = strings.TrimSpace(comment)
|
||||
authorizedKeysBytes = rest
|
||||
}
|
||||
return authorizedKeysMap, nil
|
||||
}
|
@@ -1,385 +0,0 @@
|
||||
// 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 ssh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
libio "github.com/fatedier/golib/io"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
"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/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
"github.com/fatedier/frp/pkg/virtual"
|
||||
)
|
||||
|
||||
const (
|
||||
// https://datatracker.ietf.org/doc/html/rfc4254#page-16
|
||||
ChannelTypeServerOpenChannel = "forwarded-tcpip"
|
||||
RequestTypeForward = "tcpip-forward"
|
||||
)
|
||||
|
||||
type tcpipForward struct {
|
||||
Host string
|
||||
Port uint32
|
||||
}
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc4254#page-16
|
||||
type forwardedTCPPayload struct {
|
||||
Addr string
|
||||
Port uint32
|
||||
|
||||
OriginAddr string
|
||||
OriginPort uint32
|
||||
}
|
||||
|
||||
type TunnelServer struct {
|
||||
underlyingConn net.Conn
|
||||
sshConn *ssh.ServerConn
|
||||
sc *ssh.ServerConfig
|
||||
firstChannel ssh.Channel
|
||||
|
||||
vc *virtual.Client
|
||||
peerServerListener *netpkg.InternalListener
|
||||
doneCh chan struct{}
|
||||
closeDoneChOnce sync.Once
|
||||
}
|
||||
|
||||
func NewTunnelServer(conn net.Conn, sc *ssh.ServerConfig, peerServerListener *netpkg.InternalListener) (*TunnelServer, error) {
|
||||
s := &TunnelServer{
|
||||
underlyingConn: conn,
|
||||
sc: sc,
|
||||
peerServerListener: peerServerListener,
|
||||
doneCh: make(chan struct{}),
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *TunnelServer) Run() error {
|
||||
sshConn, channels, requests, err := ssh.NewServerConn(s.underlyingConn, s.sc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.sshConn = sshConn
|
||||
|
||||
addr, extraPayload, err := s.waitForwardAddrAndExtraPayload(channels, requests, 3*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientCfg, pc, helpMessage, err := s.parseClientAndProxyConfigurer(addr, extraPayload)
|
||||
if err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
s.writeToClient(helpMessage)
|
||||
return nil
|
||||
}
|
||||
s.writeToClient(err.Error())
|
||||
return fmt.Errorf("parse flags from ssh client error: %v", err)
|
||||
}
|
||||
clientCfg.Complete()
|
||||
if sshConn.Permissions != nil {
|
||||
clientCfg.User = util.EmptyOr(sshConn.Permissions.Extensions["user"], clientCfg.User)
|
||||
}
|
||||
pc.Complete(clientCfg.User)
|
||||
|
||||
vc, err := virtual.NewClient(virtual.ClientOptions{
|
||||
Common: clientCfg,
|
||||
Spec: &msg.ClientSpec{
|
||||
Type: "ssh-tunnel",
|
||||
// If ssh does not require authentication, then the virtual client needs to authenticate through a token.
|
||||
// Otherwise, once ssh authentication is passed, the virtual client does not need to authenticate again.
|
||||
AlwaysAuthPass: !s.sc.NoClientAuth,
|
||||
},
|
||||
HandleWorkConnCb: func(base *v1.ProxyBaseConfig, workConn net.Conn, m *msg.StartWorkConn) bool {
|
||||
// join workConn and ssh channel
|
||||
c, err := s.openConn(addr)
|
||||
if err != nil {
|
||||
log.Trace("open conn error: %v", err)
|
||||
workConn.Close()
|
||||
return false
|
||||
}
|
||||
libio.Join(c, workConn)
|
||||
return false
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.vc = vc
|
||||
|
||||
// transfer connection from virtual client to server peer listener
|
||||
go func() {
|
||||
l := s.vc.PeerListener()
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = s.peerServerListener.PutConn(conn)
|
||||
}
|
||||
}()
|
||||
xl := xlog.New().AddPrefix(xlog.LogPrefix{Name: "sshVirtualClient", Value: "sshVirtualClient", Priority: 100})
|
||||
ctx := xlog.NewContext(context.Background(), xl)
|
||||
go func() {
|
||||
vcErr := s.vc.Run(ctx)
|
||||
if vcErr != nil {
|
||||
s.writeToClient(vcErr.Error())
|
||||
}
|
||||
|
||||
// If vc.Run returns, it means that the virtual client has been closed, and the ssh tunnel connection should be closed.
|
||||
// One scenario is that the virtual client exits due to login failure.
|
||||
s.closeDoneChOnce.Do(func() {
|
||||
_ = sshConn.Close()
|
||||
close(s.doneCh)
|
||||
})
|
||||
}()
|
||||
|
||||
s.vc.UpdateProxyConfigurer([]v1.ProxyConfigurer{pc})
|
||||
|
||||
if ps, err := s.waitProxyStatusReady(pc.GetBaseConfig().Name, time.Second); err != nil {
|
||||
s.writeToClient(err.Error())
|
||||
log.Warn("wait proxy status ready error: %v", err)
|
||||
} else {
|
||||
// success
|
||||
s.writeToClient(createSuccessInfo(clientCfg.User, pc, ps))
|
||||
_ = sshConn.Wait()
|
||||
}
|
||||
|
||||
s.vc.Close()
|
||||
log.Trace("ssh tunnel connection from %v closed", sshConn.RemoteAddr())
|
||||
s.closeDoneChOnce.Do(func() {
|
||||
_ = sshConn.Close()
|
||||
close(s.doneCh)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TunnelServer) writeToClient(data string) {
|
||||
if s.firstChannel == nil {
|
||||
return
|
||||
}
|
||||
_, _ = s.firstChannel.Write([]byte(data + "\n"))
|
||||
}
|
||||
|
||||
func (s *TunnelServer) waitForwardAddrAndExtraPayload(
|
||||
channels <-chan ssh.NewChannel,
|
||||
requests <-chan *ssh.Request,
|
||||
timeout time.Duration,
|
||||
) (*tcpipForward, string, error) {
|
||||
addrCh := make(chan *tcpipForward, 1)
|
||||
extraPayloadCh := make(chan string, 1)
|
||||
|
||||
// get forward address
|
||||
go func() {
|
||||
addrGot := false
|
||||
for req := range requests {
|
||||
if req.Type == RequestTypeForward && !addrGot {
|
||||
payload := tcpipForward{}
|
||||
if err := ssh.Unmarshal(req.Payload, &payload); err != nil {
|
||||
return
|
||||
}
|
||||
addrGot = true
|
||||
addrCh <- &payload
|
||||
}
|
||||
if req.WantReply {
|
||||
_ = req.Reply(true, nil)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// get extra payload
|
||||
go func() {
|
||||
for newChannel := range channels {
|
||||
// extraPayload will send to extraPayloadCh
|
||||
go s.handleNewChannel(newChannel, extraPayloadCh)
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
addr *tcpipForward
|
||||
extraPayload string
|
||||
)
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case v := <-addrCh:
|
||||
addr = v
|
||||
case extra := <-extraPayloadCh:
|
||||
extraPayload = extra
|
||||
case <-timer.C:
|
||||
return nil, "", fmt.Errorf("get addr and extra payload timeout")
|
||||
}
|
||||
if addr != nil && extraPayload != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return addr, extraPayload, nil
|
||||
}
|
||||
|
||||
func (s *TunnelServer) parseClientAndProxyConfigurer(_ *tcpipForward, extraPayload string) (*v1.ClientCommonConfig, v1.ProxyConfigurer, string, error) {
|
||||
helpMessage := ""
|
||||
cmd := &cobra.Command{
|
||||
Use: "ssh v0@{address} [command]",
|
||||
Short: "ssh v0@{address} [command]",
|
||||
Run: func(*cobra.Command, []string) {},
|
||||
}
|
||||
cmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc)
|
||||
|
||||
args := strings.Split(extraPayload, " ")
|
||||
if len(args) < 1 {
|
||||
return nil, nil, helpMessage, fmt.Errorf("invalid extra payload")
|
||||
}
|
||||
proxyType := strings.TrimSpace(args[0])
|
||||
supportTypes := []string{"tcp", "http", "https", "tcpmux", "stcp"}
|
||||
if !lo.Contains(supportTypes, proxyType) {
|
||||
return nil, nil, helpMessage, fmt.Errorf("invalid proxy type: %s, support types: %v", proxyType, supportTypes)
|
||||
}
|
||||
pc := v1.NewProxyConfigurerByType(v1.ProxyType(proxyType))
|
||||
if pc == nil {
|
||||
return nil, nil, helpMessage, fmt.Errorf("new proxy configurer error")
|
||||
}
|
||||
config.RegisterProxyFlags(cmd, pc, config.WithSSHMode())
|
||||
|
||||
clientCfg := v1.ClientCommonConfig{}
|
||||
config.RegisterClientCommonConfigFlags(cmd, &clientCfg, config.WithSSHMode())
|
||||
|
||||
cmd.InitDefaultHelpCmd()
|
||||
if err := cmd.ParseFlags(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
helpMessage = cmd.UsageString()
|
||||
}
|
||||
return nil, nil, helpMessage, err
|
||||
}
|
||||
// if name is not set, generate a random one
|
||||
if pc.GetBaseConfig().Name == "" {
|
||||
id, err := util.RandIDWithLen(8)
|
||||
if err != nil {
|
||||
return nil, nil, helpMessage, fmt.Errorf("generate random id error: %v", err)
|
||||
}
|
||||
pc.GetBaseConfig().Name = fmt.Sprintf("sshtunnel-%s-%s", proxyType, id)
|
||||
}
|
||||
return &clientCfg, pc, helpMessage, nil
|
||||
}
|
||||
|
||||
func (s *TunnelServer) handleNewChannel(channel ssh.NewChannel, extraPayloadCh chan string) {
|
||||
ch, reqs, err := channel.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if s.firstChannel == nil {
|
||||
s.firstChannel = ch
|
||||
}
|
||||
go s.keepAlive(ch)
|
||||
|
||||
for req := range reqs {
|
||||
if req.WantReply {
|
||||
_ = req.Reply(true, nil)
|
||||
}
|
||||
if req.Type != "exec" || len(req.Payload) <= 4 {
|
||||
continue
|
||||
}
|
||||
end := 4 + binary.BigEndian.Uint32(req.Payload[:4])
|
||||
if len(req.Payload) < int(end) {
|
||||
continue
|
||||
}
|
||||
extraPayload := string(req.Payload[4:end])
|
||||
select {
|
||||
case extraPayloadCh <- extraPayload:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TunnelServer) keepAlive(ch ssh.Channel) {
|
||||
tk := time.NewTicker(time.Second * 30)
|
||||
defer tk.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tk.C:
|
||||
_, err := ch.SendRequest("heartbeat", false, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case <-s.doneCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TunnelServer) openConn(addr *tcpipForward) (net.Conn, error) {
|
||||
payload := forwardedTCPPayload{
|
||||
Addr: addr.Host,
|
||||
Port: addr.Port,
|
||||
// Note: Here is just for compatibility, not the real source address.
|
||||
OriginAddr: addr.Host,
|
||||
OriginPort: addr.Port,
|
||||
}
|
||||
channel, reqs, err := s.sshConn.OpenChannel(ChannelTypeServerOpenChannel, ssh.Marshal(&payload))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open ssh channel error: %v", err)
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
conn := netpkg.WrapReadWriteCloserToConn(channel, s.underlyingConn)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (s *TunnelServer) waitProxyStatusReady(name string, timeout time.Duration) (*proxy.WorkingStatus, error) {
|
||||
ticker := time.NewTicker(100 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
ps, err := s.vc.Service().GetProxyStatus(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
switch ps.Phase {
|
||||
case proxy.ProxyPhaseRunning:
|
||||
return ps, nil
|
||||
case proxy.ProxyPhaseStartErr, proxy.ProxyPhaseClosed:
|
||||
return ps, errors.New(ps.Err)
|
||||
}
|
||||
case <-timer.C:
|
||||
return nil, fmt.Errorf("wait proxy status ready timeout")
|
||||
case <-s.doneCh:
|
||||
return nil, fmt.Errorf("ssh tunnel server closed")
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
// 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 ssh
|
||||
|
||||
import (
|
||||
"github.com/fatedier/frp/client/proxy"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
)
|
||||
|
||||
func createSuccessInfo(user string, pc v1.ProxyConfigurer, ps *proxy.WorkingStatus) string {
|
||||
base := pc.GetBaseConfig()
|
||||
out := "\n"
|
||||
out += "frp (via SSH) (Ctrl+C to quit)\n\n"
|
||||
out += "User: " + user + "\n"
|
||||
out += "ProxyName: " + base.Name + "\n"
|
||||
out += "Type: " + base.Type + "\n"
|
||||
out += "RemoteAddress: " + ps.RemoteAddr + "\n"
|
||||
return out
|
||||
}
|
@@ -29,9 +29,7 @@ type MessageTransporter interface {
|
||||
// Recv(ctx context.Context, laneKey string, msgType string) (Message, error)
|
||||
// Do will first send msg, then recv msg with the same laneKey and specified msgType.
|
||||
Do(ctx context.Context, req msg.Message, laneKey, recvMsgType string) (msg.Message, error)
|
||||
// Dispatch will dispatch message to related channel registered in Do function by its message type and laneKey.
|
||||
Dispatch(m msg.Message, laneKey string) bool
|
||||
// Same with Dispatch but with specified message type.
|
||||
DispatchWithType(m msg.Message, msgType, laneKey string) bool
|
||||
}
|
||||
|
||||
@@ -46,7 +44,7 @@ type transporterImpl struct {
|
||||
sendCh chan msg.Message
|
||||
|
||||
// First key is message type and second key is lane key.
|
||||
// Dispatch will dispatch message to related channel by its message type
|
||||
// Dispatch will dispatch message to releated channel by its message type
|
||||
// and lane key.
|
||||
registry map[string]map[string]chan msg.Message
|
||||
mu sync.RWMutex
|
||||
|
@@ -128,15 +128,3 @@ func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Conf
|
||||
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func NewRandomPrivateKey() ([]byte, error) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
})
|
||||
return keyPEM, nil
|
||||
}
|
||||
|
@@ -1,126 +0,0 @@
|
||||
// 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 http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultReadTimeout = 60 * time.Second
|
||||
defaultWriteTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
addr string
|
||||
ln net.Listener
|
||||
tlsCfg *tls.Config
|
||||
|
||||
router *mux.Router
|
||||
hs *http.Server
|
||||
|
||||
authMiddleware mux.MiddlewareFunc
|
||||
}
|
||||
|
||||
func NewServer(cfg v1.WebServerConfig) (*Server, error) {
|
||||
assets.Load(cfg.AssetsDir)
|
||||
|
||||
addr := net.JoinHostPort(cfg.Addr, strconv.Itoa(cfg.Port))
|
||||
if addr == ":" {
|
||||
addr = ":http"
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
router := mux.NewRouter()
|
||||
hs := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: router,
|
||||
ReadTimeout: defaultReadTimeout,
|
||||
WriteTimeout: defaultWriteTimeout,
|
||||
}
|
||||
s := &Server{
|
||||
addr: addr,
|
||||
ln: ln,
|
||||
hs: hs,
|
||||
router: router,
|
||||
}
|
||||
if cfg.PprofEnable {
|
||||
s.registerPprofHandlers()
|
||||
}
|
||||
if cfg.TLS != nil {
|
||||
cert, err := tls.LoadX509KeyPair(cfg.TLS.CertFile, cfg.TLS.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.tlsCfg = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
}
|
||||
s.authMiddleware = netpkg.NewHTTPAuthMiddleware(cfg.User, cfg.Password).SetAuthFailDelay(200 * time.Millisecond).Middleware
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) Address() string {
|
||||
return s.addr
|
||||
}
|
||||
|
||||
func (s *Server) Run() error {
|
||||
ln := s.ln
|
||||
if s.tlsCfg != nil {
|
||||
ln = tls.NewListener(ln, s.tlsCfg)
|
||||
}
|
||||
return s.hs.Serve(ln)
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
return s.hs.Close()
|
||||
}
|
||||
|
||||
type RouterRegisterHelper struct {
|
||||
Router *mux.Router
|
||||
AssetsFS http.FileSystem
|
||||
AuthMiddleware mux.MiddlewareFunc
|
||||
}
|
||||
|
||||
func (s *Server) RouteRegister(register func(helper *RouterRegisterHelper)) {
|
||||
register(&RouterRegisterHelper{
|
||||
Router: s.router,
|
||||
AssetsFS: assets.FileSystem,
|
||||
AuthMiddleware: s.authMiddleware,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) registerPprofHandlers() {
|
||||
s.router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
s.router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
s.router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
s.router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
s.router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||
}
|
@@ -22,7 +22,6 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
quic "github.com/quic-go/quic-go"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
@@ -217,18 +216,3 @@ func (conn *wrapQuicStream) Close() error {
|
||||
conn.Stream.CancelRead(0)
|
||||
return conn.Stream.Close()
|
||||
}
|
||||
|
||||
func NewCryptoReadWriter(rw io.ReadWriter, key []byte) (io.ReadWriter, error) {
|
||||
encReader := crypto.NewReader(rw, key)
|
||||
encWriter, err := crypto.NewWriter(rw, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}{
|
||||
Reader: encReader,
|
||||
Writer: encWriter,
|
||||
}, nil
|
||||
}
|
||||
|
@@ -1,33 +0,0 @@
|
||||
// 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 net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
func SetDefaultDNSAddress(dnsAddress string) {
|
||||
if _, _, err := net.SplitHostPort(dnsAddress); err != nil {
|
||||
dnsAddress = net.JoinHostPort(dnsAddress, "53")
|
||||
}
|
||||
// Change default dns server
|
||||
net.DefaultResolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return net.Dial("udp", dnsAddress)
|
||||
},
|
||||
}
|
||||
}
|
@@ -24,21 +24,21 @@ import (
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type HTTPAuthWrapper struct {
|
||||
type HTTPAuthWraper struct {
|
||||
h http.Handler
|
||||
user string
|
||||
passwd string
|
||||
}
|
||||
|
||||
func NewHTTPBasicAuthWrapper(h http.Handler, user, passwd string) http.Handler {
|
||||
return &HTTPAuthWrapper{
|
||||
func NewHTTPBasicAuthWraper(h http.Handler, user, passwd string) http.Handler {
|
||||
return &HTTPAuthWraper{
|
||||
h: h,
|
||||
user: user,
|
||||
passwd: passwd,
|
||||
}
|
||||
}
|
||||
|
||||
func (aw *HTTPAuthWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (aw *HTTPAuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
user, passwd, hasAuth := r.BasicAuth()
|
||||
if (aw.user == "" && aw.passwd == "") || (hasAuth && user == aw.user && passwd == aw.passwd) {
|
||||
aw.h.ServeHTTP(w, r)
|
||||
@@ -83,11 +83,11 @@ func (authMid *HTTPAuthMiddleware) Middleware(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
type HTTPGzipWrapper struct {
|
||||
type HTTPGzipWraper struct {
|
||||
h http.Handler
|
||||
}
|
||||
|
||||
func (gw *HTTPGzipWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (gw *HTTPGzipWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
gw.h.ServeHTTP(w, r)
|
||||
return
|
||||
@@ -100,7 +100,7 @@ func (gw *HTTPGzipWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func MakeHTTPGzipHandler(h http.Handler) http.Handler {
|
||||
return &HTTPGzipWrapper{
|
||||
return &HTTPGzipWraper{
|
||||
h: h,
|
||||
}
|
||||
}
|
||||
|
@@ -52,10 +52,7 @@ func (l *InternalListener) PutConn(conn net.Conn) error {
|
||||
conn.Close()
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("put conn error: listener is closed")
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *InternalListener) Close() error {
|
||||
|
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
libnet "github.com/fatedier/golib/net"
|
||||
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/vhost"
|
||||
)
|
||||
|
||||
@@ -59,10 +59,10 @@ func (muxer *HTTPConnectTCPMuxer) readHTTPConnectRequest(rd io.Reader) (host, ht
|
||||
return
|
||||
}
|
||||
|
||||
host, _ = httppkg.CanonicalHost(req.Host)
|
||||
host, _ = util.CanonicalHost(req.Host)
|
||||
proxyAuth := req.Header.Get("Proxy-Authorization")
|
||||
if proxyAuth != "" {
|
||||
httpUser, httpPwd, _ = httppkg.ParseBasicAuth(proxyAuth)
|
||||
httpUser, httpPwd, _ = util.ParseBasicAuth(proxyAuth)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func (muxer *HTTPConnectTCPMuxer) sendConnectResponse(c net.Conn, _ map[string]s
|
||||
if muxer.passthrough {
|
||||
return nil
|
||||
}
|
||||
res := httppkg.OkResponse()
|
||||
res := util.OkResponse()
|
||||
if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func (muxer *HTTPConnectTCPMuxer) auth(c net.Conn, username, password string, re
|
||||
return true, nil
|
||||
}
|
||||
|
||||
resp := httppkg.ProxyUnauthorizedResponse()
|
||||
resp := util.ProxyUnauthorizedResponse()
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package http
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var version = "0.53.2"
|
||||
var version = "0.52.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
@@ -47,7 +47,7 @@ func TestVersion(t *testing.T) {
|
||||
proto := Proto(Full())
|
||||
major := Major(Full())
|
||||
minor := Minor(Full())
|
||||
parseVersion := fmt.Sprintf("%d.%d.%d", proto, major, minor)
|
||||
parseVerion := fmt.Sprintf("%d.%d.%d", proto, major, minor)
|
||||
version := Full()
|
||||
assert.Equal(parseVersion, version)
|
||||
assert.Equal(parseVerion, version)
|
||||
}
|
||||
|
@@ -31,8 +31,8 @@ import (
|
||||
libio "github.com/fatedier/golib/io"
|
||||
"github.com/fatedier/golib/pool"
|
||||
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
logpkg "github.com/fatedier/frp/pkg/util/log"
|
||||
frpLog "github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
var ErrNoRouteFound = errors.New("no route found")
|
||||
@@ -61,7 +61,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "http"
|
||||
reqRouteInfo := req.Context().Value(RouteInfoKey).(*RequestRouteInfo)
|
||||
oldHost, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
||||
oldHost, _ := util.CanonicalHost(reqRouteInfo.Host)
|
||||
|
||||
rc := rp.GetRouteConfig(oldHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
||||
if rc != nil {
|
||||
@@ -74,7 +74,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
// ignore error here, it will use CreateConnFn instead later
|
||||
endpoint, _ = rc.ChooseEndpointFn()
|
||||
reqRouteInfo.Endpoint = endpoint
|
||||
logpkg.Trace("choose endpoint name [%s] for http request host [%s] path [%s] httpuser [%s]",
|
||||
frpLog.Trace("choose endpoint name [%s] for http request host [%s] path [%s] httpuser [%s]",
|
||||
endpoint, oldHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
||||
}
|
||||
// Set {domain}.{location}.{routeByHTTPUser}.{endpoint} as URL host here to let http transport reuse connections.
|
||||
@@ -116,7 +116,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
BufferPool: newWrapPool(),
|
||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
logpkg.Warn("do http proxy request [host: %s] error: %v", req.Host, err)
|
||||
frpLog.Warn("do http proxy request [host: %s] error: %v", req.Host, err)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
_, _ = rw.Write(getNotFoundPageContent())
|
||||
},
|
||||
@@ -143,7 +143,7 @@ func (rp *HTTPReverseProxy) UnRegister(routeCfg RouteConfig) {
|
||||
func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser string) *RouteConfig {
|
||||
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
|
||||
if ok {
|
||||
logpkg.Debug("get new HTTP request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser)
|
||||
frpLog.Debug("get new HTTP request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser)
|
||||
return vr.payload.(*RouteConfig)
|
||||
}
|
||||
return nil
|
||||
@@ -159,7 +159,7 @@ func (rp *HTTPReverseProxy) GetHeaders(domain, location, routeByHTTPUser string)
|
||||
|
||||
// CreateConnection create a new connection by route config
|
||||
func (rp *HTTPReverseProxy) CreateConnection(reqRouteInfo *RequestRouteInfo, byEndpoint bool) (net.Conn, error) {
|
||||
host, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
||||
host, _ := util.CanonicalHost(reqRouteInfo.Host)
|
||||
vr, ok := rp.getVhost(host, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
||||
if ok {
|
||||
if byEndpoint {
|
||||
@@ -188,7 +188,7 @@ func (rp *HTTPReverseProxy) CheckAuth(domain, location, routeByHTTPUser, user, p
|
||||
return true
|
||||
}
|
||||
|
||||
// getVhost tries to get vhost router by route policy.
|
||||
// getVhost trys to get vhost router by route policy.
|
||||
func (rp *HTTPReverseProxy) getVhost(domain, location, routeByHTTPUser string) (*Router, bool) {
|
||||
findRouter := func(inDomain, inLocation, inRouteByHTTPUser string) (*Router, bool) {
|
||||
vr, ok := rp.vhostRouter.Get(inDomain, inLocation, inRouteByHTTPUser)
|
||||
@@ -303,7 +303,7 @@ func (rp *HTTPReverseProxy) injectRequestInfoToCtx(req *http.Request) *http.Requ
|
||||
}
|
||||
|
||||
func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
domain, _ := httppkg.CanonicalHost(req.Host)
|
||||
domain, _ := util.CanonicalHost(req.Host)
|
||||
location := req.URL.Path
|
||||
user, passwd, _ := req.BasicAuth()
|
||||
if !rp.CheckAuth(domain, location, user, user, passwd) {
|
||||
@@ -333,6 +333,6 @@ type wrapLogger struct{}
|
||||
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
|
||||
|
||||
func (l *wrapLogger) Write(p []byte) (n int, err error) {
|
||||
logpkg.Warn("%s", string(bytes.TrimRight(p, "\n")))
|
||||
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
|
||||
return len(p), nil
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
logpkg "github.com/fatedier/frp/pkg/util/log"
|
||||
frpLog "github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
)
|
||||
|
||||
@@ -58,7 +58,7 @@ func getNotFoundPageContent() []byte {
|
||||
if NotFoundPagePath != "" {
|
||||
buf, err = os.ReadFile(NotFoundPagePath)
|
||||
if err != nil {
|
||||
logpkg.Warn("read custom 404 page error: %v", err)
|
||||
frpLog.Warn("read custom 404 page error: %v", err)
|
||||
buf = []byte(NotFound)
|
||||
}
|
||||
} else {
|
||||
|
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/fatedier/golib/errors"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
)
|
||||
|
||||
@@ -284,7 +284,7 @@ func (l *Listener) Accept() (net.Conn, error) {
|
||||
xl.Debug("rewrite host to [%s] success", l.rewriteHost)
|
||||
conn = sConn
|
||||
}
|
||||
return netpkg.NewContextConn(l.ctx, conn), nil
|
||||
return utilnet.NewContextConn(l.ctx, conn), nil
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
|
@@ -1,194 +0,0 @@
|
||||
// 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 wait
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type BackoffFunc func(previousDuration time.Duration, previousConditionError bool) time.Duration
|
||||
|
||||
func (f BackoffFunc) Backoff(previousDuration time.Duration, previousConditionError bool) time.Duration {
|
||||
return f(previousDuration, previousConditionError)
|
||||
}
|
||||
|
||||
type BackoffManager interface {
|
||||
Backoff(previousDuration time.Duration, previousConditionError bool) time.Duration
|
||||
}
|
||||
|
||||
type FastBackoffOptions struct {
|
||||
Duration time.Duration
|
||||
Factor float64
|
||||
Jitter float64
|
||||
MaxDuration time.Duration
|
||||
InitDurationIfFail time.Duration
|
||||
|
||||
// If FastRetryCount > 0, then within the FastRetryWindow time window,
|
||||
// the retry will be performed with a delay of FastRetryDelay for the first FastRetryCount calls.
|
||||
FastRetryCount int
|
||||
FastRetryDelay time.Duration
|
||||
FastRetryJitter float64
|
||||
FastRetryWindow time.Duration
|
||||
}
|
||||
|
||||
type fastBackoffImpl struct {
|
||||
options FastBackoffOptions
|
||||
|
||||
lastCalledTime time.Time
|
||||
consecutiveErrCount int
|
||||
|
||||
fastRetryCutoffTime time.Time
|
||||
countsInFastRetryWindow int
|
||||
}
|
||||
|
||||
func NewFastBackoffManager(options FastBackoffOptions) BackoffManager {
|
||||
return &fastBackoffImpl{
|
||||
options: options,
|
||||
countsInFastRetryWindow: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fastBackoffImpl) Backoff(previousDuration time.Duration, previousConditionError bool) time.Duration {
|
||||
if f.lastCalledTime.IsZero() {
|
||||
f.lastCalledTime = time.Now()
|
||||
return f.options.Duration
|
||||
}
|
||||
now := time.Now()
|
||||
f.lastCalledTime = now
|
||||
|
||||
if previousConditionError {
|
||||
f.consecutiveErrCount++
|
||||
} else {
|
||||
f.consecutiveErrCount = 0
|
||||
}
|
||||
|
||||
if f.options.FastRetryCount > 0 && previousConditionError {
|
||||
f.countsInFastRetryWindow++
|
||||
if f.countsInFastRetryWindow <= f.options.FastRetryCount {
|
||||
return Jitter(f.options.FastRetryDelay, f.options.FastRetryJitter)
|
||||
}
|
||||
if now.After(f.fastRetryCutoffTime) {
|
||||
// reset
|
||||
f.fastRetryCutoffTime = now.Add(f.options.FastRetryWindow)
|
||||
f.countsInFastRetryWindow = 0
|
||||
}
|
||||
}
|
||||
|
||||
if previousConditionError {
|
||||
var duration time.Duration
|
||||
if f.consecutiveErrCount == 1 {
|
||||
duration = util.EmptyOr(f.options.InitDurationIfFail, previousDuration)
|
||||
} else {
|
||||
duration = previousDuration
|
||||
}
|
||||
|
||||
duration = util.EmptyOr(duration, time.Second)
|
||||
if f.options.Factor != 0 {
|
||||
duration = time.Duration(float64(duration) * f.options.Factor)
|
||||
}
|
||||
if f.options.Jitter > 0 {
|
||||
duration = Jitter(duration, f.options.Jitter)
|
||||
}
|
||||
if f.options.MaxDuration > 0 && duration > f.options.MaxDuration {
|
||||
duration = f.options.MaxDuration
|
||||
}
|
||||
return duration
|
||||
}
|
||||
return f.options.Duration
|
||||
}
|
||||
|
||||
func BackoffUntil(f func() (bool, error), backoff BackoffManager, sliding bool, stopCh <-chan struct{}) {
|
||||
var delay time.Duration
|
||||
previousError := false
|
||||
|
||||
ticker := time.NewTicker(backoff.Backoff(delay, previousError))
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if !sliding {
|
||||
delay = backoff.Backoff(delay, previousError)
|
||||
}
|
||||
|
||||
if done, err := f(); done {
|
||||
return
|
||||
} else if err != nil {
|
||||
previousError = true
|
||||
} else {
|
||||
previousError = false
|
||||
}
|
||||
|
||||
if sliding {
|
||||
delay = backoff.Backoff(delay, previousError)
|
||||
}
|
||||
|
||||
ticker.Reset(delay)
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Jitter returns a time.Duration between duration and duration + maxFactor *
|
||||
// duration.
|
||||
//
|
||||
// This allows clients to avoid converging on periodic behavior. If maxFactor
|
||||
// is 0.0, a suggested default value will be chosen.
|
||||
func Jitter(duration time.Duration, maxFactor float64) time.Duration {
|
||||
if maxFactor <= 0.0 {
|
||||
maxFactor = 1.0
|
||||
}
|
||||
wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration))
|
||||
return wait
|
||||
}
|
||||
|
||||
func Until(f func(), period time.Duration, stopCh <-chan struct{}) {
|
||||
ff := func() (bool, error) {
|
||||
f()
|
||||
return false, nil
|
||||
}
|
||||
BackoffUntil(ff, BackoffFunc(func(time.Duration, bool) time.Duration {
|
||||
return period
|
||||
}), true, stopCh)
|
||||
}
|
||||
|
||||
func MergeAndCloseOnAnyStopChannel[T any](upstreams ...<-chan T) <-chan T {
|
||||
out := make(chan T)
|
||||
closeOnce := sync.Once{}
|
||||
for _, upstream := range upstreams {
|
||||
ch := upstream
|
||||
go func() {
|
||||
select {
|
||||
case <-ch:
|
||||
closeOnce.Do(func() {
|
||||
close(out)
|
||||
})
|
||||
case <-out:
|
||||
}
|
||||
}()
|
||||
}
|
||||
return out
|
||||
}
|
@@ -15,81 +15,40 @@
|
||||
package xlog
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
)
|
||||
|
||||
type LogPrefix struct {
|
||||
// Name is the name of the prefix, it won't be displayed in log but used to identify the prefix.
|
||||
Name string
|
||||
// Value is the value of the prefix, it will be displayed in log.
|
||||
Value string
|
||||
// The prefix with higher priority will be displayed first, default is 10.
|
||||
Priority int
|
||||
}
|
||||
|
||||
// Logger is not thread safety for operations on prefix
|
||||
type Logger struct {
|
||||
prefixes []LogPrefix
|
||||
prefixes []string
|
||||
|
||||
prefixString string
|
||||
}
|
||||
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
prefixes: make([]LogPrefix, 0),
|
||||
prefixes: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) ResetPrefixes() (old []LogPrefix) {
|
||||
func (l *Logger) ResetPrefixes() (old []string) {
|
||||
old = l.prefixes
|
||||
l.prefixes = make([]LogPrefix, 0)
|
||||
l.prefixes = make([]string, 0)
|
||||
l.prefixString = ""
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Logger) AppendPrefix(prefix string) *Logger {
|
||||
return l.AddPrefix(LogPrefix{
|
||||
Name: prefix,
|
||||
Value: prefix,
|
||||
Priority: 10,
|
||||
})
|
||||
}
|
||||
|
||||
func (l *Logger) AddPrefix(prefix LogPrefix) *Logger {
|
||||
found := false
|
||||
if prefix.Priority <= 0 {
|
||||
prefix.Priority = 10
|
||||
}
|
||||
for _, p := range l.prefixes {
|
||||
if p.Name == prefix.Name {
|
||||
found = true
|
||||
p.Value = prefix.Value
|
||||
p.Priority = prefix.Priority
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
l.prefixes = append(l.prefixes, prefix)
|
||||
}
|
||||
l.renderPrefixString()
|
||||
l.prefixString += "[" + prefix + "] "
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Logger) renderPrefixString() {
|
||||
sort.SliceStable(l.prefixes, func(i, j int) bool {
|
||||
return l.prefixes[i].Priority < l.prefixes[j].Priority
|
||||
})
|
||||
l.prefixString = ""
|
||||
for _, v := range l.prefixes {
|
||||
l.prefixString += "[" + v.Value + "] "
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Spawn() *Logger {
|
||||
nl := New()
|
||||
nl.prefixes = append(nl.prefixes, l.prefixes...)
|
||||
nl.renderPrefixString()
|
||||
for _, v := range l.prefixes {
|
||||
nl.AppendPrefix(v)
|
||||
}
|
||||
return nl
|
||||
}
|
||||
|
||||
|
@@ -1,107 +0,0 @@
|
||||
// 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 virtual
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
type ClientOptions struct {
|
||||
Common *v1.ClientCommonConfig
|
||||
Spec *msg.ClientSpec
|
||||
HandleWorkConnCb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
l *netpkg.InternalListener
|
||||
svr *client.Service
|
||||
}
|
||||
|
||||
func NewClient(options ClientOptions) (*Client, error) {
|
||||
if options.Common != nil {
|
||||
options.Common.Complete()
|
||||
}
|
||||
|
||||
ln := netpkg.NewInternalListener()
|
||||
|
||||
serviceOptions := client.ServiceOptions{
|
||||
Common: options.Common,
|
||||
ClientSpec: options.Spec,
|
||||
ConnectorCreator: func(context.Context, *v1.ClientCommonConfig) client.Connector {
|
||||
return &pipeConnector{
|
||||
peerListener: ln,
|
||||
}
|
||||
},
|
||||
HandleWorkConnCb: options.HandleWorkConnCb,
|
||||
}
|
||||
svr, err := client.NewService(serviceOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{
|
||||
l: ln,
|
||||
svr: svr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) PeerListener() net.Listener {
|
||||
return c.l
|
||||
}
|
||||
|
||||
func (c *Client) UpdateProxyConfigurer(proxyCfgs []v1.ProxyConfigurer) {
|
||||
_ = c.svr.UpdateAllConfigurer(proxyCfgs, nil)
|
||||
}
|
||||
|
||||
func (c *Client) Run(ctx context.Context) error {
|
||||
return c.svr.Run(ctx)
|
||||
}
|
||||
|
||||
func (c *Client) Service() *client.Service {
|
||||
return c.svr
|
||||
}
|
||||
|
||||
func (c *Client) Close() {
|
||||
c.svr.Close()
|
||||
c.l.Close()
|
||||
}
|
||||
|
||||
type pipeConnector struct {
|
||||
peerListener *netpkg.InternalListener
|
||||
}
|
||||
|
||||
func (pc *pipeConnector) Open() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pc *pipeConnector) Connect() (net.Conn, error) {
|
||||
c1, c2 := net.Pipe()
|
||||
if err := pc.peerListener.PutConn(c1); err != nil {
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
return nil, err
|
||||
}
|
||||
return c2, nil
|
||||
}
|
||||
|
||||
func (pc *pipeConnector) Close() error {
|
||||
pc.peerListener.Close()
|
||||
return nil
|
||||
}
|
@@ -17,12 +17,15 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/golib/control/shutdown"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
"github.com/fatedier/golib/errors"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/pkg/auth"
|
||||
@@ -32,10 +35,8 @@ import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/pkg/util/wait"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
"github.com/fatedier/frp/server/controller"
|
||||
"github.com/fatedier/frp/server/metrics"
|
||||
@@ -110,16 +111,18 @@ type Control struct {
|
||||
// other components can use this to communicate with client
|
||||
msgTransporter transport.MessageTransporter
|
||||
|
||||
// msgDispatcher is a wrapper for control connection.
|
||||
// It provides a channel for sending messages, and you can register handlers to process messages based on their respective types.
|
||||
msgDispatcher *msg.Dispatcher
|
||||
|
||||
// login message
|
||||
loginMsg *msg.Login
|
||||
|
||||
// control connection
|
||||
conn net.Conn
|
||||
|
||||
// put a message in this channel to send it over control connection to client
|
||||
sendCh chan (msg.Message)
|
||||
|
||||
// read from this channel to get the next message sent by client
|
||||
readCh chan (msg.Message)
|
||||
|
||||
// work connections
|
||||
workConnCh chan net.Conn
|
||||
|
||||
@@ -133,13 +136,20 @@ type Control struct {
|
||||
portsUsedNum int
|
||||
|
||||
// last time got the Ping message
|
||||
lastPing atomic.Value
|
||||
lastPing time.Time
|
||||
|
||||
// A new run id will be generated when a new client login.
|
||||
// If run id got from login message has same run id, it means it's the same client, so we can
|
||||
// replace old controller instantly.
|
||||
runID string
|
||||
|
||||
readerShutdown *shutdown.Shutdown
|
||||
writerShutdown *shutdown.Shutdown
|
||||
managerShutdown *shutdown.Shutdown
|
||||
allShutdown *shutdown.Shutdown
|
||||
|
||||
started bool
|
||||
|
||||
mu sync.RWMutex
|
||||
|
||||
// Server configuration information
|
||||
@@ -147,10 +157,8 @@ type Control struct {
|
||||
|
||||
xl *xlog.Logger
|
||||
ctx context.Context
|
||||
doneCh chan struct{}
|
||||
}
|
||||
|
||||
// TODO(fatedier): Referencing the implementation of frpc, encapsulate the input parameters as SessionContext.
|
||||
func NewControl(
|
||||
ctx context.Context,
|
||||
rc *controller.ResourceController,
|
||||
@@ -158,10 +166,9 @@ func NewControl(
|
||||
pluginManager *plugin.Manager,
|
||||
authVerifier auth.Verifier,
|
||||
ctlConn net.Conn,
|
||||
ctlConnEncrypted bool,
|
||||
loginMsg *msg.Login,
|
||||
serverCfg *v1.ServerConfig,
|
||||
) (*Control, error) {
|
||||
) *Control {
|
||||
poolCount := loginMsg.PoolCount
|
||||
if poolCount > int(serverCfg.Transport.MaxPoolCount) {
|
||||
poolCount = int(serverCfg.Transport.MaxPoolCount)
|
||||
@@ -173,30 +180,24 @@ func NewControl(
|
||||
authVerifier: authVerifier,
|
||||
conn: ctlConn,
|
||||
loginMsg: loginMsg,
|
||||
sendCh: make(chan msg.Message, 10),
|
||||
readCh: make(chan msg.Message, 10),
|
||||
workConnCh: make(chan net.Conn, poolCount+10),
|
||||
proxies: make(map[string]proxy.Proxy),
|
||||
poolCount: poolCount,
|
||||
portsUsedNum: 0,
|
||||
lastPing: time.Now(),
|
||||
runID: loginMsg.RunID,
|
||||
readerShutdown: shutdown.New(),
|
||||
writerShutdown: shutdown.New(),
|
||||
managerShutdown: shutdown.New(),
|
||||
allShutdown: shutdown.New(),
|
||||
serverCfg: serverCfg,
|
||||
xl: xlog.FromContextSafe(ctx),
|
||||
ctx: ctx,
|
||||
doneCh: make(chan struct{}),
|
||||
}
|
||||
ctl.lastPing.Store(time.Now())
|
||||
|
||||
if ctlConnEncrypted {
|
||||
cryptoRW, err := netpkg.NewCryptoReadWriter(ctl.conn, []byte(ctl.serverCfg.Auth.Token))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctl.msgDispatcher = msg.NewDispatcher(cryptoRW)
|
||||
} else {
|
||||
ctl.msgDispatcher = msg.NewDispatcher(ctl.conn)
|
||||
}
|
||||
ctl.registerMsgHandlers()
|
||||
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher.SendChannel())
|
||||
return ctl, nil
|
||||
ctl.msgTransporter = transport.NewMessageTransporter(ctl.sendCh)
|
||||
return ctl
|
||||
}
|
||||
|
||||
// Start send a login success message to client and start working.
|
||||
@@ -207,18 +208,27 @@ func (ctl *Control) Start() {
|
||||
Error: "",
|
||||
}
|
||||
_ = msg.WriteMsg(ctl.conn, loginRespMsg)
|
||||
ctl.mu.Lock()
|
||||
ctl.started = true
|
||||
ctl.mu.Unlock()
|
||||
|
||||
go ctl.writer()
|
||||
go func() {
|
||||
for i := 0; i < ctl.poolCount; i++ {
|
||||
// ignore error here, that means that this control is closed
|
||||
_ = ctl.msgDispatcher.Send(&msg.ReqWorkConn{})
|
||||
_ = errors.PanicToError(func() {
|
||||
ctl.sendCh <- &msg.ReqWorkConn{}
|
||||
})
|
||||
}
|
||||
}()
|
||||
go ctl.worker()
|
||||
|
||||
go ctl.manager()
|
||||
go ctl.reader()
|
||||
go ctl.stoper()
|
||||
}
|
||||
|
||||
func (ctl *Control) Close() error {
|
||||
ctl.conn.Close()
|
||||
ctl.allShutdown.Start()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -226,7 +236,7 @@ func (ctl *Control) Replaced(newCtl *Control) {
|
||||
xl := ctl.xl
|
||||
xl.Info("Replaced by client [%s]", newCtl.runID)
|
||||
ctl.runID = ""
|
||||
ctl.conn.Close()
|
||||
ctl.allShutdown.Start()
|
||||
}
|
||||
|
||||
func (ctl *Control) RegisterWorkConn(conn net.Conn) error {
|
||||
@@ -272,7 +282,9 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||
xl.Debug("get work connection from pool")
|
||||
default:
|
||||
// no work connections available in the poll, send message to frpc to get more
|
||||
if err := ctl.msgDispatcher.Send(&msg.ReqWorkConn{}); err != nil {
|
||||
if err = errors.PanicToError(func() {
|
||||
ctl.sendCh <- &msg.ReqWorkConn{}
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("control is already closed")
|
||||
}
|
||||
|
||||
@@ -292,40 +304,92 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||
}
|
||||
|
||||
// When we get a work connection from pool, replace it with a new one.
|
||||
_ = ctl.msgDispatcher.Send(&msg.ReqWorkConn{})
|
||||
_ = errors.PanicToError(func() {
|
||||
ctl.sendCh <- &msg.ReqWorkConn{}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (ctl *Control) heartbeatWorker() {
|
||||
func (ctl *Control) writer() {
|
||||
xl := ctl.xl
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
xl.Error("panic error: %v", err)
|
||||
xl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
// Don't need application heartbeat if TCPMux is enabled,
|
||||
// yamux will do same thing.
|
||||
// TODO(fatedier): let default HeartbeatTimeout to -1 if TCPMux is enabled. Users can still set it to positive value to enable it.
|
||||
if !lo.FromPtr(ctl.serverCfg.Transport.TCPMux) && ctl.serverCfg.Transport.HeartbeatTimeout > 0 {
|
||||
go wait.Until(func() {
|
||||
if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second {
|
||||
xl.Warn("heartbeat timeout")
|
||||
defer ctl.allShutdown.Start()
|
||||
defer ctl.writerShutdown.Done()
|
||||
|
||||
encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Auth.Token))
|
||||
if err != nil {
|
||||
xl.Error("crypto new writer error: %v", err)
|
||||
ctl.allShutdown.Start()
|
||||
return
|
||||
}
|
||||
for {
|
||||
m, ok := <-ctl.sendCh
|
||||
if !ok {
|
||||
xl.Info("control writer is closing")
|
||||
return
|
||||
}
|
||||
|
||||
if err := msg.WriteMsg(encWriter, m); err != nil {
|
||||
xl.Warn("write message to control connection error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) reader() {
|
||||
xl := ctl.xl
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
xl.Error("panic error: %v", err)
|
||||
xl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
defer ctl.allShutdown.Start()
|
||||
defer ctl.readerShutdown.Done()
|
||||
|
||||
encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Auth.Token))
|
||||
for {
|
||||
m, err := msg.ReadMsg(encReader)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
xl.Debug("control connection closed")
|
||||
return
|
||||
}
|
||||
xl.Warn("read error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
}, time.Second, ctl.doneCh)
|
||||
|
||||
ctl.readCh <- m
|
||||
}
|
||||
}
|
||||
|
||||
// block until Control closed
|
||||
func (ctl *Control) WaitClosed() {
|
||||
<-ctl.doneCh
|
||||
}
|
||||
|
||||
func (ctl *Control) worker() {
|
||||
func (ctl *Control) stoper() {
|
||||
xl := ctl.xl
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
xl.Error("panic error: %v", err)
|
||||
xl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
go ctl.heartbeatWorker()
|
||||
go ctl.msgDispatcher.Run()
|
||||
ctl.allShutdown.WaitStart()
|
||||
|
||||
<-ctl.msgDispatcher.Done()
|
||||
ctl.conn.Close()
|
||||
ctl.readerShutdown.WaitDone()
|
||||
|
||||
close(ctl.readCh)
|
||||
ctl.managerShutdown.WaitDone()
|
||||
|
||||
close(ctl.sendCh)
|
||||
ctl.writerShutdown.WaitDone()
|
||||
|
||||
ctl.mu.Lock()
|
||||
defer ctl.mu.Unlock()
|
||||
@@ -355,104 +419,136 @@ func (ctl *Control) worker() {
|
||||
}()
|
||||
}
|
||||
|
||||
metrics.Server.CloseClient()
|
||||
ctl.allShutdown.Done()
|
||||
xl.Info("client exit success")
|
||||
close(ctl.doneCh)
|
||||
metrics.Server.CloseClient()
|
||||
}
|
||||
|
||||
func (ctl *Control) registerMsgHandlers() {
|
||||
ctl.msgDispatcher.RegisterHandler(&msg.NewProxy{}, ctl.handleNewProxy)
|
||||
ctl.msgDispatcher.RegisterHandler(&msg.Ping{}, ctl.handlePing)
|
||||
ctl.msgDispatcher.RegisterHandler(&msg.NatHoleVisitor{}, msg.AsyncHandler(ctl.handleNatHoleVisitor))
|
||||
ctl.msgDispatcher.RegisterHandler(&msg.NatHoleClient{}, msg.AsyncHandler(ctl.handleNatHoleClient))
|
||||
ctl.msgDispatcher.RegisterHandler(&msg.NatHoleReport{}, msg.AsyncHandler(ctl.handleNatHoleReport))
|
||||
ctl.msgDispatcher.RegisterHandler(&msg.CloseProxy{}, ctl.handleCloseProxy)
|
||||
// block until Control closed
|
||||
func (ctl *Control) WaitClosed() {
|
||||
ctl.mu.RLock()
|
||||
started := ctl.started
|
||||
ctl.mu.RUnlock()
|
||||
|
||||
if !started {
|
||||
ctl.allShutdown.Done()
|
||||
return
|
||||
}
|
||||
ctl.allShutdown.WaitDone()
|
||||
}
|
||||
|
||||
func (ctl *Control) handleNewProxy(m msg.Message) {
|
||||
func (ctl *Control) manager() {
|
||||
xl := ctl.xl
|
||||
inMsg := m.(*msg.NewProxy)
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
xl.Error("panic error: %v", err)
|
||||
xl.Error(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
defer ctl.allShutdown.Start()
|
||||
defer ctl.managerShutdown.Done()
|
||||
|
||||
var heartbeatCh <-chan time.Time
|
||||
// Don't need application heartbeat if TCPMux is enabled,
|
||||
// yamux will do same thing.
|
||||
if !lo.FromPtr(ctl.serverCfg.Transport.TCPMux) && ctl.serverCfg.Transport.HeartbeatTimeout > 0 {
|
||||
heartbeat := time.NewTicker(time.Second)
|
||||
defer heartbeat.Stop()
|
||||
heartbeatCh = heartbeat.C
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-heartbeatCh:
|
||||
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second {
|
||||
xl.Warn("heartbeat timeout")
|
||||
return
|
||||
}
|
||||
case rawMsg, ok := <-ctl.readCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch m := rawMsg.(type) {
|
||||
case *msg.NewProxy:
|
||||
content := &plugin.NewProxyContent{
|
||||
User: plugin.UserInfo{
|
||||
User: ctl.loginMsg.User,
|
||||
Metas: ctl.loginMsg.Metas,
|
||||
RunID: ctl.loginMsg.RunID,
|
||||
},
|
||||
NewProxy: *inMsg,
|
||||
NewProxy: *m,
|
||||
}
|
||||
var remoteAddr string
|
||||
retContent, err := ctl.pluginManager.NewProxy(content)
|
||||
if err == nil {
|
||||
inMsg = &retContent.NewProxy
|
||||
remoteAddr, err = ctl.RegisterProxy(inMsg)
|
||||
m = &retContent.NewProxy
|
||||
remoteAddr, err = ctl.RegisterProxy(m)
|
||||
}
|
||||
|
||||
// register proxy in this control
|
||||
resp := &msg.NewProxyResp{
|
||||
ProxyName: inMsg.ProxyName,
|
||||
ProxyName: m.ProxyName,
|
||||
}
|
||||
if err != nil {
|
||||
xl.Warn("new proxy [%s] type [%s] error: %v", inMsg.ProxyName, inMsg.ProxyType, err)
|
||||
resp.Error = util.GenerateResponseErrorString(fmt.Sprintf("new proxy [%s] error", inMsg.ProxyName),
|
||||
xl.Warn("new proxy [%s] type [%s] error: %v", m.ProxyName, m.ProxyType, err)
|
||||
resp.Error = util.GenerateResponseErrorString(fmt.Sprintf("new proxy [%s] error", m.ProxyName),
|
||||
err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient))
|
||||
} else {
|
||||
resp.RemoteAddr = remoteAddr
|
||||
xl.Info("new proxy [%s] type [%s] success", inMsg.ProxyName, inMsg.ProxyType)
|
||||
metrics.Server.NewProxy(inMsg.ProxyName, inMsg.ProxyType)
|
||||
xl.Info("new proxy [%s] type [%s] success", m.ProxyName, m.ProxyType)
|
||||
metrics.Server.NewProxy(m.ProxyName, m.ProxyType)
|
||||
}
|
||||
_ = ctl.msgDispatcher.Send(resp)
|
||||
}
|
||||
|
||||
func (ctl *Control) handlePing(m msg.Message) {
|
||||
xl := ctl.xl
|
||||
inMsg := m.(*msg.Ping)
|
||||
|
||||
ctl.sendCh <- resp
|
||||
case *msg.NatHoleVisitor:
|
||||
go ctl.HandleNatHoleVisitor(m)
|
||||
case *msg.NatHoleClient:
|
||||
go ctl.HandleNatHoleClient(m)
|
||||
case *msg.NatHoleReport:
|
||||
go ctl.HandleNatHoleReport(m)
|
||||
case *msg.CloseProxy:
|
||||
_ = ctl.CloseProxy(m)
|
||||
xl.Info("close proxy [%s] success", m.ProxyName)
|
||||
case *msg.Ping:
|
||||
content := &plugin.PingContent{
|
||||
User: plugin.UserInfo{
|
||||
User: ctl.loginMsg.User,
|
||||
Metas: ctl.loginMsg.Metas,
|
||||
RunID: ctl.loginMsg.RunID,
|
||||
},
|
||||
Ping: *inMsg,
|
||||
Ping: *m,
|
||||
}
|
||||
retContent, err := ctl.pluginManager.Ping(content)
|
||||
if err == nil {
|
||||
inMsg = &retContent.Ping
|
||||
err = ctl.authVerifier.VerifyPing(inMsg)
|
||||
m = &retContent.Ping
|
||||
err = ctl.authVerifier.VerifyPing(m)
|
||||
}
|
||||
if err != nil {
|
||||
xl.Warn("received invalid ping: %v", err)
|
||||
_ = ctl.msgDispatcher.Send(&msg.Pong{
|
||||
ctl.sendCh <- &msg.Pong{
|
||||
Error: util.GenerateResponseErrorString("invalid ping", err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient)),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
ctl.lastPing.Store(time.Now())
|
||||
ctl.lastPing = time.Now()
|
||||
xl.Debug("receive heartbeat")
|
||||
_ = ctl.msgDispatcher.Send(&msg.Pong{})
|
||||
ctl.sendCh <- &msg.Pong{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *Control) handleNatHoleVisitor(m msg.Message) {
|
||||
inMsg := m.(*msg.NatHoleVisitor)
|
||||
ctl.rc.NatHoleController.HandleVisitor(inMsg, ctl.msgTransporter, ctl.loginMsg.User)
|
||||
func (ctl *Control) HandleNatHoleVisitor(m *msg.NatHoleVisitor) {
|
||||
ctl.rc.NatHoleController.HandleVisitor(m, ctl.msgTransporter, ctl.loginMsg.User)
|
||||
}
|
||||
|
||||
func (ctl *Control) handleNatHoleClient(m msg.Message) {
|
||||
inMsg := m.(*msg.NatHoleClient)
|
||||
ctl.rc.NatHoleController.HandleClient(inMsg, ctl.msgTransporter)
|
||||
func (ctl *Control) HandleNatHoleClient(m *msg.NatHoleClient) {
|
||||
ctl.rc.NatHoleController.HandleClient(m, ctl.msgTransporter)
|
||||
}
|
||||
|
||||
func (ctl *Control) handleNatHoleReport(m msg.Message) {
|
||||
inMsg := m.(*msg.NatHoleReport)
|
||||
ctl.rc.NatHoleController.HandleReport(inMsg)
|
||||
}
|
||||
|
||||
func (ctl *Control) handleCloseProxy(m msg.Message) {
|
||||
xl := ctl.xl
|
||||
inMsg := m.(*msg.CloseProxy)
|
||||
_ = ctl.CloseProxy(inMsg)
|
||||
xl.Info("close proxy [%s] success", inMsg.ProxyName)
|
||||
func (ctl *Control) HandleNatHoleReport(m *msg.NatHoleReport) {
|
||||
ctl.rc.NatHoleController.HandleReport(m)
|
||||
}
|
||||
|
||||
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
||||
@@ -562,5 +658,6 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||
go func() {
|
||||
_ = ctl.pluginManager.CloseProxy(notifyContent)
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
99
server/dashboard.go
Normal file
99
server/dashboard.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2017 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 server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerReadTimeout = 60 * time.Second
|
||||
httpServerWriteTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
func (svr *Service) RunDashboardServer(address string) (err error) {
|
||||
// url router
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/healthz", svr.Healthz)
|
||||
|
||||
// debug
|
||||
if svr.cfg.WebServer.PprofEnable {
|
||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||
}
|
||||
|
||||
subRouter := router.NewRoute().Subrouter()
|
||||
|
||||
user, passwd := svr.cfg.WebServer.User, svr.cfg.WebServer.Password
|
||||
subRouter.Use(utilnet.NewHTTPAuthMiddleware(user, passwd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||
|
||||
// metrics
|
||||
if svr.cfg.EnablePrometheus {
|
||||
subRouter.Handle("/metrics", promhttp.Handler())
|
||||
}
|
||||
|
||||
// api, see dashboard_api.go
|
||||
subRouter.HandleFunc("/api/serverinfo", svr.APIServerInfo).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{type}", svr.APIProxyByType).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.APIProxyByTypeAndName).Methods("GET")
|
||||
subRouter.HandleFunc("/api/traffic/{name}", svr.APIProxyTraffic).Methods("GET")
|
||||
|
||||
// view
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
|
||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Addr: address,
|
||||
Handler: router,
|
||||
ReadTimeout: httpServerReadTimeout,
|
||||
WriteTimeout: httpServerWriteTimeout,
|
||||
}
|
||||
ln, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if svr.cfg.WebServer.TLS != nil {
|
||||
cert, err := tls.LoadX509KeyPair(svr.cfg.WebServer.TLS.CertFile, svr.cfg.WebServer.TLS.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsCfg := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
ln = tls.NewListener(ln, tlsCfg)
|
||||
}
|
||||
go func() {
|
||||
_ = server.Serve(ln)
|
||||
}()
|
||||
return
|
||||
}
|
@@ -19,81 +19,48 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/metrics/mem"
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
)
|
||||
|
||||
// TODO(fatedier): add an API to clean status of all offline proxies.
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
|
||||
helper.Router.HandleFunc("/healthz", svr.healthz)
|
||||
subRouter := helper.Router.NewRoute().Subrouter()
|
||||
|
||||
subRouter.Use(helper.AuthMiddleware.Middleware)
|
||||
|
||||
// metrics
|
||||
if svr.cfg.EnablePrometheus {
|
||||
subRouter.Handle("/metrics", promhttp.Handler())
|
||||
}
|
||||
|
||||
// apis
|
||||
subRouter.HandleFunc("/api/serverinfo", svr.apiServerInfo).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{type}", svr.apiProxyByType).Methods("GET")
|
||||
subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET")
|
||||
subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET")
|
||||
|
||||
// view
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(
|
||||
netpkg.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(helper.AssetsFS))),
|
||||
).Methods("GET")
|
||||
|
||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
}
|
||||
|
||||
type serverInfoResp struct {
|
||||
Version string `json:"version"`
|
||||
BindPort int `json:"bindPort"`
|
||||
VhostHTTPPort int `json:"vhostHTTPPort"`
|
||||
VhostHTTPSPort int `json:"vhostHTTPSPort"`
|
||||
TCPMuxHTTPConnectPort int `json:"tcpmuxHTTPConnectPort"`
|
||||
KCPBindPort int `json:"kcpBindPort"`
|
||||
QUICBindPort int `json:"quicBindPort"`
|
||||
SubdomainHost string `json:"subdomainHost"`
|
||||
MaxPoolCount int64 `json:"maxPoolCount"`
|
||||
MaxPortsPerClient int64 `json:"maxPortsPerClient"`
|
||||
HeartBeatTimeout int64 `json:"heartbeatTimeout"`
|
||||
AllowPortsStr string `json:"allowPortsStr,omitempty"`
|
||||
TLSForce bool `json:"tlsForce,omitempty"`
|
||||
BindPort int `json:"bind_port"`
|
||||
VhostHTTPPort int `json:"vhost_http_port"`
|
||||
VhostHTTPSPort int `json:"vhost_https_port"`
|
||||
TCPMuxHTTPConnectPort int `json:"tcpmux_httpconnect_port"`
|
||||
KCPBindPort int `json:"kcp_bind_port"`
|
||||
QUICBindPort int `json:"quic_bind_port"`
|
||||
SubdomainHost string `json:"subdomain_host"`
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||
AllowPortsStr string `json:"allow_ports_str,omitempty"`
|
||||
TLSOnly bool `json:"tls_only,omitempty"`
|
||||
|
||||
TotalTrafficIn int64 `json:"totalTrafficIn"`
|
||||
TotalTrafficOut int64 `json:"totalTrafficOut"`
|
||||
CurConns int64 `json:"curConns"`
|
||||
ClientCounts int64 `json:"clientCounts"`
|
||||
ProxyTypeCounts map[string]int64 `json:"proxyTypeCount"`
|
||||
TotalTrafficIn int64 `json:"total_traffic_in"`
|
||||
TotalTrafficOut int64 `json:"total_traffic_out"`
|
||||
CurConns int64 `json:"cur_conns"`
|
||||
ClientCounts int64 `json:"client_counts"`
|
||||
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
|
||||
}
|
||||
|
||||
// /healthz
|
||||
func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
|
||||
func (svr *Service) Healthz(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
||||
// /api/serverinfo
|
||||
func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||
func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
defer func() {
|
||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
@@ -118,7 +85,7 @@ func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
|
||||
HeartBeatTimeout: svr.cfg.Transport.HeartbeatTimeout,
|
||||
AllowPortsStr: types.PortsRangeSlice(svr.cfg.AllowPorts).String(),
|
||||
TLSForce: svr.cfg.Transport.TLS.Force,
|
||||
TLSOnly: svr.cfg.Transport.TLS.Force,
|
||||
|
||||
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||
@@ -137,7 +104,7 @@ type BaseOutConf struct {
|
||||
|
||||
type TCPOutConf struct {
|
||||
BaseOutConf
|
||||
RemotePort int `json:"remotePort"`
|
||||
RemotePort int `json:"remote_port"`
|
||||
}
|
||||
|
||||
type TCPMuxOutConf struct {
|
||||
@@ -148,14 +115,14 @@ type TCPMuxOutConf struct {
|
||||
|
||||
type UDPOutConf struct {
|
||||
BaseOutConf
|
||||
RemotePort int `json:"remotePort"`
|
||||
RemotePort int `json:"remote_port"`
|
||||
}
|
||||
|
||||
type HTTPOutConf struct {
|
||||
BaseOutConf
|
||||
v1.DomainConfig
|
||||
Locations []string `json:"locations"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||
}
|
||||
|
||||
type HTTPSOutConf struct {
|
||||
@@ -196,12 +163,12 @@ func getConfByType(proxyType string) any {
|
||||
type ProxyStatsInfo struct {
|
||||
Name string `json:"name"`
|
||||
Conf interface{} `json:"conf"`
|
||||
ClientVersion string `json:"clientVersion,omitempty"`
|
||||
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
||||
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
||||
CurConns int64 `json:"curConns"`
|
||||
LastStartTime string `json:"lastStartTime"`
|
||||
LastCloseTime string `json:"lastCloseTime"`
|
||||
ClientVersion string `json:"client_version,omitempty"`
|
||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||
TodayTrafficOut int64 `json:"today_traffic_out"`
|
||||
CurConns int64 `json:"cur_conns"`
|
||||
LastStartTime string `json:"last_start_time"`
|
||||
LastCloseTime string `json:"last_close_time"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
@@ -210,7 +177,7 @@ type GetProxyInfoResp struct {
|
||||
}
|
||||
|
||||
// /api/proxy/:type
|
||||
func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||
func (svr *Service) APIProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
proxyType := params["type"]
|
||||
@@ -269,16 +236,16 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
|
||||
type GetProxyStatsResp struct {
|
||||
Name string `json:"name"`
|
||||
Conf interface{} `json:"conf"`
|
||||
TodayTrafficIn int64 `json:"todayTrafficIn"`
|
||||
TodayTrafficOut int64 `json:"todayTrafficOut"`
|
||||
CurConns int64 `json:"curConns"`
|
||||
LastStartTime string `json:"lastStartTime"`
|
||||
LastCloseTime string `json:"lastCloseTime"`
|
||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||
TodayTrafficOut int64 `json:"today_traffic_out"`
|
||||
CurConns int64 `json:"cur_conns"`
|
||||
LastStartTime string `json:"last_start_time"`
|
||||
LastCloseTime string `json:"last_close_time"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// /api/proxy/:type/:name
|
||||
func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
|
||||
func (svr *Service) APIProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
proxyType := params["type"]
|
||||
@@ -343,11 +310,11 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
|
||||
// /api/traffic/:name
|
||||
type GetProxyTrafficResp struct {
|
||||
Name string `json:"name"`
|
||||
TrafficIn []int64 `json:"trafficIn"`
|
||||
TrafficOut []int64 `json:"trafficOut"`
|
||||
TrafficIn []int64 `json:"traffic_in"`
|
||||
TrafficOut []int64 `json:"traffic_out"`
|
||||
}
|
||||
|
||||
func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
||||
func (svr *Service) APIProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
name := params["name"]
|
||||
|
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/vhost"
|
||||
"github.com/fatedier/frp/server/metrics"
|
||||
@@ -180,8 +180,8 @@ func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err err
|
||||
})
|
||||
}
|
||||
|
||||
workConn = netpkg.WrapReadWriteCloserToConn(rwc, tmpConn)
|
||||
workConn = netpkg.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
|
||||
workConn = utilnet.WrapReadWriteCloserToConn(rwc, tmpConn)
|
||||
workConn = utilnet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
|
||||
metrics.Server.OpenConnection(pxy.GetName(), pxy.GetConfigurer().GetBaseConfig().Type)
|
||||
return
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
netpkg "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/server/controller"
|
||||
"github.com/fatedier/frp/server/metrics"
|
||||
@@ -130,7 +130,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn,
|
||||
}
|
||||
xl.Debug("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
||||
xl.Spawn().AppendPrefix(pxy.GetName())
|
||||
workConn = netpkg.NewContextConn(pxy.ctx, workConn)
|
||||
workConn = utilnet.NewContextConn(pxy.ctx, workConn)
|
||||
|
||||
var (
|
||||
srcAddr string
|
||||
|
@@ -30,7 +30,7 @@ import (
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/proto/udp"
|
||||
"github.com/fatedier/frp/pkg/util/limit"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/server/metrics"
|
||||
)
|
||||
|
||||
@@ -222,7 +222,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
|
||||
})
|
||||
}
|
||||
|
||||
pxy.workConn = netpkg.WrapReadWriteCloserToConn(rwc, workConn)
|
||||
pxy.workConn = utilnet.WrapReadWriteCloserToConn(rwc, workConn)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go workConnReaderFn(pxy.workConn)
|
||||
go workConnSenderFn(pxy.workConn, ctx)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user