Compare commits

...

13 Commits

Author SHA1 Message Date
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
39 changed files with 324 additions and 102 deletions

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,13 @@
### New ### New
* Added `connect_server_local_ip` in frpc to specify local IP connected to frps. * Added `dial_server_timeout` in frpc to specify connect timeout to frps.
* Added `tcp_mux_keepalive_interval` both in frpc and frps to set `tcp_mux` keepalive interval seconds if `tcp_mux` is enabled. After using this params, you can set `heartbeat_interval` to `-1` to disable application layer heartbeat to reduce traffic usage(Make sure frps is in the latest version). * Additional EndpointParams can be set for OIDC.
* Added CloseProxy operation in server plugin.
### Improve ### Improve
* Server Plugin: Added `client_address` in Login Operation. * Added some randomness in reconnect delay.
### Fix ### Fix
* Remove authentication for healthz api. * TLS server name is ignored when `tls_trusted_ca_file` isnt set.

View File

@@ -251,6 +251,7 @@ 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.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,7 @@ 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.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)
} }

View File

@@ -6,6 +6,9 @@
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
# 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 +51,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

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.20220218075713-264f72dfbfd9
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

6
go.sum
View File

@@ -88,8 +88,10 @@ 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.20220218073251-9509a597216b h1:5r5/G3NFsFK+7svxvxZYA8yy8Ubs4hWIq+QYYMgEBe8=
github.com/fatedier/golib v0.1.1-0.20220119075718-78e5cf8c00ee/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo= github.com/fatedier/golib v0.1.1-0.20220218073251-9509a597216b/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo=
github.com/fatedier/golib v0.1.1-0.20220218075713-264f72dfbfd9 h1:AOGf9Z1ri+3MiyGIAYXe+shEXx6/uVGJlufb6ZfnZls=
github.com/fatedier/golib v0.1.1-0.20220218075713-264f72dfbfd9/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,8 @@ 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"`
// 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.
@@ -157,6 +159,7 @@ 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,
HTTPProxy: os.Getenv("http_proxy"), HTTPProxy: os.Getenv("http_proxy"),
LogFile: "console", LogFile: "console",
LogWay: "console", LogWay: "console",
@@ -258,6 +261,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,7 @@ func Test_LoadClientCommonConf(t *testing.T) {
}, },
ServerAddr: "0.0.0.9", ServerAddr: "0.0.0.9",
ServerPort: 7009, ServerPort: 7009,
DialServerTimeout: 10,
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

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

@@ -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
@@ -199,7 +200,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 +233,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 +289,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