mirror of
https://github.com/fatedier/frp.git
synced 2025-06-16 08:08:20 +00:00
Compare commits
11 Commits
2225a1781f
...
cceab7e1b1
Author | SHA1 | Date | |
---|---|---|---|
|
cceab7e1b1 | ||
|
9aef3b9944 | ||
|
341a5e3e3a | ||
|
c7a0cfc66d | ||
|
555db9d272 | ||
|
98068402c8 | ||
|
4915852b9c | ||
|
756dd1ad5e | ||
|
c71efde303 | ||
|
9f029e3248 | ||
|
8095075719 |
3
Makefile
3
Makefile
@ -19,6 +19,9 @@ fmt:
|
|||||||
fmt-more:
|
fmt-more:
|
||||||
gofumpt -l -w .
|
gofumpt -l -w .
|
||||||
|
|
||||||
|
gci:
|
||||||
|
gci write -s standard -s default -s "prefix(github.com/fatedier/frp/)" ./
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet ./...
|
go vet ./...
|
||||||
|
|
||||||
|
25
README.md
25
README.md
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# frp
|
# frp
|
||||||
|
|
||||||
[](https://circleci.com/gh/fatedier/frp)
|
[](https://circleci.com/gh/fatedier/frp)
|
||||||
@ -12,12 +11,7 @@
|
|||||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||||
</a>
|
</a>
|
||||||
<a> </a>
|
|
||||||
<a href="https://asocks.com/c/vDu6Dk" target="_blank">
|
|
||||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_asocks.jpg">
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!--gold sponsors end-->
|
<!--gold sponsors end-->
|
||||||
|
|
||||||
## What is frp?
|
## What is frp?
|
||||||
@ -349,20 +343,15 @@ Configure `frps` same as above.
|
|||||||
|
|
||||||
Note that it may not work with all types of NAT devices. You might want to fallback to stcp if xtcp doesn't work.
|
Note that it may not work with all types of NAT devices. You might want to fallback to stcp if xtcp doesn't work.
|
||||||
|
|
||||||
1. In `frps.ini` configure a UDP port for xtcp:
|
1. Start `frpc` on machine B, and expose the SSH port. Note that the `remote_port` field is removed:
|
||||||
|
|
||||||
```ini
|
|
||||||
# frps.ini
|
|
||||||
bind_udp_port = 7001
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Start `frpc` on machine B, and expose the SSH port. Note that the `remote_port` field is removed:
|
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[common]
|
[common]
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
|
# set up a new stun server if the default one is not available.
|
||||||
|
# nat_hole_stun_server = xxx
|
||||||
|
|
||||||
[p2p_ssh]
|
[p2p_ssh]
|
||||||
type = xtcp
|
type = xtcp
|
||||||
@ -371,13 +360,15 @@ Note that it may not work with all types of NAT devices. You might want to fallb
|
|||||||
local_port = 22
|
local_port = 22
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Start another `frpc` (typically on another machine C) with the configuration to connect to SSH using P2P mode:
|
2. Start another `frpc` (typically on another machine C) with the configuration to connect to SSH using P2P mode:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
[common]
|
[common]
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
|
# set up a new stun server if the default one is not available.
|
||||||
|
# nat_hole_stun_server = xxx
|
||||||
|
|
||||||
[p2p_ssh_visitor]
|
[p2p_ssh_visitor]
|
||||||
type = xtcp
|
type = xtcp
|
||||||
@ -386,9 +377,11 @@ Note that it may not work with all types of NAT devices. You might want to fallb
|
|||||||
sk = abcdefg
|
sk = abcdefg
|
||||||
bind_addr = 127.0.0.1
|
bind_addr = 127.0.0.1
|
||||||
bind_port = 6000
|
bind_port = 6000
|
||||||
|
# when automatic tunnel persistence is required, set it to true
|
||||||
|
keep_tunnel_open = false
|
||||||
```
|
```
|
||||||
|
|
||||||
4. On machine C, connect to SSH on machine B, using this command:
|
3. On machine C, connect to SSH on machine B, using this command:
|
||||||
|
|
||||||
`ssh -oPort=6000 127.0.0.1`
|
`ssh -oPort=6000 127.0.0.1`
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# frp
|
# frp
|
||||||
|
|
||||||
[](https://travis-ci.org/fatedier/frp)
|
[](https://circleci.com/gh/fatedier/frp)
|
||||||
[](https://github.com/fatedier/frp/releases)
|
[](https://github.com/fatedier/frp/releases)
|
||||||
|
|
||||||
[README](README.md) | [中文文档](README_zh.md)
|
[README](README.md) | [中文文档](README_zh.md)
|
||||||
@ -13,10 +13,6 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
|
|||||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||||
</a>
|
</a>
|
||||||
<a> </a>
|
|
||||||
<a href="https://asocks.com/c/vDu6Dk" target="_blank">
|
|
||||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_asocks.jpg">
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
<!--gold sponsors end-->
|
<!--gold sponsors end-->
|
||||||
|
|
||||||
|
19
Release.md
19
Release.md
@ -1,8 +1,19 @@
|
|||||||
|
## Notes
|
||||||
|
|
||||||
|
We have thoroughly refactored xtcp in this version to improve its penetration rate and stability.
|
||||||
|
|
||||||
|
In this version, different penetration strategies can be attempted by retrying connections multiple times. Once a hole is successfully punched, the strategy will be recorded in the server cache for future reuse. When new users connect, the successfully penetrated tunnel can be reused instead of punching a new hole.
|
||||||
|
|
||||||
|
**Due to a significant refactor of xtcp, this version is not compatible with previous versions of xtcp.**
|
||||||
|
|
||||||
|
**To use features related to xtcp, both frpc and frps need to be updated to the latest version.**
|
||||||
|
|
||||||
### New
|
### New
|
||||||
|
|
||||||
* The `httpconnect` type in `tcpmux` now supports authentication through the parameters `http_user` and `http_pwd`.
|
* The frpc has added the `nathole discover` command for testing the NAT type of the current network.
|
||||||
|
* `XTCP` has been refactored, resulting in a significant improvement in the success rate of penetration.
|
||||||
|
* When verifying passwords, use `subtle.ConstantTimeCompare` and introduce a certain delay when the password is incorrect.
|
||||||
|
|
||||||
### Improved
|
### Fix
|
||||||
|
|
||||||
* The web framework has been upgraded to vue3 + element-plus, and the dashboard has added some information display and supports dark mode.
|
* Fix the problem of lagging when opening multiple table entries in the frps dashboard.
|
||||||
* The e2e testing has been switched to ginkgo v2.
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>frps dashboard</title>
|
<title>frps dashboard</title>
|
||||||
<script type="module" crossorigin src="./index-93e38bbf.js"></script>
|
<script type="module" crossorigin src="./index-ea3edf22.js"></script>
|
||||||
<link rel="stylesheet" href="./index-1e0c7400.css">
|
<link rel="stylesheet" href="./index-1e0c7400.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -48,7 +48,7 @@ func (svr *Service) RunAdminServer(address string) (err error) {
|
|||||||
|
|
||||||
subRouter := router.NewRoute().Subrouter()
|
subRouter := router.NewRoute().Subrouter()
|
||||||
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
|
||||||
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
subRouter.Use(utilnet.NewHTTPAuthMiddleware(user, passwd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||||
|
|
||||||
// api, see admin_api.go
|
// api, see admin_api.go
|
||||||
subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||||
@ -58,7 +58,7 @@ func (svr *Service) RunAdminServer(address string) (err error) {
|
|||||||
|
|
||||||
// view
|
// view
|
||||||
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||||
subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
subRouter.PathPrefix("/static/").Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||||
})
|
})
|
||||||
|
@ -25,10 +25,11 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GeneralResponse struct {
|
type GeneralResponse struct {
|
||||||
@ -90,7 +91,7 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
|||||||
Status: status.Phase,
|
Status: status.Phase,
|
||||||
Err: status.Err,
|
Err: status.Err,
|
||||||
}
|
}
|
||||||
baseCfg := status.Cfg.GetBaseInfo()
|
baseCfg := status.Cfg.GetBaseConfig()
|
||||||
if baseCfg.LocalPort != 0 {
|
if baseCfg.LocalPort != 0 {
|
||||||
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
|
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
|
||||||
}
|
}
|
||||||
@ -98,7 +99,7 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
|||||||
|
|
||||||
if status.Err == "" {
|
if status.Err == "" {
|
||||||
psr.RemoteAddr = status.RemoteAddr
|
psr.RemoteAddr = status.RemoteAddr
|
||||||
if util.InSlice(status.Type, []string{"tcp", "udp"}) {
|
if lo.Contains([]string{"tcp", "udp"}, status.Type) {
|
||||||
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,21 @@ import (
|
|||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
|
"github.com/fatedier/frp/client/visitor"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Control struct {
|
type Control struct {
|
||||||
// uniq id got from frps, attach it in loginMsg
|
// service context
|
||||||
|
ctx context.Context
|
||||||
|
xl *xlog.Logger
|
||||||
|
|
||||||
|
// Unique ID obtained from frps.
|
||||||
|
// It should be attached to the login message when reconnecting.
|
||||||
runID string
|
runID string
|
||||||
|
|
||||||
// manage all proxies
|
// manage all proxies
|
||||||
@ -40,7 +47,7 @@ type Control struct {
|
|||||||
pm *proxy.Manager
|
pm *proxy.Manager
|
||||||
|
|
||||||
// manage all visitors
|
// manage all visitors
|
||||||
vm *VisitorManager
|
vm *visitor.Manager
|
||||||
|
|
||||||
// control connection
|
// control connection
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
@ -68,16 +75,10 @@ type Control struct {
|
|||||||
writerShutdown *shutdown.Shutdown
|
writerShutdown *shutdown.Shutdown
|
||||||
msgHandlerShutdown *shutdown.Shutdown
|
msgHandlerShutdown *shutdown.Shutdown
|
||||||
|
|
||||||
// The UDP port that the server is listening on
|
|
||||||
serverUDPPort int
|
|
||||||
|
|
||||||
xl *xlog.Logger
|
|
||||||
|
|
||||||
// service context
|
|
||||||
ctx context.Context
|
|
||||||
|
|
||||||
// sets authentication based on selected method
|
// sets authentication based on selected method
|
||||||
authSetter auth.Setter
|
authSetter auth.Setter
|
||||||
|
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewControl(
|
func NewControl(
|
||||||
@ -85,11 +86,12 @@ func NewControl(
|
|||||||
clientCfg config.ClientCommonConf,
|
clientCfg config.ClientCommonConf,
|
||||||
pxyCfgs map[string]config.ProxyConf,
|
pxyCfgs map[string]config.ProxyConf,
|
||||||
visitorCfgs map[string]config.VisitorConf,
|
visitorCfgs map[string]config.VisitorConf,
|
||||||
serverUDPPort int,
|
|
||||||
authSetter auth.Setter,
|
authSetter auth.Setter,
|
||||||
) *Control {
|
) *Control {
|
||||||
// new xlog instance
|
// new xlog instance
|
||||||
ctl := &Control{
|
ctl := &Control{
|
||||||
|
ctx: ctx,
|
||||||
|
xl: xlog.FromContextSafe(ctx),
|
||||||
runID: runID,
|
runID: runID,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
cm: cm,
|
cm: cm,
|
||||||
@ -102,14 +104,12 @@ func NewControl(
|
|||||||
readerShutdown: shutdown.New(),
|
readerShutdown: shutdown.New(),
|
||||||
writerShutdown: shutdown.New(),
|
writerShutdown: shutdown.New(),
|
||||||
msgHandlerShutdown: shutdown.New(),
|
msgHandlerShutdown: shutdown.New(),
|
||||||
serverUDPPort: serverUDPPort,
|
|
||||||
xl: xlog.FromContextSafe(ctx),
|
|
||||||
ctx: ctx,
|
|
||||||
authSetter: authSetter,
|
authSetter: authSetter,
|
||||||
}
|
}
|
||||||
ctl.pm = proxy.NewManager(ctl.ctx, ctl.sendCh, clientCfg, serverUDPPort)
|
ctl.msgTransporter = transport.NewMessageTransporter(ctl.sendCh)
|
||||||
|
ctl.pm = proxy.NewManager(ctl.ctx, clientCfg, ctl.msgTransporter)
|
||||||
|
|
||||||
ctl.vm = NewVisitorManager(ctl.ctx, ctl)
|
ctl.vm = visitor.NewManager(ctl.ctx, ctl.runID, ctl.clientCfg, ctl.connectServer, ctl.msgTransporter)
|
||||||
ctl.vm.Reload(visitorCfgs)
|
ctl.vm.Reload(visitorCfgs)
|
||||||
return ctl
|
return ctl
|
||||||
}
|
}
|
||||||
@ -173,6 +173,16 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) HandleNatHoleResp(inMsg *msg.NatHoleResp) {
|
||||||
|
xl := ctl.xl
|
||||||
|
|
||||||
|
// Dispatch the NatHoleResp message to the related proxy.
|
||||||
|
ok := ctl.msgTransporter.DispatchWithType(inMsg, msg.TypeNameNatHoleResp, inMsg.TransactionID)
|
||||||
|
if !ok {
|
||||||
|
xl.Trace("dispatch NatHoleResp message to related proxy error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ctl *Control) Close() error {
|
func (ctl *Control) Close() error {
|
||||||
return ctl.GracefulClose(0)
|
return ctl.GracefulClose(0)
|
||||||
}
|
}
|
||||||
@ -188,7 +198,7 @@ func (ctl *Control) GracefulClose(d time.Duration) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClosedDoneCh returns a channel which will be closed after all resources are released
|
// ClosedDoneCh returns a channel that will be closed after all resources are released
|
||||||
func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
||||||
return ctl.closedDoneCh
|
return ctl.closedDoneCh
|
||||||
}
|
}
|
||||||
@ -250,7 +260,7 @@ func (ctl *Control) writer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// msgHandler handles all channel events and do corresponding operations.
|
// msgHandler handles all channel events and performs corresponding operations.
|
||||||
func (ctl *Control) msgHandler() {
|
func (ctl *Control) msgHandler() {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -307,6 +317,8 @@ func (ctl *Control) msgHandler() {
|
|||||||
go ctl.HandleReqWorkConn(m)
|
go ctl.HandleReqWorkConn(m)
|
||||||
case *msg.NewProxyResp:
|
case *msg.NewProxyResp:
|
||||||
ctl.HandleNewProxyResp(m)
|
ctl.HandleNewProxyResp(m)
|
||||||
|
case *msg.NatHoleResp:
|
||||||
|
ctl.HandleNatHoleResp(m)
|
||||||
case *msg.Pong:
|
case *msg.Pong:
|
||||||
if m.Error != "" {
|
if m.Error != "" {
|
||||||
xl.Error("Pong contains error: %s", m.Error)
|
xl.Error("Pong contains error: %s", m.Error)
|
||||||
|
47
client/proxy/general_tcp.go
Normal file
47
client/proxy/general_tcp.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pxyConfs := []config.ProxyConf{
|
||||||
|
&config.TCPProxyConf{},
|
||||||
|
&config.HTTPProxyConf{},
|
||||||
|
&config.HTTPSProxyConf{},
|
||||||
|
&config.STCPProxyConf{},
|
||||||
|
&config.TCPMuxProxyConf{},
|
||||||
|
}
|
||||||
|
for _, cfg := range pxyConfs {
|
||||||
|
RegisterProxyFactory(reflect.TypeOf(cfg), NewGeneralTCPProxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneralTCPProxy is a general implementation of Proxy interface for TCP protocol.
|
||||||
|
// If the default GeneralTCPProxy cannot meet the requirements, you can customize
|
||||||
|
// the implementation of the Proxy interface.
|
||||||
|
type GeneralTCPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGeneralTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
|
||||||
|
return &GeneralTCPProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
}
|
||||||
|
}
|
@ -19,28 +19,31 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
libio "github.com/fatedier/golib/io"
|
||||||
frpIo "github.com/fatedier/golib/io"
|
|
||||||
libdial "github.com/fatedier/golib/net/dial"
|
libdial "github.com/fatedier/golib/net/dial"
|
||||||
"github.com/fatedier/golib/pool"
|
|
||||||
fmux "github.com/hashicorp/yamux"
|
|
||||||
pp "github.com/pires/go-proxyproto"
|
pp "github.com/pires/go-proxyproto"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||||
"github.com/fatedier/frp/pkg/proto/udp"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/limit"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, config.ProxyConf) Proxy{}
|
||||||
|
|
||||||
|
func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, config.ProxyConf) Proxy) {
|
||||||
|
proxyFactoryRegistry[proxyConfType] = factory
|
||||||
|
}
|
||||||
|
|
||||||
// Proxy defines how to handle work connections for different proxy type.
|
// Proxy defines how to handle work connections for different proxy type.
|
||||||
type Proxy interface {
|
type Proxy interface {
|
||||||
Run() error
|
Run() error
|
||||||
@ -51,715 +54,101 @@ type Proxy interface {
|
|||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
|
func NewProxy(
|
||||||
|
ctx context.Context,
|
||||||
|
pxyConf config.ProxyConf,
|
||||||
|
clientCfg config.ClientCommonConf,
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
) (pxy Proxy) {
|
||||||
var limiter *rate.Limiter
|
var limiter *rate.Limiter
|
||||||
limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
|
limitBytes := pxyConf.GetBaseConfig().BandwidthLimit.Bytes()
|
||||||
if limitBytes > 0 && pxyConf.GetBaseInfo().BandwidthLimitMode == config.BandwidthLimitModeClient {
|
if limitBytes > 0 && pxyConf.GetBaseConfig().BandwidthLimitMode == config.BandwidthLimitModeClient {
|
||||||
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
baseProxy := BaseProxy{
|
baseProxy := BaseProxy{
|
||||||
clientCfg: clientCfg,
|
baseProxyConfig: pxyConf.GetBaseConfig(),
|
||||||
serverUDPPort: serverUDPPort,
|
clientCfg: clientCfg,
|
||||||
limiter: limiter,
|
limiter: limiter,
|
||||||
xl: xlog.FromContextSafe(ctx),
|
msgTransporter: msgTransporter,
|
||||||
ctx: ctx,
|
xl: xlog.FromContextSafe(ctx),
|
||||||
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
switch cfg := pxyConf.(type) {
|
|
||||||
case *config.TCPProxyConf:
|
factory := proxyFactoryRegistry[reflect.TypeOf(pxyConf)]
|
||||||
pxy = &TCPProxy{
|
if factory == nil {
|
||||||
BaseProxy: &baseProxy,
|
return nil
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.TCPMuxProxyConf:
|
|
||||||
pxy = &TCPMuxProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.UDPProxyConf:
|
|
||||||
pxy = &UDPProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.HTTPProxyConf:
|
|
||||||
pxy = &HTTPProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.HTTPSProxyConf:
|
|
||||||
pxy = &HTTPSProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.STCPProxyConf:
|
|
||||||
pxy = &STCPProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.XTCPProxyConf:
|
|
||||||
pxy = &XTCPProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.SUDPProxyConf:
|
|
||||||
pxy = &SUDPProxy{
|
|
||||||
BaseProxy: &baseProxy,
|
|
||||||
cfg: cfg,
|
|
||||||
closeCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return factory(&baseProxy, pxyConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseProxy struct {
|
type BaseProxy struct {
|
||||||
closed bool
|
baseProxyConfig *config.BaseProxyConf
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg config.ClientCommonConf
|
||||||
serverUDPPort int
|
msgTransporter transport.MessageTransporter
|
||||||
limiter *rate.Limiter
|
limiter *rate.Limiter
|
||||||
|
// proxyPlugin is used to handle connections instead of dialing to local service.
|
||||||
|
// It's only validate for TCP protocol now.
|
||||||
|
proxyPlugin plugin.Plugin
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
xl *xlog.Logger
|
xl *xlog.Logger
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCP
|
func (pxy *BaseProxy) Run() error {
|
||||||
type TCPProxy struct {
|
if pxy.baseProxyConfig.Plugin != "" {
|
||||||
*BaseProxy
|
p, err := plugin.Create(pxy.baseProxyConfig.Plugin, pxy.baseProxyConfig.PluginParams)
|
||||||
|
|
||||||
cfg *config.TCPProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *TCPProxy) Run() (err error) {
|
|
||||||
if pxy.cfg.Plugin != "" {
|
|
||||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
pxy.proxyPlugin = p
|
||||||
}
|
}
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TCPProxy) Close() {
|
func (pxy *BaseProxy) Close() {
|
||||||
if pxy.proxyPlugin != nil {
|
if pxy.proxyPlugin != nil {
|
||||||
pxy.proxyPlugin.Close()
|
pxy.proxyPlugin.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
pxy.HandleTCPWorkConnection(conn, m, []byte(pxy.clientCfg.Token))
|
||||||
conn, []byte(pxy.clientCfg.Token), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCP Multiplexer
|
|
||||||
type TCPMuxProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.TCPMuxProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *TCPMuxProxy) Run() (err error) {
|
|
||||||
if pxy.cfg.Plugin != "" {
|
|
||||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *TCPMuxProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *TCPMuxProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
conn, []byte(pxy.clientCfg.Token), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP
|
|
||||||
type HTTPProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.HTTPProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *HTTPProxy) Run() (err error) {
|
|
||||||
if pxy.cfg.Plugin != "" {
|
|
||||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *HTTPProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *HTTPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
conn, []byte(pxy.clientCfg.Token), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPS
|
|
||||||
type HTTPSProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.HTTPSProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *HTTPSProxy) Run() (err error) {
|
|
||||||
if pxy.cfg.Plugin != "" {
|
|
||||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *HTTPSProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *HTTPSProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
conn, []byte(pxy.clientCfg.Token), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// STCP
|
|
||||||
type STCPProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.STCPProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *STCPProxy) Run() (err error) {
|
|
||||||
if pxy.cfg.Plugin != "" {
|
|
||||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *STCPProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *STCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
conn, []byte(pxy.clientCfg.Token), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// XTCP
|
|
||||||
type XTCPProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.XTCPProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *XTCPProxy) Run() (err error) {
|
|
||||||
if pxy.cfg.Plugin != "" {
|
|
||||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *XTCPProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
xl := pxy.xl
|
|
||||||
defer conn.Close()
|
|
||||||
var natHoleSidMsg msg.NatHoleSid
|
|
||||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("xtcp read from workConn error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
natHoleClientMsg := &msg.NatHoleClient{
|
|
||||||
ProxyName: pxy.cfg.ProxyName,
|
|
||||||
Sid: natHoleSidMsg.Sid,
|
|
||||||
}
|
|
||||||
serverAddr := pxy.clientCfg.NatHoleServerAddr
|
|
||||||
if serverAddr == "" {
|
|
||||||
serverAddr = pxy.clientCfg.ServerAddr
|
|
||||||
}
|
|
||||||
raddr, _ := net.ResolveUDPAddr("udp",
|
|
||||||
net.JoinHostPort(serverAddr, strconv.Itoa(pxy.serverUDPPort)))
|
|
||||||
clientConn, err := net.DialUDP("udp", nil, raddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("dial server udp addr error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer clientConn.Close()
|
|
||||||
|
|
||||||
err = msg.WriteMsg(clientConn, natHoleClientMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("send natHoleClientMsg to server error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for client address at most 5 seconds.
|
|
||||||
var natHoleRespMsg msg.NatHoleResp
|
|
||||||
_ = clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
||||||
|
|
||||||
buf := pool.GetBuf(1024)
|
|
||||||
n, err := clientConn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("get natHoleRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("get natHoleRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = clientConn.SetReadDeadline(time.Time{})
|
|
||||||
_ = clientConn.Close()
|
|
||||||
|
|
||||||
if natHoleRespMsg.Error != "" {
|
|
||||||
xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
|
||||||
|
|
||||||
// Send detect message
|
|
||||||
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())
|
|
||||||
|
|
||||||
port, err := strconv.ParseInt(portStr, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = pxy.sendDetectMsg(host, int(port), laddr, []byte(natHoleRespMsg.Sid))
|
|
||||||
xl.Trace("send all detect msg done")
|
|
||||||
|
|
||||||
if err := msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{}); err != nil {
|
|
||||||
xl.Error("write message error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 address error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer lConn.Close()
|
|
||||||
|
|
||||||
_ = lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
|
||||||
sidBuf := pool.GetBuf(1024)
|
|
||||||
var uAddr *net.UDPAddr
|
|
||||||
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("get sid from visitor error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = lConn.SetReadDeadline(time.Time{})
|
|
||||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
|
||||||
xl.Warn("incorrect sid from visitor")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pool.PutBuf(sidBuf)
|
|
||||||
xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
|
||||||
|
|
||||||
if _, err := lConn.WriteToUDP(sidBuf[:n], uAddr); err != nil {
|
|
||||||
xl.Error("write uaddr error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
kcpConn, err := frpNet.NewKCPConnFromUDP(lConn, false, uAddr.String())
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create kcp connection from udp connection error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
|
||||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
|
||||||
fmuxCfg.LogOutput = io.Discard
|
|
||||||
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create yamux server from kcp connection error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer sess.Close()
|
|
||||||
muxConn, err := sess.Accept()
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("accept for yamux connection error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
muxConn, []byte(pxy.cfg.Sk), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *XTCPProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
|
||||||
daddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(addr, strconv.Itoa(port)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tConn, err := net.DialUDP("udp", laddr, daddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// uConn := ipv4.NewConn(tConn)
|
|
||||||
// uConn.SetTTL(3)
|
|
||||||
|
|
||||||
if _, err := tConn.Write(content); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDP
|
|
||||||
type UDPProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.UDPProxyConf
|
|
||||||
|
|
||||||
localAddr *net.UDPAddr
|
|
||||||
readCh chan *msg.UDPPacket
|
|
||||||
|
|
||||||
// include msg.UDPPacket and msg.Ping
|
|
||||||
sendCh chan msg.Message
|
|
||||||
workConn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *UDPProxy) Run() (err error) {
|
|
||||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *UDPProxy) Close() {
|
|
||||||
pxy.mu.Lock()
|
|
||||||
defer pxy.mu.Unlock()
|
|
||||||
|
|
||||||
if !pxy.closed {
|
|
||||||
pxy.closed = true
|
|
||||||
if pxy.workConn != nil {
|
|
||||||
pxy.workConn.Close()
|
|
||||||
}
|
|
||||||
if pxy.readCh != nil {
|
|
||||||
close(pxy.readCh)
|
|
||||||
}
|
|
||||||
if pxy.sendCh != nil {
|
|
||||||
close(pxy.sendCh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
xl := pxy.xl
|
|
||||||
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
|
||||||
// close resources releated with old workConn
|
|
||||||
pxy.Close()
|
|
||||||
|
|
||||||
var rwc io.ReadWriteCloser = conn
|
|
||||||
var err error
|
|
||||||
if pxy.limiter != nil {
|
|
||||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
|
||||||
return conn.Close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if pxy.cfg.UseEncryption {
|
|
||||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
xl.Error("create encryption stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pxy.cfg.UseCompression {
|
|
||||||
rwc = frpIo.WithCompression(rwc)
|
|
||||||
}
|
|
||||||
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
|
||||||
|
|
||||||
pxy.mu.Lock()
|
|
||||||
pxy.workConn = conn
|
|
||||||
pxy.readCh = make(chan *msg.UDPPacket, 1024)
|
|
||||||
pxy.sendCh = make(chan msg.Message, 1024)
|
|
||||||
pxy.closed = false
|
|
||||||
pxy.mu.Unlock()
|
|
||||||
|
|
||||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
|
||||||
for {
|
|
||||||
var udpMsg msg.UDPPacket
|
|
||||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
|
||||||
xl.Warn("read from workConn for udp error: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errRet := errors.PanicToError(func() {
|
|
||||||
xl.Trace("get udp package from workConn: %s", udpMsg.Content)
|
|
||||||
readCh <- &udpMsg
|
|
||||||
}); errRet != nil {
|
|
||||||
xl.Info("reader goroutine for udp work connection closed: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
|
||||||
defer func() {
|
|
||||||
xl.Info("writer goroutine for udp work connection closed")
|
|
||||||
}()
|
|
||||||
var errRet error
|
|
||||||
for rawMsg := range sendCh {
|
|
||||||
switch m := rawMsg.(type) {
|
|
||||||
case *msg.UDPPacket:
|
|
||||||
xl.Trace("send udp package to workConn: %s", m.Content)
|
|
||||||
case *msg.Ping:
|
|
||||||
xl.Trace("send ping message to udp workConn")
|
|
||||||
}
|
|
||||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
|
||||||
xl.Error("udp work write error: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
heartbeatFn := func(sendCh chan msg.Message) {
|
|
||||||
var errRet error
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Duration(30) * time.Second)
|
|
||||||
if errRet = errors.PanicToError(func() {
|
|
||||||
sendCh <- &msg.Ping{}
|
|
||||||
}); errRet != nil {
|
|
||||||
xl.Trace("heartbeat goroutine for udp work connection closed")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
|
||||||
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
|
||||||
go heartbeatFn(pxy.sendCh)
|
|
||||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
type SUDPProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.SUDPProxyConf
|
|
||||||
|
|
||||||
localAddr *net.UDPAddr
|
|
||||||
|
|
||||||
closeCh chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *SUDPProxy) Run() (err error) {
|
|
||||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *SUDPProxy) Close() {
|
|
||||||
pxy.mu.Lock()
|
|
||||||
defer pxy.mu.Unlock()
|
|
||||||
select {
|
|
||||||
case <-pxy.closeCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
close(pxy.closeCh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
xl := pxy.xl
|
|
||||||
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
|
||||||
|
|
||||||
var rwc io.ReadWriteCloser = conn
|
|
||||||
var err error
|
|
||||||
if pxy.limiter != nil {
|
|
||||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
|
||||||
return conn.Close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if pxy.cfg.UseEncryption {
|
|
||||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
xl.Error("create encryption stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pxy.cfg.UseCompression {
|
|
||||||
rwc = frpIo.WithCompression(rwc)
|
|
||||||
}
|
|
||||||
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
|
||||||
|
|
||||||
workConn := conn
|
|
||||||
readCh := make(chan *msg.UDPPacket, 1024)
|
|
||||||
sendCh := make(chan msg.Message, 1024)
|
|
||||||
isClose := false
|
|
||||||
|
|
||||||
mu := &sync.Mutex{}
|
|
||||||
|
|
||||||
closeFn := func() {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
if isClose {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isClose = true
|
|
||||||
if workConn != nil {
|
|
||||||
workConn.Close()
|
|
||||||
}
|
|
||||||
close(readCh)
|
|
||||||
close(sendCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
|
||||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
|
||||||
defer closeFn()
|
|
||||||
|
|
||||||
for {
|
|
||||||
// first to check sudp proxy is closed or not
|
|
||||||
select {
|
|
||||||
case <-pxy.closeCh:
|
|
||||||
xl.Trace("frpc sudp proxy is closed")
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
var udpMsg msg.UDPPacket
|
|
||||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
|
||||||
xl.Warn("read from workConn for sudp error: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if errRet := errors.PanicToError(func() {
|
|
||||||
readCh <- &udpMsg
|
|
||||||
}); errRet != nil {
|
|
||||||
xl.Warn("reader goroutine for sudp work connection closed: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
|
||||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
|
||||||
defer func() {
|
|
||||||
closeFn()
|
|
||||||
xl.Info("writer goroutine for sudp work connection closed")
|
|
||||||
}()
|
|
||||||
|
|
||||||
var errRet error
|
|
||||||
for rawMsg := range sendCh {
|
|
||||||
switch m := rawMsg.(type) {
|
|
||||||
case *msg.UDPPacket:
|
|
||||||
xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
|
|
||||||
m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
|
|
||||||
case *msg.Ping:
|
|
||||||
xl.Trace("frpc send ping message to frpc visitor")
|
|
||||||
}
|
|
||||||
|
|
||||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
|
||||||
xl.Error("sudp work write error: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
heartbeatFn := func(sendCh chan msg.Message) {
|
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
|
||||||
defer func() {
|
|
||||||
ticker.Stop()
|
|
||||||
closeFn()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var errRet error
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
if errRet = errors.PanicToError(func() {
|
|
||||||
sendCh <- &msg.Ping{}
|
|
||||||
}); errRet != nil {
|
|
||||||
xl.Warn("heartbeat goroutine for sudp work connection closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-pxy.closeCh:
|
|
||||||
xl.Trace("frpc sudp proxy is closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go workConnSenderFn(workConn, sendCh)
|
|
||||||
go workConnReaderFn(workConn, readCh)
|
|
||||||
go heartbeatFn(sendCh)
|
|
||||||
|
|
||||||
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common handler for tcp work connections.
|
// Common handler for tcp work connections.
|
||||||
func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWorkConn, encKey []byte) {
|
||||||
baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn,
|
xl := pxy.xl
|
||||||
) {
|
baseConfig := pxy.baseProxyConfig
|
||||||
xl := xlog.FromContextSafe(ctx)
|
|
||||||
var (
|
var (
|
||||||
remote io.ReadWriteCloser
|
remote io.ReadWriteCloser
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
remote = workConn
|
remote = workConn
|
||||||
if limiter != nil {
|
if pxy.limiter != nil {
|
||||||
remote = frpIo.WrapReadWriteCloser(limit.NewReader(workConn, limiter), limit.NewWriter(workConn, limiter), func() error {
|
remote = libio.WrapReadWriteCloser(limit.NewReader(workConn, pxy.limiter), limit.NewWriter(workConn, pxy.limiter), func() error {
|
||||||
return workConn.Close()
|
return workConn.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
|
xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
|
||||||
baseInfo.UseEncryption, baseInfo.UseCompression)
|
baseConfig.UseEncryption, baseConfig.UseCompression)
|
||||||
if baseInfo.UseEncryption {
|
if baseConfig.UseEncryption {
|
||||||
remote, err = frpIo.WithEncryption(remote, encKey)
|
remote, err = libio.WithEncryption(remote, encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if baseInfo.UseCompression {
|
if baseConfig.UseCompression {
|
||||||
remote = frpIo.WithCompression(remote)
|
remote = libio.WithCompression(remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we need to send proxy protocol info
|
// check if we need to send proxy protocol info
|
||||||
var extraInfo []byte
|
var extraInfo []byte
|
||||||
if baseInfo.ProxyProtocolVersion != "" {
|
if baseConfig.ProxyProtocolVersion != "" {
|
||||||
if m.SrcAddr != "" && m.SrcPort != 0 {
|
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
if m.DstAddr == "" {
|
if m.DstAddr == "" {
|
||||||
m.DstAddr = "127.0.0.1"
|
m.DstAddr = "127.0.0.1"
|
||||||
@ -778,9 +167,9 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
|
|||||||
h.TransportProtocol = pp.TCPv6
|
h.TransportProtocol = pp.TCPv6
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseInfo.ProxyProtocolVersion == "v1" {
|
if baseConfig.ProxyProtocolVersion == "v1" {
|
||||||
h.Version = 1
|
h.Version = 1
|
||||||
} else if baseInfo.ProxyProtocolVersion == "v2" {
|
} else if baseConfig.ProxyProtocolVersion == "v2" {
|
||||||
h.Version = 2
|
h.Version = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -790,21 +179,21 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxyPlugin != nil {
|
if pxy.proxyPlugin != nil {
|
||||||
// if plugin is set, let plugin handle connections first
|
// if plugin is set, let plugin handle connection first
|
||||||
xl.Debug("handle by plugin: %s", proxyPlugin.Name())
|
xl.Debug("handle by plugin: %s", pxy.proxyPlugin.Name())
|
||||||
proxyPlugin.Handle(remote, workConn, extraInfo)
|
pxy.proxyPlugin.Handle(remote, workConn, extraInfo)
|
||||||
xl.Debug("handle by plugin finished")
|
xl.Debug("handle by plugin finished")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
localConn, err := libdial.Dial(
|
localConn, err := libdial.Dial(
|
||||||
net.JoinHostPort(localInfo.LocalIP, strconv.Itoa(localInfo.LocalPort)),
|
net.JoinHostPort(baseConfig.LocalIP, strconv.Itoa(baseConfig.LocalPort)),
|
||||||
libdial.WithTimeout(10*time.Second),
|
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", baseConfig.LocalIP, baseConfig.LocalPort, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -819,7 +208,7 @@ func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, errs := frpIo.Join(localConn, remote)
|
_, _, errs := libio.Join(localConn, remote)
|
||||||
xl.Debug("join connections closed")
|
xl.Debug("join connections closed")
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
xl.Trace("join connections errors: %v", errs)
|
xl.Trace("join connections errors: %v", errs)
|
||||||
|
@ -1,42 +1,56 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/event"
|
"github.com/fatedier/frp/client/event"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
sendCh chan (msg.Message)
|
proxies map[string]*Wrapper
|
||||||
proxies map[string]*Wrapper
|
msgTransporter transport.MessageTransporter
|
||||||
|
|
||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg config.ClientCommonConf
|
||||||
|
|
||||||
// The UDP port that the server is listening on
|
|
||||||
serverUDPPort int
|
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(ctx context.Context, msgSendCh chan (msg.Message), clientCfg config.ClientCommonConf, serverUDPPort int) *Manager {
|
func NewManager(
|
||||||
|
ctx context.Context,
|
||||||
|
clientCfg config.ClientCommonConf,
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
sendCh: msgSendCh,
|
proxies: make(map[string]*Wrapper),
|
||||||
proxies: make(map[string]*Wrapper),
|
msgTransporter: msgTransporter,
|
||||||
closed: false,
|
closed: false,
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
serverUDPPort: serverUDPPort,
|
ctx: ctx,
|
||||||
ctx: ctx,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,10 +100,7 @@ func (pm *Manager) HandleEvent(payload interface{}) error {
|
|||||||
return event.ErrPayloadType
|
return event.ErrPayloadType
|
||||||
}
|
}
|
||||||
|
|
||||||
err := errors.PanicToError(func() {
|
return pm.msgTransporter.Send(m)
|
||||||
pm.sendCh <- m
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
||||||
@ -111,27 +122,24 @@ func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
|||||||
for name, pxy := range pm.proxies {
|
for name, pxy := range pm.proxies {
|
||||||
del := false
|
del := false
|
||||||
cfg, ok := pxyCfgs[name]
|
cfg, ok := pxyCfgs[name]
|
||||||
if !ok {
|
if !ok || !reflect.DeepEqual(pxy.Cfg, cfg) {
|
||||||
del = true
|
|
||||||
} else if !pxy.Cfg.Compare(cfg) {
|
|
||||||
del = true
|
del = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if del {
|
if del {
|
||||||
delPxyNames = append(delPxyNames, name)
|
delPxyNames = append(delPxyNames, name)
|
||||||
delete(pm.proxies, name)
|
delete(pm.proxies, name)
|
||||||
|
|
||||||
pxy.Stop()
|
pxy.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(delPxyNames) > 0 {
|
if len(delPxyNames) > 0 {
|
||||||
xl.Info("proxy removed: %v", delPxyNames)
|
xl.Info("proxy removed: %s", delPxyNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
addPxyNames := make([]string, 0)
|
addPxyNames := make([]string, 0)
|
||||||
for name, cfg := range pxyCfgs {
|
for name, cfg := range pxyCfgs {
|
||||||
if _, ok := pm.proxies[name]; !ok {
|
if _, ok := pm.proxies[name]; !ok {
|
||||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.serverUDPPort)
|
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter)
|
||||||
pm.proxies[name] = pxy
|
pm.proxies[name] = pxy
|
||||||
addPxyNames = append(addPxyNames, name)
|
addPxyNames = append(addPxyNames, name)
|
||||||
|
|
||||||
@ -139,6 +147,6 @@ func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(addPxyNames) > 0 {
|
if len(addPxyNames) > 0 {
|
||||||
xl.Info("proxy added: %v", addPxyNames)
|
xl.Info("proxy added: %s", addPxyNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,17 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -14,6 +28,7 @@ import (
|
|||||||
"github.com/fatedier/frp/client/health"
|
"github.com/fatedier/frp/client/health"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,6 +71,8 @@ type Wrapper struct {
|
|||||||
// event handler
|
// event handler
|
||||||
handler event.Handler
|
handler event.Handler
|
||||||
|
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
|
|
||||||
health uint32
|
health uint32
|
||||||
lastSendStartMsg time.Time
|
lastSendStartMsg time.Time
|
||||||
lastStartErr time.Time
|
lastStartErr time.Time
|
||||||
@ -67,8 +84,14 @@ type Wrapper struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.Handler, serverUDPPort int) *Wrapper {
|
func NewWrapper(
|
||||||
baseInfo := cfg.GetBaseInfo()
|
ctx context.Context,
|
||||||
|
cfg config.ProxyConf,
|
||||||
|
clientCfg config.ClientCommonConf,
|
||||||
|
eventHandler event.Handler,
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
) *Wrapper {
|
||||||
|
baseInfo := cfg.GetBaseConfig()
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
|
||||||
pw := &Wrapper{
|
pw := &Wrapper{
|
||||||
WorkingStatus: WorkingStatus{
|
WorkingStatus: WorkingStatus{
|
||||||
@ -80,6 +103,7 @@ func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.Clie
|
|||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
healthNotifyCh: make(chan struct{}),
|
healthNotifyCh: make(chan struct{}),
|
||||||
handler: eventHandler,
|
handler: eventHandler,
|
||||||
|
msgTransporter: msgTransporter,
|
||||||
xl: xl,
|
xl: xl,
|
||||||
ctx: xlog.NewContext(ctx, xl),
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
}
|
}
|
||||||
@ -92,7 +116,7 @@ func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.Clie
|
|||||||
xl.Trace("enable health check monitor")
|
xl.Trace("enable health check monitor")
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, serverUDPPort)
|
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter)
|
||||||
return pw
|
return pw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
207
client/proxy/sudp.go
Normal file
207
client/proxy/sudp.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterProxyFactory(reflect.TypeOf(&config.SUDPProxyConf{}), NewSUDPProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SUDPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *config.SUDPProxyConf
|
||||||
|
|
||||||
|
localAddr *net.UDPAddr
|
||||||
|
|
||||||
|
closeCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSUDPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
|
||||||
|
unwrapped, ok := cfg.(*config.SUDPProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &SUDPProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
cfg: unwrapped,
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *SUDPProxy) Run() (err error) {
|
||||||
|
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *SUDPProxy) Close() {
|
||||||
|
pxy.mu.Lock()
|
||||||
|
defer pxy.mu.Unlock()
|
||||||
|
select {
|
||||||
|
case <-pxy.closeCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
close(pxy.closeCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
||||||
|
|
||||||
|
var rwc io.ReadWriteCloser = conn
|
||||||
|
var err error
|
||||||
|
if pxy.limiter != nil {
|
||||||
|
rwc = libio.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||||
|
return conn.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if pxy.cfg.UseEncryption {
|
||||||
|
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pxy.cfg.UseCompression {
|
||||||
|
rwc = libio.WithCompression(rwc)
|
||||||
|
}
|
||||||
|
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
|
||||||
|
|
||||||
|
workConn := conn
|
||||||
|
readCh := make(chan *msg.UDPPacket, 1024)
|
||||||
|
sendCh := make(chan msg.Message, 1024)
|
||||||
|
isClose := false
|
||||||
|
|
||||||
|
mu := &sync.Mutex{}
|
||||||
|
|
||||||
|
closeFn := func() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if isClose {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isClose = true
|
||||||
|
if workConn != nil {
|
||||||
|
workConn.Close()
|
||||||
|
}
|
||||||
|
close(readCh)
|
||||||
|
close(sendCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||||
|
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||||
|
defer closeFn()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// first to check sudp proxy is closed or not
|
||||||
|
select {
|
||||||
|
case <-pxy.closeCh:
|
||||||
|
xl.Trace("frpc sudp proxy is closed")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
var udpMsg msg.UDPPacket
|
||||||
|
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||||
|
xl.Warn("read from workConn for sudp error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errRet := errors.PanicToError(func() {
|
||||||
|
readCh <- &udpMsg
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Warn("reader goroutine for sudp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||||
|
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||||
|
defer func() {
|
||||||
|
closeFn()
|
||||||
|
xl.Info("writer goroutine for sudp work connection closed")
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errRet error
|
||||||
|
for rawMsg := range sendCh {
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.UDPPacket:
|
||||||
|
xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
|
||||||
|
m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
|
||||||
|
case *msg.Ping:
|
||||||
|
xl.Trace("frpc send ping message to frpc visitor")
|
||||||
|
}
|
||||||
|
|
||||||
|
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||||
|
xl.Error("sudp work write error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
heartbeatFn := func(sendCh chan msg.Message) {
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
defer func() {
|
||||||
|
ticker.Stop()
|
||||||
|
closeFn()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errRet error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
if errRet = errors.PanicToError(func() {
|
||||||
|
sendCh <- &msg.Ping{}
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Warn("heartbeat goroutine for sudp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-pxy.closeCh:
|
||||||
|
xl.Trace("frpc sudp proxy is closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go workConnSenderFn(workConn, sendCh)
|
||||||
|
go workConnReaderFn(workConn, readCh)
|
||||||
|
go heartbeatFn(sendCh)
|
||||||
|
|
||||||
|
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||||
|
}
|
173
client/proxy/udp.go
Normal file
173
client/proxy/udp.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterProxyFactory(reflect.TypeOf(&config.UDPProxyConf{}), NewUDPProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UDPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *config.UDPProxyConf
|
||||||
|
|
||||||
|
localAddr *net.UDPAddr
|
||||||
|
readCh chan *msg.UDPPacket
|
||||||
|
|
||||||
|
// include msg.UDPPacket and msg.Ping
|
||||||
|
sendCh chan msg.Message
|
||||||
|
workConn net.Conn
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
|
||||||
|
unwrapped, ok := cfg.(*config.UDPProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &UDPProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
cfg: unwrapped,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *UDPProxy) Run() (err error) {
|
||||||
|
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *UDPProxy) Close() {
|
||||||
|
pxy.mu.Lock()
|
||||||
|
defer pxy.mu.Unlock()
|
||||||
|
|
||||||
|
if !pxy.closed {
|
||||||
|
pxy.closed = true
|
||||||
|
if pxy.workConn != nil {
|
||||||
|
pxy.workConn.Close()
|
||||||
|
}
|
||||||
|
if pxy.readCh != nil {
|
||||||
|
close(pxy.readCh)
|
||||||
|
}
|
||||||
|
if pxy.sendCh != nil {
|
||||||
|
close(pxy.sendCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||||
|
// close resources releated with old workConn
|
||||||
|
pxy.Close()
|
||||||
|
|
||||||
|
var rwc io.ReadWriteCloser = conn
|
||||||
|
var err error
|
||||||
|
if pxy.limiter != nil {
|
||||||
|
rwc = libio.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||||
|
return conn.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if pxy.cfg.UseEncryption {
|
||||||
|
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pxy.cfg.UseCompression {
|
||||||
|
rwc = libio.WithCompression(rwc)
|
||||||
|
}
|
||||||
|
conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
|
||||||
|
|
||||||
|
pxy.mu.Lock()
|
||||||
|
pxy.workConn = conn
|
||||||
|
pxy.readCh = make(chan *msg.UDPPacket, 1024)
|
||||||
|
pxy.sendCh = make(chan msg.Message, 1024)
|
||||||
|
pxy.closed = false
|
||||||
|
pxy.mu.Unlock()
|
||||||
|
|
||||||
|
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||||
|
for {
|
||||||
|
var udpMsg msg.UDPPacket
|
||||||
|
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||||
|
xl.Warn("read from workConn for udp error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errRet := errors.PanicToError(func() {
|
||||||
|
xl.Trace("get udp package from workConn: %s", udpMsg.Content)
|
||||||
|
readCh <- &udpMsg
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Info("reader goroutine for udp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||||
|
defer func() {
|
||||||
|
xl.Info("writer goroutine for udp work connection closed")
|
||||||
|
}()
|
||||||
|
var errRet error
|
||||||
|
for rawMsg := range sendCh {
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.UDPPacket:
|
||||||
|
xl.Trace("send udp package to workConn: %s", m.Content)
|
||||||
|
case *msg.Ping:
|
||||||
|
xl.Trace("send ping message to udp workConn")
|
||||||
|
}
|
||||||
|
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||||
|
xl.Error("udp work write error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
heartbeatFn := func(sendCh chan msg.Message) {
|
||||||
|
var errRet error
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Duration(30) * time.Second)
|
||||||
|
if errRet = errors.PanicToError(func() {
|
||||||
|
sendCh <- &msg.Ping{}
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Trace("heartbeat goroutine for udp work connection closed")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||||
|
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||||
|
go heartbeatFn(pxy.sendCh)
|
||||||
|
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||||
|
}
|
195
client/proxy/xtcp.go
Normal file
195
client/proxy/xtcp.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
"github.com/quic-go/quic-go"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/nathole"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterProxyFactory(reflect.TypeOf(&config.XTCPProxyConf{}), NewXTCPProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
type XTCPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *config.XTCPProxyConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
|
||||||
|
unwrapped, ok := cfg.(*config.XTCPProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &XTCPProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
cfg: unwrapped,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
defer conn.Close()
|
||||||
|
var natHoleSidMsg msg.NatHoleSid
|
||||||
|
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("xtcp read from workConn error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer})
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("nathole prepare error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
||||||
|
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
||||||
|
defer prepareResult.ListenConn.Close()
|
||||||
|
|
||||||
|
// send NatHoleClient msg to server
|
||||||
|
transactionID := nathole.NewTransactionID()
|
||||||
|
natHoleClientMsg := &msg.NatHoleClient{
|
||||||
|
TransactionID: transactionID,
|
||||||
|
ProxyName: pxy.cfg.ProxyName,
|
||||||
|
Sid: natHoleSidMsg.Sid,
|
||||||
|
MappedAddrs: prepareResult.Addrs,
|
||||||
|
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("nathole exchange info error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
||||||
|
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||||
|
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||||
|
|
||||||
|
listenConn := prepareResult.ListenConn
|
||||||
|
newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warn("make hole error: %v", err)
|
||||||
|
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||||
|
Sid: natHoleRespMsg.Sid,
|
||||||
|
Success: false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listenConn = newListenConn
|
||||||
|
xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||||
|
|
||||||
|
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||||
|
Sid: natHoleRespMsg.Sid,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if natHoleRespMsg.Protocol == "kcp" {
|
||||||
|
pxy.listenByKCP(listenConn, raddr, startWorkConnMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// default is quic
|
||||||
|
pxy.listenByQUIC(listenConn, raddr, startWorkConnMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, startWorkConnMsg *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
listenConn.Close()
|
||||||
|
laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String())
|
||||||
|
lConn, err := net.DialUDP("udp", laddr, raddr)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("dial udp error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lConn.Close()
|
||||||
|
|
||||||
|
remote, err := utilnet.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("create kcp connection from udp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 10 * time.Second
|
||||||
|
fmuxCfg.MaxStreamWindowSize = 2 * 1024 * 1024
|
||||||
|
fmuxCfg.LogOutput = io.Discard
|
||||||
|
session, err := fmux.Server(remote, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("create mux session error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
muxConn, err := session.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("accept connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Sk))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, startWorkConnMsg *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
defer listenConn.Close()
|
||||||
|
|
||||||
|
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("create tls config error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
|
quicListener, err := quic.Listen(listenConn, tlsConfig,
|
||||||
|
&quic.Config{
|
||||||
|
MaxIdleTimeout: time.Duration(pxy.clientCfg.QUICMaxIdleTimeout) * time.Second,
|
||||||
|
MaxIncomingStreams: int64(pxy.clientCfg.QUICMaxIncomingStreams),
|
||||||
|
KeepAlivePeriod: time.Duration(pxy.clientCfg.QUICKeepalivePeriod) * time.Second,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("dial quic error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// only accept one connection from raddr
|
||||||
|
c, err := quicListener.Accept(pxy.ctx)
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("quic accept connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
stream, err := c.AcceptStream(pxy.ctx)
|
||||||
|
if err != nil {
|
||||||
|
xl.Debug("quic accept stream error: %v", err)
|
||||||
|
_ = c.CloseWithError(0, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go pxy.HandleTCPWorkConnection(utilnet.QuicStreamToNetConn(stream, c), startWorkConnMsg, []byte(pxy.cfg.Sk))
|
||||||
|
}
|
||||||
|
}
|
@ -39,7 +39,7 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"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"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"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"
|
||||||
@ -72,9 +72,6 @@ type Service struct {
|
|||||||
// string if no configuration file was used.
|
// string if no configuration file was used.
|
||||||
cfgFile string
|
cfgFile string
|
||||||
|
|
||||||
// This is configured by the login response from frps
|
|
||||||
serverUDPPort int
|
|
||||||
|
|
||||||
exit uint32 // 0 means not exit
|
exit uint32 // 0 means not exit
|
||||||
|
|
||||||
// service context
|
// service context
|
||||||
@ -141,7 +138,7 @@ func (svr *Service) Run() error {
|
|||||||
util.RandomSleep(10*time.Second, 0.9, 1.1)
|
util.RandomSleep(10*time.Second, 0.9, 1.1)
|
||||||
} else {
|
} else {
|
||||||
// login success
|
// login success
|
||||||
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||||
ctl.Run()
|
ctl.Run()
|
||||||
svr.ctlMu.Lock()
|
svr.ctlMu.Lock()
|
||||||
svr.ctl = ctl
|
svr.ctl = ctl
|
||||||
@ -223,7 +220,7 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
// reconnect success, init delayTime
|
// reconnect success, init delayTime
|
||||||
delayTime = time.Second
|
delayTime = time.Second
|
||||||
|
|
||||||
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||||
ctl.Run()
|
ctl.Run()
|
||||||
svr.ctlMu.Lock()
|
svr.ctlMu.Lock()
|
||||||
if svr.ctl != nil {
|
if svr.ctl != nil {
|
||||||
@ -295,8 +292,7 @@ func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
|
|||||||
xl.ResetPrefixes()
|
xl.ResetPrefixes()
|
||||||
xl.AppendPrefix(svr.runID)
|
xl.AppendPrefix(svr.runID)
|
||||||
|
|
||||||
svr.serverUDPPort = loginRespMsg.ServerUDPPort
|
xl.Info("login to server success, get run id [%s]", loginRespMsg.RunID)
|
||||||
xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunID, loginRespMsg.ServerUDPPort)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,7 +369,8 @@ func (cm *ConnectionManager) OpenConnection() error {
|
|||||||
}
|
}
|
||||||
tlsConfig.NextProtos = []string{"frp"}
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
|
|
||||||
conn, err := quic.DialAddr(
|
conn, err := quic.DialAddrContext(
|
||||||
|
cm.ctx,
|
||||||
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
||||||
tlsConfig, &quic.Config{
|
tlsConfig, &quic.Config{
|
||||||
MaxIdleTimeout: time.Duration(cm.cfg.QUICMaxIdleTimeout) * time.Second,
|
MaxIdleTimeout: time.Duration(cm.cfg.QUICMaxIdleTimeout) * time.Second,
|
||||||
@ -413,7 +410,7 @@ func (cm *ConnectionManager) Connect() (net.Conn, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return frpNet.QuicStreamToNetConn(stream, cm.quicConn), nil
|
return utilnet.QuicStreamToNetConn(stream, cm.quicConn), nil
|
||||||
} else if cm.muxSession != nil {
|
} else if cm.muxSession != nil {
|
||||||
stream, err := cm.muxSession.OpenStream()
|
stream, err := cm.muxSession.OpenStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -455,7 +452,7 @@ func (cm *ConnectionManager) realConnect() (net.Conn, error) {
|
|||||||
protocol := cm.cfg.Protocol
|
protocol := cm.cfg.Protocol
|
||||||
if protocol == "websocket" {
|
if protocol == "websocket" {
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
|
dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: utilnet.DialHookWebsocket()}))
|
||||||
}
|
}
|
||||||
if cm.cfg.ConnectServerLocalIP != "" {
|
if cm.cfg.ConnectServerLocalIP != "" {
|
||||||
dialOptions = append(dialOptions, libdial.WithLocalAddr(cm.cfg.ConnectServerLocalIP))
|
dialOptions = append(dialOptions, libdial.WithLocalAddr(cm.cfg.ConnectServerLocalIP))
|
||||||
@ -468,10 +465,11 @@ func (cm *ConnectionManager) realConnect() (net.Conn, error) {
|
|||||||
libdial.WithProxyAuth(auth),
|
libdial.WithProxyAuth(auth),
|
||||||
libdial.WithTLSConfig(tlsConfig),
|
libdial.WithTLSConfig(tlsConfig),
|
||||||
libdial.WithAfterHook(libdial.AfterHook{
|
libdial.WithAfterHook(libdial.AfterHook{
|
||||||
Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, cm.cfg.DisableCustomTLSFirstByte),
|
Hook: utilnet.DialHookCustomTLSHeadByte(tlsConfig != nil, cm.cfg.DisableCustomTLSFirstByte),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
conn, err := libdial.Dial(
|
conn, err := libdial.DialContext(
|
||||||
|
cm.ctx,
|
||||||
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
|
||||||
dialOptions...,
|
dialOptions...,
|
||||||
)
|
)
|
||||||
|
@ -1,575 +0,0 @@
|
|||||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
|
||||||
"github.com/fatedier/golib/pool"
|
|
||||||
fmux "github.com/hashicorp/yamux"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
|
||||||
"github.com/fatedier/frp/pkg/proto/udp"
|
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Visitor is used for forward traffics from local port to remote service.
|
|
||||||
type Visitor interface {
|
|
||||||
Run() error
|
|
||||||
Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVisitor(ctx context.Context, ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName)
|
|
||||||
baseVisitor := BaseVisitor{
|
|
||||||
ctl: ctl,
|
|
||||||
ctx: xlog.NewContext(ctx, xl),
|
|
||||||
}
|
|
||||||
switch cfg := cfg.(type) {
|
|
||||||
case *config.STCPVisitorConf:
|
|
||||||
visitor = &STCPVisitor{
|
|
||||||
BaseVisitor: &baseVisitor,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.XTCPVisitorConf:
|
|
||||||
visitor = &XTCPVisitor{
|
|
||||||
BaseVisitor: &baseVisitor,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.SUDPVisitorConf:
|
|
||||||
visitor = &SUDPVisitor{
|
|
||||||
BaseVisitor: &baseVisitor,
|
|
||||||
cfg: cfg,
|
|
||||||
checkCloseCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseVisitor struct {
|
|
||||||
ctl *Control
|
|
||||||
l net.Listener
|
|
||||||
|
|
||||||
mu sync.RWMutex
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
type STCPVisitor struct {
|
|
||||||
*BaseVisitor
|
|
||||||
|
|
||||||
cfg *config.STCPVisitorConf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *STCPVisitor) Run() (err error) {
|
|
||||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go sv.worker()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *STCPVisitor) Close() {
|
|
||||||
sv.l.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *STCPVisitor) worker() {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
for {
|
|
||||||
conn, err := sv.l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("stcp local listener closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go sv.handleConn(conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
defer userConn.Close()
|
|
||||||
|
|
||||||
xl.Debug("get a new stcp user connection")
|
|
||||||
visitorConn, err := sv.ctl.connectServer()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer visitorConn.Close()
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
|
||||||
ProxyName: sv.cfg.ServerName,
|
|
||||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
|
||||||
Timestamp: now,
|
|
||||||
UseEncryption: sv.cfg.UseEncryption,
|
|
||||||
UseCompression: sv.cfg.UseCompression,
|
|
||||||
}
|
|
||||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("send newVisitorConnMsg to server error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("get newVisitorConnRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
|
||||||
|
|
||||||
if newVisitorConnRespMsg.Error != "" {
|
|
||||||
xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var remote io.ReadWriteCloser
|
|
||||||
remote = visitorConn
|
|
||||||
if sv.cfg.UseEncryption {
|
|
||||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create encryption stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sv.cfg.UseCompression {
|
|
||||||
remote = frpIo.WithCompression(remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
frpIo.Join(userConn, remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
type XTCPVisitor struct {
|
|
||||||
*BaseVisitor
|
|
||||||
|
|
||||||
cfg *config.XTCPVisitorConf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *XTCPVisitor) Run() (err error) {
|
|
||||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go sv.worker()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *XTCPVisitor) Close() {
|
|
||||||
sv.l.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *XTCPVisitor) worker() {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
for {
|
|
||||||
conn, err := sv.l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("xtcp local listener closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go sv.handleConn(conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
defer userConn.Close()
|
|
||||||
|
|
||||||
xl.Debug("get a new xtcp user connection")
|
|
||||||
if sv.ctl.serverUDPPort == 0 {
|
|
||||||
xl.Error("xtcp is not supported by server")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serverAddr := sv.ctl.clientCfg.NatHoleServerAddr
|
|
||||||
if serverAddr == "" {
|
|
||||||
serverAddr = sv.ctl.clientCfg.ServerAddr
|
|
||||||
}
|
|
||||||
raddr, err := net.ResolveUDPAddr("udp",
|
|
||||||
net.JoinHostPort(serverAddr, strconv.Itoa(sv.ctl.serverUDPPort)))
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("resolve server UDP addr error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
visitorConn, err := net.DialUDP("udp", nil, raddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("dial server udp addr error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer visitorConn.Close()
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
natHoleVisitorMsg := &msg.NatHoleVisitor{
|
|
||||||
ProxyName: sv.cfg.ServerName,
|
|
||||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
|
||||||
Timestamp: now,
|
|
||||||
}
|
|
||||||
err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("send natHoleVisitorMsg to server error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for client address at most 10 seconds.
|
|
||||||
var natHoleRespMsg msg.NatHoleResp
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
buf := pool.GetBuf(1024)
|
|
||||||
n, err := visitorConn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("get natHoleRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("get natHoleRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
|
||||||
pool.PutBuf(buf)
|
|
||||||
|
|
||||||
if natHoleRespMsg.Error != "" {
|
|
||||||
xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
|
||||||
|
|
||||||
// Close visitorConn, so we can use it's local address.
|
|
||||||
visitorConn.Close()
|
|
||||||
|
|
||||||
// send sid message to client
|
|
||||||
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
|
||||||
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("resolve client udp address error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("dial client udp address error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer lConn.Close()
|
|
||||||
|
|
||||||
if _, err := lConn.Write([]byte(natHoleRespMsg.Sid)); err != nil {
|
|
||||||
xl.Error("write sid error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// read ack sid from client
|
|
||||||
sidBuf := pool.GetBuf(1024)
|
|
||||||
_ = lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
|
||||||
n, err = lConn.Read(sidBuf)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("get sid from client error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = lConn.SetReadDeadline(time.Time{})
|
|
||||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
|
||||||
xl.Warn("incorrect sid from client")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pool.PutBuf(sidBuf)
|
|
||||||
|
|
||||||
xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
|
||||||
|
|
||||||
// wrap kcp connection
|
|
||||||
var remote io.ReadWriteCloser
|
|
||||||
remote, err = frpNet.NewKCPConnFromUDP(lConn, true, natHoleRespMsg.ClientAddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create kcp connection from udp connection error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
|
||||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
|
||||||
fmuxCfg.LogOutput = io.Discard
|
|
||||||
sess, err := fmux.Client(remote, fmuxCfg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create yamux session error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer sess.Close()
|
|
||||||
muxConn, err := sess.Open()
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("open yamux stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var muxConnRWCloser io.ReadWriteCloser = muxConn
|
|
||||||
if sv.cfg.UseEncryption {
|
|
||||||
muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create encryption stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sv.cfg.UseCompression {
|
|
||||||
muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, errs := frpIo.Join(userConn, muxConnRWCloser)
|
|
||||||
xl.Debug("join connections closed")
|
|
||||||
if len(errs) > 0 {
|
|
||||||
xl.Trace("join connections errors: %v", errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SUDPVisitor struct {
|
|
||||||
*BaseVisitor
|
|
||||||
|
|
||||||
checkCloseCh chan struct{}
|
|
||||||
// udpConn is the listener of udp packet
|
|
||||||
udpConn *net.UDPConn
|
|
||||||
readCh chan *msg.UDPPacket
|
|
||||||
sendCh chan *msg.UDPPacket
|
|
||||||
|
|
||||||
cfg *config.SUDPVisitorConf
|
|
||||||
}
|
|
||||||
|
|
||||||
// SUDP Run start listen a udp port
|
|
||||||
func (sv *SUDPVisitor) Run() (err error) {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sv.udpConn, err = net.ListenUDP("udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("listen udp port %s error: %v", addr.String(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sv.sendCh = make(chan *msg.UDPPacket, 1024)
|
|
||||||
sv.readCh = make(chan *msg.UDPPacket, 1024)
|
|
||||||
|
|
||||||
xl.Info("sudp start to work, listen on %s", addr)
|
|
||||||
|
|
||||||
go sv.dispatcher()
|
|
||||||
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.ctl.clientCfg.UDPPacketSize))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *SUDPVisitor) dispatcher() {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
|
|
||||||
var (
|
|
||||||
visitorConn net.Conn
|
|
||||||
err error
|
|
||||||
|
|
||||||
firstPacket *msg.UDPPacket
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case firstPacket = <-sv.sendCh:
|
|
||||||
if firstPacket == nil {
|
|
||||||
xl.Info("frpc sudp visitor proxy is closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-sv.checkCloseCh:
|
|
||||||
xl.Info("frpc sudp visitor proxy is closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
visitorConn, err = sv.getNewVisitorConn()
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// visitorConn always be closed when worker done.
|
|
||||||
sv.worker(visitorConn, firstPacket)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-sv.checkCloseCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
xl.Debug("starting sudp proxy worker")
|
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
wg.Add(2)
|
|
||||||
closeCh := make(chan struct{})
|
|
||||||
|
|
||||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
|
||||||
workConnReaderFn := func(conn net.Conn) {
|
|
||||||
defer func() {
|
|
||||||
conn.Close()
|
|
||||||
close(closeCh)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
var (
|
|
||||||
rawMsg msg.Message
|
|
||||||
errRet error
|
|
||||||
)
|
|
||||||
|
|
||||||
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
|
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
|
||||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
|
||||||
xl.Warn("read from workconn for user udp conn error: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = conn.SetReadDeadline(time.Time{})
|
|
||||||
switch m := rawMsg.(type) {
|
|
||||||
case *msg.Ping:
|
|
||||||
xl.Debug("frpc visitor get ping message from frpc")
|
|
||||||
continue
|
|
||||||
case *msg.UDPPacket:
|
|
||||||
if errRet := errors.PanicToError(func() {
|
|
||||||
sv.readCh <- m
|
|
||||||
xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content)
|
|
||||||
}); errRet != nil {
|
|
||||||
xl.Info("reader goroutine for udp work connection closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
|
||||||
workConnSenderFn := func(conn net.Conn) {
|
|
||||||
defer func() {
|
|
||||||
conn.Close()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var errRet error
|
|
||||||
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:
|
|
||||||
if !ok {
|
|
||||||
xl.Info("sender goroutine for udp work connection closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
|
||||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
xl.Trace("send udp package to workConn: %s", udpMsg.Content)
|
|
||||||
case <-closeCh:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go workConnReaderFn(workConn)
|
|
||||||
go workConnSenderFn(workConn)
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
xl.Info("sudp worker is closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
visitorConn, err := sv.ctl.connectServer()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("frpc connect frps error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
|
||||||
ProxyName: sv.cfg.ServerName,
|
|
||||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
|
||||||
Timestamp: now,
|
|
||||||
UseEncryption: sv.cfg.UseEncryption,
|
|
||||||
UseCompression: sv.cfg.UseCompression,
|
|
||||||
}
|
|
||||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err)
|
|
||||||
}
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
|
||||||
|
|
||||||
if newVisitorConnRespMsg.Error != "" {
|
|
||||||
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var remote io.ReadWriteCloser
|
|
||||||
remote = visitorConn
|
|
||||||
if sv.cfg.UseEncryption {
|
|
||||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create encryption stream error: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sv.cfg.UseCompression {
|
|
||||||
remote = frpIo.WithCompression(remote)
|
|
||||||
}
|
|
||||||
return frpNet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *SUDPVisitor) Close() {
|
|
||||||
sv.mu.Lock()
|
|
||||||
defer sv.mu.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-sv.checkCloseCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
close(sv.checkCloseCh)
|
|
||||||
}
|
|
||||||
if sv.udpConn != nil {
|
|
||||||
sv.udpConn.Close()
|
|
||||||
}
|
|
||||||
close(sv.readCh)
|
|
||||||
close(sv.sendCh)
|
|
||||||
}
|
|
133
client/visitor/stcp.go
Normal file
133
client/visitor/stcp.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package visitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type STCPVisitor struct {
|
||||||
|
*BaseVisitor
|
||||||
|
|
||||||
|
cfg *config.STCPVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) Run() (err error) {
|
||||||
|
if sv.cfg.BindPort > 0 {
|
||||||
|
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go sv.worker()
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.internalConnWorker()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) Close() {
|
||||||
|
sv.BaseVisitor.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) worker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("stcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) internalConnWorker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
for {
|
||||||
|
conn, err := sv.internalLn.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("stcp internal listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
xl.Debug("get a new stcp user connection")
|
||||||
|
visitorConn, err := sv.helper.ConnectServer()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer visitorConn.Close()
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
|
RunID: sv.helper.RunID(),
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
UseEncryption: sv.cfg.UseEncryption,
|
||||||
|
UseCompression: sv.cfg.UseCompression,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("send newVisitorConnMsg to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||||
|
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
if newVisitorConnRespMsg.Error != "" {
|
||||||
|
xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote = visitorConn
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
remote = libio.WithCompression(remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
libio.Join(userConn, remote)
|
||||||
|
}
|
264
client/visitor/sudp.go
Normal file
264
client/visitor/sudp.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package visitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SUDPVisitor struct {
|
||||||
|
*BaseVisitor
|
||||||
|
|
||||||
|
checkCloseCh chan struct{}
|
||||||
|
// udpConn is the listener of udp packet
|
||||||
|
udpConn *net.UDPConn
|
||||||
|
readCh chan *msg.UDPPacket
|
||||||
|
sendCh chan *msg.UDPPacket
|
||||||
|
|
||||||
|
cfg *config.SUDPVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
// SUDP Run start listen a udp port
|
||||||
|
func (sv *SUDPVisitor) Run() (err error) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sv.udpConn, err = net.ListenUDP("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listen udp port %s error: %v", addr.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sv.sendCh = make(chan *msg.UDPPacket, 1024)
|
||||||
|
sv.readCh = make(chan *msg.UDPPacket, 1024)
|
||||||
|
|
||||||
|
xl.Info("sudp start to work, listen on %s", addr)
|
||||||
|
|
||||||
|
go sv.dispatcher()
|
||||||
|
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.clientCfg.UDPPacketSize))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SUDPVisitor) dispatcher() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
|
||||||
|
var (
|
||||||
|
visitorConn net.Conn
|
||||||
|
err error
|
||||||
|
|
||||||
|
firstPacket *msg.UDPPacket
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case firstPacket = <-sv.sendCh:
|
||||||
|
if firstPacket == nil {
|
||||||
|
xl.Info("frpc sudp visitor proxy is closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-sv.checkCloseCh:
|
||||||
|
xl.Info("frpc sudp visitor proxy is closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
visitorConn, err = sv.getNewVisitorConn()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// visitorConn always be closed when worker done.
|
||||||
|
sv.worker(visitorConn, firstPacket)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sv.checkCloseCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
xl.Debug("starting sudp proxy worker")
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(2)
|
||||||
|
closeCh := make(chan struct{})
|
||||||
|
|
||||||
|
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||||
|
workConnReaderFn := func(conn net.Conn) {
|
||||||
|
defer func() {
|
||||||
|
conn.Close()
|
||||||
|
close(closeCh)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
rawMsg msg.Message
|
||||||
|
errRet error
|
||||||
|
)
|
||||||
|
|
||||||
|
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||||
|
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||||
|
xl.Warn("read from workconn for user udp conn error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.Ping:
|
||||||
|
xl.Debug("frpc visitor get ping message from frpc")
|
||||||
|
continue
|
||||||
|
case *msg.UDPPacket:
|
||||||
|
if errRet := errors.PanicToError(func() {
|
||||||
|
sv.readCh <- m
|
||||||
|
xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content)
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Info("reader goroutine for udp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||||
|
workConnSenderFn := func(conn net.Conn) {
|
||||||
|
defer func() {
|
||||||
|
conn.Close()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errRet error
|
||||||
|
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:
|
||||||
|
if !ok {
|
||||||
|
xl.Info("sender goroutine for udp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||||
|
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Trace("send udp package to workConn: %s", udpMsg.Content)
|
||||||
|
case <-closeCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go workConnReaderFn(workConn)
|
||||||
|
go workConnSenderFn(workConn)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
xl.Info("sudp worker is closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
visitorConn, err := sv.helper.ConnectServer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("frpc connect frps error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
|
RunID: sv.helper.RunID(),
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
UseEncryption: sv.cfg.UseEncryption,
|
||||||
|
UseCompression: sv.cfg.UseCompression,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||||
|
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err)
|
||||||
|
}
|
||||||
|
_ = visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
if newVisitorConnRespMsg.Error != "" {
|
||||||
|
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote = visitorConn
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
remote, err = libio.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
remote = libio.WithCompression(remote)
|
||||||
|
}
|
||||||
|
return utilnet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SUDPVisitor) Close() {
|
||||||
|
sv.mu.Lock()
|
||||||
|
defer sv.mu.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sv.checkCloseCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
close(sv.checkCloseCh)
|
||||||
|
}
|
||||||
|
sv.BaseVisitor.Close()
|
||||||
|
if sv.udpConn != nil {
|
||||||
|
sv.udpConn.Close()
|
||||||
|
}
|
||||||
|
close(sv.readCh)
|
||||||
|
close(sv.sendCh)
|
||||||
|
}
|
104
client/visitor/visitor.go
Normal file
104
client/visitor/visitor.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package visitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper wrapps some functions for visitor to use.
|
||||||
|
type Helper interface {
|
||||||
|
// ConnectServer directly connects to the frp server.
|
||||||
|
ConnectServer() (net.Conn, error)
|
||||||
|
// TransferConn transfers the connection to another visitor.
|
||||||
|
TransferConn(string, net.Conn) error
|
||||||
|
// MsgTransporter returns the message transporter that is used to send and receive messages
|
||||||
|
// to the frp server through the controller.
|
||||||
|
MsgTransporter() transport.MessageTransporter
|
||||||
|
// RunID returns the run id of current controller.
|
||||||
|
RunID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visitor is used for forward traffics from local port tot remote service.
|
||||||
|
type Visitor interface {
|
||||||
|
Run() error
|
||||||
|
AcceptConn(conn net.Conn) error
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitor(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg config.VisitorConf,
|
||||||
|
clientCfg config.ClientCommonConf,
|
||||||
|
helper Helper,
|
||||||
|
) (visitor Visitor) {
|
||||||
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().ProxyName)
|
||||||
|
baseVisitor := BaseVisitor{
|
||||||
|
clientCfg: clientCfg,
|
||||||
|
helper: helper,
|
||||||
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
|
internalLn: utilnet.NewInternalListener(),
|
||||||
|
}
|
||||||
|
switch cfg := cfg.(type) {
|
||||||
|
case *config.STCPVisitorConf:
|
||||||
|
visitor = &STCPVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.XTCPVisitorConf:
|
||||||
|
visitor = &XTCPVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
startTunnelCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
case *config.SUDPVisitorConf:
|
||||||
|
visitor = &SUDPVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
checkCloseCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseVisitor struct {
|
||||||
|
clientCfg config.ClientCommonConf
|
||||||
|
helper Helper
|
||||||
|
l net.Listener
|
||||||
|
internalLn *utilnet.InternalListener
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BaseVisitor) AcceptConn(conn net.Conn) error {
|
||||||
|
return v.internalLn.PutConn(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BaseVisitor) Close() {
|
||||||
|
if v.l != nil {
|
||||||
|
v.l.Close()
|
||||||
|
}
|
||||||
|
if v.internalLn != nil {
|
||||||
|
v.internalLn.Close()
|
||||||
|
}
|
||||||
|
}
|
@ -12,43 +12,60 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package client
|
package visitor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VisitorManager struct {
|
type Manager struct {
|
||||||
ctl *Control
|
clientCfg config.ClientCommonConf
|
||||||
|
cfgs map[string]config.VisitorConf
|
||||||
cfgs map[string]config.VisitorConf
|
visitors map[string]Visitor
|
||||||
visitors map[string]Visitor
|
helper Helper
|
||||||
|
|
||||||
checkInterval time.Duration
|
checkInterval time.Duration
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.RWMutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVisitorManager(ctx context.Context, ctl *Control) *VisitorManager {
|
func NewManager(
|
||||||
return &VisitorManager{
|
ctx context.Context,
|
||||||
ctl: ctl,
|
runID string,
|
||||||
|
clientCfg config.ClientCommonConf,
|
||||||
|
connectServer func() (net.Conn, error),
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
) *Manager {
|
||||||
|
m := &Manager{
|
||||||
|
clientCfg: clientCfg,
|
||||||
cfgs: make(map[string]config.VisitorConf),
|
cfgs: make(map[string]config.VisitorConf),
|
||||||
visitors: make(map[string]Visitor),
|
visitors: make(map[string]Visitor),
|
||||||
checkInterval: 10 * time.Second,
|
checkInterval: 10 * time.Second,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
m.helper = &visitorHelperImpl{
|
||||||
|
connectServerFn: connectServer,
|
||||||
|
msgTransporter: msgTransporter,
|
||||||
|
transferConnFn: m.TransferConn,
|
||||||
|
runID: runID,
|
||||||
|
}
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VisitorManager) Run() {
|
func (vm *Manager) Run() {
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
|
|
||||||
ticker := time.NewTicker(vm.checkInterval)
|
ticker := time.NewTicker(vm.checkInterval)
|
||||||
@ -62,7 +79,7 @@ func (vm *VisitorManager) Run() {
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
for _, cfg := range vm.cfgs {
|
for _, cfg := range vm.cfgs {
|
||||||
name := cfg.GetBaseInfo().ProxyName
|
name := cfg.GetBaseConfig().ProxyName
|
||||||
if _, exist := vm.visitors[name]; !exist {
|
if _, exist := vm.visitors[name]; !exist {
|
||||||
xl.Info("try to start visitor [%s]", name)
|
xl.Info("try to start visitor [%s]", name)
|
||||||
_ = vm.startVisitor(cfg)
|
_ = vm.startVisitor(cfg)
|
||||||
@ -73,11 +90,24 @@ func (vm *VisitorManager) Run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (vm *Manager) Close() {
|
||||||
|
vm.mu.Lock()
|
||||||
|
defer vm.mu.Unlock()
|
||||||
|
for _, v := range vm.visitors {
|
||||||
|
v.Close()
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-vm.stopCh:
|
||||||
|
default:
|
||||||
|
close(vm.stopCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hold lock before calling this function.
|
// Hold lock before calling this function.
|
||||||
func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
func (vm *Manager) startVisitor(cfg config.VisitorConf) (err error) {
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
name := cfg.GetBaseInfo().ProxyName
|
name := cfg.GetBaseConfig().ProxyName
|
||||||
visitor := NewVisitor(vm.ctx, vm.ctl, cfg)
|
visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
|
||||||
err = visitor.Run()
|
err = visitor.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("start error: %v", err)
|
xl.Warn("start error: %v", err)
|
||||||
@ -88,7 +118,7 @@ func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
func (vm *Manager) Reload(cfgs map[string]config.VisitorConf) {
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
defer vm.mu.Unlock()
|
defer vm.mu.Unlock()
|
||||||
@ -97,9 +127,7 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
|||||||
for name, oldCfg := range vm.cfgs {
|
for name, oldCfg := range vm.cfgs {
|
||||||
del := false
|
del := false
|
||||||
cfg, ok := cfgs[name]
|
cfg, ok := cfgs[name]
|
||||||
if !ok {
|
if !ok || !reflect.DeepEqual(oldCfg, cfg) {
|
||||||
del = true
|
|
||||||
} else if !oldCfg.Compare(cfg) {
|
|
||||||
del = true
|
del = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,15 +157,36 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VisitorManager) Close() {
|
// TransferConn transfers a connection to a visitor.
|
||||||
vm.mu.Lock()
|
func (vm *Manager) TransferConn(name string, conn net.Conn) error {
|
||||||
defer vm.mu.Unlock()
|
vm.mu.RLock()
|
||||||
for _, v := range vm.visitors {
|
defer vm.mu.RUnlock()
|
||||||
v.Close()
|
v, ok := vm.visitors[name]
|
||||||
}
|
if !ok {
|
||||||
select {
|
return fmt.Errorf("visitor [%s] not found", name)
|
||||||
case <-vm.stopCh:
|
|
||||||
default:
|
|
||||||
close(vm.stopCh)
|
|
||||||
}
|
}
|
||||||
|
return v.AcceptConn(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
type visitorHelperImpl struct {
|
||||||
|
connectServerFn func() (net.Conn, error)
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
|
transferConnFn func(name string, conn net.Conn) error
|
||||||
|
runID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) ConnectServer() (net.Conn, error) {
|
||||||
|
return v.connectServerFn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) TransferConn(name string, conn net.Conn) error {
|
||||||
|
return v.transferConnFn(name, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) MsgTransporter() transport.MessageTransporter {
|
||||||
|
return v.msgTransporter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) RunID() string {
|
||||||
|
return v.runID
|
||||||
}
|
}
|
452
client/visitor/xtcp.go
Normal file
452
client/visitor/xtcp.go
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package visitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
quic "github.com/quic-go/quic-go"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/nathole"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNoTunnelSession = errors.New("no tunnel session")
|
||||||
|
|
||||||
|
type XTCPVisitor struct {
|
||||||
|
*BaseVisitor
|
||||||
|
session TunnelSession
|
||||||
|
startTunnelCh chan struct{}
|
||||||
|
retryLimiter *rate.Limiter
|
||||||
|
cancel context.CancelFunc
|
||||||
|
|
||||||
|
cfg *config.XTCPVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) Run() (err error) {
|
||||||
|
sv.ctx, sv.cancel = context.WithCancel(sv.ctx)
|
||||||
|
|
||||||
|
if sv.cfg.Protocol == "kcp" {
|
||||||
|
sv.session = NewKCPTunnelSession()
|
||||||
|
} else {
|
||||||
|
sv.session = NewQUICTunnelSession(&sv.clientCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.BindPort > 0 {
|
||||||
|
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go sv.worker()
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.internalConnWorker()
|
||||||
|
go sv.processTunnelStartEvents()
|
||||||
|
if sv.cfg.KeepTunnelOpen {
|
||||||
|
sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour)
|
||||||
|
go sv.keepTunnelOpenWorker()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) Close() {
|
||||||
|
sv.mu.Lock()
|
||||||
|
defer sv.mu.Unlock()
|
||||||
|
sv.BaseVisitor.Close()
|
||||||
|
if sv.cancel != nil {
|
||||||
|
sv.cancel()
|
||||||
|
}
|
||||||
|
if sv.session != nil {
|
||||||
|
sv.session.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) worker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("xtcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) internalConnWorker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
for {
|
||||||
|
conn, err := sv.internalLn.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("xtcp internal listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) processTunnelStartEvents() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-sv.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-sv.startTunnelCh:
|
||||||
|
start := time.Now()
|
||||||
|
sv.makeNatHole()
|
||||||
|
duration := time.Since(start)
|
||||||
|
// avoid too frequently
|
||||||
|
if duration < 10*time.Second {
|
||||||
|
time.Sleep(10*time.Second - duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) keepTunnelOpenWorker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
ticker := time.NewTicker(time.Duration(sv.cfg.MinRetryInterval) * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
sv.startTunnelCh <- struct{}{}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-sv.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
xl.Debug("keepTunnelOpenWorker try to check tunnel...")
|
||||||
|
conn, err := sv.getTunnelConn()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("keepTunnelOpenWorker get tunnel connection error: %v", err)
|
||||||
|
_ = sv.retryLimiter.Wait(sv.ctx)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
xl.Debug("keepTunnelOpenWorker check success")
|
||||||
|
if conn != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
isConnTrasfered := false
|
||||||
|
defer func() {
|
||||||
|
if !isConnTrasfered {
|
||||||
|
userConn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
xl.Debug("get a new xtcp user connection")
|
||||||
|
|
||||||
|
// Open a tunnel connection to the server. If there is already a successful hole-punching connection,
|
||||||
|
// it will be reused. Otherwise, it will block and wait for a successful hole-punching connection until timeout.
|
||||||
|
ctx := context.Background()
|
||||||
|
if sv.cfg.FallbackTo != "" {
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(sv.cfg.FallbackTimeoutMs)*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
ctx = timeoutCtx
|
||||||
|
}
|
||||||
|
tunnelConn, err := sv.openTunnel(ctx)
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("open tunnel error: %v", err)
|
||||||
|
// no fallback, just return
|
||||||
|
if sv.cfg.FallbackTo == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Debug("try to transfer connection to visitor: %s", sv.cfg.FallbackTo)
|
||||||
|
if err := sv.helper.TransferConn(sv.cfg.FallbackTo, userConn); err != nil {
|
||||||
|
xl.Error("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isConnTrasfered = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var muxConnRWCloser io.ReadWriteCloser = tunnelConn
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
muxConnRWCloser = libio.WithCompression(muxConnRWCloser)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, errs := libio.Join(userConn, muxConnRWCloser)
|
||||||
|
xl.Debug("join connections closed")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
xl.Trace("join connections errors: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// openTunnel will open a tunnel connection to the target server.
|
||||||
|
func (sv *XTCPVisitor) openTunnel(ctx context.Context) (conn net.Conn, err error) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
timeoutC := time.After(20 * time.Second)
|
||||||
|
immediateTrigger := make(chan struct{}, 1)
|
||||||
|
defer close(immediateTrigger)
|
||||||
|
immediateTrigger <- struct{}{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-sv.ctx.Done():
|
||||||
|
return nil, sv.ctx.Err()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-immediateTrigger:
|
||||||
|
conn, err = sv.getTunnelConn()
|
||||||
|
case <-ticker.C:
|
||||||
|
conn, err = sv.getTunnelConn()
|
||||||
|
case <-timeoutC:
|
||||||
|
return nil, fmt.Errorf("open tunnel timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != ErrNoTunnelSession {
|
||||||
|
xl.Warn("get tunnel connection error: %v", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) getTunnelConn() (net.Conn, error) {
|
||||||
|
conn, err := sv.session.OpenConn(sv.ctx)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
sv.session.Close()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case sv.startTunnelCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0. PreCheck
|
||||||
|
// 1. Prepare
|
||||||
|
// 2. ExchangeInfo
|
||||||
|
// 3. MakeNATHole
|
||||||
|
// 4. Create a tunnel session using an underlying UDP connection.
|
||||||
|
func (sv *XTCPVisitor) makeNatHole() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
if err := nathole.PreCheck(sv.ctx, sv.helper.MsgTransporter(), sv.cfg.ServerName, 5*time.Second); err != nil {
|
||||||
|
xl.Warn("nathole precheck error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer})
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("nathole prepare error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
||||||
|
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
||||||
|
|
||||||
|
listenConn := prepareResult.ListenConn
|
||||||
|
|
||||||
|
// send NatHoleVisitor to server
|
||||||
|
now := time.Now().Unix()
|
||||||
|
transactionID := nathole.NewTransactionID()
|
||||||
|
natHoleVisitorMsg := &msg.NatHoleVisitor{
|
||||||
|
TransactionID: transactionID,
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
Protocol: sv.cfg.Protocol,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
MappedAddrs: prepareResult.Addrs,
|
||||||
|
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
natHoleRespMsg, err := nathole.ExchangeInfo(sv.ctx, sv.helper.MsgTransporter(), transactionID, natHoleVisitorMsg, 5*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warn("nathole exchange info error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
||||||
|
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||||
|
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||||
|
|
||||||
|
newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warn("make hole error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listenConn = newListenConn
|
||||||
|
xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||||
|
|
||||||
|
if err := sv.session.Init(listenConn, raddr); err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warn("init tunnel session error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TunnelSession interface {
|
||||||
|
Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error
|
||||||
|
OpenConn(context.Context) (net.Conn, error)
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type KCPTunnelSession struct {
|
||||||
|
session *fmux.Session
|
||||||
|
lConn *net.UDPConn
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKCPTunnelSession() TunnelSession {
|
||||||
|
return &KCPTunnelSession{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks *KCPTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error {
|
||||||
|
listenConn.Close()
|
||||||
|
laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String())
|
||||||
|
lConn, err := net.DialUDP("udp", laddr, raddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dial udp error: %v", err)
|
||||||
|
}
|
||||||
|
remote, err := utilnet.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create kcp connection from udp connection error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 10 * time.Second
|
||||||
|
fmuxCfg.MaxStreamWindowSize = 2 * 1024 * 1024
|
||||||
|
fmuxCfg.LogOutput = io.Discard
|
||||||
|
session, err := fmux.Client(remote, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
remote.Close()
|
||||||
|
return fmt.Errorf("initial client session error: %v", err)
|
||||||
|
}
|
||||||
|
ks.mu.Lock()
|
||||||
|
ks.session = session
|
||||||
|
ks.lConn = lConn
|
||||||
|
ks.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks *KCPTunnelSession) OpenConn(ctx context.Context) (net.Conn, error) {
|
||||||
|
ks.mu.RLock()
|
||||||
|
defer ks.mu.RUnlock()
|
||||||
|
session := ks.session
|
||||||
|
if session == nil {
|
||||||
|
return nil, ErrNoTunnelSession
|
||||||
|
}
|
||||||
|
return session.Open()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks *KCPTunnelSession) Close() {
|
||||||
|
ks.mu.Lock()
|
||||||
|
defer ks.mu.Unlock()
|
||||||
|
if ks.session != nil {
|
||||||
|
_ = ks.session.Close()
|
||||||
|
ks.session = nil
|
||||||
|
}
|
||||||
|
if ks.lConn != nil {
|
||||||
|
_ = ks.lConn.Close()
|
||||||
|
ks.lConn = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUICTunnelSession struct {
|
||||||
|
session quic.Connection
|
||||||
|
listenConn *net.UDPConn
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
clientCfg *config.ClientCommonConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQUICTunnelSession(clientCfg *config.ClientCommonConf) TunnelSession {
|
||||||
|
return &QUICTunnelSession{
|
||||||
|
clientCfg: clientCfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qs *QUICTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error {
|
||||||
|
tlsConfig, err := transport.NewClientTLSConfig("", "", "", raddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create tls config error: %v", err)
|
||||||
|
}
|
||||||
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
|
quicConn, err := quic.Dial(listenConn, raddr, raddr.String(), tlsConfig,
|
||||||
|
&quic.Config{
|
||||||
|
MaxIdleTimeout: time.Duration(qs.clientCfg.QUICMaxIdleTimeout) * time.Second,
|
||||||
|
MaxIncomingStreams: int64(qs.clientCfg.QUICMaxIncomingStreams),
|
||||||
|
KeepAlivePeriod: time.Duration(qs.clientCfg.QUICKeepalivePeriod) * time.Second,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dial quic error: %v", err)
|
||||||
|
}
|
||||||
|
qs.mu.Lock()
|
||||||
|
qs.session = quicConn
|
||||||
|
qs.listenConn = listenConn
|
||||||
|
qs.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qs *QUICTunnelSession) OpenConn(ctx context.Context) (net.Conn, error) {
|
||||||
|
qs.mu.RLock()
|
||||||
|
defer qs.mu.RUnlock()
|
||||||
|
session := qs.session
|
||||||
|
if session == nil {
|
||||||
|
return nil, ErrNoTunnelSession
|
||||||
|
}
|
||||||
|
stream, err := session.OpenStreamSync(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return utilnet.QuicStreamToNetConn(stream, session), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qs *QUICTunnelSession) Close() {
|
||||||
|
qs.mu.Lock()
|
||||||
|
defer qs.mu.Unlock()
|
||||||
|
if qs.session != nil {
|
||||||
|
_ = qs.session.CloseWithError(0, "")
|
||||||
|
qs.session = nil
|
||||||
|
}
|
||||||
|
if qs.listenConn != nil {
|
||||||
|
_ = qs.listenConn.Close()
|
||||||
|
qs.listenConn = nil
|
||||||
|
}
|
||||||
|
}
|
@ -79,7 +79,7 @@ var httpCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||||
|
|
||||||
err = cfg.CheckForCli()
|
err = cfg.ValidateForClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -71,7 +71,7 @@ var httpsCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||||
|
|
||||||
err = cfg.CheckForCli()
|
err = cfg.ValidateForClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -16,9 +16,7 @@ package sub
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@ -28,7 +26,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
natHoleSTUNServer string
|
natHoleSTUNServer string
|
||||||
serverUDPPort int
|
natHoleLocalAddr string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -37,8 +35,8 @@ func init() {
|
|||||||
rootCmd.AddCommand(natholeCmd)
|
rootCmd.AddCommand(natholeCmd)
|
||||||
natholeCmd.AddCommand(natholeDiscoveryCmd)
|
natholeCmd.AddCommand(natholeDiscoveryCmd)
|
||||||
|
|
||||||
natholeCmd.PersistentFlags().StringVarP(&natHoleSTUNServer, "nat_hole_stun_server", "", "stun.easyvoip.com:3478", "STUN server address for nathole")
|
natholeCmd.PersistentFlags().StringVarP(&natHoleSTUNServer, "nat_hole_stun_server", "", "", "STUN server address for nathole")
|
||||||
natholeCmd.PersistentFlags().IntVarP(&serverUDPPort, "server_udp_port", "", 0, "UDP port of frps for nathole")
|
natholeCmd.PersistentFlags().StringVarP(&natHoleLocalAddr, "nat_hole_local_addr", "l", "", "local address to connect STUN server")
|
||||||
}
|
}
|
||||||
|
|
||||||
var natholeCmd = &cobra.Command{
|
var natholeCmd = &cobra.Command{
|
||||||
@ -48,48 +46,45 @@ var natholeCmd = &cobra.Command{
|
|||||||
|
|
||||||
var natholeDiscoveryCmd = &cobra.Command{
|
var natholeDiscoveryCmd = &cobra.Command{
|
||||||
Use: "discover",
|
Use: "discover",
|
||||||
Short: "Discover nathole information by frps and stun server",
|
Short: "Discover nathole information from stun server",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// ignore error here, because we can use command line pameters
|
// ignore error here, because we can use command line pameters
|
||||||
cfg, _, _, _ := config.ParseClientConfig(cfgFile)
|
cfg, _, _, err := config.ParseClientConfig(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
cfg = config.GetDefaultClientConf()
|
||||||
|
}
|
||||||
if natHoleSTUNServer != "" {
|
if natHoleSTUNServer != "" {
|
||||||
cfg.NatHoleSTUNServer = natHoleSTUNServer
|
cfg.NatHoleSTUNServer = natHoleSTUNServer
|
||||||
}
|
}
|
||||||
if serverUDPPort != 0 {
|
|
||||||
cfg.ServerUDPPort = serverUDPPort
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateForNatHoleDiscovery(cfg); err != nil {
|
if err := validateForNatHoleDiscovery(cfg); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverAddr := ""
|
addrs, localAddr, err := nathole.Discover([]string{cfg.NatHoleSTUNServer}, natHoleLocalAddr)
|
||||||
if cfg.ServerUDPPort != 0 {
|
|
||||||
serverAddr = net.JoinHostPort(cfg.ServerAddr, strconv.Itoa(cfg.ServerUDPPort))
|
|
||||||
}
|
|
||||||
addresses, err := nathole.Discover(
|
|
||||||
serverAddr,
|
|
||||||
[]string{cfg.NatHoleSTUNServer},
|
|
||||||
[]byte(cfg.Token),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("discover error:", err)
|
fmt.Println("discover error:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if len(addresses) < 2 {
|
if len(addrs) < 2 {
|
||||||
fmt.Printf("discover error: can not get enough addresses, need 2, got: %v\n", addresses)
|
fmt.Printf("discover error: can not get enough addresses, need 2, got: %v\n", addrs)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
natType, behavior, err := nathole.ClassifyNATType(addresses)
|
localIPs, _ := nathole.ListLocalIPsForNatHole(10)
|
||||||
|
|
||||||
|
natFeature, err := nathole.ClassifyNATFeature(addrs, localIPs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("classify nat type error:", err)
|
fmt.Println("classify nat feature error:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("Your NAT type is:", natType)
|
fmt.Println("STUN server:", cfg.NatHoleSTUNServer)
|
||||||
fmt.Println("Behavior is:", behavior)
|
fmt.Println("Your NAT type is:", natFeature.NatType)
|
||||||
fmt.Println("External address is:", addresses)
|
fmt.Println("Behavior is:", natFeature.Behavior)
|
||||||
|
fmt.Println("External address is:", addrs)
|
||||||
|
fmt.Println("Local address is:", localAddr.String())
|
||||||
|
fmt.Println("Public Network:", natFeature.PublicNetwork)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ var (
|
|||||||
logFile string
|
logFile string
|
||||||
logMaxDays int
|
logMaxDays int
|
||||||
disableLogColor bool
|
disableLogColor bool
|
||||||
|
dnsServer string
|
||||||
|
|
||||||
proxyName string
|
proxyName string
|
||||||
localIP string
|
localIP string
|
||||||
@ -94,6 +95,7 @@ func RegisterCommonFlags(cmd *cobra.Command) {
|
|||||||
cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "enable frpc tls")
|
cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "enable frpc tls")
|
||||||
|
cmd.PersistentFlags().StringVarP(&dnsServer, "dns_server", "", "", "specify dns server instead of using system default one")
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@ -108,39 +110,40 @@ var rootCmd = &cobra.Command{
|
|||||||
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
// 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.
|
// Note that it's only designed for testing. It's not guaranteed to be stable.
|
||||||
if cfgDir != "" {
|
if cfgDir != "" {
|
||||||
var wg sync.WaitGroup
|
_ = runMultipleClients(cfgDir)
|
||||||
_ = 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not show command usage here.
|
// Do not show command usage here.
|
||||||
err := runClient(cfgFile)
|
err := runClient(cfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runMultipleClients(cfgDir string) error {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
err := filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil || 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 err
|
||||||
|
}
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -177,6 +180,7 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
|||||||
cfg.LogFile = logFile
|
cfg.LogFile = logFile
|
||||||
cfg.LogMaxDays = int64(logMaxDays)
|
cfg.LogMaxDays = int64(logMaxDays)
|
||||||
cfg.DisableLogColor = disableLogColor
|
cfg.DisableLogColor = disableLogColor
|
||||||
|
cfg.DNSServer = dnsServer
|
||||||
|
|
||||||
// Only token authentication is supported in cmd mode
|
// Only token authentication is supported in cmd mode
|
||||||
cfg.ClientConfig = auth.GetDefaultClientConf()
|
cfg.ClientConfig = auth.GetDefaultClientConf()
|
||||||
@ -194,6 +198,7 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
|||||||
func runClient(cfgFilePath string) error {
|
func runClient(cfgFilePath string) error {
|
||||||
cfg, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(cfgFilePath)
|
cfg, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(cfgFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
|
||||||
@ -209,8 +214,8 @@ func startService(
|
|||||||
cfg.LogMaxDays, cfg.DisableLogColor)
|
cfg.LogMaxDays, cfg.DisableLogColor)
|
||||||
|
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
log.Trace("start frpc service for config file [%s]", cfgFile)
|
log.Info("start frpc service for config file [%s]", cfgFile)
|
||||||
defer log.Trace("frpc service for config file [%s] stopped", cfgFile)
|
defer log.Info("frpc service for config file [%s] stopped", cfgFile)
|
||||||
}
|
}
|
||||||
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
|
@ -78,7 +78,7 @@ var stcpCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||||
err = cfg.CheckForCli()
|
err = cfg.ValidateForClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -95,7 +95,7 @@ var stcpCmd = &cobra.Command{
|
|||||||
cfg.ServerName = serverName
|
cfg.ServerName = serverName
|
||||||
cfg.BindAddr = bindAddr
|
cfg.BindAddr = bindAddr
|
||||||
cfg.BindPort = bindPort
|
cfg.BindPort = bindPort
|
||||||
err = cfg.Check()
|
err = cfg.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -78,7 +78,7 @@ var sudpCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||||
err = cfg.CheckForCli()
|
err = cfg.ValidateForClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -95,7 +95,7 @@ var sudpCmd = &cobra.Command{
|
|||||||
cfg.ServerName = serverName
|
cfg.ServerName = serverName
|
||||||
cfg.BindAddr = bindAddr
|
cfg.BindAddr = bindAddr
|
||||||
cfg.BindPort = bindPort
|
cfg.BindPort = bindPort
|
||||||
err = cfg.Check()
|
err = cfg.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -68,7 +68,7 @@ var tcpCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||||
|
|
||||||
err = cfg.CheckForCli()
|
err = cfg.ValidateForClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -73,7 +73,7 @@ var tcpMuxCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||||
|
|
||||||
err = cfg.CheckForCli()
|
err = cfg.ValidateForClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -68,7 +68,7 @@ var udpCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||||
|
|
||||||
err = cfg.CheckForCli()
|
err = cfg.ValidateForClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -78,7 +78,7 @@ var xtcpCmd = &cobra.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
cfg.BandwidthLimitMode = bandwidthLimitMode
|
cfg.BandwidthLimitMode = bandwidthLimitMode
|
||||||
err = cfg.CheckForCli()
|
err = cfg.ValidateForClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -95,7 +95,7 @@ var xtcpCmd = &cobra.Command{
|
|||||||
cfg.ServerName = serverName
|
cfg.ServerName = serverName
|
||||||
cfg.BindAddr = bindAddr
|
cfg.BindAddr = bindAddr
|
||||||
cfg.BindPort = bindPort
|
cfg.BindPort = bindPort
|
||||||
err = cfg.Check()
|
err = cfg.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -39,7 +39,6 @@ var (
|
|||||||
|
|
||||||
bindAddr string
|
bindAddr string
|
||||||
bindPort int
|
bindPort int
|
||||||
bindUDPPort int
|
|
||||||
kcpBindPort int
|
kcpBindPort int
|
||||||
proxyBindAddr string
|
proxyBindAddr string
|
||||||
vhostHTTPPort int
|
vhostHTTPPort int
|
||||||
@ -70,7 +69,6 @@ func init() {
|
|||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
||||||
rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port")
|
rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port")
|
||||||
rootCmd.PersistentFlags().IntVarP(&bindUDPPort, "bind_udp_port", "", 0, "bind udp port")
|
|
||||||
rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port")
|
||||||
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
|
||||||
rootCmd.PersistentFlags().IntVarP(&vhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
|
rootCmd.PersistentFlags().IntVarP(&vhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
|
||||||
@ -159,7 +157,6 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
|
|||||||
|
|
||||||
cfg.BindAddr = bindAddr
|
cfg.BindAddr = bindAddr
|
||||||
cfg.BindPort = bindPort
|
cfg.BindPort = bindPort
|
||||||
cfg.BindUDPPort = bindUDPPort
|
|
||||||
cfg.KCPBindPort = kcpBindPort
|
cfg.KCPBindPort = kcpBindPort
|
||||||
cfg.ProxyBindAddr = proxyBindAddr
|
cfg.ProxyBindAddr = proxyBindAddr
|
||||||
cfg.VhostHTTPPort = vhostHTTPPort
|
cfg.VhostHTTPPort = vhostHTTPPort
|
||||||
|
@ -6,14 +6,6 @@
|
|||||||
server_addr = 0.0.0.0
|
server_addr = 0.0.0.0
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
|
|
||||||
# Specify another address of the server to connect for nat hole. By default, it's same with
|
|
||||||
# server_addr.
|
|
||||||
# nat_hole_server_addr = 0.0.0.0
|
|
||||||
|
|
||||||
# ServerUDPPort specifies the server port to help penetrate NAT hole. By default, this value is 0.
|
|
||||||
# This parameter is only used when executing "nathole discover" in the command line.
|
|
||||||
# server_udp_port = 0
|
|
||||||
|
|
||||||
# STUN server to help penetrate NAT hole.
|
# STUN server to help penetrate NAT hole.
|
||||||
# nat_hole_stun_server = stun.easyvoip.com:3478
|
# nat_hole_stun_server = stun.easyvoip.com:3478
|
||||||
|
|
||||||
@ -334,6 +326,9 @@ local_ip = 127.0.0.1
|
|||||||
local_port = 22
|
local_port = 22
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
|
# If not empty, only visitors from specified users can connect.
|
||||||
|
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||||
|
allow_users = *
|
||||||
|
|
||||||
# user of frpc should be same in both stcp server and stcp visitor
|
# user of frpc should be same in both stcp server and stcp visitor
|
||||||
[secret_tcp_visitor]
|
[secret_tcp_visitor]
|
||||||
@ -345,6 +340,8 @@ server_name = secret_tcp
|
|||||||
sk = abcdefg
|
sk = abcdefg
|
||||||
# connect this address to visitor stcp server
|
# connect this address to visitor stcp server
|
||||||
bind_addr = 127.0.0.1
|
bind_addr = 127.0.0.1
|
||||||
|
# bind_port can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||||
|
# other visitors. (This is not supported for SUDP now)
|
||||||
bind_port = 9000
|
bind_port = 9000
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
@ -356,16 +353,30 @@ local_ip = 127.0.0.1
|
|||||||
local_port = 22
|
local_port = 22
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
|
# If not empty, only visitors from specified users can connect.
|
||||||
|
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||||
|
allow_users = user1, user2
|
||||||
|
|
||||||
[p2p_tcp_visitor]
|
[p2p_tcp_visitor]
|
||||||
role = visitor
|
role = visitor
|
||||||
type = xtcp
|
type = xtcp
|
||||||
|
# if the server user is not set, it defaults to the current user
|
||||||
|
server_user = user1
|
||||||
server_name = p2p_tcp
|
server_name = p2p_tcp
|
||||||
sk = abcdefg
|
sk = abcdefg
|
||||||
bind_addr = 127.0.0.1
|
bind_addr = 127.0.0.1
|
||||||
|
# bind_port can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||||
|
# other visitors. (This is not supported for SUDP now)
|
||||||
bind_port = 9001
|
bind_port = 9001
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
|
# when automatic tunnel persistence is required, set it to true
|
||||||
|
keep_tunnel_open = false
|
||||||
|
# effective when keep_tunnel_open is set to true, the number of attempts to punch through per hour
|
||||||
|
max_retries_an_hour = 8
|
||||||
|
min_retry_interval = 90
|
||||||
|
# fallback_to = stcp_visitor
|
||||||
|
# fallback_timeout_ms = 500
|
||||||
|
|
||||||
[tcpmuxhttpconnect]
|
[tcpmuxhttpconnect]
|
||||||
type = tcpmux
|
type = tcpmux
|
||||||
|
@ -6,9 +6,6 @@
|
|||||||
bind_addr = 0.0.0.0
|
bind_addr = 0.0.0.0
|
||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
|
|
||||||
# udp port to help make udp hole to penetrate nat
|
|
||||||
bind_udp_port = 7001
|
|
||||||
|
|
||||||
# udp port used for kcp protocol, it can be same with 'bind_port'.
|
# udp port used for kcp protocol, it can be same with 'bind_port'.
|
||||||
# if not set, kcp is disabled in frps.
|
# if not set, kcp is disabled in frps.
|
||||||
kcp_bind_port = 7000
|
kcp_bind_port = 7000
|
||||||
@ -157,6 +154,9 @@ udp_packet_size = 1500
|
|||||||
# Dashboard port must be set first
|
# Dashboard port must be set first
|
||||||
pprof_enable = false
|
pprof_enable = false
|
||||||
|
|
||||||
|
# Retention time for NAT hole punching strategy data.
|
||||||
|
nat_hole_analysis_data_reserve_hours = 168
|
||||||
|
|
||||||
[plugin.user-manager]
|
[plugin.user-manager]
|
||||||
addr = 127.0.0.1:9000
|
addr = 127.0.0.1:9000
|
||||||
path = /handler
|
path = /handler
|
||||||
|
9
go.mod
9
go.mod
@ -18,12 +18,14 @@ require (
|
|||||||
github.com/pion/stun v0.4.0
|
github.com/pion/stun v0.4.0
|
||||||
github.com/pires/go-proxyproto v0.6.2
|
github.com/pires/go-proxyproto v0.6.2
|
||||||
github.com/prometheus/client_golang v1.13.0
|
github.com/prometheus/client_golang v1.13.0
|
||||||
github.com/quic-go/quic-go v0.32.0
|
github.com/quic-go/quic-go v0.34.0
|
||||||
github.com/rodaine/table v1.0.1
|
github.com/rodaine/table v1.0.1
|
||||||
|
github.com/samber/lo v1.38.1
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.1.3
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
golang.org/x/net v0.7.0
|
golang.org/x/net v0.7.0
|
||||||
golang.org/x/oauth2 v0.3.0
|
golang.org/x/oauth2 v0.3.0
|
||||||
|
golang.org/x/sync v0.1.0
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
k8s.io/apimachinery v0.26.1
|
k8s.io/apimachinery v0.26.1
|
||||||
@ -55,9 +57,8 @@ require (
|
|||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
||||||
|
18
go.sum
18
go.sum
@ -381,14 +381,12 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
|||||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
|
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
|
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
|
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
|
github.com/quic-go/quic-go v0.34.0 h1:OvOJ9LFjTySgwOTYUZmNoq0FzVicP8YujpV0kB7m2lU=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
github.com/quic-go/quic-go v0.34.0/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
||||||
github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA=
|
|
||||||
github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo=
|
|
||||||
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
|
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
|
||||||
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
|
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
@ -399,6 +397,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA
|
|||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||||
|
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
@ -602,6 +602,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -17,4 +17,4 @@ if [ x${LOG_LEVEL} != x"" ]; then
|
|||||||
logLevel=${LOG_LEVEL}
|
logLevel=${LOG_LEVEL}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ginkgo -nodes=8 --poll-progress-after=20s ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}
|
ginkgo -nodes=8 --poll-progress-after=30s ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}
|
||||||
|
@ -73,30 +73,30 @@ func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkC
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) VerifyLogin(loginMsg *msg.Login) error {
|
func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error {
|
||||||
if util.GetAuthKey(auth.token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
|
if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
|
||||||
return fmt.Errorf("token in login doesn't match token from configuration")
|
return fmt.Errorf("token in login doesn't match token from configuration")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) VerifyPing(pingMsg *msg.Ping) error {
|
func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
|
||||||
if !auth.AuthenticateHeartBeats {
|
if !auth.AuthenticateHeartBeats {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.GetAuthKey(auth.token, pingMsg.Timestamp) != pingMsg.PrivilegeKey {
|
if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
|
||||||
return fmt.Errorf("token in heartbeat doesn't match token from configuration")
|
return fmt.Errorf("token in heartbeat doesn't match token from configuration")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
|
||||||
if !auth.AuthenticateNewWorkConns {
|
if !auth.AuthenticateNewWorkConns {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp) != newWorkConnMsg.PrivilegeKey {
|
if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
|
||||||
return fmt.Errorf("token in NewWorkConn doesn't match token from configuration")
|
return fmt.Errorf("token in NewWorkConn doesn't match token from configuration")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -35,15 +35,9 @@ type ClientCommonConf struct {
|
|||||||
// ServerAddr specifies the address of the server to connect to. By
|
// ServerAddr specifies the address of the server to connect to. By
|
||||||
// default, this value is "0.0.0.0".
|
// default, this value is "0.0.0.0".
|
||||||
ServerAddr string `ini:"server_addr" json:"server_addr"`
|
ServerAddr string `ini:"server_addr" json:"server_addr"`
|
||||||
// Specify another address of the server to connect for nat hole. By default, it's same with
|
|
||||||
// ServerAddr.
|
|
||||||
NatHoleServerAddr string `ini:"nat_hole_server_addr" json:"nat_hole_server_addr"`
|
|
||||||
// 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"`
|
||||||
// ServerUDPPort specifies the server port to help penetrate NAT hole. By default, this value is 0.
|
|
||||||
// This parameter is only used when executing "nathole discover" in the command line.
|
|
||||||
ServerUDPPort int `ini:"server_udp_port" json:"server_udp_port"`
|
|
||||||
// STUN server to help penetrate NAT hole.
|
// STUN server to help penetrate NAT hole.
|
||||||
NatHoleSTUNServer string `ini:"nat_hole_stun_server" json:"nat_hole_stun_server"`
|
NatHoleSTUNServer string `ini:"nat_hole_stun_server" json:"nat_hole_stun_server"`
|
||||||
// The maximum amount of time a dial to server will wait for a connect to complete.
|
// The maximum amount of time a dial to server will wait for a connect to complete.
|
||||||
|
@ -500,8 +500,10 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||||
},
|
},
|
||||||
Role: "server",
|
RoleServerCommonConf: RoleServerCommonConf{
|
||||||
Sk: "abcdefg",
|
Role: "server",
|
||||||
|
Sk: "abcdefg",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
testUser + ".p2p_tcp": &XTCPProxyConf{
|
testUser + ".p2p_tcp": &XTCPProxyConf{
|
||||||
BaseProxyConf: BaseProxyConf{
|
BaseProxyConf: BaseProxyConf{
|
||||||
@ -513,8 +515,10 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||||
},
|
},
|
||||||
Role: "server",
|
RoleServerCommonConf: RoleServerCommonConf{
|
||||||
Sk: "abcdefg",
|
Role: "server",
|
||||||
|
Sk: "abcdefg",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
testUser + ".tcpmuxhttpconnect": &TCPMuxProxyConf{
|
testUser + ".tcpmuxhttpconnect": &TCPMuxProxyConf{
|
||||||
BaseProxyConf: BaseProxyConf{
|
BaseProxyConf: BaseProxyConf{
|
||||||
@ -661,6 +665,10 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
|||||||
BindAddr: "127.0.0.1",
|
BindAddr: "127.0.0.1",
|
||||||
BindPort: 9001,
|
BindPort: 9001,
|
||||||
},
|
},
|
||||||
|
Protocol: "quic",
|
||||||
|
MaxRetriesAnHour: 8,
|
||||||
|
MinRetryInterval: 90,
|
||||||
|
FallbackTimeoutMs: 1000,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,13 +51,23 @@ func NewConfByType(proxyType string) ProxyConf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProxyConf interface {
|
type ProxyConf interface {
|
||||||
GetBaseInfo() *BaseProxyConf
|
// GetBaseConfig returns the BaseProxyConf for this config.
|
||||||
|
GetBaseConfig() *BaseProxyConf
|
||||||
|
// SetDefaultValues sets the default values for this config.
|
||||||
|
SetDefaultValues()
|
||||||
|
// UnmarshalFromMsg unmarshals a msg.NewProxy message into this config.
|
||||||
|
// This function will be called on the frps side.
|
||||||
UnmarshalFromMsg(*msg.NewProxy)
|
UnmarshalFromMsg(*msg.NewProxy)
|
||||||
|
// UnmarshalFromIni unmarshals a ini.Section into this config. This function
|
||||||
|
// will be called on the frpc side.
|
||||||
UnmarshalFromIni(string, string, *ini.Section) error
|
UnmarshalFromIni(string, string, *ini.Section) error
|
||||||
|
// MarshalToMsg marshals this config into a msg.NewProxy message. This
|
||||||
|
// function will be called on the frpc side.
|
||||||
MarshalToMsg(*msg.NewProxy)
|
MarshalToMsg(*msg.NewProxy)
|
||||||
CheckForCli() error
|
// ValidateForClient checks that the config is valid for the frpc side.
|
||||||
CheckForSvr(ServerCommonConf) error
|
ValidateForClient() error
|
||||||
Compare(ProxyConf) bool
|
// ValidateForServer checks that the config is valid for the frps side.
|
||||||
|
ValidateForServer(ServerCommonConf) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalSvrConf configures what location the client will to, or what
|
// LocalSvrConf configures what location the client will to, or what
|
||||||
@ -158,6 +168,16 @@ type DomainConf struct {
|
|||||||
SubDomain string `ini:"subdomain" json:"subdomain"`
|
SubDomain string `ini:"subdomain" json:"subdomain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RoleServerCommonConf struct {
|
||||||
|
Role string `ini:"role" json:"role"`
|
||||||
|
Sk string `ini:"sk" json:"sk"`
|
||||||
|
AllowUsers []string `ini:"allow_users" json:"allow_users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *RoleServerCommonConf) setDefaultValues() {
|
||||||
|
cfg.Role = "server"
|
||||||
|
}
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
type HTTPProxyConf struct {
|
type HTTPProxyConf struct {
|
||||||
BaseProxyConf `ini:",extends"`
|
BaseProxyConf `ini:",extends"`
|
||||||
@ -183,6 +203,13 @@ type TCPProxyConf struct {
|
|||||||
RemotePort int `ini:"remote_port" json:"remote_port"`
|
RemotePort int `ini:"remote_port" json:"remote_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UDP
|
||||||
|
type UDPProxyConf struct {
|
||||||
|
BaseProxyConf `ini:",extends"`
|
||||||
|
|
||||||
|
RemotePort int `ini:"remote_port" json:"remote_port"`
|
||||||
|
}
|
||||||
|
|
||||||
// TCPMux
|
// TCPMux
|
||||||
type TCPMuxProxyConf struct {
|
type TCPMuxProxyConf struct {
|
||||||
BaseProxyConf `ini:",extends"`
|
BaseProxyConf `ini:",extends"`
|
||||||
@ -196,80 +223,30 @@ type TCPMuxProxyConf struct {
|
|||||||
|
|
||||||
// STCP
|
// STCP
|
||||||
type STCPProxyConf struct {
|
type STCPProxyConf struct {
|
||||||
BaseProxyConf `ini:",extends"`
|
BaseProxyConf `ini:",extends"`
|
||||||
|
RoleServerCommonConf `ini:",extends"`
|
||||||
Role string `ini:"role" json:"role"`
|
|
||||||
Sk string `ini:"sk" json:"sk"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// XTCP
|
// XTCP
|
||||||
type XTCPProxyConf struct {
|
type XTCPProxyConf struct {
|
||||||
BaseProxyConf `ini:",extends"`
|
BaseProxyConf `ini:",extends"`
|
||||||
|
RoleServerCommonConf `ini:",extends"`
|
||||||
Role string `ini:"role" json:"role"`
|
|
||||||
Sk string `ini:"sk" json:"sk"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDP
|
|
||||||
type UDPProxyConf struct {
|
|
||||||
BaseProxyConf `ini:",extends"`
|
|
||||||
|
|
||||||
RemotePort int `ini:"remote_port" json:"remote_port"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SUDP
|
// SUDP
|
||||||
type SUDPProxyConf struct {
|
type SUDPProxyConf struct {
|
||||||
BaseProxyConf `ini:",extends"`
|
BaseProxyConf `ini:",extends"`
|
||||||
|
RoleServerCommonConf `ini:",extends"`
|
||||||
Role string `ini:"role" json:"role"`
|
|
||||||
Sk string `ini:"sk" json:"sk"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy Conf Loader
|
// Proxy Conf Loader
|
||||||
// DefaultProxyConf creates a empty ProxyConf object by proxyType.
|
// DefaultProxyConf creates a empty ProxyConf object by proxyType.
|
||||||
// If proxyType doesn't exist, return nil.
|
// If proxyType doesn't exist, return nil.
|
||||||
func DefaultProxyConf(proxyType string) ProxyConf {
|
func DefaultProxyConf(proxyType string) ProxyConf {
|
||||||
var conf ProxyConf
|
conf := NewConfByType(proxyType)
|
||||||
switch proxyType {
|
if conf != nil {
|
||||||
case consts.TCPProxy:
|
conf.SetDefaultValues()
|
||||||
conf = &TCPProxyConf{
|
|
||||||
BaseProxyConf: defaultBaseProxyConf(proxyType),
|
|
||||||
}
|
|
||||||
case consts.TCPMuxProxy:
|
|
||||||
conf = &TCPMuxProxyConf{
|
|
||||||
BaseProxyConf: defaultBaseProxyConf(proxyType),
|
|
||||||
}
|
|
||||||
case consts.UDPProxy:
|
|
||||||
conf = &UDPProxyConf{
|
|
||||||
BaseProxyConf: defaultBaseProxyConf(proxyType),
|
|
||||||
}
|
|
||||||
case consts.HTTPProxy:
|
|
||||||
conf = &HTTPProxyConf{
|
|
||||||
BaseProxyConf: defaultBaseProxyConf(proxyType),
|
|
||||||
}
|
|
||||||
case consts.HTTPSProxy:
|
|
||||||
conf = &HTTPSProxyConf{
|
|
||||||
BaseProxyConf: defaultBaseProxyConf(proxyType),
|
|
||||||
}
|
|
||||||
case consts.STCPProxy:
|
|
||||||
conf = &STCPProxyConf{
|
|
||||||
BaseProxyConf: defaultBaseProxyConf(proxyType),
|
|
||||||
Role: "server",
|
|
||||||
}
|
|
||||||
case consts.XTCPProxy:
|
|
||||||
conf = &XTCPProxyConf{
|
|
||||||
BaseProxyConf: defaultBaseProxyConf(proxyType),
|
|
||||||
Role: "server",
|
|
||||||
}
|
|
||||||
case consts.SUDPProxy:
|
|
||||||
conf = &SUDPProxyConf{
|
|
||||||
BaseProxyConf: defaultBaseProxyConf(proxyType),
|
|
||||||
Role: "server",
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,10 +267,9 @@ func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conf.CheckForCli(); err != nil {
|
if err := conf.ValidateForClient(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return conf, nil
|
return conf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +286,7 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (ProxyC
|
|||||||
|
|
||||||
conf.UnmarshalFromMsg(pMsg)
|
conf.UnmarshalFromMsg(pMsg)
|
||||||
|
|
||||||
err := conf.CheckForSvr(serverCfg)
|
err := conf.ValidateForServer(serverCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -319,42 +295,15 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (ProxyC
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Base
|
// Base
|
||||||
func defaultBaseProxyConf(proxyType string) BaseProxyConf {
|
func (cfg *BaseProxyConf) GetBaseConfig() *BaseProxyConf {
|
||||||
return BaseProxyConf{
|
|
||||||
ProxyType: proxyType,
|
|
||||||
LocalSvrConf: LocalSvrConf{
|
|
||||||
LocalIP: "127.0.0.1",
|
|
||||||
},
|
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
|
func (cfg *BaseProxyConf) SetDefaultValues() {
|
||||||
if cfg.ProxyName != cmp.ProxyName ||
|
cfg.LocalSvrConf = LocalSvrConf{
|
||||||
cfg.ProxyType != cmp.ProxyType ||
|
LocalIP: "127.0.0.1",
|
||||||
cfg.UseEncryption != cmp.UseEncryption ||
|
|
||||||
cfg.UseCompression != cmp.UseCompression ||
|
|
||||||
cfg.Group != cmp.Group ||
|
|
||||||
cfg.GroupKey != cmp.GroupKey ||
|
|
||||||
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion ||
|
|
||||||
!cfg.BandwidthLimit.Equal(&cmp.BandwidthLimit) ||
|
|
||||||
cfg.BandwidthLimitMode != cmp.BandwidthLimitMode ||
|
|
||||||
!reflect.DeepEqual(cfg.Metas, cmp.Metas) {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
cfg.BandwidthLimitMode = BandwidthLimitModeClient
|
||||||
if !reflect.DeepEqual(cfg.LocalSvrConf, cmp.LocalSvrConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(cfg.HealthCheckConf, cmp.HealthCheckConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseProxyConf apply custom logic changes.
|
// BaseProxyConf apply custom logic changes.
|
||||||
@ -423,7 +372,7 @@ func (cfg *BaseProxyConf) unmarshalFromMsg(pMsg *msg.NewProxy) {
|
|||||||
cfg.Metas = pMsg.Metas
|
cfg.Metas = pMsg.Metas
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) checkForCli() (err error) {
|
func (cfg *BaseProxyConf) validateForClient() (err error) {
|
||||||
if cfg.ProxyProtocolVersion != "" {
|
if cfg.ProxyProtocolVersion != "" {
|
||||||
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
|
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
|
||||||
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
|
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
|
||||||
@ -434,16 +383,16 @@ func (cfg *BaseProxyConf) checkForCli() (err error) {
|
|||||||
return fmt.Errorf("bandwidth_limit_mode should be client or server")
|
return fmt.Errorf("bandwidth_limit_mode should be client or server")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = cfg.LocalSvrConf.checkForCli(); err != nil {
|
if err = cfg.LocalSvrConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = cfg.HealthCheckConf.checkForCli(); err != nil {
|
if err = cfg.HealthCheckConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) checkForSvr() (err error) {
|
func (cfg *BaseProxyConf) validateForServer() (err error) {
|
||||||
if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
|
if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
|
||||||
return fmt.Errorf("bandwidth_limit_mode should be client or server")
|
return fmt.Errorf("bandwidth_limit_mode should be client or server")
|
||||||
}
|
}
|
||||||
@ -459,14 +408,14 @@ func (cfg *DomainConf) check() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *DomainConf) checkForCli() (err error) {
|
func (cfg *DomainConf) validateForClient() (err error) {
|
||||||
if err = cfg.check(); err != nil {
|
if err = cfg.check(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) {
|
func (cfg *DomainConf) validateForServer(serverCfg ServerCommonConf) (err error) {
|
||||||
if err = cfg.check(); err != nil {
|
if err = cfg.check(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -491,7 +440,7 @@ func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LocalSvrConf
|
// LocalSvrConf
|
||||||
func (cfg *LocalSvrConf) checkForCli() (err error) {
|
func (cfg *LocalSvrConf) validateForClient() (err error) {
|
||||||
if cfg.Plugin == "" {
|
if cfg.Plugin == "" {
|
||||||
if cfg.LocalIP == "" {
|
if cfg.LocalIP == "" {
|
||||||
err = fmt.Errorf("local ip or plugin is required")
|
err = fmt.Errorf("local ip or plugin is required")
|
||||||
@ -506,7 +455,7 @@ func (cfg *LocalSvrConf) checkForCli() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HealthCheckConf
|
// HealthCheckConf
|
||||||
func (cfg *HealthCheckConf) checkForCli() error {
|
func (cfg *HealthCheckConf) validateForClient() error {
|
||||||
if cfg.HealthCheckType != "" && cfg.HealthCheckType != "tcp" && cfg.HealthCheckType != "http" {
|
if cfg.HealthCheckType != "" && cfg.HealthCheckType != "tcp" && cfg.HealthCheckType != "http" {
|
||||||
return fmt.Errorf("unsupport health check type")
|
return fmt.Errorf("unsupport health check type")
|
||||||
}
|
}
|
||||||
@ -524,7 +473,7 @@ func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, section *ini
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cfg.GetBaseInfo().decorate(prefix, name, section)
|
err = cfg.GetBaseConfig().decorate(prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -533,24 +482,6 @@ func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, section *ini
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TCP
|
// TCP
|
||||||
func (cfg *TCPProxyConf) Compare(cmp ProxyConf) bool {
|
|
||||||
cmpConf, ok := cmp.(*TCPProxyConf)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic equal if exists.
|
|
||||||
if cfg.RemotePort != cmpConf.RemotePort {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *TCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
func (cfg *TCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
cfg.BaseProxyConf.unmarshalFromMsg(pMsg)
|
cfg.BaseProxyConf.unmarshalFromMsg(pMsg)
|
||||||
|
|
||||||
@ -576,8 +507,8 @@ func (cfg *TCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
pMsg.RemotePort = cfg.RemotePort
|
pMsg.RemotePort = cfg.RemotePort
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TCPProxyConf) CheckForCli() (err error) {
|
func (cfg *TCPProxyConf) ValidateForClient() (err error) {
|
||||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -586,39 +517,14 @@ func (cfg *TCPProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
|
func (cfg *TCPProxyConf) ValidateForServer(serverCfg ServerCommonConf) error {
|
||||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCPMux
|
// TCPMux
|
||||||
func (cfg *TCPMuxProxyConf) Compare(cmp ProxyConf) bool {
|
|
||||||
cmpConf, ok := cmp.(*TCPMuxProxyConf)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic equal if exists.
|
|
||||||
if !reflect.DeepEqual(cfg.DomainConf, cmpConf.DomainConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Multiplexer != cmpConf.Multiplexer ||
|
|
||||||
cfg.HTTPUser != cmpConf.HTTPUser ||
|
|
||||||
cfg.HTTPPwd != cmpConf.HTTPPwd ||
|
|
||||||
cfg.RouteByHTTPUser != cmpConf.RouteByHTTPUser {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -654,13 +560,13 @@ func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
|
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TCPMuxProxyConf) CheckForCli() (err error) {
|
func (cfg *TCPMuxProxyConf) ValidateForClient() (err error) {
|
||||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add custom logic check if exists
|
// Add custom logic check if exists
|
||||||
if err = cfg.DomainConf.checkForCli(); err != nil {
|
if err = cfg.DomainConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -671,8 +577,8 @@ func (cfg *TCPMuxProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *TCPMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
func (cfg *TCPMuxProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
|
||||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -684,7 +590,7 @@ func (cfg *TCPMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error)
|
|||||||
return fmt.Errorf("proxy [%s] type [tcpmux] with multiplexer [httpconnect] requires tcpmux_httpconnect_port configuration", cfg.ProxyName)
|
return fmt.Errorf("proxy [%s] type [tcpmux] with multiplexer [httpconnect] requires tcpmux_httpconnect_port configuration", cfg.ProxyName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
|
||||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -693,24 +599,6 @@ func (cfg *TCPMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UDP
|
// UDP
|
||||||
func (cfg *UDPProxyConf) Compare(cmp ProxyConf) bool {
|
|
||||||
cmpConf, ok := cmp.(*UDPProxyConf)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic equal if exists.
|
|
||||||
if cfg.RemotePort != cmpConf.RemotePort {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -736,8 +624,8 @@ func (cfg *UDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
pMsg.RemotePort = cfg.RemotePort
|
pMsg.RemotePort = cfg.RemotePort
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *UDPProxyConf) CheckForCli() (err error) {
|
func (cfg *UDPProxyConf) ValidateForClient() (err error) {
|
||||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -746,41 +634,14 @@ func (cfg *UDPProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *UDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
|
func (cfg *UDPProxyConf) ValidateForServer(serverCfg ServerCommonConf) error {
|
||||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
func (cfg *HTTPProxyConf) Compare(cmp ProxyConf) bool {
|
|
||||||
cmpConf, ok := cmp.(*HTTPProxyConf)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic equal if exists.
|
|
||||||
if !reflect.DeepEqual(cfg.DomainConf, cmpConf.DomainConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(cfg.Locations, cmpConf.Locations) ||
|
|
||||||
cfg.HTTPUser != cmpConf.HTTPUser ||
|
|
||||||
cfg.HTTPPwd != cmpConf.HTTPPwd ||
|
|
||||||
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
|
|
||||||
cfg.RouteByHTTPUser != cmpConf.RouteByHTTPUser ||
|
|
||||||
!reflect.DeepEqual(cfg.Headers, cmpConf.Headers) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -789,7 +650,6 @@ func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *
|
|||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
// Add custom logic unmarshal if exists
|
||||||
cfg.Headers = GetMapWithoutPrefix(section.KeysHash(), "header_")
|
cfg.Headers = GetMapWithoutPrefix(section.KeysHash(), "header_")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -821,21 +681,21 @@ func (cfg *HTTPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
|
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *HTTPProxyConf) CheckForCli() (err error) {
|
func (cfg *HTTPProxyConf) ValidateForClient() (err error) {
|
||||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add custom logic check if exists
|
// Add custom logic check if exists
|
||||||
if err = cfg.DomainConf.checkForCli(); err != nil {
|
if err = cfg.DomainConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *HTTPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
func (cfg *HTTPProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
|
||||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -843,7 +703,7 @@ func (cfg *HTTPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
|||||||
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
|
||||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -852,24 +712,6 @@ func (cfg *HTTPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS
|
// HTTPS
|
||||||
func (cfg *HTTPSProxyConf) Compare(cmp ProxyConf) bool {
|
|
||||||
cmpConf, ok := cmp.(*HTTPSProxyConf)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic equal if exists.
|
|
||||||
if !reflect.DeepEqual(cfg.DomainConf, cmpConf.DomainConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
err := preUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -877,7 +719,6 @@ func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
// Add custom logic unmarshal if exists
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -897,21 +738,20 @@ func (cfg *HTTPSProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
pMsg.SubDomain = cfg.SubDomain
|
pMsg.SubDomain = cfg.SubDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *HTTPSProxyConf) CheckForCli() (err error) {
|
func (cfg *HTTPSProxyConf) ValidateForClient() (err error) {
|
||||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add custom logic check if exists
|
// Add custom logic check if exists
|
||||||
if err = cfg.DomainConf.checkForCli(); err != nil {
|
if err = cfg.DomainConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *HTTPSProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
func (cfg *HTTPSProxyConf) ValidateForServer(serverCfg ServerCommonConf) (err error) {
|
||||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -919,7 +759,7 @@ func (cfg *HTTPSProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
|||||||
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
|
if err = cfg.DomainConf.validateForServer(serverCfg); err != nil {
|
||||||
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -928,23 +768,9 @@ func (cfg *HTTPSProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SUDP
|
// SUDP
|
||||||
func (cfg *SUDPProxyConf) Compare(cmp ProxyConf) bool {
|
func (cfg *SUDPProxyConf) SetDefaultValues() {
|
||||||
cmpConf, ok := cmp.(*SUDPProxyConf)
|
cfg.BaseProxyConf.SetDefaultValues()
|
||||||
if !ok {
|
cfg.RoleServerCommonConf.setDefaultValues()
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic equal if exists.
|
|
||||||
if cfg.Role != cmpConf.Role ||
|
|
||||||
cfg.Sk != cmpConf.Sk {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
@ -954,7 +780,6 @@ func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom logic unmarshal if exists
|
// Add custom logic unmarshal if exists
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -973,8 +798,8 @@ func (cfg *SUDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
pMsg.Sk = cfg.Sk
|
pMsg.Sk = cfg.Sk
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *SUDPProxyConf) CheckForCli() (err error) {
|
func (cfg *SUDPProxyConf) ValidateForClient() (err error) {
|
||||||
if err := cfg.BaseProxyConf.checkForCli(); err != nil {
|
if err := cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -986,31 +811,17 @@ func (cfg *SUDPProxyConf) CheckForCli() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *SUDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
|
func (cfg *SUDPProxyConf) ValidateForServer(serverCfg ServerCommonConf) error {
|
||||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// STCP
|
// STCP
|
||||||
func (cfg *STCPProxyConf) Compare(cmp ProxyConf) bool {
|
func (cfg *STCPProxyConf) SetDefaultValues() {
|
||||||
cmpConf, ok := cmp.(*STCPProxyConf)
|
cfg.BaseProxyConf.SetDefaultValues()
|
||||||
if !ok {
|
cfg.RoleServerCommonConf.setDefaultValues()
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic equal if exists.
|
|
||||||
if cfg.Role != cmpConf.Role ||
|
|
||||||
cfg.Sk != cmpConf.Sk {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
@ -1023,7 +834,6 @@ func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *
|
|||||||
if cfg.Role == "" {
|
if cfg.Role == "" {
|
||||||
cfg.Role = "server"
|
cfg.Role = "server"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1042,8 +852,8 @@ func (cfg *STCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
pMsg.Sk = cfg.Sk
|
pMsg.Sk = cfg.Sk
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *STCPProxyConf) CheckForCli() (err error) {
|
func (cfg *STCPProxyConf) ValidateForClient() (err error) {
|
||||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1055,31 +865,17 @@ func (cfg *STCPProxyConf) CheckForCli() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *STCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
|
func (cfg *STCPProxyConf) ValidateForServer(serverCfg ServerCommonConf) error {
|
||||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// XTCP
|
// XTCP
|
||||||
func (cfg *XTCPProxyConf) Compare(cmp ProxyConf) bool {
|
func (cfg *XTCPProxyConf) SetDefaultValues() {
|
||||||
cmpConf, ok := cmp.(*XTCPProxyConf)
|
cfg.BaseProxyConf.SetDefaultValues()
|
||||||
if !ok {
|
cfg.RoleServerCommonConf.setDefaultValues()
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom logic equal if exists.
|
|
||||||
if cfg.Role != cmpConf.Role ||
|
|
||||||
cfg.Sk != cmpConf.Sk {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error {
|
||||||
@ -1092,7 +888,6 @@ func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *
|
|||||||
if cfg.Role == "" {
|
if cfg.Role == "" {
|
||||||
cfg.Role = "server"
|
cfg.Role = "server"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1111,8 +906,8 @@ func (cfg *XTCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||||||
pMsg.Sk = cfg.Sk
|
pMsg.Sk = cfg.Sk
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *XTCPProxyConf) CheckForCli() (err error) {
|
func (cfg *XTCPProxyConf) ValidateForClient() (err error) {
|
||||||
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
if err = cfg.BaseProxyConf.validateForClient(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1120,12 +915,11 @@ func (cfg *XTCPProxyConf) CheckForCli() (err error) {
|
|||||||
if cfg.Role != "server" {
|
if cfg.Role != "server" {
|
||||||
return fmt.Errorf("role should be 'server'")
|
return fmt.Errorf("role should be 'server'")
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *XTCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
|
func (cfg *XTCPProxyConf) ValidateForServer(serverCfg ServerCommonConf) error {
|
||||||
if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
|
if err := cfg.BaseProxyConf.validateForServer(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -254,8 +254,10 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
|||||||
},
|
},
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||||
},
|
},
|
||||||
Role: "server",
|
RoleServerCommonConf: RoleServerCommonConf{
|
||||||
Sk: "abcdefg",
|
Role: "server",
|
||||||
|
Sk: "abcdefg",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -279,8 +281,10 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
|
|||||||
},
|
},
|
||||||
BandwidthLimitMode: BandwidthLimitModeClient,
|
BandwidthLimitMode: BandwidthLimitModeClient,
|
||||||
},
|
},
|
||||||
Role: "server",
|
RoleServerCommonConf: RoleServerCommonConf{
|
||||||
Sk: "abcdefg",
|
Role: "server",
|
||||||
|
Sk: "abcdefg",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -38,10 +38,6 @@ type ServerCommonConf struct {
|
|||||||
// BindPort specifies the port that the server listens on. By default, this
|
// BindPort specifies the port that the server listens on. By default, this
|
||||||
// value is 7000.
|
// value is 7000.
|
||||||
BindPort int `ini:"bind_port" json:"bind_port" validate:"gte=0,lte=65535"`
|
BindPort int `ini:"bind_port" json:"bind_port" validate:"gte=0,lte=65535"`
|
||||||
// BindUDPPort specifies the UDP port that the server listens on. If this
|
|
||||||
// value is 0, the server will not listen for UDP connections. By default,
|
|
||||||
// this value is 0
|
|
||||||
BindUDPPort int `ini:"bind_udp_port" json:"bind_udp_port" validate:"gte=0,lte=65535"`
|
|
||||||
// KCPBindPort specifies the KCP port that the server listens on. If this
|
// KCPBindPort specifies the KCP port that the server listens on. If this
|
||||||
// value is 0, the server will not listen for KCP connections. By default,
|
// value is 0, the server will not listen for KCP connections. By default,
|
||||||
// this value is 0.
|
// this value is 0.
|
||||||
@ -196,35 +192,38 @@ type ServerCommonConf struct {
|
|||||||
// Enable golang pprof handlers in dashboard listener.
|
// Enable golang pprof handlers in dashboard listener.
|
||||||
// Dashboard port must be set first.
|
// Dashboard port must be set first.
|
||||||
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
||||||
|
// NatHoleAnalysisDataReserveHours specifies the hours to reserve nat hole analysis data.
|
||||||
|
NatHoleAnalysisDataReserveHours int64 `ini:"nat_hole_analysis_data_reserve_hours" json:"nat_hole_analysis_data_reserve_hours"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultServerConf returns a server configuration with reasonable
|
// GetDefaultServerConf returns a server configuration with reasonable
|
||||||
// defaults.
|
// defaults.
|
||||||
func GetDefaultServerConf() ServerCommonConf {
|
func GetDefaultServerConf() ServerCommonConf {
|
||||||
return ServerCommonConf{
|
return ServerCommonConf{
|
||||||
ServerConfig: auth.GetDefaultServerConf(),
|
ServerConfig: auth.GetDefaultServerConf(),
|
||||||
BindAddr: "0.0.0.0",
|
BindAddr: "0.0.0.0",
|
||||||
BindPort: 7000,
|
BindPort: 7000,
|
||||||
QUICKeepalivePeriod: 10,
|
QUICKeepalivePeriod: 10,
|
||||||
QUICMaxIdleTimeout: 30,
|
QUICMaxIdleTimeout: 30,
|
||||||
QUICMaxIncomingStreams: 100000,
|
QUICMaxIncomingStreams: 100000,
|
||||||
VhostHTTPTimeout: 60,
|
VhostHTTPTimeout: 60,
|
||||||
DashboardAddr: "0.0.0.0",
|
DashboardAddr: "0.0.0.0",
|
||||||
LogFile: "console",
|
LogFile: "console",
|
||||||
LogWay: "console",
|
LogWay: "console",
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
LogMaxDays: 3,
|
LogMaxDays: 3,
|
||||||
DetailedErrorsToClient: true,
|
DetailedErrorsToClient: true,
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
TCPMuxKeepaliveInterval: 60,
|
TCPMuxKeepaliveInterval: 60,
|
||||||
TCPKeepAlive: 7200,
|
TCPKeepAlive: 7200,
|
||||||
AllowPorts: make(map[int]struct{}),
|
AllowPorts: make(map[int]struct{}),
|
||||||
MaxPoolCount: 5,
|
MaxPoolCount: 5,
|
||||||
MaxPortsPerClient: 0,
|
MaxPortsPerClient: 0,
|
||||||
HeartbeatTimeout: 90,
|
HeartbeatTimeout: 90,
|
||||||
UserConnTimeout: 10,
|
UserConnTimeout: 10,
|
||||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||||
UDPPacketSize: 1500,
|
UDPPacketSize: 1500,
|
||||||
|
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
[common]
|
[common]
|
||||||
bind_addr = 0.0.0.9
|
bind_addr = 0.0.0.9
|
||||||
bind_port = 7009
|
bind_port = 7009
|
||||||
bind_udp_port = 7008
|
|
||||||
kcp_bind_port = 7007
|
kcp_bind_port = 7007
|
||||||
proxy_bind_addr = 127.0.0.9
|
proxy_bind_addr = 127.0.0.9
|
||||||
vhost_http_port = 89
|
vhost_http_port = 89
|
||||||
@ -104,7 +103,6 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
BindAddr: "0.0.0.9",
|
BindAddr: "0.0.0.9",
|
||||||
BindPort: 7009,
|
BindPort: 7009,
|
||||||
BindUDPPort: 7008,
|
|
||||||
KCPBindPort: 7007,
|
KCPBindPort: 7007,
|
||||||
QUICKeepalivePeriod: 10,
|
QUICKeepalivePeriod: 10,
|
||||||
QUICMaxIdleTimeout: 30,
|
QUICMaxIdleTimeout: 30,
|
||||||
@ -134,18 +132,19 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
12: {},
|
12: {},
|
||||||
99: {},
|
99: {},
|
||||||
},
|
},
|
||||||
AllowPortsStr: "10-12,99",
|
AllowPortsStr: "10-12,99",
|
||||||
MaxPoolCount: 59,
|
MaxPoolCount: 59,
|
||||||
MaxPortsPerClient: 9,
|
MaxPortsPerClient: 9,
|
||||||
TLSOnly: true,
|
TLSOnly: true,
|
||||||
TLSCertFile: "server.crt",
|
TLSCertFile: "server.crt",
|
||||||
TLSKeyFile: "server.key",
|
TLSKeyFile: "server.key",
|
||||||
TLSTrustedCaFile: "ca.crt",
|
TLSTrustedCaFile: "ca.crt",
|
||||||
SubDomainHost: "frps.com",
|
SubDomainHost: "frps.com",
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
TCPMuxKeepaliveInterval: 60,
|
TCPMuxKeepaliveInterval: 60,
|
||||||
TCPKeepAlive: 7200,
|
TCPKeepAlive: 7200,
|
||||||
UDPPacketSize: 1509,
|
UDPPacketSize: 1509,
|
||||||
|
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||||
|
|
||||||
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
||||||
"user-manager": {
|
"user-manager": {
|
||||||
@ -170,7 +169,6 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
[common]
|
[common]
|
||||||
bind_addr = 0.0.0.9
|
bind_addr = 0.0.0.9
|
||||||
bind_port = 7009
|
bind_port = 7009
|
||||||
bind_udp_port = 7008
|
|
||||||
`),
|
`),
|
||||||
expected: ServerCommonConf{
|
expected: ServerCommonConf{
|
||||||
ServerConfig: auth.ServerConfig{
|
ServerConfig: auth.ServerConfig{
|
||||||
@ -180,32 +178,32 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
AuthenticateNewWorkConns: false,
|
AuthenticateNewWorkConns: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
BindAddr: "0.0.0.9",
|
BindAddr: "0.0.0.9",
|
||||||
BindPort: 7009,
|
BindPort: 7009,
|
||||||
BindUDPPort: 7008,
|
QUICKeepalivePeriod: 10,
|
||||||
QUICKeepalivePeriod: 10,
|
QUICMaxIdleTimeout: 30,
|
||||||
QUICMaxIdleTimeout: 30,
|
QUICMaxIncomingStreams: 100000,
|
||||||
QUICMaxIncomingStreams: 100000,
|
ProxyBindAddr: "0.0.0.9",
|
||||||
ProxyBindAddr: "0.0.0.9",
|
VhostHTTPTimeout: 60,
|
||||||
VhostHTTPTimeout: 60,
|
DashboardAddr: "0.0.0.0",
|
||||||
DashboardAddr: "0.0.0.0",
|
DashboardUser: "",
|
||||||
DashboardUser: "",
|
DashboardPwd: "",
|
||||||
DashboardPwd: "",
|
EnablePrometheus: false,
|
||||||
EnablePrometheus: false,
|
LogFile: "console",
|
||||||
LogFile: "console",
|
LogWay: "console",
|
||||||
LogWay: "console",
|
LogLevel: "info",
|
||||||
LogLevel: "info",
|
LogMaxDays: 3,
|
||||||
LogMaxDays: 3,
|
DetailedErrorsToClient: true,
|
||||||
DetailedErrorsToClient: true,
|
TCPMux: true,
|
||||||
TCPMux: true,
|
TCPMuxKeepaliveInterval: 60,
|
||||||
TCPMuxKeepaliveInterval: 60,
|
TCPKeepAlive: 7200,
|
||||||
TCPKeepAlive: 7200,
|
AllowPorts: make(map[int]struct{}),
|
||||||
AllowPorts: make(map[int]struct{}),
|
MaxPoolCount: 5,
|
||||||
MaxPoolCount: 5,
|
HeartbeatTimeout: 90,
|
||||||
HeartbeatTimeout: 90,
|
UserConnTimeout: 10,
|
||||||
UserConnTimeout: 10,
|
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
UDPPacketSize: 1500,
|
||||||
UDPPacketSize: 1500,
|
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
"github.com/fatedier/frp/pkg/consts"
|
||||||
@ -33,10 +34,12 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type VisitorConf interface {
|
type VisitorConf interface {
|
||||||
GetBaseInfo() *BaseVisitorConf
|
// GetBaseConfig returns the base config of visitor.
|
||||||
Compare(cmp VisitorConf) bool
|
GetBaseConfig() *BaseVisitorConf
|
||||||
|
// UnmarshalFromIni unmarshals config from ini.
|
||||||
UnmarshalFromIni(prefix string, name string, section *ini.Section) error
|
UnmarshalFromIni(prefix string, name string, section *ini.Section) error
|
||||||
Check() error
|
// Validate validates config.
|
||||||
|
Validate() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseVisitorConf struct {
|
type BaseVisitorConf struct {
|
||||||
@ -46,9 +49,14 @@ type BaseVisitorConf struct {
|
|||||||
UseCompression bool `ini:"use_compression" json:"use_compression"`
|
UseCompression bool `ini:"use_compression" json:"use_compression"`
|
||||||
Role string `ini:"role" json:"role"`
|
Role string `ini:"role" json:"role"`
|
||||||
Sk string `ini:"sk" json:"sk"`
|
Sk string `ini:"sk" json:"sk"`
|
||||||
ServerName string `ini:"server_name" json:"server_name"`
|
// if the server user is not set, it defaults to the current user
|
||||||
BindAddr string `ini:"bind_addr" json:"bind_addr"`
|
ServerUser string `ini:"server_user" json:"server_user"`
|
||||||
BindPort int `ini:"bind_port" json:"bind_port"`
|
ServerName string `ini:"server_name" json:"server_name"`
|
||||||
|
BindAddr string `ini:"bind_addr" json:"bind_addr"`
|
||||||
|
// BindPort is the port that visitor listens on.
|
||||||
|
// It can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||||
|
// other visitors. (This is not supported for SUDP now)
|
||||||
|
BindPort int `ini:"bind_port" json:"bind_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SUDPVisitorConf struct {
|
type SUDPVisitorConf struct {
|
||||||
@ -61,6 +69,13 @@ type STCPVisitorConf struct {
|
|||||||
|
|
||||||
type XTCPVisitorConf struct {
|
type XTCPVisitorConf struct {
|
||||||
BaseVisitorConf `ini:",extends"`
|
BaseVisitorConf `ini:",extends"`
|
||||||
|
|
||||||
|
Protocol string `ini:"protocol" json:"protocol,omitempty"`
|
||||||
|
KeepTunnelOpen bool `ini:"keep_tunnel_open" json:"keep_tunnel_open,omitempty"`
|
||||||
|
MaxRetriesAnHour int `ini:"max_retries_an_hour" json:"max_retries_an_hour,omitempty"`
|
||||||
|
MinRetryInterval int `ini:"min_retry_interval" json:"min_retry_interval,omitempty"`
|
||||||
|
FallbackTo string `ini:"fallback_to" json:"fallback_to,omitempty"`
|
||||||
|
FallbackTimeoutMs int `ini:"fallback_timeout_ms" json:"fallback_timeout_ms,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
|
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
|
||||||
@ -70,7 +85,6 @@ func DefaultVisitorConf(visitorType string) VisitorConf {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return reflect.New(v).Interface().(VisitorConf)
|
return reflect.New(v).Interface().(VisitorConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +106,7 @@ func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (Vi
|
|||||||
return nil, fmt.Errorf("visitor [%s] type [%s] error", name, visitorType)
|
return nil, fmt.Errorf("visitor [%s] type [%s] error", name, visitorType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conf.Check(); err != nil {
|
if err := conf.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,26 +114,11 @@ func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (Vi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Base
|
// Base
|
||||||
func (cfg *BaseVisitorConf) GetBaseInfo() *BaseVisitorConf {
|
func (cfg *BaseVisitorConf) GetBaseConfig() *BaseVisitorConf {
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *BaseVisitorConf) compare(cmp *BaseVisitorConf) bool {
|
func (cfg *BaseVisitorConf) validate() (err error) {
|
||||||
if cfg.ProxyName != cmp.ProxyName ||
|
|
||||||
cfg.ProxyType != cmp.ProxyType ||
|
|
||||||
cfg.UseEncryption != cmp.UseEncryption ||
|
|
||||||
cfg.UseCompression != cmp.UseCompression ||
|
|
||||||
cfg.Role != cmp.Role ||
|
|
||||||
cfg.Sk != cmp.Sk ||
|
|
||||||
cfg.ServerName != cmp.ServerName ||
|
|
||||||
cfg.BindAddr != cmp.BindAddr ||
|
|
||||||
cfg.BindPort != cmp.BindPort {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *BaseVisitorConf) check() (err error) {
|
|
||||||
if cfg.Role != "visitor" {
|
if cfg.Role != "visitor" {
|
||||||
err = fmt.Errorf("invalid role")
|
err = fmt.Errorf("invalid role")
|
||||||
return
|
return
|
||||||
@ -128,7 +127,9 @@ func (cfg *BaseVisitorConf) check() (err error) {
|
|||||||
err = fmt.Errorf("bind_addr shouldn't be empty")
|
err = fmt.Errorf("bind_addr shouldn't be empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cfg.BindPort <= 0 {
|
// BindPort can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||||
|
// other visitors
|
||||||
|
if cfg.BindPort == 0 {
|
||||||
err = fmt.Errorf("bind_port is required")
|
err = fmt.Errorf("bind_port is required")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -143,13 +144,16 @@ func (cfg *BaseVisitorConf) unmarshalFromIni(prefix string, name string, section
|
|||||||
cfg.ProxyName = prefix + name
|
cfg.ProxyName = prefix + name
|
||||||
|
|
||||||
// server_name
|
// server_name
|
||||||
cfg.ServerName = prefix + cfg.ServerName
|
if cfg.ServerUser == "" {
|
||||||
|
cfg.ServerName = prefix + cfg.ServerName
|
||||||
|
} else {
|
||||||
|
cfg.ServerName = cfg.ServerUser + "." + cfg.ServerName
|
||||||
|
}
|
||||||
|
|
||||||
// bind_addr
|
// bind_addr
|
||||||
if cfg.BindAddr == "" {
|
if cfg.BindAddr == "" {
|
||||||
cfg.BindAddr = "127.0.0.1"
|
cfg.BindAddr = "127.0.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,32 +163,16 @@ func preVisitorUnmarshalFromIni(cfg VisitorConf, prefix string, name string, sec
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cfg.GetBaseInfo().unmarshalFromIni(prefix, name, section)
|
err = cfg.GetBaseConfig().unmarshalFromIni(prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SUDP
|
// SUDP
|
||||||
var _ VisitorConf = &SUDPVisitorConf{}
|
var _ VisitorConf = &SUDPVisitorConf{}
|
||||||
|
|
||||||
func (cfg *SUDPVisitorConf) Compare(cmp VisitorConf) bool {
|
|
||||||
cmpConf, ok := cmp.(*SUDPVisitorConf)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom login equal, if exists
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -196,8 +184,8 @@ func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *SUDPVisitorConf) Check() (err error) {
|
func (cfg *SUDPVisitorConf) Validate() (err error) {
|
||||||
if err = cfg.BaseVisitorConf.check(); err != nil {
|
if err = cfg.BaseVisitorConf.validate(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,21 +197,6 @@ func (cfg *SUDPVisitorConf) Check() (err error) {
|
|||||||
// STCP
|
// STCP
|
||||||
var _ VisitorConf = &STCPVisitorConf{}
|
var _ VisitorConf = &STCPVisitorConf{}
|
||||||
|
|
||||||
func (cfg *STCPVisitorConf) Compare(cmp VisitorConf) bool {
|
|
||||||
cmpConf, ok := cmp.(*STCPVisitorConf)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom login equal, if exists
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -235,8 +208,8 @@ func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *STCPVisitorConf) Check() (err error) {
|
func (cfg *STCPVisitorConf) Validate() (err error) {
|
||||||
if err = cfg.BaseVisitorConf.check(); err != nil {
|
if err = cfg.BaseVisitorConf.validate(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,21 +221,6 @@ func (cfg *STCPVisitorConf) Check() (err error) {
|
|||||||
// XTCP
|
// XTCP
|
||||||
var _ VisitorConf = &XTCPVisitorConf{}
|
var _ VisitorConf = &XTCPVisitorConf{}
|
||||||
|
|
||||||
func (cfg *XTCPVisitorConf) Compare(cmp VisitorConf) bool {
|
|
||||||
cmpConf, ok := cmp.(*XTCPVisitorConf)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom login equal, if exists
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) {
|
||||||
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
err = preVisitorUnmarshalFromIni(cfg, prefix, name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -270,16 +228,29 @@ func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom logic unmarshal, if exists
|
// Add custom logic unmarshal, if exists
|
||||||
|
if cfg.Protocol == "" {
|
||||||
|
cfg.Protocol = "quic"
|
||||||
|
}
|
||||||
|
if cfg.MaxRetriesAnHour <= 0 {
|
||||||
|
cfg.MaxRetriesAnHour = 8
|
||||||
|
}
|
||||||
|
if cfg.MinRetryInterval <= 0 {
|
||||||
|
cfg.MinRetryInterval = 90
|
||||||
|
}
|
||||||
|
if cfg.FallbackTimeoutMs <= 0 {
|
||||||
|
cfg.FallbackTimeoutMs = 1000
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *XTCPVisitorConf) Check() (err error) {
|
func (cfg *XTCPVisitorConf) Validate() (err error) {
|
||||||
if err = cfg.BaseVisitorConf.check(); err != nil {
|
if err = cfg.BaseVisitorConf.validate(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add custom logic validate, if exists
|
// Add custom logic validate, if exists
|
||||||
|
if !lo.Contains([]string{"", "kcp", "quic"}, cfg.Protocol) {
|
||||||
|
return fmt.Errorf("protocol should be 'kcp' or 'quic'")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,10 @@ func Test_Visitor_UnmarshalFromIni(t *testing.T) {
|
|||||||
BindAddr: "127.0.0.1",
|
BindAddr: "127.0.0.1",
|
||||||
BindPort: 9001,
|
BindPort: 9001,
|
||||||
},
|
},
|
||||||
|
Protocol: "quic",
|
||||||
|
MaxRetriesAnHour: 8,
|
||||||
|
MinRetryInterval: 90,
|
||||||
|
FallbackTimeoutMs: 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -60,25 +60,30 @@ func (m *serverMetrics) run() {
|
|||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
time.Sleep(12 * time.Hour)
|
time.Sleep(12 * time.Hour)
|
||||||
log.Debug("start to clear useless proxy statistics data...")
|
start := time.Now()
|
||||||
m.clearUselessInfo()
|
count, total := m.clearUselessInfo()
|
||||||
log.Debug("finish to clear useless proxy statistics data")
|
log.Debug("clear useless proxy statistics data count %d/%d, cost %v", count, total, time.Since(start))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverMetrics) clearUselessInfo() {
|
func (m *serverMetrics) clearUselessInfo() (int, int) {
|
||||||
|
count := 0
|
||||||
|
total := 0
|
||||||
// To check if there are proxies that closed than 7 days and drop them.
|
// To check if there are proxies that closed than 7 days and drop them.
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
total = len(m.info.ProxyStatistics)
|
||||||
for name, data := range m.info.ProxyStatistics {
|
for name, data := range m.info.ProxyStatistics {
|
||||||
if !data.LastCloseTime.IsZero() &&
|
if !data.LastCloseTime.IsZero() &&
|
||||||
data.LastStartTime.Before(data.LastCloseTime) &&
|
data.LastStartTime.Before(data.LastCloseTime) &&
|
||||||
time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
||||||
delete(m.info.ProxyStatistics, name)
|
delete(m.info.ProxyStatistics, name)
|
||||||
|
count++
|
||||||
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return count, total
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverMetrics) NewClient() {
|
func (m *serverMetrics) NewClient() {
|
||||||
|
150
pkg/msg/msg.go
150
pkg/msg/msg.go
@ -16,54 +16,53 @@ package msg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeLogin = 'o'
|
TypeLogin = 'o'
|
||||||
TypeLoginResp = '1'
|
TypeLoginResp = '1'
|
||||||
TypeNewProxy = 'p'
|
TypeNewProxy = 'p'
|
||||||
TypeNewProxyResp = '2'
|
TypeNewProxyResp = '2'
|
||||||
TypeCloseProxy = 'c'
|
TypeCloseProxy = 'c'
|
||||||
TypeNewWorkConn = 'w'
|
TypeNewWorkConn = 'w'
|
||||||
TypeReqWorkConn = 'r'
|
TypeReqWorkConn = 'r'
|
||||||
TypeStartWorkConn = 's'
|
TypeStartWorkConn = 's'
|
||||||
TypeNewVisitorConn = 'v'
|
TypeNewVisitorConn = 'v'
|
||||||
TypeNewVisitorConnResp = '3'
|
TypeNewVisitorConnResp = '3'
|
||||||
TypePing = 'h'
|
TypePing = 'h'
|
||||||
TypePong = '4'
|
TypePong = '4'
|
||||||
TypeUDPPacket = 'u'
|
TypeUDPPacket = 'u'
|
||||||
TypeNatHoleVisitor = 'i'
|
TypeNatHoleVisitor = 'i'
|
||||||
TypeNatHoleClient = 'n'
|
TypeNatHoleClient = 'n'
|
||||||
TypeNatHoleResp = 'm'
|
TypeNatHoleResp = 'm'
|
||||||
TypeNatHoleClientDetectOK = 'd'
|
TypeNatHoleSid = '5'
|
||||||
TypeNatHoleSid = '5'
|
TypeNatHoleReport = '6'
|
||||||
TypeNatHoleBinding = 'b'
|
|
||||||
TypeNatHoleBindingResp = '6'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var msgTypeMap = map[byte]interface{}{
|
var msgTypeMap = map[byte]interface{}{
|
||||||
TypeLogin: Login{},
|
TypeLogin: Login{},
|
||||||
TypeLoginResp: LoginResp{},
|
TypeLoginResp: LoginResp{},
|
||||||
TypeNewProxy: NewProxy{},
|
TypeNewProxy: NewProxy{},
|
||||||
TypeNewProxyResp: NewProxyResp{},
|
TypeNewProxyResp: NewProxyResp{},
|
||||||
TypeCloseProxy: CloseProxy{},
|
TypeCloseProxy: CloseProxy{},
|
||||||
TypeNewWorkConn: NewWorkConn{},
|
TypeNewWorkConn: NewWorkConn{},
|
||||||
TypeReqWorkConn: ReqWorkConn{},
|
TypeReqWorkConn: ReqWorkConn{},
|
||||||
TypeStartWorkConn: StartWorkConn{},
|
TypeStartWorkConn: StartWorkConn{},
|
||||||
TypeNewVisitorConn: NewVisitorConn{},
|
TypeNewVisitorConn: NewVisitorConn{},
|
||||||
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
||||||
TypePing: Ping{},
|
TypePing: Ping{},
|
||||||
TypePong: Pong{},
|
TypePong: Pong{},
|
||||||
TypeUDPPacket: UDPPacket{},
|
TypeUDPPacket: UDPPacket{},
|
||||||
TypeNatHoleVisitor: NatHoleVisitor{},
|
TypeNatHoleVisitor: NatHoleVisitor{},
|
||||||
TypeNatHoleClient: NatHoleClient{},
|
TypeNatHoleClient: NatHoleClient{},
|
||||||
TypeNatHoleResp: NatHoleResp{},
|
TypeNatHoleResp: NatHoleResp{},
|
||||||
TypeNatHoleClientDetectOK: NatHoleClientDetectOK{},
|
TypeNatHoleSid: NatHoleSid{},
|
||||||
TypeNatHoleSid: NatHoleSid{},
|
TypeNatHoleReport: NatHoleReport{},
|
||||||
TypeNatHoleBinding: NatHoleBinding{},
|
|
||||||
TypeNatHoleBindingResp: NatHoleBindingResp{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var TypeNameNatHoleResp = reflect.TypeOf(&NatHoleResp{}).Elem().Name()
|
||||||
|
|
||||||
// When frpc start, client send this message to login to server.
|
// When frpc start, client send this message to login to server.
|
||||||
type Login struct {
|
type Login struct {
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
@ -81,10 +80,9 @@ type Login struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LoginResp struct {
|
type LoginResp struct {
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
RunID string `json:"run_id,omitempty"`
|
RunID string `json:"run_id,omitempty"`
|
||||||
ServerUDPPort int `json:"server_udp_port,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
Error string `json:"error,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When frpc login success, send this message to frps for running a new proxy.
|
// When frpc login success, send this message to frps for running a new proxy.
|
||||||
@ -147,6 +145,7 @@ type StartWorkConn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NewVisitorConn struct {
|
type NewVisitorConn struct {
|
||||||
|
RunID string `json:"run_id,omitempty"`
|
||||||
ProxyName string `json:"proxy_name,omitempty"`
|
ProxyName string `json:"proxy_name,omitempty"`
|
||||||
SignKey string `json:"sign_key,omitempty"`
|
SignKey string `json:"sign_key,omitempty"`
|
||||||
Timestamp int64 `json:"timestamp,omitempty"`
|
Timestamp int64 `json:"timestamp,omitempty"`
|
||||||
@ -175,35 +174,58 @@ type UDPPacket struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NatHoleVisitor struct {
|
type NatHoleVisitor struct {
|
||||||
ProxyName string `json:"proxy_name,omitempty"`
|
TransactionID string `json:"transaction_id,omitempty"`
|
||||||
SignKey string `json:"sign_key,omitempty"`
|
ProxyName string `json:"proxy_name,omitempty"`
|
||||||
Timestamp int64 `json:"timestamp,omitempty"`
|
PreCheck bool `json:"pre_check,omitempty"`
|
||||||
|
Protocol string `json:"protocol,omitempty"`
|
||||||
|
SignKey string `json:"sign_key,omitempty"`
|
||||||
|
Timestamp int64 `json:"timestamp,omitempty"`
|
||||||
|
MappedAddrs []string `json:"mapped_addrs,omitempty"`
|
||||||
|
AssistedAddrs []string `json:"assisted_addrs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NatHoleClient struct {
|
type NatHoleClient struct {
|
||||||
ProxyName string `json:"proxy_name,omitempty"`
|
TransactionID string `json:"transaction_id,omitempty"`
|
||||||
Sid string `json:"sid,omitempty"`
|
ProxyName string `json:"proxy_name,omitempty"`
|
||||||
|
Sid string `json:"sid,omitempty"`
|
||||||
|
MappedAddrs []string `json:"mapped_addrs,omitempty"`
|
||||||
|
AssistedAddrs []string `json:"assisted_addrs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortsRange struct {
|
||||||
|
From int `json:"from,omitempty"`
|
||||||
|
To int `json:"to,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleDetectBehavior struct {
|
||||||
|
Role string `json:"role,omitempty"` // sender or receiver
|
||||||
|
Mode int `json:"mode,omitempty"` // 0, 1, 2...
|
||||||
|
TTL int `json:"ttl,omitempty"`
|
||||||
|
SendDelayMs int `json:"send_delay_ms,omitempty"`
|
||||||
|
ReadTimeoutMs int `json:"read_timeout,omitempty"`
|
||||||
|
CandidatePorts []PortsRange `json:"candidate_ports,omitempty"`
|
||||||
|
SendRandomPorts int `json:"send_random_ports,omitempty"`
|
||||||
|
ListenRandomPorts int `json:"listen_random_ports,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NatHoleResp struct {
|
type NatHoleResp struct {
|
||||||
Sid string `json:"sid,omitempty"`
|
TransactionID string `json:"transaction_id,omitempty"`
|
||||||
VisitorAddr string `json:"visitor_addr,omitempty"`
|
Sid string `json:"sid,omitempty"`
|
||||||
ClientAddr string `json:"client_addr,omitempty"`
|
Protocol string `json:"protocol,omitempty"`
|
||||||
Error string `json:"error,omitempty"`
|
CandidateAddrs []string `json:"candidate_addrs,omitempty"`
|
||||||
|
AssistedAddrs []string `json:"assisted_addrs,omitempty"`
|
||||||
|
DetectBehavior NatHoleDetectBehavior `json:"detect_behavior,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NatHoleClientDetectOK struct{}
|
|
||||||
|
|
||||||
type NatHoleSid struct {
|
type NatHoleSid struct {
|
||||||
Sid string `json:"sid,omitempty"`
|
TransactionID string `json:"transaction_id,omitempty"`
|
||||||
|
Sid string `json:"sid,omitempty"`
|
||||||
|
Response bool `json:"response,omitempty"`
|
||||||
|
Nonce string `json:"nonce,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NatHoleBinding struct {
|
type NatHoleReport struct {
|
||||||
TransactionID string `json:"transaction_id,omitempty"`
|
Sid string `json:"sid,omitempty"`
|
||||||
}
|
Success bool `json:"success,omitempty"`
|
||||||
|
|
||||||
type NatHoleBindingResp struct {
|
|
||||||
TransactionID string `json:"transaction_id,omitempty"`
|
|
||||||
Address string `json:"address,omitempty"`
|
|
||||||
Error string `json:"error,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
328
pkg/nathole/analysis.go
Normal file
328
pkg/nathole/analysis.go
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package nathole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// mode 0, both EasyNAT, PublicNetwork is always receiver
|
||||||
|
// sender | receiver, ttl 7
|
||||||
|
// receiver, ttl 7 | sender
|
||||||
|
// sender | receiver, ttl 4
|
||||||
|
// receiver, ttl 4 | sender
|
||||||
|
// sender | receiver
|
||||||
|
// receiver | sender
|
||||||
|
// sender, sendDelayMs 5000 | receiver
|
||||||
|
// sender, sendDelayMs 10000 | receiver
|
||||||
|
// receiver | sender, sendDelayMs 5000
|
||||||
|
// receiver | sender, sendDelayMs 10000
|
||||||
|
mode0Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 7}, RecommandBehavior{Role: DetectRoleSender}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 4}, RecommandBehavior{Role: DetectRoleSender}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver}, RecommandBehavior{Role: DetectRoleSender}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 5000}, RecommandBehavior{Role: DetectRoleReceiver}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 10000}, RecommandBehavior{Role: DetectRoleReceiver}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver}, RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 5000}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver}, RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 10000}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode 1, HardNAT is sender, EasyNAT is receiver, port changes is regular
|
||||||
|
// sender | receiver, ttl 7, portsRangeNumber max 10
|
||||||
|
// sender, sendDelayMs 2000 | receiver, ttl 7, portsRangeNumber max 10
|
||||||
|
// sender | receiver, ttl 4, portsRangeNumber max 10
|
||||||
|
// sender, sendDelayMs 2000 | receiver, ttl 4, portsRangeNumber max 10
|
||||||
|
// sender | receiver, portsRangeNumber max 10
|
||||||
|
// sender, sendDelayMs 2000 | receiver, portsRangeNumber max 10
|
||||||
|
mode1Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode 2, HardNAT is receiver, EasyNAT is sender
|
||||||
|
// sender, portsRandomNumber 1000, sendDelayMs 3000 | receiver, listen 256 ports, ttl 7
|
||||||
|
// sender, portsRandomNumber 1000, sendDelayMs 3000 | receiver, listen 256 ports, ttl 4
|
||||||
|
// sender, portsRandomNumber 1000, sendDelayMs 3000 | receiver, listen 256 ports
|
||||||
|
mode2Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 7},
|
||||||
|
),
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 4},
|
||||||
|
),
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode 3, For HardNAT & HardNAT, both changes in the ports are regular
|
||||||
|
// sender, portsRangeNumber 10 | receiver, ttl 7, portsRangeNumber 10
|
||||||
|
// sender, portsRangeNumber 10 | receiver, ttl 4, portsRangeNumber 10
|
||||||
|
// sender, portsRangeNumber 10 | receiver, portsRangeNumber 10
|
||||||
|
// receiver, ttl 7, portsRangeNumber 10 | sender, portsRangeNumber 10
|
||||||
|
// receiver, ttl 4, portsRangeNumber 10 | sender, portsRangeNumber 10
|
||||||
|
// receiver, portsRangeNumber 10 | sender, portsRangeNumber 10
|
||||||
|
mode3Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode 4, Regular ports changes are usually the sender.
|
||||||
|
// sender, portsRandomNumber 1000, sendDelayMs: 2000 | receiver, listen 256 ports, ttl 7, portsRangeNumber 2
|
||||||
|
// sender, portsRandomNumber 1000, sendDelayMs: 2000 | receiver, listen 256 ports, ttl 4, portsRangeNumber 2
|
||||||
|
// sender, portsRandomNumber 1000, SendDelayMs: 2000 | receiver, listen 256 ports, portsRangeNumber 2
|
||||||
|
mode4Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 7, PortsRangeNumber: 2},
|
||||||
|
),
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 4, PortsRangeNumber: 2},
|
||||||
|
),
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, PortsRangeNumber: 2},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func getBehaviorByMode(mode int) []lo.Tuple2[RecommandBehavior, RecommandBehavior] {
|
||||||
|
switch mode {
|
||||||
|
case 0:
|
||||||
|
return mode0Behaviors
|
||||||
|
case 1:
|
||||||
|
return mode1Behaviors
|
||||||
|
case 2:
|
||||||
|
return mode2Behaviors
|
||||||
|
case 3:
|
||||||
|
return mode3Behaviors
|
||||||
|
case 4:
|
||||||
|
return mode4Behaviors
|
||||||
|
}
|
||||||
|
// default
|
||||||
|
return mode0Behaviors
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBehaviorByModeAndIndex(mode int, index int) (RecommandBehavior, RecommandBehavior) {
|
||||||
|
behaviors := getBehaviorByMode(mode)
|
||||||
|
if index >= len(behaviors) {
|
||||||
|
return RecommandBehavior{}, RecommandBehavior{}
|
||||||
|
}
|
||||||
|
return behaviors[index].A, behaviors[index].B
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBehaviorScoresByMode(mode int, defaultScore int) []*BehaviorScore {
|
||||||
|
return getBehaviorScoresByMode2(mode, defaultScore, defaultScore)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBehaviorScoresByMode2(mode int, senderScore, receiverScore int) []*BehaviorScore {
|
||||||
|
behaviors := getBehaviorByMode(mode)
|
||||||
|
scores := make([]*BehaviorScore, 0, len(behaviors))
|
||||||
|
for i := 0; i < len(behaviors); i++ {
|
||||||
|
score := receiverScore
|
||||||
|
if behaviors[i].A.Role == DetectRoleSender {
|
||||||
|
score = senderScore
|
||||||
|
}
|
||||||
|
scores = append(scores, &BehaviorScore{Mode: mode, Index: i, Score: score})
|
||||||
|
}
|
||||||
|
return scores
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecommandBehavior struct {
|
||||||
|
Role string
|
||||||
|
TTL int
|
||||||
|
SendDelayMs int
|
||||||
|
PortsRangeNumber int
|
||||||
|
PortsRandomNumber int
|
||||||
|
ListenRandomPorts int
|
||||||
|
}
|
||||||
|
|
||||||
|
type MakeHoleRecords struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
scores []*BehaviorScore
|
||||||
|
LastUpdateTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMakeHoleRecords(c, v *NatFeature) *MakeHoleRecords {
|
||||||
|
scores := []*BehaviorScore{}
|
||||||
|
easyCount, hardCount, portsChangedRegularCount := ClassifyFeatureCount([]*NatFeature{c, v})
|
||||||
|
appendMode0 := func() {
|
||||||
|
switch {
|
||||||
|
case c.PublicNetwork:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode2(DetectMode0, 0, 1)...)
|
||||||
|
case v.PublicNetwork:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode2(DetectMode0, 1, 0)...)
|
||||||
|
default:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode0, 0)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case easyCount == 2:
|
||||||
|
appendMode0()
|
||||||
|
case hardCount == 1 && portsChangedRegularCount == 1:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode1, 0)...)
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode2, 0)...)
|
||||||
|
appendMode0()
|
||||||
|
case hardCount == 1 && portsChangedRegularCount == 0:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode2, 0)...)
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode1, 0)...)
|
||||||
|
appendMode0()
|
||||||
|
case hardCount == 2 && portsChangedRegularCount == 2:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode3, 0)...)
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode4, 0)...)
|
||||||
|
case hardCount == 2 && portsChangedRegularCount == 1:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode4, 0)...)
|
||||||
|
default:
|
||||||
|
// hard to make hole, just trying it out.
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode0, 1)...)
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode1, 1)...)
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode3, 1)...)
|
||||||
|
}
|
||||||
|
return &MakeHoleRecords{scores: scores, LastUpdateTime: time.Now()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mhr *MakeHoleRecords) ReportSuccess(mode int, index int) {
|
||||||
|
mhr.mu.Lock()
|
||||||
|
defer mhr.mu.Unlock()
|
||||||
|
mhr.LastUpdateTime = time.Now()
|
||||||
|
for i := range mhr.scores {
|
||||||
|
score := mhr.scores[i]
|
||||||
|
if score.Mode != mode || score.Index != index {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
score.Score += 2
|
||||||
|
score.Score = lo.Min([]int{score.Score, 10})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mhr *MakeHoleRecords) Recommand() (mode, index int) {
|
||||||
|
mhr.mu.Lock()
|
||||||
|
defer mhr.mu.Unlock()
|
||||||
|
|
||||||
|
maxScore := lo.MaxBy(mhr.scores, func(item, max *BehaviorScore) bool {
|
||||||
|
return item.Score > max.Score
|
||||||
|
})
|
||||||
|
if maxScore == nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
maxScore.Score--
|
||||||
|
mhr.LastUpdateTime = time.Now()
|
||||||
|
return maxScore.Mode, maxScore.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
type BehaviorScore struct {
|
||||||
|
Mode int
|
||||||
|
Index int
|
||||||
|
// between -10 and 10
|
||||||
|
Score int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Analyzer struct {
|
||||||
|
// key is client ip + visitor ip
|
||||||
|
records map[string]*MakeHoleRecords
|
||||||
|
dataReserveDuration time.Duration
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnalyzer(dataReserveDuration time.Duration) *Analyzer {
|
||||||
|
return &Analyzer{
|
||||||
|
records: make(map[string]*MakeHoleRecords),
|
||||||
|
dataReserveDuration: dataReserveDuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) GetRecommandBehaviors(key string, c, v *NatFeature) (mode, index int, _ RecommandBehavior, _ RecommandBehavior) {
|
||||||
|
a.mu.Lock()
|
||||||
|
records, ok := a.records[key]
|
||||||
|
if !ok {
|
||||||
|
records = NewMakeHoleRecords(c, v)
|
||||||
|
a.records[key] = records
|
||||||
|
}
|
||||||
|
a.mu.Unlock()
|
||||||
|
|
||||||
|
mode, index = records.Recommand()
|
||||||
|
cBehavior, vBehavior := getBehaviorByModeAndIndex(mode, index)
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case DetectMode1:
|
||||||
|
// HardNAT is always the sender
|
||||||
|
if c.NatType == EasyNAT {
|
||||||
|
cBehavior, vBehavior = vBehavior, cBehavior
|
||||||
|
}
|
||||||
|
case DetectMode2:
|
||||||
|
// HardNAT is always the receiver
|
||||||
|
if c.NatType == HardNAT {
|
||||||
|
cBehavior, vBehavior = vBehavior, cBehavior
|
||||||
|
}
|
||||||
|
case DetectMode4:
|
||||||
|
// Regular ports changes is always the sender
|
||||||
|
if !c.RegularPortsChange {
|
||||||
|
cBehavior, vBehavior = vBehavior, cBehavior
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mode, index, cBehavior, vBehavior
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) ReportSuccess(key string, mode, index int) {
|
||||||
|
a.mu.Lock()
|
||||||
|
records, ok := a.records[key]
|
||||||
|
a.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
records.ReportSuccess(mode, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) Clean() (int, int) {
|
||||||
|
now := time.Now()
|
||||||
|
total := 0
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
// cleanup 10w records may take 5ms
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
total = len(a.records)
|
||||||
|
// clean up records that have not been used for a period of time.
|
||||||
|
for key, records := range a.records {
|
||||||
|
if now.Sub(records.LastUpdateTime) > a.dataReserveDuration {
|
||||||
|
delete(a.records, key)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count, total
|
||||||
|
}
|
@ -17,6 +17,9 @@ package nathole
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -29,46 +32,97 @@ const (
|
|||||||
BehaviorBothChanged = "BehaviorBothChanged"
|
BehaviorBothChanged = "BehaviorBothChanged"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClassifyNATType classify NAT type by given addresses.
|
type NatFeature struct {
|
||||||
func ClassifyNATType(addresses []string) (string, string, error) {
|
NatType string
|
||||||
|
Behavior string
|
||||||
|
PortsDifference int
|
||||||
|
RegularPortsChange bool
|
||||||
|
PublicNetwork bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClassifyNATFeature(addresses []string, localIPs []string) (*NatFeature, error) {
|
||||||
if len(addresses) <= 1 {
|
if len(addresses) <= 1 {
|
||||||
return "", "", fmt.Errorf("not enough addresses")
|
return nil, fmt.Errorf("not enough addresses")
|
||||||
}
|
}
|
||||||
|
natFeature := &NatFeature{}
|
||||||
ipChanged := false
|
ipChanged := false
|
||||||
portChanged := false
|
portChanged := false
|
||||||
|
|
||||||
var baseIP, basePort string
|
var baseIP, basePort string
|
||||||
|
var portMax, portMin int
|
||||||
for _, addr := range addresses {
|
for _, addr := range addresses {
|
||||||
ip, port, err := net.SplitHostPort(addr)
|
ip, port, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
portNum, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if lo.Contains(localIPs, ip) {
|
||||||
|
natFeature.PublicNetwork = true
|
||||||
|
}
|
||||||
|
|
||||||
if baseIP == "" {
|
if baseIP == "" {
|
||||||
baseIP = ip
|
baseIP = ip
|
||||||
basePort = port
|
basePort = port
|
||||||
|
portMax = portNum
|
||||||
|
portMin = portNum
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if portNum > portMax {
|
||||||
|
portMax = portNum
|
||||||
|
}
|
||||||
|
if portNum < portMin {
|
||||||
|
portMin = portNum
|
||||||
|
}
|
||||||
if baseIP != ip {
|
if baseIP != ip {
|
||||||
ipChanged = true
|
ipChanged = true
|
||||||
}
|
}
|
||||||
if basePort != port {
|
if basePort != port {
|
||||||
portChanged = true
|
portChanged = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipChanged && portChanged {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case ipChanged && portChanged:
|
case ipChanged && portChanged:
|
||||||
return HardNAT, BehaviorBothChanged, nil
|
natFeature.NatType = HardNAT
|
||||||
|
natFeature.Behavior = BehaviorBothChanged
|
||||||
case ipChanged:
|
case ipChanged:
|
||||||
return HardNAT, BehaviorIPChanged, nil
|
natFeature.NatType = HardNAT
|
||||||
|
natFeature.Behavior = BehaviorIPChanged
|
||||||
case portChanged:
|
case portChanged:
|
||||||
return HardNAT, BehaviorPortChanged, nil
|
natFeature.NatType = HardNAT
|
||||||
|
natFeature.Behavior = BehaviorPortChanged
|
||||||
default:
|
default:
|
||||||
return EasyNAT, BehaviorNoChange, nil
|
natFeature.NatType = EasyNAT
|
||||||
|
natFeature.Behavior = BehaviorNoChange
|
||||||
}
|
}
|
||||||
|
if natFeature.Behavior == BehaviorPortChanged {
|
||||||
|
natFeature.PortsDifference = portMax - portMin
|
||||||
|
if natFeature.PortsDifference <= 5 && natFeature.PortsDifference >= 1 {
|
||||||
|
natFeature.RegularPortsChange = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return natFeature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClassifyFeatureCount(features []*NatFeature) (int, int, int) {
|
||||||
|
easyCount := 0
|
||||||
|
hardCount := 0
|
||||||
|
// for HardNAT
|
||||||
|
portsChangedRegularCount := 0
|
||||||
|
for _, feature := range features {
|
||||||
|
if feature.NatType == EasyNAT {
|
||||||
|
easyCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hardCount++
|
||||||
|
if feature.RegularPortsChange {
|
||||||
|
portsChangedRegularCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return easyCount, hardCount, portsChangedRegularCount
|
||||||
}
|
}
|
||||||
|
382
pkg/nathole/controller.go
Normal file
382
pkg/nathole/controller.go
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package nathole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NatHoleTimeout seconds.
|
||||||
|
var NatHoleTimeout int64 = 10
|
||||||
|
|
||||||
|
func NewTransactionID() string {
|
||||||
|
id, _ := util.RandID()
|
||||||
|
return fmt.Sprintf("%d%s", time.Now().Unix(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientCfg struct {
|
||||||
|
name string
|
||||||
|
sk string
|
||||||
|
sidCh chan string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
sid string
|
||||||
|
analysisKey string
|
||||||
|
recommandMode int
|
||||||
|
recommandIndex int
|
||||||
|
|
||||||
|
visitorMsg *msg.NatHoleVisitor
|
||||||
|
visitorTransporter transport.MessageTransporter
|
||||||
|
vResp *msg.NatHoleResp
|
||||||
|
vNatFeature *NatFeature
|
||||||
|
vBehavior RecommandBehavior
|
||||||
|
|
||||||
|
clientMsg *msg.NatHoleClient
|
||||||
|
clientTransporter transport.MessageTransporter
|
||||||
|
cResp *msg.NatHoleResp
|
||||||
|
cNatFeature *NatFeature
|
||||||
|
cBehavior RecommandBehavior
|
||||||
|
|
||||||
|
notifyCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) genAnalysisKey() {
|
||||||
|
hash := md5.New()
|
||||||
|
vIPs := lo.Uniq(parseIPs(s.visitorMsg.MappedAddrs))
|
||||||
|
if len(vIPs) > 0 {
|
||||||
|
hash.Write([]byte(vIPs[0]))
|
||||||
|
}
|
||||||
|
hash.Write([]byte(s.vNatFeature.NatType))
|
||||||
|
hash.Write([]byte(s.vNatFeature.Behavior))
|
||||||
|
hash.Write([]byte(strconv.FormatBool(s.vNatFeature.RegularPortsChange)))
|
||||||
|
|
||||||
|
cIPs := lo.Uniq(parseIPs(s.clientMsg.MappedAddrs))
|
||||||
|
if len(cIPs) > 0 {
|
||||||
|
hash.Write([]byte(cIPs[0]))
|
||||||
|
}
|
||||||
|
hash.Write([]byte(s.cNatFeature.NatType))
|
||||||
|
hash.Write([]byte(s.cNatFeature.Behavior))
|
||||||
|
hash.Write([]byte(strconv.FormatBool(s.cNatFeature.RegularPortsChange)))
|
||||||
|
s.analysisKey = hex.EncodeToString(hash.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
clientCfgs map[string]*ClientCfg
|
||||||
|
sessions map[string]*Session
|
||||||
|
analyzer *Analyzer
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(analysisDataReserveDuration time.Duration) (*Controller, error) {
|
||||||
|
return &Controller{
|
||||||
|
clientCfgs: make(map[string]*ClientCfg),
|
||||||
|
sessions: make(map[string]*Session),
|
||||||
|
analyzer: NewAnalyzer(analysisDataReserveDuration),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) CleanWorker(ctx context.Context) {
|
||||||
|
ticker := time.NewTicker(time.Hour)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
start := time.Now()
|
||||||
|
count, total := c.analyzer.Clean()
|
||||||
|
log.Trace("clean %d/%d nathole analysis data, cost %v", count, total, time.Since(start))
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ListenClient(name string, sk string) chan string {
|
||||||
|
cfg := &ClientCfg{
|
||||||
|
name: name,
|
||||||
|
sk: sk,
|
||||||
|
sidCh: make(chan string),
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.clientCfgs[name] = cfg
|
||||||
|
return cfg.sidCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) CloseClient(name string) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
delete(c.clientCfgs, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) GenSid() string {
|
||||||
|
t := time.Now().Unix()
|
||||||
|
id, _ := util.RandID()
|
||||||
|
return fmt.Sprintf("%d%s", t, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.MessageTransporter) {
|
||||||
|
if m.PreCheck {
|
||||||
|
_, ok := c.clientCfgs[m.ProxyName]
|
||||||
|
if !ok {
|
||||||
|
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)))
|
||||||
|
} else {
|
||||||
|
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, ""))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := c.GenSid()
|
||||||
|
session := &Session{
|
||||||
|
sid: sid,
|
||||||
|
visitorMsg: m,
|
||||||
|
visitorTransporter: transporter,
|
||||||
|
notifyCh: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
clientCfg *ClientCfg
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
err := func() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
clientCfg, ok = c.clientCfgs[m.ProxyName]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("xtcp server for [%s] doesn't exist", m.ProxyName)
|
||||||
|
}
|
||||||
|
if !util.ConstantTimeEqString(m.SignKey, util.GetAuthKey(clientCfg.sk, m.Timestamp)) {
|
||||||
|
return fmt.Errorf("xtcp connection of [%s] auth failed", m.ProxyName)
|
||||||
|
}
|
||||||
|
c.sessions[sid] = session
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("handle visitorMsg error: %v", err)
|
||||||
|
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("handle visitor message, sid [%s]", sid)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
delete(c.sessions, sid)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := errors.PanicToError(func() {
|
||||||
|
clientCfg.sidCh <- sid
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for NatHoleClient message
|
||||||
|
select {
|
||||||
|
case <-session.notifyCh:
|
||||||
|
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
|
||||||
|
log.Debug("wait for NatHoleClient message timeout, sid [%s]", sid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make hole-punching decisions based on the NAT information of the client and visitor.
|
||||||
|
vResp, cResp, err := c.analysis(session)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("sid [%s] analysis error: %v", err)
|
||||||
|
vResp = c.GenNatHoleResponse(session.visitorMsg.TransactionID, nil, err.Error())
|
||||||
|
cResp = c.GenNatHoleResponse(session.clientMsg.TransactionID, nil, err.Error())
|
||||||
|
}
|
||||||
|
session.cResp = cResp
|
||||||
|
session.vResp = vResp
|
||||||
|
|
||||||
|
// send response to visitor and client
|
||||||
|
var g errgroup.Group
|
||||||
|
g.Go(func() error {
|
||||||
|
// if it's sender, wait for a while to make sure the client has send the detect messages
|
||||||
|
if vResp.DetectBehavior.Role == "sender" {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
_ = session.visitorTransporter.Send(vResp)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
g.Go(func() error {
|
||||||
|
// if it's sender, wait for a while to make sure the client has send the detect messages
|
||||||
|
if cResp.DetectBehavior.Role == "sender" {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
_ = session.clientTransporter.Send(cResp)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
_ = g.Wait()
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(cResp.DetectBehavior.ReadTimeoutMs+30000) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) HandleClient(m *msg.NatHoleClient, transporter transport.MessageTransporter) {
|
||||||
|
c.mu.RLock()
|
||||||
|
session, ok := c.sessions[m.Sid]
|
||||||
|
c.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("handle client message, sid [%s]", session.sid)
|
||||||
|
session.clientMsg = m
|
||||||
|
session.clientTransporter = transporter
|
||||||
|
select {
|
||||||
|
case session.notifyCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) HandleReport(m *msg.NatHoleReport) {
|
||||||
|
c.mu.RLock()
|
||||||
|
session, ok := c.sessions[m.Sid]
|
||||||
|
c.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
log.Trace("sid [%s] report make hole success: %v, but session not found", m.Sid, m.Success)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m.Success {
|
||||||
|
c.analyzer.ReportSuccess(session.analysisKey, session.recommandMode, session.recommandIndex)
|
||||||
|
}
|
||||||
|
log.Info("sid [%s] report make hole success: %v, mode %v, index %v",
|
||||||
|
m.Sid, m.Success, session.recommandMode, session.recommandIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) GenNatHoleResponse(transactionID string, session *Session, errInfo string) *msg.NatHoleResp {
|
||||||
|
var sid string
|
||||||
|
if session != nil {
|
||||||
|
sid = session.sid
|
||||||
|
}
|
||||||
|
return &msg.NatHoleResp{
|
||||||
|
TransactionID: transactionID,
|
||||||
|
Sid: sid,
|
||||||
|
Error: errInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// analysis analyzes the NAT type and behavior of the visitor and client, then makes hole-punching decisions.
|
||||||
|
// return the response to the visitor and client.
|
||||||
|
func (c *Controller) analysis(session *Session) (*msg.NatHoleResp, *msg.NatHoleResp, error) {
|
||||||
|
cm := session.clientMsg
|
||||||
|
vm := session.visitorMsg
|
||||||
|
|
||||||
|
cNatFeature, err := ClassifyNATFeature(cm.MappedAddrs, parseIPs(cm.AssistedAddrs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("classify client nat feature error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vNatFeature, err := ClassifyNATFeature(vm.MappedAddrs, parseIPs(vm.AssistedAddrs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("classify visitor nat feature error: %v", err)
|
||||||
|
}
|
||||||
|
session.cNatFeature = cNatFeature
|
||||||
|
session.vNatFeature = vNatFeature
|
||||||
|
session.genAnalysisKey()
|
||||||
|
|
||||||
|
mode, index, cBehavior, vBehavior := c.analyzer.GetRecommandBehaviors(session.analysisKey, cNatFeature, vNatFeature)
|
||||||
|
session.recommandMode = mode
|
||||||
|
session.recommandIndex = index
|
||||||
|
session.cBehavior = cBehavior
|
||||||
|
session.vBehavior = vBehavior
|
||||||
|
|
||||||
|
timeoutMs := lo.Max([]int{cBehavior.SendDelayMs, vBehavior.SendDelayMs}) + 5000
|
||||||
|
if cBehavior.ListenRandomPorts > 0 || vBehavior.ListenRandomPorts > 0 {
|
||||||
|
timeoutMs += 30000
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol := vm.Protocol
|
||||||
|
vResp := &msg.NatHoleResp{
|
||||||
|
TransactionID: vm.TransactionID,
|
||||||
|
Sid: session.sid,
|
||||||
|
Protocol: protocol,
|
||||||
|
CandidateAddrs: lo.Uniq(cm.MappedAddrs),
|
||||||
|
AssistedAddrs: lo.Uniq(cm.AssistedAddrs),
|
||||||
|
DetectBehavior: msg.NatHoleDetectBehavior{
|
||||||
|
Mode: mode,
|
||||||
|
Role: vBehavior.Role,
|
||||||
|
TTL: vBehavior.TTL,
|
||||||
|
SendDelayMs: vBehavior.SendDelayMs,
|
||||||
|
ReadTimeoutMs: timeoutMs - vBehavior.SendDelayMs,
|
||||||
|
SendRandomPorts: vBehavior.PortsRandomNumber,
|
||||||
|
ListenRandomPorts: vBehavior.ListenRandomPorts,
|
||||||
|
CandidatePorts: getRangePorts(cm.MappedAddrs, cNatFeature.PortsDifference, vBehavior.PortsRangeNumber),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cResp := &msg.NatHoleResp{
|
||||||
|
TransactionID: cm.TransactionID,
|
||||||
|
Sid: session.sid,
|
||||||
|
Protocol: protocol,
|
||||||
|
CandidateAddrs: lo.Uniq(vm.MappedAddrs),
|
||||||
|
AssistedAddrs: lo.Uniq(vm.AssistedAddrs),
|
||||||
|
DetectBehavior: msg.NatHoleDetectBehavior{
|
||||||
|
Mode: mode,
|
||||||
|
Role: cBehavior.Role,
|
||||||
|
TTL: cBehavior.TTL,
|
||||||
|
SendDelayMs: cBehavior.SendDelayMs,
|
||||||
|
ReadTimeoutMs: timeoutMs - cBehavior.SendDelayMs,
|
||||||
|
SendRandomPorts: cBehavior.PortsRandomNumber,
|
||||||
|
ListenRandomPorts: cBehavior.ListenRandomPorts,
|
||||||
|
CandidatePorts: getRangePorts(vm.MappedAddrs, vNatFeature.PortsDifference, cBehavior.PortsRangeNumber),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("sid [%s] visitor nat: %+v, candidateAddrs: %v; client nat: %+v, candidateAddrs: %v, protocol: %s",
|
||||||
|
session.sid, *vNatFeature, vm.MappedAddrs, *cNatFeature, cm.MappedAddrs, protocol)
|
||||||
|
log.Debug("sid [%s] visitor detect behavior: %+v", session.sid, vResp.DetectBehavior)
|
||||||
|
log.Debug("sid [%s] client detect behavior: %+v", session.sid, cResp.DetectBehavior)
|
||||||
|
return vResp, cResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRangePorts(addrs []string, difference, maxNumber int) []msg.PortsRange {
|
||||||
|
if maxNumber <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := lo.Last(addrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var ports []msg.PortsRange
|
||||||
|
_, portStr, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ports = append(ports, msg.PortsRange{
|
||||||
|
From: lo.Max([]int{port - difference - 5, port - maxNumber, 1}),
|
||||||
|
To: lo.Min([]int{port + difference + 5, port + maxNumber, 65535}),
|
||||||
|
})
|
||||||
|
return ports
|
||||||
|
}
|
@ -20,8 +20,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/stun"
|
"github.com/pion/stun"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var responseTimeout = 3 * time.Second
|
var responseTimeout = 3 * time.Second
|
||||||
@ -31,35 +29,27 @@ type Message struct {
|
|||||||
Addr string
|
Addr string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Discover(serverAddress string, stunServers []string, key []byte) ([]string, error) {
|
// If the localAddr is empty, it will listen on a random port.
|
||||||
|
func Discover(stunServers []string, localAddr string) ([]string, net.Addr, error) {
|
||||||
// create a discoverConn and get response from messageChan
|
// create a discoverConn and get response from messageChan
|
||||||
discoverConn, err := listen()
|
discoverConn, err := listen(localAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
defer discoverConn.Close()
|
defer discoverConn.Close()
|
||||||
|
|
||||||
go discoverConn.readLoop()
|
go discoverConn.readLoop()
|
||||||
|
|
||||||
addresses := make([]string, 0, len(stunServers)+1)
|
addresses := make([]string, 0, len(stunServers))
|
||||||
if serverAddress != "" {
|
|
||||||
// get external address from frp server
|
|
||||||
externalAddr, err := discoverConn.discoverFromServer(serverAddress, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
addresses = append(addresses, externalAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range stunServers {
|
for _, addr := range stunServers {
|
||||||
// get external address from stun server
|
// get external address from stun server
|
||||||
externalAddrs, err := discoverConn.discoverFromStunServer(addr)
|
externalAddrs, err := discoverConn.discoverFromStunServer(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
addresses = append(addresses, externalAddrs...)
|
addresses = append(addresses, externalAddrs...)
|
||||||
}
|
}
|
||||||
return addresses, nil
|
return addresses, discoverConn.localAddr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type stunResponse struct {
|
type stunResponse struct {
|
||||||
@ -74,8 +64,16 @@ type discoverConn struct {
|
|||||||
messageChan chan *Message
|
messageChan chan *Message
|
||||||
}
|
}
|
||||||
|
|
||||||
func listen() (*discoverConn, error) {
|
func listen(localAddr string) (*discoverConn, error) {
|
||||||
conn, err := net.ListenUDP("udp4", nil)
|
var local *net.UDPAddr
|
||||||
|
if localAddr != "" {
|
||||||
|
addr, err := net.ResolveUDPAddr("udp4", localAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
local = addr
|
||||||
|
}
|
||||||
|
conn, err := net.ListenUDP("udp4", local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -159,43 +157,6 @@ func (c *discoverConn) doSTUNRequest(addr string) (*stunResponse, error) {
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *discoverConn) discoverFromServer(serverAddress string, key []byte) (string, error) {
|
|
||||||
addr, err := net.ResolveUDPAddr("udp4", serverAddress)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
m := &msg.NatHoleBinding{
|
|
||||||
TransactionID: NewTransactionID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := EncodeMessage(m, key)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := c.conn.WriteTo(buf, addr); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var respMsg msg.NatHoleBindingResp
|
|
||||||
select {
|
|
||||||
case rawMsg := <-c.messageChan:
|
|
||||||
if err := DecodeMessageInto(rawMsg.Body, key, &respMsg); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
case <-time.After(responseTimeout):
|
|
||||||
return "", fmt.Errorf("wait response from frp server timeout")
|
|
||||||
}
|
|
||||||
|
|
||||||
if respMsg.TransactionID == "" {
|
|
||||||
return "", fmt.Errorf("error format: no transaction id found")
|
|
||||||
}
|
|
||||||
if respMsg.Error != "" {
|
|
||||||
return "", fmt.Errorf("get externalAddr from frp server error: %s", respMsg.Error)
|
|
||||||
}
|
|
||||||
return respMsg.Address, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *discoverConn) discoverFromStunServer(addr string) ([]string, error) {
|
func (c *discoverConn) discoverFromStunServer(addr string) ([]string, error) {
|
||||||
resp, err := c.doSTUNRequest(addr)
|
resp, err := c.doSTUNRequest(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -15,249 +15,426 @@
|
|||||||
package nathole
|
package nathole
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/golib/crypto"
|
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NatHoleTimeout seconds.
|
var (
|
||||||
var NatHoleTimeout int64 = 10
|
// mode 0: simple detect mode, usually for both EasyNAT or HardNAT & EasyNAT(Public Network)
|
||||||
|
// a. receiver sends detect message with low TTL
|
||||||
|
// b. sender sends normal detect message to receiver
|
||||||
|
// c. receiver receives detect message and sends back a message to sender
|
||||||
|
//
|
||||||
|
// mode 1: For HardNAT & EasyNAT, send detect messages to multiple guessed ports.
|
||||||
|
// Usually applicable to scenarios where port changes are regular.
|
||||||
|
// Most of the steps are the same as mode 0, but EasyNAT is fixed as the receiver and will send detect messages
|
||||||
|
// with low TTL to multiple guessed ports of the sender.
|
||||||
|
//
|
||||||
|
// mode 2: For HardNAT & EasyNAT, ports changes are not regular.
|
||||||
|
// a. HardNAT machine will listen on multiple ports and send detect messages with low TTL to EasyNAT machine
|
||||||
|
// b. EasyNAT machine will send detect messages to random ports of HardNAT machine.
|
||||||
|
//
|
||||||
|
// mode 3: For HardNAT & HardNAT, both changes in the ports are regular.
|
||||||
|
// Most of the steps are the same as mode 1, but the sender also needs to send detect messages to multiple guessed
|
||||||
|
// ports of the receiver.
|
||||||
|
//
|
||||||
|
// mode 4: For HardNAT & HardNAT, one of the changes in the ports is regular.
|
||||||
|
// Regular port changes are usually on the sender side.
|
||||||
|
// a. Receiver listens on multiple ports and sends detect messages with low TTL to the sender's guessed range ports.
|
||||||
|
// b. Sender sends detect messages to random ports of the receiver.
|
||||||
|
SupportedModes = []int{DetectMode0, DetectMode1, DetectMode2, DetectMode3, DetectMode4}
|
||||||
|
SupportedRoles = []string{DetectRoleSender, DetectRoleReceiver}
|
||||||
|
|
||||||
func NewTransactionID() string {
|
DetectMode0 = 0
|
||||||
id, _ := util.RandID()
|
DetectMode1 = 1
|
||||||
return fmt.Sprintf("%d%s", time.Now().Unix(), id)
|
DetectMode2 = 2
|
||||||
|
DetectMode3 = 3
|
||||||
|
DetectMode4 = 4
|
||||||
|
DetectRoleSender = "sender"
|
||||||
|
DetectRoleReceiver = "receiver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrepareResult struct {
|
||||||
|
Addrs []string
|
||||||
|
AssistedAddrs []string
|
||||||
|
ListenConn *net.UDPConn
|
||||||
|
NatType string
|
||||||
|
Behavior string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SidRequest struct {
|
// PreCheck is used to check if the proxy is ready for penetration.
|
||||||
Sid string
|
// Call this function before calling Prepare to avoid unnecessary preparation work.
|
||||||
NotifyCh chan struct{}
|
func PreCheck(
|
||||||
}
|
ctx context.Context, transporter transport.MessageTransporter,
|
||||||
|
proxyName string, timeout time.Duration,
|
||||||
|
) error {
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
type Controller struct {
|
var natHoleRespMsg *msg.NatHoleResp
|
||||||
listener *net.UDPConn
|
transactionID := NewTransactionID()
|
||||||
|
m, err := transporter.Do(timeoutCtx, &msg.NatHoleVisitor{
|
||||||
clientCfgs map[string]*ClientCfg
|
TransactionID: transactionID,
|
||||||
sessions map[string]*Session
|
ProxyName: proxyName,
|
||||||
|
PreCheck: true,
|
||||||
encryptionKey []byte
|
}, transactionID, msg.TypeNameNatHoleResp)
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewController(udpBindAddr string, encryptionKey []byte) (nc *Controller, err error) {
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", udpBindAddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return fmt.Errorf("get natHoleRespMsg error: %v", err)
|
||||||
}
|
}
|
||||||
lconn, err := net.ListenUDP("udp", addr)
|
mm, ok := m.(*msg.NatHoleResp)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("get natHoleRespMsg error: invalid message type")
|
||||||
|
}
|
||||||
|
natHoleRespMsg = mm
|
||||||
|
|
||||||
|
if natHoleRespMsg.Error != "" {
|
||||||
|
return fmt.Errorf("%s", natHoleRespMsg.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare is used to do some preparation work before penetration.
|
||||||
|
func Prepare(stunServers []string) (*PrepareResult, error) {
|
||||||
|
// discover for Nat type
|
||||||
|
addrs, localAddr, err := Discover(stunServers, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("discover error: %v", err)
|
||||||
}
|
}
|
||||||
nc = &Controller{
|
if len(addrs) < 2 {
|
||||||
listener: lconn,
|
return nil, fmt.Errorf("discover error: not enough addresses")
|
||||||
clientCfgs: make(map[string]*ClientCfg),
|
|
||||||
sessions: make(map[string]*Session),
|
|
||||||
encryptionKey: encryptionKey,
|
|
||||||
}
|
}
|
||||||
return nc, nil
|
|
||||||
|
localIPs, _ := ListLocalIPsForNatHole(10)
|
||||||
|
natFeature, err := ClassifyNATFeature(addrs, localIPs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("classify nat feature error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
laddr, err := net.ResolveUDPAddr("udp4", localAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolve local udp addr error: %v", err)
|
||||||
|
}
|
||||||
|
listenConn, err := net.ListenUDP("udp4", laddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listen local udp addr error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assistedAddrs := make([]string, 0, len(localIPs))
|
||||||
|
for _, ip := range localIPs {
|
||||||
|
assistedAddrs = append(assistedAddrs, net.JoinHostPort(ip, strconv.Itoa(laddr.Port)))
|
||||||
|
}
|
||||||
|
return &PrepareResult{
|
||||||
|
Addrs: addrs,
|
||||||
|
AssistedAddrs: assistedAddrs,
|
||||||
|
ListenConn: listenConn,
|
||||||
|
NatType: natFeature.NatType,
|
||||||
|
Behavior: natFeature.Behavior,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *Controller) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
|
// ExchangeInfo is used to exchange information between client and visitor.
|
||||||
clientCfg := &ClientCfg{
|
// 1. Send input message to server by msgTransporter.
|
||||||
Name: name,
|
// 2. Server will gather information from client and visitor and analyze it. Then send back a NatHoleResp message to them to tell them how to do next.
|
||||||
Sk: sk,
|
// 3. Receive NatHoleResp message from server.
|
||||||
SidCh: make(chan *SidRequest),
|
func ExchangeInfo(
|
||||||
|
ctx context.Context, transporter transport.MessageTransporter,
|
||||||
|
laneKey string, m msg.Message, timeout time.Duration,
|
||||||
|
) (*msg.NatHoleResp, error) {
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var natHoleRespMsg *msg.NatHoleResp
|
||||||
|
m, err := transporter.Do(timeoutCtx, m, laneKey, msg.TypeNameNatHoleResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get natHoleRespMsg error: %v", err)
|
||||||
}
|
}
|
||||||
nc.mu.Lock()
|
mm, ok := m.(*msg.NatHoleResp)
|
||||||
nc.clientCfgs[name] = clientCfg
|
if !ok {
|
||||||
nc.mu.Unlock()
|
return nil, fmt.Errorf("get natHoleRespMsg error: invalid message type")
|
||||||
return clientCfg.SidCh
|
}
|
||||||
|
natHoleRespMsg = mm
|
||||||
|
|
||||||
|
if natHoleRespMsg.Error != "" {
|
||||||
|
return nil, fmt.Errorf("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||||
|
}
|
||||||
|
if len(natHoleRespMsg.CandidateAddrs) == 0 {
|
||||||
|
return nil, fmt.Errorf("natHoleRespMsg get empty candidate addresses")
|
||||||
|
}
|
||||||
|
return natHoleRespMsg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *Controller) CloseClient(name string) {
|
// MakeHole is used to make a NAT hole between client and visitor.
|
||||||
nc.mu.Lock()
|
func MakeHole(ctx context.Context, listenConn *net.UDPConn, m *msg.NatHoleResp, key []byte) (*net.UDPConn, *net.UDPAddr, error) {
|
||||||
defer nc.mu.Unlock()
|
xl := xlog.FromContextSafe(ctx)
|
||||||
delete(nc.clientCfgs, name)
|
transactionID := NewTransactionID()
|
||||||
|
sendToRangePortsFunc := func(conn *net.UDPConn, addr string) error {
|
||||||
|
return sendSidMessage(ctx, conn, m.Sid, transactionID, addr, key, m.DetectBehavior.TTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
listenConns := []*net.UDPConn{listenConn}
|
||||||
|
var detectAddrs []string
|
||||||
|
if m.DetectBehavior.Role == DetectRoleSender {
|
||||||
|
// sender
|
||||||
|
if m.DetectBehavior.SendDelayMs > 0 {
|
||||||
|
time.Sleep(time.Duration(m.DetectBehavior.SendDelayMs) * time.Millisecond)
|
||||||
|
}
|
||||||
|
detectAddrs = m.AssistedAddrs
|
||||||
|
detectAddrs = append(detectAddrs, m.CandidateAddrs...)
|
||||||
|
} else {
|
||||||
|
// receiver
|
||||||
|
if len(m.DetectBehavior.CandidatePorts) == 0 {
|
||||||
|
detectAddrs = m.CandidateAddrs
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.DetectBehavior.ListenRandomPorts > 0 {
|
||||||
|
for i := 0; i < m.DetectBehavior.ListenRandomPorts; i++ {
|
||||||
|
tmpConn, err := net.ListenUDP("udp4", nil)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("listen random udp addr error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
listenConns = append(listenConns, tmpConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
detectAddrs = lo.Uniq(detectAddrs)
|
||||||
|
for _, detectAddr := range detectAddrs {
|
||||||
|
for _, conn := range listenConns {
|
||||||
|
if err := sendSidMessage(ctx, conn, m.Sid, transactionID, detectAddr, key, m.DetectBehavior.TTL); err != nil {
|
||||||
|
xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(m.DetectBehavior.CandidatePorts) > 0 {
|
||||||
|
for _, conn := range listenConns {
|
||||||
|
sendSidMessageToRangePorts(ctx, conn, m.CandidateAddrs, m.DetectBehavior.CandidatePorts, sendToRangePortsFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.DetectBehavior.SendRandomPorts > 0 {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
for i := range listenConns {
|
||||||
|
go sendSidMessageToRandomPorts(ctx, listenConns[i], m.CandidateAddrs, m.DetectBehavior.SendRandomPorts, sendToRangePortsFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := 5 * time.Second
|
||||||
|
if m.DetectBehavior.ReadTimeoutMs > 0 {
|
||||||
|
timeout = time.Duration(m.DetectBehavior.ReadTimeoutMs) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(listenConns) == 1 {
|
||||||
|
raddr, err := waitDetectMessage(ctx, listenConns[0], m.Sid, key, timeout, m.DetectBehavior.Role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("wait detect message error: %v", err)
|
||||||
|
}
|
||||||
|
return listenConns[0], raddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
lConn *net.UDPConn
|
||||||
|
raddr *net.UDPAddr
|
||||||
|
}
|
||||||
|
resultCh := make(chan result)
|
||||||
|
for _, conn := range listenConns {
|
||||||
|
go func(lConn *net.UDPConn) {
|
||||||
|
addr, err := waitDetectMessage(ctx, lConn, m.Sid, key, timeout, m.DetectBehavior.Role)
|
||||||
|
if err != nil {
|
||||||
|
lConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case resultCh <- result{lConn: lConn, raddr: addr}:
|
||||||
|
default:
|
||||||
|
lConn.Close()
|
||||||
|
}
|
||||||
|
}(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result := <-resultCh:
|
||||||
|
return result.lConn, result.raddr, nil
|
||||||
|
case <-time.After(timeout):
|
||||||
|
return nil, nil, fmt.Errorf("wait detect message timeout")
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, nil, fmt.Errorf("wait detect message canceled")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *Controller) Run() {
|
func waitDetectMessage(
|
||||||
|
ctx context.Context, conn *net.UDPConn, sid string, key []byte,
|
||||||
|
timeout time.Duration, role string,
|
||||||
|
) (*net.UDPAddr, error) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
for {
|
for {
|
||||||
buf := pool.GetBuf(1024)
|
buf := pool.GetBuf(1024)
|
||||||
n, raddr, err := nc.listener.ReadFromUDP(buf)
|
_ = conn.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
n, raddr, err := conn.ReadFromUDP(buf)
|
||||||
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("nat hole listener read from udp error: %v", err)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
plain, err := crypto.Decode(buf[:n], nc.encryptionKey)
|
xl.Debug("get udp message local %s, from %s", conn.LocalAddr(), raddr)
|
||||||
if err != nil {
|
var m msg.NatHoleSid
|
||||||
log.Warn("nathole listener decode from %s error: %v", raddr.String(), err)
|
if err := DecodeMessageInto(buf[:n], key, &m); err != nil {
|
||||||
continue
|
xl.Warn("decode sid message error: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
rawMsg, err := msg.ReadMsg(bytes.NewReader(plain))
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("read nat hole message error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch m := rawMsg.(type) {
|
|
||||||
case *msg.NatHoleBinding:
|
|
||||||
go nc.HandleBinding(m, raddr)
|
|
||||||
case *msg.NatHoleVisitor:
|
|
||||||
go nc.HandleVisitor(m, raddr)
|
|
||||||
case *msg.NatHoleClient:
|
|
||||||
go nc.HandleClient(m, raddr)
|
|
||||||
default:
|
|
||||||
log.Trace("unknown nat hole message type")
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pool.PutBuf(buf)
|
pool.PutBuf(buf)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nc *Controller) GenSid() string {
|
if m.Sid != sid {
|
||||||
t := time.Now().Unix()
|
xl.Warn("get sid message with wrong sid: %s, expect: %s", m.Sid, sid)
|
||||||
id, _ := util.RandID()
|
continue
|
||||||
return fmt.Sprintf("%d%s", t, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nc *Controller) HandleBinding(m *msg.NatHoleBinding, raddr *net.UDPAddr) {
|
|
||||||
log.Trace("handle binding message from %s", raddr.String())
|
|
||||||
resp := &msg.NatHoleBindingResp{
|
|
||||||
TransactionID: m.TransactionID,
|
|
||||||
Address: raddr.String(),
|
|
||||||
}
|
|
||||||
plain, err := msg.Pack(resp)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("pack nat hole binding response error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf, err := crypto.Encode(plain, nc.encryptionKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("encode nat hole binding response error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = nc.listener.WriteToUDP(buf, raddr)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("write nat hole binding response to %s error: %v", raddr.String(), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nc *Controller) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
|
|
||||||
sid := nc.GenSid()
|
|
||||||
session := &Session{
|
|
||||||
Sid: sid,
|
|
||||||
VisitorAddr: raddr,
|
|
||||||
NotifyCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
nc.mu.Lock()
|
|
||||||
clientCfg, ok := nc.clientCfgs[m.ProxyName]
|
|
||||||
if !ok {
|
|
||||||
nc.mu.Unlock()
|
|
||||||
errInfo := fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)
|
|
||||||
log.Debug(errInfo)
|
|
||||||
_, _ = nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
|
|
||||||
nc.mu.Unlock()
|
|
||||||
errInfo := fmt.Sprintf("xtcp connection of [%s] auth failed", m.ProxyName)
|
|
||||||
log.Debug(errInfo)
|
|
||||||
_, _ = nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nc.sessions[sid] = session
|
|
||||||
nc.mu.Unlock()
|
|
||||||
log.Trace("handle visitor message, sid [%s]", sid)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
nc.mu.Lock()
|
|
||||||
delete(nc.sessions, sid)
|
|
||||||
nc.mu.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
err := errors.PanicToError(func() {
|
|
||||||
clientCfg.SidCh <- &SidRequest{
|
|
||||||
Sid: sid,
|
|
||||||
NotifyCh: session.NotifyCh,
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
if !m.Response {
|
||||||
|
// only wait for response messages if we are a sender
|
||||||
|
if role == DetectRoleSender {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Response = true
|
||||||
|
buf2, err := EncodeMessage(&m, key)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("encode sid message error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, _ = conn.WriteToUDP(buf2, raddr)
|
||||||
|
}
|
||||||
|
return raddr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendSidMessage(
|
||||||
|
ctx context.Context, conn *net.UDPConn,
|
||||||
|
sid string, transactionID string, addr string, key []byte, ttl int,
|
||||||
|
) error {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
ttlStr := ""
|
||||||
|
if ttl > 0 {
|
||||||
|
ttlStr = fmt.Sprintf(" with ttl %d", ttl)
|
||||||
|
}
|
||||||
|
xl.Trace("send sid message from %s to %s%s", conn.LocalAddr(), addr, ttlStr)
|
||||||
|
raddr, err := net.ResolveUDPAddr("udp4", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
if transactionID == "" {
|
||||||
// Wait client connections.
|
transactionID = NewTransactionID()
|
||||||
select {
|
|
||||||
case <-session.NotifyCh:
|
|
||||||
resp := nc.GenNatHoleResponse(session, "")
|
|
||||||
log.Trace("send nat hole response to visitor")
|
|
||||||
_, _ = nc.listener.WriteToUDP(resp, raddr)
|
|
||||||
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
m := &msg.NatHoleSid{
|
||||||
|
TransactionID: transactionID,
|
||||||
func (nc *Controller) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
|
Sid: sid,
|
||||||
nc.mu.RLock()
|
Response: false,
|
||||||
session, ok := nc.sessions[m.Sid]
|
Nonce: strings.Repeat("0", rand.Intn(20)),
|
||||||
nc.mu.RUnlock()
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
log.Trace("handle client message, sid [%s]", session.Sid)
|
buf, err := EncodeMessage(m, key)
|
||||||
session.ClientAddr = raddr
|
|
||||||
|
|
||||||
resp := nc.GenNatHoleResponse(session, "")
|
|
||||||
log.Trace("send nat hole response to client")
|
|
||||||
_, _ = nc.listener.WriteToUDP(resp, raddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nc *Controller) GenNatHoleResponse(session *Session, errInfo string) []byte {
|
|
||||||
var (
|
|
||||||
sid string
|
|
||||||
visitorAddr string
|
|
||||||
clientAddr string
|
|
||||||
)
|
|
||||||
if session != nil {
|
|
||||||
sid = session.Sid
|
|
||||||
visitorAddr = session.VisitorAddr.String()
|
|
||||||
clientAddr = session.ClientAddr.String()
|
|
||||||
}
|
|
||||||
m := &msg.NatHoleResp{
|
|
||||||
Sid: sid,
|
|
||||||
VisitorAddr: visitorAddr,
|
|
||||||
ClientAddr: clientAddr,
|
|
||||||
Error: errInfo,
|
|
||||||
}
|
|
||||||
b := bytes.NewBuffer(nil)
|
|
||||||
err := msg.WriteMsg(b, m)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte("")
|
return err
|
||||||
}
|
}
|
||||||
return b.Bytes()
|
if ttl > 0 {
|
||||||
|
uConn := ipv4.NewConn(conn)
|
||||||
|
original, err := uConn.TTL()
|
||||||
|
if err != nil {
|
||||||
|
xl.Trace("get ttl error %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
xl.Trace("original ttl %d", original)
|
||||||
|
|
||||||
|
err = uConn.SetTTL(ttl)
|
||||||
|
if err != nil {
|
||||||
|
xl.Trace("set ttl error %v", err)
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
_ = uConn.SetTTL(original)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.WriteToUDP(buf, raddr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session struct {
|
func sendSidMessageToRangePorts(
|
||||||
Sid string
|
ctx context.Context, conn *net.UDPConn, addrs []string, ports []msg.PortsRange,
|
||||||
VisitorAddr *net.UDPAddr
|
sendFunc func(*net.UDPConn, string) error,
|
||||||
ClientAddr *net.UDPAddr
|
) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
NotifyCh chan struct{}
|
for _, ip := range lo.Uniq(parseIPs(addrs)) {
|
||||||
|
for _, portsRange := range ports {
|
||||||
|
for i := portsRange.From; i <= portsRange.To; i++ {
|
||||||
|
detectAddr := net.JoinHostPort(ip, strconv.Itoa(i))
|
||||||
|
if err := sendFunc(conn, detectAddr); err != nil {
|
||||||
|
xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
||||||
|
}
|
||||||
|
time.Sleep(2 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientCfg struct {
|
func sendSidMessageToRandomPorts(
|
||||||
Name string
|
ctx context.Context, conn *net.UDPConn, addrs []string, count int,
|
||||||
Sk string
|
sendFunc func(*net.UDPConn, string) error,
|
||||||
SidCh chan *SidRequest
|
) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
used := sets.New[int]()
|
||||||
|
getUnusedPort := func() int {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
port := rand.Intn(65535-1024) + 1024
|
||||||
|
if !used.Has(port) {
|
||||||
|
used.Insert(port)
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
port := getUnusedPort()
|
||||||
|
if port == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range lo.Uniq(parseIPs(addrs)) {
|
||||||
|
detectAddr := net.JoinHostPort(ip, strconv.Itoa(port))
|
||||||
|
if err := sendFunc(conn, detectAddr); err != nil {
|
||||||
|
xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIPs(addrs []string) []string {
|
||||||
|
var ips []string
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if ip, _, err := net.SplitHostPort(addr); err == nil {
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package nathole
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@ -63,3 +64,49 @@ func (s *ChangedAddress) GetFrom(m *stun.Message) error {
|
|||||||
func (s *ChangedAddress) String() string {
|
func (s *ChangedAddress) String() string {
|
||||||
return net.JoinHostPort(s.IP.String(), strconv.Itoa(s.Port))
|
return net.JoinHostPort(s.IP.String(), strconv.Itoa(s.Port))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListAllLocalIPs() ([]net.IP, error) {
|
||||||
|
addrs, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ips := make([]net.IP, 0, len(addrs))
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ip, _, err := net.ParseCIDR(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListLocalIPsForNatHole(max int) ([]string, error) {
|
||||||
|
if max <= 0 {
|
||||||
|
return nil, fmt.Errorf("max must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
ips, err := ListAllLocalIPs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := make([]string, 0, max)
|
||||||
|
for _, ip := range ips {
|
||||||
|
if len(filtered) >= max {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore ipv6 address
|
||||||
|
if ip.To4() == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// ignore localhost IP
|
||||||
|
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = append(filtered, ip.String())
|
||||||
|
}
|
||||||
|
return filtered, nil
|
||||||
|
}
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PluginHTTP2HTTPS = "http2https"
|
const PluginHTTP2HTTPS = "http2https"
|
||||||
@ -98,7 +98,7 @@ func NewHTTP2HTTPSPlugin(params map[string]string) (Plugin, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
_ = p.l.PutConn(wrapConn)
|
_ = p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,11 +21,13 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
gnet "github.com/fatedier/golib/net"
|
libnet "github.com/fatedier/golib/net"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PluginHTTPProxy = "http_proxy"
|
const PluginHTTPProxy = "http_proxy"
|
||||||
@ -67,9 +69,9 @@ func (hp *HTTPProxy) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
|
||||||
sc, rd := gnet.NewSharedConn(wrapConn)
|
sc, rd := libnet.NewSharedConn(wrapConn)
|
||||||
firstBytes := make([]byte, 7)
|
firstBytes := make([]byte, 7)
|
||||||
_, err := rd.Read(firstBytes)
|
_, err := rd.Read(firstBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -84,7 +86,7 @@ func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBuf
|
|||||||
wrapConn.Close()
|
wrapConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(bufRd, wrapConn, wrapConn.Close))
|
hp.handleConnectReq(request, libio.WrapReadWriteCloser(bufRd, wrapConn, wrapConn.Close))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +158,7 @@ func (hp *HTTPProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
_, _ = client.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
|
_, _ = client.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
|
||||||
|
|
||||||
go frpIo.Join(remote, client)
|
go libio.Join(remote, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HTTPProxy) Auth(req *http.Request) bool {
|
func (hp *HTTPProxy) Auth(req *http.Request) bool {
|
||||||
@ -179,7 +181,9 @@ func (hp *HTTPProxy) Auth(req *http.Request) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if pair[0] != hp.AuthUser || pair[1] != hp.AuthPasswd {
|
if !util.ConstantTimeEqString(pair[0], hp.AuthUser) ||
|
||||||
|
!util.ConstantTimeEqString(pair[1], hp.AuthPasswd) {
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -209,7 +213,7 @@ func (hp *HTTPProxy) handleConnectReq(req *http.Request, rwc io.ReadWriteCloser)
|
|||||||
}
|
}
|
||||||
_, _ = rwc.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
|
_, _ = rwc.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
|
||||||
|
|
||||||
frpIo.Join(remote, rwc)
|
libio.Join(remote, rwc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyHeaders(dst, src http.Header) {
|
func copyHeaders(dst, src http.Header) {
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PluginHTTPS2HTTP = "https2http"
|
const PluginHTTPS2HTTP = "https2http"
|
||||||
@ -123,7 +123,7 @@ func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
_ = p.l.PutConn(wrapConn)
|
_ = p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PluginHTTPS2HTTPS = "https2https"
|
const PluginHTTPS2HTTPS = "https2https"
|
||||||
@ -128,7 +128,7 @@ func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
_ = p.l.PutConn(wrapConn)
|
_ = p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
|
|
||||||
gosocks5 "github.com/armon/go-socks5"
|
gosocks5 "github.com/armon/go-socks5"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PluginSocks5 = "socks5"
|
const PluginSocks5 = "socks5"
|
||||||
@ -52,7 +52,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
|||||||
|
|
||||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
_ = sp.Server.ServeConn(wrapConn)
|
_ = sp.Server.ServeConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,10 +18,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PluginStaticFile = "static_file"
|
const PluginStaticFile = "static_file"
|
||||||
@ -64,8 +65,8 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
router.Use(frpNet.NewHTTPAuthMiddleware(httpUser, httpPasswd).Middleware)
|
router.Use(utilnet.NewHTTPAuthMiddleware(httpUser, httpPasswd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||||
router.PathPrefix(prefix).Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
|
router.PathPrefix(prefix).Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET")
|
||||||
sp.s = &http.Server{
|
sp.s = &http.Server{
|
||||||
Handler: router,
|
Handler: router,
|
||||||
}
|
}
|
||||||
@ -76,7 +77,7 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
|
||||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := utilnet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
_ = sp.l.PutConn(wrapConn)
|
_ = sp.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PluginUnixDomainSocket = "unix_domain_socket"
|
const PluginUnixDomainSocket = "unix_domain_socket"
|
||||||
@ -62,7 +62,7 @@ func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn net.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frpIo.Join(localConn, conn)
|
libio.Join(localConn, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uds *UnixDomainSocketPlugin) Name() string {
|
func (uds *UnixDomainSocketPlugin) Name() string {
|
||||||
|
119
pkg/transport/message.go
Normal file
119
pkg/transport/message.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MessageTransporter interface {
|
||||||
|
Send(msg.Message) error
|
||||||
|
// Recv(ctx context.Context, laneKey string, msgType string) (Message, error)
|
||||||
|
// Do will first send msg, then recv msg with the same laneKey and specified msgType.
|
||||||
|
Do(ctx context.Context, req msg.Message, laneKey, recvMsgType string) (msg.Message, error)
|
||||||
|
Dispatch(m msg.Message, laneKey string) bool
|
||||||
|
DispatchWithType(m msg.Message, msgType, laneKey string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMessageTransporter(sendCh chan msg.Message) MessageTransporter {
|
||||||
|
return &transporterImpl{
|
||||||
|
sendCh: sendCh,
|
||||||
|
registry: make(map[string]map[string]chan msg.Message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type transporterImpl struct {
|
||||||
|
sendCh chan msg.Message
|
||||||
|
|
||||||
|
// First key is message type and second key is lane key.
|
||||||
|
// Dispatch will dispatch message to releated channel by its message type
|
||||||
|
// and lane key.
|
||||||
|
registry map[string]map[string]chan msg.Message
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (impl *transporterImpl) Send(m msg.Message) error {
|
||||||
|
return errors.PanicToError(func() {
|
||||||
|
impl.sendCh <- m
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (impl *transporterImpl) Do(ctx context.Context, req msg.Message, laneKey, recvMsgType string) (msg.Message, error) {
|
||||||
|
ch := make(chan msg.Message, 1)
|
||||||
|
defer close(ch)
|
||||||
|
unregisterFn := impl.registerMsgChan(ch, laneKey, recvMsgType)
|
||||||
|
defer unregisterFn()
|
||||||
|
|
||||||
|
if err := impl.Send(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case resp := <-ch:
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (impl *transporterImpl) DispatchWithType(m msg.Message, msgType, laneKey string) bool {
|
||||||
|
var ch chan msg.Message
|
||||||
|
impl.mu.RLock()
|
||||||
|
byLaneKey, ok := impl.registry[msgType]
|
||||||
|
if ok {
|
||||||
|
ch = byLaneKey[laneKey]
|
||||||
|
}
|
||||||
|
impl.mu.RUnlock()
|
||||||
|
|
||||||
|
if ch == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := errors.PanicToError(func() {
|
||||||
|
ch <- m
|
||||||
|
}); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (impl *transporterImpl) Dispatch(m msg.Message, laneKey string) bool {
|
||||||
|
msgType := reflect.TypeOf(m).Elem().Name()
|
||||||
|
return impl.DispatchWithType(m, msgType, laneKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (impl *transporterImpl) registerMsgChan(recvCh chan msg.Message, laneKey string, msgType string) (unregister func()) {
|
||||||
|
impl.mu.Lock()
|
||||||
|
byLaneKey, ok := impl.registry[msgType]
|
||||||
|
if !ok {
|
||||||
|
byLaneKey = make(map[string]chan msg.Message)
|
||||||
|
impl.registry[msgType] = byLaneKey
|
||||||
|
}
|
||||||
|
byLaneKey[laneKey] = recvCh
|
||||||
|
impl.mu.Unlock()
|
||||||
|
|
||||||
|
unregister = func() {
|
||||||
|
impl.mu.Lock()
|
||||||
|
delete(byLaneKey, laneKey)
|
||||||
|
impl.mu.Unlock()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -1,3 +1,17 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package transport
|
package transport
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -19,6 +19,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPAuthWraper struct {
|
type HTTPAuthWraper struct {
|
||||||
@ -46,8 +49,9 @@ func (aw *HTTPAuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HTTPAuthMiddleware struct {
|
type HTTPAuthMiddleware struct {
|
||||||
user string
|
user string
|
||||||
passwd string
|
passwd string
|
||||||
|
authFailDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPAuthMiddleware(user, passwd string) *HTTPAuthMiddleware {
|
func NewHTTPAuthMiddleware(user, passwd string) *HTTPAuthMiddleware {
|
||||||
@ -57,32 +61,28 @@ func NewHTTPAuthMiddleware(user, passwd string) *HTTPAuthMiddleware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (authMid *HTTPAuthMiddleware) SetAuthFailDelay(delay time.Duration) *HTTPAuthMiddleware {
|
||||||
|
authMid.authFailDelay = delay
|
||||||
|
return authMid
|
||||||
|
}
|
||||||
|
|
||||||
func (authMid *HTTPAuthMiddleware) Middleware(next http.Handler) http.Handler {
|
func (authMid *HTTPAuthMiddleware) Middleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
reqUser, reqPasswd, hasAuth := r.BasicAuth()
|
reqUser, reqPasswd, hasAuth := r.BasicAuth()
|
||||||
if (authMid.user == "" && authMid.passwd == "") ||
|
if (authMid.user == "" && authMid.passwd == "") ||
|
||||||
(hasAuth && reqUser == authMid.user && reqPasswd == authMid.passwd) {
|
(hasAuth && util.ConstantTimeEqString(reqUser, authMid.user) &&
|
||||||
|
util.ConstantTimeEqString(reqPasswd, authMid.passwd)) {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
} else {
|
} else {
|
||||||
|
if authMid.authFailDelay > 0 {
|
||||||
|
time.Sleep(authMid.authFailDelay)
|
||||||
|
}
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func HTTPBasicAuth(h http.HandlerFunc, user, passwd string) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
reqUser, reqPasswd, hasAuth := r.BasicAuth()
|
|
||||||
if (user == "" && passwd == "") ||
|
|
||||||
(hasAuth && reqUser == user && reqPasswd == passwd) {
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPGzipWraper struct {
|
type HTTPGzipWraper struct {
|
||||||
h http.Handler
|
h http.Handler
|
||||||
}
|
}
|
||||||
|
@ -22,20 +22,21 @@ import (
|
|||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Custom listener
|
// InternalListener is a listener that can be used to accept connections from
|
||||||
type CustomListener struct {
|
// other goroutines.
|
||||||
|
type InternalListener struct {
|
||||||
acceptCh chan net.Conn
|
acceptCh chan net.Conn
|
||||||
closed bool
|
closed bool
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCustomListener() *CustomListener {
|
func NewInternalListener() *InternalListener {
|
||||||
return &CustomListener{
|
return &InternalListener{
|
||||||
acceptCh: make(chan net.Conn, 64),
|
acceptCh: make(chan net.Conn, 128),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CustomListener) Accept() (net.Conn, error) {
|
func (l *InternalListener) Accept() (net.Conn, error) {
|
||||||
conn, ok := <-l.acceptCh
|
conn, ok := <-l.acceptCh
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("listener closed")
|
return nil, fmt.Errorf("listener closed")
|
||||||
@ -43,7 +44,7 @@ func (l *CustomListener) Accept() (net.Conn, error) {
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CustomListener) PutConn(conn net.Conn) error {
|
func (l *InternalListener) PutConn(conn net.Conn) error {
|
||||||
err := errors.PanicToError(func() {
|
err := errors.PanicToError(func() {
|
||||||
select {
|
select {
|
||||||
case l.acceptCh <- conn:
|
case l.acceptCh <- conn:
|
||||||
@ -54,7 +55,7 @@ func (l *CustomListener) PutConn(conn net.Conn) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CustomListener) Close() error {
|
func (l *InternalListener) Close() error {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
if !l.closed {
|
if !l.closed {
|
||||||
@ -64,6 +65,16 @@ func (l *CustomListener) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CustomListener) Addr() net.Addr {
|
func (l *InternalListener) Addr() net.Addr {
|
||||||
return (*net.TCPAddr)(nil)
|
return &InternalAddr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalAddr struct{}
|
||||||
|
|
||||||
|
func (ia *InternalAddr) Network() string {
|
||||||
|
return "internal"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ia *InternalAddr) String() string {
|
||||||
|
return "internal"
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
gnet "github.com/fatedier/golib/net"
|
libnet "github.com/fatedier/golib/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
var FRPTLSHeadByte = 0x17
|
var FRPTLSHeadByte = 0x17
|
||||||
@ -28,7 +28,7 @@ var FRPTLSHeadByte = 0x17
|
|||||||
func CheckAndEnableTLSServerConnWithTimeout(
|
func CheckAndEnableTLSServerConnWithTimeout(
|
||||||
c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration,
|
c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration,
|
||||||
) (out net.Conn, isTLS bool, custom bool, err error) {
|
) (out net.Conn, isTLS bool, custom bool, err error) {
|
||||||
sc, r := gnet.NewSharedConnSize(c, 2)
|
sc, r := libnet.NewSharedConnSize(c, 2)
|
||||||
buf := make([]byte, 1)
|
buf := make([]byte, 1)
|
||||||
var n int
|
var n int
|
||||||
_ = c.SetReadDeadline(time.Now().Add(timeout))
|
_ = c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
@ -256,3 +256,11 @@ func (l *UDPListener) Close() error {
|
|||||||
func (l *UDPListener) Addr() net.Addr {
|
func (l *UDPListener) Addr() net.Addr {
|
||||||
return l.addr
|
return l.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConnectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls
|
||||||
|
// to Write syscalls that are 4 times faster on some OS'es. This should only be
|
||||||
|
// used for connections that were produced by a net.Dial* call.
|
||||||
|
type ConnectedUDPConn struct{ *net.UDPConn }
|
||||||
|
|
||||||
|
// WriteTo redirects all writes to the Write syscall, which is 4 times faster.
|
||||||
|
func (c *ConnectedUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { return c.Write(b) }
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
gnet "github.com/fatedier/golib/net"
|
libnet "github.com/fatedier/golib/net"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
"github.com/fatedier/frp/pkg/util/vhost"
|
"github.com/fatedier/frp/pkg/util/vhost"
|
||||||
@ -94,7 +94,7 @@ func (muxer *HTTPConnectTCPMuxer) auth(c net.Conn, username, password string, re
|
|||||||
|
|
||||||
func (muxer *HTTPConnectTCPMuxer) getHostFromHTTPConnect(c net.Conn) (net.Conn, map[string]string, error) {
|
func (muxer *HTTPConnectTCPMuxer) getHostFromHTTPConnect(c net.Conn) (net.Conn, map[string]string, error) {
|
||||||
reqInfoMap := make(map[string]string, 0)
|
reqInfoMap := make(map[string]string, 0)
|
||||||
sc, rd := gnet.NewSharedConn(c)
|
sc, rd := libnet.NewSharedConn(c)
|
||||||
|
|
||||||
host, httpUser, httpPwd, err := muxer.readHTTPConnectRequest(rd)
|
host, httpUser, httpPwd, err := muxer.readHTTPConnectRequest(rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
func InSlice[T comparable](v T, s []T) bool {
|
|
||||||
for _, vv := range s {
|
|
||||||
if v == vv {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func InSliceAny[T any](v T, s []T, equalFn func(a, b T) bool) bool {
|
|
||||||
for _, vv := range s {
|
|
||||||
if equalFn(v, vv) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func InSliceAnyFunc[T any](equalFn func(a, b T) bool) func(v T, s []T) bool {
|
|
||||||
return func(v T, s []T) bool {
|
|
||||||
return InSliceAny(v, s, equalFn)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInSlice(t *testing.T) {
|
|
||||||
require := require.New(t)
|
|
||||||
require.True(InSlice(1, []int{1, 2, 3}))
|
|
||||||
require.False(InSlice(0, []int{1, 2, 3}))
|
|
||||||
require.True(InSlice("foo", []string{"foo", "bar"}))
|
|
||||||
require.False(InSlice("not exist", []string{"foo", "bar"}))
|
|
||||||
}
|
|
||||||
|
|
||||||
type testStructA struct {
|
|
||||||
Name string
|
|
||||||
Age int
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInSliceAny(t *testing.T) {
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
a := testStructA{Name: "foo", Age: 20}
|
|
||||||
b := testStructA{Name: "foo", Age: 30}
|
|
||||||
c := testStructA{Name: "bar", Age: 20}
|
|
||||||
|
|
||||||
equalFn := func(o, p testStructA) bool {
|
|
||||||
return o.Name == p.Name
|
|
||||||
}
|
|
||||||
require.True(InSliceAny(a, []testStructA{b, c}, equalFn))
|
|
||||||
require.False(InSliceAny(c, []testStructA{a, b}, equalFn))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInSliceAnyFunc(t *testing.T) {
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
a := testStructA{Name: "foo", Age: 20}
|
|
||||||
b := testStructA{Name: "foo", Age: 30}
|
|
||||||
c := testStructA{Name: "bar", Age: 20}
|
|
||||||
|
|
||||||
equalFn := func(o, p testStructA) bool {
|
|
||||||
return o.Name == p.Name
|
|
||||||
}
|
|
||||||
testStructAInSlice := InSliceAnyFunc(equalFn)
|
|
||||||
require.True(testStructAInSlice(a, []testStructA{b, c}))
|
|
||||||
require.False(testStructAInSlice(c, []testStructA{a, b}))
|
|
||||||
}
|
|
@ -17,6 +17,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/subtle"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
mathrand "math/rand"
|
mathrand "math/rand"
|
||||||
@ -28,19 +29,32 @@ import (
|
|||||||
|
|
||||||
// RandID return a rand string used in frp.
|
// RandID return a rand string used in frp.
|
||||||
func RandID() (id string, err error) {
|
func RandID() (id string, err error) {
|
||||||
return RandIDWithLen(8)
|
return RandIDWithLen(16)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RandIDWithLen return a rand string with idLen length.
|
// RandIDWithLen return a rand string with idLen length.
|
||||||
func RandIDWithLen(idLen int) (id string, err error) {
|
func RandIDWithLen(idLen int) (id string, err error) {
|
||||||
b := make([]byte, idLen)
|
if idLen <= 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
b := make([]byte, idLen/2+1)
|
||||||
_, err = rand.Read(b)
|
_, err = rand.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id = fmt.Sprintf("%x", b)
|
id = fmt.Sprintf("%x", b)
|
||||||
return
|
return id[:idLen], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandIDWithRandLen return a rand string with length between [start, end).
|
||||||
|
func RandIDWithRandLen(start, end int) (id string, err error) {
|
||||||
|
if start >= end {
|
||||||
|
err = fmt.Errorf("start should be less than end")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
idLen := mathrand.Intn(end-start) + start
|
||||||
|
return RandIDWithLen(idLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAuthKey(token string, timestamp int64) (key string) {
|
func GetAuthKey(token string, timestamp int64) (key string) {
|
||||||
@ -126,3 +140,7 @@ func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Durati
|
|||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConstantTimeEqString(a, b string) bool {
|
||||||
|
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
|
||||||
|
}
|
||||||
|
@ -14,10 +14,51 @@ func TestRandId(t *testing.T) {
|
|||||||
assert.Equal(16, len(id))
|
assert.Equal(16, len(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRandIDWithRandLen(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
start int
|
||||||
|
end int
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "start and end are equal",
|
||||||
|
start: 5,
|
||||||
|
end: 5,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "start is less than end",
|
||||||
|
start: 5,
|
||||||
|
end: 10,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "start is greater than end",
|
||||||
|
start: 10,
|
||||||
|
end: 5,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
id, err := RandIDWithRandLen(tt.start, tt.end)
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.GreaterOrEqual(len(id), tt.start)
|
||||||
|
assert.Less(len(id), tt.end)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetAuthKey(t *testing.T) {
|
func TestGetAuthKey(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
key := GetAuthKey("1234", 1488720000)
|
key := GetAuthKey("1234", 1488720000)
|
||||||
t.Log(key)
|
|
||||||
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
|
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "0.48.0"
|
var version = "0.49.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
@ -28,7 +28,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
|
|
||||||
frpLog "github.com/fatedier/frp/pkg/util/log"
|
frpLog "github.com/fatedier/frp/pkg/util/log"
|
||||||
@ -256,7 +256,7 @@ func (rp *HTTPReverseProxy) connectHandler(rw http.ResponseWriter, req *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = req.Write(remote)
|
_ = req.Write(remote)
|
||||||
go frpIo.Join(remote, client)
|
go libio.Join(remote, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBasicAuth(auth string) (username, password string, ok bool) {
|
func parseBasicAuth(auth string) (username, password string, ok bool) {
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
gnet "github.com/fatedier/golib/net"
|
libnet "github.com/fatedier/golib/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPSMuxer struct {
|
type HTTPSMuxer struct {
|
||||||
@ -37,7 +37,7 @@ func NewHTTPSMuxer(listener net.Listener, timeout time.Duration) (*HTTPSMuxer, e
|
|||||||
|
|
||||||
func GetHTTPSHostname(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
|
func GetHTTPSHostname(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
|
||||||
reqInfoMap := make(map[string]string, 0)
|
reqInfoMap := make(map[string]string, 0)
|
||||||
sc, rd := gnet.NewSharedConn(c)
|
sc, rd := libnet.NewSharedConn(c)
|
||||||
|
|
||||||
clientHello, err := readClientHello(rd)
|
clientHello, err := readClientHello(rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -282,7 +282,7 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
xl.Debug("rewrite host to [%s] success", l.rewriteHost)
|
xl.Debug("rewrite host to [%s] success", l.rewriteHost)
|
||||||
conn = sConn
|
conn = sConn
|
||||||
}
|
}
|
||||||
return frpNet.NewContextConn(l.ctx, conn), nil
|
return utilnet.NewContextConn(l.ctx, conn), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) Close() error {
|
func (l *Listener) Close() error {
|
||||||
|
@ -30,9 +30,10 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
"github.com/fatedier/frp/pkg/consts"
|
||||||
frpErr "github.com/fatedier/frp/pkg/errors"
|
pkgerr "github.com/fatedier/frp/pkg/errors"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"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"
|
||||||
@ -82,6 +83,16 @@ func (cm *ControlManager) GetByID(runID string) (ctl *Control, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cm *ControlManager) Close() error {
|
||||||
|
cm.mu.Lock()
|
||||||
|
defer cm.mu.Unlock()
|
||||||
|
for _, ctl := range cm.ctlsByRunID {
|
||||||
|
ctl.Close()
|
||||||
|
}
|
||||||
|
cm.ctlsByRunID = make(map[string]*Control)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Control struct {
|
type Control struct {
|
||||||
// all resource managers and controllers
|
// all resource managers and controllers
|
||||||
rc *controller.ResourceController
|
rc *controller.ResourceController
|
||||||
@ -95,6 +106,9 @@ type Control struct {
|
|||||||
// verifies authentication based on selected method
|
// verifies authentication based on selected method
|
||||||
authVerifier auth.Verifier
|
authVerifier auth.Verifier
|
||||||
|
|
||||||
|
// other components can use this to communicate with client
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
|
|
||||||
// login message
|
// login message
|
||||||
loginMsg *msg.Login
|
loginMsg *msg.Login
|
||||||
|
|
||||||
@ -158,7 +172,7 @@ func NewControl(
|
|||||||
if poolCount > int(serverCfg.MaxPoolCount) {
|
if poolCount > int(serverCfg.MaxPoolCount) {
|
||||||
poolCount = int(serverCfg.MaxPoolCount)
|
poolCount = int(serverCfg.MaxPoolCount)
|
||||||
}
|
}
|
||||||
return &Control{
|
ctl := &Control{
|
||||||
rc: rc,
|
rc: rc,
|
||||||
pxyManager: pxyManager,
|
pxyManager: pxyManager,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
@ -182,15 +196,16 @@ func NewControl(
|
|||||||
xl: xlog.FromContextSafe(ctx),
|
xl: xlog.FromContextSafe(ctx),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
|
ctl.msgTransporter = transport.NewMessageTransporter(ctl.sendCh)
|
||||||
|
return ctl
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start send a login success message to client and start working.
|
// Start send a login success message to client and start working.
|
||||||
func (ctl *Control) Start() {
|
func (ctl *Control) Start() {
|
||||||
loginRespMsg := &msg.LoginResp{
|
loginRespMsg := &msg.LoginResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
RunID: ctl.runID,
|
RunID: ctl.runID,
|
||||||
ServerUDPPort: ctl.serverCfg.BindUDPPort,
|
Error: "",
|
||||||
Error: "",
|
|
||||||
}
|
}
|
||||||
_ = msg.WriteMsg(ctl.conn, loginRespMsg)
|
_ = msg.WriteMsg(ctl.conn, loginRespMsg)
|
||||||
|
|
||||||
@ -204,6 +219,18 @@ func (ctl *Control) Start() {
|
|||||||
go ctl.stoper()
|
go ctl.stoper()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) Close() error {
|
||||||
|
ctl.allShutdown.Start()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) Replaced(newCtl *Control) {
|
||||||
|
xl := ctl.xl
|
||||||
|
xl.Info("Replaced by client [%s]", newCtl.runID)
|
||||||
|
ctl.runID = ""
|
||||||
|
ctl.allShutdown.Start()
|
||||||
|
}
|
||||||
|
|
||||||
func (ctl *Control) RegisterWorkConn(conn net.Conn) error {
|
func (ctl *Control) RegisterWorkConn(conn net.Conn) error {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -241,7 +268,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
|||||||
select {
|
select {
|
||||||
case workConn, ok = <-ctl.workConnCh:
|
case workConn, ok = <-ctl.workConnCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
err = frpErr.ErrCtlClosed
|
err = pkgerr.ErrCtlClosed
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
xl.Debug("get work connection from pool")
|
xl.Debug("get work connection from pool")
|
||||||
@ -256,7 +283,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
|||||||
select {
|
select {
|
||||||
case workConn, ok = <-ctl.workConnCh:
|
case workConn, ok = <-ctl.workConnCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
err = frpErr.ErrCtlClosed
|
err = pkgerr.ErrCtlClosed
|
||||||
xl.Warn("no work connections available, %v", err)
|
xl.Warn("no work connections available, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -275,13 +302,6 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) Replaced(newCtl *Control) {
|
|
||||||
xl := ctl.xl
|
|
||||||
xl.Info("Replaced by client [%s]", newCtl.runID)
|
|
||||||
ctl.runID = ""
|
|
||||||
ctl.allShutdown.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctl *Control) writer() {
|
func (ctl *Control) writer() {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -374,7 +394,7 @@ func (ctl *Control) stoper() {
|
|||||||
for _, pxy := range ctl.proxies {
|
for _, pxy := range ctl.proxies {
|
||||||
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().GetBaseConfig().ProxyType)
|
||||||
|
|
||||||
notifyContent := &plugin.CloseProxyContent{
|
notifyContent := &plugin.CloseProxyContent{
|
||||||
User: plugin.UserInfo{
|
User: plugin.UserInfo{
|
||||||
@ -465,6 +485,12 @@ func (ctl *Control) manager() {
|
|||||||
metrics.Server.NewProxy(m.ProxyName, m.ProxyType)
|
metrics.Server.NewProxy(m.ProxyName, m.ProxyType)
|
||||||
}
|
}
|
||||||
ctl.sendCh <- resp
|
ctl.sendCh <- resp
|
||||||
|
case *msg.NatHoleVisitor:
|
||||||
|
go ctl.HandleNatHoleVisitor(m)
|
||||||
|
case *msg.NatHoleClient:
|
||||||
|
go ctl.HandleNatHoleClient(m)
|
||||||
|
case *msg.NatHoleReport:
|
||||||
|
go ctl.HandleNatHoleReport(m)
|
||||||
case *msg.CloseProxy:
|
case *msg.CloseProxy:
|
||||||
_ = ctl.CloseProxy(m)
|
_ = ctl.CloseProxy(m)
|
||||||
xl.Info("close proxy [%s] success", m.ProxyName)
|
xl.Info("close proxy [%s] success", m.ProxyName)
|
||||||
@ -497,6 +523,18 @@ func (ctl *Control) manager() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) HandleNatHoleVisitor(m *msg.NatHoleVisitor) {
|
||||||
|
ctl.rc.NatHoleController.HandleVisitor(m, ctl.msgTransporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) HandleNatHoleClient(m *msg.NatHoleClient) {
|
||||||
|
ctl.rc.NatHoleController.HandleClient(m, ctl.msgTransporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) HandleNatHoleReport(m *msg.NatHoleReport) {
|
||||||
|
ctl.rc.NatHoleController.HandleReport(m)
|
||||||
|
}
|
||||||
|
|
||||||
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
||||||
var pxyConf config.ProxyConf
|
var pxyConf config.ProxyConf
|
||||||
// Load configures from NewProxy message and check.
|
// Load configures from NewProxy message and check.
|
||||||
@ -576,7 +614,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
|||||||
delete(ctl.proxies, closeMsg.ProxyName)
|
delete(ctl.proxies, closeMsg.ProxyName)
|
||||||
ctl.mu.Unlock()
|
ctl.mu.Unlock()
|
||||||
|
|
||||||
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
metrics.Server.CloseProxy(pxy.GetName(), pxy.GetConf().GetBaseConfig().ProxyType)
|
||||||
|
|
||||||
notifyContent := &plugin.CloseProxyContent{
|
notifyContent := &plugin.CloseProxyContent{
|
||||||
User: plugin.UserInfo{
|
User: plugin.UserInfo{
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -50,7 +50,7 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
|
|||||||
subRouter := router.NewRoute().Subrouter()
|
subRouter := router.NewRoute().Subrouter()
|
||||||
|
|
||||||
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
|
user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
|
||||||
subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
|
subRouter.Use(utilnet.NewHTTPAuthMiddleware(user, passwd).SetAuthFailDelay(200 * time.Millisecond).Middleware)
|
||||||
|
|
||||||
// metrics
|
// metrics
|
||||||
if svr.cfg.EnablePrometheus {
|
if svr.cfg.EnablePrometheus {
|
||||||
@ -65,7 +65,7 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
|
|||||||
|
|
||||||
// view
|
// view
|
||||||
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||||
subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
subRouter.PathPrefix("/static/").Handler(utilnet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||||
|
|
||||||
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||||
|
@ -35,7 +35,6 @@ type GeneralResponse struct {
|
|||||||
type serverInfoResp struct {
|
type serverInfoResp struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
BindPort int `json:"bind_port"`
|
BindPort int `json:"bind_port"`
|
||||||
BindUDPPort int `json:"bind_udp_port"`
|
|
||||||
VhostHTTPPort int `json:"vhost_http_port"`
|
VhostHTTPPort int `json:"vhost_http_port"`
|
||||||
VhostHTTPSPort int `json:"vhost_https_port"`
|
VhostHTTPSPort int `json:"vhost_https_port"`
|
||||||
TCPMuxHTTPConnectPort int `json:"tcpmux_httpconnect_port"`
|
TCPMuxHTTPConnectPort int `json:"tcpmux_httpconnect_port"`
|
||||||
@ -76,7 +75,6 @@ func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
svrResp := serverInfoResp{
|
svrResp := serverInfoResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
BindPort: svr.cfg.BindPort,
|
BindPort: svr.cfg.BindPort,
|
||||||
BindUDPPort: svr.cfg.BindUDPPort,
|
|
||||||
VhostHTTPPort: svr.cfg.VhostHTTPPort,
|
VhostHTTPPort: svr.cfg.VhostHTTPPort,
|
||||||
VhostHTTPSPort: svr.cfg.VhostHTTPSPort,
|
VhostHTTPSPort: svr.cfg.VhostHTTPSPort,
|
||||||
TCPMuxHTTPConnectPort: svr.cfg.TCPMuxHTTPConnectPort,
|
TCPMuxHTTPConnectPort: svr.cfg.TCPMuxHTTPConnectPort,
|
||||||
|
@ -19,12 +19,12 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/util/limit"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
"github.com/fatedier/frp/pkg/util/vhost"
|
"github.com/fatedier/frp/pkg/util/vhost"
|
||||||
"github.com/fatedier/frp/server/metrics"
|
"github.com/fatedier/frp/server/metrics"
|
||||||
@ -157,31 +157,31 @@ func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err err
|
|||||||
|
|
||||||
var rwc io.ReadWriteCloser = tmpConn
|
var rwc io.ReadWriteCloser = tmpConn
|
||||||
if pxy.cfg.UseEncryption {
|
if pxy.cfg.UseEncryption {
|
||||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
|
rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if pxy.cfg.UseCompression {
|
if pxy.cfg.UseCompression {
|
||||||
rwc = frpIo.WithCompression(rwc)
|
rwc = libio.WithCompression(rwc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pxy.GetLimiter() != nil {
|
if pxy.GetLimiter() != nil {
|
||||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(rwc, pxy.GetLimiter()), limit.NewWriter(rwc, pxy.GetLimiter()), func() error {
|
rwc = libio.WrapReadWriteCloser(limit.NewReader(rwc, pxy.GetLimiter()), limit.NewWriter(rwc, pxy.GetLimiter()), func() error {
|
||||||
return rwc.Close()
|
return rwc.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
|
workConn = utilnet.WrapReadWriteCloserToConn(rwc, tmpConn)
|
||||||
workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
|
workConn = utilnet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
|
||||||
metrics.Server.OpenConnection(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
metrics.Server.OpenConnection(pxy.GetName(), pxy.GetConf().GetBaseConfig().ProxyType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HTTPProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
|
func (pxy *HTTPProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) {
|
||||||
name := pxy.GetName()
|
name := pxy.GetName()
|
||||||
proxyType := pxy.GetConf().GetBaseInfo().ProxyType
|
proxyType := pxy.GetConf().GetBaseConfig().ProxyType
|
||||||
metrics.Server.CloseConnection(name, proxyType)
|
metrics.Server.CloseConnection(name, proxyType)
|
||||||
metrics.Server.AddTrafficIn(name, proxyType, totalWrite)
|
metrics.Server.AddTrafficIn(name, proxyType, totalWrite)
|
||||||
metrics.Server.AddTrafficOut(name, proxyType, totalRead)
|
metrics.Server.AddTrafficOut(name, proxyType, totalRead)
|
||||||
|
@ -23,14 +23,14 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||||
"github.com/fatedier/frp/pkg/util/limit"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
"github.com/fatedier/frp/server/controller"
|
"github.com/fatedier/frp/server/controller"
|
||||||
"github.com/fatedier/frp/server/metrics"
|
"github.com/fatedier/frp/server/metrics"
|
||||||
@ -113,7 +113,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn,
|
|||||||
}
|
}
|
||||||
xl.Debug("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
xl.Debug("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
||||||
xl.Spawn().AppendPrefix(pxy.GetName())
|
xl.Spawn().AppendPrefix(pxy.GetName())
|
||||||
workConn = frpNet.NewContextConn(pxy.ctx, workConn)
|
workConn = utilnet.NewContextConn(pxy.ctx, workConn)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
srcAddr string
|
srcAddr string
|
||||||
@ -156,7 +156,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startListenHandler start a goroutine handler for each listener.
|
// startListenHandler start a goroutine handler for each listener.
|
||||||
// p: p will just be passed to handler(Proxy, frpNet.Conn).
|
// p: p will just be passed to handler(Proxy, utilnet.Conn).
|
||||||
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
|
||||||
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn, config.ServerCommonConf)) {
|
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn, config.ServerCommonConf)) {
|
||||||
xl := xlog.FromContextSafe(pxy.ctx)
|
xl := xlog.FromContextSafe(pxy.ctx)
|
||||||
@ -196,16 +196,16 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn,
|
|||||||
func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.ResourceController, poolCount int,
|
func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.ResourceController, poolCount int,
|
||||||
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf, loginMsg *msg.Login,
|
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf, loginMsg *msg.Login,
|
||||||
) (pxy Proxy, err error) {
|
) (pxy Proxy, err error) {
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseInfo().ProxyName)
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseConfig().ProxyName)
|
||||||
|
|
||||||
var limiter *rate.Limiter
|
var limiter *rate.Limiter
|
||||||
limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
|
limitBytes := pxyConf.GetBaseConfig().BandwidthLimit.Bytes()
|
||||||
if limitBytes > 0 && pxyConf.GetBaseInfo().BandwidthLimitMode == config.BandwidthLimitModeServer {
|
if limitBytes > 0 && pxyConf.GetBaseConfig().BandwidthLimitMode == config.BandwidthLimitModeServer {
|
||||||
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
basePxy := BaseProxy{
|
basePxy := BaseProxy{
|
||||||
name: pxyConf.GetBaseInfo().ProxyName,
|
name: pxyConf.GetBaseConfig().ProxyName,
|
||||||
rc: rc,
|
rc: rc,
|
||||||
listeners: make([]net.Listener, 0),
|
listeners: make([]net.Listener, 0),
|
||||||
poolCount: poolCount,
|
poolCount: poolCount,
|
||||||
@ -277,7 +277,7 @@ func HandleUserTCPConnection(pxy Proxy, userConn net.Conn, serverCfg config.Serv
|
|||||||
content := &plugin.NewUserConnContent{
|
content := &plugin.NewUserConnContent{
|
||||||
User: pxy.GetUserInfo(),
|
User: pxy.GetUserInfo(),
|
||||||
ProxyName: pxy.GetName(),
|
ProxyName: pxy.GetName(),
|
||||||
ProxyType: pxy.GetConf().GetBaseInfo().ProxyType,
|
ProxyType: pxy.GetConf().GetBaseConfig().ProxyType,
|
||||||
RemoteAddr: userConn.RemoteAddr().String(),
|
RemoteAddr: userConn.RemoteAddr().String(),
|
||||||
}
|
}
|
||||||
_, err := rc.PluginManager.NewUserConn(content)
|
_, err := rc.PluginManager.NewUserConn(content)
|
||||||
@ -294,21 +294,21 @@ func HandleUserTCPConnection(pxy Proxy, userConn net.Conn, serverCfg config.Serv
|
|||||||
defer workConn.Close()
|
defer workConn.Close()
|
||||||
|
|
||||||
var local io.ReadWriteCloser = workConn
|
var local io.ReadWriteCloser = workConn
|
||||||
cfg := pxy.GetConf().GetBaseInfo()
|
cfg := pxy.GetConf().GetBaseConfig()
|
||||||
xl.Trace("handler user tcp connection, use_encryption: %t, use_compression: %t", cfg.UseEncryption, cfg.UseCompression)
|
xl.Trace("handler user tcp connection, use_encryption: %t, use_compression: %t", cfg.UseEncryption, cfg.UseCompression)
|
||||||
if cfg.UseEncryption {
|
if cfg.UseEncryption {
|
||||||
local, err = frpIo.WithEncryption(local, []byte(serverCfg.Token))
|
local, err = libio.WithEncryption(local, []byte(serverCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cfg.UseCompression {
|
if cfg.UseCompression {
|
||||||
local = frpIo.WithCompression(local)
|
local = libio.WithCompression(local)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pxy.GetLimiter() != nil {
|
if pxy.GetLimiter() != nil {
|
||||||
local = frpIo.WrapReadWriteCloser(limit.NewReader(local, pxy.GetLimiter()), limit.NewWriter(local, pxy.GetLimiter()), func() error {
|
local = libio.WrapReadWriteCloser(limit.NewReader(local, pxy.GetLimiter()), limit.NewWriter(local, pxy.GetLimiter()), func() error {
|
||||||
return local.Close()
|
return local.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -317,9 +317,9 @@ func HandleUserTCPConnection(pxy Proxy, userConn net.Conn, serverCfg config.Serv
|
|||||||
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
|
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
|
||||||
|
|
||||||
name := pxy.GetName()
|
name := pxy.GetName()
|
||||||
proxyType := pxy.GetConf().GetBaseInfo().ProxyType
|
proxyType := pxy.GetConf().GetBaseConfig().ProxyType
|
||||||
metrics.Server.OpenConnection(name, proxyType)
|
metrics.Server.OpenConnection(name, proxyType)
|
||||||
inCount, outCount, _ := frpIo.Join(local, userConn)
|
inCount, outCount, _ := libio.Join(local, userConn)
|
||||||
metrics.Server.CloseConnection(name, proxyType)
|
metrics.Server.CloseConnection(name, proxyType)
|
||||||
metrics.Server.AddTrafficIn(name, proxyType, inCount)
|
metrics.Server.AddTrafficIn(name, proxyType, inCount)
|
||||||
metrics.Server.AddTrafficOut(name, proxyType, outCount)
|
metrics.Server.AddTrafficOut(name, proxyType, outCount)
|
||||||
|
@ -23,14 +23,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
frpIo "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/proto/udp"
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
"github.com/fatedier/frp/pkg/util/limit"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/server/metrics"
|
"github.com/fatedier/frp/server/metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
|
|||||||
pxy.readCh <- m
|
pxy.readCh <- m
|
||||||
metrics.Server.AddTrafficOut(
|
metrics.Server.AddTrafficOut(
|
||||||
pxy.GetName(),
|
pxy.GetName(),
|
||||||
pxy.GetConf().GetBaseInfo().ProxyType,
|
pxy.GetConf().GetBaseConfig().ProxyType,
|
||||||
int64(len(m.Content)),
|
int64(len(m.Content)),
|
||||||
)
|
)
|
||||||
}); errRet != nil {
|
}); errRet != nil {
|
||||||
@ -154,7 +154,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
|
|||||||
xl.Trace("send message to udp workConn: %s", udpMsg.Content)
|
xl.Trace("send message to udp workConn: %s", udpMsg.Content)
|
||||||
metrics.Server.AddTrafficIn(
|
metrics.Server.AddTrafficIn(
|
||||||
pxy.GetName(),
|
pxy.GetName(),
|
||||||
pxy.GetConf().GetBaseInfo().ProxyType,
|
pxy.GetConf().GetBaseConfig().ProxyType,
|
||||||
int64(len(udpMsg.Content)),
|
int64(len(udpMsg.Content)),
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
@ -189,7 +189,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
|
|||||||
|
|
||||||
var rwc io.ReadWriteCloser = workConn
|
var rwc io.ReadWriteCloser = workConn
|
||||||
if pxy.cfg.UseEncryption {
|
if pxy.cfg.UseEncryption {
|
||||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
|
rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Error("create encryption stream error: %v", err)
|
xl.Error("create encryption stream error: %v", err)
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
@ -197,16 +197,16 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if pxy.cfg.UseCompression {
|
if pxy.cfg.UseCompression {
|
||||||
rwc = frpIo.WithCompression(rwc)
|
rwc = libio.WithCompression(rwc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pxy.GetLimiter() != nil {
|
if pxy.GetLimiter() != nil {
|
||||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(rwc, pxy.GetLimiter()), limit.NewWriter(rwc, pxy.GetLimiter()), func() error {
|
rwc = libio.WrapReadWriteCloser(limit.NewReader(rwc, pxy.GetLimiter()), limit.NewWriter(rwc, pxy.GetLimiter()), func() error {
|
||||||
return rwc.Close()
|
return rwc.Close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pxy.workConn = frpNet.WrapReadWriteCloserToConn(rwc, workConn)
|
pxy.workConn = utilnet.WrapReadWriteCloserToConn(rwc, workConn)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
go workConnReaderFn(pxy.workConn)
|
go workConnReaderFn(pxy.workConn)
|
||||||
go workConnSenderFn(pxy.workConn, ctx)
|
go workConnSenderFn(pxy.workConn, ctx)
|
||||||
|
@ -44,41 +44,20 @@ func (pxy *XTCPProxy) Run() (remoteAddr string, err error) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-pxy.closeCh:
|
case <-pxy.closeCh:
|
||||||
break
|
return
|
||||||
case sidRequest := <-sidCh:
|
case sid := <-sidCh:
|
||||||
sr := sidRequest
|
|
||||||
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
|
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m := &msg.NatHoleSid{
|
m := &msg.NatHoleSid{
|
||||||
Sid: sr.Sid,
|
Sid: sid,
|
||||||
}
|
}
|
||||||
errRet = msg.WriteMsg(workConn, m)
|
errRet = msg.WriteMsg(workConn, m)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
xl.Warn("write nat hole sid package error, %v", errRet)
|
xl.Warn("write nat hole sid package error, %v", errRet)
|
||||||
workConn.Close()
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
workConn.Close()
|
||||||
go func() {
|
|
||||||
raw, errRet := msg.ReadMsg(workConn)
|
|
||||||
if errRet != nil {
|
|
||||||
xl.Warn("read nat hole client ok package error: %v", errRet)
|
|
||||||
workConn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
|
|
||||||
xl.Warn("read nat hole client ok package format error")
|
|
||||||
workConn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case sr.NotifyCh <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -39,7 +39,7 @@ import (
|
|||||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||||
"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"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/tcpmux"
|
"github.com/fatedier/frp/pkg/util/tcpmux"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
"github.com/fatedier/frp/pkg/util/version"
|
"github.com/fatedier/frp/pkg/util/version"
|
||||||
@ -99,6 +99,11 @@ type Service struct {
|
|||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
|
|
||||||
cfg config.ServerCommonConf
|
cfg config.ServerCommonConf
|
||||||
|
|
||||||
|
// service context
|
||||||
|
ctx context.Context
|
||||||
|
// call cancel to stop service
|
||||||
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||||
@ -110,6 +115,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
ctlManager: NewControlManager(),
|
ctlManager: NewControlManager(),
|
||||||
pxyManager: proxy.NewManager(),
|
pxyManager: proxy.NewManager(),
|
||||||
@ -123,6 +129,8 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
authVerifier: auth.NewAuthVerifier(cfg.ServerConfig),
|
authVerifier: auth.NewAuthVerifier(cfg.ServerConfig),
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create tcpmux httpconnect multiplexer.
|
// Create tcpmux httpconnect multiplexer.
|
||||||
@ -202,7 +210,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
// Listen for accepting connections from client using kcp protocol.
|
// Listen for accepting connections from client using kcp protocol.
|
||||||
if cfg.KCPBindPort > 0 {
|
if cfg.KCPBindPort > 0 {
|
||||||
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.KCPBindPort))
|
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.KCPBindPort))
|
||||||
svr.kcpListener, err = frpNet.ListenKcp(address)
|
svr.kcpListener, err = utilnet.ListenKcp(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("listen on kcp udp address %s error: %v", address, err)
|
err = fmt.Errorf("listen on kcp udp address %s error: %v", address, err)
|
||||||
return
|
return
|
||||||
@ -227,11 +235,11 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Listen for accepting connections from client using websocket protocol.
|
// Listen for accepting connections from client using websocket protocol.
|
||||||
websocketPrefix := []byte("GET " + frpNet.FrpWebsocketPath)
|
websocketPrefix := []byte("GET " + utilnet.FrpWebsocketPath)
|
||||||
websocketLn := svr.muxer.Listen(0, uint32(len(websocketPrefix)), func(data []byte) bool {
|
websocketLn := svr.muxer.Listen(0, uint32(len(websocketPrefix)), func(data []byte) bool {
|
||||||
return bytes.Equal(data, websocketPrefix)
|
return bytes.Equal(data, websocketPrefix)
|
||||||
})
|
})
|
||||||
svr.websocketListener = frpNet.NewWebsocketListener(websocketLn)
|
svr.websocketListener = utilnet.NewWebsocketListener(websocketLn)
|
||||||
|
|
||||||
// Create http vhost muxer.
|
// Create http vhost muxer.
|
||||||
if cfg.VhostHTTPPort > 0 {
|
if cfg.VhostHTTPPort > 0 {
|
||||||
@ -286,21 +294,16 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
// frp tls listener
|
// frp tls listener
|
||||||
svr.tlsListener = svr.muxer.Listen(2, 1, func(data []byte) bool {
|
svr.tlsListener = svr.muxer.Listen(2, 1, func(data []byte) bool {
|
||||||
// tls first byte can be 0x16 only when vhost https port is not same with bind port
|
// tls first byte can be 0x16 only when vhost https port is not same with bind port
|
||||||
return int(data[0]) == frpNet.FRPTLSHeadByte || int(data[0]) == 0x16
|
return int(data[0]) == utilnet.FRPTLSHeadByte || int(data[0]) == 0x16
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create nat hole controller.
|
// Create nat hole controller.
|
||||||
if cfg.BindUDPPort > 0 {
|
nc, err := nathole.NewController(time.Duration(cfg.NatHoleAnalysisDataReserveHours) * time.Hour)
|
||||||
var nc *nathole.Controller
|
if err != nil {
|
||||||
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.BindUDPPort))
|
err = fmt.Errorf("create nat hole controller error, %v", err)
|
||||||
nc, err = nathole.NewController(address, []byte(cfg.Token))
|
return
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("create nat hole controller error, %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
svr.rc.NatHoleController = nc
|
|
||||||
log.Info("nat hole udp service listen on %s", address)
|
|
||||||
}
|
}
|
||||||
|
svr.rc.NatHoleController = nc
|
||||||
|
|
||||||
var statsEnable bool
|
var statsEnable bool
|
||||||
// Create dashboard web server.
|
// Create dashboard web server.
|
||||||
@ -327,22 +330,43 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) Run() {
|
func (svr *Service) Run() {
|
||||||
if svr.rc.NatHoleController != nil {
|
|
||||||
go svr.rc.NatHoleController.Run()
|
|
||||||
}
|
|
||||||
if svr.kcpListener != nil {
|
if svr.kcpListener != nil {
|
||||||
go svr.HandleListener(svr.kcpListener)
|
go svr.HandleListener(svr.kcpListener)
|
||||||
}
|
}
|
||||||
if svr.quicListener != nil {
|
if svr.quicListener != nil {
|
||||||
go svr.HandleQUICListener(svr.quicListener)
|
go svr.HandleQUICListener(svr.quicListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
go svr.HandleListener(svr.websocketListener)
|
go svr.HandleListener(svr.websocketListener)
|
||||||
go svr.HandleListener(svr.tlsListener)
|
go svr.HandleListener(svr.tlsListener)
|
||||||
|
|
||||||
|
if svr.rc.NatHoleController != nil {
|
||||||
|
go svr.rc.NatHoleController.CleanWorker(svr.ctx)
|
||||||
|
}
|
||||||
svr.HandleListener(svr.listener)
|
svr.HandleListener(svr.listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svr *Service) Close() error {
|
||||||
|
if svr.kcpListener != nil {
|
||||||
|
svr.kcpListener.Close()
|
||||||
|
}
|
||||||
|
if svr.quicListener != nil {
|
||||||
|
svr.quicListener.Close()
|
||||||
|
}
|
||||||
|
if svr.websocketListener != nil {
|
||||||
|
svr.websocketListener.Close()
|
||||||
|
}
|
||||||
|
if svr.tlsListener != nil {
|
||||||
|
svr.tlsListener.Close()
|
||||||
|
}
|
||||||
|
if svr.listener != nil {
|
||||||
|
svr.listener.Close()
|
||||||
|
}
|
||||||
|
svr.cancel()
|
||||||
|
|
||||||
|
svr.ctlManager.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (svr *Service) handleConnection(ctx context.Context, conn net.Conn) {
|
func (svr *Service) handleConnection(ctx context.Context, conn net.Conn) {
|
||||||
xl := xlog.FromContextSafe(ctx)
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
|
||||||
@ -418,12 +442,12 @@ func (svr *Service) HandleListener(l net.Listener) {
|
|||||||
xl := xlog.New()
|
xl := xlog.New()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
c = frpNet.NewContextConn(xlog.NewContext(ctx, xl), c)
|
c = utilnet.NewContextConn(xlog.NewContext(ctx, xl), c)
|
||||||
|
|
||||||
log.Trace("start check TLS connection...")
|
log.Trace("start check TLS connection...")
|
||||||
originConn := c
|
originConn := c
|
||||||
var isTLS, custom bool
|
var isTLS, custom bool
|
||||||
c, isTLS, custom, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TLSOnly, connReadTimeout)
|
c, isTLS, custom, err = utilnet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TLSOnly, connReadTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
|
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
|
||||||
originConn.Close()
|
originConn.Close()
|
||||||
@ -477,7 +501,7 @@ func (svr *Service) HandleQUICListener(l quic.Listener) {
|
|||||||
_ = frpConn.CloseWithError(0, "")
|
_ = frpConn.CloseWithError(0, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go svr.handleConnection(ctx, frpNet.QuicStreamToNetConn(stream, frpConn))
|
go svr.handleConnection(ctx, utilnet.QuicStreamToNetConn(stream, frpConn))
|
||||||
}
|
}
|
||||||
}(context.Background(), c)
|
}(context.Background(), c)
|
||||||
}
|
}
|
||||||
@ -493,7 +517,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := frpNet.NewContextFromConn(ctlConn)
|
ctx := utilnet.NewContextFromConn(ctlConn)
|
||||||
xl := xlog.FromContextSafe(ctx)
|
xl := xlog.FromContextSafe(ctx)
|
||||||
xl.AppendPrefix(loginMsg.RunID)
|
xl.AppendPrefix(loginMsg.RunID)
|
||||||
ctx = xlog.NewContext(ctx, xl)
|
ctx = xlog.NewContext(ctx, xl)
|
||||||
@ -531,7 +555,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err
|
|||||||
|
|
||||||
// RegisterWorkConn register a new work connection to control and proxies need it.
|
// RegisterWorkConn register a new work connection to control and proxies need it.
|
||||||
func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) error {
|
func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) error {
|
||||||
xl := frpNet.NewLogFromConn(workConn)
|
xl := utilnet.NewLogFromConn(workConn)
|
||||||
ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
|
ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
|
||||||
if !exist {
|
if !exist {
|
||||||
xl.Warn("No client control found for run id [%s]", newMsg.RunID)
|
xl.Warn("No client control found for run id [%s]", newMsg.RunID)
|
||||||
|
@ -20,15 +20,15 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
utilnet "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manager for visitor listeners.
|
// Manager for visitor listeners.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
visitorListeners map[string]*frpNet.CustomListener
|
visitorListeners map[string]*utilnet.InternalListener
|
||||||
skMap map[string]string
|
skMap map[string]string
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@ -36,12 +36,12 @@ type Manager struct {
|
|||||||
|
|
||||||
func NewManager() *Manager {
|
func NewManager() *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
visitorListeners: make(map[string]*frpNet.CustomListener),
|
visitorListeners: make(map[string]*utilnet.InternalListener),
|
||||||
skMap: make(map[string]string),
|
skMap: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *Manager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
|
func (vm *Manager) Listen(name string, sk string) (l *utilnet.InternalListener, err error) {
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
defer vm.mu.Unlock()
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ func (vm *Manager) Listen(name string, sk string) (l *frpNet.CustomListener, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l = frpNet.NewCustomListener()
|
l = utilnet.NewInternalListener()
|
||||||
vm.visitorListeners[name] = l
|
vm.visitorListeners[name] = l
|
||||||
vm.skMap[name] = sk
|
vm.skMap[name] = sk
|
||||||
return
|
return
|
||||||
@ -71,15 +71,15 @@ func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64, signKey
|
|||||||
|
|
||||||
var rwc io.ReadWriteCloser = conn
|
var rwc io.ReadWriteCloser = conn
|
||||||
if useEncryption {
|
if useEncryption {
|
||||||
if rwc, err = frpIo.WithEncryption(rwc, []byte(sk)); err != nil {
|
if rwc, err = libio.WithEncryption(rwc, []byte(sk)); err != nil {
|
||||||
err = fmt.Errorf("create encryption connection failed: %v", err)
|
err = fmt.Errorf("create encryption connection failed: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if useCompression {
|
if useCompression {
|
||||||
rwc = frpIo.WithCompression(rwc)
|
rwc = libio.WithCompression(rwc)
|
||||||
}
|
}
|
||||||
err = l.PutConn(frpNet.WrapReadWriteCloserToConn(rwc, conn))
|
err = l.PutConn(utilnet.WrapReadWriteCloserToConn(rwc, conn))
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("custom listener for [%s] doesn't exist", name)
|
err = fmt.Errorf("custom listener for [%s] doesn't exist", name)
|
||||||
return
|
return
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
"github.com/onsi/ginkgo/v2"
|
||||||
|
|
||||||
@ -275,8 +276,8 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
ginkgo.Describe("STCP && SUDP", func() {
|
ginkgo.Describe("STCP && SUDP && XTCP", func() {
|
||||||
types := []string{"stcp", "sudp"}
|
types := []string{"stcp", "sudp", "xtcp"}
|
||||||
for _, t := range types {
|
for _, t := range types {
|
||||||
proxyType := t
|
proxyType := t
|
||||||
ginkgo.It(fmt.Sprintf("Expose echo server with %s", strings.ToUpper(proxyType)), func() {
|
ginkgo.It(fmt.Sprintf("Expose echo server with %s", strings.ToUpper(proxyType)), func() {
|
||||||
@ -293,6 +294,9 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
|
|||||||
case "sudp":
|
case "sudp":
|
||||||
localPortName = framework.UDPEchoServerPort
|
localPortName = framework.UDPEchoServerPort
|
||||||
protocol = "udp"
|
protocol = "udp"
|
||||||
|
case "xtcp":
|
||||||
|
localPortName = framework.TCPEchoServerPort
|
||||||
|
protocol = "tcp"
|
||||||
}
|
}
|
||||||
|
|
||||||
correctSK := "abc"
|
correctSK := "abc"
|
||||||
@ -371,6 +375,9 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
framework.NewRequestExpect(f).
|
framework.NewRequestExpect(f).
|
||||||
|
RequestModify(func(r *request.Request) {
|
||||||
|
r.Timeout(10 * time.Second)
|
||||||
|
}).
|
||||||
Protocol(protocol).
|
Protocol(protocol).
|
||||||
PortName(test.bindPortName).
|
PortName(test.bindPortName).
|
||||||
Explain(test.proxyName).
|
Explain(test.proxyName).
|
||||||
|
52
test/e2e/basic/xtcp.go
Normal file
52
test/e2e/basic/xtcp.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/onsi/ginkgo/v2"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/test/e2e/framework"
|
||||||
|
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||||
|
"github.com/fatedier/frp/test/e2e/pkg/port"
|
||||||
|
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = ginkgo.Describe("[Feature: XTCP]", func() {
|
||||||
|
f := framework.NewDefaultFramework()
|
||||||
|
|
||||||
|
ginkgo.It("Fallback To STCP", func() {
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
clientConf := consts.DefaultClientConfig
|
||||||
|
|
||||||
|
bindPortName := port.GenName("XTCP")
|
||||||
|
clientConf += fmt.Sprintf(`
|
||||||
|
[foo]
|
||||||
|
type = stcp
|
||||||
|
local_port = {{ .%s }}
|
||||||
|
|
||||||
|
[foo-visitor]
|
||||||
|
type = stcp
|
||||||
|
role = visitor
|
||||||
|
server_name = foo
|
||||||
|
bind_port = -1
|
||||||
|
|
||||||
|
[bar-visitor]
|
||||||
|
type = xtcp
|
||||||
|
role = visitor
|
||||||
|
server_name = bar
|
||||||
|
bind_port = {{ .%s }}
|
||||||
|
keep_tunnel_open = true
|
||||||
|
fallback_to = foo-visitor
|
||||||
|
fallback_timeout_ms = 200
|
||||||
|
`, framework.TCPEchoServerPort, bindPortName)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
framework.NewRequestExpect(f).
|
||||||
|
RequestModify(func(r *request.Request) {
|
||||||
|
r.Timeout(time.Second)
|
||||||
|
}).
|
||||||
|
PortName(bindPortName).
|
||||||
|
Ensure()
|
||||||
|
})
|
||||||
|
})
|
@ -66,8 +66,8 @@ func NewDefaultFramework() *Framework {
|
|||||||
options := Options{
|
options := Options{
|
||||||
TotalParallelNode: suiteConfig.ParallelTotal,
|
TotalParallelNode: suiteConfig.ParallelTotal,
|
||||||
CurrentNodeIndex: suiteConfig.ParallelProcess,
|
CurrentNodeIndex: suiteConfig.ParallelProcess,
|
||||||
FromPortIndex: 20000,
|
FromPortIndex: 10000,
|
||||||
ToPortIndex: 50000,
|
ToPortIndex: 60000,
|
||||||
}
|
}
|
||||||
return NewFramework(options)
|
return NewFramework(options)
|
||||||
}
|
}
|
||||||
@ -118,14 +118,14 @@ func (f *Framework) AfterEach() {
|
|||||||
// stop processor
|
// stop processor
|
||||||
for _, p := range f.serverProcesses {
|
for _, p := range f.serverProcesses {
|
||||||
_ = p.Stop()
|
_ = p.Stop()
|
||||||
if TestContext.Debug {
|
if TestContext.Debug || ginkgo.CurrentSpecReport().Failed() {
|
||||||
fmt.Println(p.ErrorOutput())
|
fmt.Println(p.ErrorOutput())
|
||||||
fmt.Println(p.StdOutput())
|
fmt.Println(p.StdOutput())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range f.clientProcesses {
|
for _, p := range f.clientProcesses {
|
||||||
_ = p.Stop()
|
_ = p.Stop()
|
||||||
if TestContext.Debug {
|
if TestContext.Debug || ginkgo.CurrentSpecReport().Failed() {
|
||||||
fmt.Println(p.ErrorOutput())
|
fmt.Println(p.ErrorOutput())
|
||||||
fmt.Println(p.StdOutput())
|
fmt.Println(p.StdOutput())
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
|||||||
err = p.Start()
|
err = p.Start()
|
||||||
ExpectNoError(err)
|
ExpectNoError(err)
|
||||||
}
|
}
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
currentClientProcesses := make([]*process.Process, 0, len(clientTemplates))
|
currentClientProcesses := make([]*process.Process, 0, len(clientTemplates))
|
||||||
for i := range clientTemplates {
|
for i := range clientTemplates {
|
||||||
@ -56,7 +56,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
|||||||
ExpectNoError(err)
|
ExpectNoError(err)
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
}
|
}
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
return currentServerProcesses, currentClientProcesses
|
return currentServerProcesses, currentClientProcesses
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ func (pa *Allocator) GetByName(portName string) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
l, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
l, err := net.Listen("tcp", net.JoinHostPort("0.0.0.0", 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)
|
||||||
@ -66,7 +66,7 @@ func (pa *Allocator) GetByName(portName string) int {
|
|||||||
}
|
}
|
||||||
l.Close()
|
l.Close()
|
||||||
|
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
|
udpAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort("0.0.0.0", strconv.Itoa(port)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,6 @@
|
|||||||
<el-form-item label="BindPort">
|
<el-form-item label="BindPort">
|
||||||
<span>{{ data.bind_port }}</span>
|
<span>{{ data.bind_port }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Bind UDP Port" v-if="data.bind_udp_port != 0">
|
|
||||||
<span>{{ data.bind_udp_port }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="KCP Bind Port" v-if="data.kcp_bind_port != 0">
|
<el-form-item label="KCP Bind Port" v-if="data.kcp_bind_port != 0">
|
||||||
<span>{{ data.kcp_bind_port }}</span>
|
<span>{{ data.kcp_bind_port }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -91,7 +88,6 @@ import LongSpan from './LongSpan.vue'
|
|||||||
let data = ref({
|
let data = ref({
|
||||||
version: '',
|
version: '',
|
||||||
bind_port: 0,
|
bind_port: 0,
|
||||||
bind_udp_port: 0,
|
|
||||||
kcp_bind_port: 0,
|
kcp_bind_port: 0,
|
||||||
quic_bind_port: 0,
|
quic_bind_port: 0,
|
||||||
vhost_http_port: 0,
|
vhost_http_port: 0,
|
||||||
@ -114,7 +110,6 @@ const fetchData = () => {
|
|||||||
.then((json) => {
|
.then((json) => {
|
||||||
data.value.version = json.version
|
data.value.version = json.version
|
||||||
data.value.bind_port = json.bind_port
|
data.value.bind_port = json.bind_port
|
||||||
data.value.bind_udp_port = json.bind_udp_port
|
|
||||||
data.value.kcp_bind_port = json.kcp_bind_port
|
data.value.kcp_bind_port = json.kcp_bind_port
|
||||||
data.value.quic_bind_port = json.quic_bind_port
|
data.value.quic_bind_port = json.quic_bind_port
|
||||||
data.value.vhost_http_port = json.vhost_http_port
|
data.value.vhost_http_port = json.vhost_http_port
|
||||||
|
Loading…
x
Reference in New Issue
Block a user