Compare commits

..

33 Commits

Author SHA1 Message Date
fatedier
2dab5d0bca Merge pull request #2782 from fatedier/dev
bump version
2022-01-26 20:17:54 +08:00
fatedier
9ca2b586f8 update release note 2022-01-26 20:13:25 +08:00
fatedier
e59eacb8a2 version: bump to v0.39.0 2022-01-26 19:53:22 +08:00
Blizard
0db4fc07fb feat: support set local ip in client when connect server (#2774)
* feat: support set local ip in client when connect server

* fix: typo

Co-authored-by: blizard863 <760076784@qq.com>
2022-01-26 19:47:40 +08:00
fatedier
70f4caac23 move dial functions into golib (#2767) 2022-01-20 20:03:07 +08:00
fatedier
293003fcdb allow to disable application layer heartbeat to reduce traffic cost (#2758)
fix #2754
2022-01-13 14:26:07 +08:00
fatedier
4bfc89d988 update doc for Login operation 2022-01-11 16:36:56 +08:00
fatedier
22412851b4 server plugin: add client address in Login operation, fix #2742 (#2751) 2022-01-11 16:32:20 +08:00
bobo liu
e9775bd70f doc: no X-Real-IP in http proxy (#2743) 2022-01-09 23:47:28 +08:00
nitinSophos
ff7b8b0b62 ISSUE: 2730 Alpine version update for security fixes (#2731)
update docker file to pick alpine:3
2022-01-04 10:40:45 +08:00
fatedier
491c1d7dc4 add new sponsor (#2729) 2022-01-02 14:31:08 +08:00
Blizard
ea568e8a4f refactor: refine pkg net utils (#2720)
* refactor: refine pkg net utils

* fix: x

Co-authored-by: blizard863 <760076784@qq.com>
2021-12-28 21:14:57 +08:00
fatedier
0fb6aeef58 update sponsor content 2021-12-17 15:09:23 +08:00
fatedier
032f33fe5a update readme add sponsor pic (#2704) 2021-12-17 15:05:39 +08:00
fatedier
bbc8b438d5 doc: add local_port for tcpmux example (#2695) 2021-12-08 18:41:21 +08:00
fatedier
05b1ace21f remove authentication for healthz api (#2672) 2021-11-23 14:14:27 +08:00
fatedier
cbdd73b94f typo 2021-11-12 22:18:36 +08:00
fatedier
bf06e3b107 doc for v2 (#2648) 2021-11-09 14:18:40 +08:00
fatedier
143750901e Merge pull request #2638 from fatedier/dev
bump version to v0.38.0
2021-10-25 20:31:13 +08:00
fatedier
71489d194c e2e: add delay duration for monitor case 2021-10-25 20:27:22 +08:00
fatedier
85aa3df256 bump version 2021-10-25 20:14:21 +08:00
fatedier
f1a51eba18 client: lint 2021-10-19 15:02:45 +08:00
Blizard
1d26ea440b fix: kcp protocol cause delay release resource (#2621)
Co-authored-by: blizard863 <760076784@qq.com>
2021-10-19 14:57:26 +08:00
Blizard
998e678a7f fix: typo (#2614)
Co-authored-by: blizard863 <760076784@qq.com>
2021-10-11 12:11:59 +08:00
kekeimiku
0cee1877e3 refactor: move from io/ioutil to io and os package (#2592) 2021-09-29 10:33:57 +08:00
fatedier
72a7fd948e Update bug_report.yaml 2021-09-03 11:52:04 +08:00
fatedier
357c9b0dcb use github issue form (#2563) 2021-09-03 11:47:55 +08:00
fatedier
14bd0716d0 Create FUNDING.yml (#2558) 2021-08-31 20:28:13 +08:00
bobo liu
2f74f54f18 Let's get rid of ugly statik (#2255)
* Get rid of ugly statik

go1.16 introduced the embed package, it's the more graceful solution for embedding file into binary.
https://golang.org/pkg/embed/

* remove statik totally

* split go and static files in assets
2021-08-17 20:20:04 +08:00
fatedier
a62a9431b1 support go1.17 and remove go1.15 (#2532) 2021-08-17 15:26:43 +08:00
fatedier
42745a3da2 frpc: add disable_custom_tls_first_byte to not send first custom tls to frps (#2520) 2021-08-11 23:10:35 +08:00
fatedier
82f80a22be add healthz api (#2511) 2021-08-04 14:33:53 +08:00
fatedier
f570dcb307 update stale action 2021-08-04 10:51:18 +08:00
73 changed files with 792 additions and 741 deletions

View File

@@ -2,16 +2,14 @@ version: 2
jobs:
go-version-latest:
docker:
- image: circleci/golang:1.16-node
working_directory: /go/src/github.com/fatedier/frp
- image: cimg/go:1.17-node
steps:
- checkout
- run: make
- run: make alltest
go-version-last:
docker:
- image: circleci/golang:1.15-node
working_directory: /go/src/github.com/fatedier/frp
- image: cimg/go:1.16-node
steps:
- checkout
- run: make

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [fatedier]

View File

@@ -1,44 +0,0 @@
---
name: Bug Report
about: Bug Report for FRP
title: ''
labels: Requires Testing
assignees: ''
---
<!-- From Chinese to English by machine translation, welcome to revise and polish. -->
<!-- ⚠️⚠️ Incomplete reports will be marked as invalid, and closed, with few exceptions ⚠️⚠️ -->
<!-- in addition, please use search well so that the same solution can be found in the feedback, we will close it directly -->
<!-- for convenience of differentiation, use FRPS or FRPC to refer to the FRP server or client -->
**[REQUIRED] hat version of frp are you using**
<!-- Use ./frpc -v or ./frps -v -->
Version:
**[REQUIRED] What operating system and processor architecture are you using**
OS:
CPU architecture:
**[REQUIRED] description of errors**
**confile**
<!-- Please pay attention to hiding the token, server_addr and other privacy information -->
**log file**
<!-- If the file is too large, use Pastebin, for example https://pastebin.ubuntu.com/ -->
**Steps to reproduce the issue**
1.
2.
3.
**Supplementary information**
**Can you guess what caused this issue**
**Checklist**:
<!--- Make sure you've completed the following steps (put an "X" between of brackets): -->
- [] I included all information required in the sections above
- [] I made sure there are no duplicates of this report [(Use Search)](https://github.com/fatedier/frp/issues?q=is%3Aissue)

77
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: Bug report
description: Report a bug to help us improve frp
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: bug-description
attributes:
label: Bug Description
description: Tell us what issues you ran into
placeholder: Include information about what you tried, what you expected to happen, and what actually happened. The more details, the better!
validations:
required: true
- type: input
id: frpc-version
attributes:
label: frpc Version
description: Include the output of `frpc -v`
validations:
required: true
- type: input
id: frps-version
attributes:
label: frps Version
description: Include the output of `frps -v`
validations:
required: true
- type: input
id: system-architecture
attributes:
label: System Architecture
description: Include which architecture you used, such as `linux/amd64`, `windows/amd64`
validations:
required: true
- type: textarea
id: config
attributes:
label: Configurations
description: Include what configurrations you used and ran into this problem
placeholder: Pay attention to hiding the token and password in your output
validations:
required: true
- type: textarea
id: log
attributes:
label: Logs
description: Prefer you providing releated error logs here
placeholder: Pay attention to hiding your personal informations
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: How to reproduce it? It's important for us to find the bug
value: |
1.
2.
3.
...
- type: checkboxes
id: area
attributes:
label: Affected area
options:
- label: "Docs"
- label: "Installation"
- label: "Performance and Scalability"
- label: "Security"
- label: "User Experience"
- label: "Test and Release"
- label: "Developer Infrastructure"
- label: "Client Plugin"
- label: "Server Plugin"
- label: "Extensions"
- label: "Others"

View File

@@ -1,5 +1 @@
blank_issues_enabled: false
contact_links:
- name: DOCS
url: https://github.com/fatedier/frp
about: Here you can find out how to configure frp.

View File

@@ -1,22 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: "[+] Enhancement"
assignees: ''
---
<!-- From Chinese to English by machine translation, welcome to revise and polish. -->
**The solution you want**
<!--A clear and concise description of the solution you want. -->
**Alternatives considered**
<!--A clear and concise description of any alternative solutions or features you have considered. -->
**How to implement this function**
<!--Implementation steps for the solution you want. -->
**Application scenarios of this function**
<!--Make a clear and concise description of the application scenario of the solution you want. -->

View File

@@ -0,0 +1,36 @@
name: Feature Request
description: Suggest an idea to improve frp
title: "[Feature Request] "
body:
- type: markdown
attributes:
value: |
This is only used to request new product features.
- type: textarea
id: feature-request
attributes:
label: Describe the feature request
description: Tell us what's you want and why it should be added in frp.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered
- type: checkboxes
id: area
attributes:
label: Affected area
options:
- label: "Docs"
- label: "Installation"
- label: "Performance and Scalability"
- label: "Security"
- label: "User Experience"
- label: "Test and Release"
- label: "Developer Infrastructure"
- label: "Client Plugin"
- label: "Server Plugin"
- label: "Extensions"
- label: "Others"

View File

@@ -17,7 +17,7 @@ jobs:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.17
- run: |
# https://github.com/actions/setup-go/issues/107

View File

@@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.17
- run: |
# https://github.com/actions/setup-go/issues/107

View File

@@ -15,12 +15,12 @@ jobs:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Issues go stale after 45d of inactivity. Stale issues rot after an additional 10d of inactivity and eventually close.'
stale-pr-message: 'Issues go stale after 45d of inactivity. Stale issues rot after an additional 10d of inactivity and eventually close.'
stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
stale-pr-message: 'Issues go stale after 30d of inactivity. Stale issues 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'
exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
days-before-stale: 45
days-before-close: 10
days-before-stale: 30
days-before-close: 7
debug-only: ${{ github.event.inputs.debug-only }}

View File

@@ -12,9 +12,6 @@ file:
rm -rf ./assets/frpc/static/*
cp -rf ./web/frps/dist/* ./assets/frps/static
cp -rf ./web/frpc/dist/* ./assets/frpc/static
rm -rf ./assets/frps/statik
rm -rf ./assets/frpc/statik
go generate ./assets/...
fmt:
go fmt ./...

View File

@@ -12,6 +12,21 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
frp also has a P2P connect mode.
<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">Silver Sponsors</h3>
* Sakura Frp - 欢迎点击 "加入我们"
## Table of Contents
<!-- vim-markdown-toc GFM -->
@@ -77,7 +92,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 +884,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 +1000,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:

View File

@@ -7,6 +7,21 @@
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">Silver Sponsors</h3>
* Sakura Frp - 欢迎点击 "加入我们"
## 为什么使用 frp
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
@@ -25,6 +40,10 @@ frp 目前已被很多公司广泛用于测试、生产环境。
master 分支用于发布稳定版本dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续一段时间。
现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。
## 文档
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。

View File

@@ -1,5 +1,12 @@
### New
* Added `connect_server_local_ip` in frpc to specify local IP connected to frps.
* Added `tcp_mux_keepalive_interval` both in frpc and frps to set `tcp_mux` keepalive interval seconds if `tcp_mux` is enabled. After using this params, you can set `heartbeat_interval` to `-1` to disable application layer heartbeat to reduce traffic usage(Make sure frps is in the latest version).
### Improve
* Server Plugin: Added `client_address` in Login Operation.
### Fix
* Plugin `https2https` not work.
* `context canceled` problem for `http_proxy` plugin when multiple requests reuse same connection.
* In some cases, frps can't get server name for `https` proxy.
* Remove authentication for healthz api.

View File

@@ -14,22 +14,15 @@
package assets
//go:generate statik -src=./frps/static -dest=./frps
//go:generate statik -src=./frpc/static -dest=./frpc
//go:generate go fmt ./frps/statik/statik.go
//go:generate go fmt ./frpc/statik/statik.go
import (
"io/ioutil"
"io/fs"
"net/http"
"os"
"path"
"github.com/rakyll/statik/fs"
)
var (
// store static files in memory by statik
// read-only filesystem created by "embed" for embedded files
content fs.FS
FileSystem http.FileSystem
// if prefix is not empty, we get file content from disk
@@ -38,40 +31,18 @@ var (
// if path is empty, load assets in memory
// or set FileSystem using disk files
func Load(path string) (err error) {
func Load(path string) {
prefixPath = path
if prefixPath != "" {
FileSystem = http.Dir(prefixPath)
return nil
} else {
FileSystem, err = fs.New()
FileSystem = http.FS(content)
}
return err
}
func ReadFile(file string) (content string, err error) {
if prefixPath == "" {
file, err := FileSystem.Open(path.Join("/", file))
if err != nil {
return content, err
}
defer file.Close()
buf, err := ioutil.ReadAll(file)
if err != nil {
return content, err
}
content = string(buf)
} else {
file, err := os.Open(path.Join(prefixPath, file))
if err != nil {
return content, err
}
defer file.Close()
buf, err := ioutil.ReadAll(file)
if err != nil {
return content, err
}
content = string(buf)
func Register(fileSystem fs.FS) {
subFs, err := fs.Sub(fileSystem, "static")
if err == nil {
content = subFs
}
return content, err
}

14
assets/frpc/embed.go Normal file
View File

@@ -0,0 +1,14 @@
package frpc
import (
"embed"
"github.com/fatedier/frp/assets"
)
//go:embed static/*
var content embed.FS
func init() {
assets.Register(content)
}

File diff suppressed because one or more lines are too long

14
assets/frps/embed.go Normal file
View File

@@ -0,0 +1,14 @@
package frpc
import (
"embed"
"github.com/fatedier/frp/assets"
)
//go:embed static/*
var content embed.FS
func init() {
assets.Register(content)
}

File diff suppressed because one or more lines are too long

View File

@@ -34,19 +34,22 @@ func (svr *Service) RunAdminServer(address string) (err error) {
// url router
router := mux.NewRouter()
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
router.HandleFunc("/healthz", svr.healthz)
// api, see dashboard_api.go
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 := router.NewRoute().Subrouter()
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
// api, see admin_api.go
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)
})

View File

@@ -17,8 +17,9 @@ package client
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"os"
"sort"
"strings"
@@ -32,6 +33,11 @@ type GeneralResponse struct {
Msg string
}
// /healthz
func (svr *Service) healthz(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}
// GET api/reload
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
@@ -251,7 +257,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
}()
// get new config content
body, err := ioutil.ReadAll(r.Body)
body, err := io.ReadAll(r.Body)
if err != nil {
res.Code = 400
res.Msg = fmt.Sprintf("read request body error: %v", err)
@@ -268,7 +274,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
// get token from origin content
token := ""
b, err := ioutil.ReadFile(svr.cfgFile)
b, err := os.ReadFile(svr.cfgFile)
if err != nil {
res.Code = 400
res.Msg = err.Error()
@@ -307,7 +313,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
}
content = strings.Join(newRows, "\n")
err = ioutil.WriteFile(svr.cfgFile, []byte(content), 0644)
err = os.WriteFile(svr.cfgFile, []byte(content), 0644)
if err != nil {
res.Code = 500
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)

View File

@@ -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"
)
@@ -182,9 +183,16 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
}
func (ctl *Control) Close() error {
return ctl.GracefulClose(0)
}
func (ctl *Control) GracefulClose(d time.Duration) error {
ctl.pm.Close()
ctl.conn.Close()
ctl.vm.Close()
time.Sleep(d)
ctl.conn.Close()
if ctl.session != nil {
ctl.session.Close()
}
@@ -227,12 +235,36 @@ 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)
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.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
@@ -301,16 +333,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{}
@@ -319,7 +362,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

View File

@@ -19,7 +19,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"time"
@@ -162,7 +161,7 @@ func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
return err
}
defer resp.Body.Close()
io.Copy(ioutil.Discard, resp.Body)
io.Copy(io.Discard, resp.Body)
if resp.StatusCode/100 != 2 {
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)

View File

@@ -19,7 +19,6 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"net"
"strconv"
"strings"
@@ -36,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"
@@ -401,7 +401,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 5 * time.Second
fmuxCfg.LogOutput = ioutil.Discard
fmuxCfg.LogOutput = io.Discard
sess, err := fmux.Server(kcpConn, fmuxCfg)
if err != nil {
xl.Error("create yamux server from kcp connection error: %v", err)
@@ -791,7 +791,7 @@ 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)))
if err != nil {
workConn.Close()
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err)

View File

@@ -19,7 +19,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"io"
"net"
"runtime"
"strconv"
@@ -36,6 +36,7 @@ import (
frpNet "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/version"
"github.com/fatedier/frp/pkg/util/xlog"
libdial "github.com/fatedier/golib/net/dial"
fmux "github.com/hashicorp/yamux"
)
@@ -124,13 +125,10 @@ func (svr *Service) Run() error {
if svr.cfg.AdminPort != 0 {
// Init admin server assets
err := assets.Load(svr.cfg.AssetsDir)
if err != nil {
return fmt.Errorf("Load assets error: %v", err)
}
assets.Load(svr.cfg.AssetsDir)
address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort))
err = svr.RunAdminServer(address)
err := svr.RunAdminServer(address)
if err != nil {
log.Warn("run admin server error: %v", err)
}
@@ -231,8 +229,33 @@ 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)
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.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
}
@@ -248,8 +271,8 @@ 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.LogOutput = ioutil.Discard
fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second
fmuxCfg.LogOutput = io.Discard
session, err = fmux.Client(conn, fmuxCfg)
if err != nil {
return
@@ -315,9 +338,13 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
}
func (svr *Service) Close() {
svr.GracefulClose(time.Duration(0))
}
func (svr *Service) GracefulClose(d time.Duration) {
atomic.StoreUint32(&svr.exit, 1)
if svr.ctl != nil {
svr.ctl.Close()
svr.ctl.GracefulClose(d)
}
svr.cancel()
}

View File

@@ -19,7 +19,6 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"net"
"sync"
"time"
@@ -308,7 +307,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 5 * time.Second
fmuxCfg.LogOutput = ioutil.Discard
fmuxCfg.LogOutput = io.Discard
sess, err := fmux.Client(remote, fmuxCfg)
if err != nil {
xl.Error("create yamux session error: %v", err)

View File

@@ -18,7 +18,7 @@ import (
"math/rand"
"time"
_ "github.com/fatedier/frp/assets/frpc/statik"
_ "github.com/fatedier/frp/assets/frpc"
"github.com/fatedier/frp/cmd/frpc/sub"
"github.com/fatedier/golib/crypto"

View File

@@ -17,7 +17,7 @@ package sub
import (
"encoding/base64"
"fmt"
"io/ioutil"
"io"
"net/http"
"os"
"strings"
@@ -76,7 +76,7 @@ func reload(clientCfg config.ClientCommonConf) error {
return nil
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

View File

@@ -124,8 +124,7 @@ func handleSignal(svr *client.Service) {
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
svr.Close()
time.Sleep(250 * time.Millisecond)
svr.GracefulClose(500 * time.Millisecond)
close(kcpDoneCh)
}

View File

@@ -18,7 +18,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"os"
"strings"
@@ -77,7 +77,7 @@ func status(clientCfg config.ClientCommonConf) error {
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

View File

@@ -20,7 +20,7 @@ import (
"github.com/fatedier/golib/crypto"
_ "github.com/fatedier/frp/assets/frps/statik"
_ "github.com/fatedier/frp/assets/frps"
_ "github.com/fatedier/frp/pkg/metrics"
)

View File

@@ -61,6 +61,9 @@ pool_count = 5
# if tcp stream multiplexing is used, default is true, it must be same with frps
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 +76,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
@@ -89,7 +96,8 @@ tls_enable = true
# 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
@@ -105,6 +113,10 @@ udp_packet_size = 1500
# include other config files for proxies.
# includes = ./confd/*.ini
# By default, frpc will connect frps with first custom byte if tls is enabled.
# If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
disable_custom_tls_first_byte = 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]
@@ -181,9 +193,9 @@ use_compression = true
# if not set, you can access this custom_domains without certification
http_user = admin
http_pwd = admin
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://test.frps.com
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://web01.frps.com
subdomain = web01
custom_domains = web02.yourdomain.com
custom_domains = web01.yourdomain.com
# locations is only available for http type
locations = /,/pic
host_header_rewrite = example.com

View File

@@ -92,7 +92,7 @@ oidc_skip_expiry_check = 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
@@ -121,6 +121,9 @@ subdomain_host = frps.com
# if tcp stream multiplexing is used, default is true
tcp_mux = true
# specify keep alive interval for tcp mux.
# only valid if tcp_mux is true.
# tcp_mux_keepalive_interval = 60
# custom 404 page for HTTP requests
# custom_404_page = /path/to/404.html

BIN
doc/pic/sponsor_doppler.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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>
}
}
```

View File

@@ -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
```

View File

@@ -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

View File

@@ -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

9
go.mod
View File

@@ -6,29 +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.20220119075718-78e5cf8c00ee
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/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/prometheus/client_golang v1.11.0
github.com/rakyll/statik v0.1.1
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

39
go.sum
View File

@@ -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.20220119075718-78e5cf8c00ee h1:iS0wlj2uZPxh3pciAf/HTzi88Kqu7DPh1jNKgJaFhtI=
github.com/fatedier/golib v0.1.1-0.20220119075718-78e5cf8c00ee/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=
@@ -332,8 +335,6 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -373,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=
@@ -395,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=
@@ -403,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=
@@ -444,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=
@@ -463,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=
@@ -621,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=

View File

@@ -5,7 +5,7 @@ ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
which ginkgo &> /dev/null
if [ $? -ne 0 ]; then
echo "ginkgo not found, try to install..."
go get -u github.com/onsi/ginkgo/ginkgo
go install github.com/onsi/ginkgo/ginkgo@latest
fi
debug=false

View File

@@ -38,6 +38,10 @@ 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"`
// 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 +90,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 "".
@@ -124,13 +131,16 @@ type ClientCommonConf struct {
// TLSServerName specifices 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.
// If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
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"`
@@ -144,36 +154,37 @@ type ClientCommonConf struct {
// 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,
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),
}
}
@@ -186,12 +197,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 {

View File

@@ -259,33 +259,34 @@ 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,
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",

View File

@@ -17,7 +17,6 @@ package config
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
@@ -77,7 +76,7 @@ func getIncludeContents(paths []string) ([]byte, error) {
if _, err := os.Stat(absDir); os.IsNotExist(err) {
return nil, err
}
files, err := ioutil.ReadDir(absDir)
files, err := os.ReadDir(absDir)
if err != nil {
return nil, err
}

View File

@@ -118,6 +118,9 @@ 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"`
// 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 +157,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.
@@ -170,42 +173,43 @@ type ServerCommonConf struct {
// 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,
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,
}
}

View File

@@ -131,15 +131,16 @@ 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,
UDPPacketSize: 1509,
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
"user-manager": {
@@ -174,27 +175,28 @@ 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,
AllowPorts: make(map[int]struct{}),
MaxPoolCount: 5,
HeartbeatTimeout: 90,
UserConnTimeout: 10,
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
UDPPacketSize: 1500,
},
},
}

View File

@@ -16,7 +16,6 @@ package config
import (
"bytes"
"io/ioutil"
"os"
"strings"
"text/template"
@@ -67,7 +66,7 @@ func RenderContent(in []byte) (out []byte, err error) {
func GetRenderedConfFromFile(path string) (out []byte, err error) {
var b []byte
b, err = ioutil.ReadFile(path)
b, err = os.ReadFile(path)
if err != nil {
return
}

View File

@@ -16,7 +16,6 @@ package plugin
import (
"io"
"io/ioutil"
"log"
"net"
@@ -43,7 +42,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
passwd := params["plugin_passwd"]
cfg := &gosocks5.Config{
Logger: log.New(ioutil.Discard, "", log.LstdFlags),
Logger: log.New(io.Discard, "", log.LstdFlags),
}
if user != "" || passwd != "" {
cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})

View File

@@ -20,7 +20,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"reflect"
@@ -116,7 +116,7 @@ func (p *httpPlugin) do(ctx context.Context, r *Request, res *Response) error {
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("do http request error code: %d", resp.StatusCode)
}
buf, err = ioutil.ReadAll(resp.Body)
buf, err = io.ReadAll(resp.Body)
if err != nil {
return err
}

View File

@@ -33,6 +33,8 @@ type Response struct {
type LoginContent struct {
msg.Login
ClientAddress string `json:"client_address,omitempty"`
}
type UserInfo struct {

View File

@@ -6,8 +6,8 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"math/big"
"os"
)
func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, error) {
@@ -47,7 +47,7 @@ func newRandomTLSKeyPair() *tls.Certificate {
func newCertPool(caPath string) (*x509.CertPool, error) {
pool := x509.NewCertPool()
caCrt, err := ioutil.ReadFile(caPath)
caCrt, err := os.ReadFile(caPath)
if err != nil {
return nil, err
}

View File

@@ -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) (c net.Conn, err error) {
c, err = ConnectServerByProxy(proxyURL, protocol, addr)
if err != nil {
return
}
if tlsConfig == nil {
return
}
c = WrapTLSClientConn(c, tlsConfig)
return
}

44
pkg/util/net/dial.go Normal file
View 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
}
}

View File

@@ -27,13 +27,10 @@ var (
FRPTLSHeadByte = 0x17
)
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out net.Conn) {
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) {
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration) (out net.Conn, err error) {
sc, r := gnet.NewSharedConnSize(c, 2)
buf := make([]byte, 1)
var n int
@@ -46,6 +43,11 @@ func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, t
if n == 1 && int(buf[0]) == FRPTLSHeadByte {
out = tls.Server(c, tlsConfig)
isTLS = true
custom = true
} else if n == 1 && int(buf[0]) == 0x16 {
out = tls.Server(sc, tlsConfig)
isTLS = true
} else {
if tlsOnly {
err = fmt.Errorf("non-TLS connection received on a TlsOnly server")

View File

@@ -5,8 +5,6 @@ import (
"fmt"
"net"
"net/http"
"net/url"
"time"
"golang.org/x/net/websocket"
)
@@ -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
}

View File

@@ -19,7 +19,7 @@ import (
"strings"
)
var version string = "0.37.1"
var version string = "0.39.0"
func Full() string {
return version

View File

@@ -16,8 +16,9 @@ package vhost
import (
"bytes"
"io/ioutil"
"io"
"net/http"
"os"
frpLog "github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/pkg/util/version"
@@ -57,7 +58,7 @@ func getNotFoundPageContent() []byte {
err error
)
if NotFoundPagePath != "" {
buf, err = ioutil.ReadFile(NotFoundPagePath)
buf, err = os.ReadFile(NotFoundPagePath)
if err != nil {
frpLog.Warn("read custom 404 page error: %v", err)
buf = []byte(NotFound)
@@ -80,7 +81,7 @@ func notFoundResponse() *http.Response {
ProtoMajor: 1,
ProtoMinor: 0,
Header: header,
Body: ioutil.NopCloser(bytes.NewReader(getNotFoundPageContent())),
Body: io.NopCloser(bytes.NewReader(getNotFoundPageContent())),
}
return res
}

View File

@@ -400,12 +400,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

View File

@@ -34,26 +34,29 @@ var (
func (svr *Service) RunDashboardServer(address string) (err error) {
// url router
router := mux.NewRouter()
router.HandleFunc("/healthz", svr.Healthz)
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")
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)
})

View File

@@ -51,6 +51,11 @@ type serverInfoResp struct {
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
}
// /healthz
func (svr *Service) Healthz(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}
// api/serverinfo
func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}

View File

@@ -19,7 +19,7 @@ import (
"context"
"crypto/tls"
"fmt"
"io/ioutil"
"io"
"net"
"net/http"
"sort"
@@ -258,8 +258,9 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
}
// frp tls listener
svr.tlsListener = svr.muxer.Listen(1, 1, func(data []byte) bool {
return int(data[0]) == frpNet.FRPTLSHeadByte
svr.tlsListener = svr.muxer.Listen(2, 1, func(data []byte) bool {
// tls first byte can be 0x16 only when vhost https port is not same with bind port
return int(data[0]) == frpNet.FRPTLSHeadByte || int(data[0]) == 0x16
})
// Create nat hole controller.
@@ -279,11 +280,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
// Create dashboard web server.
if cfg.DashboardPort > 0 {
// Init dashboard assets
err = assets.Load(cfg.AssetsDir)
if err != nil {
err = fmt.Errorf("Load assets error: %v", err)
return
}
assets.Load(cfg.AssetsDir)
address := net.JoinHostPort(cfg.DashboardAddr, strconv.Itoa(cfg.DashboardPort))
err = svr.RunDashboardServer(address)
@@ -337,7 +334,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 {
@@ -395,20 +393,21 @@ func (svr *Service) HandleListener(l net.Listener) {
log.Trace("start check TLS connection...")
originConn := c
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TLSOnly, connReadTimeout)
var isTLS, custom bool
c, isTLS, custom, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TLSOnly, connReadTimeout)
if err != nil {
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
originConn.Close()
continue
}
log.Trace("success check TLS connection")
log.Trace("check TLS connection success, isTLS: %v custom: %v", isTLS, custom)
// Start a new goroutine for dealing connections.
// Start a new goroutine to handle connection.
go func(ctx context.Context, frpConn net.Conn) {
if svr.cfg.TCPMux {
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 20 * time.Second
fmuxCfg.LogOutput = ioutil.Discard
fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second
fmuxCfg.LogOutput = io.Discard
session, err := fmux.Server(frpConn, fmuxCfg)
if err != nil {
log.Warn("Failed to create mux connection: %v", err)

View File

@@ -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))
})
})

View File

@@ -231,4 +231,22 @@ var _ = Describe("[Feature: Client-Server]", func() {
})
})
})
Describe("TLS with disable_custom_tls_first_byte", func() {
supportProtocols := []string{"tcp", "kcp", "websocket"}
for _, protocol := range supportProtocols {
tmp := protocol
defineClientServerTest("TLS over "+strings.ToUpper(tmp), f, &generalTestConfigures{
server: fmt.Sprintf(`
kcp_bind_port = {{ .%s }}
protocol = %s
`, consts.PortServerName, protocol),
client: fmt.Sprintf(`
tls_enable = true
protocol = %s
disable_custom_tls_first_byte = true
`, protocol),
})
}
})
})

View File

@@ -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))
})
})

View 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()
})
})

View File

@@ -3,6 +3,7 @@ package features
import (
"fmt"
"strings"
"time"
"github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/test/e2e/framework"
@@ -35,6 +36,7 @@ var _ = Describe("[Feature: Monitor]", func() {
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
time.Sleep(500 * time.Millisecond)
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
r.HTTP().Port(dashboardPort).HTTPPath("/metrics")

View File

@@ -3,7 +3,6 @@ package framework
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
@@ -90,7 +89,7 @@ func (f *Framework) BeforeEach() {
f.cleanupHandle = AddCleanupAction(f.AfterEach)
dir, err := ioutil.TempDir(os.TempDir(), "frp-e2e-test-*")
dir, err := os.MkdirTemp(os.TempDir(), "frp-e2e-test-*")
ExpectNoError(err)
f.TempDirectory = dir
@@ -260,7 +259,7 @@ func (f *Framework) SetEnvs(envs []string) {
func (f *Framework) WriteTempFile(name string, content string) string {
filePath := filepath.Join(f.TempDirectory, name)
err := ioutil.WriteFile(filePath, []byte(content), 0766)
err := os.WriteFile(filePath, []byte(content), 0766)
ExpectNoError(err)
return filePath
}

View File

@@ -2,7 +2,7 @@ package framework
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
@@ -30,7 +30,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
for i := range serverTemplates {
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
err = ioutil.WriteFile(path, []byte(outs[i]), 0666)
err = os.WriteFile(path, []byte(outs[i]), 0666)
ExpectNoError(err)
flog.Trace("[%s] %s", path, outs[i])
@@ -45,7 +45,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
for i := range clientTemplates {
index := i + len(serverTemplates)
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i))
err = ioutil.WriteFile(path, []byte(outs[index]), 0666)
err = os.WriteFile(path, []byte(outs[index]), 0666)
ExpectNoError(err)
flog.Trace("[%s] %s", path, outs[index])
@@ -85,7 +85,7 @@ func (f *Framework) RunFrpc(args ...string) (*process.Process, string, error) {
func (f *Framework) GenerateConfigFile(content string) string {
f.configFileIndex++
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-config-%d", f.configFileIndex))
err := ioutil.WriteFile(path, []byte(content), 0666)
err := os.WriteFile(path, []byte(content), 0666)
ExpectNoError(err)
return path
}

View File

@@ -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)

View File

@@ -6,7 +6,6 @@ import (
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
@@ -14,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 {
@@ -142,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
}
@@ -219,7 +222,7 @@ func (r *Request) sendHTTPRequest(method, urlstr string, host string, headers ma
}
ret := &Response{Code: resp.StatusCode, Header: resp.Header}
buf, err := ioutil.ReadAll(resp.Body)
buf, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@@ -3,7 +3,7 @@ package client
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net"
"net/http"
"strconv"
@@ -125,7 +125,7 @@ func (c *Client) do(req *http.Request) (string, error) {
if resp.StatusCode != 200 {
return "", fmt.Errorf("api status code [%d]", resp.StatusCode)
}
buf, err := ioutil.ReadAll(resp.Body)
buf, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}

View File

@@ -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)
})
})

View File

@@ -3,7 +3,7 @@ package plugin
import (
"crypto/tls"
"encoding/json"
"io/ioutil"
"io"
"net/http"
plugin "github.com/fatedier/frp/pkg/plugin/server"
@@ -21,7 +21,7 @@ func NewHTTPPluginServer(port int, newFunc NewPluginRequest, handler PluginHandl
httpserver.WithTlsConfig(tlsConfig),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
r := newFunc()
buf, err := ioutil.ReadAll(req.Body)
buf, err := io.ReadAll(req.Body)
if err != nil {
w.WriteHeader(500)
return