mirror of
https://github.com/fatedier/frp.git
synced 2025-07-27 07:35:07 +00:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
eb1e19a821 | ||
|
6c658586f6 | ||
|
888ed25314 | ||
|
21240ed962 | ||
|
6481870d03 | ||
|
a7a4ba270d | ||
|
915d9f4c09 | ||
|
18a2af4703 | ||
|
305e40fa8a | ||
|
10f2620131 | ||
|
4acae540c8 | ||
|
11b13533a0 | ||
|
100d556336 | ||
|
452fe25cc6 | ||
|
63efa6b776 | ||
|
37c27169ac | ||
|
ce677820c6 | ||
|
1f88a7a0b8 | ||
|
eeea7602d9 | ||
|
bf635c0e90 | ||
|
cd31359a27 | ||
|
19739ed31a | ||
|
10100c28d9 | ||
|
88fcc079e8 | ||
|
ddc1e163c4 | ||
|
d20a6d3d75 | ||
|
6194273615 | ||
|
b2311e55e7 | ||
|
07873d471f |
@@ -2,14 +2,14 @@ version: 2
|
||||
jobs:
|
||||
go-version-latest:
|
||||
docker:
|
||||
- image: cimg/go:1.17-node
|
||||
- image: cimg/go:1.18-node
|
||||
steps:
|
||||
- checkout
|
||||
- run: make
|
||||
- run: make alltest
|
||||
go-version-last:
|
||||
docker:
|
||||
- image: cimg/go:1.16-node
|
||||
- image: cimg/go:1.17-node
|
||||
steps:
|
||||
- checkout
|
||||
- run: make
|
||||
|
2
.github/workflows/build-and-push-image.yml
vendored
2
.github/workflows/build-and-push-image.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18
|
||||
|
||||
- run: |
|
||||
# https://github.com/actions/setup-go/issues/107
|
||||
|
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: 1.18
|
||||
|
||||
- run: |
|
||||
# https://github.com/actions/setup-go/issues/107
|
||||
|
6
.github/workflows/stale.yml
vendored
6
.github/workflows/stale.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
|
||||
stale-pr-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
|
||||
stale-pr-message: "PRs go stale after 30d of inactivity. Stale PRs rot after an additional 7d of inactivity and eventually close."
|
||||
stale-issue-label: 'lifecycle/stale'
|
||||
exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
|
||||
stale-pr-label: 'lifecycle/stale'
|
||||
@@ -24,3 +24,5 @@ jobs:
|
||||
days-before-stale: 30
|
||||
days-before-close: 7
|
||||
debug-only: ${{ github.event.inputs.debug-only }}
|
||||
exempt-all-pr-milestones: true
|
||||
exempt-all-pr-assignees: true
|
||||
|
@@ -1,7 +1,10 @@
|
||||
builds:
|
||||
- skip: true
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
name_template: '{{ .ProjectName }}_sha256_checksums.txt'
|
||||
algorithm: sha256
|
||||
extra_files:
|
||||
- glob: ./release/packages/*
|
||||
release:
|
||||
# Same as for github
|
||||
# Note: it can only be one: either github, gitlab or gitea
|
||||
|
5
Makefile
5
Makefile
@@ -16,6 +16,9 @@ file:
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
frps:
|
||||
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
|
||||
|
||||
@@ -37,7 +40,7 @@ e2e:
|
||||
e2e-trace:
|
||||
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
|
||||
|
||||
alltest: gotest e2e
|
||||
alltest: vet gotest e2e
|
||||
|
||||
clean:
|
||||
rm -f ./bin/frpc
|
||||
|
36
README.md
36
README.md
@@ -6,12 +6,6 @@
|
||||
|
||||
[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>
|
||||
<!--platinum sponsors start-->
|
||||
|
||||
@@ -23,10 +17,27 @@ frp also has a P2P connect mode.
|
||||
|
||||
<!--platinum sponsors end-->
|
||||
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
|
||||
<p align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!--gold sponsors end-->
|
||||
|
||||
<h3 align="center">Silver Sponsors</h3>
|
||||
|
||||
* Sakura Frp - 欢迎点击 "加入我们"
|
||||
|
||||
## What is frp?
|
||||
|
||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. As of now, it supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, where requests can be forwarded to internal services by domain name.
|
||||
|
||||
frp also has a P2P connect mode.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
@@ -82,8 +93,7 @@ frp also has a P2P connect mode.
|
||||
* [Development Plan](#development-plan)
|
||||
* [Contributing](#contributing)
|
||||
* [Donation](#donation)
|
||||
* [AliPay](#alipay)
|
||||
* [Wechat Pay](#wechat-pay)
|
||||
* [GitHub Sponsors](#github-sponsors)
|
||||
* [PayPal](#paypal)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
@@ -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:
|
||||
|
||||
frp QQ group: 606194980
|
||||
### GitHub Sponsors
|
||||
|
||||
### AliPay
|
||||
Support us by [Github Sponsors](https://github.com/sponsors/fatedier).
|
||||
|
||||

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

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

|
||||
|
||||
### Paypal 捐赠
|
||||
|
||||
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。
|
||||
|
@@ -1,12 +1,7 @@
|
||||
### New
|
||||
|
||||
* Added `connect_server_local_ip` in frpc to specify local IP connected to frps.
|
||||
* Added `tcp_mux_keepalive_interval` both in frpc and frps to set `tcp_mux` keepalive interval seconds if `tcp_mux` is enabled. After using this params, you can set `heartbeat_interval` to `-1` to disable application layer heartbeat to reduce traffic usage(Make sure frps is in the latest version).
|
||||
|
||||
### Improve
|
||||
|
||||
* Server Plugin: Added `client_address` in Login Operation.
|
||||
* Added new parameter `config_dir` in frpc to run multiple client instances in one process.
|
||||
|
||||
### Fix
|
||||
|
||||
* Remove authentication for healthz api.
|
||||
* Equal sign in environment variables causes parsing error.
|
||||
|
@@ -17,6 +17,7 @@ package client
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
@@ -26,8 +27,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerReadTimeout = 10 * time.Second
|
||||
httpServerWriteTimeout = 10 * time.Second
|
||||
httpServerReadTimeout = 60 * time.Second
|
||||
httpServerWriteTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
func (svr *Service) RunAdminServer(address string) (err error) {
|
||||
@@ -36,6 +37,15 @@ func (svr *Service) RunAdminServer(address string) (err error) {
|
||||
|
||||
router.HandleFunc("/healthz", svr.healthz)
|
||||
|
||||
// debug
|
||||
if svr.cfg.PprofEnable {
|
||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||
}
|
||||
|
||||
subRouter := router.NewRoute().Subrouter()
|
||||
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
||||
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
||||
|
@@ -251,6 +251,8 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) {
|
||||
}
|
||||
dialOptions = append(dialOptions,
|
||||
libdial.WithProtocol(protocol),
|
||||
libdial.WithTimeout(time.Duration(ctl.clientCfg.DialServerTimeout)*time.Second),
|
||||
libdial.WithKeepAlive(time.Duration(ctl.clientCfg.DialServerKeepAlive)*time.Second),
|
||||
libdial.WithProxy(proxyType, addr),
|
||||
libdial.WithProxyAuth(auth),
|
||||
libdial.WithTLSConfig(tlsConfig),
|
||||
|
@@ -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)
|
||||
|
||||
// Send detect message
|
||||
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
||||
if len(array) <= 1 {
|
||||
xl.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
host, portStr, err := net.SplitHostPort(natHoleRespMsg.VisitorAddr)
|
||||
if err != nil {
|
||||
xl.Error("get NatHoleResp visitor address [%s] error: %v", natHoleRespMsg.VisitorAddr, err)
|
||||
}
|
||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||
/*
|
||||
for i := 1000; i < 65000; i++ {
|
||||
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||
}
|
||||
*/
|
||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||
|
||||
port, err := strconv.ParseInt(portStr, 10, 64)
|
||||
if err != nil {
|
||||
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
return
|
||||
}
|
||||
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||
pxy.sendDetectMsg(host, int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||
xl.Trace("send all detect msg done")
|
||||
|
||||
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||
@@ -370,7 +366,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
// Listen for clientConn's address and wait for visitor connection
|
||||
lConn, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
xl.Error("listen on visitorConn's local adress error: %v", err)
|
||||
xl.Error("listen on visitorConn's local address error: %v", err)
|
||||
return
|
||||
}
|
||||
defer lConn.Close()
|
||||
@@ -791,7 +787,10 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
|
||||
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 {
|
||||
workConn.Close()
|
||||
xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err)
|
||||
|
@@ -17,12 +17,13 @@ package client
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -34,13 +35,20 @@ import (
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
"github.com/fatedier/golib/crypto"
|
||||
libdial "github.com/fatedier/golib/net/dial"
|
||||
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
func init() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Service is a client service.
|
||||
type Service struct {
|
||||
// uniq id got from frps, attach it in loginMsg
|
||||
@@ -98,6 +106,21 @@ func (svr *Service) GetController() *Control {
|
||||
func (svr *Service) Run() error {
|
||||
xl := xlog.FromContextSafe(svr.ctx)
|
||||
|
||||
// set custom DNSServer
|
||||
if svr.cfg.DNSServer != "" {
|
||||
dnsAddr := svr.cfg.DNSServer
|
||||
if !strings.Contains(dnsAddr, ":") {
|
||||
dnsAddr += ":53"
|
||||
}
|
||||
// Change default dns server for frpc
|
||||
net.DefaultResolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return net.Dial("udp", dnsAddr)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// login to frps
|
||||
for {
|
||||
conn, session, err := svr.login()
|
||||
@@ -109,7 +132,7 @@ func (svr *Service) Run() error {
|
||||
if svr.cfg.LoginFailExit {
|
||||
return err
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
util.RandomSleep(10*time.Second, 0.9, 1.1)
|
||||
} else {
|
||||
// login success
|
||||
ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
||||
@@ -158,8 +181,11 @@ func (svr *Service) keepControllerWorking() {
|
||||
|
||||
// the first three retry with no delay
|
||||
if reconnectCounts > 3 {
|
||||
time.Sleep(reconnectDelay)
|
||||
util.RandomSleep(reconnectDelay, 0.9, 1.1)
|
||||
xl.Info("wait %v to reconnect", reconnectDelay)
|
||||
reconnectDelay *= 2
|
||||
} else {
|
||||
util.RandomSleep(time.Second, 0, 0.5)
|
||||
}
|
||||
reconnectCounts++
|
||||
|
||||
@@ -175,18 +201,12 @@ func (svr *Service) keepControllerWorking() {
|
||||
xl.Info("try to reconnect to server...")
|
||||
conn, session, err := svr.login()
|
||||
if err != nil {
|
||||
xl.Warn("reconnect to server error: %v", err)
|
||||
time.Sleep(delayTime)
|
||||
xl.Warn("reconnect to server error: %v, wait %v for another retry", err, delayTime)
|
||||
util.RandomSleep(delayTime, 0.9, 1.1)
|
||||
|
||||
opErr := &net.OpError{}
|
||||
// quick retry for dial error
|
||||
if errors.As(err, &opErr) && opErr.Op == "dial" {
|
||||
delayTime = 2 * time.Second
|
||||
} else {
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
delayTime = delayTime * 2
|
||||
if delayTime > maxDelayTime {
|
||||
delayTime = maxDelayTime
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -245,6 +265,8 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
|
||||
}
|
||||
dialOptions = append(dialOptions,
|
||||
libdial.WithProtocol(protocol),
|
||||
libdial.WithTimeout(time.Duration(svr.cfg.DialServerTimeout)*time.Second),
|
||||
libdial.WithKeepAlive(time.Duration(svr.cfg.DialServerKeepAlive)*time.Second),
|
||||
libdial.WithProxy(proxyType, addr),
|
||||
libdial.WithProxyAuth(auth),
|
||||
libdial.WithTLSConfig(tlsConfig),
|
||||
@@ -334,7 +356,14 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
|
||||
svr.visitorCfgs = visitorCfgs
|
||||
svr.cfgMu.Unlock()
|
||||
|
||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
svr.ctlMu.RLock()
|
||||
ctl := svr.ctl
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
if ctl != nil {
|
||||
return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Service) Close() {
|
||||
@@ -343,8 +372,12 @@ func (svr *Service) Close() {
|
||||
|
||||
func (svr *Service) GracefulClose(d time.Duration) {
|
||||
atomic.StoreUint32(&svr.exit, 1)
|
||||
|
||||
svr.ctlMu.RLock()
|
||||
if svr.ctl != nil {
|
||||
svr.ctl.GracefulClose(d)
|
||||
}
|
||||
svr.ctlMu.RUnlock()
|
||||
|
||||
svr.cancel()
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -85,7 +86,7 @@ type STCPVisitor struct {
|
||||
}
|
||||
|
||||
func (sv *STCPVisitor) Run() (err error) {
|
||||
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -174,7 +175,7 @@ type XTCPVisitor struct {
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) Run() (err error) {
|
||||
sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -352,7 +353,7 @@ type SUDPVisitor struct {
|
||||
func (sv *SUDPVisitor) Run() (err error) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
|
||||
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
||||
}
|
||||
@@ -376,29 +377,33 @@ func (sv *SUDPVisitor) Run() (err error) {
|
||||
func (sv *SUDPVisitor) dispatcher() {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
|
||||
var (
|
||||
visitorConn net.Conn
|
||||
err error
|
||||
|
||||
firstPacket *msg.UDPPacket
|
||||
)
|
||||
|
||||
for {
|
||||
// loop for get frpc to frps tcp conn
|
||||
// setup worker
|
||||
// wait worker to finished
|
||||
// retry or exit
|
||||
visitorConn, err := sv.getNewVisitorConn()
|
||||
if err != nil {
|
||||
// check if proxy is closed
|
||||
// if checkCloseCh is close, we will return, other case we will continue to reconnect
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
select {
|
||||
case firstPacket = <-sv.sendCh:
|
||||
if firstPacket == nil {
|
||||
xl.Info("frpc sudp visitor proxy is closed")
|
||||
return
|
||||
default:
|
||||
}
|
||||
case <-sv.checkCloseCh:
|
||||
xl.Info("frpc sudp visitor proxy is closed")
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
visitorConn, err = sv.getNewVisitorConn()
|
||||
if err != nil {
|
||||
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||
continue
|
||||
}
|
||||
|
||||
sv.worker(visitorConn)
|
||||
// visitorConn always be closed when worker done.
|
||||
sv.worker(visitorConn, firstPacket)
|
||||
|
||||
select {
|
||||
case <-sv.checkCloseCh:
|
||||
@@ -406,9 +411,10 @@ func (sv *SUDPVisitor) dispatcher() {
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (sv *SUDPVisitor) worker(workConn net.Conn) {
|
||||
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
xl.Debug("starting sudp proxy worker")
|
||||
|
||||
@@ -462,6 +468,14 @@ func (sv *SUDPVisitor) worker(workConn net.Conn) {
|
||||
}()
|
||||
|
||||
var errRet error
|
||||
if firstPacket != nil {
|
||||
if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil {
|
||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||
return
|
||||
}
|
||||
xl.Trace("send udp package to workConn: %s", firstPacket.Content)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case udpMsg, ok := <-sv.sendCh:
|
||||
|
@@ -15,18 +15,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/frpc"
|
||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
crypto.DefaultSalt = "frp"
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
sub.Execute()
|
||||
}
|
||||
|
@@ -15,13 +15,14 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -41,6 +42,7 @@ const (
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
cfgDir string
|
||||
showVersion bool
|
||||
|
||||
serverAddr string
|
||||
@@ -72,15 +74,12 @@ var (
|
||||
bindPort int
|
||||
|
||||
tlsEnable bool
|
||||
|
||||
kcpDoneCh chan struct{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||
|
||||
kcpDoneCh = make(chan struct{})
|
||||
}
|
||||
|
||||
func RegisterCommonFlags(cmd *cobra.Command) {
|
||||
@@ -104,6 +103,32 @@ var rootCmd = &cobra.Command{
|
||||
return nil
|
||||
}
|
||||
|
||||
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
||||
// Note that it's only designed for testing. It's not guaranteed to be stable.
|
||||
if cfgDir != "" {
|
||||
var wg sync.WaitGroup
|
||||
filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
wg.Add(1)
|
||||
time.Sleep(time.Millisecond)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := runClient(path)
|
||||
if err != nil {
|
||||
fmt.Printf("frpc service error for config file [%s]\n", path)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not show command usage here.
|
||||
err := runClient(cfgFile)
|
||||
if err != nil {
|
||||
@@ -120,12 +145,12 @@ func Execute() {
|
||||
}
|
||||
}
|
||||
|
||||
func handleSignal(svr *client.Service) {
|
||||
ch := make(chan os.Signal)
|
||||
func handleSignal(svr *client.Service, doneCh chan struct{}) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-ch
|
||||
svr.GracefulClose(500 * time.Millisecond)
|
||||
close(kcpDoneCh)
|
||||
close(doneCh)
|
||||
}
|
||||
|
||||
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
||||
@@ -182,18 +207,9 @@ func startService(
|
||||
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
|
||||
cfg.LogMaxDays, cfg.DisableLogColor)
|
||||
|
||||
if cfg.DNSServer != "" {
|
||||
s := cfg.DNSServer
|
||||
if !strings.Contains(s, ":") {
|
||||
s += ":53"
|
||||
}
|
||||
// Change default dns server for frpc
|
||||
net.DefaultResolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return net.Dial("udp", s)
|
||||
},
|
||||
}
|
||||
if cfgFile != "" {
|
||||
log.Trace("start frpc service for config file [%s]", cfgFile)
|
||||
defer log.Trace("frpc service for config file [%s] stopped", cfgFile)
|
||||
}
|
||||
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||
if errRet != nil {
|
||||
@@ -201,9 +217,10 @@ func startService(
|
||||
return
|
||||
}
|
||||
|
||||
kcpDoneCh := make(chan struct{})
|
||||
// Capture the exit signal if we use kcp.
|
||||
if cfg.Protocol == "kcp" {
|
||||
go handleSignal(svr)
|
||||
go handleSignal(svr, kcpDoneCh)
|
||||
}
|
||||
|
||||
err = svr.Run()
|
||||
|
@@ -6,6 +6,13 @@
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
|
||||
# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
|
||||
# dial_server_timeout = 10
|
||||
|
||||
# dial_server_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
# If negative, keep-alive probes are disabled.
|
||||
# dial_server_keepalive = 7200
|
||||
|
||||
# if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set http_proxy here or in global environment variables
|
||||
# it only works when protocol is tcp
|
||||
# http_proxy = http://user:passwd@192.168.1.128:8080
|
||||
@@ -48,6 +55,12 @@ oidc_audience =
|
||||
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
|
||||
oidc_token_endpoint_url =
|
||||
|
||||
# oidc_additional_xxx specifies additional parameters to be sent to the OIDC Token Endpoint.
|
||||
# For example, if you want to specify the "audience" parameter, you can set as follow.
|
||||
# frp will add "audience=<value>" "var1=<value>" to the additional parameters.
|
||||
# oidc_additional_audience = https://dev.auth.com/api/v2/
|
||||
# oidc_additional_var1 = foobar
|
||||
|
||||
# set admin address for control frpc's action by http api such as reload
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
@@ -60,7 +73,8 @@ admin_pwd = admin
|
||||
pool_count = 5
|
||||
|
||||
# if tcp stream multiplexing is used, default is true, it must be same with frps
|
||||
tcp_mux = true
|
||||
# tcp_mux = true
|
||||
|
||||
# specify keep alive interval for tcp mux.
|
||||
# only valid if tcp_mux is true.
|
||||
# tcp_mux_keepalive_interval = 60
|
||||
@@ -91,7 +105,7 @@ tls_enable = true
|
||||
# specify a dns server, so frpc will use this instead of default one
|
||||
# dns_server = 8.8.8.8
|
||||
|
||||
# proxy names you want to start seperated by ','
|
||||
# proxy names you want to start separated by ','
|
||||
# default is empty, means all proxies
|
||||
# start = ssh,dns
|
||||
|
||||
@@ -117,6 +131,10 @@ udp_packet_size = 1500
|
||||
# If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
|
||||
disable_custom_tls_first_byte = false
|
||||
|
||||
# Enable golang pprof handlers in admin listener.
|
||||
# Admin port must be set first.
|
||||
pprof_enable = false
|
||||
|
||||
# 'ssh' is the unique proxy name
|
||||
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
|
||||
[ssh]
|
||||
|
@@ -86,7 +86,6 @@ oidc_audience =
|
||||
# By default, this value is false.
|
||||
oidc_skip_expiry_check = false
|
||||
|
||||
|
||||
# oidc_skip_issuer_check specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer.
|
||||
# By default, this value is false.
|
||||
oidc_skip_issuer_check = false
|
||||
@@ -120,11 +119,16 @@ tls_only = false
|
||||
subdomain_host = frps.com
|
||||
|
||||
# if tcp stream multiplexing is used, default is true
|
||||
tcp_mux = true
|
||||
# tcp_mux = true
|
||||
|
||||
# specify keep alive interval for tcp mux.
|
||||
# only valid if tcp_mux is true.
|
||||
# tcp_mux_keepalive_interval = 60
|
||||
|
||||
# tcp_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
# If negative, keep-alive probes are disabled.
|
||||
# tcp_keepalive = 7200
|
||||
|
||||
# custom 404 page for HTTP requests
|
||||
# custom_404_page = /path/to/404.html
|
||||
|
||||
@@ -133,6 +137,10 @@ tcp_mux = true
|
||||
# It affects the udp and sudp proxy.
|
||||
udp_packet_size = 1500
|
||||
|
||||
# Enable golang pprof handlers in dashboard listener.
|
||||
# Dashboard port must be set first
|
||||
pprof_enable = false
|
||||
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:9000
|
||||
path = /handler
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 41 KiB |
BIN
doc/pic/sponsor_workos.png
Normal file
BIN
doc/pic/sponsor_workos.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
@@ -70,7 +70,7 @@ The response can look like any of the following:
|
||||
|
||||
### Operation
|
||||
|
||||
Currently `Login`, `NewProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
||||
Currently `Login`, `NewProxy`, `CloseProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
||||
|
||||
#### Login
|
||||
|
||||
@@ -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
|
||||
|
||||
Heartbeat from frpc
|
||||
|
4
go.mod
4
go.mod
@@ -6,7 +6,7 @@ require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||
github.com/fatedier/golib v0.1.1-0.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/go-playground/validator/v10 v10.6.1
|
||||
github.com/google/uuid v1.2.0
|
||||
@@ -16,7 +16,7 @@ require (
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/onsi/ginkgo v1.16.4
|
||||
github.com/onsi/gomega v1.13.0
|
||||
github.com/pires/go-proxyproto v0.5.0
|
||||
github.com/pires/go-proxyproto v0.6.2
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/rodaine/table v1.0.1
|
||||
|
8
go.sum
8
go.sum
@@ -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/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
||||
github.com/fatedier/golib v0.1.1-0.20220119075718-78e5cf8c00ee h1:iS0wlj2uZPxh3pciAf/HTzi88Kqu7DPh1jNKgJaFhtI=
|
||||
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 h1:td1FJwN/oz8+9GldeEm3YdBX0Husc0FSPywLesZxi4w=
|
||||
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
@@ -300,8 +300,8 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
|
||||
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
@@ -40,14 +40,20 @@ type OidcClientConfig struct {
|
||||
// It will be used to get an OIDC token if AuthenticationMethod == "oidc".
|
||||
// By default, this value is "".
|
||||
OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"`
|
||||
|
||||
// OidcAdditionalEndpointParams specifies additional parameters to be sent
|
||||
// this field will be transfer to map[string][]string in OIDC token generator
|
||||
// The field will be set by prefix "oidc_additional_"
|
||||
OidcAdditionalEndpointParams map[string]string `ini:"-" json:"oidc_additional_endpoint_params"`
|
||||
}
|
||||
|
||||
func getDefaultOidcClientConf() OidcClientConfig {
|
||||
return OidcClientConfig{
|
||||
OidcClientID: "",
|
||||
OidcClientSecret: "",
|
||||
OidcAudience: "",
|
||||
OidcTokenEndpointURL: "",
|
||||
OidcClientID: "",
|
||||
OidcClientSecret: "",
|
||||
OidcAudience: "",
|
||||
OidcTokenEndpointURL: "",
|
||||
OidcAdditionalEndpointParams: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,11 +94,17 @@ type OidcAuthProvider struct {
|
||||
}
|
||||
|
||||
func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider {
|
||||
eps := make(map[string][]string)
|
||||
for k, v := range cfg.OidcAdditionalEndpointParams {
|
||||
eps[k] = []string{v}
|
||||
}
|
||||
|
||||
tokenGenerator := &clientcredentials.Config{
|
||||
ClientID: cfg.OidcClientID,
|
||||
ClientSecret: cfg.OidcClientSecret,
|
||||
Scopes: []string{cfg.OidcAudience},
|
||||
TokenURL: cfg.OidcTokenEndpointURL,
|
||||
ClientID: cfg.OidcClientID,
|
||||
ClientSecret: cfg.OidcClientSecret,
|
||||
Scopes: []string{cfg.OidcAudience},
|
||||
TokenURL: cfg.OidcTokenEndpointURL,
|
||||
EndpointParams: eps,
|
||||
}
|
||||
|
||||
return &OidcAuthProvider{
|
||||
|
@@ -38,6 +38,11 @@ type ClientCommonConf struct {
|
||||
// ServerPort specifies the port to connect to the server on. By default,
|
||||
// this value is 7000.
|
||||
ServerPort int `ini:"server_port" json:"server_port"`
|
||||
// The maximum amount of time a dial to server will wait for a connect to complete.
|
||||
DialServerTimeout int64 `ini:"dial_server_timeout" json:"dial_server_timeout"`
|
||||
// DialServerKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
// If negative, keep-alive probes are disabled.
|
||||
DialServerKeepAlive int64 `ini:"dial_server_keepalive" json:"dial_server_keepalive"`
|
||||
// ConnectServerLocalIP specifies the address of the client bind when it connect to server.
|
||||
// By default, this value is empty.
|
||||
// this value only use in TCP/Websocket protocol. Not support in KCP protocol.
|
||||
@@ -128,7 +133,7 @@ type ClientCommonConf struct {
|
||||
// It only works when "tls_enable" is valid and tls configuration of server
|
||||
// has been specified.
|
||||
TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
|
||||
// TLSServerName specifices the custom server name of tls certificate. By
|
||||
// TLSServerName specifies the custom server name of tls certificate. By
|
||||
// default, server name if same to ServerAddr.
|
||||
TLSServerName string `ini:"tls_server_name" json:"tls_server_name"`
|
||||
// By default, frpc will connect frps with first custom byte if tls is enabled.
|
||||
@@ -149,6 +154,9 @@ type ClientCommonConf struct {
|
||||
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
|
||||
// Include other config files for proxies.
|
||||
IncludeConfigFiles []string `ini:"includes" json:"includes"`
|
||||
// Enable golang pprof handlers in admin listener.
|
||||
// Admin port must be set first.
|
||||
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
||||
}
|
||||
|
||||
// GetDefaultClientConf returns a client configuration with default values.
|
||||
@@ -157,6 +165,8 @@ func GetDefaultClientConf() ClientCommonConf {
|
||||
ClientConfig: auth.GetDefaultClientConf(),
|
||||
ServerAddr: "0.0.0.0",
|
||||
ServerPort: 7000,
|
||||
DialServerTimeout: 10,
|
||||
DialServerKeepAlive: 7200,
|
||||
HTTPProxy: os.Getenv("http_proxy"),
|
||||
LogFile: "console",
|
||||
LogWay: "console",
|
||||
@@ -185,6 +195,7 @@ func GetDefaultClientConf() ClientCommonConf {
|
||||
Metas: make(map[string]string),
|
||||
UDPPacketSize: 1500,
|
||||
IncludeConfigFiles: make([]string, 0),
|
||||
PprofEnable: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +269,8 @@ func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
|
||||
}
|
||||
|
||||
common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
|
||||
common.ClientConfig.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
|
||||
|
||||
return common, nil
|
||||
}
|
||||
|
||||
|
@@ -261,6 +261,8 @@ func Test_LoadClientCommonConf(t *testing.T) {
|
||||
},
|
||||
ServerAddr: "0.0.0.9",
|
||||
ServerPort: 7009,
|
||||
DialServerTimeout: 10,
|
||||
DialServerKeepAlive: 7200,
|
||||
HTTPProxy: "http://user:passwd@192.168.1.128:8080",
|
||||
LogFile: "./frpc.log9",
|
||||
LogWay: "file",
|
||||
|
@@ -121,6 +121,9 @@ type ServerCommonConf struct {
|
||||
// TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
|
||||
// If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
|
||||
TCPMuxKeepaliveInterval int64 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"`
|
||||
// TCPKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||
// If negative, keep-alive probes are disabled.
|
||||
TCPKeepAlive int64 `ini:"tcp_keepalive" json:"tcp_keepalive"`
|
||||
// Custom404Page specifies a path to a custom 404 page to display. If this
|
||||
// value is "", a default page will be displayed. By default, this value is
|
||||
// "".
|
||||
@@ -167,6 +170,9 @@ type ServerCommonConf struct {
|
||||
// UDPPacketSize specifies the UDP packet size
|
||||
// By default, this value is 1500
|
||||
UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
|
||||
// Enable golang pprof handlers in dashboard listener.
|
||||
// Dashboard port must be set first.
|
||||
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
||||
}
|
||||
|
||||
// GetDefaultServerConf returns a server configuration with reasonable
|
||||
@@ -198,6 +204,7 @@ func GetDefaultServerConf() ServerCommonConf {
|
||||
SubDomainHost: "",
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
MaxPortsPerClient: 0,
|
||||
@@ -210,6 +217,7 @@ func GetDefaultServerConf() ServerCommonConf {
|
||||
Custom404Page: "",
|
||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||
UDPPacketSize: 1500,
|
||||
PprofEnable: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -140,6 +140,7 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
||||
SubDomainHost: "frps.com",
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
UDPPacketSize: 1509,
|
||||
|
||||
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
||||
@@ -191,6 +192,7 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
||||
DetailedErrorsToClient: true,
|
||||
TCPMux: true,
|
||||
TCPMuxKeepaliveInterval: 60,
|
||||
TCPKeepAlive: 7200,
|
||||
AllowPorts: make(map[int]struct{}),
|
||||
MaxPoolCount: 5,
|
||||
HeartbeatTimeout: 90,
|
||||
|
@@ -29,11 +29,11 @@ func init() {
|
||||
glbEnvs = make(map[string]string)
|
||||
envs := os.Environ()
|
||||
for _, env := range envs {
|
||||
kv := strings.Split(env, "=")
|
||||
if len(kv) != 2 {
|
||||
pair := strings.SplitN(env, "=", 2)
|
||||
if len(pair) != 2 {
|
||||
continue
|
||||
}
|
||||
glbEnvs[kv[0]] = kv[1]
|
||||
glbEnvs[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
type Manager struct {
|
||||
loginPlugins []Plugin
|
||||
newProxyPlugins []Plugin
|
||||
closeProxyPlugins []Plugin
|
||||
pingPlugins []Plugin
|
||||
newWorkConnPlugins []Plugin
|
||||
newUserConnPlugins []Plugin
|
||||
@@ -35,6 +37,7 @@ func NewManager() *Manager {
|
||||
return &Manager{
|
||||
loginPlugins: make([]Plugin, 0),
|
||||
newProxyPlugins: make([]Plugin, 0),
|
||||
closeProxyPlugins: make([]Plugin, 0),
|
||||
pingPlugins: make([]Plugin, 0),
|
||||
newWorkConnPlugins: make([]Plugin, 0),
|
||||
newUserConnPlugins: make([]Plugin, 0),
|
||||
@@ -48,6 +51,9 @@ func (m *Manager) Register(p Plugin) {
|
||||
if p.IsSupport(OpNewProxy) {
|
||||
m.newProxyPlugins = append(m.newProxyPlugins, p)
|
||||
}
|
||||
if p.IsSupport(OpCloseProxy) {
|
||||
m.closeProxyPlugins = append(m.closeProxyPlugins, p)
|
||||
}
|
||||
if p.IsSupport(OpPing) {
|
||||
m.pingPlugins = append(m.pingPlugins, p)
|
||||
}
|
||||
@@ -127,6 +133,32 @@ func (m *Manager) NewProxy(content *NewProxyContent) (*NewProxyContent, error) {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (m *Manager) CloseProxy(content *CloseProxyContent) error {
|
||||
if len(m.closeProxyPlugins) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := make([]string, 0)
|
||||
reqid, _ := util.RandID()
|
||||
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||
ctx := xlog.NewContext(context.Background(), xl)
|
||||
ctx = NewReqidContext(ctx, reqid)
|
||||
|
||||
for _, p := range m.closeProxyPlugins {
|
||||
_, _, err := p.Handle(ctx, OpCloseProxy, *content)
|
||||
if err != nil {
|
||||
xl.Warn("send CloseProxy request to plugin [%s] error: %v", p.Name(), err)
|
||||
errs = append(errs, fmt.Sprintf("[%s]: %v", p.Name(), err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("send CloseProxy request to plugin errors: %s", strings.Join(errs, "; "))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Ping(content *PingContent) (*PingContent, error) {
|
||||
if len(m.pingPlugins) == 0 {
|
||||
return content, nil
|
||||
|
@@ -23,6 +23,7 @@ const (
|
||||
|
||||
OpLogin = "Login"
|
||||
OpNewProxy = "NewProxy"
|
||||
OpCloseProxy = "CloseProxy"
|
||||
OpPing = "Ping"
|
||||
OpNewWorkConn = "NewWorkConn"
|
||||
OpNewUserConn = "NewUserConn"
|
||||
|
@@ -48,6 +48,11 @@ type NewProxyContent struct {
|
||||
msg.NewProxy
|
||||
}
|
||||
|
||||
type CloseProxyContent struct {
|
||||
User UserInfo `json:"user"`
|
||||
msg.CloseProxy
|
||||
}
|
||||
|
||||
type PingContent struct {
|
||||
User UserInfo `json:"user"`
|
||||
msg.Ping
|
||||
|
@@ -100,6 +100,8 @@ func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Conf
|
||||
base.Certificates = []tls.Certificate{*cert}
|
||||
}
|
||||
|
||||
base.ServerName = serverName
|
||||
|
||||
if caPath != "" {
|
||||
pool, err := newCertPool(caPath)
|
||||
if err != nil {
|
||||
@@ -107,7 +109,6 @@ func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Conf
|
||||
}
|
||||
|
||||
base.RootCAs = pool
|
||||
base.ServerName = serverName
|
||||
base.InsecureSkipVerify = false
|
||||
} else {
|
||||
base.InsecureSkipVerify = true
|
||||
|
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -163,7 +164,7 @@ type UDPListener struct {
|
||||
}
|
||||
|
||||
func ListenUDP(bindAddr string, bindPort int) (l *UDPListener, err error) {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(bindAddr, strconv.Itoa(bindPort)))
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
|
@@ -2,9 +2,9 @@ package net
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
@@ -52,7 +52,7 @@ func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
|
||||
}
|
||||
|
||||
func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error) {
|
||||
tcpLn, err := net.Listen("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
||||
tcpLn, err := net.Listen("tcp", net.JoinHostPort(bindAddr, strconv.Itoa(bindPort)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -48,7 +48,7 @@ func readHTTPConnectRequest(rd io.Reader) (host string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
host = util.GetHostFromAddr(req.Host)
|
||||
host, _ = util.CanonicalHost(req.Host)
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -34,17 +34,6 @@ func OkResponse() *http.Response {
|
||||
return res
|
||||
}
|
||||
|
||||
// TODO: use "CanonicalHost" func to replace all "GetHostFromAddr" func.
|
||||
func GetHostFromAddr(addr string) (host string) {
|
||||
strs := strings.Split(addr, ":")
|
||||
if len(strs) > 1 {
|
||||
host = strs[0]
|
||||
} else {
|
||||
host = addr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// canonicalHost strips port from host if present and returns the canonicalized
|
||||
// host name.
|
||||
func CanonicalHost(host string) (string, error) {
|
||||
|
@@ -19,8 +19,11 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RandID return a rand string used in frp.
|
||||
@@ -52,7 +55,7 @@ func CanonicalAddr(host string, port int) (addr string) {
|
||||
if port == 80 || port == 443 {
|
||||
addr = host
|
||||
} else {
|
||||
addr = fmt.Sprintf("%s:%d", host, port)
|
||||
addr = net.JoinHostPort(host, strconv.Itoa(port))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -108,3 +111,17 @@ func GenerateResponseErrorString(summary string, err error, detailed bool) strin
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Duration {
|
||||
min := int64(minRatio * 1000.0)
|
||||
max := int64(maxRatio * 1000.0)
|
||||
var n int64
|
||||
if max <= min {
|
||||
n = min
|
||||
} else {
|
||||
n = mathrand.Int63n(max-min) + min
|
||||
}
|
||||
d := duration * time.Duration(n) / time.Duration(1000)
|
||||
time.Sleep(d)
|
||||
return d
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var version string = "0.39.0"
|
||||
var version string = "0.42.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
@@ -59,7 +59,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "http"
|
||||
url := req.Context().Value(RouteInfoURL).(string)
|
||||
oldHost := util.GetHostFromAddr(req.Context().Value(RouteInfoHost).(string))
|
||||
oldHost, _ := util.CanonicalHost(req.Context().Value(RouteInfoHost).(string))
|
||||
rc := rp.GetRouteConfig(oldHost, url)
|
||||
if rc != nil {
|
||||
if rc.RewriteHost != "" {
|
||||
@@ -81,7 +81,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
IdleConnTimeout: 60 * time.Second,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
url := ctx.Value(RouteInfoURL).(string)
|
||||
host := util.GetHostFromAddr(ctx.Value(RouteInfoHost).(string))
|
||||
host, _ := util.CanonicalHost(ctx.Value(RouteInfoHost).(string))
|
||||
remote := ctx.Value(RouteInfoRemote).(string)
|
||||
return rp.CreateConnection(host, url, remote)
|
||||
},
|
||||
@@ -89,7 +89,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
BufferPool: newWrapPool(),
|
||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
frpLog.Warn("do http proxy request error: %v", err)
|
||||
frpLog.Warn("do http proxy request [host: %s] error: %v", req.Host, err)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
rw.Write(getNotFoundPageContent())
|
||||
},
|
||||
@@ -191,7 +191,7 @@ func (rp *HTTPReverseProxy) getVhost(domain string, location string) (vr *Router
|
||||
}
|
||||
|
||||
func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
domain := util.GetHostFromAddr(req.Host)
|
||||
domain, _ := util.CanonicalHost(req.Host)
|
||||
location := req.URL.Path
|
||||
user, passwd, _ := req.BasicAuth()
|
||||
if !rp.CheckAuth(domain, location, user, passwd) {
|
||||
|
@@ -258,7 +258,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
||||
case workConn, ok = <-ctl.workConnCh:
|
||||
if !ok {
|
||||
err = frpErr.ErrCtlClosed
|
||||
xl.Warn("no work connections avaiable, %v", err)
|
||||
xl.Warn("no work connections available, %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -376,6 +376,20 @@ func (ctl *Control) stoper() {
|
||||
pxy.Close()
|
||||
ctl.pxyManager.Del(pxy.GetName())
|
||||
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||
|
||||
notifyContent := &plugin.CloseProxyContent{
|
||||
User: plugin.UserInfo{
|
||||
User: ctl.loginMsg.User,
|
||||
Metas: ctl.loginMsg.Metas,
|
||||
RunID: ctl.loginMsg.RunID,
|
||||
},
|
||||
CloseProxy: msg.CloseProxy{
|
||||
ProxyName: pxy.GetName(),
|
||||
},
|
||||
}
|
||||
go func() {
|
||||
ctl.pluginManager.CloseProxy(notifyContent)
|
||||
}()
|
||||
}
|
||||
|
||||
ctl.allShutdown.Done()
|
||||
@@ -564,5 +578,20 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||
ctl.mu.Unlock()
|
||||
|
||||
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||
|
||||
notifyContent := &plugin.CloseProxyContent{
|
||||
User: plugin.UserInfo{
|
||||
User: ctl.loginMsg.User,
|
||||
Metas: ctl.loginMsg.Metas,
|
||||
RunID: ctl.loginMsg.RunID,
|
||||
},
|
||||
CloseProxy: msg.CloseProxy{
|
||||
ProxyName: pxy.GetName(),
|
||||
},
|
||||
}
|
||||
go func() {
|
||||
ctl.pluginManager.CloseProxy(notifyContent)
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ package server
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
@@ -27,8 +28,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
httpServerReadTimeout = 10 * time.Second
|
||||
httpServerWriteTimeout = 10 * time.Second
|
||||
httpServerReadTimeout = 60 * time.Second
|
||||
httpServerWriteTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
func (svr *Service) RunDashboardServer(address string) (err error) {
|
||||
@@ -36,6 +37,15 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/healthz", svr.Healthz)
|
||||
|
||||
// debug
|
||||
if svr.cfg.PprofEnable {
|
||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||
}
|
||||
|
||||
subRouter := router.NewRoute().Subrouter()
|
||||
|
||||
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
|
||||
|
@@ -15,8 +15,8 @@
|
||||
package group
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/server/ports"
|
||||
@@ -101,7 +101,7 @@ func (tg *TCPGroup) Listen(proxyName string, group string, groupKey string, addr
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tcpLn, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port))
|
||||
tcpLn, errRet := net.Listen("tcp", net.JoinHostPort(addr, strconv.Itoa(port)))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
|
@@ -2,8 +2,8 @@ package ports
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -134,7 +134,7 @@ func (pm *Manager) Acquire(name string, port int) (realPort int, err error) {
|
||||
|
||||
func (pm *Manager) isPortAvailable(port int) bool {
|
||||
if pm.netType == "udp" {
|
||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pm.bindAddr, port))
|
||||
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(pm.bindAddr, strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -146,7 +146,7 @@ func (pm *Manager) isPortAvailable(port int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port))
|
||||
l, err := net.Listen(pm.netType, net.JoinHostPort(pm.bindAddr, strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ package proxy
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
)
|
||||
@@ -54,7 +55,7 @@ func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
|
||||
pxy.rc.TCPPortManager.Release(pxy.realPort)
|
||||
}
|
||||
}()
|
||||
listener, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
|
||||
listener, errRet := net.Listen("tcp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realPort)))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
@@ -70,7 +71,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
|
||||
|
||||
remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
|
||||
pxy.cfg.RemotePort = pxy.realPort
|
||||
addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
|
||||
addr, errRet := net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realPort)))
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
|
@@ -124,7 +124,8 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
// Create tcpmux httpconnect multiplexer.
|
||||
if cfg.TCPMuxHTTPConnectPort > 0 {
|
||||
var l net.Listener
|
||||
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort))
|
||||
address := net.JoinHostPort(cfg.ProxyBindAddr, strconv.Itoa(cfg.TCPMuxHTTPConnectPort))
|
||||
l, err = net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Create server listener error, %v", err)
|
||||
return
|
||||
@@ -135,7 +136,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
err = fmt.Errorf("Create vhost tcpMuxer error, %v", err)
|
||||
return
|
||||
}
|
||||
log.Info("tcpmux httpconnect multiplexer listen on %s:%d", cfg.ProxyBindAddr, cfg.TCPMuxHTTPConnectPort)
|
||||
log.Info("tcpmux httpconnect multiplexer listen on %s", address)
|
||||
}
|
||||
|
||||
// Init all plugins
|
||||
@@ -185,6 +186,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
}
|
||||
|
||||
svr.muxer = mux.NewMux(ln)
|
||||
svr.muxer.SetKeepAlive(time.Duration(cfg.TCPKeepAlive) * time.Second)
|
||||
go svr.muxer.Serve()
|
||||
ln = svr.muxer.DefaultListener()
|
||||
|
||||
@@ -199,7 +201,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
err = fmt.Errorf("Listen on kcp address udp %s error: %v", address, err)
|
||||
return
|
||||
}
|
||||
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KCPBindPort)
|
||||
log.Info("frps kcp listen on udp %s", address)
|
||||
}
|
||||
|
||||
// Listen for accepting connections from client using websocket protocol.
|
||||
@@ -232,7 +234,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
}
|
||||
}
|
||||
go server.Serve(l)
|
||||
log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHTTPPort)
|
||||
log.Info("http service listen on %s", address)
|
||||
}
|
||||
|
||||
// Create https vhost muxer.
|
||||
@@ -288,7 +290,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
||||
return
|
||||
}
|
||||
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
|
||||
log.Info("Dashboard listen on %s", address)
|
||||
statsEnable = true
|
||||
}
|
||||
if statsEnable {
|
||||
|
@@ -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),
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// RunProcesses run multiple processes from templates.
|
||||
// The first template should always be frps.
|
||||
func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) {
|
||||
func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []string) ([]*process.Process, []*process.Process) {
|
||||
templates := make([]string, 0, len(serverTemplates)+len(clientTemplates))
|
||||
for _, t := range serverTemplates {
|
||||
templates = append(templates, t)
|
||||
@@ -28,6 +28,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
||||
f.usedPorts[name] = port
|
||||
}
|
||||
|
||||
currentServerProcesses := make([]*process.Process, 0, len(serverTemplates))
|
||||
for i := range serverTemplates {
|
||||
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
|
||||
err = os.WriteFile(path, []byte(outs[i]), 0666)
|
||||
@@ -37,11 +38,13 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
||||
p := process.NewWithEnvs(TestContext.FRPServerPath, []string{"-c", path}, f.osEnvs)
|
||||
f.serverConfPaths = append(f.serverConfPaths, path)
|
||||
f.serverProcesses = append(f.serverProcesses, p)
|
||||
currentServerProcesses = append(currentServerProcesses, p)
|
||||
err = p.Start()
|
||||
ExpectNoError(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
currentClientProcesses := make([]*process.Process, 0, len(clientTemplates))
|
||||
for i := range clientTemplates {
|
||||
index := i + len(serverTemplates)
|
||||
path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i))
|
||||
@@ -52,11 +55,14 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
||||
p := process.NewWithEnvs(TestContext.FRPClientPath, []string{"-c", path}, f.osEnvs)
|
||||
f.clientConfPaths = append(f.clientConfPaths, path)
|
||||
f.clientProcesses = append(f.clientProcesses, p)
|
||||
currentClientProcesses = append(currentClientProcesses, p)
|
||||
err = p.Start()
|
||||
ExpectNoError(err)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
return currentServerProcesses, currentClientProcesses
|
||||
}
|
||||
|
||||
func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) {
|
||||
|
@@ -2,7 +2,6 @@ package httpserver
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -97,7 +96,7 @@ func (s *Server) Close() error {
|
||||
}
|
||||
|
||||
func (s *Server) initListener() (err error) {
|
||||
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
|
||||
s.l, err = net.Listen("tcp", net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort)))
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
libnet "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/rpc"
|
||||
@@ -99,7 +100,7 @@ func (s *Server) Close() error {
|
||||
func (s *Server) initListener() (err error) {
|
||||
switch s.netType {
|
||||
case TCP:
|
||||
s.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.bindAddr, s.bindPort))
|
||||
s.l, err = net.Listen("tcp", net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort)))
|
||||
case UDP:
|
||||
s.l, err = libnet.ListenUDP(s.bindAddr, s.bindPort)
|
||||
case Unix:
|
||||
|
@@ -3,6 +3,7 @@ package port
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
@@ -57,7 +58,7 @@ func (pa *Allocator) GetByName(portName string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
l, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
// Maybe not controlled by us, mark it used.
|
||||
pa.used.Insert(port)
|
||||
@@ -65,7 +66,7 @@ func (pa *Allocator) GetByName(portName string) int {
|
||||
}
|
||||
l.Close()
|
||||
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
@@ -150,7 +150,7 @@ var _ = Describe("[Feature: Server-Plugins]", func() {
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = 0
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
`, framework.TCPEchoServerPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
@@ -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() {
|
||||
newFunc := func() *plugin.Request {
|
||||
var r plugin.Request
|
||||
|
Reference in New Issue
Block a user