Compare commits

..

40 Commits

Author SHA1 Message Date
fatedier
813c45f5c2 Merge pull request #1993 from fatedier/dev
bump version to v0.34.0
2020-09-20 00:30:51 +08:00
fatedier
c0e05bb41e bump version to v0.34.0 2020-09-20 00:29:27 +08:00
fatedier
aa74dc4646 Merge pull request #1990 from fatedier/dev
bump version to v0.34.0
2020-09-20 00:10:32 +08:00
fatedier
1e420cc766 update tls 2020-09-18 20:06:33 +08:00
yuyulei
4fff3c7472 Add tls configuration to both client and server (#1974) 2020-09-18 19:58:58 +08:00
fatedier
48fa618c34 update e2e tests (#1973) 2020-09-07 15:45:44 +08:00
fatedier
c9fe23eb10 more e2e tests (#1845) 2020-09-07 14:57:23 +08:00
Luka Čehovin Zajc
268afb3438 Sorting plugins so that execution order is deterministic (#1961) 2020-08-31 19:49:46 +08:00
fzhyzamt
b1181fd17a support non-preemptive authentication (#1888) 2020-07-01 11:18:23 +08:00
Albert Zhao
b23548eeff fix grammar issue (#1850) (#1852) 2020-06-11 15:39:59 +08:00
fatedier
262317192c new e2e framework (#1835) 2020-06-02 22:48:55 +08:00
fatedier
8b75b8b837 fix by golint (#1822) 2020-05-24 17:48:37 +08:00
fatedier
2170c481ce update doc (#1821) 2020-05-24 14:05:31 +08:00
Tank
dfbf9c4542 style: adjust frps files (#1820) 2020-05-24 11:28:57 +08:00
Tank
964a1bbf39 refine: frpc flags (#1811) 2020-05-19 10:49:29 +08:00
Tank
228e225f84 fix: sync/atomic bug, fix #1804 (#1805)
Co-authored-by: tanghuafa <tanghuafa@bytedance.com>
2020-05-12 22:09:16 +08:00
Tank
bd6435c982 fix: frps plugin manager (#1803) 2020-05-12 15:55:35 +08:00
Tank
591023a1f0 fix: add frpc tls_enable flag and frps tls_only flag (#1798) 2020-05-12 14:33:34 +08:00
Tank
1ab23b5e0e fix: typo (#1799) 2020-05-10 17:58:35 +08:00
Tank
d193519329 feat: Support user specify udp packet size in config (#1794) 2020-05-07 17:47:36 +08:00
fatedier
2406ecdfea Merge pull request #1780 from fatedier/dev
bump version
2020-04-27 16:50:34 +08:00
fatedier
7266154d54 bump version to v0.33.0 2020-04-27 16:24:17 +08:00
Tank
4797136965 feat: support sudp proxy (#1730) 2020-04-22 21:37:45 +08:00
Guy Lewin
6d78af6144 feat: group TCP mux proxies (#1765) 2020-04-20 13:35:47 +08:00
Tank
7728e35c52 fix: frps handle multi conn may happen data race (#1768) 2020-04-19 16:16:24 +08:00
Tank
5a61fd84ad fix: auth token bug (#1762) 2020-04-16 20:20:36 +08:00
zhang-wei
ad0c449a75 Server manager support the NewUserConn operation (#1740)
support NewUserConn operation
2020-04-16 13:06:46 +08:00
fatedier
1c330185c4 typo 2020-04-03 01:24:37 +08:00
fatedier
8668fef136 Merge pull request #1728 from fatedier/dev
bump version to v0.32.1
2020-04-03 01:14:58 +08:00
fatedier
7491b327f8 update ISSUE_TEMPLATE 2020-04-03 01:03:13 +08:00
fatedier
abb5b05d49 update package.sh 2020-04-03 00:59:47 +08:00
fatedier
b6ec9dad28 bump version to v0.32.1 2020-04-02 11:49:16 +08:00
Tank
caa6e8cf01 fix: frpc reconnect frps frequently lead to memory leak (#1722) 2020-04-02 10:58:37 +08:00
fatedier
ffb932390f remove qq info 2020-03-28 22:29:02 +08:00
xcffl
a8efaee1f3 Improve basic examples for newbies (#1711) 2020-03-26 17:41:18 +08:00
fatedier
4c2afb5c28 doc: add plugin repo link (#1710) 2020-03-20 20:54:22 +08:00
fatedier
809f517db8 server plugin: set version and op in http request query (#1707) 2020-03-20 20:53:14 +08:00
Guy Lewin
a4b105dedb [Feature] Server Plugin - Ping and NewWorkConn RPC (#1702) 2020-03-18 01:52:44 +08:00
Guy Lewin
10acf638f8 [Feature] Include RunId in FRP Server Plugin NewProxy message (#1700)
* feat: include RunId in FRP Server Plugin NewProxy message

* doc: rewrite server plugin documentation
2020-03-14 23:26:35 +08:00
fatedier
f65ffe2812 remove vendor 2020-03-11 14:34:17 +08:00
128 changed files with 4758 additions and 2848 deletions

View File

@@ -1,7 +1,4 @@
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
(请不要在 issue 评论中出现无意义的 **加1****我也是** 等内容,将会被直接删除。)
(由于个人精力有限,和系统环境,网络环境等相关的求助问题请转至其他论坛或社交平台。)
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST

1
.gitignore vendored
View File

@@ -26,6 +26,7 @@ _testmain.go
# Self
bin/
packages/
release/
test/bin/
vendor/

View File

@@ -2,8 +2,8 @@ sudo: false
language: go
go:
- 1.12.x
- 1.13.x
- 1.14.x
install:
- make

View File

@@ -37,7 +37,10 @@ gotest:
ci:
go test -count=1 -p=1 -v ./tests/...
alltest: gotest ci
e2e:
./hack/run-e2e.sh
alltest: gotest ci e2e
clean:
rm -f ./bin/frpc

View File

@@ -1,4 +1,5 @@
export PATH := $(GOPATH)/bin:$(PATH)
export GO111MODULE=on
LDFLAGS := -s -w
all: build
@@ -6,32 +7,32 @@ all: build
build: app
app:
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_darwin_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_darwin_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm64 ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_386.exe ./cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_386.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_amd64.exe ./cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_amd64.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64le ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64le ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_darwin_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_darwin_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_freebsd_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frps_freebsd_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_freebsd_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_freebsd_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_arm ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_arm ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_arm64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_arm64 ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_windows_386.exe ./cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frps_windows_386.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_windows_amd64.exe ./cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_windows_amd64.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips64le ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips64le ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mipsle ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mipsle ./cmd/frps
temp:
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps

View File

@@ -1,6 +1,7 @@
# frp
[![Build Status](https://travis-ci.org/fatedier/frp.svg?branch=master)](https://travis-ci.org/fatedier/frp)
[![GitHub release](https://img.shields.io/github/tag/fatedier/frp.svg?label=release)](https://github.com/fatedier/frp/releases)
[README](README.md) | [中文文档](README_zh.md)
@@ -90,7 +91,7 @@ Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected fro
### Access your computer in LAN by SSH
1. Modify `frps.ini` on server A:
1. Modify `frps.ini` on server A and set the `bind_port` to be connected to frp clients:
```ini
# frps.ini
@@ -117,6 +118,8 @@ Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected fro
remote_port = 6000
```
Note that `local_port` (listened on client) and `remote_port` (exposed on server) are for traffic goes in/out the frp system, whereas `server_port` is used between frps.
4. Start `frpc` on server B:
`./frpc -c ./frpc.ini`
@@ -933,6 +936,8 @@ plugin_http_passwd = abc
Read the [document](/doc/server_plugin.md).
Find more plugins in [gofrp/plugin](https://github.com/gofrp/plugin).
## Development Plan
* Log HTTP request information in frps.

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
router := mux.NewRouter()
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
// api, see dashboard_api.go
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
@@ -46,7 +46,7 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
// 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.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
})

View File

@@ -82,12 +82,13 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
}
type StatusResp struct {
Tcp []ProxyStatusResp `json:"tcp"`
Udp []ProxyStatusResp `json:"udp"`
Http []ProxyStatusResp `json:"http"`
Https []ProxyStatusResp `json:"https"`
Stcp []ProxyStatusResp `json:"stcp"`
Xtcp []ProxyStatusResp `json:"xtcp"`
TCP []ProxyStatusResp `json:"tcp"`
UDP []ProxyStatusResp `json:"udp"`
HTTP []ProxyStatusResp `json:"http"`
HTTPS []ProxyStatusResp `json:"https"`
STCP []ProxyStatusResp `json:"stcp"`
XTCP []ProxyStatusResp `json:"xtcp"`
SUDP []ProxyStatusResp `json:"sudp"`
}
type ProxyStatusResp struct {
@@ -106,17 +107,17 @@ func (a ByProxyStatusResp) Len() int { return len(a) }
func (a ByProxyStatusResp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
func NewProxyStatusResp(status *proxy.ProxyStatus, serverAddr string) ProxyStatusResp {
func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxyStatusResp {
psr := ProxyStatusResp{
Name: status.Name,
Type: status.Type,
Status: status.Status,
Status: status.Phase,
Err: status.Err,
}
switch cfg := status.Cfg.(type) {
case *config.TcpProxyConf:
case *config.TCPProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
if status.Err != "" {
@@ -124,35 +125,40 @@ func NewProxyStatusResp(status *proxy.ProxyStatus, serverAddr string) ProxyStatu
} else {
psr.RemoteAddr = serverAddr + status.RemoteAddr
}
case *config.UdpProxyConf:
case *config.UDPProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
}
if status.Err != "" {
psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
} else {
psr.RemoteAddr = serverAddr + status.RemoteAddr
}
case *config.HttpProxyConf:
case *config.HTTPProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
psr.RemoteAddr = status.RemoteAddr
case *config.HttpsProxyConf:
case *config.HTTPSProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
psr.RemoteAddr = status.RemoteAddr
case *config.StcpProxyConf:
case *config.STCPProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
case *config.XtcpProxyConf:
case *config.XTCPProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
case *config.SUDPProxyConf:
if cfg.LocalPort != 0 {
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIP, cfg.LocalPort)
}
psr.Plugin = cfg.Plugin
}
@@ -165,12 +171,13 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
buf []byte
res StatusResp
)
res.Tcp = make([]ProxyStatusResp, 0)
res.Udp = make([]ProxyStatusResp, 0)
res.Http = make([]ProxyStatusResp, 0)
res.Https = make([]ProxyStatusResp, 0)
res.Stcp = make([]ProxyStatusResp, 0)
res.Xtcp = make([]ProxyStatusResp, 0)
res.TCP = make([]ProxyStatusResp, 0)
res.UDP = make([]ProxyStatusResp, 0)
res.HTTP = make([]ProxyStatusResp, 0)
res.HTTPS = make([]ProxyStatusResp, 0)
res.STCP = make([]ProxyStatusResp, 0)
res.XTCP = make([]ProxyStatusResp, 0)
res.SUDP = make([]ProxyStatusResp, 0)
log.Info("Http request [/api/status]")
defer func() {
@@ -183,25 +190,28 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
for _, status := range ps {
switch status.Type {
case "tcp":
res.Tcp = append(res.Tcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
res.TCP = append(res.TCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "udp":
res.Udp = append(res.Udp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
res.UDP = append(res.UDP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "http":
res.Http = append(res.Http, NewProxyStatusResp(status, svr.cfg.ServerAddr))
res.HTTP = append(res.HTTP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "https":
res.Https = append(res.Https, NewProxyStatusResp(status, svr.cfg.ServerAddr))
res.HTTPS = append(res.HTTPS, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "stcp":
res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
res.STCP = append(res.STCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "xtcp":
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
res.XTCP = append(res.XTCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
case "sudp":
res.SUDP = append(res.SUDP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
}
}
sort.Sort(ByProxyStatusResp(res.Tcp))
sort.Sort(ByProxyStatusResp(res.Udp))
sort.Sort(ByProxyStatusResp(res.Http))
sort.Sort(ByProxyStatusResp(res.Https))
sort.Sort(ByProxyStatusResp(res.Stcp))
sort.Sort(ByProxyStatusResp(res.Xtcp))
sort.Sort(ByProxyStatusResp(res.TCP))
sort.Sort(ByProxyStatusResp(res.UDP))
sort.Sort(ByProxyStatusResp(res.HTTP))
sort.Sort(ByProxyStatusResp(res.HTTPS))
sort.Sort(ByProxyStatusResp(res.STCP))
sort.Sort(ByProxyStatusResp(res.XTCP))
sort.Sort(ByProxyStatusResp(res.SUDP))
return
}

View File

@@ -28,6 +28,7 @@ import (
"github.com/fatedier/frp/models/auth"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/transport"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/xlog"
@@ -38,11 +39,11 @@ import (
type Control struct {
// uniq id got from frps, attach it in loginMsg
runId string
runID string
// manage all proxies
pxyCfgs map[string]config.ProxyConf
pm *proxy.ProxyManager
pm *proxy.Manager
// manage all visitors
vm *VisitorManager
@@ -88,7 +89,7 @@ type Control struct {
authSetter auth.Setter
}
func NewControl(ctx context.Context, runId string, conn net.Conn, session *fmux.Session,
func NewControl(ctx context.Context, runID string, conn net.Conn, session *fmux.Session,
clientCfg config.ClientCommonConf,
pxyCfgs map[string]config.ProxyConf,
visitorCfgs map[string]config.VisitorConf,
@@ -97,7 +98,7 @@ func NewControl(ctx context.Context, runId string, conn net.Conn, session *fmux.
// new xlog instance
ctl := &Control{
runId: runId,
runID: runID,
conn: conn,
session: session,
pxyCfgs: pxyCfgs,
@@ -114,7 +115,7 @@ func NewControl(ctx context.Context, runId string, conn net.Conn, session *fmux.
ctx: ctx,
authSetter: authSetter,
}
ctl.pm = proxy.NewProxyManager(ctl.ctx, ctl.sendCh, clientCfg, serverUDPPort)
ctl.pm = proxy.NewManager(ctl.ctx, ctl.sendCh, clientCfg, serverUDPPort)
ctl.vm = NewVisitorManager(ctl.ctx, ctl)
ctl.vm.Reload(visitorCfgs)
@@ -140,7 +141,7 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
}
m := &msg.NewWorkConn{
RunId: ctl.runId,
RunID: ctl.runID,
}
if err = ctl.authSetter.SetNewWorkConn(m); err != nil {
xl.Warn("error during NewWorkConn authentication: %v", err)
@@ -183,6 +184,7 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
func (ctl *Control) Close() error {
ctl.pm.Close()
ctl.conn.Close()
ctl.vm.Close()
if ctl.session != nil {
ctl.session.Close()
}
@@ -197,7 +199,7 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} {
// connectServer return a new connection to frps
func (ctl *Control) connectServer() (conn net.Conn, err error) {
xl := ctl.xl
if ctl.clientCfg.TcpMux {
if ctl.clientCfg.TCPMux {
stream, errRet := ctl.session.OpenStream()
if errRet != nil {
err = errRet
@@ -207,12 +209,19 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) {
conn = stream
} else {
var tlsConfig *tls.Config
if ctl.clientCfg.TLSEnable {
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
tlsConfig, err = transport.NewServerTLSConfig(
ctl.clientCfg.TLSCertFile,
ctl.clientCfg.TLSKeyFile,
ctl.clientCfg.TLSTrustedCaFile)
if err != nil {
xl.Warn("fail to build tls configuration when connecting to server, err: %v", err)
return
}
}
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HttpProxy, ctl.clientCfg.Protocol,
conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol,
fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig)
if err != nil {
xl.Warn("start new connection to server error: %v", err)
@@ -236,18 +245,17 @@ func (ctl *Control) reader() {
encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
for {
if m, err := msg.ReadMsg(encReader); err != nil {
m, err := msg.ReadMsg(encReader)
if err != nil {
if err == io.EOF {
xl.Debug("read from control connection EOF")
return
} else {
xl.Warn("read error: %v", err)
ctl.conn.Close()
return
}
} else {
ctl.readCh <- m
xl.Warn("read error: %v", err)
ctl.conn.Close()
return
}
ctl.readCh <- m
}
}
@@ -262,14 +270,15 @@ func (ctl *Control) writer() {
return
}
for {
if m, ok := <-ctl.sendCh; !ok {
m, ok := <-ctl.sendCh
if !ok {
xl.Info("control writer is closing")
return
} else {
if err := msg.WriteMsg(encWriter, m); err != nil {
xl.Warn("write message to control connection error: %v", err)
return
}
}
if err := msg.WriteMsg(encWriter, m); err != nil {
xl.Warn("write message to control connection error: %v", err)
return
}
}
}

View File

@@ -6,10 +6,10 @@ import (
"github.com/fatedier/frp/models/msg"
)
type EventType int
type Type int
const (
EvStartProxy EventType = iota
EvStartProxy Type = iota
EvCloseProxy
)
@@ -17,7 +17,7 @@ var (
ErrPayloadType = errors.New("error payload type")
)
type EventHandler func(evType EventType, payload interface{}) error
type Handler func(evType Type, payload interface{}) error
type StartProxyPayload struct {
NewProxyMsg *msg.NewProxy

View File

@@ -31,7 +31,7 @@ var (
ErrHealthCheckType = errors.New("error health check type")
)
type HealthCheckMonitor struct {
type Monitor struct {
checkType string
interval time.Duration
timeout time.Duration
@@ -52,10 +52,10 @@ type HealthCheckMonitor struct {
cancel context.CancelFunc
}
func NewHealthCheckMonitor(ctx context.Context, checkType string,
func NewMonitor(ctx context.Context, checkType string,
intervalS int, timeoutS int, maxFailedTimes int,
addr string, url string,
statusNormalFn func(), statusFailedFn func()) *HealthCheckMonitor {
statusNormalFn func(), statusFailedFn func()) *Monitor {
if intervalS <= 0 {
intervalS = 10
@@ -67,7 +67,7 @@ func NewHealthCheckMonitor(ctx context.Context, checkType string,
maxFailedTimes = 1
}
newctx, cancel := context.WithCancel(ctx)
return &HealthCheckMonitor{
return &Monitor{
checkType: checkType,
interval: time.Duration(intervalS) * time.Second,
timeout: time.Duration(timeoutS) * time.Second,
@@ -82,15 +82,15 @@ func NewHealthCheckMonitor(ctx context.Context, checkType string,
}
}
func (monitor *HealthCheckMonitor) Start() {
func (monitor *Monitor) Start() {
go monitor.checkWorker()
}
func (monitor *HealthCheckMonitor) Stop() {
func (monitor *Monitor) Stop() {
monitor.cancel()
}
func (monitor *HealthCheckMonitor) checkWorker() {
func (monitor *Monitor) checkWorker() {
xl := xlog.FromContextSafe(monitor.ctx)
for {
doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
@@ -126,18 +126,18 @@ func (monitor *HealthCheckMonitor) checkWorker() {
}
}
func (monitor *HealthCheckMonitor) doCheck(ctx context.Context) error {
func (monitor *Monitor) doCheck(ctx context.Context) error {
switch monitor.checkType {
case "tcp":
return monitor.doTcpCheck(ctx)
return monitor.doTCPCheck(ctx)
case "http":
return monitor.doHttpCheck(ctx)
return monitor.doHTTPCheck(ctx)
default:
return ErrHealthCheckType
}
}
func (monitor *HealthCheckMonitor) doTcpCheck(ctx context.Context) error {
func (monitor *Monitor) doTCPCheck(ctx context.Context) error {
// if tcp address is not specified, always return nil
if monitor.addr == "" {
return nil
@@ -152,7 +152,7 @@ func (monitor *HealthCheckMonitor) doTcpCheck(ctx context.Context) error {
return nil
}
func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
req, err := http.NewRequest("GET", monitor.url, nil)
if err != nil {
return err

View File

@@ -67,41 +67,47 @@ func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.Cl
ctx: ctx,
}
switch cfg := pxyConf.(type) {
case *config.TcpProxyConf:
pxy = &TcpProxy{
case *config.TCPProxyConf:
pxy = &TCPProxy{
BaseProxy: &baseProxy,
cfg: cfg,
}
case *config.TcpMuxProxyConf:
pxy = &TcpMuxProxy{
case *config.TCPMuxProxyConf:
pxy = &TCPMuxProxy{
BaseProxy: &baseProxy,
cfg: cfg,
}
case *config.UdpProxyConf:
pxy = &UdpProxy{
case *config.UDPProxyConf:
pxy = &UDPProxy{
BaseProxy: &baseProxy,
cfg: cfg,
}
case *config.HttpProxyConf:
pxy = &HttpProxy{
case *config.HTTPProxyConf:
pxy = &HTTPProxy{
BaseProxy: &baseProxy,
cfg: cfg,
}
case *config.HttpsProxyConf:
pxy = &HttpsProxy{
case *config.HTTPSProxyConf:
pxy = &HTTPSProxy{
BaseProxy: &baseProxy,
cfg: cfg,
}
case *config.StcpProxyConf:
pxy = &StcpProxy{
case *config.STCPProxyConf:
pxy = &STCPProxy{
BaseProxy: &baseProxy,
cfg: cfg,
}
case *config.XtcpProxyConf:
pxy = &XtcpProxy{
case *config.XTCPProxyConf:
pxy = &XTCPProxy{
BaseProxy: &baseProxy,
cfg: cfg,
}
case *config.SUDPProxyConf:
pxy = &SUDPProxy{
BaseProxy: &baseProxy,
cfg: cfg,
closeCh: make(chan struct{}),
}
}
return
}
@@ -118,14 +124,14 @@ type BaseProxy struct {
}
// TCP
type TcpProxy struct {
type TCPProxy struct {
*BaseProxy
cfg *config.TcpProxyConf
cfg *config.TCPProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *TcpProxy) Run() (err error) {
func (pxy *TCPProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
@@ -135,26 +141,26 @@ func (pxy *TcpProxy) Run() (err error) {
return
}
func (pxy *TcpProxy) Close() {
func (pxy *TCPProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *TcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
func (pxy *TCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
conn, []byte(pxy.clientCfg.Token), m)
}
// TCP Multiplexer
type TcpMuxProxy struct {
type TCPMuxProxy struct {
*BaseProxy
cfg *config.TcpMuxProxyConf
cfg *config.TCPMuxProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *TcpMuxProxy) Run() (err error) {
func (pxy *TCPMuxProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
@@ -164,26 +170,26 @@ func (pxy *TcpMuxProxy) Run() (err error) {
return
}
func (pxy *TcpMuxProxy) Close() {
func (pxy *TCPMuxProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *TcpMuxProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
func (pxy *TCPMuxProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
conn, []byte(pxy.clientCfg.Token), m)
}
// HTTP
type HttpProxy struct {
type HTTPProxy struct {
*BaseProxy
cfg *config.HttpProxyConf
cfg *config.HTTPProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *HttpProxy) Run() (err error) {
func (pxy *HTTPProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
@@ -193,26 +199,26 @@ func (pxy *HttpProxy) Run() (err error) {
return
}
func (pxy *HttpProxy) Close() {
func (pxy *HTTPProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *HttpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
func (pxy *HTTPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
conn, []byte(pxy.clientCfg.Token), m)
}
// HTTPS
type HttpsProxy struct {
type HTTPSProxy struct {
*BaseProxy
cfg *config.HttpsProxyConf
cfg *config.HTTPSProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *HttpsProxy) Run() (err error) {
func (pxy *HTTPSProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
@@ -222,26 +228,26 @@ func (pxy *HttpsProxy) Run() (err error) {
return
}
func (pxy *HttpsProxy) Close() {
func (pxy *HTTPSProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *HttpsProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
func (pxy *HTTPSProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
conn, []byte(pxy.clientCfg.Token), m)
}
// STCP
type StcpProxy struct {
type STCPProxy struct {
*BaseProxy
cfg *config.StcpProxyConf
cfg *config.STCPProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *StcpProxy) Run() (err error) {
func (pxy *STCPProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
@@ -251,26 +257,26 @@ func (pxy *StcpProxy) Run() (err error) {
return
}
func (pxy *StcpProxy) Close() {
func (pxy *STCPProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *StcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
func (pxy *STCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
conn, []byte(pxy.clientCfg.Token), m)
}
// XTCP
type XtcpProxy struct {
type XTCPProxy struct {
*BaseProxy
cfg *config.XtcpProxyConf
cfg *config.XTCPProxyConf
proxyPlugin plugin.Plugin
}
func (pxy *XtcpProxy) Run() (err error) {
func (pxy *XTCPProxy) Run() (err error) {
if pxy.cfg.Plugin != "" {
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
if err != nil {
@@ -280,13 +286,13 @@ func (pxy *XtcpProxy) Run() (err error) {
return
}
func (pxy *XtcpProxy) Close() {
func (pxy *XTCPProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
}
}
func (pxy *XtcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
xl := pxy.xl
defer conn.Close()
var natHoleSidMsg msg.NatHoleSid
@@ -383,7 +389,7 @@ func (pxy *XtcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
lConn.WriteToUDP(sidBuf[:n], uAddr)
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, uAddr.String())
kcpConn, err := frpNet.NewKCPConnFromUDP(lConn, false, uAddr.String())
if err != nil {
xl.Error("create kcp connection from udp connection error: %v", err)
return
@@ -404,11 +410,11 @@ func (pxy *XtcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
return
}
HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter,
muxConn, []byte(pxy.cfg.Sk), m)
}
func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
func (pxy *XTCPProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
if err != nil {
return err
@@ -428,28 +434,28 @@ func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, c
}
// UDP
type UdpProxy struct {
type UDPProxy struct {
*BaseProxy
cfg *config.UdpProxyConf
cfg *config.UDPProxyConf
localAddr *net.UDPAddr
readCh chan *msg.UdpPacket
readCh chan *msg.UDPPacket
// include msg.UdpPacket and msg.Ping
// include msg.UDPPacket and msg.Ping
sendCh chan msg.Message
workConn net.Conn
}
func (pxy *UdpProxy) Run() (err error) {
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIp, pxy.cfg.LocalPort))
func (pxy *UDPProxy) Run() (err error) {
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIP, pxy.cfg.LocalPort))
if err != nil {
return
}
return
}
func (pxy *UdpProxy) Close() {
func (pxy *UDPProxy) Close() {
pxy.mu.Lock()
defer pxy.mu.Unlock()
@@ -467,29 +473,42 @@ func (pxy *UdpProxy) Close() {
}
}
func (pxy *UdpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
xl := pxy.xl
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
// close resources releated with old workConn
pxy.Close()
var rwc io.ReadWriteCloser = conn
var err error
if pxy.limiter != nil {
rwc := frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
return conn.Close()
})
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
}
if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
if err != nil {
conn.Close()
xl.Error("create encryption stream error: %v", err)
return
}
}
if pxy.cfg.UseCompression {
rwc = frpIo.WithCompression(rwc)
}
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
pxy.mu.Lock()
pxy.workConn = conn
pxy.readCh = make(chan *msg.UdpPacket, 1024)
pxy.readCh = make(chan *msg.UDPPacket, 1024)
pxy.sendCh = make(chan msg.Message, 1024)
pxy.closed = false
pxy.mu.Unlock()
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UdpPacket) {
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
for {
var udpMsg msg.UdpPacket
var udpMsg msg.UDPPacket
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
xl.Warn("read from workConn for udp error: %v", errRet)
return
@@ -510,7 +529,7 @@ func (pxy *UdpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
var errRet error
for rawMsg := range sendCh {
switch m := rawMsg.(type) {
case *msg.UdpPacket:
case *msg.UDPPacket:
xl.Trace("send udp package to workConn: %s", m.Content)
case *msg.Ping:
xl.Trace("send ping message to udp workConn")
@@ -537,11 +556,169 @@ func (pxy *UdpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
go workConnSenderFn(pxy.workConn, pxy.sendCh)
go workConnReaderFn(pxy.workConn, pxy.readCh)
go heartbeatFn(pxy.workConn, pxy.sendCh)
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh)
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
}
type SUDPProxy struct {
*BaseProxy
cfg *config.SUDPProxyConf
localAddr *net.UDPAddr
closeCh chan struct{}
}
func (pxy *SUDPProxy) Run() (err error) {
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIP, pxy.cfg.LocalPort))
if err != nil {
return
}
return
}
func (pxy *SUDPProxy) Close() {
pxy.mu.Lock()
defer pxy.mu.Unlock()
select {
case <-pxy.closeCh:
return
default:
close(pxy.closeCh)
}
}
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
xl := pxy.xl
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
var rwc io.ReadWriteCloser = conn
var err error
if pxy.limiter != nil {
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
return conn.Close()
})
}
if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
if err != nil {
conn.Close()
xl.Error("create encryption stream error: %v", err)
return
}
}
if pxy.cfg.UseCompression {
rwc = frpIo.WithCompression(rwc)
}
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
workConn := conn
readCh := make(chan *msg.UDPPacket, 1024)
sendCh := make(chan msg.Message, 1024)
isClose := false
mu := &sync.Mutex{}
closeFn := func() {
mu.Lock()
defer mu.Unlock()
if isClose {
return
}
isClose = true
if workConn != nil {
workConn.Close()
}
close(readCh)
close(sendCh)
}
// udp service <- frpc <- frps <- frpc visitor <- user
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
defer closeFn()
for {
// first to check sudp proxy is closed or not
select {
case <-pxy.closeCh:
xl.Trace("frpc sudp proxy is closed")
return
default:
}
var udpMsg msg.UDPPacket
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
xl.Warn("read from workConn for sudp error: %v", errRet)
return
}
if errRet := errors.PanicToError(func() {
readCh <- &udpMsg
}); errRet != nil {
xl.Warn("reader goroutine for sudp work connection closed: %v", errRet)
return
}
}
}
// udp service -> frpc -> frps -> frpc visitor -> user
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
defer func() {
closeFn()
xl.Info("writer goroutine for sudp work connection closed")
}()
var errRet error
for rawMsg := range sendCh {
switch m := rawMsg.(type) {
case *msg.UDPPacket:
xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
case *msg.Ping:
xl.Trace("frpc send ping message to frpc visitor")
}
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
xl.Error("sudp work write error: %v", errRet)
return
}
}
}
heartbeatFn := func(conn net.Conn, sendCh chan msg.Message) {
ticker := time.NewTicker(30 * time.Second)
defer func() {
ticker.Stop()
closeFn()
}()
var errRet error
for {
select {
case <-ticker.C:
if errRet = errors.PanicToError(func() {
sendCh <- &msg.Ping{}
}); errRet != nil {
xl.Warn("heartbeat goroutine for sudp work connection closed")
return
}
case <-pxy.closeCh:
xl.Trace("frpc sudp proxy is closed")
return
}
}
}
go workConnSenderFn(workConn, sendCh)
go workConnReaderFn(workConn, readCh)
go heartbeatFn(workConn, sendCh)
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
}
// Common handler for tcp work connections.
func HandleTcpWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn) {
xl := xlog.FromContextSafe(ctx)
var (
@@ -608,22 +785,22 @@ func HandleTcpWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
proxyPlugin.Handle(remote, workConn, extraInfo)
xl.Debug("handle by plugin finished")
return
} else {
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
if err != nil {
workConn.Close()
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
return
}
xl.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
if len(extraInfo) > 0 {
localConn.Write(extraInfo)
}
frpIo.Join(localConn, remote)
xl.Debug("join connections closed")
}
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIP, localInfo.LocalPort))
if err != nil {
workConn.Close()
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err)
return
}
xl.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
if len(extraInfo) > 0 {
localConn.Write(extraInfo)
}
frpIo.Join(localConn, remote)
xl.Debug("join connections closed")
}

View File

@@ -14,9 +14,9 @@ import (
"github.com/fatedier/golib/errors"
)
type ProxyManager struct {
type Manager struct {
sendCh chan (msg.Message)
proxies map[string]*ProxyWrapper
proxies map[string]*Wrapper
closed bool
mu sync.RWMutex
@@ -29,10 +29,10 @@ type ProxyManager struct {
ctx context.Context
}
func NewProxyManager(ctx context.Context, msgSendCh chan (msg.Message), clientCfg config.ClientCommonConf, serverUDPPort int) *ProxyManager {
return &ProxyManager{
func NewManager(ctx context.Context, msgSendCh chan (msg.Message), clientCfg config.ClientCommonConf, serverUDPPort int) *Manager {
return &Manager{
sendCh: msgSendCh,
proxies: make(map[string]*ProxyWrapper),
proxies: make(map[string]*Wrapper),
closed: false,
clientCfg: clientCfg,
serverUDPPort: serverUDPPort,
@@ -40,7 +40,7 @@ func NewProxyManager(ctx context.Context, msgSendCh chan (msg.Message), clientCf
}
}
func (pm *ProxyManager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
func (pm *Manager) StartProxy(name string, remoteAddr string, serverRespErr string) error {
pm.mu.RLock()
pxy, ok := pm.proxies[name]
pm.mu.RUnlock()
@@ -55,16 +55,16 @@ func (pm *ProxyManager) StartProxy(name string, remoteAddr string, serverRespErr
return nil
}
func (pm *ProxyManager) Close() {
func (pm *Manager) Close() {
pm.mu.Lock()
defer pm.mu.Unlock()
for _, pxy := range pm.proxies {
pxy.Stop()
}
pm.proxies = make(map[string]*ProxyWrapper)
pm.proxies = make(map[string]*Wrapper)
}
func (pm *ProxyManager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWorkConn) {
func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWorkConn) {
pm.mu.RLock()
pw, ok := pm.proxies[name]
pm.mu.RUnlock()
@@ -75,7 +75,7 @@ func (pm *ProxyManager) HandleWorkConn(name string, workConn net.Conn, m *msg.St
}
}
func (pm *ProxyManager) HandleEvent(evType event.EventType, payload interface{}) error {
func (pm *Manager) HandleEvent(evType event.Type, payload interface{}) error {
var m msg.Message
switch e := payload.(type) {
case *event.StartProxyPayload:
@@ -92,8 +92,8 @@ func (pm *ProxyManager) HandleEvent(evType event.EventType, payload interface{})
return err
}
func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
ps := make([]*ProxyStatus, 0)
func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
ps := make([]*WorkingStatus, 0)
pm.mu.RLock()
defer pm.mu.RUnlock()
for _, pxy := range pm.proxies {
@@ -102,7 +102,7 @@ func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
return ps
}
func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
xl := xlog.FromContextSafe(pm.ctx)
pm.mu.Lock()
defer pm.mu.Unlock()
@@ -133,7 +133,7 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
addPxyNames := make([]string, 0)
for name, cfg := range pxyCfgs {
if _, ok := pm.proxies[name]; !ok {
pxy := NewProxyWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.serverUDPPort)
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.serverUDPPort)
pm.proxies[name] = pxy
addPxyNames = append(addPxyNames, name)

View File

@@ -18,12 +18,12 @@ import (
)
const (
ProxyStatusNew = "new"
ProxyStatusWaitStart = "wait start"
ProxyStatusStartErr = "start error"
ProxyStatusRunning = "running"
ProxyStatusCheckFailed = "check failed"
ProxyStatusClosed = "closed"
ProxyPhaseNew = "new"
ProxyPhaseWaitStart = "wait start"
ProxyPhaseStartErr = "start error"
ProxyPhaseRunning = "running"
ProxyPhaseCheckFailed = "check failed"
ProxyPhaseClosed = "closed"
)
var (
@@ -32,29 +32,29 @@ var (
startErrTimeout = 30 * time.Second
)
type ProxyStatus struct {
Name string `json:"name"`
Type string `json:"type"`
Status string `json:"status"`
Err string `json:"err"`
Cfg config.ProxyConf `json:"cfg"`
type WorkingStatus struct {
Name string `json:"name"`
Type string `json:"type"`
Phase string `json:"status"`
Err string `json:"err"`
Cfg config.ProxyConf `json:"cfg"`
// Got from server.
RemoteAddr string `json:"remote_addr"`
}
type ProxyWrapper struct {
ProxyStatus
type Wrapper struct {
WorkingStatus
// underlying proxy
pxy Proxy
// if ProxyConf has healcheck config
// monitor will watch if it is alive
monitor *health.HealthCheckMonitor
monitor *health.Monitor
// event handler
handler event.EventHandler
handler event.Handler
health uint32
lastSendStartMsg time.Time
@@ -67,15 +67,15 @@ type ProxyWrapper struct {
ctx context.Context
}
func NewProxyWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, serverUDPPort int) *ProxyWrapper {
func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.Handler, serverUDPPort int) *Wrapper {
baseInfo := cfg.GetBaseInfo()
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
pw := &ProxyWrapper{
ProxyStatus: ProxyStatus{
Name: baseInfo.ProxyName,
Type: baseInfo.ProxyType,
Status: ProxyStatusNew,
Cfg: cfg,
pw := &Wrapper{
WorkingStatus: WorkingStatus{
Name: baseInfo.ProxyName,
Type: baseInfo.ProxyType,
Phase: ProxyPhaseNew,
Cfg: cfg,
},
closeCh: make(chan struct{}),
healthNotifyCh: make(chan struct{}),
@@ -86,9 +86,9 @@ func NewProxyWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config
if baseInfo.HealthCheckType != "" {
pw.health = 1 // means failed
pw.monitor = health.NewHealthCheckMonitor(pw.ctx, baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr,
baseInfo.HealthCheckUrl, pw.statusNormalCallback, pw.statusFailedCallback)
baseInfo.HealthCheckURL, pw.statusNormalCallback, pw.statusFailedCallback)
xl.Trace("enable health check monitor")
}
@@ -96,16 +96,16 @@ func NewProxyWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config
return pw
}
func (pw *ProxyWrapper) SetRunningStatus(remoteAddr string, respErr string) error {
func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
pw.mu.Lock()
defer pw.mu.Unlock()
if pw.Status != ProxyStatusWaitStart {
if pw.Phase != ProxyPhaseWaitStart {
return fmt.Errorf("status not wait start, ignore start message")
}
pw.RemoteAddr = remoteAddr
if respErr != "" {
pw.Status = ProxyStatusStartErr
pw.Phase = ProxyPhaseStartErr
pw.Err = respErr
pw.lastStartErr = time.Now()
return fmt.Errorf(pw.Err)
@@ -113,25 +113,25 @@ func (pw *ProxyWrapper) SetRunningStatus(remoteAddr string, respErr string) erro
if err := pw.pxy.Run(); err != nil {
pw.close()
pw.Status = ProxyStatusStartErr
pw.Phase = ProxyPhaseStartErr
pw.Err = err.Error()
pw.lastStartErr = time.Now()
return err
}
pw.Status = ProxyStatusRunning
pw.Phase = ProxyPhaseRunning
pw.Err = ""
return nil
}
func (pw *ProxyWrapper) Start() {
func (pw *Wrapper) Start() {
go pw.checkWorker()
if pw.monitor != nil {
go pw.monitor.Start()
}
}
func (pw *ProxyWrapper) Stop() {
func (pw *Wrapper) Stop() {
pw.mu.Lock()
defer pw.mu.Unlock()
close(pw.closeCh)
@@ -140,11 +140,11 @@ func (pw *ProxyWrapper) Stop() {
if pw.monitor != nil {
pw.monitor.Stop()
}
pw.Status = ProxyStatusClosed
pw.Phase = ProxyPhaseClosed
pw.close()
}
func (pw *ProxyWrapper) close() {
func (pw *Wrapper) close() {
pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{
CloseProxyMsg: &msg.CloseProxy{
ProxyName: pw.Name,
@@ -152,7 +152,7 @@ func (pw *ProxyWrapper) close() {
})
}
func (pw *ProxyWrapper) checkWorker() {
func (pw *Wrapper) checkWorker() {
xl := pw.xl
if pw.monitor != nil {
// let monitor do check request first
@@ -163,13 +163,13 @@ func (pw *ProxyWrapper) checkWorker() {
now := time.Now()
if atomic.LoadUint32(&pw.health) == 0 {
pw.mu.Lock()
if pw.Status == ProxyStatusNew ||
pw.Status == ProxyStatusCheckFailed ||
(pw.Status == ProxyStatusWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
(pw.Status == ProxyStatusStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
if pw.Phase == ProxyPhaseNew ||
pw.Phase == ProxyPhaseCheckFailed ||
(pw.Phase == ProxyPhaseWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
(pw.Phase == ProxyPhaseStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
xl.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart)
pw.Status = ProxyStatusWaitStart
xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseWaitStart)
pw.Phase = ProxyPhaseWaitStart
var newProxyMsg msg.NewProxy
pw.Cfg.MarshalToMsg(&newProxyMsg)
@@ -181,10 +181,10 @@ func (pw *ProxyWrapper) checkWorker() {
pw.mu.Unlock()
} else {
pw.mu.Lock()
if pw.Status == ProxyStatusRunning || pw.Status == ProxyStatusWaitStart {
if pw.Phase == ProxyPhaseRunning || pw.Phase == ProxyPhaseWaitStart {
pw.close()
xl.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusCheckFailed)
pw.Status = ProxyStatusCheckFailed
xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseCheckFailed)
pw.Phase = ProxyPhaseCheckFailed
}
pw.mu.Unlock()
}
@@ -198,7 +198,7 @@ func (pw *ProxyWrapper) checkWorker() {
}
}
func (pw *ProxyWrapper) statusNormalCallback() {
func (pw *Wrapper) statusNormalCallback() {
xl := pw.xl
atomic.StoreUint32(&pw.health, 0)
errors.PanicToError(func() {
@@ -210,7 +210,7 @@ func (pw *ProxyWrapper) statusNormalCallback() {
xl.Info("health check success")
}
func (pw *ProxyWrapper) statusFailedCallback() {
func (pw *Wrapper) statusFailedCallback() {
xl := pw.xl
atomic.StoreUint32(&pw.health, 1)
errors.PanicToError(func() {
@@ -222,7 +222,7 @@ func (pw *ProxyWrapper) statusFailedCallback() {
xl.Info("health check failed")
}
func (pw *ProxyWrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
xl := pw.xl
pw.mu.RLock()
pxy := pw.pxy
@@ -235,13 +235,13 @@ func (pw *ProxyWrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
}
}
func (pw *ProxyWrapper) GetStatus() *ProxyStatus {
func (pw *Wrapper) GetStatus() *WorkingStatus {
pw.mu.RLock()
defer pw.mu.RUnlock()
ps := &ProxyStatus{
ps := &WorkingStatus{
Name: pw.Name,
Type: pw.Type,
Status: pw.Status,
Phase: pw.Phase,
Err: pw.Err,
Cfg: pw.Cfg,
RemoteAddr: pw.RemoteAddr,

View File

@@ -18,6 +18,7 @@ import (
"context"
"crypto/tls"
"fmt"
"github.com/fatedier/frp/models/transport"
"io/ioutil"
"net"
"runtime"
@@ -40,7 +41,7 @@ import (
// Service is a client service.
type Service struct {
// uniq id got from frps, attach it in loginMsg
runId string
runID string
// manager control connection with server
ctl *Control
@@ -73,7 +74,7 @@ func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf
ctx, cancel := context.WithCancel(context.Background())
svr = &Service{
authSetter: auth.NewAuthSetter(cfg.AuthClientConfig),
authSetter: auth.NewAuthSetter(cfg.ClientConfig),
cfg: cfg,
cfgFile: cfgFile,
pxyCfgs: pxyCfgs,
@@ -104,12 +105,11 @@ func (svr *Service) Run() error {
// otherwise sleep a while and try again to connect to server
if svr.cfg.LoginFailExit {
return err
} else {
time.Sleep(10 * time.Second)
}
time.Sleep(10 * time.Second)
} else {
// login success
ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
ctl.Run()
svr.ctlMu.Lock()
svr.ctl = ctl
@@ -142,12 +142,34 @@ func (svr *Service) keepControllerWorking() {
maxDelayTime := 20 * time.Second
delayTime := time.Second
// if frpc reconnect frps, we need to limit retry times in 1min
// current retry logic is sleep 0s, 0s, 0s, 1s, 2s, 4s, 8s, ...
// when exceed 1min, we will reset delay and counts
cutoffTime := time.Now().Add(time.Minute)
reconnectDelay := time.Second
reconnectCounts := 1
for {
<-svr.ctl.ClosedDoneCh()
if atomic.LoadUint32(&svr.exit) != 0 {
return
}
// the first three retry with no delay
if reconnectCounts > 3 {
time.Sleep(reconnectDelay)
reconnectDelay *= 2
}
reconnectCounts++
now := time.Now()
if now.After(cutoffTime) {
// reset
cutoffTime = now.Add(time.Minute)
reconnectDelay = time.Second
reconnectCounts = 1
}
for {
xl.Info("try to reconnect to server...")
conn, session, err := svr.login()
@@ -163,9 +185,12 @@ func (svr *Service) keepControllerWorking() {
// reconnect success, init delayTime
delayTime = time.Second
ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
ctl.Run()
svr.ctlMu.Lock()
if svr.ctl != nil {
svr.ctl.Close()
}
svr.ctl = ctl
svr.ctlMu.Unlock()
break
@@ -180,11 +205,17 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
xl := xlog.FromContextSafe(svr.ctx)
var tlsConfig *tls.Config
if svr.cfg.TLSEnable {
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
tlsConfig, err = transport.NewClientTLSConfig(
svr.cfg.TLSCertFile,
svr.cfg.TLSKeyFile,
svr.cfg.TLSTrustedCaFile,
svr.cfg.ServerAddr)
if err != nil {
xl.Warn("fail to build tls configuration when service login, err: %v", err)
return
}
}
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HttpProxy, svr.cfg.Protocol,
conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol,
fmt.Sprintf("%s:%d", svr.cfg.ServerAddr, svr.cfg.ServerPort), tlsConfig)
if err != nil {
return
@@ -199,7 +230,7 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
}
}()
if svr.cfg.TcpMux {
if svr.cfg.TCPMux {
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 20 * time.Second
fmuxCfg.LogOutput = ioutil.Discard
@@ -223,7 +254,7 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
User: svr.cfg.User,
Version: version.Full(),
Timestamp: time.Now().Unix(),
RunId: svr.runId,
RunID: svr.runID,
Metas: svr.cfg.Metas,
}
@@ -249,12 +280,12 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
return
}
svr.runId = loginRespMsg.RunId
svr.runID = loginRespMsg.RunID
xl.ResetPrefixes()
xl.AppendPrefix(svr.runId)
xl.AppendPrefix(svr.runID)
svr.serverUDPPort = loginRespMsg.ServerUdpPort
xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
svr.serverUDPPort = loginRespMsg.ServerUDPPort
xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunID, loginRespMsg.ServerUDPPort)
return
}
@@ -269,6 +300,8 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
func (svr *Service) Close() {
atomic.StoreUint32(&svr.exit, 1)
svr.ctl.Close()
if svr.ctl != nil {
svr.ctl.Close()
}
svr.cancel()
}

View File

@@ -26,10 +26,12 @@ import (
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/proto/udp"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/xlog"
"github.com/fatedier/golib/errors"
frpIo "github.com/fatedier/golib/io"
"github.com/fatedier/golib/pool"
fmux "github.com/hashicorp/yamux"
@@ -48,16 +50,22 @@ func NewVisitor(ctx context.Context, ctl *Control, cfg config.VisitorConf) (visi
ctx: xlog.NewContext(ctx, xl),
}
switch cfg := cfg.(type) {
case *config.StcpVisitorConf:
visitor = &StcpVisitor{
case *config.STCPVisitorConf:
visitor = &STCPVisitor{
BaseVisitor: &baseVisitor,
cfg: cfg,
}
case *config.XtcpVisitorConf:
visitor = &XtcpVisitor{
case *config.XTCPVisitorConf:
visitor = &XTCPVisitor{
BaseVisitor: &baseVisitor,
cfg: cfg,
}
case *config.SUDPVisitorConf:
visitor = &SUDPVisitor{
BaseVisitor: &baseVisitor,
cfg: cfg,
checkCloseCh: make(chan struct{}),
}
}
return
}
@@ -71,13 +79,13 @@ type BaseVisitor struct {
ctx context.Context
}
type StcpVisitor struct {
type STCPVisitor struct {
*BaseVisitor
cfg *config.StcpVisitorConf
cfg *config.STCPVisitorConf
}
func (sv *StcpVisitor) Run() (err error) {
func (sv *STCPVisitor) Run() (err error) {
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
if err != nil {
return
@@ -87,11 +95,11 @@ func (sv *StcpVisitor) Run() (err error) {
return
}
func (sv *StcpVisitor) Close() {
func (sv *STCPVisitor) Close() {
sv.l.Close()
}
func (sv *StcpVisitor) worker() {
func (sv *STCPVisitor) worker() {
xl := xlog.FromContextSafe(sv.ctx)
for {
conn, err := sv.l.Accept()
@@ -104,7 +112,7 @@ func (sv *StcpVisitor) worker() {
}
}
func (sv *StcpVisitor) handleConn(userConn net.Conn) {
func (sv *STCPVisitor) handleConn(userConn net.Conn) {
xl := xlog.FromContextSafe(sv.ctx)
defer userConn.Close()
@@ -160,13 +168,13 @@ func (sv *StcpVisitor) handleConn(userConn net.Conn) {
frpIo.Join(userConn, remote)
}
type XtcpVisitor struct {
type XTCPVisitor struct {
*BaseVisitor
cfg *config.XtcpVisitorConf
cfg *config.XTCPVisitorConf
}
func (sv *XtcpVisitor) Run() (err error) {
func (sv *XTCPVisitor) Run() (err error) {
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
if err != nil {
return
@@ -176,11 +184,11 @@ func (sv *XtcpVisitor) Run() (err error) {
return
}
func (sv *XtcpVisitor) Close() {
func (sv *XTCPVisitor) Close() {
sv.l.Close()
}
func (sv *XtcpVisitor) worker() {
func (sv *XTCPVisitor) worker() {
xl := xlog.FromContextSafe(sv.ctx)
for {
conn, err := sv.l.Accept()
@@ -193,7 +201,7 @@ func (sv *XtcpVisitor) worker() {
}
}
func (sv *XtcpVisitor) handleConn(userConn net.Conn) {
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
xl := xlog.FromContextSafe(sv.ctx)
defer userConn.Close()
@@ -292,7 +300,7 @@ func (sv *XtcpVisitor) handleConn(userConn net.Conn) {
// wrap kcp connection
var remote io.ReadWriteCloser
remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr)
remote, err = frpNet.NewKCPConnFromUDP(lConn, true, natHoleRespMsg.ClientAddr)
if err != nil {
xl.Error("create kcp connection from udp connection error: %v", err)
return
@@ -328,3 +336,218 @@ func (sv *XtcpVisitor) handleConn(userConn net.Conn) {
frpIo.Join(userConn, muxConnRWCloser)
xl.Debug("join connections closed")
}
type SUDPVisitor struct {
*BaseVisitor
checkCloseCh chan struct{}
// udpConn is the listener of udp packet
udpConn *net.UDPConn
readCh chan *msg.UDPPacket
sendCh chan *msg.UDPPacket
cfg *config.SUDPVisitorConf
}
// SUDP Run start listen a udp port
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))
if err != nil {
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
}
sv.udpConn, err = net.ListenUDP("udp", addr)
if err != nil {
return fmt.Errorf("listen udp port %s error: %v", addr.String(), err)
}
sv.sendCh = make(chan *msg.UDPPacket, 1024)
sv.readCh = make(chan *msg.UDPPacket, 1024)
xl.Info("sudp start to work")
go sv.dispatcher()
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.ctl.clientCfg.UDPPacketSize))
return
}
func (sv *SUDPVisitor) dispatcher() {
xl := xlog.FromContextSafe(sv.ctx)
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:
xl.Info("frpc sudp visitor proxy is closed")
return
default:
}
time.Sleep(3 * time.Second)
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
continue
}
sv.worker(visitorConn)
select {
case <-sv.checkCloseCh:
return
default:
}
}
}
func (sv *SUDPVisitor) worker(workConn net.Conn) {
xl := xlog.FromContextSafe(sv.ctx)
xl.Debug("starting sudp proxy worker")
wg := &sync.WaitGroup{}
wg.Add(2)
closeCh := make(chan struct{})
// udp service -> frpc -> frps -> frpc visitor -> user
workConnReaderFn := func(conn net.Conn) {
defer func() {
conn.Close()
close(closeCh)
wg.Done()
}()
for {
var (
rawMsg msg.Message
errRet error
)
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
xl.Warn("read from workconn for user udp conn error: %v", errRet)
return
}
conn.SetReadDeadline(time.Time{})
switch m := rawMsg.(type) {
case *msg.Ping:
xl.Debug("frpc visitor get ping message from frpc")
continue
case *msg.UDPPacket:
if errRet := errors.PanicToError(func() {
sv.readCh <- m
xl.Trace("frpc visitor get udp packet from frpc")
}); errRet != nil {
xl.Info("reader goroutine for udp work connection closed")
return
}
}
}
}
// udp service <- frpc <- frps <- frpc visitor <- user
workConnSenderFn := func(conn net.Conn) {
defer func() {
conn.Close()
wg.Done()
}()
var errRet error
for {
select {
case udpMsg, ok := <-sv.sendCh:
if !ok {
xl.Info("sender goroutine for udp work connection closed")
return
}
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
return
}
case <-closeCh:
return
}
}
}
go workConnReaderFn(workConn)
go workConnSenderFn(workConn)
wg.Wait()
xl.Info("sudp worker is closed")
}
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
xl := xlog.FromContextSafe(sv.ctx)
visitorConn, err := sv.ctl.connectServer()
if err != nil {
return nil, fmt.Errorf("frpc connect frps error: %v", err)
}
now := time.Now().Unix()
newVisitorConnMsg := &msg.NewVisitorConn{
ProxyName: sv.cfg.ServerName,
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
Timestamp: now,
UseEncryption: sv.cfg.UseEncryption,
UseCompression: sv.cfg.UseCompression,
}
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
if err != nil {
return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err)
}
var newVisitorConnRespMsg msg.NewVisitorConnResp
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
if err != nil {
return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err)
}
visitorConn.SetReadDeadline(time.Time{})
if newVisitorConnRespMsg.Error != "" {
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
}
var remote io.ReadWriteCloser
remote = visitorConn
if sv.cfg.UseEncryption {
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
if err != nil {
xl.Error("create encryption stream error: %v", err)
return nil, err
}
}
if sv.cfg.UseCompression {
remote = frpIo.WithCompression(remote)
}
return frpNet.WrapReadWriteCloserToConn(remote, visitorConn), nil
}
func (sv *SUDPVisitor) Close() {
sv.mu.Lock()
defer sv.mu.Unlock()
select {
case <-sv.checkCloseCh:
return
default:
close(sv.checkCloseCh)
}
if sv.udpConn != nil {
sv.udpConn.Close()
}
close(sv.readCh)
close(sv.sendCh)
}

View File

@@ -33,6 +33,8 @@ type VisitorManager struct {
mu sync.Mutex
ctx context.Context
stopCh chan struct{}
}
func NewVisitorManager(ctx context.Context, ctl *Control) *VisitorManager {
@@ -42,22 +44,32 @@ func NewVisitorManager(ctx context.Context, ctl *Control) *VisitorManager {
visitors: make(map[string]Visitor),
checkInterval: 10 * time.Second,
ctx: ctx,
stopCh: make(chan struct{}),
}
}
func (vm *VisitorManager) Run() {
xl := xlog.FromContextSafe(vm.ctx)
ticker := time.NewTicker(vm.checkInterval)
defer ticker.Stop()
for {
time.Sleep(vm.checkInterval)
vm.mu.Lock()
for _, cfg := range vm.cfgs {
name := cfg.GetBaseInfo().ProxyName
if _, exist := vm.visitors[name]; !exist {
xl.Info("try to start visitor [%s]", name)
vm.startVisitor(cfg)
select {
case <-vm.stopCh:
xl.Info("gracefully shutdown visitor manager")
return
case <-ticker.C:
vm.mu.Lock()
for _, cfg := range vm.cfgs {
name := cfg.GetBaseInfo().ProxyName
if _, exist := vm.visitors[name]; !exist {
xl.Info("try to start visitor [%s]", name)
vm.startVisitor(cfg)
}
}
vm.mu.Unlock()
}
vm.mu.Unlock()
}
}
@@ -126,4 +138,9 @@ func (vm *VisitorManager) Close() {
for _, v := range vm.visitors {
v.Close()
}
select {
case <-vm.stopCh:
default:
close(vm.stopCh)
}
}

View File

@@ -26,17 +26,10 @@ import (
)
func init() {
httpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
httpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
httpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
httpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
RegisterCommonFlags(httpCmd)
httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
httpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
httpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
@@ -60,20 +53,20 @@ var httpCmd = &cobra.Command{
os.Exit(1)
}
cfg := &config.HttpProxyConf{}
cfg := &config.HTTPProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.HttpProxy
cfg.LocalIp = localIp
cfg.ProxyType = consts.HTTPProxy
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.CustomDomains = strings.Split(customDomains, ",")
cfg.SubDomain = subDomain
cfg.Locations = strings.Split(locations, ",")
cfg.HttpUser = httpUser
cfg.HttpPwd = httpPwd
cfg.HTTPUser = httpUser
cfg.HTTPPwd = httpPwd
cfg.HostHeaderRewrite = hostHeaderRewrite
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression

View File

@@ -26,17 +26,10 @@ import (
)
func init() {
httpsCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
httpsCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
httpsCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
httpsCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
RegisterCommonFlags(httpsCmd)
httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
httpsCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
@@ -56,14 +49,14 @@ var httpsCmd = &cobra.Command{
os.Exit(1)
}
cfg := &config.HttpsProxyConf{}
cfg := &config.HTTPSProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.HttpsProxy
cfg.LocalIp = localIp
cfg.ProxyType = consts.HTTPSProxy
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.CustomDomains = strings.Split(customDomains, ",")
cfg.SubDomain = subDomain

View File

@@ -53,7 +53,7 @@ var (
disableLogColor bool
proxyName string
localIp string
localIP string
localPort int
remotePort int
useEncryption bool
@@ -71,6 +71,8 @@ var (
bindAddr string
bindPort int
tlsEnable bool
kcpDoneCh chan struct{}
)
@@ -81,6 +83,18 @@ func init() {
kcpDoneCh = make(chan struct{})
}
func RegisterCommonFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
cmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
cmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
cmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
cmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
cmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "enable frpc tls")
}
var rootCmd = &cobra.Command{
Use: "frpc",
Short: "frpc is the client of frp (https://github.com/fatedier/frp)",
@@ -170,8 +184,9 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
cfg.DisableLogColor = disableLogColor
// Only token authentication is supported in cmd mode
cfg.AuthClientConfig = auth.GetDefaultAuthClientConf()
cfg.ClientConfig = auth.GetDefaultClientConf()
cfg.Token = token
cfg.TLSEnable = tlsEnable
return
}
@@ -197,12 +212,18 @@ func runClient(cfgFilePath string) (err error) {
return
}
func startService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (err error) {
func startService(
cfg config.ClientCommonConf,
pxyCfgs map[string]config.ProxyConf,
visitorCfgs map[string]config.VisitorConf,
cfgFile string,
) (err error) {
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
cfg.LogMaxDays, cfg.DisableLogColor)
if cfg.DnsServer != "" {
s := cfg.DnsServer
if cfg.DNSServer != "" {
s := cfg.DNSServer
if !strings.Contains(s, ":") {
s += ":53"
}

View File

@@ -95,55 +95,55 @@ func status(clientCfg config.ClientCommonConf) error {
}
fmt.Println("Proxy Status...")
if len(res.Tcp) > 0 {
if len(res.TCP) > 0 {
fmt.Printf("TCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Tcp {
for _, ps := range res.TCP {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Udp) > 0 {
if len(res.UDP) > 0 {
fmt.Printf("UDP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Udp {
for _, ps := range res.UDP {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Http) > 0 {
if len(res.HTTP) > 0 {
fmt.Printf("HTTP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Http {
for _, ps := range res.HTTP {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Https) > 0 {
if len(res.HTTPS) > 0 {
fmt.Printf("HTTPS")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Https {
for _, ps := range res.HTTPS {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Stcp) > 0 {
if len(res.STCP) > 0 {
fmt.Printf("STCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Stcp {
for _, ps := range res.STCP {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()
fmt.Println("")
}
if len(res.Xtcp) > 0 {
if len(res.XTCP) > 0 {
fmt.Printf("XTCP")
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
for _, ps := range res.Xtcp {
for _, ps := range res.XTCP {
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
}
tbl.Print()

View File

@@ -25,20 +25,13 @@ import (
)
func init() {
stcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
stcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
stcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
stcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
RegisterCommonFlags(stcpCmd)
stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
stcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
stcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
stcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
stcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
stcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
@@ -67,14 +60,14 @@ var stcpCmd = &cobra.Command{
}
if role == "server" {
cfg := &config.StcpProxyConf{}
cfg := &config.STCPProxyConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy
cfg.ProxyType = consts.STCPProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg.LocalIp = localIp
cfg.LocalIP = localIP
cfg.LocalPort = localPort
err = cfg.CheckForCli()
if err != nil {
@@ -83,9 +76,9 @@ var stcpCmd = &cobra.Command{
}
proxyConfs[cfg.ProxyName] = cfg
} else if role == "visitor" {
cfg := &config.StcpVisitorConf{}
cfg := &config.STCPVisitorConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.StcpProxy
cfg.ProxyType = consts.STCPProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role

106
cmd/frpc/sub/sudp.go Normal file
View File

@@ -0,0 +1,106 @@
// Copyright 2018 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sub
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
)
func init() {
RegisterCommonFlags(sudpCmd)
sudpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
sudpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
sudpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
sudpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
sudpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
sudpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
sudpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
sudpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
sudpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
sudpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
rootCmd.AddCommand(sudpCmd)
}
var sudpCmd = &cobra.Command{
Use: "sudp",
Short: "Run frpc with a single sudp proxy",
RunE: func(cmd *cobra.Command, args []string) error {
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs := make(map[string]config.ProxyConf)
visitorConfs := make(map[string]config.VisitorConf)
var prefix string
if user != "" {
prefix = user + "."
}
if role == "server" {
cfg := &config.SUDPProxyConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.SUDPProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg.LocalIP = localIP
cfg.LocalPort = localPort
err = cfg.CheckForCli()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
proxyConfs[cfg.ProxyName] = cfg
} else if role == "visitor" {
cfg := &config.SUDPVisitorConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.SUDPProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg.ServerName = serverName
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
err = cfg.Check()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
visitorConfs[cfg.ProxyName] = cfg
} else {
fmt.Println("invalid role")
os.Exit(1)
}
err = startService(clientCfg, proxyConfs, visitorConfs, "")
if err != nil {
os.Exit(1)
}
return nil
},
}

View File

@@ -25,17 +25,10 @@ import (
)
func init() {
tcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
tcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
tcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
tcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
tcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
RegisterCommonFlags(tcpCmd)
tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
tcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
@@ -54,14 +47,14 @@ var tcpCmd = &cobra.Command{
os.Exit(1)
}
cfg := &config.TcpProxyConf{}
cfg := &config.TCPProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.TcpProxy
cfg.LocalIp = localIp
cfg.ProxyType = consts.TCPProxy
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.RemotePort = remotePort
cfg.UseEncryption = useEncryption

View File

@@ -26,17 +26,10 @@ import (
)
func init() {
tcpMuxCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
tcpMuxCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
tcpMuxCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
tcpMuxCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
tcpMuxCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
tcpMuxCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
tcpMuxCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
tcpMuxCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
RegisterCommonFlags(tcpMuxCmd)
tcpMuxCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
tcpMuxCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
tcpMuxCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
tcpMuxCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
tcpMuxCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain")
tcpMuxCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
@@ -57,14 +50,14 @@ var tcpMuxCmd = &cobra.Command{
os.Exit(1)
}
cfg := &config.TcpMuxProxyConf{}
cfg := &config.TCPMuxProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.TcpMuxProxy
cfg.LocalIp = localIp
cfg.ProxyType = consts.TCPMuxProxy
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.CustomDomains = strings.Split(customDomains, ",")
cfg.SubDomain = subDomain

View File

@@ -25,17 +25,10 @@ import (
)
func init() {
udpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
udpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
udpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
udpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
RegisterCommonFlags(udpCmd)
udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
udpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
@@ -54,14 +47,14 @@ var udpCmd = &cobra.Command{
os.Exit(1)
}
cfg := &config.UdpProxyConf{}
cfg := &config.UDPProxyConf{}
var prefix string
if user != "" {
prefix = user + "."
}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.UdpProxy
cfg.LocalIp = localIp
cfg.ProxyType = consts.UDPProxy
cfg.LocalIP = localIP
cfg.LocalPort = localPort
cfg.RemotePort = remotePort
cfg.UseEncryption = useEncryption

View File

@@ -25,20 +25,13 @@ import (
)
func init() {
xtcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
xtcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
xtcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
xtcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
RegisterCommonFlags(xtcpCmd)
xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
xtcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
xtcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
xtcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
xtcpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip")
xtcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
@@ -67,14 +60,14 @@ var xtcpCmd = &cobra.Command{
}
if role == "server" {
cfg := &config.XtcpProxyConf{}
cfg := &config.XTCPProxyConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.XtcpProxy
cfg.ProxyType = consts.XTCPProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role
cfg.Sk = sk
cfg.LocalIp = localIp
cfg.LocalIP = localIP
cfg.LocalPort = localPort
err = cfg.CheckForCli()
if err != nil {
@@ -83,9 +76,9 @@ var xtcpCmd = &cobra.Command{
}
proxyConfs[cfg.ProxyName] = cfg
} else if role == "visitor" {
cfg := &config.XtcpVisitorConf{}
cfg := &config.XTCPVisitorConf{}
cfg.ProxyName = prefix + proxyName
cfg.ProxyType = consts.XtcpProxy
cfg.ProxyType = consts.XTCPProxy
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
cfg.Role = role

View File

@@ -39,12 +39,12 @@ var (
bindAddr string
bindPort int
bindUdpPort int
bindUDPPort int
kcpBindPort int
proxyBindAddr string
vhostHttpPort int
vhostHttpsPort int
vhostHttpTimeout int64
vhostHTTPPort int
vhostHTTPSPort int
vhostHTTPTimeout int64
dashboardAddr string
dashboardPort int
dashboardUser string
@@ -60,20 +60,21 @@ var (
allowPorts string
maxPoolCount int64
maxPortsPerClient int64
tlsOnly bool
)
func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps")
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port")
rootCmd.PersistentFlags().IntVarP(&bindUdpPort, "bind_udp_port", "", 0, "bind udp port")
rootCmd.PersistentFlags().IntVarP(&bindUDPPort, "bind_udp_port", "", 0, "bind udp port")
rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
rootCmd.PersistentFlags().IntVarP(&vhostHttpPort, "vhost_http_port", "", 0, "vhost http port")
rootCmd.PersistentFlags().IntVarP(&vhostHttpsPort, "vhost_https_port", "", 0, "vhost https port")
rootCmd.PersistentFlags().Int64VarP(&vhostHttpTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
rootCmd.PersistentFlags().IntVarP(&vhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
rootCmd.PersistentFlags().IntVarP(&vhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")
rootCmd.PersistentFlags().Int64VarP(&vhostHTTPTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address")
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
@@ -87,6 +88,7 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
rootCmd.PersistentFlags().BoolVarP(&tlsOnly, "tls_only", "", false, "frps tls only")
}
var rootCmd = &cobra.Command{
@@ -159,12 +161,12 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
cfg.BindAddr = bindAddr
cfg.BindPort = bindPort
cfg.BindUdpPort = bindUdpPort
cfg.KcpBindPort = kcpBindPort
cfg.BindUDPPort = bindUDPPort
cfg.KCPBindPort = kcpBindPort
cfg.ProxyBindAddr = proxyBindAddr
cfg.VhostHttpPort = vhostHttpPort
cfg.VhostHttpsPort = vhostHttpsPort
cfg.VhostHttpTimeout = vhostHttpTimeout
cfg.VhostHTTPPort = vhostHTTPPort
cfg.VhostHTTPSPort = vhostHTTPSPort
cfg.VhostHTTPTimeout = vhostHTTPTimeout
cfg.DashboardAddr = dashboardAddr
cfg.DashboardPort = dashboardPort
cfg.DashboardUser = dashboardUser
@@ -173,9 +175,10 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
cfg.LogLevel = logLevel
cfg.LogMaxDays = logMaxDays
cfg.SubDomainHost = subDomainHost
cfg.TLSOnly = tlsOnly
// Only token authentication is supported in cmd mode
cfg.AuthServerConfig = auth.GetDefaultAuthServerConf()
cfg.ServerConfig = auth.GetDefaultServerConf()
cfg.Token = token
if len(allowPorts) > 0 {
// e.g. 1000-2000,2001,2002,3000-4000

View File

@@ -52,6 +52,10 @@ protocol = tcp
# if tls_enable is true, frpc will connect frps by tls
tls_enable = true
# tls_cert_file = client.crt
# tls_key_file = client.key
# tls_trusted_ca_file = ca.crt
# specify a dns server, so frpc will use this instead of default one
# dns_server = 8.8.8.8
@@ -68,6 +72,11 @@ tls_enable = true
meta_var1 = 123
meta_var2 = 234
# specify udp packet size, unit is byte. If not set, the default value is 1500.
# This parameter should be same between client and server.
# It affects the udp and sudp proxy.
udp_packet_size = 1500
# '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]

View File

@@ -103,6 +103,10 @@ max_ports_per_client = 0
# TlsOnly specifies whether to only accept TLS-encrypted connections. By default, the value is false.
tls_only = false
# tls_cert_file = server.crt
# tls_key_file = server.key
# tls_trusted_ca_file = ca.crt
# if subdomain_host is not empty, you can set subdomain when type is http or https in frpc's configure file
# when subdomain is test, the host used by routing is test.frps.com
subdomain_host = frps.com
@@ -113,6 +117,11 @@ tcp_mux = true
# custom 404 page for HTTP requests
# custom_404_page = /path/to/404.html
# specify udp packet size, unit is byte. If not set, the default value is 1500.
# This parameter should be same between client and server.
# It affects the udp and sudp proxy.
udp_packet_size = 1500
[plugin.user-manager]
addr = 127.0.0.1:9000
path = /handler

View File

@@ -1,27 +1,28 @@
### Manage Plugin
### Server Plugin
frp manage plugin is aim to extend frp's ability without modifing self code.
frp server plugin is aimed to extend frp's ability without modifying the Golang code.
It runs as a process and listen on a port to provide RPC interface. Before frps doing some operations, frps will send RPC requests to manage plugin and do operations by it's response.
An external server should run in a different process receiving RPC calls from frps.
Before frps is doing some operations, it will send RPC requests to notify the external RPC server and act according to its response.
### RPC request
Support HTTP first.
RPC requests are based on JSON over HTTP.
When manage plugin accept the operation request, it can give three different responses.
When a server plugin accepts an operation request, it can respond with three different responses:
* Reject operation and return the reason.
* Reject operation and return a reason.
* Allow operation and keep original content.
* Allow operation and return modified content.
### Interface
HTTP path can be configured for each manage plugin in frps. Assume here is `/handler`.
HTTP path can be configured for each manage plugin in frps. We'll assume for this example that it's `/handler`.
Request
A request to the RPC server will look like:
```
POST /handler
POST /handler?version=0.1.0&op=Login
{
"version": "0.1.0",
"op": "Login",
@@ -30,15 +31,15 @@ POST /handler
}
}
Request Header
Request Header:
X-Frp-Reqid: for tracing
```
Response
The response can look like any of the following:
Error if not return 200 http code.
* Non-200 HTTP response status code (this will automatically tell frps that the request should fail)
Reject opeartion
* Reject operation:
```
{
@@ -47,7 +48,7 @@ Reject opeartion
}
```
Allow operation and keep original content
* Allow operation and keep original content:
```
{
@@ -56,7 +57,7 @@ Allow operation and keep original content
}
```
Allow opeartion and modify content
* Allow operation and modify content
```
{
@@ -69,7 +70,7 @@ Allow opeartion and modify content
### Operation
Now it supports `Login` and `NewProxy`.
Currently `Login`, `NewProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
#### Login
@@ -102,6 +103,7 @@ Create new proxy
"user": {
"user": <string>,
"metas": map<string>string
"run_id": <string>
},
"proxy_name": <string>,
"proxy_type": <string>,
@@ -122,14 +124,77 @@ Create new proxy
"host_header_rewrite": <string>,
"headers": map<string>string,
// stcp only
"sk": <string>,
// tcpmux only
"multiplexer": <string>
"metas": map<string>string
}
}
```
### manage plugin configure
#### Ping
Heartbeat from frpc
```
{
"content": {
"user": {
"user": <string>,
"metas": map<string>string
"run_id": <string>
},
"timestamp": <int64>,
"privilege_key": <string>
}
}
```
#### NewWorkConn
New work connection received from frpc (RPC sent after `run_id` is matched with an existing frp connection)
```
{
"content": {
"user": {
"user": <string>,
"metas": map<string>string
"run_id": <string>
},
"run_id": <string>
"timestamp": <int64>,
"privilege_key": <string>
}
}
```
#### NewUserConn
New user connection received from proxy (support `tcp`, `stcp`, `https` and `tcpmux`) .
```
{
"content": {
"user": {
"user": <string>,
"metas": map<string>string
"run_id": <string>
},
"proxy_name": <string>,
"proxy_type": <string>,
"remote_addr": <string>
}
}
```
### Server Plugin Configuration
```ini
# frps.ini
[common]
bind_port = 7000
@@ -144,15 +209,19 @@ path = /handler
ops = NewProxy
```
addr: plugin listen on.
path: http request url path.
ops: opeartions plugin needs handle.
addr: the address where the external RPC service listens on.
path: http request url path for the POST request.
ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...).
### meta data
### Metadata
Meta data will be sent to manage plugin in each RCP request.
Metadata will be sent to the server plugin in each RPC request.
Meta data start with `meta_`. It can be configured in `common` and each proxy.
There are 2 types of metadata entries - 1 under `[common]` and the other under each proxy configuration.
Metadata entries under `[common]` will be sent in `Login` under the key `metas`, and in any other RPC request under `user.metas`.
Metadata entries under each proxy configuration will be sent in `NewProxy` op only, under `metas`.
Metadata entries start with `meta_`. This is an example of metadata entries in `[common]` and under the proxy named `[ssh]`:
```
# frpc.ini

View File

@@ -69,7 +69,7 @@ Response
### 操作类型
目前插件支持管理的操作类型有 `Login``NewProxy`
目前插件支持管理的操作类型有 `Login``NewProxy``Ping``NewWorkConn``NewUserConn`
#### Login
@@ -127,6 +127,63 @@ Response
}
```
#### 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

9
go.mod
View File

@@ -9,6 +9,7 @@ require (
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.0
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
@@ -16,22 +17,24 @@ require (
github.com/klauspost/cpuid v1.2.0 // indirect
github.com/klauspost/reedsolomon v1.9.1 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/onsi/ginkgo v1.12.3
github.com/onsi/gomega v1.10.1
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/prometheus/client_golang v1.4.1
github.com/rakyll/statik v0.1.1
github.com/rodaine/table v1.0.0
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.1 // indirect
github.com/stretchr/testify v1.4.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/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/text v0.3.2 // indirect
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
k8s.io/apimachinery v0.18.3
)

124
go.sum
View File

@@ -1,8 +1,12 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -15,44 +19,86 @@ github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHo
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
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/evanphx/json-patch v4.2.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.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -60,12 +106,28 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.12.3 h1:+RYp9QczoWz9zfUyLP/5SLXQVhfr6gZOoKGfQqHuLZQ=
github.com/onsi/ginkgo v1.12.3/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
@@ -84,58 +146,114 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
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.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
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=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk=
k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

15
hack/run-e2e.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
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
fi
debug=false
if [ x${DEBUG} == x"true" ]; then
debug=true
fi
ginkgo -nodes=4 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=debug -debug=${debug}

View File

@@ -72,42 +72,42 @@ func unmarshalBaseConfFromIni(conf ini.File) baseConfig {
return cfg
}
type AuthClientConfig struct {
type ClientConfig struct {
baseConfig
oidcClientConfig
tokenConfig
}
func GetDefaultAuthClientConf() AuthClientConfig {
return AuthClientConfig{
func GetDefaultClientConf() ClientConfig {
return ClientConfig{
baseConfig: getDefaultBaseConf(),
oidcClientConfig: getDefaultOidcClientConf(),
tokenConfig: getDefaultTokenConf(),
}
}
func UnmarshalAuthClientConfFromIni(conf ini.File) (cfg AuthClientConfig) {
func UnmarshalClientConfFromIni(conf ini.File) (cfg ClientConfig) {
cfg.baseConfig = unmarshalBaseConfFromIni(conf)
cfg.oidcClientConfig = unmarshalOidcClientConfFromIni(conf)
cfg.tokenConfig = unmarshalTokenConfFromIni(conf)
return cfg
}
type AuthServerConfig struct {
type ServerConfig struct {
baseConfig
oidcServerConfig
tokenConfig
}
func GetDefaultAuthServerConf() AuthServerConfig {
return AuthServerConfig{
func GetDefaultServerConf() ServerConfig {
return ServerConfig{
baseConfig: getDefaultBaseConf(),
oidcServerConfig: getDefaultOidcServerConf(),
tokenConfig: getDefaultTokenConf(),
}
}
func UnmarshalAuthServerConfFromIni(conf ini.File) (cfg AuthServerConfig) {
func UnmarshalServerConfFromIni(conf ini.File) (cfg ServerConfig) {
cfg.baseConfig = unmarshalBaseConfFromIni(conf)
cfg.oidcServerConfig = unmarshalOidcServerConfFromIni(conf)
cfg.tokenConfig = unmarshalTokenConfFromIni(conf)
@@ -120,7 +120,7 @@ type Setter interface {
SetNewWorkConn(*msg.NewWorkConn) error
}
func NewAuthSetter(cfg AuthClientConfig) (authProvider Setter) {
func NewAuthSetter(cfg ClientConfig) (authProvider Setter) {
switch cfg.AuthenticationMethod {
case consts.TokenAuthMethod:
authProvider = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig)
@@ -139,7 +139,7 @@ type Verifier interface {
VerifyNewWorkConn(*msg.NewWorkConn) error
}
func NewAuthVerifier(cfg AuthServerConfig) (authVerifier Verifier) {
func NewAuthVerifier(cfg ServerConfig) (authVerifier Verifier) {
switch cfg.AuthenticationMethod {
case consts.TokenAuthMethod:
authVerifier = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig)

View File

@@ -26,10 +26,10 @@ import (
)
type oidcClientConfig struct {
// OidcClientId specifies the client ID to use to get a token in OIDC
// OidcClientID specifies the client ID to use to get a token in OIDC
// authentication if AuthenticationMethod == "oidc". By default, this value
// is "".
OidcClientId string `json:"oidc_client_id"`
OidcClientID string `json:"oidc_client_id"`
// OidcClientSecret specifies the client secret to use to get a token in OIDC
// authentication if AuthenticationMethod == "oidc". By default, this value
// is "".
@@ -37,18 +37,18 @@ type oidcClientConfig struct {
// OidcAudience specifies the audience of the token in OIDC authentication
//if AuthenticationMethod == "oidc". By default, this value is "".
OidcAudience string `json:"oidc_audience"`
// OidcTokenEndpointUrl specifies the URL which implements OIDC Token Endpoint.
// OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint.
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
// By default, this value is "".
OidcTokenEndpointUrl string `json:"oidc_token_endpoint_url"`
OidcTokenEndpointURL string `json:"oidc_token_endpoint_url"`
}
func getDefaultOidcClientConf() oidcClientConfig {
return oidcClientConfig{
OidcClientId: "",
OidcClientID: "",
OidcClientSecret: "",
OidcAudience: "",
OidcTokenEndpointUrl: "",
OidcTokenEndpointURL: "",
}
}
@@ -61,7 +61,7 @@ func unmarshalOidcClientConfFromIni(conf ini.File) oidcClientConfig {
cfg := getDefaultOidcClientConf()
if tmpStr, ok = conf.Get("common", "oidc_client_id"); ok {
cfg.OidcClientId = tmpStr
cfg.OidcClientID = tmpStr
}
if tmpStr, ok = conf.Get("common", "oidc_client_secret"); ok {
@@ -73,7 +73,7 @@ func unmarshalOidcClientConfFromIni(conf ini.File) oidcClientConfig {
}
if tmpStr, ok = conf.Get("common", "oidc_token_endpoint_url"); ok {
cfg.OidcTokenEndpointUrl = tmpStr
cfg.OidcTokenEndpointURL = tmpStr
}
return cfg
@@ -148,10 +148,10 @@ type OidcAuthProvider struct {
func NewOidcAuthSetter(baseCfg baseConfig, cfg oidcClientConfig) *OidcAuthProvider {
tokenGenerator := &clientcredentials.Config{
ClientID: cfg.OidcClientId,
ClientID: cfg.OidcClientID,
ClientSecret: cfg.OidcClientSecret,
Scopes: []string{cfg.OidcAudience},
TokenURL: cfg.OidcTokenEndpointUrl,
TokenURL: cfg.OidcTokenEndpointURL,
}
return &OidcAuthProvider{

View File

@@ -81,7 +81,7 @@ func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
}
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
if !auth.AuthenticateHeartBeats {
if !auth.AuthenticateNewWorkConns {
return nil
}

View File

@@ -29,17 +29,17 @@ import (
// recommended to use GetDefaultClientConf instead of creating this object
// directly, so that all unspecified fields have reasonable default values.
type ClientCommonConf struct {
auth.AuthClientConfig
auth.ClientConfig
// ServerAddr specifies the address of the server to connect to. By
// default, this value is "0.0.0.0".
ServerAddr string `json:"server_addr"`
// ServerPort specifies the port to connect to the server on. By default,
// this value is 7000.
ServerPort int `json:"server_port"`
// HttpProxy specifies a proxy address to connect to the server through. If
// 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.
HttpProxy string `json:"http_proxy"`
HTTPProxy string `json:"http_proxy"`
// LogFile specifies a file where logs will be written to. This value will
// only be used if LogWay is set appropriately. By default, this value is
// "console".
@@ -79,18 +79,18 @@ type ClientCommonConf struct {
// PoolCount specifies the number of connections the client will make to
// the server in advance. By default, this value is 0.
PoolCount int `json:"pool_count"`
// TcpMux toggles TCP stream multiplexing. This allows multiple requests
// TCPMux toggles TCP stream multiplexing. This allows multiple requests
// from a client to share a single TCP connection. If this value is true,
// the server must have TCP multiplexing enabled as well. By default, this
// value is true.
TcpMux bool `json:"tcp_mux"`
TCPMux bool `json:"tcp_mux"`
// 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 "".
User string `json:"user"`
// DnsServer specifies a DNS server address for FRPC to use. If this value
// DNSServer specifies a DNS server address for FRPC to use. If this value
// is "", the default DNS will be used. By default, this value is "".
DnsServer string `json:"dns_server"`
DNSServer string `json:"dns_server"`
// LoginFailExit controls whether or not the client should exit after a
// failed login attempt. If false, the client will retry until a login
// attempt succeeds. By default, this value is true.
@@ -104,8 +104,20 @@ type ClientCommonConf struct {
// is "tcp".
Protocol string `json:"protocol"`
// TLSEnable specifies whether or not TLS should be used when communicating
// with the server.
// with the server. If "tls_cert_file" and "tls_key_file" are valid,
// client will load the supplied tls configuration.
TLSEnable bool `json:"tls_enable"`
// ClientTLSCertPath specifies the path of the cert file that client will
// load. It only works when "tls_enable" is true and "tls_key_file" is valid.
TLSCertFile string `json:"tls_cert_file"`
// ClientTLSKeyPath specifies the path of the secret key file that client
// will load. It only works when "tls_enable" is true and "tls_cert_file"
// are valid.
TLSKeyFile string `json:"tls_key_file"`
// TrustedCaFile specifies the path of the trusted ca file that will load.
// It only works when "tls_enable" is valid and tls configuration of server
// has been specified.
TLSTrustedCaFile string `json:"tls_trusted_ca_file"`
// 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.
@@ -116,6 +128,9 @@ type ClientCommonConf struct {
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
// Client meta info
Metas map[string]string `json:"metas"`
// UDPPacketSize specifies the udp packet size
// By default, this value is 1500
UDPPacketSize int64 `json:"udp_packet_size"`
}
// GetDefaultClientConf returns a client configuration with default values.
@@ -123,7 +138,7 @@ func GetDefaultClientConf() ClientCommonConf {
return ClientCommonConf{
ServerAddr: "0.0.0.0",
ServerPort: 7000,
HttpProxy: os.Getenv("http_proxy"),
HTTPProxy: os.Getenv("http_proxy"),
LogFile: "console",
LogWay: "console",
LogLevel: "info",
@@ -135,16 +150,20 @@ func GetDefaultClientConf() ClientCommonConf {
AdminPwd: "",
AssetsDir: "",
PoolCount: 1,
TcpMux: true,
TCPMux: true,
User: "",
DnsServer: "",
DNSServer: "",
LoginFailExit: true,
Start: make(map[string]struct{}),
Protocol: "tcp",
TLSEnable: false,
TLSCertFile: "",
TLSKeyFile: "",
TLSTrustedCaFile: "",
HeartBeatInterval: 30,
HeartBeatTimeout: 90,
Metas: make(map[string]string),
UDPPacketSize: 1500,
}
}
@@ -156,7 +175,7 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error
return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err)
}
cfg.AuthClientConfig = auth.UnmarshalAuthClientConfFromIni(conf)
cfg.ClientConfig = auth.UnmarshalClientConfFromIni(conf)
var (
tmpStr string
@@ -181,7 +200,7 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error
}
if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
cfg.HttpProxy = tmpStr
cfg.HTTPProxy = tmpStr
}
if tmpStr, ok = conf.Get("common", "log_file"); ok {
@@ -235,9 +254,9 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error
}
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
cfg.TcpMux = false
cfg.TCPMux = false
} else {
cfg.TcpMux = true
cfg.TCPMux = true
}
if tmpStr, ok = conf.Get("common", "user"); ok {
@@ -245,7 +264,7 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error
}
if tmpStr, ok = conf.Get("common", "dns_server"); ok {
cfg.DnsServer = tmpStr
cfg.DNSServer = tmpStr
}
if tmpStr, ok = conf.Get("common", "start"); ok {
@@ -276,28 +295,45 @@ func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error
cfg.TLSEnable = false
}
if tmpStr, ok = conf.Get("common", "tls_cert_file"); ok {
cfg.TLSCertFile = tmpStr
}
if tmpStr, ok := conf.Get("common", "tls_key_file"); ok {
cfg.TLSKeyFile = tmpStr
}
if tmpStr, ok := conf.Get("common", "tls_trusted_ca_file"); ok {
cfg.TLSTrustedCaFile = tmpStr
}
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
return
} else {
cfg.HeartBeatTimeout = v
}
cfg.HeartBeatTimeout = v
}
if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
return
} else {
cfg.HeartBeatInterval = v
}
cfg.HeartBeatInterval = v
}
for k, v := range conf.Section("common") {
if strings.HasPrefix(k, "meta_") {
cfg.Metas[strings.TrimPrefix(k, "meta_")] = v
}
}
if tmpStr, ok = conf.Get("common", "udp_packet_size"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid udp_packet_size")
return
}
cfg.UDPPacketSize = v
}
return
}
@@ -311,5 +347,19 @@ func (cfg *ClientCommonConf) Check() (err error) {
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
return
}
if cfg.TLSEnable == false {
if cfg.TLSCertFile != "" {
fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
}
if cfg.TLSKeyFile != "" {
fmt.Println("WARNING! tls_key_file is invalid when tls_enable is false")
}
if cfg.TLSTrustedCaFile != "" {
fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
}
}
return
}

View File

@@ -33,13 +33,14 @@ var (
func init() {
proxyConfTypeMap = make(map[string]reflect.Type)
proxyConfTypeMap[consts.TcpProxy] = reflect.TypeOf(TcpProxyConf{})
proxyConfTypeMap[consts.TcpMuxProxy] = reflect.TypeOf(TcpMuxProxyConf{})
proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
proxyConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpProxyConf{})
proxyConfTypeMap[consts.TCPProxy] = reflect.TypeOf(TCPProxyConf{})
proxyConfTypeMap[consts.TCPMuxProxy] = reflect.TypeOf(TCPMuxProxyConf{})
proxyConfTypeMap[consts.UDPProxy] = reflect.TypeOf(UDPProxyConf{})
proxyConfTypeMap[consts.HTTPProxy] = reflect.TypeOf(HTTPProxyConf{})
proxyConfTypeMap[consts.HTTPSProxy] = reflect.TypeOf(HTTPSProxyConf{})
proxyConfTypeMap[consts.STCPProxy] = reflect.TypeOf(STCPProxyConf{})
proxyConfTypeMap[consts.XTCPProxy] = reflect.TypeOf(XTCPProxyConf{})
proxyConfTypeMap[consts.SUDPProxy] = reflect.TypeOf(SUDPProxyConf{})
}
// NewConfByType creates a empty ProxyConf object by proxyType.
@@ -65,7 +66,7 @@ type ProxyConf interface {
func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (cfg ProxyConf, err error) {
if pMsg.ProxyType == "" {
pMsg.ProxyType = consts.TcpProxy
pMsg.ProxyType = consts.TCPProxy
}
cfg = NewConfByType(pMsg.ProxyType)
@@ -81,8 +82,8 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (cfg Pr
func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg ProxyConf, err error) {
proxyType := section["type"]
if proxyType == "" {
proxyType = consts.TcpProxy
section["type"] = consts.TcpProxy
proxyType = consts.TCPProxy
section["type"] = consts.TCPProxy
}
cfg = NewConfByType(proxyType)
if cfg == nil {
@@ -209,14 +210,14 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i
}
if cfg.HealthCheckType == "tcp" && cfg.Plugin == "" {
cfg.HealthCheckAddr = cfg.LocalIp + fmt.Sprintf(":%d", cfg.LocalPort)
cfg.HealthCheckAddr = cfg.LocalIP + fmt.Sprintf(":%d", cfg.LocalPort)
}
if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckUrl != "" {
s := fmt.Sprintf("http://%s:%d", cfg.LocalIp, cfg.LocalPort)
if !strings.HasPrefix(cfg.HealthCheckUrl, "/") {
if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckURL != "" {
s := fmt.Sprintf("http://%s:%d", cfg.LocalIP, cfg.LocalPort)
if !strings.HasPrefix(cfg.HealthCheckURL, "/") {
s += "/"
}
cfg.HealthCheckUrl = s + cfg.HealthCheckUrl
cfg.HealthCheckURL = s + cfg.HealthCheckURL
}
cfg.Metas = make(map[string]string)
@@ -279,9 +280,8 @@ func (cfg *BindInfoConf) UnmarshalFromIni(prefix string, name string, section in
if tmpStr, ok = section["remote_port"]; ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name)
} else {
cfg.RemotePort = int(v)
}
cfg.RemotePort = int(v)
} else {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name)
}
@@ -376,8 +376,8 @@ func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) {
// LocalSvrConf configures what location the client will proxy to, or what
// plugin will be used.
type LocalSvrConf struct {
// LocalIp specifies the IP address or host name to proxy to.
LocalIp string `json:"local_ip"`
// LocalIP specifies the IP address or host name to proxy to.
LocalIP string `json:"local_ip"`
// LocalPort specifies the port to proxy to.
LocalPort int `json:"local_port"`
@@ -391,7 +391,7 @@ type LocalSvrConf struct {
}
func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool {
if cfg.LocalIp != cmp.LocalIp ||
if cfg.LocalIP != cmp.LocalIP ||
cfg.LocalPort != cmp.LocalPort {
return false
}
@@ -419,8 +419,8 @@ func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section in
}
}
} else {
if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" {
cfg.LocalIp = "127.0.0.1"
if cfg.LocalIP = section["local_ip"]; cfg.LocalIP == "" {
cfg.LocalIP = "127.0.0.1"
}
if tmpStr, ok := section["local_port"]; ok {
@@ -436,7 +436,7 @@ func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section in
func (cfg *LocalSvrConf) checkForCli() (err error) {
if cfg.Plugin == "" {
if cfg.LocalIp == "" {
if cfg.LocalIP == "" {
err = fmt.Errorf("local ip or plugin is required")
return
}
@@ -459,7 +459,7 @@ type HealthCheckConf struct {
// server. If a connection cannot be established, the health check fails.
//
// If the type is "http", a GET request will be made to the endpoint
// specified by HealthCheckUrl. If the response is not a 200, the health
// specified by HealthCheckURL. If the response is not a 200, the health
// check fails.
HealthCheckType string `json:"health_check_type"` // tcp | http
// HealthCheckTimeoutS specifies the number of seconds to wait for a health
@@ -472,9 +472,9 @@ type HealthCheckConf struct {
// HealthCheckIntervalS specifies the time in seconds between health
// checks. By default, this value is 10.
HealthCheckIntervalS int `json:"health_check_interval_s"`
// HealthCheckUrl specifies the address to send health checks to if the
// HealthCheckURL specifies the address to send health checks to if the
// health check type is "http".
HealthCheckUrl string `json:"health_check_url"`
HealthCheckURL string `json:"health_check_url"`
// HealthCheckAddr specifies the address to connect to if the health check
// type is "tcp".
HealthCheckAddr string `json:"-"`
@@ -485,7 +485,7 @@ func (cfg *HealthCheckConf) compare(cmp *HealthCheckConf) bool {
cfg.HealthCheckTimeoutS != cmp.HealthCheckTimeoutS ||
cfg.HealthCheckMaxFailed != cmp.HealthCheckMaxFailed ||
cfg.HealthCheckIntervalS != cmp.HealthCheckIntervalS ||
cfg.HealthCheckUrl != cmp.HealthCheckUrl {
cfg.HealthCheckURL != cmp.HealthCheckURL {
return false
}
return true
@@ -493,7 +493,7 @@ func (cfg *HealthCheckConf) compare(cmp *HealthCheckConf) bool {
func (cfg *HealthCheckConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
cfg.HealthCheckType = section["health_check_type"]
cfg.HealthCheckUrl = section["health_check_url"]
cfg.HealthCheckURL = section["health_check_url"]
if tmpStr, ok := section["health_check_timeout_s"]; ok {
if cfg.HealthCheckTimeoutS, err = strconv.Atoi(tmpStr); err != nil {
@@ -520,7 +520,7 @@ func (cfg *HealthCheckConf) checkForCli() error {
return fmt.Errorf("unsupport health check type")
}
if cfg.HealthCheckType != "" {
if cfg.HealthCheckType == "http" && cfg.HealthCheckUrl == "" {
if cfg.HealthCheckType == "http" && cfg.HealthCheckURL == "" {
return fmt.Errorf("health_check_url is required for health check type 'http'")
}
}
@@ -528,13 +528,13 @@ func (cfg *HealthCheckConf) checkForCli() error {
}
// TCP
type TcpProxyConf struct {
type TCPProxyConf struct {
BaseProxyConf
BindInfoConf
}
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*TcpProxyConf)
func (cfg *TCPProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*TCPProxyConf)
if !ok {
return false
}
@@ -546,12 +546,12 @@ func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
return true
}
func (cfg *TcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
func (cfg *TCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
}
func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
@@ -561,30 +561,30 @@ func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section in
return
}
func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
func (cfg *TCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.BindInfoConf.MarshalToMsg(pMsg)
}
func (cfg *TcpProxyConf) CheckForCli() (err error) {
func (cfg *TCPProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return err
}
return
}
func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
func (cfg *TCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
// TCP Multiplexer
type TcpMuxProxyConf struct {
type TCPMuxProxyConf struct {
BaseProxyConf
DomainConf
Multiplexer string `json:"multiplexer"`
}
func (cfg *TcpMuxProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*TcpMuxProxyConf)
func (cfg *TCPMuxProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*TCPMuxProxyConf)
if !ok {
return false
}
@@ -597,13 +597,13 @@ func (cfg *TcpMuxProxyConf) Compare(cmp ProxyConf) bool {
return true
}
func (cfg *TcpMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.DomainConf.UnmarshalFromMsg(pMsg)
cfg.Multiplexer = pMsg.Multiplexer
}
func (cfg *TcpMuxProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
@@ -612,37 +612,37 @@ func (cfg *TcpMuxProxyConf) UnmarshalFromIni(prefix string, name string, section
}
cfg.Multiplexer = section["multiplexer"]
if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer {
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
return fmt.Errorf("parse conf error: proxy [%s] incorrect multiplexer [%s]", name, cfg.Multiplexer)
}
return
}
func (cfg *TcpMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.DomainConf.MarshalToMsg(pMsg)
pMsg.Multiplexer = cfg.Multiplexer
}
func (cfg *TcpMuxProxyConf) CheckForCli() (err error) {
func (cfg *TCPMuxProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return err
}
if err = cfg.DomainConf.checkForCli(); err != nil {
return err
}
if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer {
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
return fmt.Errorf("parse conf error: incorrect multiplexer [%s]", cfg.Multiplexer)
}
return
}
func (cfg *TcpMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
if cfg.Multiplexer != consts.HttpConnectTcpMultiplexer {
func (cfg *TCPMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
return fmt.Errorf("proxy [%s] incorrect multiplexer [%s]", cfg.ProxyName, cfg.Multiplexer)
}
if cfg.Multiplexer == consts.HttpConnectTcpMultiplexer && serverCfg.TcpMuxHttpConnectPort == 0 {
if cfg.Multiplexer == consts.HTTPConnectTCPMultiplexer && serverCfg.TCPMuxHTTPConnectPort == 0 {
return fmt.Errorf("proxy [%s] type [tcpmux] with multiplexer [httpconnect] requires tcpmux_httpconnect_port configuration", cfg.ProxyName)
}
@@ -654,13 +654,13 @@ func (cfg *TcpMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error)
}
// UDP
type UdpProxyConf struct {
type UDPProxyConf struct {
BaseProxyConf
BindInfoConf
}
func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*UdpProxyConf)
func (cfg *UDPProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*UDPProxyConf)
if !ok {
return false
}
@@ -672,12 +672,12 @@ func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
return true
}
func (cfg *UdpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
func (cfg *UDPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.BindInfoConf.UnmarshalFromMsg(pMsg)
}
func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
@@ -687,34 +687,34 @@ func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section in
return
}
func (cfg *UdpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
func (cfg *UDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.BindInfoConf.MarshalToMsg(pMsg)
}
func (cfg *UdpProxyConf) CheckForCli() (err error) {
func (cfg *UDPProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return
}
return
}
func (cfg *UdpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
func (cfg *UDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
// HTTP
type HttpProxyConf struct {
type HTTPProxyConf struct {
BaseProxyConf
DomainConf
Locations []string `json:"locations"`
HttpUser string `json:"http_user"`
HttpPwd string `json:"http_pwd"`
HTTPUser string `json:"http_user"`
HTTPPwd string `json:"http_pwd"`
HostHeaderRewrite string `json:"host_header_rewrite"`
Headers map[string]string `json:"headers"`
}
func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*HttpProxyConf)
func (cfg *HTTPProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*HTTPProxyConf)
if !ok {
return false
}
@@ -723,36 +723,36 @@ func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") ||
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
cfg.HttpUser != cmpConf.HttpUser ||
cfg.HttpPwd != cmpConf.HttpPwd ||
cfg.HTTPUser != cmpConf.HTTPUser ||
cfg.HTTPPwd != cmpConf.HTTPPwd ||
len(cfg.Headers) != len(cmpConf.Headers) {
return false
}
for k, v := range cfg.Headers {
if v2, ok := cmpConf.Headers[k]; !ok {
v2, ok := cmpConf.Headers[k]
if !ok {
return false
}
if v != v2 {
return false
} else {
if v != v2 {
return false
}
}
}
return true
}
func (cfg *HttpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
func (cfg *HTTPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.DomainConf.UnmarshalFromMsg(pMsg)
cfg.Locations = pMsg.Locations
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
cfg.HttpUser = pMsg.HttpUser
cfg.HttpPwd = pMsg.HttpPwd
cfg.HTTPUser = pMsg.HTTPUser
cfg.HTTPPwd = pMsg.HTTPPwd
cfg.Headers = pMsg.Headers
}
func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
@@ -771,8 +771,8 @@ func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section i
}
cfg.HostHeaderRewrite = section["host_header_rewrite"]
cfg.HttpUser = section["http_user"]
cfg.HttpPwd = section["http_pwd"]
cfg.HTTPUser = section["http_user"]
cfg.HTTPPwd = section["http_pwd"]
cfg.Headers = make(map[string]string)
for k, v := range section {
@@ -783,18 +783,18 @@ func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section i
return
}
func (cfg *HttpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
func (cfg *HTTPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.DomainConf.MarshalToMsg(pMsg)
pMsg.Locations = cfg.Locations
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
pMsg.HttpUser = cfg.HttpUser
pMsg.HttpPwd = cfg.HttpPwd
pMsg.HTTPUser = cfg.HTTPUser
pMsg.HTTPPwd = cfg.HTTPPwd
pMsg.Headers = cfg.Headers
}
func (cfg *HttpProxyConf) CheckForCli() (err error) {
func (cfg *HTTPProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return
}
@@ -804,8 +804,8 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) {
return
}
func (cfg *HttpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
if serverCfg.VhostHttpPort == 0 {
func (cfg *HTTPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
if serverCfg.VhostHTTPPort == 0 {
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
}
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
@@ -816,13 +816,13 @@ func (cfg *HttpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
}
// HTTPS
type HttpsProxyConf struct {
type HTTPSProxyConf struct {
BaseProxyConf
DomainConf
}
func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*HttpsProxyConf)
func (cfg *HTTPSProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*HTTPSProxyConf)
if !ok {
return false
}
@@ -834,12 +834,12 @@ func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
return true
}
func (cfg *HttpsProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
func (cfg *HTTPSProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.DomainConf.UnmarshalFromMsg(pMsg)
}
func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
@@ -849,12 +849,12 @@ func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section
return
}
func (cfg *HttpsProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
func (cfg *HTTPSProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
cfg.DomainConf.MarshalToMsg(pMsg)
}
func (cfg *HttpsProxyConf) CheckForCli() (err error) {
func (cfg *HTTPSProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return
}
@@ -864,8 +864,8 @@ func (cfg *HttpsProxyConf) CheckForCli() (err error) {
return
}
func (cfg *HttpsProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
if serverCfg.VhostHttpsPort == 0 {
func (cfg *HTTPSProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
if serverCfg.VhostHTTPSPort == 0 {
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
}
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
@@ -875,16 +875,16 @@ func (cfg *HttpsProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
return
}
// STCP
type StcpProxyConf struct {
// SUDP
type SUDPProxyConf struct {
BaseProxyConf
Role string `json:"role"`
Sk string `json:"sk"`
}
func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*StcpProxyConf)
func (cfg *SUDPProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*SUDPProxyConf)
if !ok {
return false
}
@@ -897,13 +897,7 @@ func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
return true
}
// Only for role server.
func (cfg *StcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.Sk = pMsg.Sk
}
func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
@@ -921,12 +915,12 @@ func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section i
return
}
func (cfg *StcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
func (cfg *SUDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
pMsg.Sk = cfg.Sk
}
func (cfg *StcpProxyConf) CheckForCli() (err error) {
func (cfg *SUDPProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return
}
@@ -937,20 +931,92 @@ func (cfg *StcpProxyConf) CheckForCli() (err error) {
return
}
func (cfg *StcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
func (cfg *SUDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
return
}
// XTCP
type XtcpProxyConf struct {
// Only for role server.
func (cfg *SUDPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.Sk = pMsg.Sk
}
// STCP
type STCPProxyConf struct {
BaseProxyConf
Role string `json:"role"`
Sk string `json:"sk"`
}
func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*XtcpProxyConf)
func (cfg *STCPProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*STCPProxyConf)
if !ok {
return false
}
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
cfg.Role != cmpConf.Role ||
cfg.Sk != cmpConf.Sk {
return false
}
return true
}
// Only for role server.
func (cfg *STCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.Sk = pMsg.Sk
}
func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
cfg.Role = section["role"]
if cfg.Role != "server" {
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
}
cfg.Sk = section["sk"]
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return
}
func (cfg *STCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
pMsg.Sk = cfg.Sk
}
func (cfg *STCPProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return
}
if cfg.Role != "server" {
err = fmt.Errorf("role should be 'server'")
return
}
return
}
func (cfg *STCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
return
}
// XTCP
type XTCPProxyConf struct {
BaseProxyConf
Role string `json:"role"`
Sk string `json:"sk"`
}
func (cfg *XTCPProxyConf) Compare(cmp ProxyConf) bool {
cmpConf, ok := cmp.(*XTCPProxyConf)
if !ok {
return false
}
@@ -965,12 +1031,12 @@ func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
}
// Only for role server.
func (cfg *XtcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
func (cfg *XTCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
cfg.Sk = pMsg.Sk
}
func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
@@ -988,12 +1054,12 @@ func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section i
return
}
func (cfg *XtcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
func (cfg *XTCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.MarshalToMsg(pMsg)
pMsg.Sk = cfg.Sk
}
func (cfg *XtcpProxyConf) CheckForCli() (err error) {
func (cfg *XTCPProxyConf) CheckForCli() (err error) {
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
return
}
@@ -1004,7 +1070,7 @@ func (cfg *XtcpProxyConf) CheckForCli() (err error) {
return
}
func (cfg *XtcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
func (cfg *XTCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
return
}

View File

@@ -30,40 +30,40 @@ import (
// recommended to use GetDefaultServerConf instead of creating this object
// directly, so that all unspecified fields have reasonable default values.
type ServerCommonConf struct {
auth.AuthServerConfig
auth.ServerConfig
// BindAddr specifies the address that the server binds to. By default,
// this value is "0.0.0.0".
BindAddr string `json:"bind_addr"`
// BindPort specifies the port that the server listens on. By default, this
// value is 7000.
BindPort int `json:"bind_port"`
// BindUdpPort specifies the UDP port that the server listens on. If this
// BindUDPPort specifies the UDP port that the server listens on. If this
// value is 0, the server will not listen for UDP connections. By default,
// this value is 0
BindUdpPort int `json:"bind_udp_port"`
// BindKcpPort specifies the KCP port that the server listens on. If this
BindUDPPort int `json:"bind_udp_port"`
// KCPBindPort specifies the KCP port that the server listens on. If this
// value is 0, the server will not listen for KCP connections. By default,
// this value is 0.
KcpBindPort int `json:"kcp_bind_port"`
KCPBindPort int `json:"kcp_bind_port"`
// ProxyBindAddr specifies the address that the proxy binds to. This value
// may be the same as BindAddr. By default, this value is "0.0.0.0".
ProxyBindAddr string `json:"proxy_bind_addr"`
// VhostHttpPort specifies the port that the server listens for HTTP Vhost
// VhostHTTPPort specifies the port that the server listens for HTTP Vhost
// requests. If this value is 0, the server will not listen for HTTP
// requests. By default, this value is 0.
VhostHttpPort int `json:"vhost_http_port"`
// VhostHttpsPort specifies the port that the server listens for HTTPS
VhostHTTPPort int `json:"vhost_http_port"`
// VhostHTTPSPort specifies the port that the server listens for HTTPS
// Vhost requests. If this value is 0, the server will not listen for HTTPS
// requests. By default, this value is 0.
VhostHttpsPort int `json:"vhost_https_port"`
// TcpMuxHttpConnectPort specifies the port that the server listens for TCP
VhostHTTPSPort int `json:"vhost_https_port"`
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
// requests on one single port. If it's not - it will listen on this value for
// HTTP CONNECT requests. By default, this value is 0.
TcpMuxHttpConnectPort int `json:"tcpmux_httpconnect_port"`
// VhostHttpTimeout specifies the response header timeout for the Vhost
TCPMuxHTTPConnectPort int `json:"tcpmux_httpconnect_port"`
// VhostHTTPTimeout specifies the response header timeout for the Vhost
// HTTP server, in seconds. By default, this value is 60.
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
VhostHTTPTimeout int64 `json:"vhost_http_timeout"`
// DashboardAddr specifies the address that the dashboard binds to. By
// default, this value is "0.0.0.0".
DashboardAddr string `json:"dashboard_addr"`
@@ -113,10 +113,10 @@ type ServerCommonConf struct {
// "test", the resulting URL would be "test.frps.com". By default, this
// value is "".
SubDomainHost string `json:"subdomain_host"`
// TcpMux toggles TCP stream multiplexing. This allows multiple requests
// TCPMux toggles TCP stream multiplexing. This allows multiple requests
// from a client to share a single TCP connection. By default, this value
// is true.
TcpMux bool `json:"tcp_mux"`
TCPMux bool `json:"tcp_mux"`
// 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
// "".
@@ -133,9 +133,24 @@ type ServerCommonConf struct {
// may proxy to. If this value is 0, no limit will be applied. By default,
// this value is 0.
MaxPortsPerClient int64 `json:"max_ports_per_client"`
// TlsOnly specifies whether to only accept TLS-encrypted connections. By
// default, the value is false.
TlsOnly bool `json:"tls_only"`
// TLSOnly specifies whether to only accept TLS-encrypted connections.
// By default, the value is false.
TLSOnly bool `json:"tls_only"`
// TLSCertFile specifies the path of the cert file that the server will
// load. If "tls_cert_file", "tls_key_file" are valid, the server will use this
// supplied tls configuration. Otherwise, the server will use the tls
// configuration generated by itself.
TLSCertFile string `json:"tls_cert_file"`
// TLSKeyFile specifies the path of the secret key that the server will
// load. If "tls_cert_file", "tls_key_file" are valid, the server will use this
// supplied tls configuration. Otherwise, the server will use the tls
// configuration generated by itself.
TLSKeyFile string `json:"tls_key_file"`
// TLSTrustedCaFile specifies the paths of the client cert files that the
// server will load. It only works when "tls_only" is true. If
// "tls_trusted_ca_file" is valid, the server will verify each client's
// certificate.
TLSTrustedCaFile string `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.
@@ -145,6 +160,9 @@ type ServerCommonConf struct {
UserConnTimeout int64 `json:"user_conn_timeout"`
// HTTPPlugins specify the server plugins support HTTP protocol.
HTTPPlugins map[string]plugin.HTTPPluginOptions `json:"http_plugins"`
// UDPPacketSize specifies the UDP packet size
// By default, this value is 1500
UDPPacketSize int64 `json:"udp_packet_size"`
}
// GetDefaultServerConf returns a server configuration with reasonable
@@ -153,13 +171,13 @@ func GetDefaultServerConf() ServerCommonConf {
return ServerCommonConf{
BindAddr: "0.0.0.0",
BindPort: 7000,
BindUdpPort: 0,
KcpBindPort: 0,
BindUDPPort: 0,
KCPBindPort: 0,
ProxyBindAddr: "0.0.0.0",
VhostHttpPort: 0,
VhostHttpsPort: 0,
TcpMuxHttpConnectPort: 0,
VhostHttpTimeout: 60,
VhostHTTPPort: 0,
VhostHTTPSPort: 0,
TCPMuxHTTPConnectPort: 0,
VhostHTTPTimeout: 60,
DashboardAddr: "0.0.0.0",
DashboardPort: 0,
DashboardUser: "admin",
@@ -173,15 +191,19 @@ func GetDefaultServerConf() ServerCommonConf {
DisableLogColor: false,
DetailedErrorsToClient: true,
SubDomainHost: "",
TcpMux: true,
TCPMux: true,
AllowPorts: make(map[int]struct{}),
MaxPoolCount: 5,
MaxPortsPerClient: 0,
TlsOnly: false,
TLSOnly: false,
TLSCertFile: "",
TLSKeyFile: "",
TLSTrustedCaFile: "",
HeartBeatTimeout: 90,
UserConnTimeout: 10,
Custom404Page: "",
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
UDPPacketSize: 1500,
}
}
@@ -198,7 +220,7 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
UnmarshalPluginsFromIni(conf, &cfg)
cfg.AuthServerConfig = auth.UnmarshalAuthServerConfFromIni(conf)
cfg.ServerConfig = auth.UnmarshalServerConfFromIni(conf)
var (
tmpStr string
@@ -213,27 +235,24 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid bind_port")
return
} else {
cfg.BindPort = int(v)
}
cfg.BindPort = int(v)
}
if tmpStr, ok = conf.Get("common", "bind_udp_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid bind_udp_port")
return
} else {
cfg.BindUdpPort = int(v)
}
cfg.BindUDPPort = int(v)
}
if tmpStr, ok = conf.Get("common", "kcp_bind_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid kcp_bind_port")
return
} else {
cfg.KcpBindPort = int(v)
}
cfg.KCPBindPort = int(v)
}
if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok {
@@ -246,33 +265,30 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid vhost_http_port")
return
} else {
cfg.VhostHttpPort = int(v)
}
cfg.VhostHTTPPort = int(v)
} else {
cfg.VhostHttpPort = 0
cfg.VhostHTTPPort = 0
}
if tmpStr, ok = conf.Get("common", "vhost_https_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid vhost_https_port")
return
} else {
cfg.VhostHttpsPort = int(v)
}
cfg.VhostHTTPSPort = int(v)
} else {
cfg.VhostHttpsPort = 0
cfg.VhostHTTPSPort = 0
}
if tmpStr, ok = conf.Get("common", "tcpmux_httpconnect_port"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid tcpmux_httpconnect_port")
return
} else {
cfg.TcpMuxHttpConnectPort = int(v)
}
cfg.TCPMuxHTTPConnectPort = int(v)
} else {
cfg.TcpMuxHttpConnectPort = 0
cfg.TCPMuxHTTPConnectPort = 0
}
if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok {
@@ -280,9 +296,8 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
if errRet != nil || v < 0 {
err = fmt.Errorf("Parse conf error: invalid vhost_http_timeout")
return
} else {
cfg.VhostHttpTimeout = v
}
cfg.VhostHTTPTimeout = v
}
if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok {
@@ -295,9 +310,8 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid dashboard_port")
return
} else {
cfg.DashboardPort = int(v)
}
cfg.DashboardPort = int(v)
} else {
cfg.DashboardPort = 0
}
@@ -365,26 +379,26 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
return
} else {
if v < 0 {
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
return
}
cfg.MaxPoolCount = v
}
if v < 0 {
err = fmt.Errorf("Parse conf error: invalid max_pool_count")
return
}
cfg.MaxPoolCount = v
}
if tmpStr, ok = conf.Get("common", "max_ports_per_client"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
return
} else {
if v < 0 {
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
return
}
cfg.MaxPortsPerClient = v
}
if v < 0 {
err = fmt.Errorf("Parse conf error: invalid max_ports_per_client")
return
}
cfg.MaxPortsPerClient = v
}
if tmpStr, ok = conf.Get("common", "subdomain_host"); ok {
@@ -392,9 +406,9 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
}
if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" {
cfg.TcpMux = false
cfg.TCPMux = false
} else {
cfg.TcpMux = true
cfg.TCPMux = true
}
if tmpStr, ok = conf.Get("common", "custom_404_page"); ok {
@@ -406,16 +420,37 @@ func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error
if errRet != nil {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
return
} else {
cfg.HeartBeatTimeout = v
}
cfg.HeartBeatTimeout = v
}
if tmpStr, ok = conf.Get("common", "tls_only"); ok && tmpStr == "true" {
cfg.TlsOnly = true
cfg.TLSOnly = true
} else {
cfg.TlsOnly = false
cfg.TLSOnly = false
}
if tmpStr, ok = conf.Get("common", "udp_packet_size"); ok {
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
err = fmt.Errorf("Parse conf error: invalid udp_packet_size")
return
}
cfg.UDPPacketSize = v
}
if tmpStr, ok := conf.Get("common", "tls_cert_file"); ok {
cfg.TLSCertFile = tmpStr
}
if tmpStr, ok := conf.Get("common", "tls_key_file"); ok {
cfg.TLSKeyFile = tmpStr
}
if tmpStr, ok := conf.Get("common", "tls_trusted_ca_file"); ok {
cfg.TLSTrustedCaFile = tmpStr
cfg.TLSOnly = true
}
return
}
@@ -429,7 +464,7 @@ func UnmarshalPluginsFromIni(sections ini.File, cfg *ServerCommonConf) {
Path: section["path"],
Ops: strings.Split(section["ops"], ","),
}
for i, _ := range options.Ops {
for i := range options.Ops {
options.Ops[i] = strings.TrimSpace(options.Ops[i])
}
cfg.HTTPPlugins[name] = options
@@ -437,6 +472,6 @@ func UnmarshalPluginsFromIni(sections ini.File, cfg *ServerCommonConf) {
}
}
func (cfg *ServerCommonConf) Check() (err error) {
return
func (cfg *ServerCommonConf) Check() error {
return nil
}

View File

@@ -30,8 +30,9 @@ var (
func init() {
visitorConfTypeMap = make(map[string]reflect.Type)
visitorConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpVisitorConf{})
visitorConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpVisitorConf{})
visitorConfTypeMap[consts.STCPProxy] = reflect.TypeOf(STCPVisitorConf{})
visitorConfTypeMap[consts.XTCPProxy] = reflect.TypeOf(XTCPVisitorConf{})
visitorConfTypeMap[consts.SUDPProxy] = reflect.TypeOf(SUDPVisitorConf{})
}
type VisitorConf interface {
@@ -152,12 +153,12 @@ func (cfg *BaseVisitorConf) UnmarshalFromIni(prefix string, name string, section
return nil
}
type StcpVisitorConf struct {
type SUDPVisitorConf struct {
BaseVisitorConf
}
func (cfg *StcpVisitorConf) Compare(cmp VisitorConf) bool {
cmpConf, ok := cmp.(*StcpVisitorConf)
func (cfg *SUDPVisitorConf) Compare(cmp VisitorConf) bool {
cmpConf, ok := cmp.(*SUDPVisitorConf)
if !ok {
return false
}
@@ -168,26 +169,26 @@ func (cfg *StcpVisitorConf) Compare(cmp VisitorConf) bool {
return true
}
func (cfg *StcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return
}
func (cfg *StcpVisitorConf) Check() (err error) {
func (cfg *SUDPVisitorConf) Check() (err error) {
if err = cfg.BaseVisitorConf.check(); err != nil {
return
}
return
}
type XtcpVisitorConf struct {
type STCPVisitorConf struct {
BaseVisitorConf
}
func (cfg *XtcpVisitorConf) Compare(cmp VisitorConf) bool {
cmpConf, ok := cmp.(*XtcpVisitorConf)
func (cfg *STCPVisitorConf) Compare(cmp VisitorConf) bool {
cmpConf, ok := cmp.(*STCPVisitorConf)
if !ok {
return false
}
@@ -198,14 +199,44 @@ func (cfg *XtcpVisitorConf) Compare(cmp VisitorConf) bool {
return true
}
func (cfg *XtcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return
}
func (cfg *XtcpVisitorConf) Check() (err error) {
func (cfg *STCPVisitorConf) Check() (err error) {
if err = cfg.BaseVisitorConf.check(); err != nil {
return
}
return
}
type XTCPVisitorConf struct {
BaseVisitorConf
}
func (cfg *XTCPVisitorConf) Compare(cmp VisitorConf) bool {
cmpConf, ok := cmp.(*XTCPVisitorConf)
if !ok {
return false
}
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
return false
}
return true
}
func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
return
}
return
}
func (cfg *XTCPVisitorConf) Check() (err error) {
if err = cfg.BaseVisitorConf.check(); err != nil {
return
}

View File

@@ -23,18 +23,19 @@ var (
Offline string = "offline"
// proxy type
TcpProxy string = "tcp"
UdpProxy string = "udp"
TcpMuxProxy string = "tcpmux"
HttpProxy string = "http"
HttpsProxy string = "https"
StcpProxy string = "stcp"
XtcpProxy string = "xtcp"
TCPProxy string = "tcp"
UDPProxy string = "udp"
TCPMuxProxy string = "tcpmux"
HTTPProxy string = "http"
HTTPSProxy string = "https"
STCPProxy string = "stcp"
XTCPProxy string = "xtcp"
SUDPProxy string = "sudp"
// authentication method
TokenAuthMethod string = "token"
OidcAuthMethod string = "oidc"
// tcp multiplexer
HttpConnectTcpMultiplexer string = "httpconnect"
// TCP multiplexer
HTTPConnectTCPMultiplexer string = "httpconnect"
)

View File

@@ -177,12 +177,12 @@ func (m *serverMetrics) GetServer() *ServerStats {
s := &ServerStats{
TotalTrafficIn: m.info.TotalTrafficIn.TodayCount(),
TotalTrafficOut: m.info.TotalTrafficOut.TodayCount(),
CurConns: m.info.CurConns.Count(),
ClientCounts: m.info.ClientCounts.Count(),
CurConns: int64(m.info.CurConns.Count()),
ClientCounts: int64(m.info.ClientCounts.Count()),
ProxyTypeCounts: make(map[string]int64),
}
for k, v := range m.info.ProxyTypeCounts {
s.ProxyTypeCounts[k] = v.Count()
s.ProxyTypeCounts[k] = int64(v.Count())
}
return s
}
@@ -202,7 +202,7 @@ func (m *serverMetrics) GetProxiesByType(proxyType string) []*ProxyStats {
Type: proxyStats.ProxyType,
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
CurConns: proxyStats.CurConns.Count(),
CurConns: int64(proxyStats.CurConns.Count()),
}
if !proxyStats.LastStartTime.IsZero() {
ps.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05")
@@ -233,7 +233,7 @@ func (m *serverMetrics) GetProxiesByTypeAndName(proxyType string, proxyName stri
Type: proxyStats.ProxyType,
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
CurConns: proxyStats.CurConns.Count(),
CurConns: int64(proxyStats.CurConns.Count()),
}
if !proxyStats.LastStartTime.IsZero() {
res.LastStartTime = proxyStats.LastStartTime.Format("01-02 15:04:05")

View File

@@ -29,7 +29,7 @@ const (
TypeNewVisitorConnResp = '3'
TypePing = 'h'
TypePong = '4'
TypeUdpPacket = 'u'
TypeUDPPacket = 'u'
TypeNatHoleVisitor = 'i'
TypeNatHoleClient = 'n'
TypeNatHoleResp = 'm'
@@ -51,7 +51,7 @@ var (
TypeNewVisitorConnResp: NewVisitorConnResp{},
TypePing: Ping{},
TypePong: Pong{},
TypeUdpPacket: UdpPacket{},
TypeUDPPacket: UDPPacket{},
TypeNatHoleVisitor: NatHoleVisitor{},
TypeNatHoleClient: NatHoleClient{},
TypeNatHoleResp: NatHoleResp{},
@@ -69,7 +69,7 @@ type Login struct {
User string `json:"user"`
PrivilegeKey string `json:"privilege_key"`
Timestamp int64 `json:"timestamp"`
RunId string `json:"run_id"`
RunID string `json:"run_id"`
Metas map[string]string `json:"metas"`
// Some global configures.
@@ -78,8 +78,8 @@ type Login struct {
type LoginResp struct {
Version string `json:"version"`
RunId string `json:"run_id"`
ServerUdpPort int `json:"server_udp_port"`
RunID string `json:"run_id"`
ServerUDPPort int `json:"server_udp_port"`
Error string `json:"error"`
}
@@ -100,8 +100,8 @@ type NewProxy struct {
CustomDomains []string `json:"custom_domains"`
SubDomain string `json:"subdomain"`
Locations []string `json:"locations"`
HttpUser string `json:"http_user"`
HttpPwd string `json:"http_pwd"`
HTTPUser string `json:"http_user"`
HTTPPwd string `json:"http_pwd"`
HostHeaderRewrite string `json:"host_header_rewrite"`
Headers map[string]string `json:"headers"`
@@ -123,7 +123,7 @@ type CloseProxy struct {
}
type NewWorkConn struct {
RunId string `json:"run_id"`
RunID string `json:"run_id"`
PrivilegeKey string `json:"privilege_key"`
Timestamp int64 `json:"timestamp"`
}
@@ -162,7 +162,7 @@ type Pong struct {
Error string `json:"error"`
}
type UdpPacket struct {
type UDPPacket struct {
Content string `json:"c"`
LocalAddr *net.UDPAddr `json:"l"`
RemoteAddr *net.UDPAddr `json:"r"`

View File

@@ -23,16 +23,16 @@ type SidRequest struct {
NotifyCh chan struct{}
}
type NatHoleController struct {
type Controller struct {
listener *net.UDPConn
clientCfgs map[string]*NatHoleClientCfg
sessions map[string]*NatHoleSession
clientCfgs map[string]*ClientCfg
sessions map[string]*Session
mu sync.RWMutex
}
func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error) {
func NewController(udpBindAddr string) (nc *Controller, err error) {
addr, err := net.ResolveUDPAddr("udp", udpBindAddr)
if err != nil {
return nil, err
@@ -41,16 +41,16 @@ func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error)
if err != nil {
return nil, err
}
nc = &NatHoleController{
nc = &Controller{
listener: lconn,
clientCfgs: make(map[string]*NatHoleClientCfg),
sessions: make(map[string]*NatHoleSession),
clientCfgs: make(map[string]*ClientCfg),
sessions: make(map[string]*Session),
}
return nc, nil
}
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
clientCfg := &NatHoleClientCfg{
func (nc *Controller) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
clientCfg := &ClientCfg{
Name: name,
Sk: sk,
SidCh: make(chan *SidRequest),
@@ -61,13 +61,13 @@ func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan *S
return clientCfg.SidCh
}
func (nc *NatHoleController) CloseClient(name string) {
func (nc *Controller) CloseClient(name string) {
nc.mu.Lock()
defer nc.mu.Unlock()
delete(nc.clientCfgs, name)
}
func (nc *NatHoleController) Run() {
func (nc *Controller) Run() {
for {
buf := pool.GetBuf(1024)
n, raddr, err := nc.listener.ReadFromUDP(buf)
@@ -96,15 +96,15 @@ func (nc *NatHoleController) Run() {
}
}
func (nc *NatHoleController) GenSid() string {
func (nc *Controller) GenSid() string {
t := time.Now().Unix()
id, _ := util.RandId()
id, _ := util.RandID()
return fmt.Sprintf("%d%s", t, id)
}
func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
func (nc *Controller) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
sid := nc.GenSid()
session := &NatHoleSession{
session := &Session{
Sid: sid,
VisitorAddr: raddr,
NotifyCh: make(chan struct{}, 0),
@@ -157,7 +157,7 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
}
}
func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
func (nc *Controller) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
nc.mu.RLock()
session, ok := nc.sessions[m.Sid]
nc.mu.RUnlock()
@@ -172,7 +172,7 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd
nc.listener.WriteToUDP(resp, raddr)
}
func (nc *NatHoleController) GenNatHoleResponse(session *NatHoleSession, errInfo string) []byte {
func (nc *Controller) GenNatHoleResponse(session *Session, errInfo string) []byte {
var (
sid string
visitorAddr string
@@ -197,7 +197,7 @@ func (nc *NatHoleController) GenNatHoleResponse(session *NatHoleSession, errInfo
return b.Bytes()
}
type NatHoleSession struct {
type Session struct {
Sid string
VisitorAddr *net.UDPAddr
ClientAddr *net.UDPAddr
@@ -205,7 +205,7 @@ type NatHoleSession struct {
NotifyCh chan struct{}
}
type NatHoleClientCfg struct {
type ClientCfg struct {
Name string
Sk string
SidCh chan *SidRequest

View File

@@ -28,25 +28,25 @@ import (
gnet "github.com/fatedier/golib/net"
)
const PluginHttpProxy = "http_proxy"
const PluginHTTPProxy = "http_proxy"
func init() {
Register(PluginHttpProxy, NewHttpProxyPlugin)
Register(PluginHTTPProxy, NewHTTPProxyPlugin)
}
type HttpProxy struct {
type HTTPProxy struct {
l *Listener
s *http.Server
AuthUser string
AuthPasswd string
}
func NewHttpProxyPlugin(params map[string]string) (Plugin, error) {
func NewHTTPProxyPlugin(params map[string]string) (Plugin, error) {
user := params["plugin_http_user"]
passwd := params["plugin_http_passwd"]
listener := NewProxyListener()
hp := &HttpProxy{
hp := &HTTPProxy{
l: listener,
AuthUser: user,
AuthPasswd: passwd,
@@ -60,11 +60,11 @@ func NewHttpProxyPlugin(params map[string]string) (Plugin, error) {
return hp, nil
}
func (hp *HttpProxy) Name() string {
return PluginHttpProxy
func (hp *HTTPProxy) Name() string {
return PluginHTTPProxy
}
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
sc, rd := gnet.NewSharedConn(wrapConn)
@@ -90,13 +90,13 @@ func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBuf
return
}
func (hp *HttpProxy) Close() error {
func (hp *HTTPProxy) Close() error {
hp.s.Close()
hp.l.Close()
return nil
}
func (hp *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
func (hp *HTTPProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if ok := hp.Auth(req); !ok {
rw.Header().Set("Proxy-Authenticate", "Basic")
rw.WriteHeader(http.StatusProxyAuthRequired)
@@ -108,11 +108,11 @@ func (hp *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// Connect request is handled in Handle function.
hp.ConnectHandler(rw, req)
} else {
hp.HttpHandler(rw, req)
hp.HTTPHandler(rw, req)
}
}
func (hp *HttpProxy) HttpHandler(rw http.ResponseWriter, req *http.Request) {
func (hp *HTTPProxy) HTTPHandler(rw http.ResponseWriter, req *http.Request) {
removeProxyHeaders(req)
resp, err := http.DefaultTransport.RoundTrip(req)
@@ -134,7 +134,7 @@ func (hp *HttpProxy) HttpHandler(rw http.ResponseWriter, req *http.Request) {
// deprecated
// Hijack needs to SetReadDeadline on the Conn of the request, but if we use stream compression here,
// we may always get i/o timeout error.
func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
func (hp *HTTPProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
hj, ok := rw.(http.Hijacker)
if !ok {
rw.WriteHeader(http.StatusInternalServerError)
@@ -158,7 +158,7 @@ func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
go frpIo.Join(remote, client)
}
func (hp *HttpProxy) Auth(req *http.Request) bool {
func (hp *HTTPProxy) Auth(req *http.Request) bool {
if hp.AuthUser == "" && hp.AuthPasswd == "" {
return true
}
@@ -184,7 +184,7 @@ func (hp *HttpProxy) Auth(req *http.Request) bool {
return true
}
func (hp *HttpProxy) handleConnectReq(req *http.Request, rwc io.ReadWriteCloser) {
func (hp *HTTPProxy) handleConnectReq(req *http.Request, rwc io.ReadWriteCloser) {
defer rwc.Close()
if ok := hp.Auth(req); !ok {
res := getBadResponse()
@@ -231,6 +231,7 @@ func removeProxyHeaders(req *http.Request) {
func getBadResponse() *http.Response {
header := make(map[string][]string)
header["Proxy-Authenticate"] = []string{"Basic"}
header["Connection"] = []string{"close"}
res := &http.Response{
Status: "407 Not authorized",
StatusCode: 407,

View File

@@ -64,8 +64,8 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
}
router := mux.NewRouter()
router.Use(frpNet.NewHttpAuthMiddleware(httpUser, httpPasswd).Middleware)
router.PathPrefix(prefix).Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
router.Use(frpNet.NewHTTPAuthMiddleware(httpUser, httpPasswd).Middleware)
router.PathPrefix(prefix).Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
sp.s = &http.Server{
Handler: router,
}

View File

@@ -21,6 +21,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"reflect"
)
@@ -78,7 +79,10 @@ func (p *httpPlugin) do(ctx context.Context, r *Request, res *Response) error {
if err != nil {
return err
}
req, err := http.NewRequest("POST", p.url, bytes.NewReader(buf))
v := url.Values{}
v.Set("version", r.Version)
v.Set("op", r.Op)
req, err := http.NewRequest("POST", p.url+"?"+v.Encode(), bytes.NewReader(buf))
if err != nil {
return err
}

View File

@@ -24,14 +24,20 @@ import (
)
type Manager struct {
loginPlugins []Plugin
newProxyPlugins []Plugin
loginPlugins []Plugin
newProxyPlugins []Plugin
pingPlugins []Plugin
newWorkConnPlugins []Plugin
newUserConnPlugins []Plugin
}
func NewManager() *Manager {
return &Manager{
loginPlugins: make([]Plugin, 0),
newProxyPlugins: make([]Plugin, 0),
loginPlugins: make([]Plugin, 0),
newProxyPlugins: make([]Plugin, 0),
pingPlugins: make([]Plugin, 0),
newWorkConnPlugins: make([]Plugin, 0),
newUserConnPlugins: make([]Plugin, 0),
}
}
@@ -42,9 +48,22 @@ func (m *Manager) Register(p Plugin) {
if p.IsSupport(OpNewProxy) {
m.newProxyPlugins = append(m.newProxyPlugins, p)
}
if p.IsSupport(OpPing) {
m.pingPlugins = append(m.pingPlugins, p)
}
if p.IsSupport(OpNewWorkConn) {
m.newWorkConnPlugins = append(m.newWorkConnPlugins, p)
}
if p.IsSupport(OpNewUserConn) {
m.newUserConnPlugins = append(m.newUserConnPlugins, p)
}
}
func (m *Manager) Login(content *LoginContent) (*LoginContent, error) {
if len(m.loginPlugins) == 0 {
return content, nil
}
var (
res = &Response{
Reject: false,
@@ -53,7 +72,7 @@ func (m *Manager) Login(content *LoginContent) (*LoginContent, error) {
retContent interface{}
err error
)
reqid, _ := util.RandId()
reqid, _ := util.RandID()
xl := xlog.New().AppendPrefix("reqid: " + reqid)
ctx := xlog.NewContext(context.Background(), xl)
ctx = NewReqidContext(ctx, reqid)
@@ -75,6 +94,10 @@ func (m *Manager) Login(content *LoginContent) (*LoginContent, error) {
}
func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
if len(m.newProxyPlugins) == 0 {
return content, nil
}
var (
res = &Response{
Reject: false,
@@ -83,7 +106,7 @@ func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
retContent interface{}
err error
)
reqid, _ := util.RandId()
reqid, _ := util.RandID()
xl := xlog.New().AppendPrefix("reqid: " + reqid)
ctx := xlog.NewContext(context.Background(), xl)
ctx = NewReqidContext(ctx, reqid)
@@ -103,3 +126,105 @@ func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
}
return content, nil
}
func (m *Manager) Ping(content *PingContent) (*PingContent, error) {
if len(m.pingPlugins) == 0 {
return content, nil
}
var (
res = &Response{
Reject: false,
Unchange: true,
}
retContent interface{}
err error
)
reqid, _ := util.RandID()
xl := xlog.New().AppendPrefix("reqid: " + reqid)
ctx := xlog.NewContext(context.Background(), xl)
ctx = NewReqidContext(ctx, reqid)
for _, p := range m.pingPlugins {
res, retContent, err = p.Handle(ctx, OpPing, *content)
if err != nil {
xl.Warn("send Ping request to plugin [%s] error: %v", p.Name(), err)
return nil, errors.New("send Ping request to plugin error")
}
if res.Reject {
return nil, fmt.Errorf("%s", res.RejectReason)
}
if !res.Unchange {
content = retContent.(*PingContent)
}
}
return content, nil
}
func (m *Manager) NewWorkConn(content *NewWorkConnContent) (*NewWorkConnContent, error) {
if len(m.newWorkConnPlugins) == 0 {
return content, nil
}
var (
res = &Response{
Reject: false,
Unchange: true,
}
retContent interface{}
err error
)
reqid, _ := util.RandID()
xl := xlog.New().AppendPrefix("reqid: " + reqid)
ctx := xlog.NewContext(context.Background(), xl)
ctx = NewReqidContext(ctx, reqid)
for _, p := range m.pingPlugins {
res, retContent, err = p.Handle(ctx, OpPing, *content)
if err != nil {
xl.Warn("send NewWorkConn request to plugin [%s] error: %v", p.Name(), err)
return nil, errors.New("send NewWorkConn request to plugin error")
}
if res.Reject {
return nil, fmt.Errorf("%s", res.RejectReason)
}
if !res.Unchange {
content = retContent.(*NewWorkConnContent)
}
}
return content, nil
}
func (m *Manager) NewUserConn(content *NewUserConnContent) (*NewUserConnContent, error) {
if len(m.newUserConnPlugins) == 0 {
return content, nil
}
var (
res = &Response{
Reject: false,
Unchange: true,
}
retContent interface{}
err error
)
reqid, _ := util.RandID()
xl := xlog.New().AppendPrefix("reqid: " + reqid)
ctx := xlog.NewContext(context.Background(), xl)
ctx = NewReqidContext(ctx, reqid)
for _, p := range m.newUserConnPlugins {
res, retContent, err = p.Handle(ctx, OpNewUserConn, *content)
if err != nil {
xl.Info("send NewUserConn request to plugin [%s] error: %v", p.Name(), err)
return nil, errors.New("send NewUserConn request to plugin error")
}
if res.Reject {
return nil, fmt.Errorf("%s", res.RejectReason)
}
if !res.Unchange {
content = retContent.(*NewUserConnContent)
}
}
return content, nil
}

View File

@@ -21,8 +21,11 @@ import (
const (
APIVersion = "0.1.0"
OpLogin = "Login"
OpNewProxy = "NewProxy"
OpLogin = "Login"
OpNewProxy = "NewProxy"
OpPing = "Ping"
OpNewWorkConn = "NewWorkConn"
OpNewUserConn = "NewUserConn"
)
type Plugin interface {

View File

@@ -38,9 +38,27 @@ type LoginContent struct {
type UserInfo struct {
User string `json:"user"`
Metas map[string]string `json:"metas"`
RunID string `json:"run_id"`
}
type NewProxyContent struct {
User UserInfo `json:"user"`
msg.NewProxy
}
type PingContent struct {
User UserInfo `json:"user"`
msg.Ping
}
type NewWorkConnContent struct {
User UserInfo `json:"user"`
msg.NewWorkConn
}
type NewUserConnContent struct {
User UserInfo `json:"user"`
ProxyName string `json:"proxy_name"`
ProxyType string `json:"proxy_type"`
RemoteAddr string `json:"remote_addr"`
}

View File

@@ -26,20 +26,20 @@ import (
"github.com/fatedier/golib/pool"
)
func NewUdpPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UdpPacket {
return &msg.UdpPacket{
func NewUDPPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UDPPacket {
return &msg.UDPPacket{
Content: base64.StdEncoding.EncodeToString(buf),
LocalAddr: laddr,
RemoteAddr: raddr,
}
}
func GetContent(m *msg.UdpPacket) (buf []byte, err error) {
func GetContent(m *msg.UDPPacket) (buf []byte, err error) {
buf, err = base64.StdEncoding.DecodeString(m.Content)
return
}
func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh chan<- *msg.UdpPacket) {
func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UDPPacket, sendCh chan<- *msg.UDPPacket, bufSize int) {
// read
go func() {
for udpMsg := range readCh {
@@ -52,16 +52,16 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
}()
// write
buf := pool.GetBuf(1500)
buf := pool.GetBuf(bufSize)
defer pool.PutBuf(buf)
for {
n, remoteAddr, err := udpConn.ReadFromUDP(buf)
if err != nil {
udpConn.Close()
return
}
// buf[:n] will be encoded to string, so the bytes can be reused
udpMsg := NewUdpPacket(buf[:n], nil, remoteAddr)
udpMsg := NewUDPPacket(buf[:n], nil, remoteAddr)
select {
case sendCh <- udpMsg:
default:
@@ -69,7 +69,7 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
}
}
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UDPPacket, sendCh chan<- msg.Message, bufSize int) {
var (
mu sync.RWMutex
)
@@ -85,7 +85,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
udpConn.Close()
}()
buf := pool.GetBuf(1500)
buf := pool.GetBuf(bufSize)
for {
udpConn.SetReadDeadline(time.Now().Add(30 * time.Second))
n, _, err := udpConn.ReadFromUDP(buf)
@@ -93,7 +93,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
return
}
udpMsg := NewUdpPacket(buf[:n], nil, raddr)
udpMsg := NewUDPPacket(buf[:n], nil, raddr)
if err = errors.PanicToError(func() {
select {
case sendCh <- udpMsg:

View File

@@ -10,7 +10,7 @@ func TestUdpPacket(t *testing.T) {
assert := assert.New(t)
buf := []byte("hello world")
udpMsg := NewUdpPacket(buf, nil, nil)
udpMsg := NewUDPPacket(buf, nil, nil)
newBuf, err := GetContent(udpMsg)
assert.NoError(err)

136
models/transport/tls.go Normal file
View File

@@ -0,0 +1,136 @@
package transport
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"math/big"
)
/*
Example for self-signed certificates by openssl:
Self CA:
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.ca.com" -days 5000 -out ca.crt
Server:
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=example.server.com" -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 5000
Client:
openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj "/CN=example.client.com" -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 5000
*/
func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, error) {
tlsCert, err := tls.LoadX509KeyPair(certfile, keyfile)
if err != nil {
return nil, err
}
return &tlsCert, nil
}
func newRandomTLSKeyPair() *tls.Certificate {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(
rand.Reader,
&template,
&template,
&key.PublicKey,
key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tlsCert
}
// Only supprt one ca file to add
func newCertPool(caPath string) (*x509.CertPool, error) {
pool := x509.NewCertPool()
caCrt, err := ioutil.ReadFile(caPath)
if err != nil {
return nil, err
}
pool.AppendCertsFromPEM(caCrt)
return pool, nil
}
func NewServerTLSConfig(certPath, keyPath, caPath string) (*tls.Config, error) {
var base = &tls.Config{}
if certPath == "" || keyPath == "" {
// server will generate tls conf by itself
cert := newRandomTLSKeyPair()
base.Certificates = []tls.Certificate{*cert}
} else {
cert, err := newCustomTLSKeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
base.Certificates = []tls.Certificate{*cert}
}
if caPath != "" {
pool, err := newCertPool(caPath)
if err != nil {
return nil, err
}
base.ClientAuth = tls.RequireAndVerifyClientCert
base.ClientCAs = pool
}
return base, nil
}
func NewClientTLSConfig(certPath, keyPath, caPath, servearName string) (*tls.Config, error) {
var base = &tls.Config{}
if certPath == "" || keyPath == "" {
// client will not generate tls conf by itself
} else {
cert, err := newCustomTLSKeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
base.Certificates = []tls.Certificate{*cert}
}
if caPath != "" {
pool, err := newCertPool(caPath)
if err != nil {
return nil, err
}
base.RootCAs = pool
base.ServerName = servearName
base.InsecureSkipVerify = false
} else {
base.InsecureSkipVerify = true
}
return base, nil
}

View File

@@ -11,12 +11,14 @@ echo "build version: $frp_version"
# cross_compiles
make -f ./Makefile.cross-compiles
rm -rf ./packages
mkdir ./packages
rm -rf ./release/packages
mkdir -p ./release/packages
os_all='linux windows darwin freebsd'
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'
cd ./release
for os in $os_all; do
for arch in $arch_all; do
frp_dir_name="frp_${frp_version}_${os}_${arch}"
@@ -43,8 +45,8 @@ for os in $os_all; do
mv ./frpc_${os}_${arch} ${frp_path}/frpc
mv ./frps_${os}_${arch} ${frp_path}/frps
fi
cp ./LICENSE ${frp_path}
cp -rf ./conf/* ${frp_path}
cp ../LICENSE ${frp_path}
cp -rf ../conf/* ${frp_path}
# packages
cd ./packages
@@ -57,3 +59,5 @@ for os in $os_all; do
rm -rf ${frp_path}
done
done
cd -

View File

@@ -43,42 +43,42 @@ import (
type ControlManager struct {
// controls indexed by run id
ctlsByRunId map[string]*Control
ctlsByRunID map[string]*Control
mu sync.RWMutex
}
func NewControlManager() *ControlManager {
return &ControlManager{
ctlsByRunId: make(map[string]*Control),
ctlsByRunID: make(map[string]*Control),
}
}
func (cm *ControlManager) Add(runId string, ctl *Control) (oldCtl *Control) {
func (cm *ControlManager) Add(runID string, ctl *Control) (oldCtl *Control) {
cm.mu.Lock()
defer cm.mu.Unlock()
oldCtl, ok := cm.ctlsByRunId[runId]
oldCtl, ok := cm.ctlsByRunID[runID]
if ok {
oldCtl.Replaced(ctl)
}
cm.ctlsByRunId[runId] = ctl
cm.ctlsByRunID[runID] = ctl
return
}
// we should make sure if it's the same control to prevent delete a new one
func (cm *ControlManager) Del(runId string, ctl *Control) {
func (cm *ControlManager) Del(runID string, ctl *Control) {
cm.mu.Lock()
defer cm.mu.Unlock()
if c, ok := cm.ctlsByRunId[runId]; ok && c == ctl {
delete(cm.ctlsByRunId, runId)
if c, ok := cm.ctlsByRunID[runID]; ok && c == ctl {
delete(cm.ctlsByRunID, runID)
}
}
func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
func (cm *ControlManager) GetByID(runID string) (ctl *Control, ok bool) {
cm.mu.RLock()
defer cm.mu.RUnlock()
ctl, ok = cm.ctlsByRunId[runId]
ctl, ok = cm.ctlsByRunID[runID]
return
}
@@ -87,7 +87,7 @@ type Control struct {
rc *controller.ResourceController
// proxy manager
pxyManager *proxy.ProxyManager
pxyManager *proxy.Manager
// plugin manager
pluginManager *plugin.Manager
@@ -125,7 +125,7 @@ type Control struct {
// A new run id will be generated when a new client login.
// If run id got from login message has same run id, it means it's the same client, so we can
// replace old controller instantly.
runId string
runID string
// control status
status string
@@ -147,7 +147,7 @@ type Control struct {
func NewControl(
ctx context.Context,
rc *controller.ResourceController,
pxyManager *proxy.ProxyManager,
pxyManager *proxy.Manager,
pluginManager *plugin.Manager,
authVerifier auth.Verifier,
ctlConn net.Conn,
@@ -173,7 +173,7 @@ func NewControl(
poolCount: poolCount,
portsUsedNum: 0,
lastPing: time.Now(),
runId: loginMsg.RunId,
runID: loginMsg.RunID,
status: consts.Working,
readerShutdown: shutdown.New(),
writerShutdown: shutdown.New(),
@@ -189,8 +189,8 @@ func NewControl(
func (ctl *Control) Start() {
loginRespMsg := &msg.LoginResp{
Version: version.Full(),
RunId: ctl.runId,
ServerUdpPort: ctl.serverCfg.BindUdpPort,
RunID: ctl.runID,
ServerUDPPort: ctl.serverCfg.BindUDPPort,
Error: "",
}
msg.WriteMsg(ctl.conn, loginRespMsg)
@@ -280,8 +280,8 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
func (ctl *Control) Replaced(newCtl *Control) {
xl := ctl.xl
xl.Info("Replaced by client [%s]", newCtl.runId)
ctl.runId = ""
xl.Info("Replaced by client [%s]", newCtl.runID)
ctl.runID = ""
ctl.allShutdown.Start()
}
@@ -304,14 +304,15 @@ func (ctl *Control) writer() {
return
}
for {
if m, ok := <-ctl.sendCh; !ok {
m, ok := <-ctl.sendCh
if !ok {
xl.Info("control writer is closing")
return
} else {
if err := msg.WriteMsg(encWriter, m); err != nil {
xl.Warn("write message to control connection error: %v", err)
return
}
}
if err := msg.WriteMsg(encWriter, m); err != nil {
xl.Warn("write message to control connection error: %v", err)
return
}
}
}
@@ -330,18 +331,18 @@ func (ctl *Control) reader() {
encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token))
for {
if m, err := msg.ReadMsg(encReader); err != nil {
m, err := msg.ReadMsg(encReader)
if err != nil {
if err == io.EOF {
xl.Debug("control connection closed")
return
} else {
xl.Warn("read error: %v", err)
ctl.conn.Close()
return
}
} else {
ctl.readCh <- m
xl.Warn("read error: %v", err)
ctl.conn.Close()
return
}
ctl.readCh <- m
}
}
@@ -422,6 +423,7 @@ func (ctl *Control) manager() {
User: plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.loginMsg.RunID,
},
NewProxy: *m,
}
@@ -449,10 +451,23 @@ func (ctl *Control) manager() {
ctl.CloseProxy(m)
xl.Info("close proxy [%s] success", m.ProxyName)
case *msg.Ping:
if err := ctl.authVerifier.VerifyPing(m); err != nil {
content := &plugin.PingContent{
User: plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.loginMsg.RunID,
},
Ping: *m,
}
retContent, err := ctl.pluginManager.Ping(content)
if err == nil {
m = &retContent.Ping
err = ctl.authVerifier.VerifyPing(m)
}
if err != nil {
xl.Warn("received invalid ping: %v", err)
ctl.sendCh <- &msg.Pong{
Error: "invalid authentication in ping",
Error: util.GenerateResponseErrorString("invalid ping", err, ctl.serverCfg.DetailedErrorsToClient),
}
return
}
@@ -472,9 +487,16 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
return
}
// User info
userInfo := plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.runID,
}
// NewProxy will return a interface Proxy.
// In fact it create different proxies by different proxy type, we just call run() here.
pxy, err := proxy.NewProxy(ctl.ctx, ctl.runId, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
pxy, err := proxy.NewProxy(ctl.ctx, userInfo, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
if err != nil {
return remoteAddr, err
}

View File

@@ -16,8 +16,10 @@ package controller
import (
"github.com/fatedier/frp/models/nathole"
plugin "github.com/fatedier/frp/models/plugin/server"
"github.com/fatedier/frp/server/group"
"github.com/fatedier/frp/server/ports"
"github.com/fatedier/frp/server/visitor"
"github.com/fatedier/frp/utils/tcpmux"
"github.com/fatedier/frp/utils/vhost"
)
@@ -25,29 +27,35 @@ import (
// All resource managers and controllers
type ResourceController struct {
// Manage all visitor listeners
VisitorManager *VisitorManager
VisitorManager *visitor.Manager
// Tcp Group Controller
TcpGroupCtl *group.TcpGroupCtl
// TCP Group Controller
TCPGroupCtl *group.TCPGroupCtl
// HTTP Group Controller
HTTPGroupCtl *group.HTTPGroupController
// Manage all tcp ports
TcpPortManager *ports.PortManager
// TCP Mux Group Controller
TCPMuxGroupCtl *group.TCPMuxGroupCtl
// Manage all udp ports
UdpPortManager *ports.PortManager
// Manage all TCP ports
TCPPortManager *ports.Manager
// For http proxies, forwarding http requests
HttpReverseProxy *vhost.HttpReverseProxy
// Manage all UDP ports
UDPPortManager *ports.Manager
// For https proxies, route requests to different clients by hostname and other information
VhostHttpsMuxer *vhost.HttpsMuxer
// For HTTP proxies, forwarding HTTP requests
HTTPReverseProxy *vhost.HTTPReverseProxy
// For HTTPS proxies, route requests to different clients by hostname and other information
VhostHTTPSMuxer *vhost.HTTPSMuxer
// Controller for nat hole connections
NatHoleController *nathole.NatHoleController
NatHoleController *nathole.Controller
// TcpMux HTTP CONNECT multiplexer
TcpMuxHttpConnectMuxer *tcpmux.HttpConnectTcpMuxer
// TCPMux HTTP CONNECT multiplexer
TCPMuxHTTPConnectMuxer *tcpmux.HTTPConnectTCPMuxer
// All server manager plugin
PluginManager *plugin.Manager
}

View File

@@ -37,7 +37,7 @@ func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
router := mux.NewRouter()
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
// metrics
if svr.cfg.EnablePrometheus {
@@ -45,14 +45,14 @@ func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
}
// 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("/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")
// 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.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)

View File

@@ -32,13 +32,13 @@ type GeneralResponse struct {
Msg string
}
type ServerInfoResp struct {
type serverInfoResp struct {
Version string `json:"version"`
BindPort int `json:"bind_port"`
BindUdpPort int `json:"bind_udp_port"`
VhostHttpPort int `json:"vhost_http_port"`
VhostHttpsPort int `json:"vhost_https_port"`
KcpBindPort int `json:"kcp_bind_port"`
BindUDPPort int `json:"bind_udp_port"`
VhostHTTPPort int `json:"vhost_http_port"`
VhostHTTPSPort int `json:"vhost_https_port"`
KCPBindPort int `json:"kcp_bind_port"`
SubdomainHost string `json:"subdomain_host"`
MaxPoolCount int64 `json:"max_pool_count"`
MaxPortsPerClient int64 `json:"max_ports_per_client"`
@@ -52,7 +52,7 @@ type ServerInfoResp struct {
}
// api/serverinfo
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
defer func() {
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
@@ -64,13 +64,13 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
log.Info("Http request: [%s]", r.URL.Path)
serverStats := mem.StatsCollector.GetServer()
svrResp := ServerInfoResp{
svrResp := serverInfoResp{
Version: version.Full(),
BindPort: svr.cfg.BindPort,
BindUdpPort: svr.cfg.BindUdpPort,
VhostHttpPort: svr.cfg.VhostHttpPort,
VhostHttpsPort: svr.cfg.VhostHttpsPort,
KcpBindPort: svr.cfg.KcpBindPort,
BindUDPPort: svr.cfg.BindUDPPort,
VhostHTTPPort: svr.cfg.VhostHTTPPort,
VhostHTTPSPort: svr.cfg.VhostHTTPSPort,
KCPBindPort: svr.cfg.KCPBindPort,
SubdomainHost: svr.cfg.SubDomainHost,
MaxPoolCount: svr.cfg.MaxPoolCount,
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
@@ -91,58 +91,58 @@ type BaseOutConf struct {
config.BaseProxyConf
}
type TcpOutConf struct {
type TCPOutConf struct {
BaseOutConf
RemotePort int `json:"remote_port"`
}
type TcpMuxOutConf struct {
type TCPMuxOutConf struct {
BaseOutConf
config.DomainConf
Multiplexer string `json:"multiplexer"`
}
type UdpOutConf struct {
type UDPOutConf struct {
BaseOutConf
RemotePort int `json:"remote_port"`
}
type HttpOutConf struct {
type HTTPOutConf struct {
BaseOutConf
config.DomainConf
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"`
}
type HttpsOutConf struct {
type HTTPSOutConf struct {
BaseOutConf
config.DomainConf
}
type StcpOutConf struct {
type STCPOutConf struct {
BaseOutConf
}
type XtcpOutConf struct {
type XTCPOutConf struct {
BaseOutConf
}
func getConfByType(proxyType string) interface{} {
switch proxyType {
case consts.TcpProxy:
return &TcpOutConf{}
case consts.TcpMuxProxy:
return &TcpMuxOutConf{}
case consts.UdpProxy:
return &UdpOutConf{}
case consts.HttpProxy:
return &HttpOutConf{}
case consts.HttpsProxy:
return &HttpsOutConf{}
case consts.StcpProxy:
return &StcpOutConf{}
case consts.XtcpProxy:
return &XtcpOutConf{}
case consts.TCPProxy:
return &TCPOutConf{}
case consts.TCPMuxProxy:
return &TCPMuxOutConf{}
case consts.UDPProxy:
return &UDPOutConf{}
case consts.HTTPProxy:
return &HTTPOutConf{}
case consts.HTTPSProxy:
return &HTTPSOutConf{}
case consts.STCPProxy:
return &STCPOutConf{}
case consts.XTCPProxy:
return &XTCPOutConf{}
default:
return nil
}
@@ -165,7 +165,7 @@ type GetProxyInfoResp struct {
}
// api/proxy/:type
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
func (svr *Service) APIProxyByType(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
params := mux.Vars(r)
proxyType := params["type"]
@@ -230,7 +230,7 @@ type GetProxyStatsResp struct {
}
// api/proxy/:type/:name
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
func (svr *Service) APIProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
params := mux.Vars(r)
proxyType := params["type"]
@@ -299,7 +299,7 @@ type GetProxyTrafficResp struct {
TrafficOut []int64 `json:"traffic_out"`
}
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
func (svr *Service) APIProxyTraffic(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
params := mux.Vars(r)
name := params["name"]
@@ -321,10 +321,9 @@ func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
res.Code = 404
res.Msg = "no proxy info found"
return
} else {
trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
}
trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
buf, _ := json.Marshal(&trafficResp)
res.Msg = string(buf)

View File

@@ -12,12 +12,12 @@ import (
type HTTPGroupController struct {
groups map[string]*HTTPGroup
vhostRouter *vhost.VhostRouters
vhostRouter *vhost.Routers
mu sync.Mutex
}
func NewHTTPGroupController(vhostRouter *vhost.VhostRouters) *HTTPGroupController {
func NewHTTPGroupController(vhostRouter *vhost.Routers) *HTTPGroupController {
return &HTTPGroupController{
groups: make(map[string]*HTTPGroup),
vhostRouter: vhostRouter,
@@ -25,7 +25,7 @@ func NewHTTPGroupController(vhostRouter *vhost.VhostRouters) *HTTPGroupControlle
}
func (ctl *HTTPGroupController) Register(proxyName, group, groupKey string,
routeConfig vhost.VhostRouteConfig) (err error) {
routeConfig vhost.RouteConfig) (err error) {
indexKey := httpGroupIndex(group, routeConfig.Domain, routeConfig.Location)
ctl.mu.Lock()
@@ -76,7 +76,7 @@ func NewHTTPGroup(ctl *HTTPGroupController) *HTTPGroup {
}
func (g *HTTPGroup) Register(proxyName, group, groupKey string,
routeConfig vhost.VhostRouteConfig) (err error) {
routeConfig vhost.RouteConfig) (err error) {
g.mu.Lock()
defer g.mu.Unlock()

View File

@@ -24,32 +24,32 @@ import (
gerr "github.com/fatedier/golib/errors"
)
// TcpGroupCtl manage all TcpGroups
type TcpGroupCtl struct {
groups map[string]*TcpGroup
// TCPGroupCtl manage all TCPGroups
type TCPGroupCtl struct {
groups map[string]*TCPGroup
// portManager is used to manage port
portManager *ports.PortManager
portManager *ports.Manager
mu sync.Mutex
}
// NewTcpGroupCtl return a new TcpGroupCtl
func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl {
return &TcpGroupCtl{
groups: make(map[string]*TcpGroup),
// NewTCPGroupCtl return a new TcpGroupCtl
func NewTCPGroupCtl(portManager *ports.Manager) *TCPGroupCtl {
return &TCPGroupCtl{
groups: make(map[string]*TCPGroup),
portManager: portManager,
}
}
// Listen is the wrapper for TcpGroup's Listen
// Listen is the wrapper for TCPGroup's Listen
// If there are no group, we will create one here
func (tgc *TcpGroupCtl) Listen(proxyName string, group string, groupKey string,
func (tgc *TCPGroupCtl) Listen(proxyName string, group string, groupKey string,
addr string, port int) (l net.Listener, realPort int, err error) {
tgc.mu.Lock()
tcpGroup, ok := tgc.groups[group]
if !ok {
tcpGroup = NewTcpGroup(tgc)
tcpGroup = NewTCPGroup(tgc)
tgc.groups[group] = tcpGroup
}
tgc.mu.Unlock()
@@ -57,15 +57,15 @@ func (tgc *TcpGroupCtl) Listen(proxyName string, group string, groupKey string,
return tcpGroup.Listen(proxyName, group, groupKey, addr, port)
}
// RemoveGroup remove TcpGroup from controller
func (tgc *TcpGroupCtl) RemoveGroup(group string) {
// RemoveGroup remove TCPGroup from controller
func (tgc *TCPGroupCtl) RemoveGroup(group string) {
tgc.mu.Lock()
defer tgc.mu.Unlock()
delete(tgc.groups, group)
}
// TcpGroup route connections to different proxies
type TcpGroup struct {
// TCPGroup route connections to different proxies
type TCPGroup struct {
group string
groupKey string
addr string
@@ -75,24 +75,24 @@ type TcpGroup struct {
acceptCh chan net.Conn
index uint64
tcpLn net.Listener
lns []*TcpGroupListener
ctl *TcpGroupCtl
lns []*TCPGroupListener
ctl *TCPGroupCtl
mu sync.Mutex
}
// NewTcpGroup return a new TcpGroup
func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup {
return &TcpGroup{
lns: make([]*TcpGroupListener, 0),
// NewTCPGroup return a new TCPGroup
func NewTCPGroup(ctl *TCPGroupCtl) *TCPGroup {
return &TCPGroup{
lns: make([]*TCPGroupListener, 0),
ctl: ctl,
acceptCh: make(chan net.Conn),
}
}
// Listen will return a new TcpGroupListener
// if TcpGroup already has a listener, just add a new TcpGroupListener to the queues
// Listen will return a new TCPGroupListener
// if TCPGroup already has a listener, just add a new TCPGroupListener to the queues
// otherwise, listen on the real address
func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) {
func (tg *TCPGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TCPGroupListener, realPort int, err error) {
tg.mu.Lock()
defer tg.mu.Unlock()
if len(tg.lns) == 0 {
@@ -106,7 +106,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
err = errRet
return
}
ln = newTcpGroupListener(group, tg, tcpLn.Addr())
ln = newTCPGroupListener(group, tg, tcpLn.Addr())
tg.group = group
tg.groupKey = groupKey
@@ -133,7 +133,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
err = ErrGroupAuthFailed
return
}
ln = newTcpGroupListener(group, tg, tg.lns[0].Addr())
ln = newTCPGroupListener(group, tg, tg.lns[0].Addr())
realPort = tg.realPort
tg.lns = append(tg.lns, ln)
}
@@ -141,7 +141,7 @@ func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr
}
// worker is called when the real tcp listener has been created
func (tg *TcpGroup) worker() {
func (tg *TCPGroup) worker() {
for {
c, err := tg.tcpLn.Accept()
if err != nil {
@@ -156,12 +156,12 @@ func (tg *TcpGroup) worker() {
}
}
func (tg *TcpGroup) Accept() <-chan net.Conn {
func (tg *TCPGroup) Accept() <-chan net.Conn {
return tg.acceptCh
}
// CloseListener remove the TcpGroupListener from the TcpGroup
func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
// CloseListener remove the TCPGroupListener from the TCPGroup
func (tg *TCPGroup) CloseListener(ln *TCPGroupListener) {
tg.mu.Lock()
defer tg.mu.Unlock()
for i, tmpLn := range tg.lns {
@@ -178,17 +178,17 @@ func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) {
}
}
// TcpGroupListener
type TcpGroupListener struct {
// TCPGroupListener
type TCPGroupListener struct {
groupName string
group *TcpGroup
group *TCPGroup
addr net.Addr
closeCh chan struct{}
}
func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener {
return &TcpGroupListener{
func newTCPGroupListener(name string, group *TCPGroup, addr net.Addr) *TCPGroupListener {
return &TCPGroupListener{
groupName: name,
group: group,
addr: addr,
@@ -196,8 +196,8 @@ func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupL
}
}
// Accept will accept connections from TcpGroup
func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
// Accept will accept connections from TCPGroup
func (ln *TCPGroupListener) Accept() (c net.Conn, err error) {
var ok bool
select {
case <-ln.closeCh:
@@ -210,12 +210,12 @@ func (ln *TcpGroupListener) Accept() (c net.Conn, err error) {
}
}
func (ln *TcpGroupListener) Addr() net.Addr {
func (ln *TCPGroupListener) Addr() net.Addr {
return ln.addr
}
// Close close the listener
func (ln *TcpGroupListener) Close() (err error) {
func (ln *TCPGroupListener) Close() (err error) {
close(ln.closeCh)
// remove self from TcpGroup

219
server/group/tcpmux.go Normal file
View File

@@ -0,0 +1,219 @@
// Copyright 2020 guylewin, guy@lewin.co.il
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package group
import (
"context"
"fmt"
"net"
"sync"
"github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/utils/tcpmux"
"github.com/fatedier/frp/utils/vhost"
gerr "github.com/fatedier/golib/errors"
)
// TCPMuxGroupCtl manage all TCPMuxGroups
type TCPMuxGroupCtl struct {
groups map[string]*TCPMuxGroup
// portManager is used to manage port
tcpMuxHTTPConnectMuxer *tcpmux.HTTPConnectTCPMuxer
mu sync.Mutex
}
// NewTCPMuxGroupCtl return a new TCPMuxGroupCtl
func NewTCPMuxGroupCtl(tcpMuxHTTPConnectMuxer *tcpmux.HTTPConnectTCPMuxer) *TCPMuxGroupCtl {
return &TCPMuxGroupCtl{
groups: make(map[string]*TCPMuxGroup),
tcpMuxHTTPConnectMuxer: tcpMuxHTTPConnectMuxer,
}
}
// Listen is the wrapper for TCPMuxGroup's Listen
// If there are no group, we will create one here
func (tmgc *TCPMuxGroupCtl) Listen(ctx context.Context, multiplexer string, group string, groupKey string,
domain string) (l net.Listener, err error) {
tmgc.mu.Lock()
tcpMuxGroup, ok := tmgc.groups[group]
if !ok {
tcpMuxGroup = NewTCPMuxGroup(tmgc)
tmgc.groups[group] = tcpMuxGroup
}
tmgc.mu.Unlock()
switch multiplexer {
case consts.HTTPConnectTCPMultiplexer:
return tcpMuxGroup.HTTPConnectListen(ctx, group, groupKey, domain)
default:
err = fmt.Errorf("unknown multiplexer [%s]", multiplexer)
return
}
}
// RemoveGroup remove TCPMuxGroup from controller
func (tmgc *TCPMuxGroupCtl) RemoveGroup(group string) {
tmgc.mu.Lock()
defer tmgc.mu.Unlock()
delete(tmgc.groups, group)
}
// TCPMuxGroup route connections to different proxies
type TCPMuxGroup struct {
group string
groupKey string
domain string
acceptCh chan net.Conn
index uint64
tcpMuxLn net.Listener
lns []*TCPMuxGroupListener
ctl *TCPMuxGroupCtl
mu sync.Mutex
}
// NewTCPMuxGroup return a new TCPMuxGroup
func NewTCPMuxGroup(ctl *TCPMuxGroupCtl) *TCPMuxGroup {
return &TCPMuxGroup{
lns: make([]*TCPMuxGroupListener, 0),
ctl: ctl,
acceptCh: make(chan net.Conn),
}
}
// Listen will return a new TCPMuxGroupListener
// if TCPMuxGroup already has a listener, just add a new TCPMuxGroupListener to the queues
// otherwise, listen on the real address
func (tmg *TCPMuxGroup) HTTPConnectListen(ctx context.Context, group string, groupKey string, domain string) (ln *TCPMuxGroupListener, err error) {
tmg.mu.Lock()
defer tmg.mu.Unlock()
if len(tmg.lns) == 0 {
// the first listener, listen on the real address
routeConfig := &vhost.RouteConfig{
Domain: domain,
}
tcpMuxLn, errRet := tmg.ctl.tcpMuxHTTPConnectMuxer.Listen(ctx, routeConfig)
if errRet != nil {
return nil, errRet
}
ln = newTCPMuxGroupListener(group, tmg, tcpMuxLn.Addr())
tmg.group = group
tmg.groupKey = groupKey
tmg.domain = domain
tmg.tcpMuxLn = tcpMuxLn
tmg.lns = append(tmg.lns, ln)
if tmg.acceptCh == nil {
tmg.acceptCh = make(chan net.Conn)
}
go tmg.worker()
} else {
// domain in the same group must be equal
if tmg.group != group || tmg.domain != domain {
return nil, ErrGroupParamsInvalid
}
if tmg.groupKey != groupKey {
return nil, ErrGroupAuthFailed
}
ln = newTCPMuxGroupListener(group, tmg, tmg.lns[0].Addr())
tmg.lns = append(tmg.lns, ln)
}
return
}
// worker is called when the real TCP listener has been created
func (tmg *TCPMuxGroup) worker() {
for {
c, err := tmg.tcpMuxLn.Accept()
if err != nil {
return
}
err = gerr.PanicToError(func() {
tmg.acceptCh <- c
})
if err != nil {
return
}
}
}
func (tmg *TCPMuxGroup) Accept() <-chan net.Conn {
return tmg.acceptCh
}
// CloseListener remove the TCPMuxGroupListener from the TCPMuxGroup
func (tmg *TCPMuxGroup) CloseListener(ln *TCPMuxGroupListener) {
tmg.mu.Lock()
defer tmg.mu.Unlock()
for i, tmpLn := range tmg.lns {
if tmpLn == ln {
tmg.lns = append(tmg.lns[:i], tmg.lns[i+1:]...)
break
}
}
if len(tmg.lns) == 0 {
close(tmg.acceptCh)
tmg.tcpMuxLn.Close()
tmg.ctl.RemoveGroup(tmg.group)
}
}
// TCPMuxGroupListener
type TCPMuxGroupListener struct {
groupName string
group *TCPMuxGroup
addr net.Addr
closeCh chan struct{}
}
func newTCPMuxGroupListener(name string, group *TCPMuxGroup, addr net.Addr) *TCPMuxGroupListener {
return &TCPMuxGroupListener{
groupName: name,
group: group,
addr: addr,
closeCh: make(chan struct{}),
}
}
// Accept will accept connections from TCPMuxGroup
func (ln *TCPMuxGroupListener) Accept() (c net.Conn, err error) {
var ok bool
select {
case <-ln.closeCh:
return nil, ErrListenerClosed
case c, ok = <-ln.group.Accept():
if !ok {
return nil, ErrListenerClosed
}
return c, nil
}
}
func (ln *TCPMuxGroupListener) Addr() net.Addr {
return ln.addr
}
// Close close the listener
func (ln *TCPMuxGroupListener) Close() (err error) {
close(ln.closeCh)
// remove self from TcpMuxGroup
ln.group.CloseListener(ln)
return
}

View File

@@ -29,7 +29,7 @@ type PortCtx struct {
UpdateTime time.Time
}
type PortManager struct {
type Manager struct {
reservedPorts map[string]*PortCtx
usedPorts map[int]*PortCtx
freePorts map[int]struct{}
@@ -39,8 +39,8 @@ type PortManager struct {
mu sync.Mutex
}
func NewPortManager(netType string, bindAddr string, allowPorts map[int]struct{}) *PortManager {
pm := &PortManager{
func NewManager(netType string, bindAddr string, allowPorts map[int]struct{}) *Manager {
pm := &Manager{
reservedPorts: make(map[string]*PortCtx),
usedPorts: make(map[int]*PortCtx),
freePorts: make(map[int]struct{}),
@@ -48,7 +48,7 @@ func NewPortManager(netType string, bindAddr string, allowPorts map[int]struct{}
netType: netType,
}
if len(allowPorts) > 0 {
for port, _ := range allowPorts {
for port := range allowPorts {
pm.freePorts[port] = struct{}{}
}
} else {
@@ -60,7 +60,7 @@ func NewPortManager(netType string, bindAddr string, allowPorts map[int]struct{}
return pm
}
func (pm *PortManager) Acquire(name string, port int) (realPort int, err error) {
func (pm *Manager) Acquire(name string, port int) (realPort int, err error) {
portCtx := &PortCtx{
ProxyName: name,
Closed: false,
@@ -94,7 +94,7 @@ func (pm *PortManager) Acquire(name string, port int) (realPort int, err error)
// get random port
count := 0
maxTryTimes := 5
for k, _ := range pm.freePorts {
for k := range pm.freePorts {
count++
if count > maxTryTimes {
break
@@ -132,7 +132,7 @@ func (pm *PortManager) Acquire(name string, port int) (realPort int, err error)
return
}
func (pm *PortManager) isPortAvailable(port int) bool {
func (pm *Manager) isPortAvailable(port int) bool {
if pm.netType == "udp" {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pm.bindAddr, port))
if err != nil {
@@ -144,17 +144,17 @@ func (pm *PortManager) isPortAvailable(port int) bool {
}
l.Close()
return true
} else {
l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port))
if err != nil {
return false
}
l.Close()
return true
}
l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port))
if err != nil {
return false
}
l.Close()
return true
}
func (pm *PortManager) Release(port int) {
func (pm *Manager) Release(port int) {
pm.mu.Lock()
defer pm.mu.Unlock()
if ctx, ok := pm.usedPorts[port]; ok {
@@ -166,7 +166,7 @@ func (pm *PortManager) Release(port int) {
}
// Release reserved port if it isn't used in last 24 hours.
func (pm *PortManager) cleanReservedPortsWorker() {
func (pm *Manager) cleanReservedPortsWorker() {
for {
time.Sleep(CleanReservedPortsInterval)
pm.mu.Lock()

View File

@@ -28,20 +28,20 @@ import (
frpIo "github.com/fatedier/golib/io"
)
type HttpProxy struct {
type HTTPProxy struct {
*BaseProxy
cfg *config.HttpProxyConf
cfg *config.HTTPProxyConf
closeFuncs []func()
}
func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl
routeConfig := vhost.VhostRouteConfig{
routeConfig := vhost.RouteConfig{
RewriteHost: pxy.cfg.HostHeaderRewrite,
Headers: pxy.cfg.Headers,
Username: pxy.cfg.HttpUser,
Password: pxy.cfg.HttpPwd,
Username: pxy.cfg.HTTPUser,
Password: pxy.cfg.HTTPPwd,
CreateConnFn: pxy.GetRealConn,
}
@@ -80,15 +80,15 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
})
} else {
// no group
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
err = pxy.rc.HTTPReverseProxy.Register(routeConfig)
if err != nil {
return
}
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
pxy.rc.HTTPReverseProxy.UnRegister(tmpDomain, tmpLocation)
})
}
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpPort)))
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHTTPPort)))
xl.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
}
}
@@ -111,15 +111,15 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
pxy.rc.HTTPGroupCtl.UnRegister(pxy.name, pxy.cfg.Group, tmpDomain, tmpLocation)
})
} else {
err = pxy.rc.HttpReverseProxy.Register(routeConfig)
err = pxy.rc.HTTPReverseProxy.Register(routeConfig)
if err != nil {
return
}
pxy.closeFuncs = append(pxy.closeFuncs, func() {
pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
pxy.rc.HTTPReverseProxy.UnRegister(tmpDomain, tmpLocation)
})
}
addrs = append(addrs, util.CanonicalAddr(tmpDomain, pxy.serverCfg.VhostHttpPort))
addrs = append(addrs, util.CanonicalAddr(tmpDomain, pxy.serverCfg.VhostHTTPPort))
xl.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
}
@@ -128,11 +128,11 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
return
}
func (pxy *HttpProxy) GetConf() config.ProxyConf {
func (pxy *HTTPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err error) {
func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err error) {
xl := pxy.xl
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
if errRet != nil {
@@ -163,7 +163,7 @@ func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err err
return
}
func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
func (pxy *HTTPProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
name := pxy.GetName()
proxyType := pxy.GetConf().GetBaseInfo().ProxyType
metrics.Server.CloseConnection(name, proxyType)
@@ -171,7 +171,7 @@ func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
metrics.Server.AddTrafficOut(name, proxyType, totalRead)
}
func (pxy *HttpProxy) Close() {
func (pxy *HTTPProxy) Close() {
pxy.BaseProxy.Close()
for _, closeFn := range pxy.closeFuncs {
closeFn()

View File

@@ -22,14 +22,14 @@ import (
"github.com/fatedier/frp/utils/vhost"
)
type HttpsProxy struct {
type HTTPSProxy struct {
*BaseProxy
cfg *config.HttpsProxyConf
cfg *config.HTTPSProxyConf
}
func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl
routeConfig := &vhost.VhostRouteConfig{}
routeConfig := &vhost.RouteConfig{}
defer func() {
if err != nil {
@@ -43,37 +43,37 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
}
routeConfig.Domain = domain
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(pxy.ctx, routeConfig)
l, errRet := pxy.rc.VhostHTTPSMuxer.Listen(pxy.ctx, routeConfig)
if errRet != nil {
err = errRet
return
}
xl.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort))
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort))
}
if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(pxy.ctx, routeConfig)
l, errRet := pxy.rc.VhostHTTPSMuxer.Listen(pxy.ctx, routeConfig)
if errRet != nil {
err = errRet
return
}
xl.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort)))
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHTTPSPort)))
}
pxy.startListenHandler(pxy, HandleUserTcpConnection)
pxy.startListenHandler(pxy, HandleUserTCPConnection)
remoteAddr = strings.Join(addrs, ",")
return
}
func (pxy *HttpsProxy) GetConf() config.ProxyConf {
func (pxy *HTTPSProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpsProxy) Close() {
func (pxy *HTTPSProxy) Close() {
pxy.BaseProxy.Close()
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
plugin "github.com/fatedier/frp/models/plugin/server"
"github.com/fatedier/frp/server/controller"
"github.com/fatedier/frp/server/metrics"
frpNet "github.com/fatedier/frp/utils/net"
@@ -41,6 +42,8 @@ type Proxy interface {
GetConf() config.ProxyConf
GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error)
GetUsedPortsNum() int
GetResourceController() *controller.ResourceController
GetUserInfo() plugin.UserInfo
Close()
}
@@ -52,6 +55,7 @@ type BaseProxy struct {
poolCount int
getWorkConnFn GetWorkConnFn
serverCfg config.ServerCommonConf
userInfo plugin.UserInfo
mu sync.RWMutex
xl *xlog.Logger
@@ -70,6 +74,14 @@ func (pxy *BaseProxy) GetUsedPortsNum() int {
return pxy.usedPortsNum
}
func (pxy *BaseProxy) GetResourceController() *controller.ResourceController {
return pxy.rc
}
func (pxy *BaseProxy) GetUserInfo() plugin.UserInfo {
return pxy.userInfo
}
func (pxy *BaseProxy) Close() {
xl := xlog.FromContextSafe(pxy.ctx)
xl.Info("proxy closing")
@@ -90,7 +102,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn,
}
xl.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
xl.Spawn().AppendPrefix(pxy.GetName())
workConn = frpNet.NewContextConn(workConn, pxy.ctx)
workConn = frpNet.NewContextConn(pxy.ctx, workConn)
var (
srcAddr string
@@ -154,7 +166,7 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn,
}
}
func NewProxy(ctx context.Context, runId string, rc *controller.ResourceController, poolCount int,
func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.ResourceController, poolCount int,
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (pxy Proxy, err error) {
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseInfo().ProxyName)
@@ -167,42 +179,48 @@ func NewProxy(ctx context.Context, runId string, rc *controller.ResourceControll
serverCfg: serverCfg,
xl: xl,
ctx: xlog.NewContext(ctx, xl),
userInfo: userInfo,
}
switch cfg := pxyConf.(type) {
case *config.TcpProxyConf:
case *config.TCPProxyConf:
basePxy.usedPortsNum = 1
pxy = &TcpProxy{
pxy = &TCPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.TcpMuxProxyConf:
pxy = &TcpMuxProxy{
case *config.TCPMuxProxyConf:
pxy = &TCPMuxProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.HttpProxyConf:
pxy = &HttpProxy{
case *config.HTTPProxyConf:
pxy = &HTTPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.HttpsProxyConf:
pxy = &HttpsProxy{
case *config.HTTPSProxyConf:
pxy = &HTTPSProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.UdpProxyConf:
case *config.UDPProxyConf:
basePxy.usedPortsNum = 1
pxy = &UdpProxy{
pxy = &UDPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.StcpProxyConf:
pxy = &StcpProxy{
case *config.STCPProxyConf:
pxy = &STCPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.XtcpProxyConf:
pxy = &XtcpProxy{
case *config.XTCPProxyConf:
pxy = &XTCPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.SUDPProxyConf:
pxy = &SUDPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
@@ -212,12 +230,26 @@ func NewProxy(ctx context.Context, runId string, rc *controller.ResourceControll
return
}
// HandleUserTcpConnection is used for incoming tcp user connections.
// HandleUserTCPConnection is used for incoming user TCP connections.
// It can be used for tcp, http, https type.
func HandleUserTcpConnection(pxy Proxy, userConn net.Conn, serverCfg config.ServerCommonConf) {
func HandleUserTCPConnection(pxy Proxy, userConn net.Conn, serverCfg config.ServerCommonConf) {
xl := xlog.FromContextSafe(pxy.Context())
defer userConn.Close()
// server plugin hook
rc := pxy.GetResourceController()
content := &plugin.NewUserConnContent{
User: pxy.GetUserInfo(),
ProxyName: pxy.GetName(),
ProxyType: pxy.GetConf().GetBaseInfo().ProxyType,
RemoteAddr: userConn.RemoteAddr().String(),
}
_, err := rc.PluginManager.NewUserConn(content)
if err != nil {
xl.Warn("the user conn [%s] was rejected, err:%v", content.RemoteAddr, err)
return
}
// try all connections from the pool
workConn, err := pxy.GetWorkConnFromPool(userConn.RemoteAddr(), userConn.LocalAddr())
if err != nil {
@@ -251,20 +283,20 @@ func HandleUserTcpConnection(pxy Proxy, userConn net.Conn, serverCfg config.Serv
xl.Debug("join connections closed")
}
type ProxyManager struct {
type Manager struct {
// proxies indexed by proxy name
pxys map[string]Proxy
mu sync.RWMutex
}
func NewProxyManager() *ProxyManager {
return &ProxyManager{
func NewManager() *Manager {
return &Manager{
pxys: make(map[string]Proxy),
}
}
func (pm *ProxyManager) Add(name string, pxy Proxy) error {
func (pm *Manager) Add(name string, pxy Proxy) error {
pm.mu.Lock()
defer pm.mu.Unlock()
if _, ok := pm.pxys[name]; ok {
@@ -275,13 +307,13 @@ func (pm *ProxyManager) Add(name string, pxy Proxy) error {
return nil
}
func (pm *ProxyManager) Del(name string) {
func (pm *Manager) Del(name string) {
pm.mu.Lock()
defer pm.mu.Unlock()
delete(pm.pxys, name)
}
func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
func (pm *Manager) GetByName(name string) (pxy Proxy, ok bool) {
pm.mu.RLock()
defer pm.mu.RUnlock()
pxy, ok = pm.pxys[name]

View File

@@ -18,12 +18,12 @@ import (
"github.com/fatedier/frp/models/config"
)
type StcpProxy struct {
type STCPProxy struct {
*BaseProxy
cfg *config.StcpProxyConf
cfg *config.STCPProxyConf
}
func (pxy *StcpProxy) Run() (remoteAddr string, err error) {
func (pxy *STCPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
if errRet != nil {
@@ -33,15 +33,15 @@ func (pxy *StcpProxy) Run() (remoteAddr string, err error) {
pxy.listeners = append(pxy.listeners, listener)
xl.Info("stcp proxy custom listen success")
pxy.startListenHandler(pxy, HandleUserTcpConnection)
pxy.startListenHandler(pxy, HandleUserTCPConnection)
return
}
func (pxy *StcpProxy) GetConf() config.ProxyConf {
func (pxy *STCPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *StcpProxy) Close() {
func (pxy *STCPProxy) Close() {
pxy.BaseProxy.Close()
pxy.rc.VisitorManager.CloseListener(pxy.GetName())
}

48
server/proxy/sudp.go Normal file
View File

@@ -0,0 +1,48 @@
// Copyright 2019 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proxy
import (
"github.com/fatedier/frp/models/config"
)
type SUDPProxy struct {
*BaseProxy
cfg *config.SUDPProxyConf
}
func (pxy *SUDPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
if errRet != nil {
err = errRet
return
}
pxy.listeners = append(pxy.listeners, listener)
xl.Info("sudp proxy custom listen success")
pxy.startListenHandler(pxy, HandleUserTCPConnection)
return
}
func (pxy *SUDPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *SUDPProxy) Close() {
pxy.BaseProxy.Close()
pxy.rc.VisitorManager.CloseListener(pxy.GetName())
}

View File

@@ -21,17 +21,17 @@ import (
"github.com/fatedier/frp/models/config"
)
type TcpProxy struct {
type TCPProxy struct {
*BaseProxy
cfg *config.TcpProxyConf
cfg *config.TCPProxyConf
realPort int
}
func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl
if pxy.cfg.Group != "" {
l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
l, realPort, errRet := pxy.rc.TCPGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
if errRet != nil {
err = errRet
return
@@ -45,13 +45,13 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
pxy.listeners = append(pxy.listeners, l)
xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
} else {
pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
pxy.realPort, err = pxy.rc.TCPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
if err != nil {
return
}
defer func() {
if err != nil {
pxy.rc.TcpPortManager.Release(pxy.realPort)
pxy.rc.TCPPortManager.Release(pxy.realPort)
}
}()
listener, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
@@ -65,17 +65,17 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
pxy.cfg.RemotePort = pxy.realPort
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
pxy.startListenHandler(pxy, HandleUserTcpConnection)
pxy.startListenHandler(pxy, HandleUserTCPConnection)
return
}
func (pxy *TcpProxy) GetConf() config.ProxyConf {
func (pxy *TCPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *TcpProxy) Close() {
func (pxy *TCPProxy) Close() {
pxy.BaseProxy.Close()
if pxy.cfg.Group == "" {
pxy.rc.TcpPortManager.Release(pxy.realPort)
pxy.rc.TCPPortManager.Release(pxy.realPort)
}
}

View File

@@ -16,6 +16,7 @@ package proxy
import (
"fmt"
"net"
"strings"
"github.com/fatedier/frp/models/config"
@@ -24,27 +25,30 @@ import (
"github.com/fatedier/frp/utils/vhost"
)
type TcpMuxProxy struct {
type TCPMuxProxy struct {
*BaseProxy
cfg *config.TcpMuxProxyConf
realPort int
cfg *config.TCPMuxProxyConf
}
func (pxy *TcpMuxProxy) httpConnectListen(domain string, addrs []string) ([]string, error) {
routeConfig := &vhost.VhostRouteConfig{
Domain: domain,
func (pxy *TCPMuxProxy) httpConnectListen(domain string, addrs []string) (_ []string, err error) {
var l net.Listener
if pxy.cfg.Group != "" {
l, err = pxy.rc.TCPMuxGroupCtl.Listen(pxy.ctx, pxy.cfg.Multiplexer, pxy.cfg.Group, pxy.cfg.GroupKey, domain)
} else {
routeConfig := &vhost.RouteConfig{
Domain: domain,
}
l, err = pxy.rc.TCPMuxHTTPConnectMuxer.Listen(pxy.ctx, routeConfig)
}
l, err := pxy.rc.TcpMuxHttpConnectMuxer.Listen(pxy.ctx, routeConfig)
if err != nil {
return nil, err
}
pxy.xl.Info("tcpmux httpconnect multiplexer listens for host [%s]", routeConfig.Domain)
pxy.xl.Info("tcpmux httpconnect multiplexer listens for host [%s]", domain)
pxy.listeners = append(pxy.listeners, l)
return append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.TcpMuxHttpConnectPort)), nil
return append(addrs, util.CanonicalAddr(domain, pxy.serverCfg.TCPMuxHTTPConnectPort)), nil
}
func (pxy *TcpMuxProxy) httpConnectRun() (remoteAddr string, err error) {
func (pxy *TCPMuxProxy) httpConnectRun() (remoteAddr string, err error) {
addrs := make([]string, 0)
for _, domain := range pxy.cfg.CustomDomains {
if domain == "" {
@@ -64,14 +68,14 @@ func (pxy *TcpMuxProxy) httpConnectRun() (remoteAddr string, err error) {
}
}
pxy.startListenHandler(pxy, HandleUserTcpConnection)
pxy.startListenHandler(pxy, HandleUserTCPConnection)
remoteAddr = strings.Join(addrs, ",")
return remoteAddr, err
}
func (pxy *TcpMuxProxy) Run() (remoteAddr string, err error) {
func (pxy *TCPMuxProxy) Run() (remoteAddr string, err error) {
switch pxy.cfg.Multiplexer {
case consts.HttpConnectTcpMultiplexer:
case consts.HTTPConnectTCPMultiplexer:
remoteAddr, err = pxy.httpConnectRun()
default:
err = fmt.Errorf("unknown multiplexer [%s]", pxy.cfg.Multiplexer)
@@ -83,13 +87,10 @@ func (pxy *TcpMuxProxy) Run() (remoteAddr string, err error) {
return remoteAddr, err
}
func (pxy *TcpMuxProxy) GetConf() config.ProxyConf {
func (pxy *TCPMuxProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *TcpMuxProxy) Close() {
func (pxy *TCPMuxProxy) Close() {
pxy.BaseProxy.Close()
if pxy.cfg.Group == "" {
pxy.rc.TcpPortManager.Release(pxy.realPort)
}
}

View File

@@ -17,6 +17,7 @@ package proxy
import (
"context"
"fmt"
"io"
"net"
"time"
@@ -24,13 +25,15 @@ import (
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/proto/udp"
"github.com/fatedier/frp/server/metrics"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/golib/errors"
frpIo "github.com/fatedier/golib/io"
)
type UdpProxy struct {
type UDPProxy struct {
*BaseProxy
cfg *config.UdpProxyConf
cfg *config.UDPProxyConf
realPort int
@@ -42,10 +45,10 @@ type UdpProxy struct {
workConn net.Conn
// sendCh is used for sending packages to workConn
sendCh chan *msg.UdpPacket
sendCh chan *msg.UDPPacket
// readCh is used for reading packages from workConn
readCh chan *msg.UdpPacket
readCh chan *msg.UDPPacket
// checkCloseCh is used for watching if workConn is closed
checkCloseCh chan int
@@ -53,15 +56,15 @@ type UdpProxy struct {
isClosed bool
}
func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl
pxy.realPort, err = pxy.rc.UdpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
pxy.realPort, err = pxy.rc.UDPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
if err != nil {
return
}
defer func() {
if err != nil {
pxy.rc.UdpPortManager.Release(pxy.realPort)
pxy.rc.UDPPortManager.Release(pxy.realPort)
}
}()
@@ -81,8 +84,8 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
xl.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
pxy.udpConn = udpConn
pxy.sendCh = make(chan *msg.UdpPacket, 1024)
pxy.readCh = make(chan *msg.UdpPacket, 1024)
pxy.sendCh = make(chan *msg.UDPPacket, 1024)
pxy.readCh = make(chan *msg.UDPPacket, 1024)
pxy.checkCloseCh = make(chan int)
// read message from workConn, if it returns any error, notify proxy to start a new workConn
@@ -110,7 +113,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
case *msg.Ping:
xl.Trace("udp work conn get ping message")
continue
case *msg.UdpPacket:
case *msg.UDPPacket:
if errRet := errors.PanicToError(func() {
xl.Trace("get udp message from workConn: %s", m.Content)
pxy.readCh <- m
@@ -142,15 +145,14 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
xl.Info("sender goroutine for udp work connection closed: %v", errRet)
conn.Close()
return
} else {
xl.Trace("send message to udp workConn: %s", udpMsg.Content)
metrics.Server.AddTrafficIn(
pxy.GetName(),
pxy.GetConf().GetBaseInfo().ProxyType,
int64(len(udpMsg.Content)),
)
continue
}
xl.Trace("send message to udp workConn: %s", udpMsg.Content)
metrics.Server.AddTrafficIn(
pxy.GetName(),
pxy.GetConf().GetBaseInfo().ProxyType,
int64(len(udpMsg.Content)),
)
continue
case <-ctx.Done():
xl.Info("sender goroutine for udp work connection closed")
return
@@ -175,14 +177,28 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
}
continue
}
// close the old workConn and replac it with a new one
// close the old workConn and replace it with a new one
if pxy.workConn != nil {
pxy.workConn.Close()
}
pxy.workConn = workConn
var rwc io.ReadWriteCloser = workConn
if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
if err != nil {
xl.Error("create encryption stream error: %v", err)
workConn.Close()
continue
}
}
if pxy.cfg.UseCompression {
rwc = frpIo.WithCompression(rwc)
}
pxy.workConn = frpNet.WrapReadWriteCloserToConn(rwc, workConn)
ctx, cancel := context.WithCancel(context.Background())
go workConnReaderFn(workConn)
go workConnSenderFn(workConn, ctx)
go workConnReaderFn(pxy.workConn)
go workConnSenderFn(pxy.workConn, ctx)
_, ok := <-pxy.checkCloseCh
cancel()
if !ok {
@@ -196,17 +212,17 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
// Response will be wrapped to be forwarded by work connection to server.
// Close readCh and sendCh at the end.
go func() {
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh)
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh, int(pxy.serverCfg.UDPPacketSize))
pxy.Close()
}()
return remoteAddr, nil
}
func (pxy *UdpProxy) GetConf() config.ProxyConf {
func (pxy *UDPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *UdpProxy) Close() {
func (pxy *UDPProxy) Close() {
pxy.mu.Lock()
defer pxy.mu.Unlock()
if !pxy.isClosed {
@@ -223,5 +239,5 @@ func (pxy *UdpProxy) Close() {
close(pxy.readCh)
close(pxy.sendCh)
}
pxy.rc.UdpPortManager.Release(pxy.realPort)
pxy.rc.UDPPortManager.Release(pxy.realPort)
}

View File

@@ -23,14 +23,14 @@ import (
"github.com/fatedier/golib/errors"
)
type XtcpProxy struct {
type XTCPProxy struct {
*BaseProxy
cfg *config.XtcpProxyConf
cfg *config.XTCPProxyConf
closeCh chan struct{}
}
func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
func (pxy *XTCPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl
if pxy.rc.NatHoleController == nil {
@@ -84,11 +84,11 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
return
}
func (pxy *XtcpProxy) GetConf() config.ProxyConf {
func (pxy *XTCPProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *XtcpProxy) Close() {
func (pxy *XTCPProxy) Close() {
pxy.BaseProxy.Close()
pxy.rc.NatHoleController.CloseClient(pxy.GetName())
errors.PanicToError(func() {

View File

@@ -17,16 +17,12 @@ package server
import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"net/http"
"sort"
"time"
"github.com/fatedier/frp/assets"
@@ -36,11 +32,13 @@ import (
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/nathole"
plugin "github.com/fatedier/frp/models/plugin/server"
"github.com/fatedier/frp/models/transport"
"github.com/fatedier/frp/server/controller"
"github.com/fatedier/frp/server/group"
"github.com/fatedier/frp/server/metrics"
"github.com/fatedier/frp/server/ports"
"github.com/fatedier/frp/server/proxy"
"github.com/fatedier/frp/server/visitor"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/tcpmux"
@@ -79,13 +77,13 @@ type Service struct {
ctlManager *ControlManager
// Manage all proxies
pxyManager *proxy.ProxyManager
pxyManager *proxy.Manager
// Manage all plugins
pluginManager *plugin.Manager
// HTTP vhost router
httpVhostRouter *vhost.VhostRouters
httpVhostRouter *vhost.Routers
// All resource managers and controllers
rc *controller.ResourceController
@@ -99,33 +97,68 @@ type Service struct {
}
func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
tlsConfig, err := transport.NewServerTLSConfig(
cfg.TLSCertFile,
cfg.TLSKeyFile,
cfg.TLSTrustedCaFile)
if err != nil {
return
}
svr = &Service{
ctlManager: NewControlManager(),
pxyManager: proxy.NewProxyManager(),
pxyManager: proxy.NewManager(),
pluginManager: plugin.NewManager(),
rc: &controller.ResourceController{
VisitorManager: controller.NewVisitorManager(),
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
VisitorManager: visitor.NewManager(),
TCPPortManager: ports.NewManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
UDPPortManager: ports.NewManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
},
httpVhostRouter: vhost.NewVhostRouters(),
authVerifier: auth.NewAuthVerifier(cfg.AuthServerConfig),
tlsConfig: generateTLSConfig(),
httpVhostRouter: vhost.NewRouters(),
authVerifier: auth.NewAuthVerifier(cfg.ServerConfig),
tlsConfig: tlsConfig,
cfg: cfg,
}
// Init all plugins
for name, options := range cfg.HTTPPlugins {
svr.pluginManager.Register(plugin.NewHTTPPluginOptions(options))
log.Info("plugin [%s] has been registered", name)
// 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))
if err != nil {
err = fmt.Errorf("Create server listener error, %v", err)
return
}
svr.rc.TCPMuxHTTPConnectMuxer, err = tcpmux.NewHTTPConnectTCPMuxer(l, vhostReadWriteTimeout)
if err != nil {
err = fmt.Errorf("Create vhost tcpMuxer error, %v", err)
return
}
log.Info("tcpmux httpconnect multiplexer listen on %s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort)
}
// Init all plugins
plugin_names := make([]string, 0, len(cfg.HTTPPlugins))
for n := range cfg.HTTPPlugins {
plugin_names = append(plugin_names, n)
}
sort.Strings(plugin_names)
for _, name := range plugin_names {
svr.pluginManager.Register(plugin.NewHTTPPluginOptions(cfg.HTTPPlugins[name]))
log.Info("plugin [%s] has been registered", name)
}
svr.rc.PluginManager = svr.pluginManager
// Init group controller
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
svr.rc.TCPGroupCtl = group.NewTCPGroupCtl(svr.rc.TCPPortManager)
// Init HTTP group controller
svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
// Init TCP mux group controller
svr.rc.TCPMuxGroupCtl = group.NewTCPMuxGroupCtl(svr.rc.TCPMuxHTTPConnectMuxer)
// Init 404 not found page
vhost.NotFoundPagePath = cfg.Custom404Page
@@ -134,10 +167,10 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
httpsMuxOn bool
)
if cfg.BindAddr == cfg.ProxyBindAddr {
if cfg.BindPort == cfg.VhostHttpPort {
if cfg.BindPort == cfg.VhostHTTPPort {
httpMuxOn = true
}
if cfg.BindPort == cfg.VhostHttpsPort {
if cfg.BindPort == cfg.VhostHTTPSPort {
httpsMuxOn = true
}
}
@@ -157,13 +190,13 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
// Listen for accepting connections from client using kcp protocol.
if cfg.KcpBindPort > 0 {
svr.kcpListener, err = frpNet.ListenKcp(cfg.BindAddr, cfg.KcpBindPort)
if cfg.KCPBindPort > 0 {
svr.kcpListener, err = frpNet.ListenKcp(cfg.BindAddr, cfg.KCPBindPort)
if err != nil {
err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", cfg.BindAddr, cfg.KcpBindPort, err)
err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", cfg.BindAddr, cfg.KCPBindPort, err)
return
}
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KcpBindPort)
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KCPBindPort)
}
// Listen for accepting connections from client using websocket protocol.
@@ -174,13 +207,13 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
svr.websocketListener = frpNet.NewWebsocketListener(websocketLn)
// Create http vhost muxer.
if cfg.VhostHttpPort > 0 {
rp := vhost.NewHttpReverseProxy(vhost.HttpReverseProxyOptions{
ResponseHeaderTimeoutS: cfg.VhostHttpTimeout,
if cfg.VhostHTTPPort > 0 {
rp := vhost.NewHTTPReverseProxy(vhost.HTTPReverseProxyOptions{
ResponseHeaderTimeoutS: cfg.VhostHTTPTimeout,
}, svr.httpVhostRouter)
svr.rc.HttpReverseProxy = rp
svr.rc.HTTPReverseProxy = rp
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHTTPPort)
server := &http.Server{
Addr: address,
Handler: rp,
@@ -196,63 +229,46 @@ 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:%d", cfg.ProxyBindAddr, cfg.VhostHTTPPort)
}
// Create https vhost muxer.
if cfg.VhostHttpsPort > 0 {
if cfg.VhostHTTPSPort > 0 {
var l net.Listener
if httpsMuxOn {
l = svr.muxer.ListenHttps(1)
} else {
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort))
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHTTPSPort))
if err != nil {
err = fmt.Errorf("Create server listener error, %v", err)
return
}
}
svr.rc.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(l, vhostReadWriteTimeout)
svr.rc.VhostHTTPSMuxer, err = vhost.NewHTTPSMuxer(l, vhostReadWriteTimeout)
if err != nil {
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
return
}
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
}
// 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))
if err != nil {
err = fmt.Errorf("Create server listener error, %v", err)
return
}
svr.rc.TcpMuxHttpConnectMuxer, err = tcpmux.NewHttpConnectTcpMuxer(l, vhostReadWriteTimeout)
if err != nil {
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("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHTTPSPort)
}
// frp tls listener
svr.tlsListener = svr.muxer.Listen(1, 1, func(data []byte) bool {
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
return int(data[0]) == frpNet.FRPTLSHeadByte
})
// Create nat hole controller.
if cfg.BindUdpPort > 0 {
var nc *nathole.NatHoleController
addr := fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindUdpPort)
nc, err = nathole.NewNatHoleController(addr)
if cfg.BindUDPPort > 0 {
var nc *nathole.Controller
addr := fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindUDPPort)
nc, err = nathole.NewController(addr)
if err != nil {
err = fmt.Errorf("Create nat hole controller error, %v", err)
return
}
svr.rc.NatHoleController = nc
log.Info("nat hole udp service listen on %s:%d", cfg.BindAddr, cfg.BindUdpPort)
log.Info("nat hole udp service listen on %s:%d", cfg.BindAddr, cfg.BindUDPPort)
}
var statsEnable bool
@@ -286,7 +302,7 @@ func (svr *Service) Run() {
if svr.rc.NatHoleController != nil {
go svr.rc.NatHoleController.Run()
}
if svr.cfg.KcpBindPort > 0 {
if svr.cfg.KCPBindPort > 0 {
go svr.HandleListener(svr.kcpListener)
}
@@ -296,6 +312,68 @@ func (svr *Service) Run() {
svr.HandleListener(svr.listener)
}
func (svr *Service) handleConnection(ctx context.Context, conn net.Conn) {
xl := xlog.FromContextSafe(ctx)
var (
rawMsg msg.Message
err error
)
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
if rawMsg, err = msg.ReadMsg(conn); err != nil {
log.Trace("Failed to read message: %v", err)
conn.Close()
return
}
conn.SetReadDeadline(time.Time{})
switch m := rawMsg.(type) {
case *msg.Login:
// server plugin hook
content := &plugin.LoginContent{
Login: *m,
}
retContent, err := svr.pluginManager.Login(content)
if err == nil {
m = &retContent.Login
err = svr.RegisterControl(conn, m)
}
// If login failed, send error message there.
// Otherwise send success message in control's work goroutine.
if err != nil {
xl.Warn("register control error: %v", err)
msg.WriteMsg(conn, &msg.LoginResp{
Version: version.Full(),
Error: util.GenerateResponseErrorString("register control error", err, svr.cfg.DetailedErrorsToClient),
})
conn.Close()
}
case *msg.NewWorkConn:
if err := svr.RegisterWorkConn(conn, m); err != nil {
conn.Close()
}
case *msg.NewVisitorConn:
if err = svr.RegisterVisitorConn(conn, m); err != nil {
xl.Warn("register visitor conn error: %v", err)
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
ProxyName: m.ProxyName,
Error: util.GenerateResponseErrorString("register visitor conn error", err, svr.cfg.DetailedErrorsToClient),
})
conn.Close()
} else {
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
ProxyName: m.ProxyName,
Error: "",
})
}
default:
log.Warn("Error message type for the new connection [%s]", conn.RemoteAddr().String())
conn.Close()
}
}
func (svr *Service) HandleListener(l net.Listener) {
// Listen for incoming connections from client.
for {
@@ -306,11 +384,13 @@ func (svr *Service) HandleListener(l net.Listener) {
}
// inject xlog object into net.Conn context
xl := xlog.New()
c = frpNet.NewContextConn(c, xlog.NewContext(context.Background(), xl))
ctx := context.Background()
c = frpNet.NewContextConn(xlog.NewContext(ctx, xl), c)
log.Trace("start check TLS connection...")
originConn := c
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TlsOnly, connReadTimeout)
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TLSOnly, connReadTimeout)
if err != nil {
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
originConn.Close()
@@ -319,64 +399,8 @@ func (svr *Service) HandleListener(l net.Listener) {
log.Trace("success check TLS connection")
// Start a new goroutine for dealing connections.
go func(frpConn net.Conn) {
dealFn := func(conn net.Conn) {
var rawMsg msg.Message
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
if rawMsg, err = msg.ReadMsg(conn); err != nil {
log.Trace("Failed to read message: %v", err)
conn.Close()
return
}
conn.SetReadDeadline(time.Time{})
switch m := rawMsg.(type) {
case *msg.Login:
// server plugin hook
content := &plugin.LoginContent{
Login: *m,
}
retContent, err := svr.pluginManager.Login(content)
if err == nil {
m = &retContent.Login
err = svr.RegisterControl(conn, m)
}
// If login failed, send error message there.
// Otherwise send success message in control's work goroutine.
if err != nil {
xl.Warn("register control error: %v", err)
msg.WriteMsg(conn, &msg.LoginResp{
Version: version.Full(),
Error: util.GenerateResponseErrorString("register control error", err, svr.cfg.DetailedErrorsToClient),
})
conn.Close()
}
case *msg.NewWorkConn:
if err := svr.RegisterWorkConn(conn, m); err != nil {
conn.Close()
}
case *msg.NewVisitorConn:
if err = svr.RegisterVisitorConn(conn, m); err != nil {
xl.Warn("register visitor conn error: %v", err)
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
ProxyName: m.ProxyName,
Error: util.GenerateResponseErrorString("register visitor conn error", err, svr.cfg.DetailedErrorsToClient),
})
conn.Close()
} else {
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
ProxyName: m.ProxyName,
Error: "",
})
}
default:
log.Warn("Error message type for the new connection [%s]", conn.RemoteAddr().String())
conn.Close()
}
}
if svr.cfg.TcpMux {
go func(ctx context.Context, frpConn net.Conn) {
if svr.cfg.TCPMux {
fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 20 * time.Second
fmuxCfg.LogOutput = ioutil.Discard
@@ -394,20 +418,20 @@ func (svr *Service) HandleListener(l net.Listener) {
session.Close()
return
}
go dealFn(stream)
go svr.handleConnection(ctx, stream)
}
} else {
dealFn(frpConn)
svr.handleConnection(ctx, frpConn)
}
}(c)
}(ctx, c)
}
}
func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err error) {
// If client's RunId is empty, it's a new client, we just create a new controller.
// If client's RunID is empty, it's a new client, we just create a new controller.
// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
if loginMsg.RunId == "" {
loginMsg.RunId, err = util.RandId()
if loginMsg.RunID == "" {
loginMsg.RunID, err = util.RandID()
if err != nil {
return
}
@@ -415,7 +439,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err
ctx := frpNet.NewContextFromConn(ctlConn)
xl := xlog.FromContextSafe(ctx)
xl.AppendPrefix(loginMsg.RunId)
xl.AppendPrefix(loginMsg.RunID)
ctx = xlog.NewContext(ctx, xl)
xl.Info("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]",
ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
@@ -432,7 +456,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err
}
ctl := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, svr.authVerifier, ctlConn, loginMsg, svr.cfg)
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
if oldCtl := svr.ctlManager.Add(loginMsg.RunID, ctl); oldCtl != nil {
oldCtl.allShutdown.WaitDone()
}
@@ -444,7 +468,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err
go func() {
// block until control closed
ctl.WaitClosed()
svr.ctlManager.Del(loginMsg.RunId, ctl)
svr.ctlManager.Del(loginMsg.RunID, ctl)
}()
return
}
@@ -452,18 +476,32 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err
// RegisterWorkConn register a new work connection to control and proxies need it.
func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) error {
xl := frpNet.NewLogFromConn(workConn)
ctl, exist := svr.ctlManager.GetById(newMsg.RunId)
ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
if !exist {
xl.Warn("No client control found for run id [%s]", newMsg.RunId)
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunId)
xl.Warn("No client control found for run id [%s]", newMsg.RunID)
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
}
// Check auth.
if err := svr.authVerifier.VerifyNewWorkConn(newMsg); err != nil {
xl.Warn("Invalid authentication in NewWorkConn message on run id [%s]", newMsg.RunId)
// server plugin hook
content := &plugin.NewWorkConnContent{
User: plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.loginMsg.RunID,
},
NewWorkConn: *newMsg,
}
retContent, err := svr.pluginManager.NewWorkConn(content)
if err == nil {
newMsg = &retContent.NewWorkConn
// Check auth.
err = svr.authVerifier.VerifyNewWorkConn(newMsg)
}
if err != nil {
xl.Warn("invalid NewWorkConn with run id [%s]", newMsg.RunID)
msg.WriteMsg(workConn, &msg.StartWorkConn{
Error: "invalid authentication in NewWorkConn",
Error: util.GenerateResponseErrorString("invalid NewWorkConn", err, ctl.serverCfg.DetailedErrorsToClient),
})
return fmt.Errorf("invalid authentication in NewWorkConn message on run id [%s]", newMsg.RunId)
return fmt.Errorf("invalid NewWorkConn with run id [%s]", newMsg.RunID)
}
return ctl.RegisterWorkConn(workConn)
}
@@ -472,24 +510,3 @@ func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVis
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
newMsg.UseEncryption, newMsg.UseCompression)
}
// Setup a bare-bones TLS config for the server
func generateTLSConfig() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package controller
package visitor
import (
"fmt"
@@ -27,21 +27,21 @@ import (
)
// Manager for visitor listeners.
type VisitorManager struct {
type Manager struct {
visitorListeners map[string]*frpNet.CustomListener
skMap map[string]string
mu sync.RWMutex
}
func NewVisitorManager() *VisitorManager {
return &VisitorManager{
func NewManager() *Manager {
return &Manager{
visitorListeners: make(map[string]*frpNet.CustomListener),
skMap: make(map[string]string),
}
}
func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
func (vm *Manager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
vm.mu.Lock()
defer vm.mu.Unlock()
@@ -56,7 +56,7 @@ func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListen
return
}
func (vm *VisitorManager) NewConn(name string, conn net.Conn, timestamp int64, signKey string,
func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64, signKey string,
useEncryption bool, useCompression bool) (err error) {
vm.mu.RLock()
@@ -87,7 +87,7 @@ func (vm *VisitorManager) NewConn(name string, conn net.Conn, timestamp int64, s
return
}
func (vm *VisitorManager) CloseListener(name string) {
func (vm *Manager) CloseListener(name string) {
vm.mu.Lock()
defer vm.mu.Unlock()

198
test/e2e/basic/basic.go Normal file
View File

@@ -0,0 +1,198 @@
package basic
import (
"fmt"
"strings"
"time"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
. "github.com/onsi/ginkgo"
)
var connTimeout = 2 * time.Second
var _ = Describe("[Feature: Basic]", func() {
f := framework.NewDefaultFramework()
Describe("TCP && UDP", func() {
types := []string{"tcp", "udp"}
for _, t := range types {
proxyType := t
It(fmt.Sprintf("Expose a %s echo server", strings.ToUpper(proxyType)), func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
localPortName := ""
protocol := "tcp"
switch proxyType {
case "tcp":
localPortName = framework.TCPEchoServerPort
protocol = "tcp"
case "udp":
localPortName = framework.UDPEchoServerPort
protocol = "udp"
}
getProxyConf := func(proxyName string, portName string, extra string) string {
return fmt.Sprintf(`
[%s]
type = %s
local_port = {{ .%s }}
remote_port = {{ .%s }}
`+extra, proxyName, proxyType, localPortName, portName)
}
tests := []struct {
proxyName string
portName string
extraConfig string
}{
{
proxyName: "normal",
portName: framework.GenPortName("Normal"),
},
{
proxyName: "with-encryption",
portName: framework.GenPortName("WithEncryption"),
extraConfig: "use_encryption = true",
},
{
proxyName: "with-compression",
portName: framework.GenPortName("WithCompression"),
extraConfig: "use_compression = true",
},
{
proxyName: "with-encryption-and-compression",
portName: framework.GenPortName("WithEncryptionAndCompression"),
extraConfig: `
use_encryption = true
use_compression = true
`,
},
}
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
for _, test := range tests {
framework.ExpectRequest(protocol, f.UsedPorts[test.portName],
[]byte(consts.TestString), []byte(consts.TestString), connTimeout, test.proxyName)
}
})
}
})
Describe("STCP && SUDP", func() {
types := []string{"stcp", "sudp"}
for _, t := range types {
proxyType := t
It(fmt.Sprintf("Expose echo server with %s", strings.ToUpper(proxyType)), func() {
serverConf := consts.DefaultServerConfig
clientServerConf := consts.DefaultClientConfig
clientVisitorConf := consts.DefaultClientConfig
localPortName := ""
protocol := "tcp"
switch proxyType {
case "stcp":
localPortName = framework.TCPEchoServerPort
protocol = "tcp"
case "sudp":
localPortName = framework.UDPEchoServerPort
protocol = "udp"
}
correctSK := "abc"
wrongSK := "123"
getProxyServerConf := func(proxyName string, extra string) string {
return fmt.Sprintf(`
[%s]
type = %s
role = server
sk = %s
local_port = {{ .%s }}
`+extra, proxyName, proxyType, correctSK, localPortName)
}
getProxyVisitorConf := func(proxyName string, portName, visitorSK, extra string) string {
return fmt.Sprintf(`
[%s]
type = %s
role = visitor
server_name = %s
sk = %s
bind_port = {{ .%s }}
`+extra, proxyName, proxyType, proxyName, visitorSK, portName)
}
tests := []struct {
proxyName string
bindPortName string
visitorSK string
extraConfig string
expectError bool
}{
{
proxyName: "normal",
bindPortName: framework.GenPortName("Normal"),
visitorSK: correctSK,
},
{
proxyName: "with-encryption",
bindPortName: framework.GenPortName("WithEncryption"),
visitorSK: correctSK,
extraConfig: "use_encryption = true",
},
{
proxyName: "with-compression",
bindPortName: framework.GenPortName("WithCompression"),
visitorSK: correctSK,
extraConfig: "use_compression = true",
},
{
proxyName: "with-encryption-and-compression",
bindPortName: framework.GenPortName("WithEncryptionAndCompression"),
visitorSK: correctSK,
extraConfig: `
use_encryption = true
use_compression = true
`,
},
{
proxyName: "with-error-sk",
bindPortName: framework.GenPortName("WithErrorSK"),
visitorSK: wrongSK,
expectError: true,
},
}
// build all client config
for _, test := range tests {
clientServerConf += getProxyServerConf(test.proxyName, test.extraConfig) + "\n"
}
for _, test := range tests {
clientVisitorConf += getProxyVisitorConf(test.proxyName, test.bindPortName, test.visitorSK, test.extraConfig) + "\n"
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf})
for _, test := range tests {
expectResp := []byte(consts.TestString)
if test.expectError {
framework.ExpectRequestError(protocol, f.UsedPorts[test.bindPortName],
[]byte(consts.TestString), connTimeout, test.proxyName)
continue
}
framework.ExpectRequest(protocol, f.UsedPorts[test.bindPortName],
[]byte(consts.TestString), expectResp, connTimeout, test.proxyName)
}
})
}
})
})

View File

@@ -0,0 +1,115 @@
package basic
import (
"fmt"
"strings"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
. "github.com/onsi/ginkgo"
)
type generalTestConfigures struct {
server string
client string
expectError bool
}
func defineClientServerTest(desc string, f *framework.Framework, configures *generalTestConfigures) {
It(desc, func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
serverConf += fmt.Sprintf(`
%s
`, configures.server)
clientConf += fmt.Sprintf(`
%s
[tcp]
type = tcp
local_port = {{ .%s }}
remote_port = {{ .%s }}
[udp]
type = udp
local_port = {{ .%s }}
remote_port = {{ .%s }}
`, configures.client,
framework.TCPEchoServerPort, framework.GenPortName("TCP"),
framework.UDPEchoServerPort, framework.GenPortName("UDP"),
)
f.RunProcesses([]string{serverConf}, []string{clientConf})
if !configures.expectError {
framework.ExpectTCPRequest(f.UsedPorts[framework.GenPortName("TCP")],
[]byte(consts.TestString), []byte(consts.TestString), connTimeout, "tcp proxy")
framework.ExpectUDPRequest(f.UsedPorts[framework.GenPortName("UDP")],
[]byte(consts.TestString), []byte(consts.TestString), connTimeout, "udp proxy")
} else {
framework.ExpectTCPRequestError(f.UsedPorts[framework.GenPortName("TCP")],
[]byte(consts.TestString), connTimeout, "tcp proxy")
framework.ExpectUDPRequestError(f.UsedPorts[framework.GenPortName("UDP")],
[]byte(consts.TestString), connTimeout, "udp proxy")
}
})
}
var _ = Describe("[Feature: Client-Server]", func() {
f := framework.NewDefaultFramework()
Describe("Protocol", func() {
supportProtocols := []string{"tcp", "kcp", "websocket"}
for _, protocol := range supportProtocols {
configures := &generalTestConfigures{
server: fmt.Sprintf(`
kcp_bind_port = {{ .%s }}
protocol = %s"
`, consts.PortServerName, protocol),
client: "protocol = " + protocol,
}
defineClientServerTest(protocol, f, configures)
}
})
Describe("Authentication", func() {
defineClientServerTest("Token Correct", f, &generalTestConfigures{
server: "token = 123456",
client: "token = 123456",
})
defineClientServerTest("Token Incorrect", f, &generalTestConfigures{
server: "token = 123456",
client: "token = invalid",
expectError: true,
})
})
Describe("TLS", 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
`, protocol),
})
}
defineClientServerTest("enable tls_only, client with TLS", f, &generalTestConfigures{
server: "tls_only = true",
client: "tls_enable = true",
})
defineClientServerTest("enable tls_only, client without TLS", f, &generalTestConfigures{
server: "tls_only = true",
expectError: true,
})
})
})

62
test/e2e/e2e.go Normal file
View File

@@ -0,0 +1,62 @@
package e2e
import (
"testing"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/utils/log"
"github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/config"
"github.com/onsi/gomega"
)
var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
setupSuite()
return nil
}, func(data []byte) {
// Run on all Ginkgo nodes
setupSuitePerGinkgoNode()
})
var _ = ginkgo.SynchronizedAfterSuite(func() {
CleanupSuite()
}, func() {
AfterSuiteActions()
})
// RunE2ETests checks configuration parameters (specified through flags) and then runs
// E2E tests using the Ginkgo runner.
// If a "report directory" is specified, one or more JUnit test reports will be
// generated in this directory, and cluster logs will also be saved.
// This function is called on each Ginkgo node in parallel mode.
func RunE2ETests(t *testing.T) {
gomega.RegisterFailHandler(framework.Fail)
log.Info("Starting e2e run %q on Ginkgo node %d of total %d",
framework.RunID, config.GinkgoConfig.ParallelNode, config.GinkgoConfig.ParallelTotal)
ginkgo.RunSpecs(t, "frp e2e suite")
}
// setupSuite is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step.
// There are certain operations we only want to run once per overall test invocation
// (such as deleting old namespaces, or verifying that all system pods are running.
// Because of the way Ginkgo runs tests in parallel, we must use SynchronizedBeforeSuite
// to ensure that these operations only run on the first parallel Ginkgo node.
//
// This function takes two parameters: one function which runs on only the first Ginkgo node,
// returning an opaque byte array, and then a second function which runs on all Ginkgo nodes,
// accepting the byte array.
func setupSuite() {
// Run only on Ginkgo node 1
// TODO
}
// setupSuitePerGinkgoNode is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step.
// There are certain operations we only want to run once per overall test invocation on each Ginkgo node
// such as making some global variables accessible to all parallel executions
// Because of the way Ginkgo runs tests in parallel, we must use SynchronizedBeforeSuite
// Ref: https://onsi.github.io/ginkgo/#parallel-specs
func setupSuitePerGinkgoNode() {
// config.GinkgoConfig.ParallelNode
}

40
test/e2e/e2e_test.go Normal file
View File

@@ -0,0 +1,40 @@
package e2e
import (
"flag"
"fmt"
"os"
"testing"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/utils/log"
// test source
_ "github.com/fatedier/frp/test/e2e/basic"
_ "github.com/fatedier/frp/test/e2e/plugin"
_ "github.com/onsi/ginkgo"
)
// handleFlags sets up all flags and parses the command line.
func handleFlags() {
framework.RegisterCommonFlags(flag.CommandLine)
flag.Parse()
}
func TestMain(m *testing.M) {
// Register test flags, then parse flags.
handleFlags()
if err := framework.ValidateTestContext(&framework.TestContext); err != nil {
fmt.Println(err)
os.Exit(1)
}
log.InitLog("console", "", framework.TestContext.LogLevel, 0, true)
os.Exit(m.Run())
}
func TestE2E(t *testing.T) {
RunE2ETests(t)
}

35
test/e2e/examples.go Normal file
View File

@@ -0,0 +1,35 @@
package e2e
import (
"fmt"
"time"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
. "github.com/onsi/ginkgo"
)
var connTimeout = 2 * time.Second
var _ = Describe("[Feature: Example]", func() {
f := framework.NewDefaultFramework()
Describe("TCP", func() {
It("Expose a TCP echo server", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[tcp]
type = tcp
local_port = {{ .%s }}
remote_port = {{ .%s }}
`, framework.TCPEchoServerPort, framework.GenPortName("TCP"))
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.ExpectTCPRequest(f.UsedPorts[framework.GenPortName("TCP")], []byte(consts.TestString), []byte(consts.TestString), connTimeout)
})
})
})

View File

@@ -0,0 +1,59 @@
package framework
import (
"sync"
)
// CleanupActionHandle is an integer pointer type for handling cleanup action
type CleanupActionHandle *int
type cleanupFuncHandle struct {
actionHandle CleanupActionHandle
actionHook func()
}
var cleanupActionsLock sync.Mutex
var cleanupHookList = []cleanupFuncHandle{}
// AddCleanupAction installs a function that will be called in the event of the
// whole test being terminated. This allows arbitrary pieces of the overall
// test to hook into SynchronizedAfterSuite().
// The hooks are called in last-in-first-out order.
func AddCleanupAction(fn func()) CleanupActionHandle {
p := CleanupActionHandle(new(int))
cleanupActionsLock.Lock()
defer cleanupActionsLock.Unlock()
c := cleanupFuncHandle{actionHandle: p, actionHook: fn}
cleanupHookList = append([]cleanupFuncHandle{c}, cleanupHookList...)
return p
}
// RemoveCleanupAction removes a function that was installed by
// AddCleanupAction.
func RemoveCleanupAction(p CleanupActionHandle) {
cleanupActionsLock.Lock()
defer cleanupActionsLock.Unlock()
for i, item := range cleanupHookList {
if item.actionHandle == p {
cleanupHookList = append(cleanupHookList[:i], cleanupHookList[i+1:]...)
break
}
}
}
// RunCleanupActions runs all functions installed by AddCleanupAction. It does
// not remove them (see RemoveCleanupAction) but it does run unlocked, so they
// may remove themselves.
func RunCleanupActions() {
list := []func(){}
func() {
cleanupActionsLock.Lock()
defer cleanupActionsLock.Unlock()
for _, p := range cleanupHookList {
list = append(list, p.actionHook)
}
}()
// Run unlocked.
for _, fn := range list {
fn()
}
}

View File

@@ -0,0 +1,11 @@
package framework
type FRPClient struct {
port int
}
func (f *Framework) FRPClient(port int) *FRPClient {
return &FRPClient{
port: port,
}
}

View File

@@ -0,0 +1,21 @@
package consts
const (
TestString = "frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet."
)
const (
PortServerName = "PortServer"
)
const (
DefaultServerConfig = `
[common]
bind_port = {{ .PortServer }}
`
DefaultClientConfig = `
[common]
server_port = {{ .PortServer }}
`
)

View File

@@ -0,0 +1,55 @@
package framework
import (
"github.com/onsi/gomega"
)
// ExpectEqual expects the specified two are the same, otherwise an exception raises
func ExpectEqual(actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).To(gomega.Equal(extra), explain...)
}
// ExpectEqualValues expects the specified two are the same, it not strict about type
func ExpectEqualValues(actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).To(gomega.BeEquivalentTo(extra), explain...)
}
// ExpectNotEqual expects the specified two are not the same, otherwise an exception raises
func ExpectNotEqual(actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).NotTo(gomega.Equal(extra), explain...)
}
// ExpectError expects an error happens, otherwise an exception raises
func ExpectError(err error, explain ...interface{}) {
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred(), explain...)
}
// ExpectNoError checks if "err" is set, and if so, fails assertion while logging the error.
func ExpectNoError(err error, explain ...interface{}) {
ExpectNoErrorWithOffset(1, err, explain...)
}
// ExpectNoErrorWithOffset checks if "err" is set, and if so, fails assertion while logging the error at "offset" levels above its caller
// (for example, for call chain f -> g -> ExpectNoErrorWithOffset(1, ...) error would be logged for "f").
func ExpectNoErrorWithOffset(offset int, err error, explain ...interface{}) {
gomega.ExpectWithOffset(1+offset, err).NotTo(gomega.HaveOccurred(), explain...)
}
// ExpectConsistOf expects actual contains precisely the extra elements. The ordering of the elements does not matter.
func ExpectConsistOf(actual interface{}, extra interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).To(gomega.ConsistOf(extra), explain...)
}
// ExpectHaveKey expects the actual map has the key in the keyset
func ExpectHaveKey(actual interface{}, key interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).To(gomega.HaveKey(key), explain...)
}
// ExpectEmpty expects actual is empty
func ExpectEmpty(actual interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).To(gomega.BeEmpty(), explain...)
}
func ExpectTrue(actual interface{}, explain ...interface{}) {
gomega.ExpectWithOffset(1, actual).Should(gomega.BeTrue(), explain...)
}

View File

@@ -0,0 +1,187 @@
package framework
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"regexp"
"strings"
"text/template"
"github.com/fatedier/frp/test/e2e/pkg/port"
"github.com/fatedier/frp/test/e2e/pkg/process"
"github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/config"
)
type Options struct {
TotalParallelNode int
CurrentNodeIndex int
FromPortIndex int
ToPortIndex int
}
type Framework struct {
TempDirectory string
UsedPorts map[string]int
portAllocator *port.Allocator
// Multiple mock servers used for e2e testing.
mockServers *MockServers
// To make sure that this framework cleans up after itself, no matter what,
// we install a Cleanup action before each test and clear it after. If we
// should abort, the AfterSuite hook should run all Cleanup actions.
cleanupHandle CleanupActionHandle
// beforeEachStarted indicates that BeforeEach has started
beforeEachStarted bool
serverConfPaths []string
serverProcesses []*process.Process
clientConfPaths []string
clientProcesses []*process.Process
}
func NewDefaultFramework() *Framework {
options := Options{
TotalParallelNode: config.GinkgoConfig.ParallelTotal,
CurrentNodeIndex: config.GinkgoConfig.ParallelNode,
FromPortIndex: 20000,
ToPortIndex: 50000,
}
return NewFramework(options)
}
func NewFramework(opt Options) *Framework {
f := &Framework{
portAllocator: port.NewAllocator(opt.FromPortIndex, opt.ToPortIndex, opt.TotalParallelNode, opt.CurrentNodeIndex-1),
}
ginkgo.BeforeEach(f.BeforeEach)
ginkgo.AfterEach(f.AfterEach)
return f
}
// BeforeEach create a temp directory.
func (f *Framework) BeforeEach() {
f.beforeEachStarted = true
f.cleanupHandle = AddCleanupAction(f.AfterEach)
dir, err := ioutil.TempDir(os.TempDir(), "frpe2e-test-*")
ExpectNoError(err)
f.TempDirectory = dir
f.mockServers = NewMockServers(f.portAllocator)
if err := f.mockServers.Run(); err != nil {
Failf("%v", err)
}
}
func (f *Framework) AfterEach() {
if !f.beforeEachStarted {
return
}
RemoveCleanupAction(f.cleanupHandle)
// stop processor
for _, p := range f.serverProcesses {
p.Stop()
if TestContext.Debug {
fmt.Println(p.ErrorOutput())
fmt.Println(p.StdOutput())
}
}
for _, p := range f.clientProcesses {
p.Stop()
if TestContext.Debug {
fmt.Println(p.ErrorOutput())
fmt.Println(p.StdOutput())
}
}
f.serverProcesses = nil
f.clientProcesses = nil
// close mock servers
f.mockServers.Close()
// clean directory
os.RemoveAll(f.TempDirectory)
f.TempDirectory = ""
f.serverConfPaths = nil
f.clientConfPaths = nil
// release used ports
for _, port := range f.UsedPorts {
f.portAllocator.Release(port)
}
f.UsedPorts = nil
}
var portRegex = regexp.MustCompile(`{{ \.Port.*? }}`)
// RenderPortsTemplate render templates with ports.
//
// Local: {{ .Port1 }}
// Target: {{ .Port2 }}
//
// return rendered content and all allocated ports.
func (f *Framework) genPortsFromTemplates(templates []string) (ports map[string]int, err error) {
ports = make(map[string]int)
for _, t := range templates {
arrs := portRegex.FindAllString(t, -1)
for _, str := range arrs {
str = strings.TrimPrefix(str, "{{ .")
str = strings.TrimSuffix(str, " }}")
str = strings.TrimSpace(str)
ports[str] = 0
}
}
defer func() {
if err != nil {
for _, port := range ports {
f.portAllocator.Release(port)
}
}
}()
for name := range ports {
port := f.portAllocator.Get()
if port <= 0 {
return nil, fmt.Errorf("can't allocate port")
}
ports[name] = port
}
return
}
func (f *Framework) RenderTemplates(templates []string) (outs []string, ports map[string]int, err error) {
ports, err = f.genPortsFromTemplates(templates)
if err != nil {
return
}
params := f.mockServers.GetTemplateParams()
for name, port := range ports {
params[name] = port
}
for _, t := range templates {
tmpl, err := template.New("").Parse(t)
if err != nil {
return nil, nil, err
}
buffer := bytes.NewBuffer(nil)
if err = tmpl.Execute(buffer, params); err != nil {
return nil, nil, err
}
outs = append(outs, buffer.String())
}
return
}

View File

@@ -0,0 +1,80 @@
// Package ginkgowrapper wraps Ginkgo Fail and Skip functions to panic
// with structured data instead of a constant string.
package ginkgowrapper
import (
"bufio"
"bytes"
"regexp"
"runtime"
"runtime/debug"
"strings"
"github.com/onsi/ginkgo"
)
// FailurePanic is the value that will be panicked from Fail.
type FailurePanic struct {
Message string // The failure message passed to Fail
Filename string // The filename that is the source of the failure
Line int // The line number of the filename that is the source of the failure
FullStackTrace string // A full stack trace starting at the source of the failure
}
// String makes FailurePanic look like the old Ginkgo panic when printed.
func (FailurePanic) String() string { return ginkgo.GINKGO_PANIC }
// Fail wraps ginkgo.Fail so that it panics with more useful
// information about the failure. This function will panic with a
// FailurePanic.
func Fail(message string, callerSkip ...int) {
skip := 1
if len(callerSkip) > 0 {
skip += callerSkip[0]
}
_, file, line, _ := runtime.Caller(skip)
fp := FailurePanic{
Message: message,
Filename: file,
Line: line,
FullStackTrace: pruneStack(skip),
}
defer func() {
e := recover()
if e != nil {
panic(fp)
}
}()
ginkgo.Fail(message, skip)
}
// ginkgo adds a lot of test running infrastructure to the stack, so
// we filter those out
var stackSkipPattern = regexp.MustCompile(`onsi/ginkgo`)
func pruneStack(skip int) string {
skip += 2 // one for pruneStack and one for debug.Stack
stack := debug.Stack()
scanner := bufio.NewScanner(bytes.NewBuffer(stack))
var prunedStack []string
// skip the top of the stack
for i := 0; i < 2*skip+1; i++ {
scanner.Scan()
}
for scanner.Scan() {
if stackSkipPattern.Match(scanner.Bytes()) {
scanner.Scan() // these come in pairs
} else {
prunedStack = append(prunedStack, scanner.Text())
scanner.Scan() // these come in pairs
prunedStack = append(prunedStack, scanner.Text())
}
}
return strings.Join(prunedStack, "\n")
}

94
test/e2e/framework/log.go Normal file
View File

@@ -0,0 +1,94 @@
package framework
import (
"bytes"
"fmt"
"regexp"
"runtime/debug"
"time"
"github.com/onsi/ginkgo"
e2eginkgowrapper "github.com/fatedier/frp/test/e2e/framework/ginkgowrapper"
)
func nowStamp() string {
return time.Now().Format(time.StampMilli)
}
func log(level string, format string, args ...interface{}) {
fmt.Fprintf(ginkgo.GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...)
}
// Logf logs the info.
func Logf(format string, args ...interface{}) {
log("INFO", format, args...)
}
// Failf logs the fail info, including a stack trace.
func Failf(format string, args ...interface{}) {
FailfWithOffset(1, format, args...)
}
// FailfWithOffset calls "Fail" and logs the error with a stack trace that starts at "offset" levels above its caller
// (for example, for call chain f -> g -> FailfWithOffset(1, ...) error would be logged for "f").
func FailfWithOffset(offset int, format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
skip := offset + 1
log("FAIL", "%s\n\nFull Stack Trace\n%s", msg, PrunedStack(skip))
e2eginkgowrapper.Fail(nowStamp()+": "+msg, skip)
}
// Fail is a replacement for ginkgo.Fail which logs the problem as it occurs
// together with a stack trace and then calls ginkgowrapper.Fail.
func Fail(msg string, callerSkip ...int) {
skip := 1
if len(callerSkip) > 0 {
skip += callerSkip[0]
}
log("FAIL", "%s\n\nFull Stack Trace\n%s", msg, PrunedStack(skip))
e2eginkgowrapper.Fail(nowStamp()+": "+msg, skip)
}
var codeFilterRE = regexp.MustCompile(`/github.com/onsi/ginkgo/`)
// PrunedStack is a wrapper around debug.Stack() that removes information
// about the current goroutine and optionally skips some of the initial stack entries.
// With skip == 0, the returned stack will start with the caller of PruneStack.
// From the remaining entries it automatically filters out useless ones like
// entries coming from Ginkgo.
//
// This is a modified copy of PruneStack in https://github.com/onsi/ginkgo/blob/f90f37d87fa6b1dd9625e2b1e83c23ffae3de228/internal/codelocation/code_location.go#L25:
// - simplified API and thus renamed (calls debug.Stack() instead of taking a parameter)
// - source code filtering updated to be specific to Kubernetes
// - optimized to use bytes and in-place slice filtering from
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
func PrunedStack(skip int) []byte {
fullStackTrace := debug.Stack()
stack := bytes.Split(fullStackTrace, []byte("\n"))
// Ensure that the even entries are the method names and the
// the odd entries the source code information.
if len(stack) > 0 && bytes.HasPrefix(stack[0], []byte("goroutine ")) {
// Ignore "goroutine 29 [running]:" line.
stack = stack[1:]
}
// The "+2" is for skipping over:
// - runtime/debug.Stack()
// - PrunedStack()
skip += 2
if len(stack) > 2*skip {
stack = stack[2*skip:]
}
n := 0
for i := 0; i < len(stack)/2; i++ {
// We filter out based on the source code file name.
if !codeFilterRE.Match([]byte(stack[i*2+1])) {
stack[n] = stack[i*2]
stack[n+1] = stack[i*2+1]
n += 2
}
}
stack = stack[:n]
return bytes.Join(stack, []byte("\n"))
}

View File

@@ -0,0 +1,85 @@
package framework
import (
"fmt"
"os"
"github.com/fatedier/frp/test/e2e/mock/echoserver"
"github.com/fatedier/frp/test/e2e/pkg/port"
)
const (
TCPEchoServerPort = "TCPEchoServerPort"
UDPEchoServerPort = "UDPEchoServerPort"
UDSEchoServerAddr = "UDSEchoServerAddr"
)
type MockServers struct {
tcpEchoServer *echoserver.Server
udpEchoServer *echoserver.Server
udsEchoServer *echoserver.Server
}
func NewMockServers(portAllocator *port.Allocator) *MockServers {
s := &MockServers{}
tcpPort := portAllocator.Get()
udpPort := portAllocator.Get()
s.tcpEchoServer = echoserver.New(echoserver.Options{
Type: echoserver.TCP,
BindAddr: "127.0.0.1",
BindPort: int32(tcpPort),
RepeatNum: 1,
})
s.udpEchoServer = echoserver.New(echoserver.Options{
Type: echoserver.UDP,
BindAddr: "127.0.0.1",
BindPort: int32(udpPort),
RepeatNum: 1,
})
udsIndex := portAllocator.Get()
udsAddr := fmt.Sprintf("%s/frp_echo_server_%d.sock", os.TempDir(), udsIndex)
os.Remove(udsAddr)
s.udsEchoServer = echoserver.New(echoserver.Options{
Type: echoserver.Unix,
BindAddr: udsAddr,
RepeatNum: 1,
})
return s
}
func (m *MockServers) Run() error {
if err := m.tcpEchoServer.Run(); err != nil {
return err
}
if err := m.udpEchoServer.Run(); err != nil {
return err
}
if err := m.udsEchoServer.Run(); err != nil {
return err
}
return nil
}
func (m *MockServers) Close() {
m.tcpEchoServer.Close()
m.udpEchoServer.Close()
m.udsEchoServer.Close()
os.Remove(m.udsEchoServer.GetOptions().BindAddr)
}
func (m *MockServers) GetTemplateParams() map[string]interface{} {
ret := make(map[string]interface{})
ret[TCPEchoServerPort] = m.tcpEchoServer.GetOptions().BindPort
ret[UDPEchoServerPort] = m.udpEchoServer.GetOptions().BindPort
ret[UDSEchoServerAddr] = m.udsEchoServer.GetOptions().BindAddr
return ret
}
func (m *MockServers) GetParam(key string) interface{} {
params := m.GetTemplateParams()
if v, ok := params[key]; ok {
return v
}
return nil
}

View File

@@ -0,0 +1,59 @@
package framework
import (
"fmt"
"io/ioutil"
"path/filepath"
"time"
"github.com/fatedier/frp/test/e2e/pkg/process"
flog "github.com/fatedier/frp/utils/log"
)
func GenerateConfigFile(path string, content string) error {
return ioutil.WriteFile(path, []byte(content), 0666)
}
// RunProcesses run multiple processes from templates.
// The first template should always be frps.
func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) {
templates := make([]string, 0, len(serverTemplates)+len(clientTemplates))
for _, t := range serverTemplates {
templates = append(templates, t)
}
for _, t := range clientTemplates {
templates = append(templates, t)
}
outs, ports, err := f.RenderTemplates(templates)
ExpectNoError(err)
ExpectTrue(len(templates) > 0)
f.UsedPorts = ports
for i := range serverTemplates {
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
err = ioutil.WriteFile(path, []byte(outs[i]), 0666)
ExpectNoError(err)
flog.Trace("[%s] %s", path, outs[i])
p := process.New(TestContext.FRPServerPath, []string{"-c", path})
f.serverConfPaths = append(f.serverConfPaths, path)
f.serverProcesses = append(f.serverProcesses, p)
err = p.Start()
ExpectNoError(err)
time.Sleep(500 * time.Millisecond)
}
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)
ExpectNoError(err)
flog.Trace("[%s] %s", path, outs[index])
p := process.New(TestContext.FRPClientPath, []string{"-c", path})
f.clientConfPaths = append(f.clientConfPaths, path)
f.clientProcesses = append(f.clientProcesses, p)
err = p.Start()
ExpectNoError(err)
time.Sleep(500 * time.Millisecond)
}
}

View File

@@ -0,0 +1,51 @@
package framework
import (
"time"
"github.com/fatedier/frp/test/e2e/pkg/request"
)
func ExpectRequest(protocol string, port int, in, out []byte, timeout time.Duration, explain ...interface{}) {
switch protocol {
case "tcp":
ExpectTCPRequest(port, in, out, timeout, explain...)
case "udp":
ExpectUDPRequest(port, in, out, timeout, explain...)
default:
Failf("ExpectRequest not support protocol: %s", protocol)
}
}
func ExpectRequestError(protocol string, port int, in []byte, timeout time.Duration, explain ...interface{}) {
switch protocol {
case "tcp":
ExpectTCPRequestError(port, in, timeout, explain...)
case "udp":
ExpectUDPRequestError(port, in, timeout, explain...)
default:
Failf("ExpectRequestError not support protocol: %s", protocol)
}
}
func ExpectTCPRequest(port int, in, out []byte, timeout time.Duration, explain ...interface{}) {
res, err := request.SendTCPRequest(port, in, timeout)
ExpectNoError(err, explain...)
ExpectEqual(string(out), res, explain...)
}
func ExpectTCPRequestError(port int, in []byte, timeout time.Duration, explain ...interface{}) {
_, err := request.SendTCPRequest(port, in, timeout)
ExpectError(err, explain...)
}
func ExpectUDPRequest(port int, in, out []byte, timeout time.Duration, explain ...interface{}) {
res, err := request.SendUDPRequest(port, in, timeout)
ExpectNoError(err, explain...)
ExpectEqual(string(out), res, explain...)
}
func ExpectUDPRequestError(port int, in []byte, timeout time.Duration, explain ...interface{}) {
_, err := request.SendUDPRequest(port, in, timeout)
ExpectError(err, explain...)
}

View File

@@ -0,0 +1,52 @@
package framework
import (
"flag"
"fmt"
"os"
"github.com/onsi/ginkgo/config"
)
type TestContextType struct {
FRPClientPath string
FRPServerPath string
LogLevel string
Debug bool
}
var TestContext TestContextType
// RegisterCommonFlags registers flags common to all e2e test suites.
// The flag set can be flag.CommandLine (if desired) or a custom
// flag set that then gets passed to viperconfig.ViperizeFlags.
//
// The other Register*Flags methods below can be used to add more
// test-specific flags. However, those settings then get added
// regardless whether the test is actually in the test suite.
//
func RegisterCommonFlags(flags *flag.FlagSet) {
// Turn on EmitSpecProgress to get spec progress (especially on interrupt)
config.GinkgoConfig.EmitSpecProgress = true
// Randomize specs as well as suites
config.GinkgoConfig.RandomizeAllSpecs = true
flags.StringVar(&TestContext.FRPClientPath, "frpc-path", "../../bin/frpc", "The frp client binary to use.")
flags.StringVar(&TestContext.FRPServerPath, "frps-path", "../../bin/frps", "The frp server binary to use.")
flags.StringVar(&TestContext.LogLevel, "log-level", "debug", "Log level.")
flags.BoolVar(&TestContext.Debug, "debug", false, "Enable debug mode to print detail info.")
}
func ValidateTestContext(t *TestContextType) error {
if t.FRPClientPath == "" || t.FRPServerPath == "" {
return fmt.Errorf("frpc and frps binary path can't be empty")
}
if _, err := os.Stat(t.FRPClientPath); err != nil {
return fmt.Errorf("load frpc-path error: %v", err)
}
if _, err := os.Stat(t.FRPServerPath); err != nil {
return fmt.Errorf("load frps-path error: %v", err)
}
return nil
}

View File

@@ -0,0 +1,18 @@
package framework
import (
"github.com/google/uuid"
)
// RunID is a unique identifier of the e2e run.
// Beware that this ID is not the same for all tests in the e2e run, because each Ginkgo node creates it separately.
var RunID string
func init() {
uuid, _ := uuid.NewUUID()
RunID = uuid.String()
}
func GenPortName(name string) string {
return "Port" + name
}

View File

@@ -0,0 +1,111 @@
package echoserver
import (
"fmt"
"net"
"strings"
fnet "github.com/fatedier/frp/utils/net"
)
type ServerType string
const (
TCP ServerType = "tcp"
UDP ServerType = "udp"
Unix ServerType = "unix"
)
type Options struct {
Type ServerType
BindAddr string
BindPort int32
RepeatNum int
SpecifiedResponse string
}
type Server struct {
opt Options
l net.Listener
}
func New(opt Options) *Server {
if opt.Type == "" {
opt.Type = TCP
}
if opt.BindAddr == "" {
opt.BindAddr = "127.0.0.1"
}
if opt.RepeatNum <= 0 {
opt.RepeatNum = 1
}
return &Server{
opt: opt,
}
}
func (s *Server) GetOptions() Options {
return s.opt
}
func (s *Server) Run() error {
if err := s.initListener(); err != nil {
return err
}
go func() {
for {
c, err := s.l.Accept()
if err != nil {
return
}
go s.handle(c)
}
}()
return nil
}
func (s *Server) Close() error {
if s.l != nil {
return s.l.Close()
}
return nil
}
func (s *Server) initListener() (err error) {
switch s.opt.Type {
case TCP:
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.opt.BindAddr, s.opt.BindPort))
case UDP:
s.l, err = fnet.ListenUDP(s.opt.BindAddr, int(s.opt.BindPort))
case Unix:
s.l, err = net.Listen("unix", s.opt.BindAddr)
default:
return fmt.Errorf("unknown server type: %s", s.opt.Type)
}
if err != nil {
return
}
return nil
}
func (s *Server) handle(c net.Conn) {
defer c.Close()
buf := make([]byte, 2048)
for {
n, err := c.Read(buf)
if err != nil {
return
}
var response string
if len(s.opt.SpecifiedResponse) > 0 {
response = s.opt.SpecifiedResponse
} else {
response = strings.Repeat(string(buf[:n]), s.opt.RepeatNum)
}
c.Write([]byte(response))
}
}

57
test/e2e/pkg/port/port.go Normal file
View File

@@ -0,0 +1,57 @@
package port
import (
"fmt"
"net"
"k8s.io/apimachinery/pkg/util/sets"
)
type Allocator struct {
reserved sets.Int
used sets.Int
}
// NewAllocator return a port allocator for testing.
// Example: from: 10, to: 20, mod 4, index 1
// Reserved ports: 13, 17
func NewAllocator(from int, to int, mod int, index int) *Allocator {
pa := &Allocator{
reserved: sets.NewInt(),
used: sets.NewInt(),
}
for i := from; i <= to; i++ {
if i%mod == index {
pa.reserved.Insert(i)
}
}
return pa
}
func (pa *Allocator) Get() int {
for i := 0; i < 10; i++ {
port, _ := pa.reserved.PopAny()
if port == 0 {
return 0
}
// TODO: Distinguish between TCP and UDP
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
// Maybe not controlled by us, mark it used.
pa.used.Insert(port)
continue
}
l.Close()
pa.used.Insert(port)
return port
}
return 0
}
func (pa *Allocator) Release(port int) {
if pa.used.Has(port) {
pa.used.Delete(port)
pa.reserved.Insert(port)
}
}

View File

@@ -0,0 +1,54 @@
package process
import (
"bytes"
"context"
"os/exec"
)
type Process struct {
cmd *exec.Cmd
cancel context.CancelFunc
errorOutput *bytes.Buffer
stdOutput *bytes.Buffer
beforeStopHandler func()
}
func New(path string, params []string) *Process {
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, path, params...)
p := &Process{
cmd: cmd,
cancel: cancel,
}
p.errorOutput = bytes.NewBufferString("")
p.stdOutput = bytes.NewBufferString("")
cmd.Stderr = p.errorOutput
cmd.Stdout = p.stdOutput
return p
}
func (p *Process) Start() error {
return p.cmd.Start()
}
func (p *Process) Stop() error {
if p.beforeStopHandler != nil {
p.beforeStopHandler()
}
p.cancel()
return p.cmd.Wait()
}
func (p *Process) ErrorOutput() string {
return p.errorOutput.String()
}
func (p *Process) StdOutput() string {
return p.stdOutput.String()
}
func (p *Process) SetBeforeStopHandler(fn func()) {
p.beforeStopHandler = fn
}

View File

@@ -0,0 +1,40 @@
package request
import (
"fmt"
"net"
"time"
)
func SendTCPRequest(port int, content []byte, timeout time.Duration) (string, error) {
c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
return "", fmt.Errorf("connect to tcp server error: %v", err)
}
defer c.Close()
c.SetDeadline(time.Now().Add(timeout))
return sendRequestByConn(c, content)
}
func SendUDPRequest(port int, content []byte, timeout time.Duration) (string, error) {
c, err := net.Dial("udp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
return "", fmt.Errorf("connect to udp server error: %v", err)
}
defer c.Close()
c.SetDeadline(time.Now().Add(timeout))
return sendRequestByConn(c, content)
}
func sendRequestByConn(c net.Conn, content []byte) (string, error) {
c.Write(content)
buf := make([]byte, 2048)
n, err := c.Read(buf)
if err != nil {
return "", fmt.Errorf("read error: %v", err)
}
return string(buf[:n]), nil
}

View File

@@ -0,0 +1,76 @@
package plugin
import (
"fmt"
"time"
"github.com/fatedier/frp/test/e2e/framework"
"github.com/fatedier/frp/test/e2e/framework/consts"
. "github.com/onsi/ginkgo"
)
var connTimeout = 2 * time.Second
var _ = Describe("[Feature: Client-Plugins]", func() {
f := framework.NewDefaultFramework()
Describe("UnixDomainSocket", func() {
It("Expose a unix domain socket echo server", func() {
serverConf := consts.DefaultServerConfig
clientConf := consts.DefaultClientConfig
getProxyConf := func(proxyName string, portName string, extra string) string {
return fmt.Sprintf(`
[%s]
type = tcp
remote_port = {{ .%s }}
plugin = unix_domain_socket
plugin_unix_path = {{ .%s }}
`+extra, proxyName, portName, framework.UDSEchoServerAddr)
}
tests := []struct {
proxyName string
portName string
extraConfig string
}{
{
proxyName: "normal",
portName: framework.GenPortName("Normal"),
},
{
proxyName: "with-encryption",
portName: framework.GenPortName("WithEncryption"),
extraConfig: "use_encryption = true",
},
{
proxyName: "with-compression",
portName: framework.GenPortName("WithCompression"),
extraConfig: "use_compression = true",
},
{
proxyName: "with-encryption-and-compression",
portName: framework.GenPortName("WithEncryptionAndCompression"),
extraConfig: `
use_encryption = true
use_compression = true
`,
},
}
// build all client config
for _, test := range tests {
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
}
// run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientConf})
for _, test := range tests {
framework.ExpectTCPRequest(f.UsedPorts[test.portName],
[]byte(consts.TestString), []byte(consts.TestString),
connTimeout, test.proxyName)
}
})
})
})

16
test/e2e/suites.go Normal file
View File

@@ -0,0 +1,16 @@
package e2e
// CleanupSuite is the boilerplate that can be used after tests on ginkgo were run, on the SynchronizedAfterSuite step.
// Similar to SynchronizedBeforeSuite, we want to run some operations only once (such as collecting cluster logs).
// Here, the order of functions is reversed; first, the function which runs everywhere,
// and then the function that only runs on the first Ginkgo node.
func CleanupSuite() {
// Run on all Ginkgo nodes
// TODO
}
// AfterSuiteActions are actions that are run on ginkgo's SynchronizedAfterSuite
func AfterSuiteActions() {
// Run only Ginkgo on node 1
// TODO
}

View File

@@ -1,72 +0,0 @@
package ci
import (
"os"
"testing"
"time"
"github.com/fatedier/frp/tests/config"
"github.com/fatedier/frp/tests/consts"
"github.com/fatedier/frp/tests/util"
"github.com/stretchr/testify/assert"
)
const FRPS_TOKEN_TCP_CONF = `
[common]
bind_addr = 0.0.0.0
bind_port = 20000
log_file = console
log_level = debug
authentication_method = token
token = 123456
`
const FRPC_TOKEN_TCP_CONF = `
[common]
server_addr = 127.0.0.1
server_port = 20000
log_file = console
log_level = debug
authentication_method = token
token = 123456
protocol = tcp
[tcp]
type = tcp
local_port = 10701
remote_port = 20801
`
func TestTcpWithTokenAuthentication(t *testing.T) {
assert := assert.New(t)
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TOKEN_TCP_CONF)
if assert.NoError(err) {
defer os.Remove(frpsCfgPath)
}
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TOKEN_TCP_CONF)
if assert.NoError(err) {
defer os.Remove(frpcCfgPath)
}
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
err = frpsProcess.Start()
if assert.NoError(err) {
defer frpsProcess.Stop()
}
time.Sleep(200 * time.Millisecond)
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
err = frpcProcess.Start()
if assert.NoError(err) {
defer frpcProcess.Stop()
}
time.Sleep(500 * time.Millisecond)
// test tcp
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
assert.NoError(err)
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
}

Some files were not shown because too many files have changed in this diff Show More