mirror of
https://github.com/fatedier/frp.git
synced 2025-07-27 07:35:07 +00:00
Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
eb1e19a821 | ||
|
6c658586f6 | ||
|
888ed25314 | ||
|
21240ed962 | ||
|
6481870d03 | ||
|
a7a4ba270d | ||
|
915d9f4c09 | ||
|
18a2af4703 | ||
|
305e40fa8a | ||
|
10f2620131 | ||
|
4acae540c8 | ||
|
11b13533a0 | ||
|
100d556336 | ||
|
452fe25cc6 | ||
|
63efa6b776 | ||
|
37c27169ac | ||
|
ce677820c6 | ||
|
1f88a7a0b8 | ||
|
eeea7602d9 | ||
|
bf635c0e90 | ||
|
cd31359a27 | ||
|
19739ed31a | ||
|
10100c28d9 | ||
|
88fcc079e8 | ||
|
ddc1e163c4 | ||
|
d20a6d3d75 | ||
|
6194273615 | ||
|
b2311e55e7 | ||
|
07873d471f | ||
|
2dab5d0bca | ||
|
9ca2b586f8 | ||
|
e59eacb8a2 | ||
|
0db4fc07fb | ||
|
70f4caac23 | ||
|
293003fcdb | ||
|
4bfc89d988 | ||
|
22412851b4 | ||
|
e9775bd70f | ||
|
ff7b8b0b62 | ||
|
491c1d7dc4 | ||
|
ea568e8a4f | ||
|
0fb6aeef58 | ||
|
032f33fe5a | ||
|
bbc8b438d5 | ||
|
05b1ace21f | ||
|
cbdd73b94f | ||
|
bf06e3b107 |
@@ -2,14 +2,14 @@ version: 2
|
||||
jobs:
|
||||
go-version-latest:
|
||||
docker:
|
||||
- image: cimg/go:1.17-node
|
||||
- image: cimg/go:1.18-node
|
||||
steps:
|
||||
- checkout
|
||||
- run: make
|
||||
- run: make alltest
|
||||
go-version-last:
|
||||
docker:
|
||||
- image: cimg/go:1.16-node
|
||||
- image: cimg/go:1.17-node
|
||||
steps:
|
||||
- checkout
|
||||
- run: make
|
||||
|
2
.github/workflows/build-and-push-image.yml
vendored
2
.github/workflows/build-and-push-image.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18
|
||||
|
||||
- run: |
|
||||
# https://github.com/actions/setup-go/issues/107
|
||||
|
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18
|
||||
|
||||
- run: |
|
||||
# https://github.com/actions/setup-go/issues/107
|
||||
|
6
.github/workflows/stale.yml
vendored
6
.github/workflows/stale.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
|
||||
stale-pr-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
|
||||
stale-pr-message: "PRs go stale after 30d of inactivity. Stale PRs rot after an additional 7d of inactivity and eventually close."
|
||||
stale-issue-label: 'lifecycle/stale'
|
||||
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||
stale-pr-label: 'lifecycle/stale'
|
||||
@@ -24,3 +24,5 @@ jobs:
|
||||
days-before-stale: 30
|
||||
days-before-close: 7
|
||||
debug-only: ${{ github.event.inputs.debug-only }}
|
||||
exempt-all-pr-milestones: true
|
||||
exempt-all-pr-assignees: true
|
||||
|
@@ -1,7 +1,10 @@
|
||||
builds:
|
||||
- skip: true
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
name_template: '{{ .ProjectName }}_sha256_checksums.txt'
|
||||
algorithm: sha256
|
||||
extra_files:
|
||||
- glob: ./release/packages/*
|
||||
release:
|
||||
# Same as for github
|
||||
# Note: it can only be one: either github, gitlab or gitea
|
||||
|
5
Makefile
5
Makefile
@@ -16,6 +16,9 @@ file:
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
frps:
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
|
||||
|
||||
@@ -37,7 +40,7 @@ e2e:
|
||||
e2e-trace:
|
||||
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
|
||||
|
||||
alltest: gotest e2e
|
||||
alltest: vet gotest e2e
|
||||
|
||||
clean:
|
||||
rm -f ./bin/frpc
|
||||
|
47
README.md
47
README.md
@@ -6,6 +6,32 @@
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
<h3 align="center">Platinum Sponsors</h3>
|
||||
<!--platinum sponsors start-->
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.doppler.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_doppler.png">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!--platinum sponsors end-->
|
||||
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
|
||||
<p align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!--gold sponsors end-->
|
||||
|
||||
<h3 align="center">Silver Sponsors</h3>
|
||||
|
||||
* Sakura Frp - 欢迎点击 "加入我们"
|
||||
|
||||
## What is frp?
|
||||
|
||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. As of now, it supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, where requests can be forwarded to internal services by domain name.
|
||||
@@ -67,8 +93,7 @@ frp also has a P2P connect mode.
|
||||
* [Development Plan](#development-plan)
|
||||
* [Contributing](#contributing)
|
||||
* [Donation](#donation)
|
||||
* [AliPay](#alipay)
|
||||
* [Wechat Pay](#wechat-pay)
|
||||
* [GitHub Sponsors](#github-sponsors)
|
||||
* [PayPal](#paypal)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
@@ -77,7 +102,9 @@ frp also has a P2P connect mode.
|
||||
|
||||
frp is under development. Try the latest release version in the `master` branch, or use the `dev` branch for the version in development.
|
||||
|
||||
**The protocol might change at a release and we don't promise backwards compatibility. Please check the release log when upgrading the client and the server.**
|
||||
We are working on v2 version and trying to do some code refactor and improvements. It won't be compatible with v1.
|
||||
|
||||
We will switch v0 to v1 at the right time and only accept bug fixes and improvements instead of big feature requirements.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -867,7 +894,7 @@ In this example, it will set header `X-From-Where: frp` in the HTTP request.
|
||||
|
||||
This feature is for http proxy only.
|
||||
|
||||
You can get user's real IP from HTTP request headers `X-Forwarded-For` and `X-Real-IP`.
|
||||
You can get user's real IP from HTTP request headers `X-Forwarded-For`.
|
||||
|
||||
#### Proxy Protocol
|
||||
|
||||
@@ -983,11 +1010,13 @@ server_port = 7000
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
custom_domains = test1
|
||||
local_port = 80
|
||||
|
||||
[proxy2]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
custom_domains = test2
|
||||
local_port = 8080
|
||||
```
|
||||
|
||||
In the above configuration - frps can be contacted on port 1337 with a HTTP CONNECT header such as:
|
||||
@@ -1073,15 +1102,11 @@ Interested in getting involved? We would like to help you!
|
||||
|
||||
If frp helps you a lot, you can support us by:
|
||||
|
||||
frp QQ group: 606194980
|
||||
### GitHub Sponsors
|
||||
|
||||
### AliPay
|
||||
Support us by [Github Sponsors](https://github.com/sponsors/fatedier).
|
||||
|
||||

|
||||
|
||||
### Wechat Pay
|
||||
|
||||

|
||||
You can have your company's logo placed on README file of this project.
|
||||
|
||||
### PayPal
|
||||
|
||||
|
40
README_zh.md
40
README_zh.md
@@ -7,6 +7,32 @@
|
||||
|
||||
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
|
||||
|
||||
<h3 align="center">Platinum Sponsors</h3>
|
||||
<!--platinum sponsors start-->
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.doppler.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="400px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_doppler.png">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!--platinum sponsors end-->
|
||||
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
|
||||
<p align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!--gold sponsors end-->
|
||||
|
||||
<h3 align="center">Silver Sponsors</h3>
|
||||
|
||||
* Sakura Frp - 欢迎点击 "加入我们"
|
||||
|
||||
## 为什么使用 frp ?
|
||||
|
||||
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
|
||||
@@ -25,6 +51,10 @@ frp 目前已被很多公司广泛用于测试、生产环境。
|
||||
|
||||
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||
|
||||
我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续一段时间。
|
||||
|
||||
现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。
|
||||
|
||||
## 文档
|
||||
|
||||
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。
|
||||
@@ -46,6 +76,12 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
||||
|
||||
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
|
||||
|
||||
### GitHub Sponsors
|
||||
|
||||
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
||||
|
||||
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
||||
|
||||
### 知识星球
|
||||
|
||||
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
|
||||
@@ -59,7 +95,3 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
||||
### 微信支付捐赠
|
||||
|
||||

|
||||
|
||||
### Paypal 捐赠
|
||||
|
||||
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。
|
||||
|
@@ -1,8 +1,7 @@
|
||||
### New
|
||||
|
||||
* Add `/healthz` API.
|
||||
* frpc support `disable_custom_tls_first_byte` .If set true, frpc will not send custom header byte.
|
||||
* Added new parameter `config_dir` in frpc to run multiple client instances in one process.
|
||||
|
||||
### Improve
|
||||
### Fix
|
||||
|
||||
* Use go standard embed package instead of statik.
|
||||
* Equal sign in environment variables causes parsing error.
|
||||
|
@@ -17,6 +17,7 @@ package client
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
@@ -26,28 +27,39 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerReadTimeout = 10 * time.Second
|
||||
httpServerWriteTimeout = 10 * time.Second
|
||||
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.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.AdminUser, svr.cfg.AdminPwd
|
||||
router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
||||
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
||||
|
||||
// api, see admin_api.go
|
||||
router.HandleFunc("/healthz", svr.healthz)
|
||||
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||
subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||
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
|
||||
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
router.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(frpNet.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)
|
||||
})
|
||||
|
||||
|
@@ -34,6 +34,7 @@ import (
|
||||
|
||||
"github.com/fatedier/golib/control/shutdown"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
@@ -234,12 +235,38 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort))
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol, address, tlsConfig, ctl.clientCfg.DisableCustomTLSFirstByte)
|
||||
|
||||
proxyType, addr, auth, err := libdial.ParseProxyURL(ctl.clientCfg.HTTPProxy)
|
||||
if err != nil {
|
||||
xl.Error("fail to parse proxy url")
|
||||
return nil, err
|
||||
}
|
||||
dialOptions := []libdial.DialOption{}
|
||||
protocol := ctl.clientCfg.Protocol
|
||||
if protocol == "websocket" {
|
||||
protocol = "tcp"
|
||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
|
||||
}
|
||||
if ctl.clientCfg.ConnectServerLocalIP != "" {
|
||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(ctl.clientCfg.ConnectServerLocalIP))
|
||||
}
|
||||
dialOptions = append(dialOptions,
|
||||
libdial.WithProtocol(protocol),
|
||||
libdial.WithTimeout(time.Duration(ctl.clientCfg.DialServerTimeout)*time.Second),
|
||||
libdial.WithKeepAlive(time.Duration(ctl.clientCfg.DialServerKeepAlive)*time.Second),
|
||||
libdial.WithProxy(proxyType, addr),
|
||||
libdial.WithProxyAuth(auth),
|
||||
libdial.WithTLSConfig(tlsConfig),
|
||||
libdial.WithAfterHook(libdial.AfterHook{
|
||||
Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, ctl.clientCfg.DisableCustomTLSFirstByte),
|
||||
}),
|
||||
)
|
||||
conn, err = libdial.Dial(
|
||||
net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort)),
|
||||
dialOptions...,
|
||||
)
|
||||
if err != nil {
|
||||
xl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -308,16 +335,27 @@ func (ctl *Control) msgHandler() {
|
||||
}()
|
||||
defer ctl.msgHandlerShutdown.Done()
|
||||
|
||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
|
||||
defer hbSend.Stop()
|
||||
hbCheck := time.NewTicker(time.Second)
|
||||
defer hbCheck.Stop()
|
||||
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.HeartbeatInterval > 0 {
|
||||
hbSend := time.NewTicker(time.Duration(ctl.clientCfg.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.HeartbeatInterval > 0 && ctl.clientCfg.HeartbeatTimeout > 0 && !ctl.clientCfg.TCPMux {
|
||||
hbCheck := time.NewTicker(time.Second)
|
||||
defer hbCheck.Stop()
|
||||
hbCheckCh = hbCheck.C
|
||||
}
|
||||
|
||||
ctl.lastPong = time.Now()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-hbSend.C:
|
||||
case <-hbSendCh:
|
||||
// send heartbeat to server
|
||||
xl.Debug("send heartbeat to server")
|
||||
pingMsg := &msg.Ping{}
|
||||
@@ -326,7 +364,7 @@ func (ctl *Control) msgHandler() {
|
||||
return
|
||||
}
|
||||
ctl.sendCh <- pingMsg
|
||||
case <-hbCheck.C:
|
||||
case <-hbCheckCh:
|
||||
if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second {
|
||||
xl.Warn("heartbeat timeout")
|
||||
// let reader() stop
|
||||
|
@@ -35,6 +35,7 @@ import (
|
||||
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
"github.com/fatedier/golib/pool"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
pp "github.com/pires/go-proxyproto"
|
||||
@@ -346,22 +347,18 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||
|
||||
// Send detect message
|
||||
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
||||
if len(array) <= 1 {
|
||||
xl.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
host, portStr, err := net.SplitHostPort(natHoleRespMsg.VisitorAddr)
|
||||
if err != nil {
|
||||
xl.Error("get NatHoleResp visitor address [%s] error: %v", natHoleRespMsg.VisitorAddr, err)
|
||||
}
|
||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||
/*
|
||||
for i := 1000; i < 65000; i++ {
|
||||
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||
}
|
||||
*/
|
||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||
|
||||
port, err := strconv.ParseInt(portStr, 10, 64)
|
||||
if err != nil {
|
||||
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
return
|
||||
}
|
||||
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||
pxy.sendDetectMsg(host, int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||
xl.Trace("send all detect msg done")
|
||||
|
||||
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||
@@ -369,7 +366,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
// Listen for clientConn's address and wait for visitor connection
|
||||
lConn, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
xl.Error("listen on visitorConn's local adress error: %v", err)
|
||||
xl.Error("listen on visitorConn's local address error: %v", err)
|
||||
return
|
||||
}
|
||||
defer lConn.Close()
|
||||
@@ -790,7 +787,10 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
|
||||
return
|
||||
}
|
||||
|
||||
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIP, localInfo.LocalPort))
|
||||
localConn, err := libdial.Dial(
|
||||
net.JoinHostPort(localInfo.LocalIP, strconv.Itoa(localInfo.LocalPort)),
|
||||
libdial.WithTimeout(10*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
workConn.Close()
|
||||
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err)
|
||||
|
@@ -17,12 +17,13 @@ package client
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -34,12 +35,20 @@ import (
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
frpNet "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/xlog"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
func init() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Service is a client service.
|
||||
type Service struct {
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
@@ -97,6 +106,21 @@ func (svr *Service) GetController() *Control {
|
||||
func (svr *Service) Run() error {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
|
||||
// set custom DNSServer
|
||||
if svr.cfg.DNSServer != "" {
|
||||
dnsAddr := svr.cfg.DNSServer
|
||||
if !strings.Contains(dnsAddr, ":") {
|
||||
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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// login to frps
|
||||
for {
|
||||
conn, session, err := svr.login()
|
||||
@@ -108,7 +132,7 @@ func (svr *Service) Run() error {
|
||||
if svr.cfg.LoginFailExit {
|
||||
return err
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
util.RandomSleep(10*time.Second, 0.9, 1.1)
|
||||
} else {
|
||||
// login success
|
||||
ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
||||
@@ -157,8 +181,11 @@ func (svr *Service) keepControllerWorking() {
|
||||
|
||||
// the first three retry with no delay
|
||||
if reconnectCounts > 3 {
|
||||
time.Sleep(reconnectDelay)
|
||||
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++
|
||||
|
||||
@@ -174,18 +201,12 @@ func (svr *Service) keepControllerWorking() {
|
||||
xl.Info("try to reconnect to server...")
|
||||
conn, session, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warn("reconnect to server error: %v", err)
|
||||
time.Sleep(delayTime)
|
||||
xl.Warn("reconnect to server error: %v, wait %v for another retry", err, delayTime)
|
||||
util.RandomSleep(delayTime, 0.9, 1.1)
|
||||
|
||||
opErr := &net.OpError{}
|
||||
// quick retry for dial error
|
||||
if errors.As(err, &opErr) && opErr.Op == "dial" {
|
||||
delayTime = 2 * time.Second
|
||||
} else {
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -228,8 +249,35 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort))
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol, address, tlsConfig, svr.cfg.DisableCustomTLSFirstByte)
|
||||
proxyType, addr, auth, err := libdial.ParseProxyURL(svr.cfg.HTTPProxy)
|
||||
if err != nil {
|
||||
xl.Error("fail to parse proxy url")
|
||||
return
|
||||
}
|
||||
dialOptions := []libdial.DialOption{}
|
||||
protocol := svr.cfg.Protocol
|
||||
if protocol == "websocket" {
|
||||
protocol = "tcp"
|
||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
|
||||
}
|
||||
if svr.cfg.ConnectServerLocalIP != "" {
|
||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(svr.cfg.ConnectServerLocalIP))
|
||||
}
|
||||
dialOptions = append(dialOptions,
|
||||
libdial.WithProtocol(protocol),
|
||||
libdial.WithTimeout(time.Duration(svr.cfg.DialServerTimeout)*time.Second),
|
||||
libdial.WithKeepAlive(time.Duration(svr.cfg.DialServerKeepAlive)*time.Second),
|
||||
libdial.WithProxy(proxyType, addr),
|
||||
libdial.WithProxyAuth(auth),
|
||||
libdial.WithTLSConfig(tlsConfig),
|
||||
libdial.WithAfterHook(libdial.AfterHook{
|
||||
Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, svr.cfg.DisableCustomTLSFirstByte),
|
||||
}),
|
||||
)
|
||||
conn, err = libdial.Dial(
|
||||
net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort)),
|
||||
dialOptions...,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -245,7 +293,7 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
||||
|
||||
if svr.cfg.TCPMux {
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||
fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second
|
||||
fmuxCfg.LogOutput = io.Discard
|
||||
session, err = fmux.Client(conn, fmuxCfg)
|
||||
if err != nil {
|
||||
@@ -308,7 +356,14 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
|
||||
svr.visitorCfgs = visitorCfgs
|
||||
svr.cfgMu.Unlock()
|
||||
|
||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
svr.ctlMu.RLock()
|
||||
ctl := svr.ctl
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
if ctl != nil {
|
||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Service) Close() {
|
||||
@@ -317,8 +372,12 @@ func (svr *Service) Close() {
|
||||
|
||||
func (svr *Service) GracefulClose(d time.Duration) {
|
||||
atomic.StoreUint32(&svr.exit, 1)
|
||||
|
||||
svr.ctlMu.RLock()
|
||||
if svr.ctl != nil {
|
||||
svr.ctl.GracefulClose(d)
|
||||
}
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
svr.cancel()
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -85,7 +86,7 @@ type STCPVisitor struct {
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) Run() (err error) {
|
||||
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -174,7 +175,7 @@ type XTCPVisitor struct {
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) Run() (err error) {
|
||||
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -352,7 +353,7 @@ type SUDPVisitor struct {
|
||||
func (sv *SUDPVisitor) Run() (err error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
||||
}
|
||||
@@ -376,29 +377,33 @@ func (sv *SUDPVisitor) Run() (err error) {
|
||||
func (sv *SUDPVisitor) dispatcher() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
var (
|
||||
visitorConn net.Conn
|
||||
err error
|
||||
|
||||
firstPacket *msg.UDPPacket
|
||||
)
|
||||
|
||||
for {
|
||||
// loop for get frpc to frps tcp conn
|
||||
// setup worker
|
||||
// wait worker to finished
|
||||
// retry or exit
|
||||
visitorConn, err := sv.getNewVisitorConn()
|
||||
if err != nil {
|
||||
// check if proxy is closed
|
||||
// if checkCloseCh is close, we will return, other case we will continue to reconnect
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
select {
|
||||
case firstPacket = <-sv.sendCh:
|
||||
if firstPacket == nil {
|
||||
xl.Info("frpc sudp visitor proxy is closed")
|
||||
return
|
||||
default:
|
||||
}
|
||||
case <-sv.checkCloseCh:
|
||||
xl.Info("frpc sudp visitor proxy is closed")
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
visitorConn, err = sv.getNewVisitorConn()
|
||||
if err != nil {
|
||||
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||
continue
|
||||
}
|
||||
|
||||
sv.worker(visitorConn)
|
||||
// visitorConn always be closed when worker done.
|
||||
sv.worker(visitorConn, firstPacket)
|
||||
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
@@ -406,9 +411,10 @@ func (sv *SUDPVisitor) dispatcher() {
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) worker(workConn net.Conn) {
|
||||
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
xl.Debug("starting sudp proxy worker")
|
||||
|
||||
@@ -462,6 +468,14 @@ func (sv *SUDPVisitor) worker(workConn net.Conn) {
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
if firstPacket != nil {
|
||||
if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil {
|
||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
xl.Trace("send udp package to workConn: %s", firstPacket.Content)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case udpMsg, ok := <-sv.sendCh:
|
||||
|
@@ -15,18 +15,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/frpc"
|
||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
sub.Execute()
|
||||
}
|
||||
|
@@ -15,13 +15,14 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -41,6 +42,7 @@ const (
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
cfgDir string
|
||||
showVersion bool
|
||||
|
||||
serverAddr string
|
||||
@@ -72,15 +74,12 @@ var (
|
||||
bindPort int
|
||||
|
||||
tlsEnable bool
|
||||
|
||||
kcpDoneCh chan struct{}
|
||||
)
|
||||
|
||||
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")
|
||||
|
||||
kcpDoneCh = make(chan struct{})
|
||||
}
|
||||
|
||||
func RegisterCommonFlags(cmd *cobra.Command) {
|
||||
@@ -104,6 +103,32 @@ var rootCmd = &cobra.Command{
|
||||
return nil
|
||||
}
|
||||
|
||||
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
||||
// Note that it's only designed for testing. It's not guaranteed to be stable.
|
||||
if cfgDir != "" {
|
||||
var wg sync.WaitGroup
|
||||
filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
wg.Add(1)
|
||||
time.Sleep(time.Millisecond)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := runClient(path)
|
||||
if err != nil {
|
||||
fmt.Printf("frpc service error for config file [%s]\n", path)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not show command usage here.
|
||||
err := runClient(cfgFile)
|
||||
if err != nil {
|
||||
@@ -120,12 +145,12 @@ func Execute() {
|
||||
}
|
||||
}
|
||||
|
||||
func handleSignal(svr *client.Service) {
|
||||
ch := make(chan os.Signal)
|
||||
func handleSignal(svr *client.Service, doneCh chan struct{}) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-ch
|
||||
svr.GracefulClose(500 * time.Millisecond)
|
||||
close(kcpDoneCh)
|
||||
close(doneCh)
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||
@@ -182,18 +207,9 @@ func startService(
|
||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
||||
cfg.LogMaxDays, cfg.DisableLogColor)
|
||||
|
||||
if cfg.DNSServer != "" {
|
||||
s := cfg.DNSServer
|
||||
if !strings.Contains(s, ":") {
|
||||
s += ":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", s)
|
||||
},
|
||||
}
|
||||
if cfgFile != "" {
|
||||
log.Trace("start frpc service for config file [%s]", cfgFile)
|
||||
defer log.Trace("frpc service for config file [%s] stopped", cfgFile)
|
||||
}
|
||||
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||
if errRet != nil {
|
||||
@@ -201,9 +217,10 @@ func startService(
|
||||
return
|
||||
}
|
||||
|
||||
kcpDoneCh := make(chan struct{})
|
||||
// Capture the exit signal if we use kcp.
|
||||
if cfg.Protocol == "kcp" {
|
||||
go handleSignal(svr)
|
||||
go handleSignal(svr, kcpDoneCh)
|
||||
}
|
||||
|
||||
err = svr.Run()
|
||||
|
@@ -6,6 +6,13 @@
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
|
||||
# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
|
||||
# dial_server_timeout = 10
|
||||
|
||||
# dial_server_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
# If negative, keep-alive probes are disabled.
|
||||
# dial_server_keepalive = 7200
|
||||
|
||||
# if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set http_proxy here or in global environment variables
|
||||
# it only works when protocol is tcp
|
||||
# http_proxy = http://user:passwd@192.168.1.128:8080
|
||||
@@ -48,6 +55,12 @@ oidc_audience =
|
||||
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_token_endpoint_url =
|
||||
|
||||
# oidc_additional_xxx 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.
|
||||
# oidc_additional_audience = https://dev.auth.com/api/v2/
|
||||
# oidc_additional_var1 = foobar
|
||||
|
||||
# set admin address for control frpc's action by http api such as reload
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
@@ -60,7 +73,11 @@ admin_pwd = admin
|
||||
pool_count = 5
|
||||
|
||||
# if tcp stream multiplexing is used, default is true, it must be same with frps
|
||||
tcp_mux = true
|
||||
# tcp_mux = true
|
||||
|
||||
# specify keep alive interval for tcp mux.
|
||||
# only valid if tcp_mux is true.
|
||||
# tcp_mux_keepalive_interval = 60
|
||||
|
||||
# your proxy name will be changed to {user}.{proxy}
|
||||
user = your_name
|
||||
@@ -73,6 +90,10 @@ login_fail_exit = true
|
||||
# now it supports tcp, kcp and websocket, default is tcp
|
||||
protocol = tcp
|
||||
|
||||
# set client binding ip when connect server, default is empty.
|
||||
# only when protocol = tcp or websocket, the value will be used.
|
||||
connect_server_local_ip = 0.0.0.0
|
||||
|
||||
# if tls_enable is true, frpc will connect frps by tls
|
||||
tls_enable = true
|
||||
|
||||
@@ -84,12 +105,13 @@ tls_enable = true
|
||||
# specify a dns server, so frpc will use this instead of default one
|
||||
# dns_server = 8.8.8.8
|
||||
|
||||
# proxy names you want to start seperated by ','
|
||||
# proxy names you want to start separated by ','
|
||||
# default is empty, means all proxies
|
||||
# start = ssh,dns
|
||||
|
||||
# heartbeat configure, it's not recommended to modify the default value
|
||||
# the default value of heartbeat_interval is 10 and heartbeat_timeout is 90
|
||||
# The default value of heartbeat_interval is 10 and heartbeat_timeout is 90. Set negative value
|
||||
# to disable it.
|
||||
# heartbeat_interval = 30
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
@@ -109,6 +131,10 @@ udp_packet_size = 1500
|
||||
# If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
|
||||
disable_custom_tls_first_byte = false
|
||||
|
||||
# Enable golang pprof handlers in admin listener.
|
||||
# Admin port must be set first.
|
||||
pprof_enable = false
|
||||
|
||||
# 'ssh' is the unique proxy name
|
||||
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
|
||||
[ssh]
|
||||
|
@@ -86,13 +86,12 @@ oidc_audience =
|
||||
# By default, this value is false.
|
||||
oidc_skip_expiry_check = false
|
||||
|
||||
|
||||
# oidc_skip_issuer_check specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer.
|
||||
# By default, this value is false.
|
||||
oidc_skip_issuer_check = false
|
||||
|
||||
# heartbeat configure, it's not recommended to modify the default value
|
||||
# the default value of heartbeat_timeout is 90
|
||||
# the default value of heartbeat_timeout is 90. Set negative value to disable it.
|
||||
# heartbeat_timeout = 90
|
||||
|
||||
# user_conn_timeout configure, it's not recommended to modify the default value
|
||||
@@ -120,7 +119,15 @@ tls_only = false
|
||||
subdomain_host = frps.com
|
||||
|
||||
# if tcp stream multiplexing is used, default is true
|
||||
tcp_mux = true
|
||||
# tcp_mux = true
|
||||
|
||||
# specify keep alive interval for tcp mux.
|
||||
# only valid if tcp_mux is true.
|
||||
# tcp_mux_keepalive_interval = 60
|
||||
|
||||
# tcp_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
# If negative, keep-alive probes are disabled.
|
||||
# tcp_keepalive = 7200
|
||||
|
||||
# custom 404 page for HTTP requests
|
||||
# custom_404_page = /path/to/404.html
|
||||
@@ -130,6 +137,10 @@ tcp_mux = true
|
||||
# It affects the udp and sudp proxy.
|
||||
udp_packet_size = 1500
|
||||
|
||||
# Enable golang pprof handlers in dashboard listener.
|
||||
# Dashboard port must be set first
|
||||
pprof_enable = false
|
||||
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:9000
|
||||
path = /handler
|
||||
|
BIN
doc/pic/sponsor_doppler.png
Normal file
BIN
doc/pic/sponsor_doppler.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
doc/pic/sponsor_workos.png
Normal file
BIN
doc/pic/sponsor_workos.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
@@ -70,7 +70,7 @@ The response can look like any of the following:
|
||||
|
||||
### Operation
|
||||
|
||||
Currently `Login`, `NewProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
||||
Currently `Login`, `NewProxy`, `CloseProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
||||
|
||||
#### Login
|
||||
|
||||
@@ -88,7 +88,8 @@ Client login operation
|
||||
"privilege_key": <string>,
|
||||
"run_id": <string>,
|
||||
"pool_count": <int>,
|
||||
"metas": map<string>string
|
||||
"metas": map<string>string,
|
||||
"client_address": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -135,6 +136,26 @@ Create new proxy
|
||||
}
|
||||
```
|
||||
|
||||
#### CloseProxy
|
||||
|
||||
A previously created proxy is closed.
|
||||
|
||||
Please note that one request will be sent for every proxy that is closed, do **NOT** use this
|
||||
if you have too many proxies bound to a single client, as this may exhaust the server's resources.
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"proxy_name": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Ping
|
||||
|
||||
Heartbeat from frpc
|
||||
|
@@ -1,228 +0,0 @@
|
||||
### 服务端管理插件
|
||||
|
||||
frp 管理插件的作用是在不侵入自身代码的前提下,扩展 frp 服务端的能力。
|
||||
|
||||
frp 管理插件会以单独进程的形式运行,并且监听在一个端口上,对外提供 RPC 接口,响应 frps 的请求。
|
||||
|
||||
frps 在执行某些操作前,会根据配置向管理插件发送 RPC 请求,根据管理插件的响应来执行相应的操作。
|
||||
|
||||
### RPC 请求
|
||||
|
||||
管理插件接收到操作请求后,可以给出三种回应。
|
||||
|
||||
* 拒绝操作,需要返回拒绝操作的原因。
|
||||
* 允许操作,不需要修改操作内容。
|
||||
* 允许操作,对操作请求进行修改后,返回修改后的内容。
|
||||
|
||||
### 接口
|
||||
|
||||
接口路径可以在 frps 配置中为每个插件单独配置,这里以 `/handler` 为例。
|
||||
|
||||
Request
|
||||
|
||||
```
|
||||
POST /handler
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"op": "Login",
|
||||
"content": {
|
||||
... // 具体的操作信息
|
||||
}
|
||||
}
|
||||
|
||||
请求 Header
|
||||
X-Frp-Reqid: 用于追踪请求
|
||||
```
|
||||
|
||||
Response
|
||||
|
||||
非 200 的返回都认为是请求异常。
|
||||
|
||||
拒绝执行操作
|
||||
|
||||
```
|
||||
{
|
||||
"reject": true,
|
||||
"reject_reason": "invalid user"
|
||||
}
|
||||
```
|
||||
|
||||
允许且内容不需要变动
|
||||
|
||||
```
|
||||
{
|
||||
"reject": false,
|
||||
"unchange": true
|
||||
}
|
||||
```
|
||||
|
||||
允许且需要替换操作内容
|
||||
|
||||
```
|
||||
{
|
||||
"unchange": "false",
|
||||
"content": {
|
||||
... // 替换后的操作信息,格式必须和请求时的一致
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 操作类型
|
||||
|
||||
目前插件支持管理的操作类型有 `Login`、`NewProxy`、`Ping`、`NewWorkConn` 和 `NewUserConn`。
|
||||
|
||||
#### Login
|
||||
|
||||
用户登录操作信息
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"version": <string>,
|
||||
"hostname": <string>,
|
||||
"os": <string>,
|
||||
"arch": <string>,
|
||||
"user": <string>,
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>,
|
||||
"run_id": <string>,
|
||||
"pool_count": <int>,
|
||||
"metas": map<string>string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewProxy
|
||||
|
||||
创建代理的相关信息
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
},
|
||||
"proxy_name": <string>,
|
||||
"proxy_type": <string>,
|
||||
"use_encryption": <bool>,
|
||||
"use_compression": <bool>,
|
||||
"group": <string>,
|
||||
"group_key": <string>,
|
||||
|
||||
// tcp and udp only
|
||||
"remote_port": <int>,
|
||||
|
||||
// http and https only
|
||||
"custom_domains": []<string>,
|
||||
"subdomain": <string>,
|
||||
"locations": <string>,
|
||||
"http_user": <string>,
|
||||
"http_pwd": <string>,
|
||||
"host_header_rewrite": <string>,
|
||||
"headers": map<string>string,
|
||||
|
||||
"metas": map<string>string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Ping
|
||||
|
||||
心跳相关信息
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewWorkConn
|
||||
|
||||
新增 `frpc` 连接相关信息
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"run_id": <string>
|
||||
"timestamp": <int64>,
|
||||
"privilege_key": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### NewUserConn
|
||||
|
||||
新增 `proxy` 连接相关信息 (支持 `tcp`、`stcp`、`https` 和 `tcpmux` 协议)。
|
||||
|
||||
```
|
||||
{
|
||||
"content": {
|
||||
"user": {
|
||||
"user": <string>,
|
||||
"metas": map<string>string
|
||||
"run_id": <string>
|
||||
},
|
||||
"proxy_name": <string>,
|
||||
"proxy_type": <string>,
|
||||
"remote_addr": <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### frps 中插件配置
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_port = 7000
|
||||
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:9000
|
||||
path = /handler
|
||||
ops = Login
|
||||
|
||||
[plugin.port-manager]
|
||||
addr = 127.0.0.1:9001
|
||||
path = /handler
|
||||
ops = NewProxy
|
||||
```
|
||||
|
||||
addr: 插件监听的网络地址。
|
||||
path: 插件监听的 HTTP 请求路径。
|
||||
ops: 插件需要处理的操作列表,多个 op 以英文逗号分隔。
|
||||
|
||||
### 元数据
|
||||
|
||||
为了减少 frps 的代码修改,同时提高管理插件的扩展能力,在 frpc 的配置文件中引入自定义元数据的概念。元数据会在调用 RPC 请求时发送给插件。
|
||||
|
||||
元数据以 `meta_` 开头,可以配置多个,元数据分为两种,一种配置在 `common` 下,一种配置在各个 proxy 中。
|
||||
|
||||
```
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = 127.0.0.1
|
||||
server_port = 7000
|
||||
user = fake
|
||||
meta_token = fake
|
||||
meta_version = 1.0.0
|
||||
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
remote_port = 6000
|
||||
meta_id = 123
|
||||
```
|
@@ -1,11 +1,11 @@
|
||||
FROM alpine:3.12.0 AS temp
|
||||
FROM alpine:3 AS temp
|
||||
|
||||
COPY bin/frpc /tmp
|
||||
|
||||
RUN chmod -R 777 /tmp/frpc
|
||||
|
||||
|
||||
FROM alpine:3.12.0
|
||||
FROM alpine:3
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
@@ -1,11 +1,11 @@
|
||||
FROM alpine:3.12.0 AS temp
|
||||
FROM alpine:3 AS temp
|
||||
|
||||
COPY bin/frps /tmp
|
||||
|
||||
RUN chmod -R 777 /tmp/frps
|
||||
|
||||
|
||||
FROM alpine:3.12.0
|
||||
FROM alpine:3
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
10
go.mod
10
go.mod
@@ -6,28 +6,22 @@ require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||
github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185
|
||||
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
|
||||
github.com/go-playground/validator/v10 v10.6.1
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c
|
||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/onsi/ginkgo v1.16.4
|
||||
github.com/onsi/gomega v1.13.0
|
||||
github.com/pires/go-proxyproto v0.5.0
|
||||
github.com/pires/go-proxyproto v0.6.2
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/rodaine/table v1.0.1
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
|
41
go.sum
41
go.sum
@@ -63,6 +63,7 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||
@@ -80,13 +81,15 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
||||
github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185 h1:2p4W5xYizIYwhiGQgeHOQcRD2O84j0tjD40P6gUCRrk=
|
||||
github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185/go.mod h1:MUs+IH/MGJNz5Cj2JVJBPZBKw2exON7LzO3HrJHmGiQ=
|
||||
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac h1:td1FJwN/oz8+9GldeEm3YdBX0Husc0FSPywLesZxi4w=
|
||||
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
@@ -231,10 +234,10 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
|
||||
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
|
||||
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/reedsolomon v1.9.15 h1:g2erWKD2M6rgnPf89fCji6jNlhMKMdXcuNHMW1SYCIo=
|
||||
github.com/klauspost/reedsolomon v1.9.15/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
@@ -297,8 +300,8 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
|
||||
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -371,16 +374,16 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
|
||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
@@ -393,7 +396,6 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -401,6 +403,7 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -442,7 +445,6 @@ golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -461,6 +463,7 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
||||
@@ -619,9 +622,11 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@@ -40,14 +40,20 @@ type OidcClientConfig struct {
|
||||
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
||||
// By default, this value is "".
|
||||
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
|
||||
|
||||
// OidcAdditionalEndpointParams specifies additional parameters to be sent
|
||||
// this field will be transfer to map[string][]string in OIDC token generator
|
||||
// The field will be set by prefix "oidc_additional_"
|
||||
OidcAdditionalEndpointParams map[string]string `ini:"-" json:"oidc_additional_endpoint_params"`
|
||||
}
|
||||
|
||||
func getDefaultOidcClientConf() OidcClientConfig {
|
||||
return OidcClientConfig{
|
||||
OidcClientID: "",
|
||||
OidcClientSecret: "",
|
||||
OidcAudience: "",
|
||||
OidcTokenEndpointURL: "",
|
||||
OidcClientID: "",
|
||||
OidcClientSecret: "",
|
||||
OidcAudience: "",
|
||||
OidcTokenEndpointURL: "",
|
||||
OidcAdditionalEndpointParams: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +94,17 @@ type OidcAuthProvider struct {
|
||||
}
|
||||
|
||||
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider {
|
||||
eps := make(map[string][]string)
|
||||
for k, v := range cfg.OidcAdditionalEndpointParams {
|
||||
eps[k] = []string{v}
|
||||
}
|
||||
|
||||
tokenGenerator := &clientcredentials.Config{
|
||||
ClientID: cfg.OidcClientID,
|
||||
ClientSecret: cfg.OidcClientSecret,
|
||||
Scopes: []string{cfg.OidcAudience},
|
||||
TokenURL: cfg.OidcTokenEndpointURL,
|
||||
ClientID: cfg.OidcClientID,
|
||||
ClientSecret: cfg.OidcClientSecret,
|
||||
Scopes: []string{cfg.OidcAudience},
|
||||
TokenURL: cfg.OidcTokenEndpointURL,
|
||||
EndpointParams: eps,
|
||||
}
|
||||
|
||||
return &OidcAuthProvider{
|
||||
|
@@ -38,6 +38,15 @@ type ClientCommonConf struct {
|
||||
// ServerPort specifies the port to connect to the server on. By default,
|
||||
// this value is 7000.
|
||||
ServerPort int `ini:"server_port" json:"server_port"`
|
||||
// The maximum amount of time a dial to server will wait for a connect to complete.
|
||||
DialServerTimeout int64 `ini:"dial_server_timeout" json:"dial_server_timeout"`
|
||||
// DialServerKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
// If negative, keep-alive probes are disabled.
|
||||
DialServerKeepAlive int64 `ini:"dial_server_keepalive" json:"dial_server_keepalive"`
|
||||
// ConnectServerLocalIP specifies the address of the client bind when it connect to server.
|
||||
// By default, this value is empty.
|
||||
// this value only use in TCP/Websocket protocol. Not support in KCP protocol.
|
||||
ConnectServerLocalIP string `ini:"connect_server_local_ip" json:"connect_server_local_ip"`
|
||||
// HTTPProxy specifies a proxy address to connect to the server through. If
|
||||
// this value is "", the server will be connected to directly. By default,
|
||||
// this value is read from the "http_proxy" environment variable.
|
||||
@@ -86,6 +95,9 @@ 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 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
|
||||
// clients. If this value is not "", proxy names will automatically be
|
||||
// changed to "{user}.{proxy_name}". By default, this value is "".
|
||||
@@ -121,7 +133,7 @@ type ClientCommonConf struct {
|
||||
// It only works when "tls_enable" is valid and tls configuration of server
|
||||
// has been specified.
|
||||
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
||||
// TLSServerName specifices the custom server name of tls certificate. By
|
||||
// TLSServerName specifies the custom server name of tls certificate. By
|
||||
// default, server name if same to ServerAddr.
|
||||
TLSServerName string `ini:"tls_server_name" json:"tls_server_name"`
|
||||
// By default, frpc will connect frps with first custom byte if tls is enabled.
|
||||
@@ -129,11 +141,11 @@ type ClientCommonConf struct {
|
||||
DisableCustomTLSFirstByte bool `ini:"disable_custom_tls_first_byte" json:"disable_custom_tls_first_byte"`
|
||||
// HeartBeatInterval specifies at what interval heartbeats are sent to the
|
||||
// server, in seconds. It is not recommended to change this value. By
|
||||
// default, this value is 30.
|
||||
// default, this value is 30. Set negative value to disable it.
|
||||
HeartbeatInterval int64 `ini:"heartbeat_interval" json:"heartbeat_interval"`
|
||||
// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
|
||||
// before the connection is terminated, in seconds. It is not recommended
|
||||
// to change this value. By default, this value is 90.
|
||||
// to change this value. By default, this value is 90. Set negative value to disable it.
|
||||
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
||||
// Client meta info
|
||||
Metas map[string]string `ini:"-" json:"metas"`
|
||||
@@ -142,41 +154,48 @@ type ClientCommonConf struct {
|
||||
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
|
||||
// Include other config files for proxies.
|
||||
IncludeConfigFiles []string `ini:"includes" json:"includes"`
|
||||
// Enable golang pprof handlers in admin listener.
|
||||
// Admin port must be set first.
|
||||
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
||||
}
|
||||
|
||||
// GetDefaultClientConf returns a client configuration with default values.
|
||||
func GetDefaultClientConf() ClientCommonConf {
|
||||
return ClientCommonConf{
|
||||
ClientConfig: auth.GetDefaultClientConf(),
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
HTTPProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
AdminAddr: "127.0.0.1",
|
||||
AdminPort: 0,
|
||||
AdminUser: "",
|
||||
AdminPwd: "",
|
||||
AssetsDir: "",
|
||||
PoolCount: 1,
|
||||
TCPMux: true,
|
||||
User: "",
|
||||
DNSServer: "",
|
||||
LoginFailExit: true,
|
||||
Start: make([]string, 0),
|
||||
Protocol: "tcp",
|
||||
TLSEnable: false,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSTrustedCaFile: "",
|
||||
HeartbeatInterval: 30,
|
||||
HeartbeatTimeout: 90,
|
||||
Metas: make(map[string]string),
|
||||
UDPPacketSize: 1500,
|
||||
IncludeConfigFiles: make([]string, 0),
|
||||
ClientConfig: auth.GetDefaultClientConf(),
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
DialServerTimeout: 10,
|
||||
DialServerKeepAlive: 7200,
|
||||
HTTPProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
AdminAddr: "127.0.0.1",
|
||||
AdminPort: 0,
|
||||
AdminUser: "",
|
||||
AdminPwd: "",
|
||||
AssetsDir: "",
|
||||
PoolCount: 1,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
User: "",
|
||||
DNSServer: "",
|
||||
LoginFailExit: true,
|
||||
Start: make([]string, 0),
|
||||
Protocol: "tcp",
|
||||
TLSEnable: false,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSTrustedCaFile: "",
|
||||
HeartbeatInterval: 30,
|
||||
HeartbeatTimeout: 90,
|
||||
Metas: make(map[string]string),
|
||||
UDPPacketSize: 1500,
|
||||
IncludeConfigFiles: make([]string, 0),
|
||||
PprofEnable: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,12 +208,10 @@ func (cfg *ClientCommonConf) Complete() {
|
||||
}
|
||||
|
||||
func (cfg *ClientCommonConf) Validate() error {
|
||||
if cfg.HeartbeatInterval <= 0 {
|
||||
return fmt.Errorf("invalid heartbeat_interval")
|
||||
}
|
||||
|
||||
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||
if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
|
||||
if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
|
||||
return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.TLSEnable == false {
|
||||
@@ -252,6 +269,8 @@ func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
||||
}
|
||||
|
||||
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
||||
common.ClientConfig.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
|
||||
|
||||
return common, nil
|
||||
}
|
||||
|
||||
|
@@ -259,33 +259,36 @@ func Test_LoadClientCommonConf(t *testing.T) {
|
||||
OidcTokenEndpointURL: "endpoint_url",
|
||||
},
|
||||
},
|
||||
ServerAddr: "0.0.0.9",
|
||||
ServerPort: 7009,
|
||||
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
|
||||
LogFile: "./frpc.log9",
|
||||
LogWay: "file",
|
||||
LogLevel: "info9",
|
||||
LogMaxDays: 39,
|
||||
DisableLogColor: false,
|
||||
AdminAddr: "127.0.0.9",
|
||||
AdminPort: 7409,
|
||||
AdminUser: "admin9",
|
||||
AdminPwd: "admin9",
|
||||
AssetsDir: "./static9",
|
||||
PoolCount: 59,
|
||||
TCPMux: true,
|
||||
User: "your_name",
|
||||
LoginFailExit: true,
|
||||
Protocol: "tcp",
|
||||
TLSEnable: true,
|
||||
TLSCertFile: "client.crt",
|
||||
TLSKeyFile: "client.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
TLSServerName: "example.com",
|
||||
DNSServer: "8.8.8.9",
|
||||
Start: []string{"ssh", "dns"},
|
||||
HeartbeatInterval: 39,
|
||||
HeartbeatTimeout: 99,
|
||||
ServerAddr: "0.0.0.9",
|
||||
ServerPort: 7009,
|
||||
DialServerTimeout: 10,
|
||||
DialServerKeepAlive: 7200,
|
||||
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
|
||||
LogFile: "./frpc.log9",
|
||||
LogWay: "file",
|
||||
LogLevel: "info9",
|
||||
LogMaxDays: 39,
|
||||
DisableLogColor: false,
|
||||
AdminAddr: "127.0.0.9",
|
||||
AdminPort: 7409,
|
||||
AdminUser: "admin9",
|
||||
AdminPwd: "admin9",
|
||||
AssetsDir: "./static9",
|
||||
PoolCount: 59,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
User: "your_name",
|
||||
LoginFailExit: true,
|
||||
Protocol: "tcp",
|
||||
TLSEnable: true,
|
||||
TLSCertFile: "client.crt",
|
||||
TLSKeyFile: "client.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
TLSServerName: "example.com",
|
||||
DNSServer: "8.8.8.9",
|
||||
Start: []string{"ssh", "dns"},
|
||||
HeartbeatInterval: 39,
|
||||
HeartbeatTimeout: 99,
|
||||
Metas: map[string]string{
|
||||
"var1": "123",
|
||||
"var2": "234",
|
||||
|
@@ -118,6 +118,12 @@ 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 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.
|
||||
// If negative, keep-alive probes are disabled.
|
||||
TCPKeepAlive int64 `ini:"tcp_keepalive" json:"tcp_keepalive"`
|
||||
// Custom404Page specifies a path to a custom 404 page to display. If this
|
||||
// value is "", a default page will be displayed. By default, this value is
|
||||
// "".
|
||||
@@ -154,7 +160,7 @@ type ServerCommonConf struct {
|
||||
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
||||
// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
|
||||
// before terminating the connection. It is not recommended to change this
|
||||
// value. By default, this value is 90.
|
||||
// value. By default, this value is 90. Set negative value to disable it.
|
||||
HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
|
||||
// UserConnTimeout specifies the maximum time to wait for a work
|
||||
// connection. By default, this value is 10.
|
||||
@@ -164,48 +170,54 @@ type ServerCommonConf struct {
|
||||
// UDPPacketSize specifies the UDP packet size
|
||||
// By default, this value is 1500
|
||||
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
|
||||
// Enable golang pprof handlers in dashboard listener.
|
||||
// Dashboard port must be set first.
|
||||
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
||||
}
|
||||
|
||||
// GetDefaultServerConf returns a server configuration with reasonable
|
||||
// defaults.
|
||||
func GetDefaultServerConf() ServerCommonConf {
|
||||
return ServerCommonConf{
|
||||
ServerConfig: auth.GetDefaultServerConf(),
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
BindUDPPort: 0,
|
||||
KCPBindPort: 0,
|
||||
ProxyBindAddr: "",
|
||||
VhostHTTPPort: 0,
|
||||
VhostHTTPSPort: 0,
|
||||
TCPMuxHTTPConnectPort: 0,
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "",
|
||||
DashboardPwd: "",
|
||||
EnablePrometheus: false,
|
||||
AssetsDir: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
DetailedErrorsToClient: true,
|
||||
SubDomainHost: "",
|
||||
TCPMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
TLSOnly: false,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSTrustedCaFile: "",
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
Custom404Page: "",
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
ServerConfig: auth.GetDefaultServerConf(),
|
||||
BindAddr: "0.0.0.0",
|
||||
BindPort: 7000,
|
||||
BindUDPPort: 0,
|
||||
KCPBindPort: 0,
|
||||
ProxyBindAddr: "",
|
||||
VhostHTTPPort: 0,
|
||||
VhostHTTPSPort: 0,
|
||||
TCPMuxHTTPConnectPort: 0,
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardPort: 0,
|
||||
DashboardUser: "",
|
||||
DashboardPwd: "",
|
||||
EnablePrometheus: false,
|
||||
AssetsDir: "",
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DisableLogColor: false,
|
||||
DetailedErrorsToClient: true,
|
||||
SubDomainHost: "",
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
TLSOnly: false,
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSTrustedCaFile: "",
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
Custom404Page: "",
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
PprofEnable: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -131,15 +131,17 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
||||
12: struct{}{},
|
||||
99: struct{}{},
|
||||
},
|
||||
MaxPoolCount: 59,
|
||||
MaxPortsPerClient: 9,
|
||||
TLSOnly: true,
|
||||
TLSCertFile: "server.crt",
|
||||
TLSKeyFile: "server.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
SubDomainHost: "frps.com",
|
||||
TCPMux: true,
|
||||
UDPPacketSize: 1509,
|
||||
MaxPoolCount: 59,
|
||||
MaxPortsPerClient: 9,
|
||||
TLSOnly: true,
|
||||
TLSCertFile: "server.crt",
|
||||
TLSKeyFile: "server.key",
|
||||
TLSTrustedCaFile: "ca.crt",
|
||||
SubDomainHost: "frps.com",
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
UDPPacketSize: 1509,
|
||||
|
||||
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
||||
"user-manager": {
|
||||
@@ -174,27 +176,29 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
||||
AuthenticateNewWorkConns: false,
|
||||
},
|
||||
},
|
||||
BindAddr: "0.0.0.9",
|
||||
BindPort: 7009,
|
||||
BindUDPPort: 7008,
|
||||
ProxyBindAddr: "0.0.0.9",
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardUser: "",
|
||||
DashboardPwd: "",
|
||||
EnablePrometheus: false,
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DetailedErrorsToClient: true,
|
||||
TCPMux: true,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
BindAddr: "0.0.0.9",
|
||||
BindPort: 7009,
|
||||
BindUDPPort: 7008,
|
||||
ProxyBindAddr: "0.0.0.9",
|
||||
VhostHTTPTimeout: 60,
|
||||
DashboardAddr: "0.0.0.0",
|
||||
DashboardUser: "",
|
||||
DashboardPwd: "",
|
||||
EnablePrometheus: false,
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
LogLevel: "info",
|
||||
LogMaxDays: 3,
|
||||
DetailedErrorsToClient: true,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
HeartbeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@@ -29,11 +29,11 @@ func init() {
|
||||
glbEnvs = make(map[string]string)
|
||||
envs := os.Environ()
|
||||
for _, env := range envs {
|
||||
kv := strings.Split(env, "=")
|
||||
if len(kv) != 2 {
|
||||
pair := strings.SplitN(env, "=", 2)
|
||||
if len(pair) != 2 {
|
||||
continue
|
||||
}
|
||||
glbEnvs[kv[0]] = kv[1]
|
||||
glbEnvs[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
type Manager struct {
|
||||
loginPlugins []Plugin
|
||||
newProxyPlugins []Plugin
|
||||
closeProxyPlugins []Plugin
|
||||
pingPlugins []Plugin
|
||||
newWorkConnPlugins []Plugin
|
||||
newUserConnPlugins []Plugin
|
||||
@@ -35,6 +37,7 @@ func NewManager() *Manager {
|
||||
return &Manager{
|
||||
loginPlugins: make([]Plugin, 0),
|
||||
newProxyPlugins: make([]Plugin, 0),
|
||||
closeProxyPlugins: make([]Plugin, 0),
|
||||
pingPlugins: make([]Plugin, 0),
|
||||
newWorkConnPlugins: make([]Plugin, 0),
|
||||
newUserConnPlugins: make([]Plugin, 0),
|
||||
@@ -48,6 +51,9 @@ func (m *Manager) Register(p Plugin) {
|
||||
if p.IsSupport(OpNewProxy) {
|
||||
m.newProxyPlugins = append(m.newProxyPlugins, p)
|
||||
}
|
||||
if p.IsSupport(OpCloseProxy) {
|
||||
m.closeProxyPlugins = append(m.closeProxyPlugins, p)
|
||||
}
|
||||
if p.IsSupport(OpPing) {
|
||||
m.pingPlugins = append(m.pingPlugins, p)
|
||||
}
|
||||
@@ -127,6 +133,32 @@ func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (m *Manager) CloseProxy(content *CloseProxyContent) error {
|
||||
if len(m.closeProxyPlugins) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := make([]string, 0)
|
||||
reqid, _ := util.RandID()
|
||||
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||
ctx := xlog.NewContext(context.Background(), xl)
|
||||
ctx = NewReqidContext(ctx, reqid)
|
||||
|
||||
for _, p := range m.closeProxyPlugins {
|
||||
_, _, err := p.Handle(ctx, OpCloseProxy, *content)
|
||||
if err != nil {
|
||||
xl.Warn("send CloseProxy request to plugin [%s] error: %v", p.Name(), err)
|
||||
errs = append(errs, fmt.Sprintf("[%s]: %v", p.Name(), err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("send CloseProxy request to plugin errors: %s", strings.Join(errs, "; "))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Ping(content *PingContent) (*PingContent, error) {
|
||||
if len(m.pingPlugins) == 0 {
|
||||
return content, nil
|
||||
|
@@ -23,6 +23,7 @@ const (
|
||||
|
||||
OpLogin = "Login"
|
||||
OpNewProxy = "NewProxy"
|
||||
OpCloseProxy = "CloseProxy"
|
||||
OpPing = "Ping"
|
||||
OpNewWorkConn = "NewWorkConn"
|
||||
OpNewUserConn = "NewUserConn"
|
||||
|
@@ -33,6 +33,8 @@ type Response struct {
|
||||
|
||||
type LoginContent struct {
|
||||
msg.Login
|
||||
|
||||
ClientAddress string `json:"client_address,omitempty"`
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
@@ -46,6 +48,11 @@ type NewProxyContent struct {
|
||||
msg.NewProxy
|
||||
}
|
||||
|
||||
type CloseProxyContent struct {
|
||||
User UserInfo `json:"user"`
|
||||
msg.CloseProxy
|
||||
}
|
||||
|
||||
type PingContent struct {
|
||||
User UserInfo `json:"user"`
|
||||
msg.Ping
|
||||
|
@@ -100,6 +100,8 @@ func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Conf
|
||||
base.Certificates = []tls.Certificate{*cert}
|
||||
}
|
||||
|
||||
base.ServerName = serverName
|
||||
|
||||
if caPath != "" {
|
||||
pool, err := newCertPool(caPath)
|
||||
if err != nil {
|
||||
@@ -107,7 +109,6 @@ func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Conf
|
||||
}
|
||||
|
||||
base.RootCAs = pool
|
||||
base.ServerName = serverName
|
||||
base.InsecureSkipVerify = false
|
||||
} else {
|
||||
base.InsecureSkipVerify = true
|
||||
|
@@ -16,18 +16,13 @@ package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
|
||||
gnet "github.com/fatedier/golib/net"
|
||||
kcp "github.com/fatedier/kcp-go"
|
||||
)
|
||||
|
||||
type ContextGetter interface {
|
||||
@@ -188,56 +183,3 @@ func (statsConn *StatsConn) Close() (err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ConnectServer(protocol string, addr string) (c net.Conn, err error) {
|
||||
switch protocol {
|
||||
case "tcp":
|
||||
return net.Dial("tcp", addr)
|
||||
case "kcp":
|
||||
kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
kcpConn.SetStreamMode(true)
|
||||
kcpConn.SetWriteDelay(true)
|
||||
kcpConn.SetNoDelay(1, 20, 2, 1)
|
||||
kcpConn.SetWindowSize(128, 512)
|
||||
kcpConn.SetMtu(1350)
|
||||
kcpConn.SetACKNoDelay(false)
|
||||
kcpConn.SetReadBuffer(4194304)
|
||||
kcpConn.SetWriteBuffer(4194304)
|
||||
c = kcpConn
|
||||
return
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func ConnectServerByProxy(proxyURL string, protocol string, addr string) (c net.Conn, err error) {
|
||||
switch protocol {
|
||||
case "tcp":
|
||||
return gnet.DialTcpByProxy(proxyURL, addr)
|
||||
case "kcp":
|
||||
// http proxy is not supported for kcp
|
||||
return ConnectServer(protocol, addr)
|
||||
case "websocket":
|
||||
return ConnectWebsocketServer(addr)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func ConnectServerByProxyWithTLS(proxyURL string, protocol string, addr string, tlsConfig *tls.Config, disableCustomTLSHeadByte bool) (c net.Conn, err error) {
|
||||
c, err = ConnectServerByProxy(proxyURL, protocol, addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tlsConfig == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c = WrapTLSClientConn(c, tlsConfig, disableCustomTLSHeadByte)
|
||||
return
|
||||
}
|
||||
|
44
pkg/util/net/dial.go
Normal file
44
pkg/util/net/dial.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
func DialHookCustomTLSHeadByte(enableTLS bool, disableCustomTLSHeadByte bool) libdial.AfterHookFunc {
|
||||
return func(ctx context.Context, c net.Conn, addr string) (context.Context, net.Conn, error) {
|
||||
if enableTLS && !disableCustomTLSHeadByte {
|
||||
_, err := c.Write([]byte{byte(FRPTLSHeadByte)})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return ctx, c, nil
|
||||
}
|
||||
}
|
||||
|
||||
func DialHookWebsocket() libdial.AfterHookFunc {
|
||||
return func(ctx context.Context, c net.Conn, addr string) (context.Context, net.Conn, error) {
|
||||
addr = "ws://" + addr + FrpWebsocketPath
|
||||
uri, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
origin := "http://" + uri.Host
|
||||
cfg, err := websocket.NewConfig(addr, origin)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
conn, err := websocket.NewClient(cfg, c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return ctx, conn, nil
|
||||
}
|
||||
}
|
@@ -27,14 +27,6 @@ var (
|
||||
FRPTLSHeadByte = 0x17
|
||||
)
|
||||
|
||||
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config, disableCustomTLSHeadByte bool) (out net.Conn) {
|
||||
if !disableCustomTLSHeadByte {
|
||||
c.Write([]byte{byte(FRPTLSHeadByte)})
|
||||
}
|
||||
out = tls.Client(c, tlsConfig)
|
||||
return
|
||||
}
|
||||
|
||||
func CheckAndEnableTLSServerConnWithTimeout(
|
||||
c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration,
|
||||
) (out net.Conn, isTLS bool, custom bool, err error) {
|
||||
|
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -163,7 +164,7 @@ type UDPListener struct {
|
||||
}
|
||||
|
||||
func ListenUDP(bindAddr string, bindPort int) (l *UDPListener, err error) {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(bindAddr, strconv.Itoa(bindPort)))
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
|
@@ -2,11 +2,9 @@ package net
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
@@ -54,7 +52,7 @@ func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
|
||||
}
|
||||
|
||||
func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) {
|
||||
tcpLn, err := net.Listen("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
||||
tcpLn, err := net.Listen("tcp", net.JoinHostPort(bindAddr, strconv.Itoa(bindPort)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -77,27 +75,3 @@ func (p *WebsocketListener) Close() error {
|
||||
func (p *WebsocketListener) Addr() net.Addr {
|
||||
return p.ln.Addr()
|
||||
}
|
||||
|
||||
// addr: domain:port
|
||||
func ConnectWebsocketServer(addr string) (net.Conn, error) {
|
||||
addr = "ws://" + addr + FrpWebsocketPath
|
||||
uri, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
origin := "http://" + uri.Host
|
||||
cfg, err := websocket.NewConfig(addr, origin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Dialer = &net.Dialer{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
conn, err := websocket.DialConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
@@ -48,7 +48,7 @@ func readHTTPConnectRequest(rd io.Reader) (host string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
host = util.GetHostFromAddr(req.Host)
|
||||
host, _ = util.CanonicalHost(req.Host)
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -34,17 +34,6 @@ func OkResponse() *http.Response {
|
||||
return res
|
||||
}
|
||||
|
||||
// TODO: use "CanonicalHost" func to replace all "GetHostFromAddr" func.
|
||||
func GetHostFromAddr(addr string) (host string) {
|
||||
strs := strings.Split(addr, ":")
|
||||
if len(strs) > 1 {
|
||||
host = strs[0]
|
||||
} else {
|
||||
host = addr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// canonicalHost strips port from host if present and returns the canonicalized
|
||||
// host name.
|
||||
func CanonicalHost(host string) (string, error) {
|
||||
|
@@ -19,8 +19,11 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RandID return a rand string used in frp.
|
||||
@@ -52,7 +55,7 @@ func CanonicalAddr(host string, port int) (addr string) {
|
||||
if port == 80 || port == 443 {
|
||||
addr = host
|
||||
} else {
|
||||
addr = fmt.Sprintf("%s:%d", host, port)
|
||||
addr = net.JoinHostPort(host, strconv.Itoa(port))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -108,3 +111,17 @@ func GenerateResponseErrorString(summary string, err error, detailed bool) strin
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Duration {
|
||||
min := int64(minRatio * 1000.0)
|
||||
max := int64(maxRatio * 1000.0)
|
||||
var n int64
|
||||
if max <= min {
|
||||
n = min
|
||||
} else {
|
||||
n = mathrand.Int63n(max-min) + min
|
||||
}
|
||||
d := duration * time.Duration(n) / time.Duration(1000)
|
||||
time.Sleep(d)
|
||||
return d
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var version string = "0.38.0"
|
||||
var version string = "0.42.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
@@ -59,7 +59,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "http"
|
||||
url := req.Context().Value(RouteInfoURL).(string)
|
||||
oldHost := util.GetHostFromAddr(req.Context().Value(RouteInfoHost).(string))
|
||||
oldHost, _ := util.CanonicalHost(req.Context().Value(RouteInfoHost).(string))
|
||||
rc := rp.GetRouteConfig(oldHost, url)
|
||||
if rc != nil {
|
||||
if rc.RewriteHost != "" {
|
||||
@@ -81,7 +81,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
IdleConnTimeout: 60 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
url := ctx.Value(RouteInfoURL).(string)
|
||||
host := util.GetHostFromAddr(ctx.Value(RouteInfoHost).(string))
|
||||
host, _ := util.CanonicalHost(ctx.Value(RouteInfoHost).(string))
|
||||
remote := ctx.Value(RouteInfoRemote).(string)
|
||||
return rp.CreateConnection(host, url, remote)
|
||||
},
|
||||
@@ -89,7 +89,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
BufferPool: newWrapPool(),
|
||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
frpLog.Warn("do http proxy request error: %v", err)
|
||||
frpLog.Warn("do http proxy request [host: %s] error: %v", req.Host, err)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
rw.Write(getNotFoundPageContent())
|
||||
},
|
||||
@@ -191,7 +191,7 @@ func (rp *HTTPReverseProxy) getVhost(domain string, location string) (vr *Router
|
||||
}
|
||||
|
||||
func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
domain := util.GetHostFromAddr(req.Host)
|
||||
domain, _ := util.CanonicalHost(req.Host)
|
||||
location := req.URL.Path
|
||||
user, passwd, _ := req.BasicAuth()
|
||||
if !rp.CheckAuth(domain, location, user, passwd) {
|
||||
|
@@ -258,7 +258,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||
case workConn, ok = <-ctl.workConnCh:
|
||||
if !ok {
|
||||
err = frpErr.ErrCtlClosed
|
||||
xl.Warn("no work connections avaiable, %v", err)
|
||||
xl.Warn("no work connections available, %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -376,6 +376,20 @@ func (ctl *Control) stoper() {
|
||||
pxy.Close()
|
||||
ctl.pxyManager.Del(pxy.GetName())
|
||||
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||
|
||||
notifyContent := &plugin.CloseProxyContent{
|
||||
User: plugin.UserInfo{
|
||||
User: ctl.loginMsg.User,
|
||||
Metas: ctl.loginMsg.Metas,
|
||||
RunID: ctl.loginMsg.RunID,
|
||||
},
|
||||
CloseProxy: msg.CloseProxy{
|
||||
ProxyName: pxy.GetName(),
|
||||
},
|
||||
}
|
||||
go func() {
|
||||
ctl.pluginManager.CloseProxy(notifyContent)
|
||||
}()
|
||||
}
|
||||
|
||||
ctl.allShutdown.Done()
|
||||
@@ -400,12 +414,19 @@ func (ctl *Control) manager() {
|
||||
defer ctl.allShutdown.Start()
|
||||
defer ctl.managerShutdown.Done()
|
||||
|
||||
heartbeat := time.NewTicker(time.Second)
|
||||
defer heartbeat.Stop()
|
||||
var heartbeatCh <-chan time.Time
|
||||
if ctl.serverCfg.TCPMux || ctl.serverCfg.HeartbeatTimeout <= 0 {
|
||||
// Don't need application heartbeat here.
|
||||
// yamux will do same thing.
|
||||
} else {
|
||||
heartbeat := time.NewTicker(time.Second)
|
||||
defer heartbeat.Stop()
|
||||
heartbeatCh = heartbeat.C
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-heartbeat.C:
|
||||
case <-heartbeatCh:
|
||||
if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartbeatTimeout)*time.Second {
|
||||
xl.Warn("heartbeat timeout")
|
||||
return
|
||||
@@ -557,5 +578,20 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||
ctl.mu.Unlock()
|
||||
|
||||
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||
|
||||
notifyContent := &plugin.CloseProxyContent{
|
||||
User: plugin.UserInfo{
|
||||
User: ctl.loginMsg.User,
|
||||
Metas: ctl.loginMsg.Metas,
|
||||
RunID: ctl.loginMsg.RunID,
|
||||
},
|
||||
CloseProxy: msg.CloseProxy{
|
||||
ProxyName: pxy.GetName(),
|
||||
},
|
||||
}
|
||||
go func() {
|
||||
ctl.pluginManager.CloseProxy(notifyContent)
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ package server
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
@@ -27,34 +28,45 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerReadTimeout = 10 * time.Second
|
||||
httpServerWriteTimeout = 10 * time.Second
|
||||
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.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.DashboardUser, svr.cfg.DashboardPwd
|
||||
router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
||||
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
||||
|
||||
// metrics
|
||||
if svr.cfg.EnablePrometheus {
|
||||
router.Handle("/metrics", promhttp.Handler())
|
||||
subRouter.Handle("/metrics", promhttp.Handler())
|
||||
}
|
||||
|
||||
// api, see dashboard_api.go
|
||||
router.HandleFunc("/api/serverinfo", svr.APIServerInfo).Methods("GET")
|
||||
router.HandleFunc("/api/proxy/{type}", svr.APIProxyByType).Methods("GET")
|
||||
router.HandleFunc("/api/proxy/{type}/{name}", svr.APIProxyByTypeAndName).Methods("GET")
|
||||
router.HandleFunc("/api/traffic/{name}", svr.APIProxyTraffic).Methods("GET")
|
||||
router.HandleFunc("/healthz", svr.Healthz)
|
||||
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
|
||||
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
router.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
|
@@ -15,8 +15,8 @@
|
||||
package group
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/server/ports"
|
||||
@@ -101,7 +101,7 @@ func (tg *TCPGroup) Listen(proxyName string, group string, groupKey string, addr
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tcpLn, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port))
|
||||
tcpLn, errRet := net.Listen("tcp", net.JoinHostPort(addr, strconv.Itoa(port)))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
|
@@ -2,8 +2,8 @@ package ports
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -134,7 +134,7 @@ func (pm *Manager) Acquire(name string, port int) (realPort int, err error) {
|
||||
|
||||
func (pm *Manager) isPortAvailable(port int) bool {
|
||||
if pm.netType == "udp" {
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pm.bindAddr, port))
|
||||
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(pm.bindAddr, strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -146,7 +146,7 @@ func (pm *Manager) isPortAvailable(port int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port))
|
||||
l, err := net.Listen(pm.netType, net.JoinHostPort(pm.bindAddr, strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ package proxy
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
)
|
||||
@@ -54,7 +55,7 @@ func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
|
||||
pxy.rc.TCPPortManager.Release(pxy.realPort)
|
||||
}
|
||||
}()
|
||||
listener, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
|
||||
listener, errRet := net.Listen("tcp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realPort)))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
@@ -70,7 +71,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
|
||||
|
||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||
pxy.cfg.RemotePort = pxy.realPort
|
||||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
|
||||
addr, errRet := net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realPort)))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
|
@@ -124,7 +124,8 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
// Create tcpmux httpconnect multiplexer.
|
||||
if cfg.TCPMuxHTTPConnectPort > 0 {
|
||||
var l net.Listener
|
||||
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort))
|
||||
address := net.JoinHostPort(cfg.ProxyBindAddr, strconv.Itoa(cfg.TCPMuxHTTPConnectPort))
|
||||
l, err = net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create server listener error, %v", err)
|
||||
return
|
||||
@@ -135,7 +136,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
err = fmt.Errorf("Create vhost tcpMuxer error, %v", err)
|
||||
return
|
||||
}
|
||||
log.Info("tcpmux httpconnect multiplexer listen on %s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort)
|
||||
log.Info("tcpmux httpconnect multiplexer listen on %s", address)
|
||||
}
|
||||
|
||||
// Init all plugins
|
||||
@@ -185,6 +186,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
}
|
||||
|
||||
svr.muxer = mux.NewMux(ln)
|
||||
svr.muxer.SetKeepAlive(time.Duration(cfg.TCPKeepAlive) * time.Second)
|
||||
go svr.muxer.Serve()
|
||||
ln = svr.muxer.DefaultListener()
|
||||
|
||||
@@ -199,7 +201,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
err = fmt.Errorf("Listen on kcp address udp %s error: %v", address, err)
|
||||
return
|
||||
}
|
||||
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KCPBindPort)
|
||||
log.Info("frps kcp listen on udp %s", address)
|
||||
}
|
||||
|
||||
// Listen for accepting connections from client using websocket protocol.
|
||||
@@ -232,7 +234,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
}
|
||||
}
|
||||
go server.Serve(l)
|
||||
log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHTTPPort)
|
||||
log.Info("http service listen on %s", address)
|
||||
}
|
||||
|
||||
// Create https vhost muxer.
|
||||
@@ -288,7 +290,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
||||
return
|
||||
}
|
||||
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
|
||||
log.Info("Dashboard listen on %s", address)
|
||||
statsEnable = true
|
||||
}
|
||||
if statsEnable {
|
||||
@@ -334,7 +336,8 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn) {
|
||||
case *msg.Login:
|
||||
// server plugin hook
|
||||
content := &plugin.LoginContent{
|
||||
Login: *m,
|
||||
Login: *m,
|
||||
ClientAddress: conn.RemoteAddr().String(),
|
||||
}
|
||||
retContent, err := svr.pluginManager.Login(content)
|
||||
if err == nil {
|
||||
@@ -405,7 +408,7 @@ func (svr *Service) HandleListener(l net.Listener) {
|
||||
go func(ctx context.Context, frpConn net.Conn) {
|
||||
if svr.cfg.TCPMux {
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||
fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second
|
||||
fmuxCfg.LogOutput = io.Discard
|
||||
session, err := fmux.Server(frpConn, fmuxCfg)
|
||||
if err != nil {
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||
clientsdk "github.com/fatedier/frp/test/e2e/pkg/sdk/client"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
@@ -75,4 +76,28 @@ var _ = Describe("[Feature: ClientManage]", func() {
|
||||
framework.NewRequestExpect(f).Port(newP2Port).Explain("new p2 port").Ensure()
|
||||
framework.NewRequestExpect(f).Port(p3Port).Explain("p3 port").ExpectError(true).Ensure()
|
||||
})
|
||||
|
||||
It("healthz", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
|
||||
dashboardPort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
admin_addr = 0.0.0.0
|
||||
admin_port = %d
|
||||
admin_user = admin
|
||||
admin_pwd = admin
|
||||
`, dashboardPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
|
||||
r.HTTP().HTTPPath("/healthz")
|
||||
}).Port(dashboardPort).ExpectResp([]byte("")).Ensure()
|
||||
|
||||
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
|
||||
r.HTTP().HTTPPath("/")
|
||||
}).Port(dashboardPort).
|
||||
Ensure(framework.ExpectResponseCode(401))
|
||||
})
|
||||
|
||||
})
|
||||
|
@@ -249,4 +249,23 @@ var _ = Describe("[Feature: Client-Server]", func() {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
Describe("IPv6 bind address", func() {
|
||||
supportProtocols := []string{"tcp", "kcp", "websocket"}
|
||||
for _, protocol := range supportProtocols {
|
||||
tmp := protocol
|
||||
defineClientServerTest("IPv6 bind address: "+strings.ToUpper(tmp), f, &generalTestConfigures{
|
||||
server: fmt.Sprintf(`
|
||||
bind_addr = ::
|
||||
kcp_bind_port = {{ .%s }}
|
||||
protocol = %s
|
||||
`, consts.PortServerName, protocol),
|
||||
client: fmt.Sprintf(`
|
||||
tls_enable = true
|
||||
protocol = %s
|
||||
disable_custom_tls_first_byte = true
|
||||
`, protocol),
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@@ -144,4 +144,36 @@ var _ = Describe("[Feature: Server Manager]", func() {
|
||||
r.HTTP().HTTPHost("example.com")
|
||||
}).PortName(consts.PortServerName).Ensure()
|
||||
})
|
||||
|
||||
It("healthz", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
dashboardPort := f.AllocPort()
|
||||
|
||||
// Use same port as PortServer
|
||||
serverConf += fmt.Sprintf(`
|
||||
vhost_http_port = {{ .%s }}
|
||||
dashboard_addr = 0.0.0.0
|
||||
dashboard_port = %d
|
||||
dashboard_user = admin
|
||||
dashboard_pwd = admin
|
||||
`, consts.PortServerName, dashboardPort)
|
||||
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
[http]
|
||||
type = http
|
||||
local_port = {{ .%s }}
|
||||
custom_domains = example.com
|
||||
`, framework.HTTPSimpleServerPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
|
||||
r.HTTP().HTTPPath("/healthz")
|
||||
}).Port(dashboardPort).ExpectResp([]byte("")).Ensure()
|
||||
|
||||
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
|
||||
r.HTTP().HTTPPath("/")
|
||||
}).Port(dashboardPort).
|
||||
Ensure(framework.ExpectResponseCode(401))
|
||||
})
|
||||
})
|
||||
|
48
test/e2e/features/heartbeat.go
Normal file
48
test/e2e/features/heartbeat.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
)
|
||||
|
||||
var _ = Describe("[Feature: Heartbeat]", func() {
|
||||
f := framework.NewDefaultFramework()
|
||||
|
||||
It("disable application layer heartbeat", func() {
|
||||
serverPort := f.AllocPort()
|
||||
serverConf := fmt.Sprintf(`
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = %d
|
||||
heartbeat_timeout = -1
|
||||
tcp_mux_keepalive_interval = 2
|
||||
`, serverPort)
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf := fmt.Sprintf(`
|
||||
[common]
|
||||
server_port = %d
|
||||
log_level = trace
|
||||
heartbeat_interval = -1
|
||||
heartbeat_timeout = -1
|
||||
tcp_mux_keepalive_interval = 2
|
||||
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = %d
|
||||
remote_port = %d
|
||||
`, serverPort, f.PortByName(framework.TCPEchoServerPort), remotePort)
|
||||
|
||||
// run frps and frpc
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure()
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
framework.NewRequestExpect(f).Protocol("tcp").Port(remotePort).Ensure()
|
||||
})
|
||||
})
|
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// RunProcesses run multiple processes from templates.
|
||||
// The first template should always be frps.
|
||||
func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) {
|
||||
func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) ([]*process.Process, []*process.Process) {
|
||||
templates := make([]string, 0, len(serverTemplates)+len(clientTemplates))
|
||||
for _, t := range serverTemplates {
|
||||
templates = append(templates, t)
|
||||
@@ -28,6 +28,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
||||
f.usedPorts[name] = port
|
||||
}
|
||||
|
||||
currentServerProcesses := make([]*process.Process, 0, len(serverTemplates))
|
||||
for i := range serverTemplates {
|
||||
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
|
||||
err = os.WriteFile(path, []byte(outs[i]), 0666)
|
||||
@@ -37,11 +38,13 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
||||
p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs)
|
||||
f.serverConfPaths = append(f.serverConfPaths, path)
|
||||
f.serverProcesses = append(f.serverProcesses, p)
|
||||
currentServerProcesses = append(currentServerProcesses, p)
|
||||
err = p.Start()
|
||||
ExpectNoError(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
currentClientProcesses := make([]*process.Process, 0, len(clientTemplates))
|
||||
for i := range clientTemplates {
|
||||
index := i + len(serverTemplates)
|
||||
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i))
|
||||
@@ -52,11 +55,14 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
||||
p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs)
|
||||
f.clientConfPaths = append(f.clientConfPaths, path)
|
||||
f.clientProcesses = append(f.clientProcesses, p)
|
||||
currentClientProcesses = append(currentClientProcesses, p)
|
||||
err = p.Start()
|
||||
ExpectNoError(err)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
return currentServerProcesses, currentClientProcesses
|
||||
}
|
||||
|
||||
func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) {
|
||||
|
@@ -113,7 +113,7 @@ func (e *RequestExpect) Ensure(fns ...EnsureFunc) {
|
||||
if !bytes.Equal(e.expectResp, ret.Content) {
|
||||
flog.Trace("Response info: %+v", ret)
|
||||
}
|
||||
ExpectEqualValuesWithOffset(1, e.expectResp, ret.Content, e.explain...)
|
||||
ExpectEqualValuesWithOffset(1, ret.Content, e.expectResp, e.explain...)
|
||||
} else {
|
||||
for _, fn := range fns {
|
||||
ok := fn(ret)
|
||||
|
@@ -2,7 +2,6 @@ package httpserver
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -97,7 +96,7 @@ func (s *Server) Close() error {
|
||||
}
|
||||
|
||||
func (s *Server) initListener() (err error) {
|
||||
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
|
||||
s.l, err = net.Listen("tcp", net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort)))
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
libnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/rpc"
|
||||
@@ -99,7 +100,7 @@ func (s *Server) Close() error {
|
||||
func (s *Server) initListener() (err error) {
|
||||
switch s.netType {
|
||||
case TCP:
|
||||
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
|
||||
s.l, err = net.Listen("tcp", net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort)))
|
||||
case UDP:
|
||||
s.l, err = libnet.ListenUDP(s.bindAddr, s.bindPort)
|
||||
case Unix:
|
||||
|
@@ -3,6 +3,7 @@ package port
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
@@ -57,7 +58,7 @@ func (pa *Allocator) GetByName(portName string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
l, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
// Maybe not controlled by us, mark it used.
|
||||
pa.used.Insert(port)
|
||||
@@ -65,7 +66,7 @@ func (pa *Allocator) GetByName(portName string) int {
|
||||
}
|
||||
l.Close()
|
||||
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/pkg/rpc"
|
||||
libnet "github.com/fatedier/golib/net"
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
@@ -141,7 +141,11 @@ func (r *Request) Do() (*Response, error) {
|
||||
if r.protocol != "tcp" {
|
||||
return nil, fmt.Errorf("only tcp protocol is allowed for proxy")
|
||||
}
|
||||
conn, err = libnet.DialTcpByProxy(r.proxyURL, addr)
|
||||
proxyType, proxyAddress, auth, err := libdial.ParseProxyURL(r.proxyURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse ProxyURL error: %v", err)
|
||||
}
|
||||
conn, err = libdial.Dial(addr, libdial.WithProxy(proxyType, proxyAddress), libdial.WithProxyAuth(auth))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -24,9 +24,14 @@ var _ = Describe("[Feature: Server-Plugins]", func() {
|
||||
|
||||
It("Auth for custom meta token", func() {
|
||||
localPort := f.AllocPort()
|
||||
|
||||
clientAddressGot := false
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.LoginContent)
|
||||
if content.ClientAddress != "" {
|
||||
clientAddressGot = true
|
||||
}
|
||||
if content.Metas["token"] == "123" {
|
||||
ret.Unchange = true
|
||||
} else {
|
||||
@@ -69,6 +74,8 @@ var _ = Describe("[Feature: Server-Plugins]", func() {
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
framework.NewRequestExpect(f).Port(remotePort2).ExpectError(true).Ensure()
|
||||
|
||||
framework.ExpectTrue(clientAddressGot)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -143,7 +150,7 @@ var _ = Describe("[Feature: Server-Plugins]", func() {
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = 0
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
`, framework.TCPEchoServerPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
@@ -151,6 +158,56 @@ var _ = Describe("[Feature: Server-Plugins]", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("CloseProxy", func() {
|
||||
newFunc := func() *plugin.Request {
|
||||
var r plugin.Request
|
||||
r.Content = &plugin.CloseProxyContent{}
|
||||
return &r
|
||||
}
|
||||
|
||||
It("Validate Info", func() {
|
||||
localPort := f.AllocPort()
|
||||
var recordProxyName string
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.CloseProxyContent)
|
||||
recordProxyName = content.ProxyName
|
||||
return &ret
|
||||
}
|
||||
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
|
||||
|
||||
f.RunServer("", pluginServer)
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
[plugin.test]
|
||||
addr = 127.0.0.1:%d
|
||||
path = /handler
|
||||
ops = CloseProxy
|
||||
`, localPort)
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = %d
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
|
||||
_, clients := f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
|
||||
for _, c := range clients {
|
||||
c.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
framework.ExpectEqual(recordProxyName, "tcp")
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Ping", func() {
|
||||
newFunc := func() *plugin.Request {
|
||||
var r plugin.Request
|
||||
|
Reference in New Issue
Block a user