Compare commits

...

20 Commits

Author SHA1 Message Date
fatedier
10f2620131 Merge pull request #2869 from fatedier/dev
bump version to v0.41.0
2022-03-23 21:19:59 +08:00
fatedier
4acae540c8 support go1.18 and remove go1.16 (#2868) 2022-03-23 21:15:01 +08:00
fatedier
11b13533a0 add release note (#2867) 2022-03-23 20:14:55 +08:00
fatedier
100d556336 support tcp keepalive params (#2863) 2022-03-22 19:29:30 +08:00
Blizard
452fe25cc6 feat: SUDP alway reconnect and print too much log when no data ready (#2844)
* feat: random sleep duration before reconnecting

* fix: bug
2022-03-17 12:03:20 +08:00
fatedier
63efa6b776 support pprof (#2849) 2022-03-17 11:42:59 +08:00
fatedier
37c27169ac workflows: update stale action (#2846) 2022-03-15 11:53:14 +08:00
fatedier
ce677820c6 Merge pull request #2834 from fatedier/dev
bump version
2022-03-11 19:51:32 +08:00
fatedier
1f88a7a0b8 bump version to v0.40.0 (#2833) 2022-03-11 19:45:34 +08:00
Johan Hernefeldt
eeea7602d9 bugfix: Issue #2831 - Cant connect to frps behind ingress with tls (#2832)
Co-authored-by: Johan Hernefeldt <johan.hernefeldt@moralis.io>
2022-03-11 14:51:47 +08:00
Harry Cheng
bf635c0e90 Notify server plugins when a proxy is closed (#2823)
* add close proxy op

* Move to actual closing routine

* Fix e2e tests for CloseProxy

* Add warning on resource exhaustion

* Add CloseProxy to manual close

* retuen errors to `CloseProxy` callers
2022-03-08 15:08:09 +08:00
Blizard
cd31359a27 feat: support add additional params for OIDC (#2814)
* feat: support add additional params and test access by auth0

* fix: config name

Co-authored-by: blizard863 <760076784@qq.com>
2022-03-07 14:23:49 +08:00
fatedier
19739ed31a random sleep duration before reconnecting (#2816) 2022-02-24 11:59:36 +08:00
fatedier
10100c28d9 client: add dial_server_timeout (#2805) 2022-02-19 16:49:21 +08:00
fatedier
88fcc079e8 Merge pull request #2792 from fatedier/dev
bump version
2022-02-09 16:11:20 +08:00
fatedier
ddc1e163c4 update README 2022-02-09 15:42:34 +08:00
fatedier
d20a6d3d75 update release note 2022-02-09 15:23:01 +08:00
fatedier
6194273615 use net.JoinHostPort instead of fmt.Sprintf (#2791) 2022-02-09 15:19:35 +08:00
fatedier
b2311e55e7 add new sponsor logo (#2785) 2022-01-28 15:29:43 +08:00
fatedier
07873d471f doc: update donation section (#2783) 2022-01-26 20:56:00 +08:00
49 changed files with 426 additions and 134 deletions

View File

@@ -2,14 +2,14 @@ version: 2
jobs: jobs:
go-version-latest: go-version-latest:
docker: docker:
- image: cimg/go:1.17-node - image: cimg/go:1.18-node
steps: steps:
- checkout - checkout
- run: make - run: make
- run: make alltest - run: make alltest
go-version-last: go-version-last:
docker: docker:
- image: cimg/go:1.16-node - image: cimg/go:1.17-node
steps: steps:
- checkout - checkout
- run: make - run: make

View File

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

View File

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

View File

@@ -12,11 +12,11 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v3 - uses: actions/stale@v5
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.' stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
stale-pr-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.' stale-pr-message: "PRs go stale after 30d of inactivity. Stale PRs rot after an additional 7d of inactivity and eventually close."
stale-issue-label: 'lifecycle/stale' stale-issue-label: 'lifecycle/stale'
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned' exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
stale-pr-label: 'lifecycle/stale' stale-pr-label: 'lifecycle/stale'
@@ -24,3 +24,5 @@ jobs:
days-before-stale: 30 days-before-stale: 30
days-before-close: 7 days-before-close: 7
debug-only: ${{ github.event.inputs.debug-only }} debug-only: ${{ github.event.inputs.debug-only }}
exempt-all-pr-milestones: true
exempt-all-pr-assignees: true

View File

@@ -1,7 +1,10 @@
builds: builds:
- skip: true - skip: true
checksum: checksum:
name_template: 'checksums.txt' name_template: '{{ .ProjectName }}_{{ .Version }}_sha256_checksums.txt'
algorithm: sha256
extra_files:
- glob: ./release/packages/*
release: release:
# Same as for github # Same as for github
# Note: it can only be one: either github, gitlab or gitea # Note: it can only be one: either github, gitlab or gitea

View File

@@ -6,12 +6,6 @@
[README](README.md) | [中文文档](README_zh.md) [README](README.md) | [中文文档](README_zh.md)
## What is frp?
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. As of now, it supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, where requests can be forwarded to internal services by domain name.
frp also has a P2P connect mode.
<h3 align="center">Platinum Sponsors</h3> <h3 align="center">Platinum Sponsors</h3>
<!--platinum sponsors start--> <!--platinum sponsors start-->
@@ -23,10 +17,27 @@ frp also has a P2P connect mode.
<!--platinum sponsors end--> <!--platinum sponsors end-->
<h3 align="center">Gold Sponsors</h3>
<!--gold sponsors start-->
<p align="center">
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
<img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
</a>
</p>
<!--gold sponsors end-->
<h3 align="center">Silver Sponsors</h3> <h3 align="center">Silver Sponsors</h3>
* Sakura Frp - 欢迎点击 "加入我们" * Sakura Frp - 欢迎点击 "加入我们"
## What is frp?
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. As of now, it supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, where requests can be forwarded to internal services by domain name.
frp also has a P2P connect mode.
## Table of Contents ## Table of Contents
<!-- vim-markdown-toc GFM --> <!-- vim-markdown-toc GFM -->
@@ -82,8 +93,7 @@ frp also has a P2P connect mode.
* [Development Plan](#development-plan) * [Development Plan](#development-plan)
* [Contributing](#contributing) * [Contributing](#contributing)
* [Donation](#donation) * [Donation](#donation)
* [AliPay](#alipay) * [GitHub Sponsors](#github-sponsors)
* [Wechat Pay](#wechat-pay)
* [PayPal](#paypal) * [PayPal](#paypal)
<!-- vim-markdown-toc --> <!-- vim-markdown-toc -->
@@ -1092,15 +1102,11 @@ Interested in getting involved? We would like to help you!
If frp helps you a lot, you can support us by: If frp helps you a lot, you can support us by:
frp QQ group: 606194980 ### GitHub Sponsors
### AliPay Support us by [Github Sponsors](https://github.com/sponsors/fatedier).
![donation-alipay](/doc/pic/donate-alipay.png) You can have your company's logo placed on README file of this project.
### Wechat Pay
![donation-wechatpay](/doc/pic/donate-wechatpay.png)
### PayPal ### PayPal

View File

@@ -18,6 +18,17 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
<!--platinum sponsors end--> <!--platinum sponsors end-->
<h3 align="center">Gold Sponsors</h3>
<!--gold sponsors start-->
<p align="center">
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
<img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
</a>
</p>
<!--gold sponsors end-->
<h3 align="center">Silver Sponsors</h3> <h3 align="center">Silver Sponsors</h3>
* Sakura Frp - 欢迎点击 "加入我们" * Sakura Frp - 欢迎点击 "加入我们"
@@ -65,6 +76,12 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。 如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。
### GitHub Sponsors
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
### 知识星球 ### 知识星球
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群: 如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
@@ -78,7 +95,3 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
### 微信支付捐赠 ### 微信支付捐赠
![donate-wechatpay](/doc/pic/donate-wechatpay.png) ![donate-wechatpay](/doc/pic/donate-wechatpay.png)
### Paypal 捐赠
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。

View File

@@ -1,12 +1,8 @@
### New ### New
* Added `connect_server_local_ip` in frpc to specify local IP connected to frps. * Support go http pprof.
* Added `tcp_mux_keepalive_interval` both in frpc and frps to set `tcp_mux` keepalive interval seconds if `tcp_mux` is enabled. After using this params, you can set `heartbeat_interval` to `-1` to disable application layer heartbeat to reduce traffic usage(Make sure frps is in the latest version).
### Improve ### Improve
* Server Plugin: Added `client_address` in Login Operation. * Change underlying TCP connection keepalive interval to 2 hours.
* Create new connection to server for `sudp` visitor when needed, to avoid frequent reconnections.
### Fix
* Remove authentication for healthz api.

View File

@@ -17,6 +17,7 @@ package client
import ( import (
"net" "net"
"net/http" "net/http"
"net/http/pprof"
"time" "time"
"github.com/fatedier/frp/assets" "github.com/fatedier/frp/assets"
@@ -26,8 +27,8 @@ import (
) )
var ( var (
httpServerReadTimeout = 10 * time.Second httpServerReadTimeout = 60 * time.Second
httpServerWriteTimeout = 10 * time.Second httpServerWriteTimeout = 60 * time.Second
) )
func (svr *Service) RunAdminServer(address string) (err error) { func (svr *Service) RunAdminServer(address string) (err error) {
@@ -36,6 +37,15 @@ func (svr *Service) RunAdminServer(address string) (err error) {
router.HandleFunc("/healthz", svr.healthz) router.HandleFunc("/healthz", svr.healthz)
// debug
if svr.cfg.PprofEnable {
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
}
subRouter := router.NewRoute().Subrouter() subRouter := router.NewRoute().Subrouter()
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware) subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)

View File

@@ -251,6 +251,8 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) {
} }
dialOptions = append(dialOptions, dialOptions = append(dialOptions,
libdial.WithProtocol(protocol), libdial.WithProtocol(protocol),
libdial.WithTimeout(time.Duration(ctl.clientCfg.DialServerTimeout)*time.Second),
libdial.WithKeepAlive(time.Duration(ctl.clientCfg.DialServerKeepAlive)*time.Second),
libdial.WithProxy(proxyType, addr), libdial.WithProxy(proxyType, addr),
libdial.WithProxyAuth(auth), libdial.WithProxyAuth(auth),
libdial.WithTLSConfig(tlsConfig), libdial.WithTLSConfig(tlsConfig),

View File

@@ -347,22 +347,18 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr) xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
// Send detect message // Send detect message
array := strings.Split(natHoleRespMsg.VisitorAddr, ":") host, portStr, err := net.SplitHostPort(natHoleRespMsg.VisitorAddr)
if len(array) <= 1 { if err != nil {
xl.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr) xl.Error("get NatHoleResp visitor address [%s] error: %v", natHoleRespMsg.VisitorAddr, err)
} }
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String()) laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
/*
for i := 1000; i < 65000; i++ { port, err := strconv.ParseInt(portStr, 10, 64)
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
}
*/
port, err := strconv.ParseInt(array[1], 10, 64)
if err != nil { if err != nil {
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr) xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
return return
} }
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid)) pxy.sendDetectMsg(host, int(port), laddr, []byte(natHoleRespMsg.Sid))
xl.Trace("send all detect msg done") xl.Trace("send all detect msg done")
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{}) msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
@@ -791,7 +787,10 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
return return
} }
localConn, err := libdial.Dial(net.JoinHostPort(localInfo.LocalIP, strconv.Itoa(localInfo.LocalPort))) localConn, err := libdial.Dial(
net.JoinHostPort(localInfo.LocalIP, strconv.Itoa(localInfo.LocalPort)),
libdial.WithTimeout(10*time.Second),
)
if err != nil { if err != nil {
workConn.Close() workConn.Close()
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err) xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err)

View File

@@ -17,7 +17,6 @@ package client
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@@ -34,6 +33,7 @@ import (
"github.com/fatedier/frp/pkg/transport" "github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/log" "github.com/fatedier/frp/pkg/util/log"
frpNet "github.com/fatedier/frp/pkg/util/net" frpNet "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/version" "github.com/fatedier/frp/pkg/util/version"
"github.com/fatedier/frp/pkg/util/xlog" "github.com/fatedier/frp/pkg/util/xlog"
libdial "github.com/fatedier/golib/net/dial" libdial "github.com/fatedier/golib/net/dial"
@@ -109,7 +109,7 @@ func (svr *Service) Run() error {
if svr.cfg.LoginFailExit { if svr.cfg.LoginFailExit {
return err return err
} }
time.Sleep(10 * time.Second) util.RandomSleep(10*time.Second, 0.9, 1.1)
} else { } else {
// login success // 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)
@@ -158,8 +158,11 @@ func (svr *Service) keepControllerWorking() {
// the first three retry with no delay // the first three retry with no delay
if reconnectCounts > 3 { if reconnectCounts > 3 {
time.Sleep(reconnectDelay) util.RandomSleep(reconnectDelay, 0.9, 1.1)
xl.Info("wait %v to reconnect", reconnectDelay)
reconnectDelay *= 2 reconnectDelay *= 2
} else {
util.RandomSleep(time.Second, 0, 0.5)
} }
reconnectCounts++ reconnectCounts++
@@ -175,18 +178,12 @@ func (svr *Service) keepControllerWorking() {
xl.Info("try to reconnect to server...") xl.Info("try to reconnect to server...")
conn, session, err := svr.login() conn, session, err := svr.login()
if err != nil { if err != nil {
xl.Warn("reconnect to server error: %v", err) xl.Warn("reconnect to server error: %v, wait %v for another retry", err, delayTime)
time.Sleep(delayTime) util.RandomSleep(delayTime, 0.9, 1.1)
opErr := &net.OpError{} delayTime = delayTime * 2
// quick retry for dial error if delayTime > maxDelayTime {
if errors.As(err, &opErr) && opErr.Op == "dial" { delayTime = maxDelayTime
delayTime = 2 * time.Second
} else {
delayTime = delayTime * 2
if delayTime > maxDelayTime {
delayTime = maxDelayTime
}
} }
continue continue
} }
@@ -245,6 +242,8 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
} }
dialOptions = append(dialOptions, dialOptions = append(dialOptions,
libdial.WithProtocol(protocol), libdial.WithProtocol(protocol),
libdial.WithTimeout(time.Duration(svr.cfg.DialServerTimeout)*time.Second),
libdial.WithKeepAlive(time.Duration(svr.cfg.DialServerKeepAlive)*time.Second),
libdial.WithProxy(proxyType, addr), libdial.WithProxy(proxyType, addr),
libdial.WithProxyAuth(auth), libdial.WithProxyAuth(auth),
libdial.WithTLSConfig(tlsConfig), libdial.WithTLSConfig(tlsConfig),

View File

@@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv"
"sync" "sync"
"time" "time"
@@ -85,7 +86,7 @@ type STCPVisitor struct {
} }
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)) sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
if err != nil { if err != nil {
return return
} }
@@ -174,7 +175,7 @@ type XTCPVisitor struct {
} }
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)) sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
if err != nil { if err != nil {
return return
} }
@@ -352,7 +353,7 @@ type SUDPVisitor struct {
func (sv *SUDPVisitor) Run() (err error) { func (sv *SUDPVisitor) Run() (err error) {
xl := xlog.FromContextSafe(sv.ctx) xl := xlog.FromContextSafe(sv.ctx)
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort)) addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
if err != nil { if err != nil {
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err) return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
} }
@@ -376,29 +377,33 @@ func (sv *SUDPVisitor) Run() (err error) {
func (sv *SUDPVisitor) dispatcher() { func (sv *SUDPVisitor) dispatcher() {
xl := xlog.FromContextSafe(sv.ctx) xl := xlog.FromContextSafe(sv.ctx)
var (
visitorConn net.Conn
err error
firstPacket *msg.UDPPacket
)
for { for {
// loop for get frpc to frps tcp conn select {
// setup worker case firstPacket = <-sv.sendCh:
// wait worker to finished if firstPacket == nil {
// 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") xl.Info("frpc sudp visitor proxy is closed")
return return
default:
} }
case <-sv.checkCloseCh:
xl.Info("frpc sudp visitor proxy is closed")
return
}
time.Sleep(3 * time.Second) visitorConn, err = sv.getNewVisitorConn()
if err != nil {
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err) xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
continue continue
} }
sv.worker(visitorConn) // visitorConn always be closed when worker done.
sv.worker(visitorConn, firstPacket)
select { select {
case <-sv.checkCloseCh: case <-sv.checkCloseCh:
@@ -406,9 +411,10 @@ func (sv *SUDPVisitor) dispatcher() {
default: default:
} }
} }
} }
func (sv *SUDPVisitor) worker(workConn net.Conn) { func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
xl := xlog.FromContextSafe(sv.ctx) xl := xlog.FromContextSafe(sv.ctx)
xl.Debug("starting sudp proxy worker") xl.Debug("starting sudp proxy worker")
@@ -462,6 +468,14 @@ func (sv *SUDPVisitor) worker(workConn net.Conn) {
}() }()
var errRet error var errRet error
if firstPacket != nil {
if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil {
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
return
}
xl.Trace("send udp package to workConn: %s", firstPacket.Content)
}
for { for {
select { select {
case udpMsg, ok := <-sv.sendCh: case udpMsg, ok := <-sv.sendCh:

View File

@@ -6,6 +6,13 @@
server_addr = 0.0.0.0 server_addr = 0.0.0.0
server_port = 7000 server_port = 7000
# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
# dial_server_timeout = 10
# dial_server_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
# If negative, keep-alive probes are disabled.
# dial_server_keepalive = 7200
# if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set http_proxy here or in global environment variables # if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set http_proxy here or in global environment variables
# it only works when protocol is tcp # it only works when protocol is tcp
# http_proxy = http://user:passwd@192.168.1.128:8080 # http_proxy = http://user:passwd@192.168.1.128:8080
@@ -48,6 +55,12 @@ oidc_audience =
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "". # It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
oidc_token_endpoint_url = oidc_token_endpoint_url =
# oidc_additional_xxx specifies additional parameters to be sent to the OIDC Token Endpoint.
# For example, if you want to specify the "audience" parameter, you can set as follow.
# frp will add "audience=<value>" "var1=<value>" to the additional parameters.
# oidc_additional_audience = https://dev.auth.com/api/v2/
# oidc_additional_var1 = foobar
# set admin address for control frpc's action by http api such as reload # set admin address for control frpc's action by http api such as reload
admin_addr = 127.0.0.1 admin_addr = 127.0.0.1
admin_port = 7400 admin_port = 7400
@@ -60,7 +73,8 @@ admin_pwd = admin
pool_count = 5 pool_count = 5
# if tcp stream multiplexing is used, default is true, it must be same with frps # if tcp stream multiplexing is used, default is true, it must be same with frps
tcp_mux = true # tcp_mux = true
# specify keep alive interval for tcp mux. # specify keep alive interval for tcp mux.
# only valid if tcp_mux is true. # only valid if tcp_mux is true.
# tcp_mux_keepalive_interval = 60 # tcp_mux_keepalive_interval = 60
@@ -117,6 +131,10 @@ udp_packet_size = 1500
# If DisableCustomTLSFirstByte is true, frpc will not send that custom byte. # If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
disable_custom_tls_first_byte = false disable_custom_tls_first_byte = false
# Enable golang pprof handlers in admin listener.
# Admin port must be set first.
pprof_enable = false
# 'ssh' is the unique proxy name # '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' # if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
[ssh] [ssh]

View File

@@ -86,7 +86,6 @@ oidc_audience =
# By default, this value is false. # By default, this value is false.
oidc_skip_expiry_check = false oidc_skip_expiry_check = false
# oidc_skip_issuer_check specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer. # oidc_skip_issuer_check specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer.
# By default, this value is false. # By default, this value is false.
oidc_skip_issuer_check = false oidc_skip_issuer_check = false
@@ -120,11 +119,16 @@ tls_only = false
subdomain_host = frps.com subdomain_host = frps.com
# if tcp stream multiplexing is used, default is true # if tcp stream multiplexing is used, default is true
tcp_mux = true # tcp_mux = true
# specify keep alive interval for tcp mux. # specify keep alive interval for tcp mux.
# only valid if tcp_mux is true. # only valid if tcp_mux is true.
# tcp_mux_keepalive_interval = 60 # tcp_mux_keepalive_interval = 60
# tcp_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
# If negative, keep-alive probes are disabled.
# tcp_keepalive = 7200
# custom 404 page for HTTP requests # custom 404 page for HTTP requests
# custom_404_page = /path/to/404.html # custom_404_page = /path/to/404.html
@@ -133,6 +137,10 @@ tcp_mux = true
# It affects the udp and sudp proxy. # It affects the udp and sudp proxy.
udp_packet_size = 1500 udp_packet_size = 1500
# Enable golang pprof handlers in dashboard listener.
# Dashboard port must be set first
pprof_enable = false
[plugin.user-manager] [plugin.user-manager]
addr = 127.0.0.1:9000 addr = 127.0.0.1:9000
path = /handler path = /handler

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 41 KiB

BIN
doc/pic/sponsor_workos.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -70,7 +70,7 @@ The response can look like any of the following:
### Operation ### Operation
Currently `Login`, `NewProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported. Currently `Login`, `NewProxy`, `CloseProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
#### Login #### Login
@@ -136,6 +136,26 @@ Create new proxy
} }
``` ```
#### CloseProxy
A previously created proxy is closed.
Please note that one request will be sent for every proxy that is closed, do **NOT** use this
if you have too many proxies bound to a single client, as this may exhaust the server's resources.
```
{
"content": {
"user": {
"user": <string>,
"metas": map<string>string
"run_id": <string>
},
"proxy_name": <string>
}
}
```
#### Ping #### Ping
Heartbeat from frpc Heartbeat from frpc

2
go.mod
View File

@@ -6,7 +6,7 @@ require (
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/coreos/go-oidc v2.2.1+incompatible github.com/coreos/go-oidc v2.2.1+incompatible
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
github.com/fatedier/golib v0.1.1-0.20220119075718-78e5cf8c00ee github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
github.com/go-playground/validator/v10 v10.6.1 github.com/go-playground/validator/v10 v10.6.1
github.com/google/uuid v1.2.0 github.com/google/uuid v1.2.0

4
go.sum
View File

@@ -88,8 +88,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw= github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk= github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
github.com/fatedier/golib v0.1.1-0.20220119075718-78e5cf8c00ee h1:iS0wlj2uZPxh3pciAf/HTzi88Kqu7DPh1jNKgJaFhtI= github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac h1:td1FJwN/oz8+9GldeEm3YdBX0Husc0FSPywLesZxi4w=
github.com/fatedier/golib v0.1.1-0.20220119075718-78e5cf8c00ee/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo= github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo=
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74= github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s= github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=

View File

@@ -40,14 +40,20 @@ type OidcClientConfig struct {
// It will be used to get an OIDC token if AuthenticationMethod == "oidc". // It will be used to get an OIDC token if AuthenticationMethod == "oidc".
// By default, this value is "". // By default, this value is "".
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"` OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
// OidcAdditionalEndpointParams specifies additional parameters to be sent
// this field will be transfer to map[string][]string in OIDC token generator
// The field will be set by prefix "oidc_additional_"
OidcAdditionalEndpointParams map[string]string `ini:"-" json:"oidc_additional_endpoint_params"`
} }
func getDefaultOidcClientConf() OidcClientConfig { func getDefaultOidcClientConf() OidcClientConfig {
return OidcClientConfig{ return OidcClientConfig{
OidcClientID: "", OidcClientID: "",
OidcClientSecret: "", OidcClientSecret: "",
OidcAudience: "", OidcAudience: "",
OidcTokenEndpointURL: "", OidcTokenEndpointURL: "",
OidcAdditionalEndpointParams: make(map[string]string),
} }
} }
@@ -88,11 +94,17 @@ type OidcAuthProvider struct {
} }
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider { func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider {
eps := make(map[string][]string)
for k, v := range cfg.OidcAdditionalEndpointParams {
eps[k] = []string{v}
}
tokenGenerator := &clientcredentials.Config{ tokenGenerator := &clientcredentials.Config{
ClientID: cfg.OidcClientID, ClientID: cfg.OidcClientID,
ClientSecret: cfg.OidcClientSecret, ClientSecret: cfg.OidcClientSecret,
Scopes: []string{cfg.OidcAudience}, Scopes: []string{cfg.OidcAudience},
TokenURL: cfg.OidcTokenEndpointURL, TokenURL: cfg.OidcTokenEndpointURL,
EndpointParams: eps,
} }
return &OidcAuthProvider{ return &OidcAuthProvider{

View File

@@ -38,6 +38,11 @@ type ClientCommonConf struct {
// ServerPort specifies the port to connect to the server on. By default, // ServerPort specifies the port to connect to the server on. By default,
// this value is 7000. // this value is 7000.
ServerPort int `ini:"server_port" json:"server_port"` ServerPort int `ini:"server_port" json:"server_port"`
// The maximum amount of time a dial to server will wait for a connect to complete.
DialServerTimeout int64 `ini:"dial_server_timeout" json:"dial_server_timeout"`
// DialServerKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
// If negative, keep-alive probes are disabled.
DialServerKeepAlive int64 `ini:"dial_server_keepalive" json:"dial_server_keepalive"`
// ConnectServerLocalIP specifies the address of the client bind when it connect to server. // ConnectServerLocalIP specifies the address of the client bind when it connect to server.
// By default, this value is empty. // By default, this value is empty.
// this value only use in TCP/Websocket protocol. Not support in KCP protocol. // this value only use in TCP/Websocket protocol. Not support in KCP protocol.
@@ -149,6 +154,9 @@ type ClientCommonConf struct {
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"` UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
// Include other config files for proxies. // Include other config files for proxies.
IncludeConfigFiles []string `ini:"includes" json:"includes"` IncludeConfigFiles []string `ini:"includes" json:"includes"`
// Enable golang pprof handlers in admin listener.
// Admin port must be set first.
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
} }
// GetDefaultClientConf returns a client configuration with default values. // GetDefaultClientConf returns a client configuration with default values.
@@ -157,6 +165,8 @@ func GetDefaultClientConf() ClientCommonConf {
ClientConfig: auth.GetDefaultClientConf(), ClientConfig: auth.GetDefaultClientConf(),
ServerAddr: "0.0.0.0", ServerAddr: "0.0.0.0",
ServerPort: 7000, ServerPort: 7000,
DialServerTimeout: 10,
DialServerKeepAlive: 7200,
HTTPProxy: os.Getenv("http_proxy"), HTTPProxy: os.Getenv("http_proxy"),
LogFile: "console", LogFile: "console",
LogWay: "console", LogWay: "console",
@@ -185,6 +195,7 @@ func GetDefaultClientConf() ClientCommonConf {
Metas: make(map[string]string), Metas: make(map[string]string),
UDPPacketSize: 1500, UDPPacketSize: 1500,
IncludeConfigFiles: make([]string, 0), IncludeConfigFiles: make([]string, 0),
PprofEnable: false,
} }
} }
@@ -258,6 +269,8 @@ func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
} }
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_") common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
common.ClientConfig.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
return common, nil return common, nil
} }

View File

@@ -261,6 +261,8 @@ func Test_LoadClientCommonConf(t *testing.T) {
}, },
ServerAddr: "0.0.0.9", ServerAddr: "0.0.0.9",
ServerPort: 7009, ServerPort: 7009,
DialServerTimeout: 10,
DialServerKeepAlive: 7200,
HTTPProxy: "http://user:passwd@192.168.1.128:8080", HTTPProxy: "http://user:passwd@192.168.1.128:8080",
LogFile: "./frpc.log9", LogFile: "./frpc.log9",
LogWay: "file", LogWay: "file",

View File

@@ -121,6 +121,9 @@ type ServerCommonConf struct {
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler. // TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux. // If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
TCPMuxKeepaliveInterval int64 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"` TCPMuxKeepaliveInterval int64 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"`
// TCPKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
// If negative, keep-alive probes are disabled.
TCPKeepAlive int64 `ini:"tcp_keepalive" json:"tcp_keepalive"`
// Custom404Page specifies a path to a custom 404 page to display. If this // 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 // value is "", a default page will be displayed. By default, this value is
// "". // "".
@@ -167,6 +170,9 @@ type ServerCommonConf struct {
// UDPPacketSize specifies the UDP packet size // UDPPacketSize specifies the UDP packet size
// By default, this value is 1500 // By default, this value is 1500
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"` UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
// Enable golang pprof handlers in dashboard listener.
// Dashboard port must be set first.
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
} }
// GetDefaultServerConf returns a server configuration with reasonable // GetDefaultServerConf returns a server configuration with reasonable
@@ -198,6 +204,7 @@ func GetDefaultServerConf() ServerCommonConf {
SubDomainHost: "", SubDomainHost: "",
TCPMux: true, TCPMux: true,
TCPMuxKeepaliveInterval: 60, TCPMuxKeepaliveInterval: 60,
TCPKeepAlive: 7200,
AllowPorts: make(map[int]struct{}), AllowPorts: make(map[int]struct{}),
MaxPoolCount: 5, MaxPoolCount: 5,
MaxPortsPerClient: 0, MaxPortsPerClient: 0,
@@ -210,6 +217,7 @@ func GetDefaultServerConf() ServerCommonConf {
Custom404Page: "", Custom404Page: "",
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions), HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
UDPPacketSize: 1500, UDPPacketSize: 1500,
PprofEnable: false,
} }
} }

View File

@@ -140,6 +140,7 @@ func Test_LoadServerCommonConf(t *testing.T) {
SubDomainHost: "frps.com", SubDomainHost: "frps.com",
TCPMux: true, TCPMux: true,
TCPMuxKeepaliveInterval: 60, TCPMuxKeepaliveInterval: 60,
TCPKeepAlive: 7200,
UDPPacketSize: 1509, UDPPacketSize: 1509,
HTTPPlugins: map[string]plugin.HTTPPluginOptions{ HTTPPlugins: map[string]plugin.HTTPPluginOptions{
@@ -191,6 +192,7 @@ func Test_LoadServerCommonConf(t *testing.T) {
DetailedErrorsToClient: true, DetailedErrorsToClient: true,
TCPMux: true, TCPMux: true,
TCPMuxKeepaliveInterval: 60, TCPMuxKeepaliveInterval: 60,
TCPKeepAlive: 7200,
AllowPorts: make(map[int]struct{}), AllowPorts: make(map[int]struct{}),
MaxPoolCount: 5, MaxPoolCount: 5,
HeartbeatTimeout: 90, HeartbeatTimeout: 90,

View File

@@ -18,6 +18,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/fatedier/frp/pkg/util/util" "github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/xlog" "github.com/fatedier/frp/pkg/util/xlog"
@@ -26,6 +27,7 @@ import (
type Manager struct { type Manager struct {
loginPlugins []Plugin loginPlugins []Plugin
newProxyPlugins []Plugin newProxyPlugins []Plugin
closeProxyPlugins []Plugin
pingPlugins []Plugin pingPlugins []Plugin
newWorkConnPlugins []Plugin newWorkConnPlugins []Plugin
newUserConnPlugins []Plugin newUserConnPlugins []Plugin
@@ -35,6 +37,7 @@ func NewManager() *Manager {
return &Manager{ return &Manager{
loginPlugins: make([]Plugin, 0), loginPlugins: make([]Plugin, 0),
newProxyPlugins: make([]Plugin, 0), newProxyPlugins: make([]Plugin, 0),
closeProxyPlugins: make([]Plugin, 0),
pingPlugins: make([]Plugin, 0), pingPlugins: make([]Plugin, 0),
newWorkConnPlugins: make([]Plugin, 0), newWorkConnPlugins: make([]Plugin, 0),
newUserConnPlugins: make([]Plugin, 0), newUserConnPlugins: make([]Plugin, 0),
@@ -48,6 +51,9 @@ func (m *Manager) Register(p Plugin) {
if p.IsSupport(OpNewProxy) { if p.IsSupport(OpNewProxy) {
m.newProxyPlugins = append(m.newProxyPlugins, p) m.newProxyPlugins = append(m.newProxyPlugins, p)
} }
if p.IsSupport(OpCloseProxy) {
m.closeProxyPlugins = append(m.closeProxyPlugins, p)
}
if p.IsSupport(OpPing) { if p.IsSupport(OpPing) {
m.pingPlugins = append(m.pingPlugins, p) m.pingPlugins = append(m.pingPlugins, p)
} }
@@ -127,6 +133,32 @@ func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
return content, nil return content, nil
} }
func (m *Manager) CloseProxy(content *CloseProxyContent) error {
if len(m.closeProxyPlugins) == 0 {
return nil
}
errs := make([]string, 0)
reqid, _ := util.RandID()
xl := xlog.New().AppendPrefix("reqid: " + reqid)
ctx := xlog.NewContext(context.Background(), xl)
ctx = NewReqidContext(ctx, reqid)
for _, p := range m.closeProxyPlugins {
_, _, err := p.Handle(ctx, OpCloseProxy, *content)
if err != nil {
xl.Warn("send CloseProxy request to plugin [%s] error: %v", p.Name(), err)
errs = append(errs, fmt.Sprintf("[%s]: %v", p.Name(), err))
}
}
if len(errs) > 0 {
return fmt.Errorf("send CloseProxy request to plugin errors: %s", strings.Join(errs, "; "))
} else {
return nil
}
}
func (m *Manager) Ping(content *PingContent) (*PingContent, error) { func (m *Manager) Ping(content *PingContent) (*PingContent, error) {
if len(m.pingPlugins) == 0 { if len(m.pingPlugins) == 0 {
return content, nil return content, nil

View File

@@ -23,6 +23,7 @@ const (
OpLogin = "Login" OpLogin = "Login"
OpNewProxy = "NewProxy" OpNewProxy = "NewProxy"
OpCloseProxy = "CloseProxy"
OpPing = "Ping" OpPing = "Ping"
OpNewWorkConn = "NewWorkConn" OpNewWorkConn = "NewWorkConn"
OpNewUserConn = "NewUserConn" OpNewUserConn = "NewUserConn"

View File

@@ -48,6 +48,11 @@ type NewProxyContent struct {
msg.NewProxy msg.NewProxy
} }
type CloseProxyContent struct {
User UserInfo `json:"user"`
msg.CloseProxy
}
type PingContent struct { type PingContent struct {
User UserInfo `json:"user"` User UserInfo `json:"user"`
msg.Ping msg.Ping

View File

@@ -100,6 +100,8 @@ func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Conf
base.Certificates = []tls.Certificate{*cert} base.Certificates = []tls.Certificate{*cert}
} }
base.ServerName = serverName
if caPath != "" { if caPath != "" {
pool, err := newCertPool(caPath) pool, err := newCertPool(caPath)
if err != nil { if err != nil {
@@ -107,7 +109,6 @@ func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Conf
} }
base.RootCAs = pool base.RootCAs = pool
base.ServerName = serverName
base.InsecureSkipVerify = false base.InsecureSkipVerify = false
} else { } else {
base.InsecureSkipVerify = true base.InsecureSkipVerify = true

View File

@@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv"
"sync" "sync"
"time" "time"
@@ -163,7 +164,7 @@ type UDPListener struct {
} }
func ListenUDP(bindAddr string, bindPort int) (l *UDPListener, err error) { func ListenUDP(bindAddr string, bindPort int) (l *UDPListener, err error) {
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort)) udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(bindAddr, strconv.Itoa(bindPort)))
if err != nil { if err != nil {
return l, err return l, err
} }

View File

@@ -2,9 +2,9 @@ package net
import ( import (
"errors" "errors"
"fmt"
"net" "net"
"net/http" "net/http"
"strconv"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
) )
@@ -52,7 +52,7 @@ func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
} }
func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) { func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) {
tcpLn, err := net.Listen("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort)) tcpLn, err := net.Listen("tcp", net.JoinHostPort(bindAddr, strconv.Itoa(bindPort)))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -48,7 +48,7 @@ func readHTTPConnectRequest(rd io.Reader) (host string, err error) {
return return
} }
host = util.GetHostFromAddr(req.Host) host, _ = util.CanonicalHost(req.Host)
return return
} }

View File

@@ -34,17 +34,6 @@ func OkResponse() *http.Response {
return res return res
} }
// TODO: use "CanonicalHost" func to replace all "GetHostFromAddr" func.
func GetHostFromAddr(addr string) (host string) {
strs := strings.Split(addr, ":")
if len(strs) > 1 {
host = strs[0]
} else {
host = addr
}
return
}
// canonicalHost strips port from host if present and returns the canonicalized // canonicalHost strips port from host if present and returns the canonicalized
// host name. // host name.
func CanonicalHost(host string) (string, error) { func CanonicalHost(host string) (string, error) {

View File

@@ -19,8 +19,11 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
mathrand "math/rand"
"net"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// RandID return a rand string used in frp. // RandID return a rand string used in frp.
@@ -52,7 +55,7 @@ func CanonicalAddr(host string, port int) (addr string) {
if port == 80 || port == 443 { if port == 80 || port == 443 {
addr = host addr = host
} else { } else {
addr = fmt.Sprintf("%s:%d", host, port) addr = net.JoinHostPort(host, strconv.Itoa(port))
} }
return return
} }
@@ -108,3 +111,17 @@ func GenerateResponseErrorString(summary string, err error, detailed bool) strin
} }
return summary return summary
} }
func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Duration {
min := int64(minRatio * 1000.0)
max := int64(maxRatio * 1000.0)
var n int64
if max <= min {
n = min
} else {
n = mathrand.Int63n(max-min) + min
}
d := duration * time.Duration(n) / time.Duration(1000)
time.Sleep(d)
return d
}

View File

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

View File

@@ -59,7 +59,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
Director: func(req *http.Request) { Director: func(req *http.Request) {
req.URL.Scheme = "http" req.URL.Scheme = "http"
url := req.Context().Value(RouteInfoURL).(string) url := req.Context().Value(RouteInfoURL).(string)
oldHost := util.GetHostFromAddr(req.Context().Value(RouteInfoHost).(string)) oldHost, _ := util.CanonicalHost(req.Context().Value(RouteInfoHost).(string))
rc := rp.GetRouteConfig(oldHost, url) rc := rp.GetRouteConfig(oldHost, url)
if rc != nil { if rc != nil {
if rc.RewriteHost != "" { if rc.RewriteHost != "" {
@@ -81,7 +81,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
IdleConnTimeout: 60 * time.Second, IdleConnTimeout: 60 * time.Second,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value(RouteInfoURL).(string) url := ctx.Value(RouteInfoURL).(string)
host := util.GetHostFromAddr(ctx.Value(RouteInfoHost).(string)) host, _ := util.CanonicalHost(ctx.Value(RouteInfoHost).(string))
remote := ctx.Value(RouteInfoRemote).(string) remote := ctx.Value(RouteInfoRemote).(string)
return rp.CreateConnection(host, url, remote) return rp.CreateConnection(host, url, remote)
}, },
@@ -191,7 +191,7 @@ func (rp *HTTPReverseProxy) getVhost(domain string, location string) (vr *Router
} }
func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
domain := util.GetHostFromAddr(req.Host) domain, _ := util.CanonicalHost(req.Host)
location := req.URL.Path location := req.URL.Path
user, passwd, _ := req.BasicAuth() user, passwd, _ := req.BasicAuth()
if !rp.CheckAuth(domain, location, user, passwd) { if !rp.CheckAuth(domain, location, user, passwd) {

View File

@@ -376,6 +376,20 @@ func (ctl *Control) stoper() {
pxy.Close() pxy.Close()
ctl.pxyManager.Del(pxy.GetName()) ctl.pxyManager.Del(pxy.GetName())
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType) metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
notifyContent := &plugin.CloseProxyContent{
User: plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.loginMsg.RunID,
},
CloseProxy: msg.CloseProxy{
ProxyName: pxy.GetName(),
},
}
go func() {
ctl.pluginManager.CloseProxy(notifyContent)
}()
} }
ctl.allShutdown.Done() ctl.allShutdown.Done()
@@ -564,5 +578,20 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
ctl.mu.Unlock() ctl.mu.Unlock()
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType) metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
notifyContent := &plugin.CloseProxyContent{
User: plugin.UserInfo{
User: ctl.loginMsg.User,
Metas: ctl.loginMsg.Metas,
RunID: ctl.loginMsg.RunID,
},
CloseProxy: msg.CloseProxy{
ProxyName: pxy.GetName(),
},
}
go func() {
ctl.pluginManager.CloseProxy(notifyContent)
}()
return return
} }

View File

@@ -17,6 +17,7 @@ package server
import ( import (
"net" "net"
"net/http" "net/http"
"net/http/pprof"
"time" "time"
"github.com/fatedier/frp/assets" "github.com/fatedier/frp/assets"
@@ -27,8 +28,8 @@ import (
) )
var ( var (
httpServerReadTimeout = 10 * time.Second httpServerReadTimeout = 60 * time.Second
httpServerWriteTimeout = 10 * time.Second httpServerWriteTimeout = 60 * time.Second
) )
func (svr *Service) RunDashboardServer(address string) (err error) { func (svr *Service) RunDashboardServer(address string) (err error) {
@@ -36,6 +37,15 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
router := mux.NewRouter() router := mux.NewRouter()
router.HandleFunc("/healthz", svr.Healthz) router.HandleFunc("/healthz", svr.Healthz)
// debug
if svr.cfg.PprofEnable {
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
}
subRouter := router.NewRoute().Subrouter() subRouter := router.NewRoute().Subrouter()
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd

View File

@@ -15,8 +15,8 @@
package group package group
import ( import (
"fmt"
"net" "net"
"strconv"
"sync" "sync"
"github.com/fatedier/frp/server/ports" "github.com/fatedier/frp/server/ports"
@@ -101,7 +101,7 @@ func (tg *TCPGroup) Listen(proxyName string, group string, groupKey string, addr
if err != nil { if err != nil {
return return
} }
tcpLn, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port)) tcpLn, errRet := net.Listen("tcp", net.JoinHostPort(addr, strconv.Itoa(port)))
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return

View File

@@ -2,8 +2,8 @@ package ports
import ( import (
"errors" "errors"
"fmt"
"net" "net"
"strconv"
"sync" "sync"
"time" "time"
) )
@@ -134,7 +134,7 @@ func (pm *Manager) Acquire(name string, port int) (realPort int, err error) {
func (pm *Manager) isPortAvailable(port int) bool { func (pm *Manager) isPortAvailable(port int) bool {
if pm.netType == "udp" { if pm.netType == "udp" {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pm.bindAddr, port)) addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(pm.bindAddr, strconv.Itoa(port)))
if err != nil { if err != nil {
return false return false
} }
@@ -146,7 +146,7 @@ func (pm *Manager) isPortAvailable(port int) bool {
return true return true
} }
l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port)) l, err := net.Listen(pm.netType, net.JoinHostPort(pm.bindAddr, strconv.Itoa(port)))
if err != nil { if err != nil {
return false return false
} }

View File

@@ -17,6 +17,7 @@ package proxy
import ( import (
"fmt" "fmt"
"net" "net"
"strconv"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
) )
@@ -54,7 +55,7 @@ func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
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)) listener, errRet := net.Listen("tcp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realPort)))
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return

View File

@@ -19,6 +19,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv"
"time" "time"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
@@ -70,7 +71,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
pxy.cfg.RemotePort = pxy.realPort pxy.cfg.RemotePort = pxy.realPort
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort)) addr, errRet := net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realPort)))
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return

View File

@@ -124,7 +124,8 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
// Create tcpmux httpconnect multiplexer. // Create tcpmux httpconnect multiplexer.
if cfg.TCPMuxHTTPConnectPort > 0 { if cfg.TCPMuxHTTPConnectPort > 0 {
var l net.Listener var l net.Listener
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort)) address := net.JoinHostPort(cfg.ProxyBindAddr, strconv.Itoa(cfg.TCPMuxHTTPConnectPort))
l, err = net.Listen("tcp", address)
if err != nil { if err != nil {
err = fmt.Errorf("Create server listener error, %v", err) err = fmt.Errorf("Create server listener error, %v", err)
return return
@@ -135,7 +136,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
err = fmt.Errorf("Create vhost tcpMuxer error, %v", err) err = fmt.Errorf("Create vhost tcpMuxer error, %v", err)
return return
} }
log.Info("tcpmux httpconnect multiplexer listen on %s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort) log.Info("tcpmux httpconnect multiplexer listen on %s", address)
} }
// Init all plugins // Init all plugins
@@ -185,6 +186,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
} }
svr.muxer = mux.NewMux(ln) svr.muxer = mux.NewMux(ln)
svr.muxer.SetKeepAlive(time.Duration(cfg.TCPKeepAlive) * time.Second)
go svr.muxer.Serve() go svr.muxer.Serve()
ln = svr.muxer.DefaultListener() ln = svr.muxer.DefaultListener()
@@ -199,7 +201,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
err = fmt.Errorf("Listen on kcp address udp %s error: %v", address, err) err = fmt.Errorf("Listen on kcp address udp %s error: %v", address, err)
return return
} }
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KCPBindPort) log.Info("frps kcp listen on udp %s", address)
} }
// Listen for accepting connections from client using websocket protocol. // Listen for accepting connections from client using websocket protocol.
@@ -232,7 +234,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
} }
} }
go server.Serve(l) go server.Serve(l)
log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHTTPPort) log.Info("http service listen on %s", address)
} }
// Create https vhost muxer. // Create https vhost muxer.
@@ -288,7 +290,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
err = fmt.Errorf("Create dashboard web server error, %v", err) err = fmt.Errorf("Create dashboard web server error, %v", err)
return return
} }
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort) log.Info("Dashboard listen on %s", address)
statsEnable = true statsEnable = true
} }
if statsEnable { if statsEnable {

View File

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

View File

@@ -12,7 +12,7 @@ import (
// RunProcesses run multiple processes from templates. // RunProcesses run multiple processes from templates.
// The first template should always be frps. // The first template should always be frps.
func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) { func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) ([]*process.Process, []*process.Process) {
templates := make([]string, 0, len(serverTemplates)+len(clientTemplates)) templates := make([]string, 0, len(serverTemplates)+len(clientTemplates))
for _, t := range serverTemplates { for _, t := range serverTemplates {
templates = append(templates, t) templates = append(templates, t)
@@ -28,6 +28,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
f.usedPorts[name] = port f.usedPorts[name] = port
} }
currentServerProcesses := make([]*process.Process, 0, len(serverTemplates))
for i := range serverTemplates { for i := range serverTemplates {
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i)) path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
err = os.WriteFile(path, []byte(outs[i]), 0666) err = os.WriteFile(path, []byte(outs[i]), 0666)
@@ -37,11 +38,13 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs) p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs)
f.serverConfPaths = append(f.serverConfPaths, path) f.serverConfPaths = append(f.serverConfPaths, path)
f.serverProcesses = append(f.serverProcesses, p) f.serverProcesses = append(f.serverProcesses, p)
currentServerProcesses = append(currentServerProcesses, p)
err = p.Start() err = p.Start()
ExpectNoError(err) ExpectNoError(err)
} }
time.Sleep(time.Second) time.Sleep(time.Second)
currentClientProcesses := make([]*process.Process, 0, len(clientTemplates))
for i := range clientTemplates { for i := range clientTemplates {
index := i + len(serverTemplates) index := i + len(serverTemplates)
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i)) path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i))
@@ -52,11 +55,14 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs) p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs)
f.clientConfPaths = append(f.clientConfPaths, path) f.clientConfPaths = append(f.clientConfPaths, path)
f.clientProcesses = append(f.clientProcesses, p) f.clientProcesses = append(f.clientProcesses, p)
currentClientProcesses = append(currentClientProcesses, p)
err = p.Start() err = p.Start()
ExpectNoError(err) ExpectNoError(err)
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
return currentServerProcesses, currentClientProcesses
} }
func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) { func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) {

View File

@@ -2,7 +2,6 @@ package httpserver
import ( import (
"crypto/tls" "crypto/tls"
"fmt"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
@@ -97,7 +96,7 @@ func (s *Server) Close() error {
} }
func (s *Server) initListener() (err error) { func (s *Server) initListener() (err error) {
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort)) s.l, err = net.Listen("tcp", net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort)))
return return
} }

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv"
libnet "github.com/fatedier/frp/pkg/util/net" libnet "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/test/e2e/pkg/rpc" "github.com/fatedier/frp/test/e2e/pkg/rpc"
@@ -99,7 +100,7 @@ func (s *Server) Close() error {
func (s *Server) initListener() (err error) { func (s *Server) initListener() (err error) {
switch s.netType { switch s.netType {
case TCP: case TCP:
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort)) s.l, err = net.Listen("tcp", net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort)))
case UDP: case UDP:
s.l, err = libnet.ListenUDP(s.bindAddr, s.bindPort) s.l, err = libnet.ListenUDP(s.bindAddr, s.bindPort)
case Unix: case Unix:

View File

@@ -3,6 +3,7 @@ package port
import ( import (
"fmt" "fmt"
"net" "net"
"strconv"
"sync" "sync"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
@@ -57,7 +58,7 @@ func (pa *Allocator) GetByName(portName string) int {
return 0 return 0
} }
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) l, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
if err != nil { if err != nil {
// Maybe not controlled by us, mark it used. // Maybe not controlled by us, mark it used.
pa.used.Insert(port) pa.used.Insert(port)
@@ -65,7 +66,7 @@ func (pa *Allocator) GetByName(portName string) int {
} }
l.Close() l.Close()
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", port)) udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
if err != nil { if err != nil {
continue continue
} }

View File

@@ -158,6 +158,56 @@ var _ = Describe("[Feature: Server-Plugins]", func() {
}) })
}) })
Describe("CloseProxy", func() {
newFunc := func() *plugin.Request {
var r plugin.Request
r.Content = &plugin.CloseProxyContent{}
return &r
}
It("Validate Info", func() {
localPort := f.AllocPort()
var recordProxyName string
handler := func(req *plugin.Request) *plugin.Response {
var ret plugin.Response
content := req.Content.(*plugin.CloseProxyContent)
recordProxyName = content.ProxyName
return &ret
}
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
f.RunServer("", pluginServer)
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
[plugin.test]
addr = 127.0.0.1:%d
path = /handler
ops = CloseProxy
`, localPort)
clientConf := consts.DefaultClientConfig
remotePort := f.AllocPort()
clientConf += fmt.Sprintf(`
[tcp]
type = tcp
local_port = {{ .%s }}
remote_port = %d
`, framework.TCPEchoServerPort, remotePort)
_, clients := f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(remotePort).Ensure()
for _, c := range clients {
c.Stop()
}
time.Sleep(1 * time.Second)
framework.ExpectEqual(recordProxyName, "tcp")
})
})
Describe("Ping", func() { Describe("Ping", func() {
newFunc := func() *plugin.Request { newFunc := func() *plugin.Request {
var r plugin.Request var r plugin.Request