Merge pull request from fatedier/dev

release v0.50.0
This commit is contained in:
fatedier 2023-06-26 17:03:56 +08:00 committed by GitHub
commit 4fd800bc48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 1454 additions and 1315 deletions

1
.gitignore vendored
View File

@ -29,6 +29,7 @@ packages/
release/ release/
test/bin/ test/bin/
vendor/ vendor/
lastversion/
dist/ dist/
.idea/ .idea/
.vscode/ .vscode/

View File

@ -46,8 +46,23 @@ e2e:
e2e-trace: e2e-trace:
DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
e2e-compatibility-last-frpc:
if [ ! -d "./lastversion" ]; then \
TARGET_DIRNAME=lastversion ./hack/download.sh; \
fi
FRPC_PATH="`pwd`/lastversion/frpc" ./hack/run-e2e.sh
rm -r ./lastversion
e2e-compatibility-last-frps:
if [ ! -d "./lastversion" ]; then \
TARGET_DIRNAME=lastversion ./hack/download.sh; \
fi
FRPS_PATH="`pwd`/lastversion/frps" ./hack/run-e2e.sh
rm -r ./lastversion
alltest: vet gotest e2e alltest: vet gotest e2e
clean: clean:
rm -f ./bin/frpc rm -f ./bin/frpc
rm -f ./bin/frps rm -f ./bin/frps
rm -rf ./lastversion

View File

@ -562,11 +562,9 @@ use_compression = true
#### TLS #### TLS
frp supports the TLS protocol between `frpc` and `frps` since v0.25.0. Since v0.50.0, the default value of `tls_enable` and `disable_custom_tls_first_byte` has been changed to true, and tls is enabled by default.
For port multiplexing, frp sends a first byte `0x17` to dial a TLS connection. For port multiplexing, frp sends a first byte `0x17` to dial a TLS connection. This only takes effect when you set `disable_custom_tls_first_byte` to false.
Configure `tls_enable = true` in the `[common]` section to `frpc.ini` to enable this feature.
To **enforce** `frps` to only accept TLS connections - configure `tls_only = true` in the `[common]` section in `frps.ini`. **This is optional.** To **enforce** `frps` to only accept TLS connections - configure `tls_only = true` in the `[common]` section in `frps.ini`. **This is optional.**
@ -581,7 +579,6 @@ tls_trusted_ca_file = ca.crt
**`frps` TLS settings (under the `[common]` section):** **`frps` TLS settings (under the `[common]` section):**
```ini ```ini
tls_only = true tls_only = true
tls_enable = true
tls_cert_file = certificate.crt tls_cert_file = certificate.crt
tls_key_file = certificate.key tls_key_file = certificate.key
tls_trusted_ca_file = ca.crt tls_trusted_ca_file = ca.crt

View File

@ -1,19 +1,18 @@
## Notes ## Notes
We have thoroughly refactored xtcp in this version to improve its penetration rate and stability. **For enhanced security, the default values for `tls_enable` and `disable_custom_tls_first_byte` have been set to true.**
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. If you wish to revert to the previous default values, you need to manually set the values of these two parameters to false.
**Due to a significant refactor of xtcp, this version is not compatible with previous versions of xtcp.** ### Features
**To use features related to xtcp, both frpc and frps need to be updated to the latest version.** * Added support for `allow_users` in stcp, sudp, xtcp. By default, only the same user is allowed to access. Use `*` to allow access from any user. The visitor configuration now supports `server_user` to connect to proxies of other users.
* Added fallback support to a specified alternative visitor when xtcp connection fails.
### New ### Improvements
* The frpc has added the `nathole discover` command for testing the NAT type of the current network. * Increased the default value of `MaxStreamWindowSize` for yamux to 6MB, improving traffic forwarding rate in high-latency scenarios.
* `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.
### Fix ### Fixes
* Fix the problem of lagging when opening multiple table entries in the frps dashboard. * Fixed an issue where having proxies with the same name would cause previously working proxies to become ineffective in `xtcp`.

File diff suppressed because one or more lines are too long

View File

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

View File

@ -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).SetAuthFailDelay(200 * time.Millisecond).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)
}) })

View File

@ -91,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))
} }

View File

@ -109,7 +109,7 @@ func NewControl(
ctl.msgTransporter = transport.NewMessageTransporter(ctl.sendCh) ctl.msgTransporter = transport.NewMessageTransporter(ctl.sendCh)
ctl.pm = proxy.NewManager(ctl.ctx, clientCfg, ctl.msgTransporter) ctl.pm = proxy.NewManager(ctl.ctx, clientCfg, ctl.msgTransporter)
ctl.vm = visitor.NewManager(ctl.ctx, ctl.clientCfg, ctl.connectServer, ctl.msgTransporter) 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
} }

View 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,
}
}

View File

@ -19,12 +19,13 @@ import (
"context" "context"
"io" "io"
"net" "net"
"reflect"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
frpIo "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
libdial "github.com/fatedier/golib/net/dial" libdial "github.com/fatedier/golib/net/dial"
pp "github.com/pires/go-proxyproto" pp "github.com/pires/go-proxyproto"
"golang.org/x/time/rate" "golang.org/x/time/rate"
@ -37,6 +38,12 @@ import (
"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
@ -54,253 +61,94 @@ func NewProxy(
msgTransporter transport.MessageTransporter, msgTransporter transport.MessageTransporter,
) (pxy Proxy) { ) (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(),
limiter: limiter, clientCfg: clientCfg,
msgTransporter: msgTransporter, 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
msgTransporter transport.MessageTransporter 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)
} }
// 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"
@ -319,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
} }
@ -331,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
} }
@ -360,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)

View File

@ -18,6 +18,7 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"reflect"
"sync" "sync"
"github.com/fatedier/frp/client/event" "github.com/fatedier/frp/client/event"
@ -121,21 +122,18 @@ 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)
@ -149,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)
} }
} }

View File

@ -91,7 +91,7 @@ func NewWrapper(
eventHandler event.Handler, eventHandler event.Handler,
msgTransporter transport.MessageTransporter, msgTransporter transport.MessageTransporter,
) *Wrapper { ) *Wrapper {
baseInfo := cfg.GetBaseInfo() 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{

View File

@ -17,20 +17,25 @@ package proxy
import ( import (
"io" "io"
"net" "net"
"reflect"
"strconv" "strconv"
"sync" "sync"
"time" "time"
"github.com/fatedier/golib/errors" "github.com/fatedier/golib/errors"
frpIo "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
"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"
) )
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.SUDPProxyConf{}), NewSUDPProxy)
}
type SUDPProxy struct { type SUDPProxy struct {
*BaseProxy *BaseProxy
@ -41,6 +46,18 @@ type SUDPProxy struct {
closeCh chan struct{} 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) { func (pxy *SUDPProxy) Run() (err error) {
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort))) pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
if err != nil { if err != nil {
@ -67,12 +84,12 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
var rwc io.ReadWriteCloser = conn var rwc io.ReadWriteCloser = conn
var err error var err error
if pxy.limiter != nil { if pxy.limiter != nil {
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error { rwc = libio.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
return conn.Close() return conn.Close()
}) })
} }
if pxy.cfg.UseEncryption { if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token)) rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
if err != nil { if err != nil {
conn.Close() conn.Close()
xl.Error("create encryption stream error: %v", err) xl.Error("create encryption stream error: %v", err)
@ -80,9 +97,9 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
} }
} }
if pxy.cfg.UseCompression { if pxy.cfg.UseCompression {
rwc = frpIo.WithCompression(rwc) rwc = libio.WithCompression(rwc)
} }
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn) conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
workConn := conn workConn := conn
readCh := make(chan *msg.UDPPacket, 1024) readCh := make(chan *msg.UDPPacket, 1024)

View File

@ -17,20 +17,24 @@ package proxy
import ( import (
"io" "io"
"net" "net"
"reflect"
"strconv" "strconv"
"time" "time"
"github.com/fatedier/golib/errors" "github.com/fatedier/golib/errors"
frpIo "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
"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"
) )
// UDP func init() {
RegisterProxyFactory(reflect.TypeOf(&config.UDPProxyConf{}), NewUDPProxy)
}
type UDPProxy struct { type UDPProxy struct {
*BaseProxy *BaseProxy
@ -42,6 +46,18 @@ type UDPProxy struct {
// include msg.UDPPacket and msg.Ping // include msg.UDPPacket and msg.Ping
sendCh chan msg.Message sendCh chan msg.Message
workConn net.Conn 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) { func (pxy *UDPProxy) Run() (err error) {
@ -79,12 +95,12 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
var rwc io.ReadWriteCloser = conn var rwc io.ReadWriteCloser = conn
var err error var err error
if pxy.limiter != nil { if pxy.limiter != nil {
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error { rwc = libio.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
return conn.Close() return conn.Close()
}) })
} }
if pxy.cfg.UseEncryption { if pxy.cfg.UseEncryption {
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token)) rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
if err != nil { if err != nil {
conn.Close() conn.Close()
xl.Error("create encryption stream error: %v", err) xl.Error("create encryption stream error: %v", err)
@ -92,9 +108,9 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
} }
} }
if pxy.cfg.UseCompression { if pxy.cfg.UseCompression {
rwc = frpIo.WithCompression(rwc) rwc = libio.WithCompression(rwc)
} }
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn) conn = utilnet.WrapReadWriteCloserToConn(rwc, conn)
pxy.mu.Lock() pxy.mu.Lock()
pxy.workConn = conn pxy.workConn = conn

View File

@ -17,6 +17,7 @@ package proxy
import ( import (
"io" "io"
"net" "net"
"reflect"
"time" "time"
fmux "github.com/hashicorp/yamux" fmux "github.com/hashicorp/yamux"
@ -25,32 +26,28 @@ import (
"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/nathole" "github.com/fatedier/frp/pkg/nathole"
plugin "github.com/fatedier/frp/pkg/plugin/client"
"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"
) )
// XTCP func init() {
RegisterProxyFactory(reflect.TypeOf(&config.XTCPProxyConf{}), NewXTCPProxy)
}
type XTCPProxy struct { type XTCPProxy struct {
*BaseProxy *BaseProxy
cfg *config.XTCPProxyConf cfg *config.XTCPProxyConf
proxyPlugin plugin.Plugin
} }
func (pxy *XTCPProxy) Run() (err error) { func NewXTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
if pxy.cfg.Plugin != "" { unwrapped, ok := cfg.(*config.XTCPProxyConf)
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams) if !ok {
if err != nil { return nil
return
}
} }
return return &XTCPProxy{
} BaseProxy: baseProxy,
cfg: unwrapped,
func (pxy *XTCPProxy) Close() {
if pxy.proxyPlugin != nil {
pxy.proxyPlugin.Close()
} }
} }
@ -64,6 +61,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
return return
} }
xl.Trace("nathole prepare start")
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer}) prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer})
if err != nil { if err != nil {
xl.Warn("nathole prepare error: %v", err) xl.Warn("nathole prepare error: %v", err)
@ -83,6 +81,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
AssistedAddrs: prepareResult.AssistedAddrs, AssistedAddrs: prepareResult.AssistedAddrs,
} }
xl.Trace("nathole exchange info start")
natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second) natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second)
if err != nil { if err != nil {
xl.Warn("nathole exchange info error: %v", err) xl.Warn("nathole exchange info error: %v", err)
@ -132,7 +131,7 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s
} }
defer lConn.Close() defer lConn.Close()
remote, err := frpNet.NewKCPConnFromUDP(lConn, true, raddr.String()) remote, err := utilnet.NewKCPConnFromUDP(lConn, true, raddr.String())
if err != nil { if err != nil {
xl.Warn("create kcp connection from udp connection error: %v", err) xl.Warn("create kcp connection from udp connection error: %v", err)
return return
@ -140,7 +139,7 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s
fmuxCfg := fmux.DefaultConfig() fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 10 * time.Second fmuxCfg.KeepAliveInterval = 10 * time.Second
fmuxCfg.MaxStreamWindowSize = 2 * 1024 * 1024 fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
fmuxCfg.LogOutput = io.Discard fmuxCfg.LogOutput = io.Discard
session, err := fmux.Server(remote, fmuxCfg) session, err := fmux.Server(remote, fmuxCfg)
if err != nil { if err != nil {
@ -155,8 +154,7 @@ func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, s
xl.Error("accept connection error: %v", err) xl.Error("accept connection error: %v", err)
return return
} }
go HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, go pxy.HandleTCPWorkConnection(muxConn, startWorkConnMsg, []byte(pxy.cfg.Sk))
muxConn, []byte(pxy.cfg.Sk), startWorkConnMsg)
} }
} }
@ -194,7 +192,6 @@ func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, star
_ = c.CloseWithError(0, "") _ = c.CloseWithError(0, "")
return return
} }
go HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, go pxy.HandleTCPWorkConnection(utilnet.QuicStreamToNetConn(stream, c), startWorkConnMsg, []byte(pxy.cfg.Sk))
frpNet.QuicStreamToNetConn(stream, c), []byte(pxy.cfg.Sk), startWorkConnMsg)
} }
} }

View File

@ -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"
@ -369,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,
@ -395,6 +396,7 @@ func (cm *ConnectionManager) OpenConnection() error {
fmuxCfg := fmux.DefaultConfig() fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = time.Duration(cm.cfg.TCPMuxKeepaliveInterval) * time.Second fmuxCfg.KeepAliveInterval = time.Duration(cm.cfg.TCPMuxKeepaliveInterval) * time.Second
fmuxCfg.LogOutput = io.Discard fmuxCfg.LogOutput = io.Discard
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
session, err := fmux.Client(conn, fmuxCfg) session, err := fmux.Client(conn, fmuxCfg)
if err != nil { if err != nil {
return err return err
@ -409,7 +411,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 {
@ -451,7 +453,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))
@ -464,10 +466,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...,
) )

View File

@ -20,7 +20,7 @@ import (
"strconv" "strconv"
"time" "time"
frpIo "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/msg" "github.com/fatedier/frp/pkg/msg"
@ -35,17 +35,20 @@ type STCPVisitor struct {
} }
func (sv *STCPVisitor) Run() (err error) { func (sv *STCPVisitor) Run() (err error) {
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort))) if sv.cfg.BindPort > 0 {
if err != nil { sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
return if err != nil {
return
}
go sv.worker()
} }
go sv.worker() go sv.internalConnWorker()
return return
} }
func (sv *STCPVisitor) Close() { func (sv *STCPVisitor) Close() {
sv.l.Close() sv.BaseVisitor.Close()
} }
func (sv *STCPVisitor) worker() { func (sv *STCPVisitor) worker() {
@ -56,7 +59,18 @@ func (sv *STCPVisitor) worker() {
xl.Warn("stcp local listener closed") xl.Warn("stcp local listener closed")
return 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) go sv.handleConn(conn)
} }
} }
@ -66,7 +80,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
defer userConn.Close() defer userConn.Close()
xl.Debug("get a new stcp user connection") xl.Debug("get a new stcp user connection")
visitorConn, err := sv.connectServer() visitorConn, err := sv.helper.ConnectServer()
if err != nil { if err != nil {
return return
} }
@ -74,6 +88,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
now := time.Now().Unix() now := time.Now().Unix()
newVisitorConnMsg := &msg.NewVisitorConn{ newVisitorConnMsg := &msg.NewVisitorConn{
RunID: sv.helper.RunID(),
ProxyName: sv.cfg.ServerName, ProxyName: sv.cfg.ServerName,
SignKey: util.GetAuthKey(sv.cfg.Sk, now), SignKey: util.GetAuthKey(sv.cfg.Sk, now),
Timestamp: now, Timestamp: now,
@ -103,7 +118,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
var remote io.ReadWriteCloser var remote io.ReadWriteCloser
remote = visitorConn remote = visitorConn
if sv.cfg.UseEncryption { if sv.cfg.UseEncryption {
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk)) remote, err = libio.WithEncryption(remote, []byte(sv.cfg.Sk))
if err != nil { if err != nil {
xl.Error("create encryption stream error: %v", err) xl.Error("create encryption stream error: %v", err)
return return
@ -111,8 +126,8 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) {
} }
if sv.cfg.UseCompression { if sv.cfg.UseCompression {
remote = frpIo.WithCompression(remote) remote = libio.WithCompression(remote)
} }
frpIo.Join(userConn, remote) libio.Join(userConn, remote)
} }

View File

@ -23,12 +23,12 @@ 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"
"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"
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/xlog" "github.com/fatedier/frp/pkg/util/xlog"
) )
@ -199,13 +199,14 @@ func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) { func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
xl := xlog.FromContextSafe(sv.ctx) xl := xlog.FromContextSafe(sv.ctx)
visitorConn, err := sv.connectServer() visitorConn, err := sv.helper.ConnectServer()
if err != nil { if err != nil {
return nil, fmt.Errorf("frpc connect frps error: %v", err) return nil, fmt.Errorf("frpc connect frps error: %v", err)
} }
now := time.Now().Unix() now := time.Now().Unix()
newVisitorConnMsg := &msg.NewVisitorConn{ newVisitorConnMsg := &msg.NewVisitorConn{
RunID: sv.helper.RunID(),
ProxyName: sv.cfg.ServerName, ProxyName: sv.cfg.ServerName,
SignKey: util.GetAuthKey(sv.cfg.Sk, now), SignKey: util.GetAuthKey(sv.cfg.Sk, now),
Timestamp: now, Timestamp: now,
@ -232,16 +233,16 @@ func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
var remote io.ReadWriteCloser var remote io.ReadWriteCloser
remote = visitorConn remote = visitorConn
if sv.cfg.UseEncryption { if sv.cfg.UseEncryption {
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk)) remote, err = libio.WithEncryption(remote, []byte(sv.cfg.Sk))
if err != nil { if err != nil {
xl.Error("create encryption stream error: %v", err) xl.Error("create encryption stream error: %v", err)
return nil, err return nil, err
} }
} }
if sv.cfg.UseCompression { if sv.cfg.UseCompression {
remote = frpIo.WithCompression(remote) remote = libio.WithCompression(remote)
} }
return frpNet.WrapReadWriteCloserToConn(remote, visitorConn), nil return utilnet.WrapReadWriteCloserToConn(remote, visitorConn), nil
} }
func (sv *SUDPVisitor) Close() { func (sv *SUDPVisitor) Close() {
@ -254,6 +255,7 @@ func (sv *SUDPVisitor) Close() {
default: default:
close(sv.checkCloseCh) close(sv.checkCloseCh)
} }
sv.BaseVisitor.Close()
if sv.udpConn != nil { if sv.udpConn != nil {
sv.udpConn.Close() sv.udpConn.Close()
} }

View File

@ -21,12 +21,27 @@ import (
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/transport" "github.com/fatedier/frp/pkg/transport"
utilnet "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/xlog" "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. // Visitor is used for forward traffics from local port tot remote service.
type Visitor interface { type Visitor interface {
Run() error Run() error
AcceptConn(conn net.Conn) error
Close() Close()
} }
@ -34,15 +49,14 @@ func NewVisitor(
ctx context.Context, ctx context.Context,
cfg config.VisitorConf, cfg config.VisitorConf,
clientCfg config.ClientCommonConf, clientCfg config.ClientCommonConf,
connectServer func() (net.Conn, error), helper Helper,
msgTransporter transport.MessageTransporter,
) (visitor Visitor) { ) (visitor Visitor) {
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName) xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().ProxyName)
baseVisitor := BaseVisitor{ baseVisitor := BaseVisitor{
clientCfg: clientCfg, clientCfg: clientCfg,
connectServer: connectServer, helper: helper,
msgTransporter: msgTransporter, ctx: xlog.NewContext(ctx, xl),
ctx: xlog.NewContext(ctx, xl), internalLn: utilnet.NewInternalListener(),
} }
switch cfg := cfg.(type) { switch cfg := cfg.(type) {
case *config.STCPVisitorConf: case *config.STCPVisitorConf:
@ -67,11 +81,24 @@ func NewVisitor(
} }
type BaseVisitor struct { type BaseVisitor struct {
clientCfg config.ClientCommonConf clientCfg config.ClientCommonConf
connectServer func() (net.Conn, error) helper Helper
msgTransporter transport.MessageTransporter l net.Listener
l net.Listener internalLn *utilnet.InternalListener
mu sync.RWMutex mu sync.RWMutex
ctx context.Context 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()
}
}

View File

@ -16,7 +16,9 @@ package visitor
import ( import (
"context" "context"
"fmt"
"net" "net"
"reflect"
"sync" "sync"
"time" "time"
@ -26,15 +28,14 @@ import (
) )
type Manager struct { type Manager struct {
clientCfg config.ClientCommonConf clientCfg config.ClientCommonConf
connectServer func() (net.Conn, error) cfgs map[string]config.VisitorConf
msgTransporter transport.MessageTransporter visitors map[string]Visitor
cfgs map[string]config.VisitorConf helper Helper
visitors map[string]Visitor
checkInterval time.Duration checkInterval time.Duration
mu sync.Mutex mu sync.RWMutex
ctx context.Context ctx context.Context
stopCh chan struct{} stopCh chan struct{}
@ -42,20 +43,26 @@ type Manager struct {
func NewManager( func NewManager(
ctx context.Context, ctx context.Context,
runID string,
clientCfg config.ClientCommonConf, clientCfg config.ClientCommonConf,
connectServer func() (net.Conn, error), connectServer func() (net.Conn, error),
msgTransporter transport.MessageTransporter, msgTransporter transport.MessageTransporter,
) *Manager { ) *Manager {
return &Manager{ m := &Manager{
clientCfg: clientCfg, clientCfg: clientCfg,
connectServer: connectServer, cfgs: make(map[string]config.VisitorConf),
msgTransporter: msgTransporter, visitors: make(map[string]Visitor),
cfgs: make(map[string]config.VisitorConf), checkInterval: 10 * time.Second,
visitors: make(map[string]Visitor), ctx: ctx,
checkInterval: 10 * time.Second, stopCh: make(chan struct{}),
ctx: ctx,
stopCh: make(chan struct{}),
} }
m.helper = &visitorHelperImpl{
connectServerFn: connectServer,
msgTransporter: msgTransporter,
transferConnFn: m.TransferConn,
runID: runID,
}
return m
} }
func (vm *Manager) Run() { func (vm *Manager) Run() {
@ -72,7 +79,7 @@ func (vm *Manager) 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)
@ -83,11 +90,24 @@ func (vm *Manager) 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 *Manager) 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, cfg, vm.clientCfg, vm.connectServer, vm.msgTransporter) 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)
@ -107,9 +127,7 @@ func (vm *Manager) 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
} }
@ -139,15 +157,36 @@ func (vm *Manager) Reload(cfgs map[string]config.VisitorConf) {
} }
} }
func (vm *Manager) 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
} }

View File

@ -24,7 +24,7 @@ import (
"sync" "sync"
"time" "time"
frpIo "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
fmux "github.com/hashicorp/yamux" fmux "github.com/hashicorp/yamux"
quic "github.com/quic-go/quic-go" quic "github.com/quic-go/quic-go"
"golang.org/x/time/rate" "golang.org/x/time/rate"
@ -33,7 +33,7 @@ import (
"github.com/fatedier/frp/pkg/msg" "github.com/fatedier/frp/pkg/msg"
"github.com/fatedier/frp/pkg/nathole" "github.com/fatedier/frp/pkg/nathole"
"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"
"github.com/fatedier/frp/pkg/util/util" "github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/xlog" "github.com/fatedier/frp/pkg/util/xlog"
) )
@ -59,12 +59,15 @@ func (sv *XTCPVisitor) Run() (err error) {
sv.session = NewQUICTunnelSession(&sv.clientCfg) sv.session = NewQUICTunnelSession(&sv.clientCfg)
} }
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort))) if sv.cfg.BindPort > 0 {
if err != nil { sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
return if err != nil {
return
}
go sv.worker()
} }
go sv.worker() go sv.internalConnWorker()
go sv.processTunnelStartEvents() go sv.processTunnelStartEvents()
if sv.cfg.KeepTunnelOpen { if sv.cfg.KeepTunnelOpen {
sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour) sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour)
@ -74,8 +77,12 @@ func (sv *XTCPVisitor) Run() (err error) {
} }
func (sv *XTCPVisitor) Close() { func (sv *XTCPVisitor) Close() {
sv.l.Close() sv.mu.Lock()
sv.cancel() defer sv.mu.Unlock()
sv.BaseVisitor.Close()
if sv.cancel != nil {
sv.cancel()
}
if sv.session != nil { if sv.session != nil {
sv.session.Close() sv.session.Close()
} }
@ -89,7 +96,18 @@ func (sv *XTCPVisitor) worker() {
xl.Warn("xtcp local listener closed") xl.Warn("xtcp local listener closed")
return 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) go sv.handleConn(conn)
} }
} }
@ -139,31 +157,53 @@ func (sv *XTCPVisitor) keepTunnelOpenWorker() {
func (sv *XTCPVisitor) handleConn(userConn net.Conn) { func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
xl := xlog.FromContextSafe(sv.ctx) xl := xlog.FromContextSafe(sv.ctx)
defer userConn.Close() isConnTrasfered := false
defer func() {
if !isConnTrasfered {
userConn.Close()
}
}()
xl.Debug("get a new xtcp user connection") xl.Debug("get a new xtcp user connection")
// Open a tunnel connection to the server. If there is already a successful hole-punching 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. // it will be reused. Otherwise, it will block and wait for a successful hole-punching connection until timeout.
tunnelConn, err := sv.openTunnel() 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 { if err != nil {
xl.Error("open tunnel error: %v", err) 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 return
} }
var muxConnRWCloser io.ReadWriteCloser = tunnelConn var muxConnRWCloser io.ReadWriteCloser = tunnelConn
if sv.cfg.UseEncryption { if sv.cfg.UseEncryption {
muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk)) muxConnRWCloser, err = libio.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
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 sv.cfg.UseCompression { if sv.cfg.UseCompression {
muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser) muxConnRWCloser = libio.WithCompression(muxConnRWCloser)
} }
_, _, errs := frpIo.Join(userConn, muxConnRWCloser) _, _, errs := libio.Join(userConn, muxConnRWCloser)
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)
@ -171,7 +211,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
} }
// openTunnel will open a tunnel connection to the target server. // openTunnel will open a tunnel connection to the target server.
func (sv *XTCPVisitor) openTunnel() (conn net.Conn, err error) { func (sv *XTCPVisitor) openTunnel(ctx context.Context) (conn net.Conn, err error) {
xl := xlog.FromContextSafe(sv.ctx) xl := xlog.FromContextSafe(sv.ctx)
ticker := time.NewTicker(500 * time.Millisecond) ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop() defer ticker.Stop()
@ -185,6 +225,8 @@ func (sv *XTCPVisitor) openTunnel() (conn net.Conn, err error) {
select { select {
case <-sv.ctx.Done(): case <-sv.ctx.Done():
return nil, sv.ctx.Err() return nil, sv.ctx.Err()
case <-ctx.Done():
return nil, ctx.Err()
case <-immediateTrigger: case <-immediateTrigger:
conn, err = sv.getTunnelConn() conn, err = sv.getTunnelConn()
case <-ticker.C: case <-ticker.C:
@ -224,11 +266,13 @@ func (sv *XTCPVisitor) getTunnelConn() (net.Conn, error) {
// 4. Create a tunnel session using an underlying UDP connection. // 4. Create a tunnel session using an underlying UDP connection.
func (sv *XTCPVisitor) makeNatHole() { func (sv *XTCPVisitor) makeNatHole() {
xl := xlog.FromContextSafe(sv.ctx) xl := xlog.FromContextSafe(sv.ctx)
if err := nathole.PreCheck(sv.ctx, sv.msgTransporter, sv.cfg.ServerName, 5*time.Second); err != nil { xl.Trace("makeNatHole start")
if err := nathole.PreCheck(sv.ctx, sv.helper.MsgTransporter(), sv.cfg.ServerName, 5*time.Second); err != nil {
xl.Warn("nathole precheck error: %v", err) xl.Warn("nathole precheck error: %v", err)
return return
} }
xl.Trace("nathole prepare start")
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer}) prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer})
if err != nil { if err != nil {
xl.Warn("nathole prepare error: %v", err) xl.Warn("nathole prepare error: %v", err)
@ -252,7 +296,8 @@ func (sv *XTCPVisitor) makeNatHole() {
AssistedAddrs: prepareResult.AssistedAddrs, AssistedAddrs: prepareResult.AssistedAddrs,
} }
natHoleRespMsg, err := nathole.ExchangeInfo(sv.ctx, sv.msgTransporter, transactionID, natHoleVisitorMsg, 5*time.Second) xl.Trace("nathole exchange info start")
natHoleRespMsg, err := nathole.ExchangeInfo(sv.ctx, sv.helper.MsgTransporter(), transactionID, natHoleVisitorMsg, 5*time.Second)
if err != nil { if err != nil {
listenConn.Close() listenConn.Close()
xl.Warn("nathole exchange info error: %v", err) xl.Warn("nathole exchange info error: %v", err)
@ -302,14 +347,14 @@ func (ks *KCPTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) er
if err != nil { if err != nil {
return fmt.Errorf("dial udp error: %v", err) return fmt.Errorf("dial udp error: %v", err)
} }
remote, err := frpNet.NewKCPConnFromUDP(lConn, true, raddr.String()) remote, err := utilnet.NewKCPConnFromUDP(lConn, true, raddr.String())
if err != nil { if err != nil {
return fmt.Errorf("create kcp connection from udp connection error: %v", err) return fmt.Errorf("create kcp connection from udp connection error: %v", err)
} }
fmuxCfg := fmux.DefaultConfig() fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = 10 * time.Second fmuxCfg.KeepAliveInterval = 10 * time.Second
fmuxCfg.MaxStreamWindowSize = 2 * 1024 * 1024 fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
fmuxCfg.LogOutput = io.Discard fmuxCfg.LogOutput = io.Discard
session, err := fmux.Client(remote, fmuxCfg) session, err := fmux.Client(remote, fmuxCfg)
if err != nil { if err != nil {
@ -393,7 +438,7 @@ func (qs *QUICTunnelSession) OpenConn(ctx context.Context) (net.Conn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return frpNet.QuicStreamToNetConn(stream, session), nil return utilnet.QuicStreamToNetConn(stream, session), nil
} }
func (qs *QUICTunnelSession) Close() { func (qs *QUICTunnelSession) Close() {

View File

@ -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)

View File

@ -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)

View File

@ -94,7 +94,7 @@ func RegisterCommonFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") cmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days") cmd.PersistentFlags().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", "", true, "enable frpc tls")
cmd.PersistentFlags().StringVarP(&dnsServer, "dns_server", "", "", "specify dns server instead of using system default one") cmd.PersistentFlags().StringVarP(&dnsServer, "dns_server", "", "", "specify dns server instead of using system default one")
} }
@ -117,7 +117,6 @@ var rootCmd = &cobra.Command{
// 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
@ -199,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)
@ -214,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 {

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -107,7 +107,8 @@ connect_server_local_ip = 0.0.0.0
# quic_max_idle_timeout = 30 # quic_max_idle_timeout = 30
# quic_max_incoming_streams = 100000 # quic_max_incoming_streams = 100000
# if tls_enable is true, frpc will connect frps by tls # If tls_enable is true, frpc will connect frps by tls.
# Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
tls_enable = true tls_enable = true
# tls_cert_file = client.crt # tls_cert_file = client.crt
@ -140,9 +141,10 @@ udp_packet_size = 1500
# include other config files for proxies. # include other config files for proxies.
# includes = ./confd/*.ini # includes = ./confd/*.ini
# By default, frpc will connect frps with first custom byte if tls is enabled. # If the disable_custom_tls_first_byte is set to false, frpc will establish a connection with frps using the
# If DisableCustomTLSFirstByte is true, frpc will not send that custom byte. # first custom byte when tls is enabled.
disable_custom_tls_first_byte = false # Since v0.50.0, the default value has been changed to true, and the first custom byte is disabled by default.
disable_custom_tls_first_byte = true
# Enable golang pprof handlers in admin listener. # Enable golang pprof handlers in admin listener.
# Admin port must be set first. # Admin port must be set first.
@ -326,6 +328,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]
@ -337,6 +342,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
@ -348,13 +355,20 @@ 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
@ -363,6 +377,8 @@ keep_tunnel_open = false
# effective when keep_tunnel_open is set to true, the number of attempts to punch through per hour # effective when keep_tunnel_open is set to true, the number of attempts to punch through per hour
max_retries_an_hour = 8 max_retries_an_hour = 8
min_retry_interval = 90 min_retry_interval = 90
# fallback_to = stcp_visitor
# fallback_timeout_ms = 500
[tcpmuxhttpconnect] [tcpmuxhttpconnect]
type = tcpmux type = tcpmux

63
hack/download.sh Executable file
View File

@ -0,0 +1,63 @@
#!/bin/sh
OS="$(go env GOOS)"
ARCH="$(go env GOARCH)"
if [ "${TARGET_OS}" ]; then
OS="${TARGET_OS}"
fi
if [ "${TARGET_ARCH}" ]; then
ARCH="${TARGET_ARCH}"
fi
# Determine the latest version by version number ignoring alpha, beta, and rc versions.
if [ "${FRP_VERSION}" = "" ] ; then
FRP_VERSION="$(curl -sL https://github.com/fatedier/frp/releases | \
grep -o 'releases/tag/v[0-9]*.[0-9]*.[0-9]*"' | sort -V | \
tail -1 | awk -F'/' '{ print $3}')"
FRP_VERSION="${FRP_VERSION%?}"
FRP_VERSION="${FRP_VERSION#?}"
fi
if [ "${FRP_VERSION}" = "" ] ; then
printf "Unable to get latest frp version. Set FRP_VERSION env var and re-run. For example: export FRP_VERSION=1.0.0"
exit 1;
fi
SUFFIX=".tar.gz"
if [ "${OS}" = "windows" ] ; then
SUFFIX=".zip"
fi
NAME="frp_${FRP_VERSION}_${OS}_${ARCH}${SUFFIX}"
DIR_NAME="frp_${FRP_VERSION}_${OS}_${ARCH}"
URL="https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${NAME}"
download_and_extract() {
printf "Downloading %s from %s ...\n" "$NAME" "${URL}"
if ! curl -o /dev/null -sIf "${URL}"; then
printf "\n%s is not found, please specify a valid FRP_VERSION\n" "${URL}"
exit 1
fi
curl -fsLO "${URL}"
filename=$NAME
if [ "${OS}" = "windows" ]; then
unzip "${filename}"
else
tar -xzf "${filename}"
fi
rm "${filename}"
if [ "${TARGET_DIRNAME}" ]; then
mv "${DIR_NAME}" "${TARGET_DIRNAME}"
DIR_NAME="${TARGET_DIRNAME}"
fi
}
download_and_extract
printf ""
printf "\nfrp %s Download Complete!\n" "$FRP_VERSION"
printf "\n"
printf "frp has been successfully downloaded into the %s folder on your system.\n" "$DIR_NAME"
printf "\n"

View File

@ -1,20 +1,30 @@
#!/usr/bin/env bash #!/bin/sh
ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd) SCRIPT=$(readlink -f "$0")
ROOT=$(unset CDPATH && cd "$(dirname "$SCRIPT")/.." && pwd)
which ginkgo &> /dev/null ginkgo_command=$(which ginkgo 2>/dev/null)
if [ $? -ne 0 ]; then if [ -z "$ginkgo_command" ]; then
echo "ginkgo not found, try to install..." echo "ginkgo not found, try to install..."
go install github.com/onsi/ginkgo/v2/ginkgo@v2.8.3 go install github.com/onsi/ginkgo/v2/ginkgo@v2.8.3
fi fi
debug=false debug=false
if [ x${DEBUG} == x"true" ]; then if [ "x${DEBUG}" = "xtrue" ]; then
debug=true debug=true
fi fi
logLevel=debug logLevel=debug
if [ x${LOG_LEVEL} != x"" ]; then if [ "${LOG_LEVEL}" ]; then
logLevel=${LOG_LEVEL} logLevel="${LOG_LEVEL}"
fi fi
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} frpcPath=${ROOT}/bin/frpc
if [ "${FRPC_PATH}" ]; then
frpcPath="${FRPC_PATH}"
fi
frpsPath=${ROOT}/bin/frps
if [ "${FRPS_PATH}" ]; then
frpsPath="${FRPS_PATH}"
fi
ginkgo -nodes=8 --poll-progress-after=60s ${ROOT}/test/e2e -- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}

View File

@ -127,6 +127,7 @@ type ClientCommonConf struct {
// TLSEnable specifies whether or not TLS should be used when communicating // TLSEnable specifies whether or not TLS should be used when communicating
// with the server. If "tls_cert_file" and "tls_key_file" are valid, // with the server. If "tls_cert_file" and "tls_key_file" are valid,
// client will load the supplied tls configuration. // client will load the supplied tls configuration.
// Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
TLSEnable bool `ini:"tls_enable" json:"tls_enable"` TLSEnable bool `ini:"tls_enable" json:"tls_enable"`
// TLSCertPath specifies the path of the cert file that client will // TLSCertPath specifies the path of the cert file that client will
// load. It only works when "tls_enable" is true and "tls_key_file" is valid. // load. It only works when "tls_enable" is true and "tls_key_file" is valid.
@ -142,8 +143,9 @@ type ClientCommonConf struct {
// TLSServerName specifies the custom server name of tls certificate. By // TLSServerName specifies the custom server name of tls certificate. By
// default, server name if same to ServerAddr. // default, server name if same to ServerAddr.
TLSServerName string `ini:"tls_server_name" json:"tls_server_name"` TLSServerName string `ini:"tls_server_name" json:"tls_server_name"`
// By default, frpc will connect frps with first custom byte if tls is enabled. // If the disable_custom_tls_first_byte is set to false, frpc will establish a connection with frps using the
// If DisableCustomTLSFirstByte is true, frpc will not send that custom byte. // first custom byte when tls is enabled.
// Since v0.50.0, the default value has been changed to true, and the first custom byte is disabled by default.
DisableCustomTLSFirstByte bool `ini:"disable_custom_tls_first_byte" json:"disable_custom_tls_first_byte"` DisableCustomTLSFirstByte bool `ini:"disable_custom_tls_first_byte" json:"disable_custom_tls_first_byte"`
// HeartBeatInterval specifies at what interval heartbeats are sent to the // HeartBeatInterval specifies at what interval heartbeats are sent to the
// server, in seconds. It is not recommended to change this value. By // server, in seconds. It is not recommended to change this value. By
@ -168,32 +170,34 @@ type ClientCommonConf struct {
// GetDefaultClientConf returns a client configuration with default values. // GetDefaultClientConf returns a client configuration with default values.
func GetDefaultClientConf() ClientCommonConf { func GetDefaultClientConf() ClientCommonConf {
return ClientCommonConf{ return ClientCommonConf{
ClientConfig: auth.GetDefaultClientConf(), ClientConfig: auth.GetDefaultClientConf(),
ServerAddr: "0.0.0.0", ServerAddr: "0.0.0.0",
ServerPort: 7000, ServerPort: 7000,
NatHoleSTUNServer: "stun.easyvoip.com:3478", NatHoleSTUNServer: "stun.easyvoip.com:3478",
DialServerTimeout: 10, DialServerTimeout: 10,
DialServerKeepAlive: 7200, DialServerKeepAlive: 7200,
HTTPProxy: os.Getenv("http_proxy"), HTTPProxy: os.Getenv("http_proxy"),
LogFile: "console", LogFile: "console",
LogWay: "console", LogWay: "console",
LogLevel: "info", LogLevel: "info",
LogMaxDays: 3, LogMaxDays: 3,
AdminAddr: "127.0.0.1", AdminAddr: "127.0.0.1",
PoolCount: 1, PoolCount: 1,
TCPMux: true, TCPMux: true,
TCPMuxKeepaliveInterval: 60, TCPMuxKeepaliveInterval: 60,
LoginFailExit: true, LoginFailExit: true,
Start: make([]string, 0), Start: make([]string, 0),
Protocol: "tcp", Protocol: "tcp",
QUICKeepalivePeriod: 10, QUICKeepalivePeriod: 10,
QUICMaxIdleTimeout: 30, QUICMaxIdleTimeout: 30,
QUICMaxIncomingStreams: 100000, QUICMaxIncomingStreams: 100000,
HeartbeatInterval: 30, TLSEnable: true,
HeartbeatTimeout: 90, DisableCustomTLSFirstByte: true,
Metas: make(map[string]string), HeartbeatInterval: 30,
UDPPacketSize: 1500, HeartbeatTimeout: 90,
IncludeConfigFiles: make([]string, 0), Metas: make(map[string]string),
UDPPacketSize: 1500,
IncludeConfigFiles: make([]string, 0),
} }
} }
@ -352,7 +356,7 @@ func LoadAllProxyConfsFromIni(
case "visitor": case "visitor":
newConf, newErr := NewVisitorConfFromIni(prefix, name, section) newConf, newErr := NewVisitorConfFromIni(prefix, name, section)
if newErr != nil { if newErr != nil {
return nil, nil, newErr return nil, nil, fmt.Errorf("failed to parse visitor %s, err: %v", name, newErr)
} }
visitorConfs[prefix+name] = newConf visitorConfs[prefix+name] = newConf
default: default:

View File

@ -258,40 +258,41 @@ func Test_LoadClientCommonConf(t *testing.T) {
OidcTokenEndpointURL: "endpoint_url", OidcTokenEndpointURL: "endpoint_url",
}, },
}, },
ServerAddr: "0.0.0.9", ServerAddr: "0.0.0.9",
ServerPort: 7009, ServerPort: 7009,
NatHoleSTUNServer: "stun.easyvoip.com:3478", NatHoleSTUNServer: "stun.easyvoip.com:3478",
DialServerTimeout: 10, DialServerTimeout: 10,
DialServerKeepAlive: 7200, DialServerKeepAlive: 7200,
HTTPProxy: "http://user:passwd@192.168.1.128:8080", HTTPProxy: "http://user:passwd@192.168.1.128:8080",
LogFile: "./frpc.log9", LogFile: "./frpc.log9",
LogWay: "file", LogWay: "file",
LogLevel: "info9", LogLevel: "info9",
LogMaxDays: 39, LogMaxDays: 39,
DisableLogColor: false, DisableLogColor: false,
AdminAddr: "127.0.0.9", AdminAddr: "127.0.0.9",
AdminPort: 7409, AdminPort: 7409,
AdminUser: "admin9", AdminUser: "admin9",
AdminPwd: "admin9", AdminPwd: "admin9",
AssetsDir: "./static9", AssetsDir: "./static9",
PoolCount: 59, PoolCount: 59,
TCPMux: true, TCPMux: true,
TCPMuxKeepaliveInterval: 60, TCPMuxKeepaliveInterval: 60,
User: "your_name", User: "your_name",
LoginFailExit: true, LoginFailExit: true,
Protocol: "tcp", Protocol: "tcp",
QUICKeepalivePeriod: 10, QUICKeepalivePeriod: 10,
QUICMaxIdleTimeout: 30, QUICMaxIdleTimeout: 30,
QUICMaxIncomingStreams: 100000, QUICMaxIncomingStreams: 100000,
TLSEnable: true, TLSEnable: true,
TLSCertFile: "client.crt", TLSCertFile: "client.crt",
TLSKeyFile: "client.key", TLSKeyFile: "client.key",
TLSTrustedCaFile: "ca.crt", TLSTrustedCaFile: "ca.crt",
TLSServerName: "example.com", TLSServerName: "example.com",
DNSServer: "8.8.8.9", DisableCustomTLSFirstByte: true,
Start: []string{"ssh", "dns"}, DNSServer: "8.8.8.9",
HeartbeatInterval: 39, Start: []string{"ssh", "dns"},
HeartbeatTimeout: 99, HeartbeatInterval: 39,
HeartbeatTimeout: 99,
Metas: map[string]string{ Metas: map[string]string{
"var1": "123", "var1": "123",
"var2": "234", "var2": "234",
@ -500,8 +501,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 +516,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,9 +666,10 @@ func Test_LoadClientBasicConf(t *testing.T) {
BindAddr: "127.0.0.1", BindAddr: "127.0.0.1",
BindPort: 9001, BindPort: 9001,
}, },
Protocol: "quic", Protocol: "quic",
MaxRetriesAnHour: 8, MaxRetriesAnHour: 8,
MinRetryInterval: 90, MinRetryInterval: 90,
FallbackTimeoutMs: 1000,
}, },
} }

File diff suppressed because it is too large Load Diff

View File

@ -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",
},
}, },
}, },
{ {

View File

@ -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
@ -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{

View File

@ -34,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 {
@ -47,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 {
@ -63,10 +70,12 @@ type STCPVisitorConf struct {
type XTCPVisitorConf struct { type XTCPVisitorConf struct {
BaseVisitorConf `ini:",extends"` BaseVisitorConf `ini:",extends"`
Protocol string `ini:"protocol" json:"protocol,omitempty"` Protocol string `ini:"protocol" json:"protocol,omitempty"`
KeepTunnelOpen bool `ini:"keep_tunnel_open" json:"keep_tunnel_open,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"` MaxRetriesAnHour int `ini:"max_retries_an_hour" json:"max_retries_an_hour,omitempty"`
MinRetryInterval int `ini:"min_retry_interval" json:"min_retry_interval,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.
@ -76,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)
} }
@ -86,19 +94,19 @@ func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (Vi
visitorType := section.Key("type").String() visitorType := section.Key("type").String()
if visitorType == "" { if visitorType == "" {
return nil, fmt.Errorf("visitor [%s] type shouldn't be empty", name) return nil, fmt.Errorf("type shouldn't be empty")
} }
conf := DefaultVisitorConf(visitorType) conf := DefaultVisitorConf(visitorType)
if conf == nil { if conf == nil {
return nil, fmt.Errorf("visitor [%s] type [%s] error", name, visitorType) return nil, fmt.Errorf("type [%s] error", visitorType)
} }
if err := conf.UnmarshalFromIni(prefix, name, section); err != nil { if err := conf.UnmarshalFromIni(prefix, name, section); err != nil {
return nil, fmt.Errorf("visitor [%s] type [%s] error", name, visitorType) return nil, fmt.Errorf("type [%s] error", visitorType)
} }
if err := conf.Check(); err != nil { if err := conf.Validate(); err != nil {
return nil, err return nil, err
} }
@ -106,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
@ -134,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
} }
@ -149,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
} }
@ -165,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 {
@ -202,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
} }
@ -215,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 {
@ -241,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
} }
@ -254,26 +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
if cfg.Protocol != cmpConf.Protocol ||
cfg.KeepTunnelOpen != cmpConf.KeepTunnelOpen ||
cfg.MaxRetriesAnHour != cmpConf.MaxRetriesAnHour ||
cfg.MinRetryInterval != cmpConf.MinRetryInterval {
return false
}
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 {
@ -290,11 +237,14 @@ func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
if cfg.MinRetryInterval <= 0 { if cfg.MinRetryInterval <= 0 {
cfg.MinRetryInterval = 90 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
} }

View File

@ -87,9 +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", Protocol: "quic",
MaxRetriesAnHour: 8, MaxRetriesAnHour: 8,
MinRetryInterval: 90, MinRetryInterval: 90,
FallbackTimeoutMs: 1000,
}, },
}, },
} }

View File

@ -110,8 +110,9 @@ type NewProxy struct {
Headers map[string]string `json:"headers,omitempty"` Headers map[string]string `json:"headers,omitempty"`
RouteByHTTPUser string `json:"route_by_http_user,omitempty"` RouteByHTTPUser string `json:"route_by_http_user,omitempty"`
// stcp // stcp, sudp, xtcp
Sk string `json:"sk,omitempty"` Sk string `json:"sk,omitempty"`
AllowUsers []string `json:"allow_users,omitempty"`
// tcpmux // tcpmux
Multiplexer string `json:"multiplexer,omitempty"` Multiplexer string `json:"multiplexer,omitempty"`
@ -145,6 +146,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"`

View File

@ -63,20 +63,20 @@ var (
} }
// mode 2, HardNAT is receiver, EasyNAT is sender // mode 2, HardNAT is receiver, EasyNAT is sender
// sender, portsRandomNumber 1000, sendDelayMs 2000 | receiver, listen 256 ports, ttl 7 // sender, portsRandomNumber 1000, sendDelayMs 3000 | receiver, listen 256 ports, ttl 7
// sender, portsRandomNumber 1000, sendDelayMs 2000 | receiver, listen 256 ports, ttl 4 // sender, portsRandomNumber 1000, sendDelayMs 3000 | receiver, listen 256 ports, ttl 4
// sender, portsRandomNumber 1000, sendDelayMs 2000 | receiver, listen 256 ports // sender, portsRandomNumber 1000, sendDelayMs 3000 | receiver, listen 256 ports
mode2Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{ mode2Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
lo.T2( lo.T2(
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 7}, RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 7},
), ),
lo.T2( lo.T2(
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 4}, RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 4},
), ),
lo.T2( lo.T2(
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256}, RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256},
), ),
} }
@ -98,21 +98,21 @@ var (
} }
// mode 4, Regular ports changes are usually the sender. // mode 4, Regular ports changes are usually the sender.
// sender, portsRandomNumber 1000, sendDelayMs: 2000 | receiver, listen 256 ports, ttl 7, portsRangeNumber 10 // 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 10 // sender, portsRandomNumber 1000, sendDelayMs: 2000 | receiver, listen 256 ports, ttl 4, portsRangeNumber 2
// sender, portsRandomNumber 1000, SendDelayMs: 2000 | receiver, listen 256 ports, portsRangeNumber 10 // sender, portsRandomNumber 1000, SendDelayMs: 2000 | receiver, listen 256 ports, portsRangeNumber 2
mode4Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{ mode4Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
lo.T2( lo.T2(
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 7, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 7, PortsRangeNumber: 2},
), ),
lo.T2( lo.T2(
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 4, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 4, PortsRangeNumber: 2},
), ),
lo.T2( lo.T2(
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 3000},
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, PortsRangeNumber: 2},
), ),
} }
) )

View File

@ -85,11 +85,6 @@ func ClassifyNATFeature(addresses []string, localIPs []string) (*NatFeature, err
} }
} }
natFeature.PortsDifference = portMax - portMin
if natFeature.PortsDifference <= 10 && natFeature.PortsDifference >= 1 {
natFeature.RegularPortsChange = true
}
switch { switch {
case ipChanged && portChanged: case ipChanged && portChanged:
natFeature.NatType = HardNAT natFeature.NatType = HardNAT
@ -104,6 +99,12 @@ func ClassifyNATFeature(addresses []string, localIPs []string) (*NatFeature, err
natFeature.NatType = EasyNAT natFeature.NatType = EasyNAT
natFeature.Behavior = BehaviorNoChange natFeature.Behavior = BehaviorNoChange
} }
if natFeature.Behavior == BehaviorPortChanged {
natFeature.PortsDifference = portMax - portMin
if natFeature.PortsDifference <= 5 && natFeature.PortsDifference >= 1 {
natFeature.RegularPortsChange = true
}
}
return natFeature, nil return natFeature, nil
} }

View File

@ -43,9 +43,10 @@ func NewTransactionID() string {
} }
type ClientCfg struct { type ClientCfg struct {
name string name string
sk string sk string
sidCh chan string allowUsers []string
sidCh chan string
} }
type Session struct { type Session struct {
@ -120,16 +121,20 @@ func (c *Controller) CleanWorker(ctx context.Context) {
} }
} }
func (c *Controller) ListenClient(name string, sk string) chan string { func (c *Controller) ListenClient(name string, sk string, allowUsers []string) (chan string, error) {
cfg := &ClientCfg{ cfg := &ClientCfg{
name: name, name: name,
sk: sk, sk: sk,
sidCh: make(chan string), allowUsers: allowUsers,
sidCh: make(chan string),
} }
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if _, ok := c.clientCfgs[name]; ok {
return nil, fmt.Errorf("proxy [%s] is repeated", name)
}
c.clientCfgs[name] = cfg c.clientCfgs[name] = cfg
return cfg.sidCh return cfg.sidCh, nil
} }
func (c *Controller) CloseClient(name string) { func (c *Controller) CloseClient(name string) {
@ -144,14 +149,18 @@ func (c *Controller) GenSid() string {
return fmt.Sprintf("%d%s", t, id) return fmt.Sprintf("%d%s", t, id)
} }
func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.MessageTransporter) { func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.MessageTransporter, visitorUser string) {
if m.PreCheck { if m.PreCheck {
_, ok := c.clientCfgs[m.ProxyName] cfg, ok := c.clientCfgs[m.ProxyName]
if !ok { if !ok {
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName))) _ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)))
} else { return
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, ""))
} }
if !lo.Contains(cfg.allowUsers, visitorUser) && !lo.Contains(cfg.allowUsers, "*") {
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp visitor user [%s] not allowed for [%s]", visitorUser, m.ProxyName)))
return
}
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, ""))
return return
} }
@ -185,7 +194,7 @@ func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, err.Error())) _ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, err.Error()))
return return
} }
log.Trace("handle visitor message, sid [%s]", sid) log.Trace("handle visitor message, sid [%s], server name: %s", sid, m.ProxyName)
defer func() { defer func() {
c.mu.Lock() c.mu.Lock()
@ -247,7 +256,7 @@ func (c *Controller) HandleClient(m *msg.NatHoleClient, transporter transport.Me
if !ok { if !ok {
return return
} }
log.Trace("handle client message, sid [%s]", session.sid) log.Trace("handle client message, sid [%s], server name: %s", session.sid, m.ProxyName)
session.clientMsg = m session.clientMsg = m
session.clientTransporter = transporter session.clientTransporter = transporter
select { select {

View File

@ -384,7 +384,7 @@ func sendSidMessageToRangePorts(
if err := sendFunc(conn, detectAddr); err != nil { if err := sendFunc(conn, detectAddr); err != nil {
xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err) xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
} }
time.Sleep(5 * time.Millisecond) time.Sleep(2 * time.Millisecond)
} }
} }
} }

View File

@ -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)
} }

View File

@ -23,10 +23,10 @@ import (
"strings" "strings"
"time" "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" "github.com/fatedier/frp/pkg/util/util"
) )
@ -69,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 {
@ -86,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
} }
@ -158,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 {
@ -213,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) {

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -22,7 +22,7 @@ import (
"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"
@ -65,8 +65,8 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
} }
router := mux.NewRouter() router := mux.NewRouter()
router.Use(frpNet.NewHTTPAuthMiddleware(httpUser, httpPasswd).SetAuthFailDelay(200 * time.Millisecond).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,
} }
@ -77,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)
} }

View File

@ -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 {

View File

@ -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"
} }

View File

@ -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))

View File

@ -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 {

View File

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

View File

@ -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) {

View File

@ -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 {

View File

@ -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 {

View File

@ -30,7 +30,7 @@ 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/transport"
@ -268,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")
@ -283,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
} }
@ -394,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{
@ -524,7 +524,7 @@ func (ctl *Control) manager() {
} }
func (ctl *Control) HandleNatHoleVisitor(m *msg.NatHoleVisitor) { func (ctl *Control) HandleNatHoleVisitor(m *msg.NatHoleVisitor) {
ctl.rc.NatHoleController.HandleVisitor(m, ctl.msgTransporter) ctl.rc.NatHoleController.HandleVisitor(m, ctl.msgTransporter, ctl.loginMsg.User)
} }
func (ctl *Control) HandleNatHoleClient(m *msg.NatHoleClient) { func (ctl *Control) HandleNatHoleClient(m *msg.NatHoleClient) {
@ -537,7 +537,7 @@ func (ctl *Control) HandleNatHoleReport(m *msg.NatHoleReport) {
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 validate.
pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg) pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg)
if err != nil { if err != nil {
return return
@ -550,8 +550,8 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
RunID: ctl.runID, RunID: ctl.runID,
} }
// NewProxy will return a interface Proxy. // NewProxy will return an interface Proxy.
// In fact it create different proxies by different proxy type, we just call run() here. // In fact, it creates different proxies based on the proxy type. We just call run() here.
pxy, err := proxy.NewProxy(ctl.ctx, userInfo, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg, ctl.loginMsg) pxy, err := proxy.NewProxy(ctl.ctx, userInfo, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg, ctl.loginMsg)
if err != nil { if err != nil {
return remoteAddr, err return remoteAddr, err
@ -577,6 +577,11 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
}() }()
} }
if ctl.pxyManager.Exist(pxyMsg.ProxyName) {
err = fmt.Errorf("proxy [%s] already exists", pxyMsg.ProxyName)
return
}
remoteAddr, err = pxy.Run() remoteAddr, err = pxy.Run()
if err != nil { if err != nil {
return return
@ -614,7 +619,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{

View File

@ -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).SetAuthFailDelay(200 * time.Millisecond).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)

View File

@ -17,19 +17,23 @@ package proxy
import ( import (
"io" "io"
"net" "net"
"reflect"
"strings" "strings"
frpIo "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
"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"
) )
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.HTTPProxyConf{}), NewHTTPProxy)
}
type HTTPProxy struct { type HTTPProxy struct {
*BaseProxy *BaseProxy
cfg *config.HTTPProxyConf cfg *config.HTTPProxyConf
@ -37,6 +41,17 @@ type HTTPProxy struct {
closeFuncs []func() closeFuncs []func()
} }
func NewHTTPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.HTTPProxyConf)
if !ok {
return nil
}
return &HTTPProxy{
BaseProxy: baseProxy,
cfg: unwrapped,
}
}
func (pxy *HTTPProxy) Run() (remoteAddr string, err error) { func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
routeConfig := vhost.RouteConfig{ routeConfig := vhost.RouteConfig{
@ -137,10 +152,6 @@ func (pxy *HTTPProxy) GetConf() config.ProxyConf {
return pxy.cfg return pxy.cfg
} }
func (pxy *HTTPProxy) GetLimiter() *rate.Limiter {
return pxy.limiter
}
func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err error) { func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err error) {
xl := pxy.xl xl := pxy.xl
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr) rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
@ -157,31 +168,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)

View File

@ -15,20 +15,34 @@
package proxy package proxy
import ( import (
"reflect"
"strings" "strings"
"golang.org/x/time/rate"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
"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"
) )
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.HTTPSProxyConf{}), NewHTTPSProxy)
}
type HTTPSProxy struct { type HTTPSProxy struct {
*BaseProxy *BaseProxy
cfg *config.HTTPSProxyConf cfg *config.HTTPSProxyConf
} }
func NewHTTPSProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.HTTPSProxyConf)
if !ok {
return nil
}
return &HTTPSProxy{
BaseProxy: baseProxy,
cfg: unwrapped,
}
}
func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) { func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
routeConfig := &vhost.RouteConfig{} routeConfig := &vhost.RouteConfig{}
@ -67,7 +81,7 @@ func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) {
addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort)) addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHTTPSPort))
} }
pxy.startListenHandler(pxy, HandleUserTCPConnection) pxy.startCommonTCPListenersHandler()
remoteAddr = strings.Join(addrs, ",") remoteAddr = strings.Join(addrs, ",")
return return
} }
@ -76,10 +90,6 @@ func (pxy *HTTPSProxy) GetConf() config.ProxyConf {
return pxy.cfg return pxy.cfg
} }
func (pxy *HTTPSProxy) GetLimiter() *rate.Limiter {
return pxy.limiter
}
func (pxy *HTTPSProxy) Close() { func (pxy *HTTPSProxy) Close() {
pxy.BaseProxy.Close() pxy.BaseProxy.Close()
} }

View File

@ -19,23 +19,30 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"reflect"
"strconv" "strconv"
"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"
) )
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, config.ProxyConf) Proxy{}
func RegisterProxyFactory(proxyConfType reflect.Type, factory func(*BaseProxy, config.ProxyConf) Proxy) {
proxyFactoryRegistry[proxyConfType] = factory
}
type GetWorkConnFn func() (net.Conn, error) type GetWorkConnFn func() (net.Conn, error)
type Proxy interface { type Proxy interface {
@ -63,6 +70,7 @@ type BaseProxy struct {
limiter *rate.Limiter limiter *rate.Limiter
userInfo plugin.UserInfo userInfo plugin.UserInfo
loginMsg *msg.Login loginMsg *msg.Login
pxyConf config.ProxyConf
mu sync.RWMutex mu sync.RWMutex
xl *xlog.Logger xl *xlog.Logger
@ -93,6 +101,10 @@ func (pxy *BaseProxy) GetLoginMsg() *msg.Login {
return pxy.loginMsg return pxy.loginMsg
} }
func (pxy *BaseProxy) GetLimiter() *rate.Limiter {
return pxy.limiter
}
func (pxy *BaseProxy) Close() { func (pxy *BaseProxy) Close() {
xl := xlog.FromContextSafe(pxy.ctx) xl := xlog.FromContextSafe(pxy.ctx)
xl.Info("proxy closing") xl.Info("proxy closing")
@ -113,7 +125,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
@ -155,10 +167,8 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn,
return return
} }
// startListenHandler start a goroutine handler for each listener. // startCommonTCPListenersHandler start a goroutine handler for each listener.
// p: p will just be passed to handler(Proxy, frpNet.Conn). func (pxy *BaseProxy) startCommonTCPListenersHandler() {
// 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)) {
xl := xlog.FromContextSafe(pxy.ctx) xl := xlog.FromContextSafe(pxy.ctx)
for _, listener := range pxy.listeners { for _, listener := range pxy.listeners {
go func(l net.Listener) { go func(l net.Listener) {
@ -187,97 +197,25 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn,
return return
} }
xl.Info("get a user connection [%s]", c.RemoteAddr().String()) xl.Info("get a user connection [%s]", c.RemoteAddr().String())
go handler(p, c, pxy.serverCfg) go pxy.handleUserTCPConnection(c)
} }
}(listener) }(listener)
} }
} }
func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.ResourceController, poolCount int,
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf, loginMsg *msg.Login,
) (pxy Proxy, err error) {
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseInfo().ProxyName)
var limiter *rate.Limiter
limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
if limitBytes > 0 && pxyConf.GetBaseInfo().BandwidthLimitMode == config.BandwidthLimitModeServer {
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
}
basePxy := BaseProxy{
name: pxyConf.GetBaseInfo().ProxyName,
rc: rc,
listeners: make([]net.Listener, 0),
poolCount: poolCount,
getWorkConnFn: getWorkConnFn,
serverCfg: serverCfg,
limiter: limiter,
xl: xl,
ctx: xlog.NewContext(ctx, xl),
userInfo: userInfo,
loginMsg: loginMsg,
}
switch cfg := pxyConf.(type) {
case *config.TCPProxyConf:
basePxy.usedPortsNum = 1
pxy = &TCPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.TCPMuxProxyConf:
pxy = &TCPMuxProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.HTTPProxyConf:
pxy = &HTTPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.HTTPSProxyConf:
pxy = &HTTPSProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.UDPProxyConf:
basePxy.usedPortsNum = 1
pxy = &UDPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.STCPProxyConf:
pxy = &STCPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.XTCPProxyConf:
pxy = &XTCPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
case *config.SUDPProxyConf:
pxy = &SUDPProxy{
BaseProxy: &basePxy,
cfg: cfg,
}
default:
return pxy, fmt.Errorf("proxy type not support")
}
return
}
// HandleUserTCPConnection is used for incoming user TCP connections. // HandleUserTCPConnection is used for incoming user TCP connections.
// It can be used for tcp, http, https type. func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
func HandleUserTCPConnection(pxy Proxy, userConn net.Conn, serverCfg config.ServerCommonConf) {
xl := xlog.FromContextSafe(pxy.Context()) xl := xlog.FromContextSafe(pxy.Context())
defer userConn.Close() defer userConn.Close()
serverCfg := pxy.serverCfg
cfg := pxy.pxyConf.GetBaseConfig()
// server plugin hook // server plugin hook
rc := pxy.GetResourceController() rc := pxy.GetResourceController()
content := &plugin.NewUserConnContent{ content := &plugin.NewUserConnContent{
User: pxy.GetUserInfo(), User: pxy.GetUserInfo(),
ProxyName: pxy.GetName(), ProxyName: pxy.GetName(),
ProxyType: pxy.GetConf().GetBaseInfo().ProxyType, ProxyType: cfg.ProxyType,
RemoteAddr: userConn.RemoteAddr().String(), RemoteAddr: userConn.RemoteAddr().String(),
} }
_, err := rc.PluginManager.NewUserConn(content) _, err := rc.PluginManager.NewUserConn(content)
@ -294,21 +232,20 @@ 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()
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,15 +254,52 @@ 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 := cfg.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)
xl.Debug("join connections closed") xl.Debug("join connections closed")
} }
func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.ResourceController, poolCount int,
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf, loginMsg *msg.Login,
) (pxy Proxy, err error) {
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseConfig().ProxyName)
var limiter *rate.Limiter
limitBytes := pxyConf.GetBaseConfig().BandwidthLimit.Bytes()
if limitBytes > 0 && pxyConf.GetBaseConfig().BandwidthLimitMode == config.BandwidthLimitModeServer {
limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
}
basePxy := BaseProxy{
name: pxyConf.GetBaseConfig().ProxyName,
rc: rc,
listeners: make([]net.Listener, 0),
poolCount: poolCount,
getWorkConnFn: getWorkConnFn,
serverCfg: serverCfg,
limiter: limiter,
xl: xl,
ctx: xlog.NewContext(ctx, xl),
userInfo: userInfo,
loginMsg: loginMsg,
pxyConf: pxyConf,
}
factory := proxyFactoryRegistry[reflect.TypeOf(pxyConf)]
if factory == nil {
return pxy, fmt.Errorf("proxy type not support")
}
pxy = factory(&basePxy, pxyConf)
if pxy == nil {
return nil, fmt.Errorf("proxy not created")
}
return pxy, nil
}
type Manager struct { type Manager struct {
// proxies indexed by proxy name // proxies indexed by proxy name
pxys map[string]Proxy pxys map[string]Proxy
@ -350,6 +324,13 @@ func (pm *Manager) Add(name string, pxy Proxy) error {
return nil return nil
} }
func (pm *Manager) Exist(name string) bool {
pm.mu.RLock()
defer pm.mu.RUnlock()
_, ok := pm.pxys[name]
return ok
}
func (pm *Manager) Del(name string) { func (pm *Manager) Del(name string) {
pm.mu.Lock() pm.mu.Lock()
defer pm.mu.Unlock() defer pm.mu.Unlock()

View File

@ -15,19 +15,39 @@
package proxy package proxy
import ( import (
"golang.org/x/time/rate" "reflect"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
) )
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.STCPProxyConf{}), NewSTCPProxy)
}
type STCPProxy struct { type STCPProxy struct {
*BaseProxy *BaseProxy
cfg *config.STCPProxyConf cfg *config.STCPProxyConf
} }
func NewSTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.STCPProxyConf)
if !ok {
return nil
}
return &STCPProxy{
BaseProxy: baseProxy,
cfg: unwrapped,
}
}
func (pxy *STCPProxy) Run() (remoteAddr string, err error) { func (pxy *STCPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk) allowUsers := pxy.cfg.AllowUsers
// if allowUsers is empty, only allow same user from proxy
if len(allowUsers) == 0 {
allowUsers = []string{pxy.GetUserInfo().User}
}
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk, allowUsers)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return
@ -35,7 +55,7 @@ func (pxy *STCPProxy) Run() (remoteAddr string, err error) {
pxy.listeners = append(pxy.listeners, listener) pxy.listeners = append(pxy.listeners, listener)
xl.Info("stcp proxy custom listen success") xl.Info("stcp proxy custom listen success")
pxy.startListenHandler(pxy, HandleUserTCPConnection) pxy.startCommonTCPListenersHandler()
return return
} }
@ -43,10 +63,6 @@ func (pxy *STCPProxy) GetConf() config.ProxyConf {
return pxy.cfg return pxy.cfg
} }
func (pxy *STCPProxy) GetLimiter() *rate.Limiter {
return pxy.limiter
}
func (pxy *STCPProxy) Close() { func (pxy *STCPProxy) Close() {
pxy.BaseProxy.Close() pxy.BaseProxy.Close()
pxy.rc.VisitorManager.CloseListener(pxy.GetName()) pxy.rc.VisitorManager.CloseListener(pxy.GetName())

View File

@ -15,20 +15,39 @@
package proxy package proxy
import ( import (
"golang.org/x/time/rate" "reflect"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
) )
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.SUDPProxyConf{}), NewSUDPProxy)
}
type SUDPProxy struct { type SUDPProxy struct {
*BaseProxy *BaseProxy
cfg *config.SUDPProxyConf cfg *config.SUDPProxyConf
} }
func NewSUDPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.SUDPProxyConf)
if !ok {
return nil
}
return &SUDPProxy{
BaseProxy: baseProxy,
cfg: unwrapped,
}
}
func (pxy *SUDPProxy) Run() (remoteAddr string, err error) { func (pxy *SUDPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
allowUsers := pxy.cfg.AllowUsers
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk) // if allowUsers is empty, only allow same user from proxy
if len(allowUsers) == 0 {
allowUsers = []string{pxy.GetUserInfo().User}
}
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk, allowUsers)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return
@ -36,7 +55,7 @@ func (pxy *SUDPProxy) Run() (remoteAddr string, err error) {
pxy.listeners = append(pxy.listeners, listener) pxy.listeners = append(pxy.listeners, listener)
xl.Info("sudp proxy custom listen success") xl.Info("sudp proxy custom listen success")
pxy.startListenHandler(pxy, HandleUserTCPConnection) pxy.startCommonTCPListenersHandler()
return return
} }
@ -44,10 +63,6 @@ func (pxy *SUDPProxy) GetConf() config.ProxyConf {
return pxy.cfg return pxy.cfg
} }
func (pxy *SUDPProxy) GetLimiter() *rate.Limiter {
return pxy.limiter
}
func (pxy *SUDPProxy) Close() { func (pxy *SUDPProxy) Close() {
pxy.BaseProxy.Close() pxy.BaseProxy.Close()
pxy.rc.VisitorManager.CloseListener(pxy.GetName()) pxy.rc.VisitorManager.CloseListener(pxy.GetName())

View File

@ -17,24 +17,39 @@ package proxy
import ( import (
"fmt" "fmt"
"net" "net"
"reflect"
"strconv" "strconv"
"golang.org/x/time/rate"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
) )
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.TCPProxyConf{}), NewTCPProxy)
}
type TCPProxy struct { type TCPProxy struct {
*BaseProxy *BaseProxy
cfg *config.TCPProxyConf cfg *config.TCPProxyConf
realPort int realBindPort int
}
func NewTCPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.TCPProxyConf)
if !ok {
return nil
}
baseProxy.usedPortsNum = 1
return &TCPProxy{
BaseProxy: baseProxy,
cfg: unwrapped,
}
} }
func (pxy *TCPProxy) Run() (remoteAddr string, err error) { func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
if pxy.cfg.Group != "" { if pxy.cfg.Group != "" {
l, realPort, errRet := pxy.rc.TCPGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort) l, realBindPort, errRet := pxy.rc.TCPGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return
@ -44,20 +59,20 @@ func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
l.Close() l.Close()
} }
}() }()
pxy.realPort = realPort pxy.realBindPort = realBindPort
pxy.listeners = append(pxy.listeners, l) pxy.listeners = append(pxy.listeners, l)
xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group) xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
} else { } else {
pxy.realPort, err = pxy.rc.TCPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) pxy.realBindPort, err = pxy.rc.TCPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
if err != nil { if err != nil {
return return
} }
defer func() { defer func() {
if err != nil { if err != nil {
pxy.rc.TCPPortManager.Release(pxy.realPort) pxy.rc.TCPPortManager.Release(pxy.realBindPort)
} }
}() }()
listener, errRet := net.Listen("tcp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realPort))) listener, errRet := net.Listen("tcp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realBindPort)))
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return
@ -66,9 +81,9 @@ func (pxy *TCPProxy) Run() (remoteAddr string, err error) {
xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort) xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
} }
pxy.cfg.RemotePort = pxy.realPort pxy.cfg.RemotePort = pxy.realBindPort
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) remoteAddr = fmt.Sprintf(":%d", pxy.realBindPort)
pxy.startListenHandler(pxy, HandleUserTCPConnection) pxy.startCommonTCPListenersHandler()
return return
} }
@ -76,13 +91,9 @@ func (pxy *TCPProxy) GetConf() config.ProxyConf {
return pxy.cfg return pxy.cfg
} }
func (pxy *TCPProxy) GetLimiter() *rate.Limiter {
return pxy.limiter
}
func (pxy *TCPProxy) Close() { func (pxy *TCPProxy) Close() {
pxy.BaseProxy.Close() pxy.BaseProxy.Close()
if pxy.cfg.Group == "" { if pxy.cfg.Group == "" {
pxy.rc.TCPPortManager.Release(pxy.realPort) pxy.rc.TCPPortManager.Release(pxy.realBindPort)
} }
} }

View File

@ -17,21 +17,35 @@ package proxy
import ( import (
"fmt" "fmt"
"net" "net"
"reflect"
"strings" "strings"
"golang.org/x/time/rate"
"github.com/fatedier/frp/pkg/config" "github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/consts" "github.com/fatedier/frp/pkg/consts"
"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"
) )
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.TCPMuxProxyConf{}), NewTCPMuxProxy)
}
type TCPMuxProxy struct { type TCPMuxProxy struct {
*BaseProxy *BaseProxy
cfg *config.TCPMuxProxyConf cfg *config.TCPMuxProxyConf
} }
func NewTCPMuxProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.TCPMuxProxyConf)
if !ok {
return nil
}
return &TCPMuxProxy{
BaseProxy: baseProxy,
cfg: unwrapped,
}
}
func (pxy *TCPMuxProxy) httpConnectListen( func (pxy *TCPMuxProxy) httpConnectListen(
domain, routeByHTTPUser, httpUser, httpPwd string, addrs []string) ([]string, error, domain, routeByHTTPUser, httpUser, httpPwd string, addrs []string) ([]string, error,
) { ) {
@ -78,7 +92,7 @@ func (pxy *TCPMuxProxy) httpConnectRun() (remoteAddr string, err error) {
} }
} }
pxy.startListenHandler(pxy, HandleUserTCPConnection) pxy.startCommonTCPListenersHandler()
remoteAddr = strings.Join(addrs, ",") remoteAddr = strings.Join(addrs, ",")
return remoteAddr, err return remoteAddr, err
} }
@ -101,10 +115,6 @@ func (pxy *TCPMuxProxy) GetConf() config.ProxyConf {
return pxy.cfg return pxy.cfg
} }
func (pxy *TCPMuxProxy) GetLimiter() *rate.Limiter {
return pxy.limiter
}
func (pxy *TCPMuxProxy) Close() { func (pxy *TCPMuxProxy) Close() {
pxy.BaseProxy.Close() pxy.BaseProxy.Close()
} }

View File

@ -19,26 +19,30 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"reflect"
"strconv" "strconv"
"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"
"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"
) )
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.UDPProxyConf{}), NewUDPProxy)
}
type UDPProxy struct { type UDPProxy struct {
*BaseProxy *BaseProxy
cfg *config.UDPProxyConf cfg *config.UDPProxyConf
realPort int realBindPort int
// udpConn is the listener of udp packages // udpConn is the listener of udp packages
udpConn *net.UDPConn udpConn *net.UDPConn
@ -59,21 +63,33 @@ type UDPProxy struct {
isClosed bool isClosed bool
} }
func NewUDPProxy(baseProxy *BaseProxy, cfg config.ProxyConf) Proxy {
unwrapped, ok := cfg.(*config.UDPProxyConf)
if !ok {
return nil
}
baseProxy.usedPortsNum = 1
return &UDPProxy{
BaseProxy: baseProxy,
cfg: unwrapped,
}
}
func (pxy *UDPProxy) Run() (remoteAddr string, err error) { func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
pxy.realPort, err = pxy.rc.UDPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) pxy.realBindPort, err = pxy.rc.UDPPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
if err != nil { if err != nil {
return "", fmt.Errorf("acquire port %d error: %v", pxy.cfg.RemotePort, err) return "", fmt.Errorf("acquire port %d error: %v", pxy.cfg.RemotePort, err)
} }
defer func() { defer func() {
if err != nil { if err != nil {
pxy.rc.UDPPortManager.Release(pxy.realPort) pxy.rc.UDPPortManager.Release(pxy.realBindPort)
} }
}() }()
remoteAddr = fmt.Sprintf(":%d", pxy.realPort) remoteAddr = fmt.Sprintf(":%d", pxy.realBindPort)
pxy.cfg.RemotePort = pxy.realPort pxy.cfg.RemotePort = pxy.realBindPort
addr, errRet := net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realPort))) addr, errRet := net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.serverCfg.ProxyBindAddr, strconv.Itoa(pxy.realBindPort)))
if errRet != nil { if errRet != nil {
err = errRet err = errRet
return return
@ -124,7 +140,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 +170,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 +205,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 +213,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)
@ -233,10 +249,6 @@ func (pxy *UDPProxy) GetConf() config.ProxyConf {
return pxy.cfg return pxy.cfg
} }
func (pxy *UDPProxy) GetLimiter() *rate.Limiter {
return pxy.limiter
}
func (pxy *UDPProxy) Close() { func (pxy *UDPProxy) Close() {
pxy.mu.Lock() pxy.mu.Lock()
defer pxy.mu.Unlock() defer pxy.mu.Unlock()
@ -254,5 +266,5 @@ func (pxy *UDPProxy) Close() {
close(pxy.readCh) close(pxy.readCh)
close(pxy.sendCh) close(pxy.sendCh)
} }
pxy.rc.UDPPortManager.Release(pxy.realPort) pxy.rc.UDPPortManager.Release(pxy.realBindPort)
} }

View File

@ -16,14 +16,18 @@ package proxy
import ( import (
"fmt" "fmt"
"reflect"
"github.com/fatedier/golib/errors" "github.com/fatedier/golib/errors"
"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"
) )
func init() {
RegisterProxyFactory(reflect.TypeOf(&config.XTCPProxyConf{}), NewXTCPProxy)
}
type XTCPProxy struct { type XTCPProxy struct {
*BaseProxy *BaseProxy
cfg *config.XTCPProxyConf cfg *config.XTCPProxyConf
@ -31,15 +35,33 @@ type XTCPProxy struct {
closeCh chan struct{} closeCh chan struct{}
} }
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) Run() (remoteAddr string, err error) { func (pxy *XTCPProxy) Run() (remoteAddr string, err error) {
xl := pxy.xl xl := pxy.xl
if pxy.rc.NatHoleController == nil { if pxy.rc.NatHoleController == nil {
xl.Error("udp port for xtcp is not specified.")
err = fmt.Errorf("xtcp is not supported in frps") err = fmt.Errorf("xtcp is not supported in frps")
return return
} }
sidCh := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk) allowUsers := pxy.cfg.AllowUsers
// if allowUsers is empty, only allow same user from proxy
if len(allowUsers) == 0 {
allowUsers = []string{pxy.GetUserInfo().User}
}
sidCh, err := pxy.rc.NatHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk, allowUsers)
if err != nil {
return "", err
}
go func() { go func() {
for { for {
select { select {
@ -68,10 +90,6 @@ func (pxy *XTCPProxy) GetConf() config.ProxyConf {
return pxy.cfg return pxy.cfg
} }
func (pxy *XTCPProxy) GetLimiter() *rate.Limiter {
return pxy.limiter
}
func (pxy *XTCPProxy) Close() { func (pxy *XTCPProxy) Close() {
pxy.BaseProxy.Close() pxy.BaseProxy.Close()
pxy.rc.NatHoleController.CloseClient(pxy.GetName()) pxy.rc.NatHoleController.CloseClient(pxy.GetName())

View File

@ -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"
@ -210,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
@ -235,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 {
@ -294,7 +294,7 @@ 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.
@ -442,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()
@ -461,6 +461,7 @@ func (svr *Service) HandleListener(l net.Listener) {
fmuxCfg := fmux.DefaultConfig() fmuxCfg := fmux.DefaultConfig()
fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second
fmuxCfg.LogOutput = io.Discard fmuxCfg.LogOutput = io.Discard
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
session, err := fmux.Server(frpConn, fmuxCfg) session, err := fmux.Server(frpConn, fmuxCfg)
if err != nil { if err != nil {
log.Warn("Failed to create mux connection: %v", err) log.Warn("Failed to create mux connection: %v", err)
@ -501,7 +502,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)
} }
@ -517,7 +518,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)
@ -555,7 +556,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)
@ -587,6 +588,15 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn)
} }
func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVisitorConn) error { func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVisitorConn) error {
visitorUser := ""
// TODO: Compatible with old versions, can be without runID, user is empty. In later versions, it will be mandatory to include runID.
if newMsg.RunID != "" {
ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
if !exist {
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
}
visitorUser = ctl.loginMsg.User
}
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey, return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
newMsg.UseEncryption, newMsg.UseCompression) newMsg.UseEncryption, newMsg.UseCompression, visitorUser)
} }

View File

@ -20,66 +20,78 @@ import (
"net" "net"
"sync" "sync"
frpIo "github.com/fatedier/golib/io" libio "github.com/fatedier/golib/io"
"github.com/samber/lo"
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"
) )
type listenerBundle struct {
l *utilnet.InternalListener
sk string
allowUsers []string
}
// Manager for visitor listeners. // Manager for visitor listeners.
type Manager struct { type Manager struct {
visitorListeners map[string]*frpNet.CustomListener listeners map[string]*listenerBundle
skMap map[string]string
mu sync.RWMutex mu sync.RWMutex
} }
func NewManager() *Manager { func NewManager() *Manager {
return &Manager{ return &Manager{
visitorListeners: make(map[string]*frpNet.CustomListener), listeners: make(map[string]*listenerBundle),
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, allowUsers []string) (l *utilnet.InternalListener, err error) {
vm.mu.Lock() vm.mu.Lock()
defer vm.mu.Unlock() defer vm.mu.Unlock()
if _, ok := vm.visitorListeners[name]; ok { if _, ok := vm.listeners[name]; ok {
err = fmt.Errorf("custom listener for [%s] is repeated", name) err = fmt.Errorf("custom listener for [%s] is repeated", name)
return return
} }
l = frpNet.NewCustomListener() l = utilnet.NewInternalListener()
vm.visitorListeners[name] = l vm.listeners[name] = &listenerBundle{
vm.skMap[name] = sk l: l,
sk: sk,
allowUsers: allowUsers,
}
return return
} }
func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64, signKey string, func (vm *Manager) NewConn(name string, conn net.Conn, timestamp int64, signKey string,
useEncryption bool, useCompression bool, useEncryption bool, useCompression bool, visitorUser string,
) (err error) { ) (err error) {
vm.mu.RLock() vm.mu.RLock()
defer vm.mu.RUnlock() defer vm.mu.RUnlock()
if l, ok := vm.visitorListeners[name]; ok { if l, ok := vm.listeners[name]; ok {
var sk string if util.GetAuthKey(l.sk, timestamp) != signKey {
if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
err = fmt.Errorf("visitor connection of [%s] auth failed", name) err = fmt.Errorf("visitor connection of [%s] auth failed", name)
return return
} }
if !lo.Contains(l.allowUsers, visitorUser) && !lo.Contains(l.allowUsers, "*") {
err = fmt.Errorf("visitor connection of [%s] user [%s] not allowed", name, visitorUser)
return
}
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(l.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.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
@ -91,6 +103,5 @@ func (vm *Manager) CloseListener(name string) {
vm.mu.Lock() vm.mu.Lock()
defer vm.mu.Unlock() defer vm.mu.Unlock()
delete(vm.visitorListeners, name) delete(vm.listeners, name)
delete(vm.skMap, name)
} }

View File

@ -282,8 +282,9 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
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() {
serverConf := consts.DefaultServerConfig serverConf := consts.DefaultServerConfig
clientServerConf := consts.DefaultClientConfig clientServerConf := consts.DefaultClientConfig + "\nuser = user1"
clientVisitorConf := consts.DefaultClientConfig clientVisitorConf := consts.DefaultClientConfig + "\nuser = user1"
clientUser2VisitorConf := consts.DefaultClientConfig + "\nuser = user2"
localPortName := "" localPortName := ""
protocol := "tcp" protocol := "tcp"
@ -323,11 +324,14 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
} }
tests := []struct { tests := []struct {
proxyName string proxyName string
bindPortName string bindPortName string
visitorSK string visitorSK string
extraConfig string commonExtraConfig string
expectError bool proxyExtraConfig string
visitorExtraConfig string
expectError bool
user2 bool
}{ }{
{ {
proxyName: "normal", proxyName: "normal",
@ -335,22 +339,22 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
visitorSK: correctSK, visitorSK: correctSK,
}, },
{ {
proxyName: "with-encryption", proxyName: "with-encryption",
bindPortName: port.GenName("WithEncryption"), bindPortName: port.GenName("WithEncryption"),
visitorSK: correctSK, visitorSK: correctSK,
extraConfig: "use_encryption = true", commonExtraConfig: "use_encryption = true",
}, },
{ {
proxyName: "with-compression", proxyName: "with-compression",
bindPortName: port.GenName("WithCompression"), bindPortName: port.GenName("WithCompression"),
visitorSK: correctSK, visitorSK: correctSK,
extraConfig: "use_compression = true", commonExtraConfig: "use_compression = true",
}, },
{ {
proxyName: "with-encryption-and-compression", proxyName: "with-encryption-and-compression",
bindPortName: port.GenName("WithEncryptionAndCompression"), bindPortName: port.GenName("WithEncryptionAndCompression"),
visitorSK: correctSK, visitorSK: correctSK,
extraConfig: ` commonExtraConfig: `
use_encryption = true use_encryption = true
use_compression = true use_compression = true
`, `,
@ -361,22 +365,57 @@ var _ = ginkgo.Describe("[Feature: Basic]", func() {
visitorSK: wrongSK, visitorSK: wrongSK,
expectError: true, expectError: true,
}, },
{
proxyName: "allowed-user",
bindPortName: port.GenName("AllowedUser"),
visitorSK: correctSK,
proxyExtraConfig: "allow_users = another, user2",
visitorExtraConfig: "server_user = user1",
user2: true,
},
{
proxyName: "not-allowed-user",
bindPortName: port.GenName("NotAllowedUser"),
visitorSK: correctSK,
proxyExtraConfig: "allow_users = invalid",
visitorExtraConfig: "server_user = user1",
expectError: true,
},
{
proxyName: "allow-all",
bindPortName: port.GenName("AllowAll"),
visitorSK: correctSK,
proxyExtraConfig: "allow_users = *",
visitorExtraConfig: "server_user = user1",
user2: true,
},
} }
// build all client config // build all client config
for _, test := range tests { for _, test := range tests {
clientServerConf += getProxyServerConf(test.proxyName, test.extraConfig) + "\n" clientServerConf += getProxyServerConf(test.proxyName, test.commonExtraConfig+"\n"+test.proxyExtraConfig) + "\n"
} }
for _, test := range tests { for _, test := range tests {
clientVisitorConf += getProxyVisitorConf(test.proxyName, test.bindPortName, test.visitorSK, test.extraConfig) + "\n" config := getProxyVisitorConf(
test.proxyName, test.bindPortName, test.visitorSK, test.commonExtraConfig+"\n"+test.visitorExtraConfig,
) + "\n"
if test.user2 {
clientUser2VisitorConf += config
} else {
clientVisitorConf += config
}
} }
// run frps and frpc // run frps and frpc
f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf}) f.RunProcesses([]string{serverConf}, []string{clientServerConf, clientVisitorConf, clientUser2VisitorConf})
for _, test := range tests { for _, test := range tests {
timeout := time.Second
if t == "xtcp" {
timeout = 4 * time.Second
}
framework.NewRequestExpect(f). framework.NewRequestExpect(f).
RequestModify(func(r *request.Request) { RequestModify(func(r *request.Request) {
r.Timeout(5 * time.Second) r.Timeout(timeout)
}). }).
Protocol(protocol). Protocol(protocol).
PortName(test.bindPortName). PortName(test.bindPortName).

View File

@ -101,11 +101,13 @@ var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"} supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
for _, protocol := range supportProtocols { for _, protocol := range supportProtocols {
tmp := protocol tmp := protocol
defineClientServerTest("TLS over "+strings.ToUpper(tmp), f, &generalTestConfigures{ // Since v0.50.0, the default value of tls_enable has been changed to true.
// Therefore, here it needs to be set as false to test the scenario of turning it off.
defineClientServerTest("Disable TLS over "+strings.ToUpper(tmp), f, &generalTestConfigures{
server: fmt.Sprintf(` server: fmt.Sprintf(`
%s %s
`, renderBindPortConfig(protocol)), `, renderBindPortConfig(protocol)),
client: fmt.Sprintf(`tls_enable = true client: fmt.Sprintf(`tls_enable = false
protocol = %s protocol = %s
`, protocol), `, protocol),
}) })
@ -113,10 +115,10 @@ var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
defineClientServerTest("enable tls_only, client with TLS", f, &generalTestConfigures{ defineClientServerTest("enable tls_only, client with TLS", f, &generalTestConfigures{
server: "tls_only = true", server: "tls_only = true",
client: "tls_enable = true",
}) })
defineClientServerTest("enable tls_only, client without TLS", f, &generalTestConfigures{ defineClientServerTest("enable tls_only, client without TLS", f, &generalTestConfigures{
server: "tls_only = true", server: "tls_only = true",
client: "tls_enable = false",
expectError: true, expectError: true,
}) })
}) })
@ -155,7 +157,6 @@ var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
`, renderBindPortConfig(tmp), caCrtPath), `, renderBindPortConfig(tmp), caCrtPath),
client: fmt.Sprintf(` client: fmt.Sprintf(`
protocol = %s protocol = %s
tls_enable = true
tls_cert_file = %s tls_cert_file = %s
tls_key_file = %s tls_key_file = %s
`, tmp, clientCrtPath, clientKeyPath), `, tmp, clientCrtPath, clientKeyPath),
@ -172,7 +173,6 @@ var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
`, renderBindPortConfig(tmp), serverCrtPath, serverKeyPath, caCrtPath), `, renderBindPortConfig(tmp), serverCrtPath, serverKeyPath, caCrtPath),
client: fmt.Sprintf(` client: fmt.Sprintf(`
protocol = %s protocol = %s
tls_enable = true
tls_cert_file = %s tls_cert_file = %s
tls_key_file = %s tls_key_file = %s
tls_trusted_ca_file = %s tls_trusted_ca_file = %s
@ -211,7 +211,6 @@ var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
tls_trusted_ca_file = %s tls_trusted_ca_file = %s
`, serverCrtPath, serverKeyPath, caCrtPath), `, serverCrtPath, serverKeyPath, caCrtPath),
client: fmt.Sprintf(` client: fmt.Sprintf(`
tls_enable = true
tls_server_name = example.com tls_server_name = example.com
tls_cert_file = %s tls_cert_file = %s
tls_key_file = %s tls_key_file = %s
@ -228,7 +227,6 @@ var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
tls_trusted_ca_file = %s tls_trusted_ca_file = %s
`, serverCrtPath, serverKeyPath, caCrtPath), `, serverCrtPath, serverKeyPath, caCrtPath),
client: fmt.Sprintf(` client: fmt.Sprintf(`
tls_enable = true
tls_server_name = invalid.com tls_server_name = invalid.com
tls_cert_file = %s tls_cert_file = %s
tls_key_file = %s tls_key_file = %s
@ -239,7 +237,7 @@ var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
}) })
}) })
ginkgo.Describe("TLS with disable_custom_tls_first_byte", func() { ginkgo.Describe("TLS with disable_custom_tls_first_byte set to false", func() {
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"} supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
for _, protocol := range supportProtocols { for _, protocol := range supportProtocols {
tmp := protocol tmp := protocol
@ -248,9 +246,8 @@ var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
%s %s
`, renderBindPortConfig(protocol)), `, renderBindPortConfig(protocol)),
client: fmt.Sprintf(` client: fmt.Sprintf(`
tls_enable = true
protocol = %s protocol = %s
disable_custom_tls_first_byte = true disable_custom_tls_first_byte = false
`, protocol), `, protocol),
}) })
} }
@ -266,9 +263,7 @@ var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
%s %s
`, renderBindPortConfig(protocol)), `, renderBindPortConfig(protocol)),
client: fmt.Sprintf(` client: fmt.Sprintf(`
tls_enable = true
protocol = %s protocol = %s
disable_custom_tls_first_byte = true
`, protocol), `, protocol),
}) })
} }

View File

@ -37,7 +37,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
[tcp-port-not-allowed] [tcp-port-not-allowed]
type = tcp type = tcp
local_port = {{ .%s }} local_port = {{ .%s }}
remote_port = 20001 remote_port = 25001
`, framework.TCPEchoServerPort) `, framework.TCPEchoServerPort)
clientConf += fmt.Sprintf(` clientConf += fmt.Sprintf(`
[tcp-port-unavailable] [tcp-port-unavailable]
@ -55,7 +55,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
[udp-port-not-allowed] [udp-port-not-allowed]
type = udp type = udp
local_port = {{ .%s }} local_port = {{ .%s }}
remote_port = 20003 remote_port = 25003
`, framework.UDPEchoServerPort) `, framework.UDPEchoServerPort)
f.RunProcesses([]string{serverConf}, []string{clientConf}) f.RunProcesses([]string{serverConf}, []string{clientConf})
@ -65,7 +65,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
framework.NewRequestExpect(f).PortName(tcpPortName).Ensure() framework.NewRequestExpect(f).PortName(tcpPortName).Ensure()
// Not Allowed // Not Allowed
framework.NewRequestExpect(f).Port(25003).ExpectError(true).Ensure() framework.NewRequestExpect(f).Port(25001).ExpectError(true).Ensure()
// Unavailable, already bind by frps // Unavailable, already bind by frps
framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure() framework.NewRequestExpect(f).PortName(consts.PortServerName).ExpectError(true).Ensure()

52
test/e2e/basic/xtcp.go Normal file
View 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()
})
})

View File

@ -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(2 * time.Second) time.Sleep(3 * time.Second)
return currentServerProcesses, currentClientProcesses return currentServerProcesses, currentClientProcesses
} }

View File

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