mirror of
https://github.com/fatedier/frp.git
synced 2025-01-22 09:32:07 +00:00
refactor the code related to xtcp (#3449)
This commit is contained in:
parent
9f029e3248
commit
c71efde303
3
Makefile
3
Makefile
@ -19,6 +19,9 @@ fmt:
|
|||||||
fmt-more:
|
fmt-more:
|
||||||
gofumpt -l -w .
|
gofumpt -l -w .
|
||||||
|
|
||||||
|
gci:
|
||||||
|
gci write -s standard -s default -s "prefix(github.com/fatedier/frp/)" ./
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet ./...
|
go vet ./...
|
||||||
|
|
||||||
|
@ -25,10 +25,11 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GeneralResponse struct {
|
type GeneralResponse struct {
|
||||||
@ -98,7 +99,7 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
|||||||
|
|
||||||
if status.Err == "" {
|
if status.Err == "" {
|
||||||
psr.RemoteAddr = status.RemoteAddr
|
psr.RemoteAddr = status.RemoteAddr
|
||||||
if util.InSlice(status.Type, []string{"tcp", "udp"}) {
|
if lo.Contains([]string{"tcp", "udp"}, status.Type) {
|
||||||
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,21 @@ import (
|
|||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
|
"github.com/fatedier/frp/client/visitor"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Control struct {
|
type Control struct {
|
||||||
// uniq id got from frps, attach it in loginMsg
|
// service context
|
||||||
|
ctx context.Context
|
||||||
|
xl *xlog.Logger
|
||||||
|
|
||||||
|
// Unique ID obtained from frps.
|
||||||
|
// It should be attached to the login message when reconnecting.
|
||||||
runID string
|
runID string
|
||||||
|
|
||||||
// manage all proxies
|
// manage all proxies
|
||||||
@ -40,7 +47,7 @@ type Control struct {
|
|||||||
pm *proxy.Manager
|
pm *proxy.Manager
|
||||||
|
|
||||||
// manage all visitors
|
// manage all visitors
|
||||||
vm *VisitorManager
|
vm *visitor.Manager
|
||||||
|
|
||||||
// control connection
|
// control connection
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
@ -68,16 +75,10 @@ type Control struct {
|
|||||||
writerShutdown *shutdown.Shutdown
|
writerShutdown *shutdown.Shutdown
|
||||||
msgHandlerShutdown *shutdown.Shutdown
|
msgHandlerShutdown *shutdown.Shutdown
|
||||||
|
|
||||||
// The UDP port that the server is listening on
|
|
||||||
serverUDPPort int
|
|
||||||
|
|
||||||
xl *xlog.Logger
|
|
||||||
|
|
||||||
// service context
|
|
||||||
ctx context.Context
|
|
||||||
|
|
||||||
// sets authentication based on selected method
|
// sets authentication based on selected method
|
||||||
authSetter auth.Setter
|
authSetter auth.Setter
|
||||||
|
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewControl(
|
func NewControl(
|
||||||
@ -85,11 +86,12 @@ func NewControl(
|
|||||||
clientCfg config.ClientCommonConf,
|
clientCfg config.ClientCommonConf,
|
||||||
pxyCfgs map[string]config.ProxyConf,
|
pxyCfgs map[string]config.ProxyConf,
|
||||||
visitorCfgs map[string]config.VisitorConf,
|
visitorCfgs map[string]config.VisitorConf,
|
||||||
serverUDPPort int,
|
|
||||||
authSetter auth.Setter,
|
authSetter auth.Setter,
|
||||||
) *Control {
|
) *Control {
|
||||||
// new xlog instance
|
// new xlog instance
|
||||||
ctl := &Control{
|
ctl := &Control{
|
||||||
|
ctx: ctx,
|
||||||
|
xl: xlog.FromContextSafe(ctx),
|
||||||
runID: runID,
|
runID: runID,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
cm: cm,
|
cm: cm,
|
||||||
@ -102,14 +104,12 @@ func NewControl(
|
|||||||
readerShutdown: shutdown.New(),
|
readerShutdown: shutdown.New(),
|
||||||
writerShutdown: shutdown.New(),
|
writerShutdown: shutdown.New(),
|
||||||
msgHandlerShutdown: shutdown.New(),
|
msgHandlerShutdown: shutdown.New(),
|
||||||
serverUDPPort: serverUDPPort,
|
|
||||||
xl: xlog.FromContextSafe(ctx),
|
|
||||||
ctx: ctx,
|
|
||||||
authSetter: authSetter,
|
authSetter: authSetter,
|
||||||
}
|
}
|
||||||
ctl.pm = proxy.NewManager(ctl.ctx, ctl.sendCh, clientCfg, serverUDPPort)
|
ctl.msgTransporter = transport.NewMessageTransporter(ctl.sendCh)
|
||||||
|
ctl.pm = proxy.NewManager(ctl.ctx, clientCfg, ctl.msgTransporter)
|
||||||
|
|
||||||
ctl.vm = NewVisitorManager(ctl.ctx, ctl)
|
ctl.vm = visitor.NewManager(ctl.ctx, ctl.clientCfg, ctl.connectServer, ctl.msgTransporter)
|
||||||
ctl.vm.Reload(visitorCfgs)
|
ctl.vm.Reload(visitorCfgs)
|
||||||
return ctl
|
return ctl
|
||||||
}
|
}
|
||||||
@ -173,6 +173,16 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) HandleNatHoleResp(inMsg *msg.NatHoleResp) {
|
||||||
|
xl := ctl.xl
|
||||||
|
|
||||||
|
// Dispatch the NatHoleResp message to the related proxy.
|
||||||
|
ok := ctl.msgTransporter.DispatchWithType(inMsg, msg.TypeNameNatHoleResp, inMsg.TransactionID)
|
||||||
|
if !ok {
|
||||||
|
xl.Trace("dispatch NatHoleResp message to related proxy error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ctl *Control) Close() error {
|
func (ctl *Control) Close() error {
|
||||||
return ctl.GracefulClose(0)
|
return ctl.GracefulClose(0)
|
||||||
}
|
}
|
||||||
@ -188,7 +198,7 @@ func (ctl *Control) GracefulClose(d time.Duration) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClosedDoneCh returns a channel which will be closed after all resources are released
|
// ClosedDoneCh returns a channel that will be closed after all resources are released
|
||||||
func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
func (ctl *Control) ClosedDoneCh() <-chan struct{} {
|
||||||
return ctl.closedDoneCh
|
return ctl.closedDoneCh
|
||||||
}
|
}
|
||||||
@ -250,7 +260,7 @@ func (ctl *Control) writer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// msgHandler handles all channel events and do corresponding operations.
|
// msgHandler handles all channel events and performs corresponding operations.
|
||||||
func (ctl *Control) msgHandler() {
|
func (ctl *Control) msgHandler() {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -307,6 +317,8 @@ func (ctl *Control) msgHandler() {
|
|||||||
go ctl.HandleReqWorkConn(m)
|
go ctl.HandleReqWorkConn(m)
|
||||||
case *msg.NewProxyResp:
|
case *msg.NewProxyResp:
|
||||||
ctl.HandleNewProxyResp(m)
|
ctl.HandleNewProxyResp(m)
|
||||||
|
case *msg.NatHoleResp:
|
||||||
|
ctl.HandleNatHoleResp(m)
|
||||||
case *msg.Pong:
|
case *msg.Pong:
|
||||||
if m.Error != "" {
|
if m.Error != "" {
|
||||||
xl.Error("Pong contains error: %s", m.Error)
|
xl.Error("Pong contains error: %s", m.Error)
|
||||||
|
@ -24,20 +24,16 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
frpIo "github.com/fatedier/golib/io"
|
||||||
libdial "github.com/fatedier/golib/net/dial"
|
libdial "github.com/fatedier/golib/net/dial"
|
||||||
"github.com/fatedier/golib/pool"
|
|
||||||
fmux "github.com/hashicorp/yamux"
|
|
||||||
pp "github.com/pires/go-proxyproto"
|
pp "github.com/pires/go-proxyproto"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||||
"github.com/fatedier/frp/pkg/proto/udp"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/limit"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,7 +47,12 @@ type Proxy interface {
|
|||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
|
func NewProxy(
|
||||||
|
ctx context.Context,
|
||||||
|
pxyConf config.ProxyConf,
|
||||||
|
clientCfg config.ClientCommonConf,
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
) (pxy Proxy) {
|
||||||
var limiter *rate.Limiter
|
var limiter *rate.Limiter
|
||||||
limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
|
limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
|
||||||
if limitBytes > 0 && pxyConf.GetBaseInfo().BandwidthLimitMode == config.BandwidthLimitModeClient {
|
if limitBytes > 0 && pxyConf.GetBaseInfo().BandwidthLimitMode == config.BandwidthLimitModeClient {
|
||||||
@ -59,11 +60,11 @@ func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.Cl
|
|||||||
}
|
}
|
||||||
|
|
||||||
baseProxy := BaseProxy{
|
baseProxy := BaseProxy{
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
serverUDPPort: serverUDPPort,
|
limiter: limiter,
|
||||||
limiter: limiter,
|
msgTransporter: msgTransporter,
|
||||||
xl: xlog.FromContextSafe(ctx),
|
xl: xlog.FromContextSafe(ctx),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
switch cfg := pxyConf.(type) {
|
switch cfg := pxyConf.(type) {
|
||||||
case *config.TCPProxyConf:
|
case *config.TCPProxyConf:
|
||||||
@ -112,10 +113,10 @@ func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.Cl
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BaseProxy struct {
|
type BaseProxy struct {
|
||||||
closed bool
|
closed bool
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg config.ClientCommonConf
|
||||||
serverUDPPort int
|
msgTransporter transport.MessageTransporter
|
||||||
limiter *rate.Limiter
|
limiter *rate.Limiter
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
xl *xlog.Logger
|
xl *xlog.Logger
|
||||||
@ -267,466 +268,6 @@ func (pxy *STCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|||||||
conn, []byte(pxy.clientCfg.Token), m)
|
conn, []byte(pxy.clientCfg.Token), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XTCP
|
|
||||||
type XTCPProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.XTCPProxyConf
|
|
||||||
proxyPlugin plugin.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *XTCPProxy) Run() (err error) {
|
|
||||||
if pxy.cfg.Plugin != "" {
|
|
||||||
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *XTCPProxy) Close() {
|
|
||||||
if pxy.proxyPlugin != nil {
|
|
||||||
pxy.proxyPlugin.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
xl := pxy.xl
|
|
||||||
defer conn.Close()
|
|
||||||
var natHoleSidMsg msg.NatHoleSid
|
|
||||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("xtcp read from workConn error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
natHoleClientMsg := &msg.NatHoleClient{
|
|
||||||
ProxyName: pxy.cfg.ProxyName,
|
|
||||||
Sid: natHoleSidMsg.Sid,
|
|
||||||
}
|
|
||||||
serverAddr := pxy.clientCfg.NatHoleServerAddr
|
|
||||||
if serverAddr == "" {
|
|
||||||
serverAddr = pxy.clientCfg.ServerAddr
|
|
||||||
}
|
|
||||||
raddr, _ := net.ResolveUDPAddr("udp",
|
|
||||||
net.JoinHostPort(serverAddr, strconv.Itoa(pxy.serverUDPPort)))
|
|
||||||
clientConn, err := net.DialUDP("udp", nil, raddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("dial server udp addr error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer clientConn.Close()
|
|
||||||
|
|
||||||
err = msg.WriteMsg(clientConn, natHoleClientMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("send natHoleClientMsg to server error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for client address at most 5 seconds.
|
|
||||||
var natHoleRespMsg msg.NatHoleResp
|
|
||||||
_ = clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
||||||
|
|
||||||
buf := pool.GetBuf(1024)
|
|
||||||
n, err := clientConn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("get natHoleRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("get natHoleRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = clientConn.SetReadDeadline(time.Time{})
|
|
||||||
_ = clientConn.Close()
|
|
||||||
|
|
||||||
if natHoleRespMsg.Error != "" {
|
|
||||||
xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
|
||||||
|
|
||||||
// Send detect message
|
|
||||||
host, portStr, err := net.SplitHostPort(natHoleRespMsg.VisitorAddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("get NatHoleResp visitor address [%s] error: %v", natHoleRespMsg.VisitorAddr, err)
|
|
||||||
}
|
|
||||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
|
||||||
|
|
||||||
port, err := strconv.ParseInt(portStr, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = pxy.sendDetectMsg(host, int(port), laddr, []byte(natHoleRespMsg.Sid))
|
|
||||||
xl.Trace("send all detect msg done")
|
|
||||||
|
|
||||||
if err := msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{}); err != nil {
|
|
||||||
xl.Error("write message error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen for clientConn's address and wait for visitor connection
|
|
||||||
lConn, err := net.ListenUDP("udp", laddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("listen on visitorConn's local address error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer lConn.Close()
|
|
||||||
|
|
||||||
_ = lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
|
||||||
sidBuf := pool.GetBuf(1024)
|
|
||||||
var uAddr *net.UDPAddr
|
|
||||||
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("get sid from visitor error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = lConn.SetReadDeadline(time.Time{})
|
|
||||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
|
||||||
xl.Warn("incorrect sid from visitor")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pool.PutBuf(sidBuf)
|
|
||||||
xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
|
||||||
|
|
||||||
if _, err := lConn.WriteToUDP(sidBuf[:n], uAddr); err != nil {
|
|
||||||
xl.Error("write uaddr error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
kcpConn, err := frpNet.NewKCPConnFromUDP(lConn, false, uAddr.String())
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create kcp connection from udp connection error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
|
||||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
|
||||||
fmuxCfg.LogOutput = io.Discard
|
|
||||||
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create yamux server from kcp connection error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer sess.Close()
|
|
||||||
muxConn, err := sess.Accept()
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("accept for yamux connection error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
|
||||||
muxConn, []byte(pxy.cfg.Sk), m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *XTCPProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
|
||||||
daddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(addr, strconv.Itoa(port)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tConn, err := net.DialUDP("udp", laddr, daddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// uConn := ipv4.NewConn(tConn)
|
|
||||||
// uConn.SetTTL(3)
|
|
||||||
|
|
||||||
if _, err := tConn.Write(content); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UDP
|
|
||||||
type UDPProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.UDPProxyConf
|
|
||||||
|
|
||||||
localAddr *net.UDPAddr
|
|
||||||
readCh chan *msg.UDPPacket
|
|
||||||
|
|
||||||
// include msg.UDPPacket and msg.Ping
|
|
||||||
sendCh chan msg.Message
|
|
||||||
workConn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *UDPProxy) Run() (err error) {
|
|
||||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *UDPProxy) Close() {
|
|
||||||
pxy.mu.Lock()
|
|
||||||
defer pxy.mu.Unlock()
|
|
||||||
|
|
||||||
if !pxy.closed {
|
|
||||||
pxy.closed = true
|
|
||||||
if pxy.workConn != nil {
|
|
||||||
pxy.workConn.Close()
|
|
||||||
}
|
|
||||||
if pxy.readCh != nil {
|
|
||||||
close(pxy.readCh)
|
|
||||||
}
|
|
||||||
if pxy.sendCh != nil {
|
|
||||||
close(pxy.sendCh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
xl := pxy.xl
|
|
||||||
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
|
||||||
// close resources releated with old workConn
|
|
||||||
pxy.Close()
|
|
||||||
|
|
||||||
var rwc io.ReadWriteCloser = conn
|
|
||||||
var err error
|
|
||||||
if pxy.limiter != nil {
|
|
||||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
|
||||||
return conn.Close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if pxy.cfg.UseEncryption {
|
|
||||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
xl.Error("create encryption stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pxy.cfg.UseCompression {
|
|
||||||
rwc = frpIo.WithCompression(rwc)
|
|
||||||
}
|
|
||||||
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
|
||||||
|
|
||||||
pxy.mu.Lock()
|
|
||||||
pxy.workConn = conn
|
|
||||||
pxy.readCh = make(chan *msg.UDPPacket, 1024)
|
|
||||||
pxy.sendCh = make(chan msg.Message, 1024)
|
|
||||||
pxy.closed = false
|
|
||||||
pxy.mu.Unlock()
|
|
||||||
|
|
||||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
|
||||||
for {
|
|
||||||
var udpMsg msg.UDPPacket
|
|
||||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
|
||||||
xl.Warn("read from workConn for udp error: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errRet := errors.PanicToError(func() {
|
|
||||||
xl.Trace("get udp package from workConn: %s", udpMsg.Content)
|
|
||||||
readCh <- &udpMsg
|
|
||||||
}); errRet != nil {
|
|
||||||
xl.Info("reader goroutine for udp work connection closed: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
|
||||||
defer func() {
|
|
||||||
xl.Info("writer goroutine for udp work connection closed")
|
|
||||||
}()
|
|
||||||
var errRet error
|
|
||||||
for rawMsg := range sendCh {
|
|
||||||
switch m := rawMsg.(type) {
|
|
||||||
case *msg.UDPPacket:
|
|
||||||
xl.Trace("send udp package to workConn: %s", m.Content)
|
|
||||||
case *msg.Ping:
|
|
||||||
xl.Trace("send ping message to udp workConn")
|
|
||||||
}
|
|
||||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
|
||||||
xl.Error("udp work write error: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
heartbeatFn := func(sendCh chan msg.Message) {
|
|
||||||
var errRet error
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Duration(30) * time.Second)
|
|
||||||
if errRet = errors.PanicToError(func() {
|
|
||||||
sendCh <- &msg.Ping{}
|
|
||||||
}); errRet != nil {
|
|
||||||
xl.Trace("heartbeat goroutine for udp work connection closed")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
|
||||||
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
|
||||||
go heartbeatFn(pxy.sendCh)
|
|
||||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
type SUDPProxy struct {
|
|
||||||
*BaseProxy
|
|
||||||
|
|
||||||
cfg *config.SUDPProxyConf
|
|
||||||
|
|
||||||
localAddr *net.UDPAddr
|
|
||||||
|
|
||||||
closeCh chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *SUDPProxy) Run() (err error) {
|
|
||||||
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *SUDPProxy) Close() {
|
|
||||||
pxy.mu.Lock()
|
|
||||||
defer pxy.mu.Unlock()
|
|
||||||
select {
|
|
||||||
case <-pxy.closeCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
close(pxy.closeCh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
|
||||||
xl := pxy.xl
|
|
||||||
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
|
||||||
|
|
||||||
var rwc io.ReadWriteCloser = conn
|
|
||||||
var err error
|
|
||||||
if pxy.limiter != nil {
|
|
||||||
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
|
||||||
return conn.Close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if pxy.cfg.UseEncryption {
|
|
||||||
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
xl.Error("create encryption stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pxy.cfg.UseCompression {
|
|
||||||
rwc = frpIo.WithCompression(rwc)
|
|
||||||
}
|
|
||||||
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
|
||||||
|
|
||||||
workConn := conn
|
|
||||||
readCh := make(chan *msg.UDPPacket, 1024)
|
|
||||||
sendCh := make(chan msg.Message, 1024)
|
|
||||||
isClose := false
|
|
||||||
|
|
||||||
mu := &sync.Mutex{}
|
|
||||||
|
|
||||||
closeFn := func() {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
if isClose {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isClose = true
|
|
||||||
if workConn != nil {
|
|
||||||
workConn.Close()
|
|
||||||
}
|
|
||||||
close(readCh)
|
|
||||||
close(sendCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
|
||||||
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
|
||||||
defer closeFn()
|
|
||||||
|
|
||||||
for {
|
|
||||||
// first to check sudp proxy is closed or not
|
|
||||||
select {
|
|
||||||
case <-pxy.closeCh:
|
|
||||||
xl.Trace("frpc sudp proxy is closed")
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
var udpMsg msg.UDPPacket
|
|
||||||
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
|
||||||
xl.Warn("read from workConn for sudp error: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if errRet := errors.PanicToError(func() {
|
|
||||||
readCh <- &udpMsg
|
|
||||||
}); errRet != nil {
|
|
||||||
xl.Warn("reader goroutine for sudp work connection closed: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
|
||||||
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
|
||||||
defer func() {
|
|
||||||
closeFn()
|
|
||||||
xl.Info("writer goroutine for sudp work connection closed")
|
|
||||||
}()
|
|
||||||
|
|
||||||
var errRet error
|
|
||||||
for rawMsg := range sendCh {
|
|
||||||
switch m := rawMsg.(type) {
|
|
||||||
case *msg.UDPPacket:
|
|
||||||
xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
|
|
||||||
m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
|
|
||||||
case *msg.Ping:
|
|
||||||
xl.Trace("frpc send ping message to frpc visitor")
|
|
||||||
}
|
|
||||||
|
|
||||||
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
|
||||||
xl.Error("sudp work write error: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
heartbeatFn := func(sendCh chan msg.Message) {
|
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
|
||||||
defer func() {
|
|
||||||
ticker.Stop()
|
|
||||||
closeFn()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var errRet error
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
if errRet = errors.PanicToError(func() {
|
|
||||||
sendCh <- &msg.Ping{}
|
|
||||||
}); errRet != nil {
|
|
||||||
xl.Warn("heartbeat goroutine for sudp work connection closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-pxy.closeCh:
|
|
||||||
xl.Trace("frpc sudp proxy is closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go workConnSenderFn(workConn, sendCh)
|
|
||||||
go workConnReaderFn(workConn, readCh)
|
|
||||||
go heartbeatFn(sendCh)
|
|
||||||
|
|
||||||
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common handler for tcp work connections.
|
// Common handler for tcp work connections.
|
||||||
func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||||
baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn,
|
baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn,
|
||||||
|
@ -1,3 +1,17 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -6,37 +20,36 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/event"
|
"github.com/fatedier/frp/client/event"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
sendCh chan (msg.Message)
|
proxies map[string]*Wrapper
|
||||||
proxies map[string]*Wrapper
|
msgTransporter transport.MessageTransporter
|
||||||
|
|
||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
clientCfg config.ClientCommonConf
|
clientCfg config.ClientCommonConf
|
||||||
|
|
||||||
// The UDP port that the server is listening on
|
|
||||||
serverUDPPort int
|
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(ctx context.Context, msgSendCh chan (msg.Message), clientCfg config.ClientCommonConf, serverUDPPort int) *Manager {
|
func NewManager(
|
||||||
|
ctx context.Context,
|
||||||
|
clientCfg config.ClientCommonConf,
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
sendCh: msgSendCh,
|
proxies: make(map[string]*Wrapper),
|
||||||
proxies: make(map[string]*Wrapper),
|
msgTransporter: msgTransporter,
|
||||||
closed: false,
|
closed: false,
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
serverUDPPort: serverUDPPort,
|
ctx: ctx,
|
||||||
ctx: ctx,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,10 +99,7 @@ func (pm *Manager) HandleEvent(payload interface{}) error {
|
|||||||
return event.ErrPayloadType
|
return event.ErrPayloadType
|
||||||
}
|
}
|
||||||
|
|
||||||
err := errors.PanicToError(func() {
|
return pm.msgTransporter.Send(m)
|
||||||
pm.sendCh <- m
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
func (pm *Manager) GetAllProxyStatus() []*WorkingStatus {
|
||||||
@ -131,7 +141,7 @@ func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) {
|
|||||||
addPxyNames := make([]string, 0)
|
addPxyNames := make([]string, 0)
|
||||||
for name, cfg := range pxyCfgs {
|
for name, cfg := range pxyCfgs {
|
||||||
if _, ok := pm.proxies[name]; !ok {
|
if _, ok := pm.proxies[name]; !ok {
|
||||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.serverUDPPort)
|
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter)
|
||||||
pm.proxies[name] = pxy
|
pm.proxies[name] = pxy
|
||||||
addPxyNames = append(addPxyNames, name)
|
addPxyNames = append(addPxyNames, name)
|
||||||
|
|
||||||
|
@ -1,3 +1,17 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -14,6 +28,7 @@ import (
|
|||||||
"github.com/fatedier/frp/client/health"
|
"github.com/fatedier/frp/client/health"
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,6 +71,8 @@ type Wrapper struct {
|
|||||||
// event handler
|
// event handler
|
||||||
handler event.Handler
|
handler event.Handler
|
||||||
|
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
|
|
||||||
health uint32
|
health uint32
|
||||||
lastSendStartMsg time.Time
|
lastSendStartMsg time.Time
|
||||||
lastStartErr time.Time
|
lastStartErr time.Time
|
||||||
@ -67,7 +84,13 @@ type Wrapper struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.Handler, serverUDPPort int) *Wrapper {
|
func NewWrapper(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg config.ProxyConf,
|
||||||
|
clientCfg config.ClientCommonConf,
|
||||||
|
eventHandler event.Handler,
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
) *Wrapper {
|
||||||
baseInfo := cfg.GetBaseInfo()
|
baseInfo := cfg.GetBaseInfo()
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
|
||||||
pw := &Wrapper{
|
pw := &Wrapper{
|
||||||
@ -80,6 +103,7 @@ func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.Clie
|
|||||||
closeCh: make(chan struct{}),
|
closeCh: make(chan struct{}),
|
||||||
healthNotifyCh: make(chan struct{}),
|
healthNotifyCh: make(chan struct{}),
|
||||||
handler: eventHandler,
|
handler: eventHandler,
|
||||||
|
msgTransporter: msgTransporter,
|
||||||
xl: xl,
|
xl: xl,
|
||||||
ctx: xlog.NewContext(ctx, xl),
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
}
|
}
|
||||||
@ -92,7 +116,7 @@ func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.Clie
|
|||||||
xl.Trace("enable health check monitor")
|
xl.Trace("enable health check monitor")
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, serverUDPPort)
|
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter)
|
||||||
return pw
|
return pw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
190
client/proxy/sudp.go
Normal file
190
client/proxy/sudp.go
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
|
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SUDPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *config.SUDPProxyConf
|
||||||
|
|
||||||
|
localAddr *net.UDPAddr
|
||||||
|
|
||||||
|
closeCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *SUDPProxy) Run() (err error) {
|
||||||
|
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *SUDPProxy) Close() {
|
||||||
|
pxy.mu.Lock()
|
||||||
|
defer pxy.mu.Unlock()
|
||||||
|
select {
|
||||||
|
case <-pxy.closeCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
close(pxy.closeCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String())
|
||||||
|
|
||||||
|
var rwc io.ReadWriteCloser = conn
|
||||||
|
var err error
|
||||||
|
if pxy.limiter != nil {
|
||||||
|
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||||
|
return conn.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if pxy.cfg.UseEncryption {
|
||||||
|
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pxy.cfg.UseCompression {
|
||||||
|
rwc = frpIo.WithCompression(rwc)
|
||||||
|
}
|
||||||
|
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
||||||
|
|
||||||
|
workConn := conn
|
||||||
|
readCh := make(chan *msg.UDPPacket, 1024)
|
||||||
|
sendCh := make(chan msg.Message, 1024)
|
||||||
|
isClose := false
|
||||||
|
|
||||||
|
mu := &sync.Mutex{}
|
||||||
|
|
||||||
|
closeFn := func() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if isClose {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isClose = true
|
||||||
|
if workConn != nil {
|
||||||
|
workConn.Close()
|
||||||
|
}
|
||||||
|
close(readCh)
|
||||||
|
close(sendCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||||
|
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||||
|
defer closeFn()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// first to check sudp proxy is closed or not
|
||||||
|
select {
|
||||||
|
case <-pxy.closeCh:
|
||||||
|
xl.Trace("frpc sudp proxy is closed")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
var udpMsg msg.UDPPacket
|
||||||
|
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||||
|
xl.Warn("read from workConn for sudp error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errRet := errors.PanicToError(func() {
|
||||||
|
readCh <- &udpMsg
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Warn("reader goroutine for sudp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||||
|
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||||
|
defer func() {
|
||||||
|
closeFn()
|
||||||
|
xl.Info("writer goroutine for sudp work connection closed")
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errRet error
|
||||||
|
for rawMsg := range sendCh {
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.UDPPacket:
|
||||||
|
xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]",
|
||||||
|
m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String())
|
||||||
|
case *msg.Ping:
|
||||||
|
xl.Trace("frpc send ping message to frpc visitor")
|
||||||
|
}
|
||||||
|
|
||||||
|
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||||
|
xl.Error("sudp work write error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
heartbeatFn := func(sendCh chan msg.Message) {
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
defer func() {
|
||||||
|
ticker.Stop()
|
||||||
|
closeFn()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errRet error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
if errRet = errors.PanicToError(func() {
|
||||||
|
sendCh <- &msg.Ping{}
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Warn("heartbeat goroutine for sudp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-pxy.closeCh:
|
||||||
|
xl.Trace("frpc sudp proxy is closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go workConnSenderFn(workConn, sendCh)
|
||||||
|
go workConnReaderFn(workConn, readCh)
|
||||||
|
go heartbeatFn(sendCh)
|
||||||
|
|
||||||
|
udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||||
|
}
|
157
client/proxy/udp.go
Normal file
157
client/proxy/udp.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
|
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UDP
|
||||||
|
type UDPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *config.UDPProxyConf
|
||||||
|
|
||||||
|
localAddr *net.UDPAddr
|
||||||
|
readCh chan *msg.UDPPacket
|
||||||
|
|
||||||
|
// include msg.UDPPacket and msg.Ping
|
||||||
|
sendCh chan msg.Message
|
||||||
|
workConn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *UDPProxy) Run() (err error) {
|
||||||
|
pxy.localAddr, err = net.ResolveUDPAddr("udp", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(pxy.cfg.LocalPort)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *UDPProxy) Close() {
|
||||||
|
pxy.mu.Lock()
|
||||||
|
defer pxy.mu.Unlock()
|
||||||
|
|
||||||
|
if !pxy.closed {
|
||||||
|
pxy.closed = true
|
||||||
|
if pxy.workConn != nil {
|
||||||
|
pxy.workConn.Close()
|
||||||
|
}
|
||||||
|
if pxy.readCh != nil {
|
||||||
|
close(pxy.readCh)
|
||||||
|
}
|
||||||
|
if pxy.sendCh != nil {
|
||||||
|
close(pxy.sendCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *UDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||||
|
// close resources releated with old workConn
|
||||||
|
pxy.Close()
|
||||||
|
|
||||||
|
var rwc io.ReadWriteCloser = conn
|
||||||
|
var err error
|
||||||
|
if pxy.limiter != nil {
|
||||||
|
rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||||
|
return conn.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if pxy.cfg.UseEncryption {
|
||||||
|
rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token))
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pxy.cfg.UseCompression {
|
||||||
|
rwc = frpIo.WithCompression(rwc)
|
||||||
|
}
|
||||||
|
conn = frpNet.WrapReadWriteCloserToConn(rwc, conn)
|
||||||
|
|
||||||
|
pxy.mu.Lock()
|
||||||
|
pxy.workConn = conn
|
||||||
|
pxy.readCh = make(chan *msg.UDPPacket, 1024)
|
||||||
|
pxy.sendCh = make(chan msg.Message, 1024)
|
||||||
|
pxy.closed = false
|
||||||
|
pxy.mu.Unlock()
|
||||||
|
|
||||||
|
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) {
|
||||||
|
for {
|
||||||
|
var udpMsg msg.UDPPacket
|
||||||
|
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
|
||||||
|
xl.Warn("read from workConn for udp error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errRet := errors.PanicToError(func() {
|
||||||
|
xl.Trace("get udp package from workConn: %s", udpMsg.Content)
|
||||||
|
readCh <- &udpMsg
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Info("reader goroutine for udp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
|
||||||
|
defer func() {
|
||||||
|
xl.Info("writer goroutine for udp work connection closed")
|
||||||
|
}()
|
||||||
|
var errRet error
|
||||||
|
for rawMsg := range sendCh {
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.UDPPacket:
|
||||||
|
xl.Trace("send udp package to workConn: %s", m.Content)
|
||||||
|
case *msg.Ping:
|
||||||
|
xl.Trace("send ping message to udp workConn")
|
||||||
|
}
|
||||||
|
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
|
||||||
|
xl.Error("udp work write error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
heartbeatFn := func(sendCh chan msg.Message) {
|
||||||
|
var errRet error
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Duration(30) * time.Second)
|
||||||
|
if errRet = errors.PanicToError(func() {
|
||||||
|
sendCh <- &msg.Ping{}
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Trace("heartbeat goroutine for udp work connection closed")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go workConnSenderFn(pxy.workConn, pxy.sendCh)
|
||||||
|
go workConnReaderFn(pxy.workConn, pxy.readCh)
|
||||||
|
go heartbeatFn(pxy.sendCh)
|
||||||
|
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh, int(pxy.clientCfg.UDPPacketSize))
|
||||||
|
}
|
200
client/proxy/xtcp.go
Normal file
200
client/proxy/xtcp.go
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
"github.com/quic-go/quic-go"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/nathole"
|
||||||
|
plugin "github.com/fatedier/frp/pkg/plugin/client"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// XTCP
|
||||||
|
type XTCPProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
|
||||||
|
cfg *config.XTCPProxyConf
|
||||||
|
proxyPlugin plugin.Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XTCPProxy) Run() (err error) {
|
||||||
|
if pxy.cfg.Plugin != "" {
|
||||||
|
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XTCPProxy) Close() {
|
||||||
|
if pxy.proxyPlugin != nil {
|
||||||
|
pxy.proxyPlugin.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
defer conn.Close()
|
||||||
|
var natHoleSidMsg msg.NatHoleSid
|
||||||
|
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("xtcp read from workConn error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer})
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("nathole prepare error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
||||||
|
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
||||||
|
defer prepareResult.ListenConn.Close()
|
||||||
|
|
||||||
|
// send NatHoleClient msg to server
|
||||||
|
transactionID := nathole.NewTransactionID()
|
||||||
|
natHoleClientMsg := &msg.NatHoleClient{
|
||||||
|
TransactionID: transactionID,
|
||||||
|
ProxyName: pxy.cfg.ProxyName,
|
||||||
|
Sid: natHoleSidMsg.Sid,
|
||||||
|
MappedAddrs: prepareResult.Addrs,
|
||||||
|
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
natHoleRespMsg, err := nathole.ExchangeInfo(pxy.ctx, pxy.msgTransporter, transactionID, natHoleClientMsg, 5*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("nathole exchange info error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
||||||
|
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||||
|
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||||
|
|
||||||
|
listenConn := prepareResult.ListenConn
|
||||||
|
newListenConn, raddr, err := nathole.MakeHole(pxy.ctx, listenConn, natHoleRespMsg, []byte(pxy.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warn("make hole error: %v", err)
|
||||||
|
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||||
|
Sid: natHoleRespMsg.Sid,
|
||||||
|
Success: false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listenConn = newListenConn
|
||||||
|
xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||||
|
|
||||||
|
_ = pxy.msgTransporter.Send(&msg.NatHoleReport{
|
||||||
|
Sid: natHoleRespMsg.Sid,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if natHoleRespMsg.Protocol == "kcp" {
|
||||||
|
pxy.listenByKCP(listenConn, raddr, startWorkConnMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// default is quic
|
||||||
|
pxy.listenByQUIC(listenConn, raddr, startWorkConnMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XTCPProxy) listenByKCP(listenConn *net.UDPConn, raddr *net.UDPAddr, startWorkConnMsg *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
listenConn.Close()
|
||||||
|
laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String())
|
||||||
|
lConn, err := net.DialUDP("udp", laddr, raddr)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("dial udp error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lConn.Close()
|
||||||
|
|
||||||
|
remote, err := frpNet.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("create kcp connection from udp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 10 * time.Second
|
||||||
|
fmuxCfg.MaxStreamWindowSize = 2 * 1024 * 1024
|
||||||
|
fmuxCfg.LogOutput = io.Discard
|
||||||
|
session, err := fmux.Server(remote, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("create mux session error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
muxConn, err := session.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("accept connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||||
|
muxConn, []byte(pxy.cfg.Sk), startWorkConnMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XTCPProxy) listenByQUIC(listenConn *net.UDPConn, _ *net.UDPAddr, startWorkConnMsg *msg.StartWorkConn) {
|
||||||
|
xl := pxy.xl
|
||||||
|
defer listenConn.Close()
|
||||||
|
|
||||||
|
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("create tls config error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
|
quicListener, err := quic.Listen(listenConn, tlsConfig,
|
||||||
|
&quic.Config{
|
||||||
|
MaxIdleTimeout: time.Duration(pxy.clientCfg.QUICMaxIdleTimeout) * time.Second,
|
||||||
|
MaxIncomingStreams: int64(pxy.clientCfg.QUICMaxIncomingStreams),
|
||||||
|
KeepAlivePeriod: time.Duration(pxy.clientCfg.QUICKeepalivePeriod) * time.Second,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("dial quic error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// only accept one connection from raddr
|
||||||
|
c, err := quicListener.Accept(pxy.ctx)
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("quic accept connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
stream, err := c.AcceptStream(pxy.ctx)
|
||||||
|
if err != nil {
|
||||||
|
xl.Debug("quic accept stream error: %v", err)
|
||||||
|
_ = c.CloseWithError(0, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter,
|
||||||
|
frpNet.QuicStreamToNetConn(stream, c), []byte(pxy.cfg.Sk), startWorkConnMsg)
|
||||||
|
}
|
||||||
|
}
|
@ -72,9 +72,6 @@ type Service struct {
|
|||||||
// string if no configuration file was used.
|
// string if no configuration file was used.
|
||||||
cfgFile string
|
cfgFile string
|
||||||
|
|
||||||
// This is configured by the login response from frps
|
|
||||||
serverUDPPort int
|
|
||||||
|
|
||||||
exit uint32 // 0 means not exit
|
exit uint32 // 0 means not exit
|
||||||
|
|
||||||
// service context
|
// service context
|
||||||
@ -141,7 +138,7 @@ func (svr *Service) Run() error {
|
|||||||
util.RandomSleep(10*time.Second, 0.9, 1.1)
|
util.RandomSleep(10*time.Second, 0.9, 1.1)
|
||||||
} else {
|
} else {
|
||||||
// login success
|
// login success
|
||||||
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||||
ctl.Run()
|
ctl.Run()
|
||||||
svr.ctlMu.Lock()
|
svr.ctlMu.Lock()
|
||||||
svr.ctl = ctl
|
svr.ctl = ctl
|
||||||
@ -223,7 +220,7 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
// reconnect success, init delayTime
|
// reconnect success, init delayTime
|
||||||
delayTime = time.Second
|
delayTime = time.Second
|
||||||
|
|
||||||
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
|
ctl := NewControl(svr.ctx, svr.runID, conn, cm, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.authSetter)
|
||||||
ctl.Run()
|
ctl.Run()
|
||||||
svr.ctlMu.Lock()
|
svr.ctlMu.Lock()
|
||||||
if svr.ctl != nil {
|
if svr.ctl != nil {
|
||||||
@ -295,8 +292,7 @@ func (svr *Service) login() (conn net.Conn, cm *ConnectionManager, err error) {
|
|||||||
xl.ResetPrefixes()
|
xl.ResetPrefixes()
|
||||||
xl.AppendPrefix(svr.runID)
|
xl.AppendPrefix(svr.runID)
|
||||||
|
|
||||||
svr.serverUDPPort = loginRespMsg.ServerUDPPort
|
xl.Info("login to server success, get run id [%s]", loginRespMsg.RunID)
|
||||||
xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunID, loginRespMsg.ServerUDPPort)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,575 +0,0 @@
|
|||||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
frpIo "github.com/fatedier/golib/io"
|
|
||||||
"github.com/fatedier/golib/pool"
|
|
||||||
fmux "github.com/hashicorp/yamux"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
|
||||||
"github.com/fatedier/frp/pkg/proto/udp"
|
|
||||||
frpNet "github.com/fatedier/frp/pkg/util/net"
|
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Visitor is used for forward traffics from local port to remote service.
|
|
||||||
type Visitor interface {
|
|
||||||
Run() error
|
|
||||||
Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVisitor(ctx context.Context, ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName)
|
|
||||||
baseVisitor := BaseVisitor{
|
|
||||||
ctl: ctl,
|
|
||||||
ctx: xlog.NewContext(ctx, xl),
|
|
||||||
}
|
|
||||||
switch cfg := cfg.(type) {
|
|
||||||
case *config.STCPVisitorConf:
|
|
||||||
visitor = &STCPVisitor{
|
|
||||||
BaseVisitor: &baseVisitor,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.XTCPVisitorConf:
|
|
||||||
visitor = &XTCPVisitor{
|
|
||||||
BaseVisitor: &baseVisitor,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
case *config.SUDPVisitorConf:
|
|
||||||
visitor = &SUDPVisitor{
|
|
||||||
BaseVisitor: &baseVisitor,
|
|
||||||
cfg: cfg,
|
|
||||||
checkCloseCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseVisitor struct {
|
|
||||||
ctl *Control
|
|
||||||
l net.Listener
|
|
||||||
|
|
||||||
mu sync.RWMutex
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
type STCPVisitor struct {
|
|
||||||
*BaseVisitor
|
|
||||||
|
|
||||||
cfg *config.STCPVisitorConf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *STCPVisitor) Run() (err error) {
|
|
||||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go sv.worker()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *STCPVisitor) Close() {
|
|
||||||
sv.l.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *STCPVisitor) worker() {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
for {
|
|
||||||
conn, err := sv.l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("stcp local listener closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go sv.handleConn(conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
defer userConn.Close()
|
|
||||||
|
|
||||||
xl.Debug("get a new stcp user connection")
|
|
||||||
visitorConn, err := sv.ctl.connectServer()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer visitorConn.Close()
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
|
||||||
ProxyName: sv.cfg.ServerName,
|
|
||||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
|
||||||
Timestamp: now,
|
|
||||||
UseEncryption: sv.cfg.UseEncryption,
|
|
||||||
UseCompression: sv.cfg.UseCompression,
|
|
||||||
}
|
|
||||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("send newVisitorConnMsg to server error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("get newVisitorConnRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
|
||||||
|
|
||||||
if newVisitorConnRespMsg.Error != "" {
|
|
||||||
xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var remote io.ReadWriteCloser
|
|
||||||
remote = visitorConn
|
|
||||||
if sv.cfg.UseEncryption {
|
|
||||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create encryption stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sv.cfg.UseCompression {
|
|
||||||
remote = frpIo.WithCompression(remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
frpIo.Join(userConn, remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
type XTCPVisitor struct {
|
|
||||||
*BaseVisitor
|
|
||||||
|
|
||||||
cfg *config.XTCPVisitorConf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *XTCPVisitor) Run() (err error) {
|
|
||||||
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go sv.worker()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *XTCPVisitor) Close() {
|
|
||||||
sv.l.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *XTCPVisitor) worker() {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
for {
|
|
||||||
conn, err := sv.l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("xtcp local listener closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go sv.handleConn(conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
defer userConn.Close()
|
|
||||||
|
|
||||||
xl.Debug("get a new xtcp user connection")
|
|
||||||
if sv.ctl.serverUDPPort == 0 {
|
|
||||||
xl.Error("xtcp is not supported by server")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serverAddr := sv.ctl.clientCfg.NatHoleServerAddr
|
|
||||||
if serverAddr == "" {
|
|
||||||
serverAddr = sv.ctl.clientCfg.ServerAddr
|
|
||||||
}
|
|
||||||
raddr, err := net.ResolveUDPAddr("udp",
|
|
||||||
net.JoinHostPort(serverAddr, strconv.Itoa(sv.ctl.serverUDPPort)))
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("resolve server UDP addr error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
visitorConn, err := net.DialUDP("udp", nil, raddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("dial server udp addr error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer visitorConn.Close()
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
natHoleVisitorMsg := &msg.NatHoleVisitor{
|
|
||||||
ProxyName: sv.cfg.ServerName,
|
|
||||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
|
||||||
Timestamp: now,
|
|
||||||
}
|
|
||||||
err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("send natHoleVisitorMsg to server error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for client address at most 10 seconds.
|
|
||||||
var natHoleRespMsg msg.NatHoleResp
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
buf := pool.GetBuf(1024)
|
|
||||||
n, err := visitorConn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("get natHoleRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("get natHoleRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
|
||||||
pool.PutBuf(buf)
|
|
||||||
|
|
||||||
if natHoleRespMsg.Error != "" {
|
|
||||||
xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
xl.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
|
||||||
|
|
||||||
// Close visitorConn, so we can use it's local address.
|
|
||||||
visitorConn.Close()
|
|
||||||
|
|
||||||
// send sid message to client
|
|
||||||
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
|
||||||
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("resolve client udp address error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("dial client udp address error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer lConn.Close()
|
|
||||||
|
|
||||||
if _, err := lConn.Write([]byte(natHoleRespMsg.Sid)); err != nil {
|
|
||||||
xl.Error("write sid error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// read ack sid from client
|
|
||||||
sidBuf := pool.GetBuf(1024)
|
|
||||||
_ = lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
|
||||||
n, err = lConn.Read(sidBuf)
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("get sid from client error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = lConn.SetReadDeadline(time.Time{})
|
|
||||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
|
||||||
xl.Warn("incorrect sid from client")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pool.PutBuf(sidBuf)
|
|
||||||
|
|
||||||
xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
|
||||||
|
|
||||||
// wrap kcp connection
|
|
||||||
var remote io.ReadWriteCloser
|
|
||||||
remote, err = frpNet.NewKCPConnFromUDP(lConn, true, natHoleRespMsg.ClientAddr)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create kcp connection from udp connection error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
|
||||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
|
||||||
fmuxCfg.LogOutput = io.Discard
|
|
||||||
sess, err := fmux.Client(remote, fmuxCfg)
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create yamux session error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer sess.Close()
|
|
||||||
muxConn, err := sess.Open()
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("open yamux stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var muxConnRWCloser io.ReadWriteCloser = muxConn
|
|
||||||
if sv.cfg.UseEncryption {
|
|
||||||
muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create encryption stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sv.cfg.UseCompression {
|
|
||||||
muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, errs := frpIo.Join(userConn, muxConnRWCloser)
|
|
||||||
xl.Debug("join connections closed")
|
|
||||||
if len(errs) > 0 {
|
|
||||||
xl.Trace("join connections errors: %v", errs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SUDPVisitor struct {
|
|
||||||
*BaseVisitor
|
|
||||||
|
|
||||||
checkCloseCh chan struct{}
|
|
||||||
// udpConn is the listener of udp packet
|
|
||||||
udpConn *net.UDPConn
|
|
||||||
readCh chan *msg.UDPPacket
|
|
||||||
sendCh chan *msg.UDPPacket
|
|
||||||
|
|
||||||
cfg *config.SUDPVisitorConf
|
|
||||||
}
|
|
||||||
|
|
||||||
// SUDP Run start listen a udp port
|
|
||||||
func (sv *SUDPVisitor) Run() (err error) {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sv.udpConn, err = net.ListenUDP("udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("listen udp port %s error: %v", addr.String(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sv.sendCh = make(chan *msg.UDPPacket, 1024)
|
|
||||||
sv.readCh = make(chan *msg.UDPPacket, 1024)
|
|
||||||
|
|
||||||
xl.Info("sudp start to work, listen on %s", addr)
|
|
||||||
|
|
||||||
go sv.dispatcher()
|
|
||||||
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.ctl.clientCfg.UDPPacketSize))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *SUDPVisitor) dispatcher() {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
|
|
||||||
var (
|
|
||||||
visitorConn net.Conn
|
|
||||||
err error
|
|
||||||
|
|
||||||
firstPacket *msg.UDPPacket
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case firstPacket = <-sv.sendCh:
|
|
||||||
if firstPacket == nil {
|
|
||||||
xl.Info("frpc sudp visitor proxy is closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-sv.checkCloseCh:
|
|
||||||
xl.Info("frpc sudp visitor proxy is closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
visitorConn, err = sv.getNewVisitorConn()
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// visitorConn always be closed when worker done.
|
|
||||||
sv.worker(visitorConn, firstPacket)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-sv.checkCloseCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
xl.Debug("starting sudp proxy worker")
|
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
wg.Add(2)
|
|
||||||
closeCh := make(chan struct{})
|
|
||||||
|
|
||||||
// udp service -> frpc -> frps -> frpc visitor -> user
|
|
||||||
workConnReaderFn := func(conn net.Conn) {
|
|
||||||
defer func() {
|
|
||||||
conn.Close()
|
|
||||||
close(closeCh)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
var (
|
|
||||||
rawMsg msg.Message
|
|
||||||
errRet error
|
|
||||||
)
|
|
||||||
|
|
||||||
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
|
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
|
||||||
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
|
||||||
xl.Warn("read from workconn for user udp conn error: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = conn.SetReadDeadline(time.Time{})
|
|
||||||
switch m := rawMsg.(type) {
|
|
||||||
case *msg.Ping:
|
|
||||||
xl.Debug("frpc visitor get ping message from frpc")
|
|
||||||
continue
|
|
||||||
case *msg.UDPPacket:
|
|
||||||
if errRet := errors.PanicToError(func() {
|
|
||||||
sv.readCh <- m
|
|
||||||
xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content)
|
|
||||||
}); errRet != nil {
|
|
||||||
xl.Info("reader goroutine for udp work connection closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// udp service <- frpc <- frps <- frpc visitor <- user
|
|
||||||
workConnSenderFn := func(conn net.Conn) {
|
|
||||||
defer func() {
|
|
||||||
conn.Close()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var errRet error
|
|
||||||
if firstPacket != nil {
|
|
||||||
if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil {
|
|
||||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
xl.Trace("send udp package to workConn: %s", firstPacket.Content)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case udpMsg, ok := <-sv.sendCh:
|
|
||||||
if !ok {
|
|
||||||
xl.Info("sender goroutine for udp work connection closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
|
||||||
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
xl.Trace("send udp package to workConn: %s", udpMsg.Content)
|
|
||||||
case <-closeCh:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go workConnReaderFn(workConn)
|
|
||||||
go workConnSenderFn(workConn)
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
xl.Info("sudp worker is closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
|
||||||
visitorConn, err := sv.ctl.connectServer()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("frpc connect frps error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
newVisitorConnMsg := &msg.NewVisitorConn{
|
|
||||||
ProxyName: sv.cfg.ServerName,
|
|
||||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
|
||||||
Timestamp: now,
|
|
||||||
UseEncryption: sv.cfg.UseEncryption,
|
|
||||||
UseCompression: sv.cfg.UseCompression,
|
|
||||||
}
|
|
||||||
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err)
|
|
||||||
}
|
|
||||||
_ = visitorConn.SetReadDeadline(time.Time{})
|
|
||||||
|
|
||||||
if newVisitorConnRespMsg.Error != "" {
|
|
||||||
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var remote io.ReadWriteCloser
|
|
||||||
remote = visitorConn
|
|
||||||
if sv.cfg.UseEncryption {
|
|
||||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
|
||||||
if err != nil {
|
|
||||||
xl.Error("create encryption stream error: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sv.cfg.UseCompression {
|
|
||||||
remote = frpIo.WithCompression(remote)
|
|
||||||
}
|
|
||||||
return frpNet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *SUDPVisitor) Close() {
|
|
||||||
sv.mu.Lock()
|
|
||||||
defer sv.mu.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-sv.checkCloseCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
close(sv.checkCloseCh)
|
|
||||||
}
|
|
||||||
if sv.udpConn != nil {
|
|
||||||
sv.udpConn.Close()
|
|
||||||
}
|
|
||||||
close(sv.readCh)
|
|
||||||
close(sv.sendCh)
|
|
||||||
}
|
|
118
client/visitor/stcp.go
Normal file
118
client/visitor/stcp.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package visitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type STCPVisitor struct {
|
||||||
|
*BaseVisitor
|
||||||
|
|
||||||
|
cfg *config.STCPVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) Run() (err error) {
|
||||||
|
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.worker()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) Close() {
|
||||||
|
sv.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) worker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("stcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *STCPVisitor) handleConn(userConn net.Conn) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
xl.Debug("get a new stcp user connection")
|
||||||
|
visitorConn, err := sv.connectServer()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer visitorConn.Close()
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
UseEncryption: sv.cfg.UseEncryption,
|
||||||
|
UseCompression: sv.cfg.UseCompression,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("send newVisitorConnMsg to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||||
|
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
if newVisitorConnRespMsg.Error != "" {
|
||||||
|
xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote = visitorConn
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
remote = frpIo.WithCompression(remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(userConn, remote)
|
||||||
|
}
|
262
client/visitor/sudp.go
Normal file
262
client/visitor/sudp.go
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package visitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/proto/udp"
|
||||||
|
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SUDPVisitor struct {
|
||||||
|
*BaseVisitor
|
||||||
|
|
||||||
|
checkCloseCh chan struct{}
|
||||||
|
// udpConn is the listener of udp packet
|
||||||
|
udpConn *net.UDPConn
|
||||||
|
readCh chan *msg.UDPPacket
|
||||||
|
sendCh chan *msg.UDPPacket
|
||||||
|
|
||||||
|
cfg *config.SUDPVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
// SUDP Run start listen a udp port
|
||||||
|
func (sv *SUDPVisitor) Run() (err error) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sudp ResolveUDPAddr error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sv.udpConn, err = net.ListenUDP("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("listen udp port %s error: %v", addr.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sv.sendCh = make(chan *msg.UDPPacket, 1024)
|
||||||
|
sv.readCh = make(chan *msg.UDPPacket, 1024)
|
||||||
|
|
||||||
|
xl.Info("sudp start to work, listen on %s", addr)
|
||||||
|
|
||||||
|
go sv.dispatcher()
|
||||||
|
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.clientCfg.UDPPacketSize))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SUDPVisitor) dispatcher() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
|
||||||
|
var (
|
||||||
|
visitorConn net.Conn
|
||||||
|
err error
|
||||||
|
|
||||||
|
firstPacket *msg.UDPPacket
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case firstPacket = <-sv.sendCh:
|
||||||
|
if firstPacket == nil {
|
||||||
|
xl.Info("frpc sudp visitor proxy is closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-sv.checkCloseCh:
|
||||||
|
xl.Info("frpc sudp visitor proxy is closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
visitorConn, err = sv.getNewVisitorConn()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// visitorConn always be closed when worker done.
|
||||||
|
sv.worker(visitorConn, firstPacket)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sv.checkCloseCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
xl.Debug("starting sudp proxy worker")
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(2)
|
||||||
|
closeCh := make(chan struct{})
|
||||||
|
|
||||||
|
// udp service -> frpc -> frps -> frpc visitor -> user
|
||||||
|
workConnReaderFn := func(conn net.Conn) {
|
||||||
|
defer func() {
|
||||||
|
conn.Close()
|
||||||
|
close(closeCh)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
rawMsg msg.Message
|
||||||
|
errRet error
|
||||||
|
)
|
||||||
|
|
||||||
|
// frpc will send heartbeat in workConn to frpc visitor for keeping alive
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||||
|
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
|
||||||
|
xl.Warn("read from workconn for user udp conn error: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.Ping:
|
||||||
|
xl.Debug("frpc visitor get ping message from frpc")
|
||||||
|
continue
|
||||||
|
case *msg.UDPPacket:
|
||||||
|
if errRet := errors.PanicToError(func() {
|
||||||
|
sv.readCh <- m
|
||||||
|
xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content)
|
||||||
|
}); errRet != nil {
|
||||||
|
xl.Info("reader goroutine for udp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// udp service <- frpc <- frps <- frpc visitor <- user
|
||||||
|
workConnSenderFn := func(conn net.Conn) {
|
||||||
|
defer func() {
|
||||||
|
conn.Close()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errRet error
|
||||||
|
if firstPacket != nil {
|
||||||
|
if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil {
|
||||||
|
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Trace("send udp package to workConn: %s", firstPacket.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case udpMsg, ok := <-sv.sendCh:
|
||||||
|
if !ok {
|
||||||
|
xl.Info("sender goroutine for udp work connection closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
|
||||||
|
xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Trace("send udp package to workConn: %s", udpMsg.Content)
|
||||||
|
case <-closeCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go workConnReaderFn(workConn)
|
||||||
|
go workConnSenderFn(workConn)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
xl.Info("sudp worker is closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
visitorConn, err := sv.connectServer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("frpc connect frps error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
UseEncryption: sv.cfg.UseEncryption,
|
||||||
|
UseCompression: sv.cfg.UseCompression,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||||
|
_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err)
|
||||||
|
}
|
||||||
|
_ = visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
if newVisitorConnRespMsg.Error != "" {
|
||||||
|
return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote = visitorConn
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
remote = frpIo.WithCompression(remote)
|
||||||
|
}
|
||||||
|
return frpNet.WrapReadWriteCloserToConn(remote, visitorConn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SUDPVisitor) Close() {
|
||||||
|
sv.mu.Lock()
|
||||||
|
defer sv.mu.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sv.checkCloseCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
close(sv.checkCloseCh)
|
||||||
|
}
|
||||||
|
if sv.udpConn != nil {
|
||||||
|
sv.udpConn.Close()
|
||||||
|
}
|
||||||
|
close(sv.readCh)
|
||||||
|
close(sv.sendCh)
|
||||||
|
}
|
77
client/visitor/visitor.go
Normal file
77
client/visitor/visitor.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package visitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Visitor is used for forward traffics from local port tot remote service.
|
||||||
|
type Visitor interface {
|
||||||
|
Run() error
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitor(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg config.VisitorConf,
|
||||||
|
clientCfg config.ClientCommonConf,
|
||||||
|
connectServer func() (net.Conn, error),
|
||||||
|
msgTransporter transport.MessageTransporter,
|
||||||
|
) (visitor Visitor) {
|
||||||
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName)
|
||||||
|
baseVisitor := BaseVisitor{
|
||||||
|
clientCfg: clientCfg,
|
||||||
|
connectServer: connectServer,
|
||||||
|
msgTransporter: msgTransporter,
|
||||||
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
|
}
|
||||||
|
switch cfg := cfg.(type) {
|
||||||
|
case *config.STCPVisitorConf:
|
||||||
|
visitor = &STCPVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.XTCPVisitorConf:
|
||||||
|
visitor = &XTCPVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
startTunnelCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
case *config.SUDPVisitorConf:
|
||||||
|
visitor = &SUDPVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
checkCloseCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseVisitor struct {
|
||||||
|
clientCfg config.ClientCommonConf
|
||||||
|
connectServer func() (net.Conn, error)
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
|
l net.Listener
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
ctx context.Context
|
||||||
|
}
|
@ -12,22 +12,25 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package client
|
package visitor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VisitorManager struct {
|
type Manager struct {
|
||||||
ctl *Control
|
clientCfg config.ClientCommonConf
|
||||||
|
connectServer func() (net.Conn, error)
|
||||||
cfgs map[string]config.VisitorConf
|
msgTransporter transport.MessageTransporter
|
||||||
visitors map[string]Visitor
|
cfgs map[string]config.VisitorConf
|
||||||
|
visitors map[string]Visitor
|
||||||
|
|
||||||
checkInterval time.Duration
|
checkInterval time.Duration
|
||||||
|
|
||||||
@ -37,18 +40,25 @@ type VisitorManager struct {
|
|||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVisitorManager(ctx context.Context, ctl *Control) *VisitorManager {
|
func NewManager(
|
||||||
return &VisitorManager{
|
ctx context.Context,
|
||||||
ctl: ctl,
|
clientCfg config.ClientCommonConf,
|
||||||
cfgs: make(map[string]config.VisitorConf),
|
connectServer func() (net.Conn, error),
|
||||||
visitors: make(map[string]Visitor),
|
msgTransporter transport.MessageTransporter,
|
||||||
checkInterval: 10 * time.Second,
|
) *Manager {
|
||||||
ctx: ctx,
|
return &Manager{
|
||||||
stopCh: make(chan struct{}),
|
clientCfg: clientCfg,
|
||||||
|
connectServer: connectServer,
|
||||||
|
msgTransporter: msgTransporter,
|
||||||
|
cfgs: make(map[string]config.VisitorConf),
|
||||||
|
visitors: make(map[string]Visitor),
|
||||||
|
checkInterval: 10 * time.Second,
|
||||||
|
ctx: ctx,
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VisitorManager) Run() {
|
func (vm *Manager) Run() {
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
|
|
||||||
ticker := time.NewTicker(vm.checkInterval)
|
ticker := time.NewTicker(vm.checkInterval)
|
||||||
@ -74,10 +84,10 @@ func (vm *VisitorManager) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hold lock before calling this function.
|
// Hold lock before calling this function.
|
||||||
func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
func (vm *Manager) startVisitor(cfg config.VisitorConf) (err error) {
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
name := cfg.GetBaseInfo().ProxyName
|
name := cfg.GetBaseInfo().ProxyName
|
||||||
visitor := NewVisitor(vm.ctx, vm.ctl, cfg)
|
visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.connectServer, vm.msgTransporter)
|
||||||
err = visitor.Run()
|
err = visitor.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warn("start error: %v", err)
|
xl.Warn("start error: %v", err)
|
||||||
@ -88,7 +98,7 @@ func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
func (vm *Manager) Reload(cfgs map[string]config.VisitorConf) {
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
defer vm.mu.Unlock()
|
defer vm.mu.Unlock()
|
||||||
@ -129,7 +139,7 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VisitorManager) Close() {
|
func (vm *Manager) Close() {
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
defer vm.mu.Unlock()
|
defer vm.mu.Unlock()
|
||||||
for _, v := range vm.visitors {
|
for _, v := range vm.visitors {
|
410
client/visitor/xtcp.go
Normal file
410
client/visitor/xtcp.go
Normal file
@ -0,0 +1,410 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package visitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
frpIo "github.com/fatedier/golib/io"
|
||||||
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
quic "github.com/quic-go/quic-go"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/nathole"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
frpNet "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNoTunnelSession = errors.New("no tunnel session")
|
||||||
|
|
||||||
|
type XTCPVisitor struct {
|
||||||
|
*BaseVisitor
|
||||||
|
session TunnelSession
|
||||||
|
startTunnelCh chan struct{}
|
||||||
|
retryLimiter *rate.Limiter
|
||||||
|
cancel context.CancelFunc
|
||||||
|
|
||||||
|
cfg *config.XTCPVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) Run() (err error) {
|
||||||
|
sv.ctx, sv.cancel = context.WithCancel(sv.ctx)
|
||||||
|
|
||||||
|
if sv.cfg.Protocol == "kcp" {
|
||||||
|
sv.session = NewKCPTunnelSession()
|
||||||
|
} else {
|
||||||
|
sv.session = NewQUICTunnelSession(&sv.clientCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
sv.l, err = net.Listen("tcp", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(sv.cfg.BindPort)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.worker()
|
||||||
|
go sv.processTunnelStartEvents()
|
||||||
|
if sv.cfg.KeepTunnelOpen {
|
||||||
|
sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour)
|
||||||
|
go sv.keepTunnelOpenWorker()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) Close() {
|
||||||
|
sv.l.Close()
|
||||||
|
sv.cancel()
|
||||||
|
if sv.session != nil {
|
||||||
|
sv.session.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) worker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("xtcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) processTunnelStartEvents() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-sv.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-sv.startTunnelCh:
|
||||||
|
start := time.Now()
|
||||||
|
sv.makeNatHole()
|
||||||
|
duration := time.Since(start)
|
||||||
|
// avoid too frequently
|
||||||
|
if duration < 10*time.Second {
|
||||||
|
time.Sleep(10*time.Second - duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) keepTunnelOpenWorker() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
ticker := time.NewTicker(time.Duration(sv.cfg.MinRetryInterval) * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
sv.startTunnelCh <- struct{}{}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-sv.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
xl.Debug("keepTunnelOpenWorker try to check tunnel...")
|
||||||
|
conn, err := sv.getTunnelConn()
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("keepTunnelOpenWorker get tunnel connection error: %v", err)
|
||||||
|
_ = sv.retryLimiter.Wait(sv.ctx)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
xl.Debug("keepTunnelOpenWorker check success")
|
||||||
|
if conn != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
xl.Debug("get a new xtcp user connection")
|
||||||
|
|
||||||
|
// Open a tunnel connection to the server. If there is already a successful hole-punching connection,
|
||||||
|
// it will be reused. Otherwise, it will block and wait for a successful hole-punching connection until timeout.
|
||||||
|
tunnelConn, err := sv.openTunnel()
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("open tunnel error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var muxConnRWCloser io.ReadWriteCloser = tunnelConn
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
xl.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, errs := frpIo.Join(userConn, muxConnRWCloser)
|
||||||
|
xl.Debug("join connections closed")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
xl.Trace("join connections errors: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// openTunnel will open a tunnel connection to the target server.
|
||||||
|
func (sv *XTCPVisitor) openTunnel() (conn net.Conn, err error) {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
timeoutC := time.After(20 * time.Second)
|
||||||
|
immediateTrigger := make(chan struct{}, 1)
|
||||||
|
defer close(immediateTrigger)
|
||||||
|
immediateTrigger <- struct{}{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-sv.ctx.Done():
|
||||||
|
return nil, sv.ctx.Err()
|
||||||
|
case <-immediateTrigger:
|
||||||
|
conn, err = sv.getTunnelConn()
|
||||||
|
case <-ticker.C:
|
||||||
|
conn, err = sv.getTunnelConn()
|
||||||
|
case <-timeoutC:
|
||||||
|
return nil, fmt.Errorf("open tunnel timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != ErrNoTunnelSession {
|
||||||
|
xl.Warn("get tunnel connection error: %v", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XTCPVisitor) getTunnelConn() (net.Conn, error) {
|
||||||
|
conn, err := sv.session.OpenConn(sv.ctx)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
sv.session.Close()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case sv.startTunnelCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0. PreCheck
|
||||||
|
// 1. Prepare
|
||||||
|
// 2. ExchangeInfo
|
||||||
|
// 3. MakeNATHole
|
||||||
|
// 4. Create a tunnel session using an underlying UDP connection.
|
||||||
|
func (sv *XTCPVisitor) makeNatHole() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
if err := nathole.PreCheck(sv.ctx, sv.msgTransporter, sv.cfg.ServerName, 5*time.Second); err != nil {
|
||||||
|
xl.Warn("nathole precheck error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer})
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("nathole prepare error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xl.Info("nathole prepare success, nat type: %s, behavior: %s, addresses: %v, assistedAddresses: %v",
|
||||||
|
prepareResult.NatType, prepareResult.Behavior, prepareResult.Addrs, prepareResult.AssistedAddrs)
|
||||||
|
|
||||||
|
listenConn := prepareResult.ListenConn
|
||||||
|
|
||||||
|
// send NatHoleVisitor to server
|
||||||
|
now := time.Now().Unix()
|
||||||
|
transactionID := nathole.NewTransactionID()
|
||||||
|
natHoleVisitorMsg := &msg.NatHoleVisitor{
|
||||||
|
TransactionID: transactionID,
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
Protocol: sv.cfg.Protocol,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
MappedAddrs: prepareResult.Addrs,
|
||||||
|
AssistedAddrs: prepareResult.AssistedAddrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
natHoleRespMsg, err := nathole.ExchangeInfo(sv.ctx, sv.msgTransporter, transactionID, natHoleVisitorMsg, 5*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warn("nathole exchange info error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Info("get natHoleRespMsg, sid [%s], protocol [%s], candidate address %v, assisted address %v, detectBehavior: %+v",
|
||||||
|
natHoleRespMsg.Sid, natHoleRespMsg.Protocol, natHoleRespMsg.CandidateAddrs,
|
||||||
|
natHoleRespMsg.AssistedAddrs, natHoleRespMsg.DetectBehavior)
|
||||||
|
|
||||||
|
newListenConn, raddr, err := nathole.MakeHole(sv.ctx, listenConn, natHoleRespMsg, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warn("make hole error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listenConn = newListenConn
|
||||||
|
xl.Info("establishing nat hole connection successful, sid [%s], remoteAddr [%s]", natHoleRespMsg.Sid, raddr)
|
||||||
|
|
||||||
|
if err := sv.session.Init(listenConn, raddr); err != nil {
|
||||||
|
listenConn.Close()
|
||||||
|
xl.Warn("init tunnel session error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TunnelSession interface {
|
||||||
|
Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error
|
||||||
|
OpenConn(context.Context) (net.Conn, error)
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type KCPTunnelSession struct {
|
||||||
|
session *fmux.Session
|
||||||
|
lConn *net.UDPConn
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKCPTunnelSession() TunnelSession {
|
||||||
|
return &KCPTunnelSession{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks *KCPTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error {
|
||||||
|
listenConn.Close()
|
||||||
|
laddr, _ := net.ResolveUDPAddr("udp", listenConn.LocalAddr().String())
|
||||||
|
lConn, err := net.DialUDP("udp", laddr, raddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dial udp error: %v", err)
|
||||||
|
}
|
||||||
|
remote, err := frpNet.NewKCPConnFromUDP(lConn, true, raddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create kcp connection from udp connection error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 10 * time.Second
|
||||||
|
fmuxCfg.MaxStreamWindowSize = 2 * 1024 * 1024
|
||||||
|
fmuxCfg.LogOutput = io.Discard
|
||||||
|
session, err := fmux.Client(remote, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
remote.Close()
|
||||||
|
return fmt.Errorf("initial client session error: %v", err)
|
||||||
|
}
|
||||||
|
ks.mu.Lock()
|
||||||
|
ks.session = session
|
||||||
|
ks.lConn = lConn
|
||||||
|
ks.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks *KCPTunnelSession) OpenConn(ctx context.Context) (net.Conn, error) {
|
||||||
|
ks.mu.RLock()
|
||||||
|
defer ks.mu.RUnlock()
|
||||||
|
session := ks.session
|
||||||
|
if session == nil {
|
||||||
|
return nil, ErrNoTunnelSession
|
||||||
|
}
|
||||||
|
return session.Open()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks *KCPTunnelSession) Close() {
|
||||||
|
ks.mu.Lock()
|
||||||
|
defer ks.mu.Unlock()
|
||||||
|
if ks.session != nil {
|
||||||
|
_ = ks.session.Close()
|
||||||
|
ks.session = nil
|
||||||
|
}
|
||||||
|
if ks.lConn != nil {
|
||||||
|
_ = ks.lConn.Close()
|
||||||
|
ks.lConn = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUICTunnelSession struct {
|
||||||
|
session quic.Connection
|
||||||
|
listenConn *net.UDPConn
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
clientCfg *config.ClientCommonConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQUICTunnelSession(clientCfg *config.ClientCommonConf) TunnelSession {
|
||||||
|
return &QUICTunnelSession{
|
||||||
|
clientCfg: clientCfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qs *QUICTunnelSession) Init(listenConn *net.UDPConn, raddr *net.UDPAddr) error {
|
||||||
|
tlsConfig, err := transport.NewClientTLSConfig("", "", "", raddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create tls config error: %v", err)
|
||||||
|
}
|
||||||
|
tlsConfig.NextProtos = []string{"frp"}
|
||||||
|
quicConn, err := quic.Dial(listenConn, raddr, raddr.String(), tlsConfig,
|
||||||
|
&quic.Config{
|
||||||
|
MaxIdleTimeout: time.Duration(qs.clientCfg.QUICMaxIdleTimeout) * time.Second,
|
||||||
|
MaxIncomingStreams: int64(qs.clientCfg.QUICMaxIncomingStreams),
|
||||||
|
KeepAlivePeriod: time.Duration(qs.clientCfg.QUICKeepalivePeriod) * time.Second,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dial quic error: %v", err)
|
||||||
|
}
|
||||||
|
qs.mu.Lock()
|
||||||
|
qs.session = quicConn
|
||||||
|
qs.listenConn = listenConn
|
||||||
|
qs.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qs *QUICTunnelSession) OpenConn(ctx context.Context) (net.Conn, error) {
|
||||||
|
qs.mu.RLock()
|
||||||
|
defer qs.mu.RUnlock()
|
||||||
|
session := qs.session
|
||||||
|
if session == nil {
|
||||||
|
return nil, ErrNoTunnelSession
|
||||||
|
}
|
||||||
|
stream, err := session.OpenStreamSync(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return frpNet.QuicStreamToNetConn(stream, session), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qs *QUICTunnelSession) Close() {
|
||||||
|
qs.mu.Lock()
|
||||||
|
defer qs.mu.Unlock()
|
||||||
|
if qs.session != nil {
|
||||||
|
_ = qs.session.CloseWithError(0, "")
|
||||||
|
qs.session = nil
|
||||||
|
}
|
||||||
|
if qs.listenConn != nil {
|
||||||
|
_ = qs.listenConn.Close()
|
||||||
|
qs.listenConn = nil
|
||||||
|
}
|
||||||
|
}
|
@ -16,9 +16,7 @@ package sub
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@ -28,7 +26,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
natHoleSTUNServer string
|
natHoleSTUNServer string
|
||||||
serverUDPPort int
|
natHoleLocalAddr string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -37,8 +35,8 @@ func init() {
|
|||||||
rootCmd.AddCommand(natholeCmd)
|
rootCmd.AddCommand(natholeCmd)
|
||||||
natholeCmd.AddCommand(natholeDiscoveryCmd)
|
natholeCmd.AddCommand(natholeDiscoveryCmd)
|
||||||
|
|
||||||
natholeCmd.PersistentFlags().StringVarP(&natHoleSTUNServer, "nat_hole_stun_server", "", "stun.easyvoip.com:3478", "STUN server address for nathole")
|
natholeCmd.PersistentFlags().StringVarP(&natHoleSTUNServer, "nat_hole_stun_server", "", "", "STUN server address for nathole")
|
||||||
natholeCmd.PersistentFlags().IntVarP(&serverUDPPort, "server_udp_port", "", 0, "UDP port of frps for nathole")
|
natholeCmd.PersistentFlags().StringVarP(&natHoleLocalAddr, "nat_hole_local_addr", "l", "", "local address to connect STUN server")
|
||||||
}
|
}
|
||||||
|
|
||||||
var natholeCmd = &cobra.Command{
|
var natholeCmd = &cobra.Command{
|
||||||
@ -48,48 +46,45 @@ var natholeCmd = &cobra.Command{
|
|||||||
|
|
||||||
var natholeDiscoveryCmd = &cobra.Command{
|
var natholeDiscoveryCmd = &cobra.Command{
|
||||||
Use: "discover",
|
Use: "discover",
|
||||||
Short: "Discover nathole information by frps and stun server",
|
Short: "Discover nathole information from stun server",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// ignore error here, because we can use command line pameters
|
// ignore error here, because we can use command line pameters
|
||||||
cfg, _, _, _ := config.ParseClientConfig(cfgFile)
|
cfg, _, _, err := config.ParseClientConfig(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
cfg = config.GetDefaultClientConf()
|
||||||
|
}
|
||||||
if natHoleSTUNServer != "" {
|
if natHoleSTUNServer != "" {
|
||||||
cfg.NatHoleSTUNServer = natHoleSTUNServer
|
cfg.NatHoleSTUNServer = natHoleSTUNServer
|
||||||
}
|
}
|
||||||
if serverUDPPort != 0 {
|
|
||||||
cfg.ServerUDPPort = serverUDPPort
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateForNatHoleDiscovery(cfg); err != nil {
|
if err := validateForNatHoleDiscovery(cfg); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverAddr := ""
|
addrs, localAddr, err := nathole.Discover([]string{cfg.NatHoleSTUNServer}, natHoleLocalAddr)
|
||||||
if cfg.ServerUDPPort != 0 {
|
|
||||||
serverAddr = net.JoinHostPort(cfg.ServerAddr, strconv.Itoa(cfg.ServerUDPPort))
|
|
||||||
}
|
|
||||||
addresses, err := nathole.Discover(
|
|
||||||
serverAddr,
|
|
||||||
[]string{cfg.NatHoleSTUNServer},
|
|
||||||
[]byte(cfg.Token),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("discover error:", err)
|
fmt.Println("discover error:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if len(addresses) < 2 {
|
if len(addrs) < 2 {
|
||||||
fmt.Printf("discover error: can not get enough addresses, need 2, got: %v\n", addresses)
|
fmt.Printf("discover error: can not get enough addresses, need 2, got: %v\n", addrs)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
natType, behavior, err := nathole.ClassifyNATType(addresses)
|
localIPs, _ := nathole.ListLocalIPsForNatHole(10)
|
||||||
|
|
||||||
|
natFeature, err := nathole.ClassifyNATFeature(addrs, localIPs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("classify nat type error:", err)
|
fmt.Println("classify nat feature error:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("Your NAT type is:", natType)
|
fmt.Println("STUN server:", cfg.NatHoleSTUNServer)
|
||||||
fmt.Println("Behavior is:", behavior)
|
fmt.Println("Your NAT type is:", natFeature.NatType)
|
||||||
fmt.Println("External address is:", addresses)
|
fmt.Println("Behavior is:", natFeature.Behavior)
|
||||||
|
fmt.Println("External address is:", addrs)
|
||||||
|
fmt.Println("Local address is:", localAddr.String())
|
||||||
|
fmt.Println("Public Network:", natFeature.PublicNetwork)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ var (
|
|||||||
logFile string
|
logFile string
|
||||||
logMaxDays int
|
logMaxDays int
|
||||||
disableLogColor bool
|
disableLogColor bool
|
||||||
|
dnsServer string
|
||||||
|
|
||||||
proxyName string
|
proxyName string
|
||||||
localIP string
|
localIP string
|
||||||
@ -94,6 +95,7 @@ func RegisterCommonFlags(cmd *cobra.Command) {
|
|||||||
cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "enable frpc tls")
|
cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "enable frpc tls")
|
||||||
|
cmd.PersistentFlags().StringVarP(&dnsServer, "dns_server", "", "", "specify dns server instead of using system default one")
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@ -108,26 +110,7 @@ var rootCmd = &cobra.Command{
|
|||||||
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
|
||||||
// Note that it's only designed for testing. It's not guaranteed to be stable.
|
// Note that it's only designed for testing. It's not guaranteed to be stable.
|
||||||
if cfgDir != "" {
|
if cfgDir != "" {
|
||||||
var wg sync.WaitGroup
|
_ = runMultipleClients(cfgDir)
|
||||||
_ = filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if d.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
wg.Add(1)
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
err := runClient(path)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("frpc service error for config file [%s]\n", path)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
wg.Wait()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +124,27 @@ var rootCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runMultipleClients(cfgDir string) error {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
err := filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil || d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
err := runClient(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("frpc service error for config file [%s]\n", path)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -177,6 +181,7 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
|
|||||||
cfg.LogFile = logFile
|
cfg.LogFile = logFile
|
||||||
cfg.LogMaxDays = int64(logMaxDays)
|
cfg.LogMaxDays = int64(logMaxDays)
|
||||||
cfg.DisableLogColor = disableLogColor
|
cfg.DisableLogColor = disableLogColor
|
||||||
|
cfg.DNSServer = dnsServer
|
||||||
|
|
||||||
// Only token authentication is supported in cmd mode
|
// Only token authentication is supported in cmd mode
|
||||||
cfg.ClientConfig = auth.GetDefaultClientConf()
|
cfg.ClientConfig = auth.GetDefaultClientConf()
|
||||||
|
9
go.mod
9
go.mod
@ -18,12 +18,14 @@ require (
|
|||||||
github.com/pion/stun v0.4.0
|
github.com/pion/stun v0.4.0
|
||||||
github.com/pires/go-proxyproto v0.6.2
|
github.com/pires/go-proxyproto v0.6.2
|
||||||
github.com/prometheus/client_golang v1.13.0
|
github.com/prometheus/client_golang v1.13.0
|
||||||
github.com/quic-go/quic-go v0.32.0
|
github.com/quic-go/quic-go v0.34.0
|
||||||
github.com/rodaine/table v1.0.1
|
github.com/rodaine/table v1.0.1
|
||||||
|
github.com/samber/lo v1.38.1
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.1.3
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
golang.org/x/net v0.7.0
|
golang.org/x/net v0.7.0
|
||||||
golang.org/x/oauth2 v0.3.0
|
golang.org/x/oauth2 v0.3.0
|
||||||
|
golang.org/x/sync v0.1.0
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
k8s.io/apimachinery v0.26.1
|
k8s.io/apimachinery v0.26.1
|
||||||
@ -55,9 +57,8 @@ require (
|
|||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
||||||
|
18
go.sum
18
go.sum
@ -381,14 +381,12 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
|||||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
|
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
|
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
|
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
|
github.com/quic-go/quic-go v0.34.0 h1:OvOJ9LFjTySgwOTYUZmNoq0FzVicP8YujpV0kB7m2lU=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
github.com/quic-go/quic-go v0.34.0/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
||||||
github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA=
|
|
||||||
github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo=
|
|
||||||
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
|
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
|
||||||
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
|
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
@ -399,6 +397,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA
|
|||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||||
|
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
@ -602,6 +602,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -661,6 +661,9 @@ func Test_LoadClientBasicConf(t *testing.T) {
|
|||||||
BindAddr: "127.0.0.1",
|
BindAddr: "127.0.0.1",
|
||||||
BindPort: 9001,
|
BindPort: 9001,
|
||||||
},
|
},
|
||||||
|
Protocol: "quic",
|
||||||
|
MaxRetriesAnHour: 8,
|
||||||
|
MinRetryInterval: 90,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1078,7 +1078,6 @@ func (cfg *XTCPProxyConf) Compare(cmp ProxyConf) bool {
|
|||||||
cfg.Sk != cmpConf.Sk {
|
cfg.Sk != cmpConf.Sk {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1092,7 +1091,6 @@ func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *
|
|||||||
if cfg.Role == "" {
|
if cfg.Role == "" {
|
||||||
cfg.Role = "server"
|
cfg.Role = "server"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1120,7 +1118,6 @@ func (cfg *XTCPProxyConf) CheckForCli() (err error) {
|
|||||||
if cfg.Role != "server" {
|
if cfg.Role != "server" {
|
||||||
return fmt.Errorf("role should be 'server'")
|
return fmt.Errorf("role should be 'server'")
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,35 +196,38 @@ type ServerCommonConf struct {
|
|||||||
// Enable golang pprof handlers in dashboard listener.
|
// Enable golang pprof handlers in dashboard listener.
|
||||||
// Dashboard port must be set first.
|
// Dashboard port must be set first.
|
||||||
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
|
||||||
|
// NatHoleAnalysisDataReserveHours specifies the hours to reserve nat hole analysis data.
|
||||||
|
NatHoleAnalysisDataReserveHours int64 `ini:"nat_hole_analysis_data_reserve_hours" json:"nat_hole_analysis_data_reserve_hours"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultServerConf returns a server configuration with reasonable
|
// GetDefaultServerConf returns a server configuration with reasonable
|
||||||
// defaults.
|
// defaults.
|
||||||
func GetDefaultServerConf() ServerCommonConf {
|
func GetDefaultServerConf() ServerCommonConf {
|
||||||
return ServerCommonConf{
|
return ServerCommonConf{
|
||||||
ServerConfig: auth.GetDefaultServerConf(),
|
ServerConfig: auth.GetDefaultServerConf(),
|
||||||
BindAddr: "0.0.0.0",
|
BindAddr: "0.0.0.0",
|
||||||
BindPort: 7000,
|
BindPort: 7000,
|
||||||
QUICKeepalivePeriod: 10,
|
QUICKeepalivePeriod: 10,
|
||||||
QUICMaxIdleTimeout: 30,
|
QUICMaxIdleTimeout: 30,
|
||||||
QUICMaxIncomingStreams: 100000,
|
QUICMaxIncomingStreams: 100000,
|
||||||
VhostHTTPTimeout: 60,
|
VhostHTTPTimeout: 60,
|
||||||
DashboardAddr: "0.0.0.0",
|
DashboardAddr: "0.0.0.0",
|
||||||
LogFile: "console",
|
LogFile: "console",
|
||||||
LogWay: "console",
|
LogWay: "console",
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
LogMaxDays: 3,
|
LogMaxDays: 3,
|
||||||
DetailedErrorsToClient: true,
|
DetailedErrorsToClient: true,
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
TCPMuxKeepaliveInterval: 60,
|
TCPMuxKeepaliveInterval: 60,
|
||||||
TCPKeepAlive: 7200,
|
TCPKeepAlive: 7200,
|
||||||
AllowPorts: make(map[int]struct{}),
|
AllowPorts: make(map[int]struct{}),
|
||||||
MaxPoolCount: 5,
|
MaxPoolCount: 5,
|
||||||
MaxPortsPerClient: 0,
|
MaxPortsPerClient: 0,
|
||||||
HeartbeatTimeout: 90,
|
HeartbeatTimeout: 90,
|
||||||
UserConnTimeout: 10,
|
UserConnTimeout: 10,
|
||||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||||
UDPPacketSize: 1500,
|
UDPPacketSize: 1500,
|
||||||
|
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,18 +134,19 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
12: {},
|
12: {},
|
||||||
99: {},
|
99: {},
|
||||||
},
|
},
|
||||||
AllowPortsStr: "10-12,99",
|
AllowPortsStr: "10-12,99",
|
||||||
MaxPoolCount: 59,
|
MaxPoolCount: 59,
|
||||||
MaxPortsPerClient: 9,
|
MaxPortsPerClient: 9,
|
||||||
TLSOnly: true,
|
TLSOnly: true,
|
||||||
TLSCertFile: "server.crt",
|
TLSCertFile: "server.crt",
|
||||||
TLSKeyFile: "server.key",
|
TLSKeyFile: "server.key",
|
||||||
TLSTrustedCaFile: "ca.crt",
|
TLSTrustedCaFile: "ca.crt",
|
||||||
SubDomainHost: "frps.com",
|
SubDomainHost: "frps.com",
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
TCPMuxKeepaliveInterval: 60,
|
TCPMuxKeepaliveInterval: 60,
|
||||||
TCPKeepAlive: 7200,
|
TCPKeepAlive: 7200,
|
||||||
UDPPacketSize: 1509,
|
UDPPacketSize: 1509,
|
||||||
|
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||||
|
|
||||||
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
HTTPPlugins: map[string]plugin.HTTPPluginOptions{
|
||||||
"user-manager": {
|
"user-manager": {
|
||||||
@ -180,32 +181,33 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||||||
AuthenticateNewWorkConns: false,
|
AuthenticateNewWorkConns: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
BindAddr: "0.0.0.9",
|
BindAddr: "0.0.0.9",
|
||||||
BindPort: 7009,
|
BindPort: 7009,
|
||||||
BindUDPPort: 7008,
|
BindUDPPort: 7008,
|
||||||
QUICKeepalivePeriod: 10,
|
QUICKeepalivePeriod: 10,
|
||||||
QUICMaxIdleTimeout: 30,
|
QUICMaxIdleTimeout: 30,
|
||||||
QUICMaxIncomingStreams: 100000,
|
QUICMaxIncomingStreams: 100000,
|
||||||
ProxyBindAddr: "0.0.0.9",
|
ProxyBindAddr: "0.0.0.9",
|
||||||
VhostHTTPTimeout: 60,
|
VhostHTTPTimeout: 60,
|
||||||
DashboardAddr: "0.0.0.0",
|
DashboardAddr: "0.0.0.0",
|
||||||
DashboardUser: "",
|
DashboardUser: "",
|
||||||
DashboardPwd: "",
|
DashboardPwd: "",
|
||||||
EnablePrometheus: false,
|
EnablePrometheus: false,
|
||||||
LogFile: "console",
|
LogFile: "console",
|
||||||
LogWay: "console",
|
LogWay: "console",
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
LogMaxDays: 3,
|
LogMaxDays: 3,
|
||||||
DetailedErrorsToClient: true,
|
DetailedErrorsToClient: true,
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
TCPMuxKeepaliveInterval: 60,
|
TCPMuxKeepaliveInterval: 60,
|
||||||
TCPKeepAlive: 7200,
|
TCPKeepAlive: 7200,
|
||||||
AllowPorts: make(map[int]struct{}),
|
AllowPorts: make(map[int]struct{}),
|
||||||
MaxPoolCount: 5,
|
MaxPoolCount: 5,
|
||||||
HeartbeatTimeout: 90,
|
HeartbeatTimeout: 90,
|
||||||
UserConnTimeout: 10,
|
UserConnTimeout: 10,
|
||||||
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
HTTPPlugins: make(map[string]plugin.HTTPPluginOptions),
|
||||||
UDPPacketSize: 1500,
|
UDPPacketSize: 1500,
|
||||||
|
NatHoleAnalysisDataReserveHours: 7 * 24,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/consts"
|
"github.com/fatedier/frp/pkg/consts"
|
||||||
@ -61,6 +62,11 @@ type STCPVisitorConf struct {
|
|||||||
|
|
||||||
type XTCPVisitorConf struct {
|
type XTCPVisitorConf struct {
|
||||||
BaseVisitorConf `ini:",extends"`
|
BaseVisitorConf `ini:",extends"`
|
||||||
|
|
||||||
|
Protocol string `ini:"protocol" json:"protocol,omitempty"`
|
||||||
|
KeepTunnelOpen bool `ini:"keep_tunnel_open" json:"keep_tunnel_open,omitempty"`
|
||||||
|
MaxRetriesAnHour int `ini:"max_retries_an_hour" json:"max_retries_an_hour,omitempty"`
|
||||||
|
MinRetryInterval int `ini:"min_retry_interval" json:"min_retry_interval,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
|
// DefaultVisitorConf creates a empty VisitorConf object by visitorType.
|
||||||
@ -259,7 +265,12 @@ func (cfg *XTCPVisitorConf) Compare(cmp VisitorConf) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom login equal, if exists
|
// 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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +281,15 @@ func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom logic unmarshal, if exists
|
// Add custom logic unmarshal, if exists
|
||||||
|
if cfg.Protocol == "" {
|
||||||
|
cfg.Protocol = "quic"
|
||||||
|
}
|
||||||
|
if cfg.MaxRetriesAnHour <= 0 {
|
||||||
|
cfg.MaxRetriesAnHour = 8
|
||||||
|
}
|
||||||
|
if cfg.MinRetryInterval <= 0 {
|
||||||
|
cfg.MinRetryInterval = 90
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,6 +299,8 @@ func (cfg *XTCPVisitorConf) Check() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom logic validate, if exists
|
// Add custom logic validate, if exists
|
||||||
|
if !lo.Contains([]string{"", "kcp", "quic"}, cfg.Protocol) {
|
||||||
|
return fmt.Errorf("protocol should be 'kcp' or 'quic'")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,9 @@ func Test_Visitor_UnmarshalFromIni(t *testing.T) {
|
|||||||
BindAddr: "127.0.0.1",
|
BindAddr: "127.0.0.1",
|
||||||
BindPort: 9001,
|
BindPort: 9001,
|
||||||
},
|
},
|
||||||
|
Protocol: "quic",
|
||||||
|
MaxRetriesAnHour: 8,
|
||||||
|
MinRetryInterval: 90,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -60,25 +60,30 @@ func (m *serverMetrics) run() {
|
|||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
time.Sleep(12 * time.Hour)
|
time.Sleep(12 * time.Hour)
|
||||||
log.Debug("start to clear useless proxy statistics data...")
|
start := time.Now()
|
||||||
m.clearUselessInfo()
|
count, total := m.clearUselessInfo()
|
||||||
log.Debug("finish to clear useless proxy statistics data")
|
log.Debug("clear useless proxy statistics data count %d/%d, cost %v", count, total, time.Since(start))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverMetrics) clearUselessInfo() {
|
func (m *serverMetrics) clearUselessInfo() (int, int) {
|
||||||
|
count := 0
|
||||||
|
total := 0
|
||||||
// To check if there are proxies that closed than 7 days and drop them.
|
// To check if there are proxies that closed than 7 days and drop them.
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
total = len(m.info.ProxyStatistics)
|
||||||
for name, data := range m.info.ProxyStatistics {
|
for name, data := range m.info.ProxyStatistics {
|
||||||
if !data.LastCloseTime.IsZero() &&
|
if !data.LastCloseTime.IsZero() &&
|
||||||
data.LastStartTime.Before(data.LastCloseTime) &&
|
data.LastStartTime.Before(data.LastCloseTime) &&
|
||||||
time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
time.Since(data.LastCloseTime) > time.Duration(7*24)*time.Hour {
|
||||||
delete(m.info.ProxyStatistics, name)
|
delete(m.info.ProxyStatistics, name)
|
||||||
|
count++
|
||||||
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
log.Trace("clear proxy [%s]'s statistics data, lastCloseTime: [%s]", name, data.LastCloseTime.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return count, total
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *serverMetrics) NewClient() {
|
func (m *serverMetrics) NewClient() {
|
||||||
|
142
pkg/msg/msg.go
142
pkg/msg/msg.go
@ -16,54 +16,53 @@ package msg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeLogin = 'o'
|
TypeLogin = 'o'
|
||||||
TypeLoginResp = '1'
|
TypeLoginResp = '1'
|
||||||
TypeNewProxy = 'p'
|
TypeNewProxy = 'p'
|
||||||
TypeNewProxyResp = '2'
|
TypeNewProxyResp = '2'
|
||||||
TypeCloseProxy = 'c'
|
TypeCloseProxy = 'c'
|
||||||
TypeNewWorkConn = 'w'
|
TypeNewWorkConn = 'w'
|
||||||
TypeReqWorkConn = 'r'
|
TypeReqWorkConn = 'r'
|
||||||
TypeStartWorkConn = 's'
|
TypeStartWorkConn = 's'
|
||||||
TypeNewVisitorConn = 'v'
|
TypeNewVisitorConn = 'v'
|
||||||
TypeNewVisitorConnResp = '3'
|
TypeNewVisitorConnResp = '3'
|
||||||
TypePing = 'h'
|
TypePing = 'h'
|
||||||
TypePong = '4'
|
TypePong = '4'
|
||||||
TypeUDPPacket = 'u'
|
TypeUDPPacket = 'u'
|
||||||
TypeNatHoleVisitor = 'i'
|
TypeNatHoleVisitor = 'i'
|
||||||
TypeNatHoleClient = 'n'
|
TypeNatHoleClient = 'n'
|
||||||
TypeNatHoleResp = 'm'
|
TypeNatHoleResp = 'm'
|
||||||
TypeNatHoleClientDetectOK = 'd'
|
TypeNatHoleSid = '5'
|
||||||
TypeNatHoleSid = '5'
|
TypeNatHoleReport = '6'
|
||||||
TypeNatHoleBinding = 'b'
|
|
||||||
TypeNatHoleBindingResp = '6'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var msgTypeMap = map[byte]interface{}{
|
var msgTypeMap = map[byte]interface{}{
|
||||||
TypeLogin: Login{},
|
TypeLogin: Login{},
|
||||||
TypeLoginResp: LoginResp{},
|
TypeLoginResp: LoginResp{},
|
||||||
TypeNewProxy: NewProxy{},
|
TypeNewProxy: NewProxy{},
|
||||||
TypeNewProxyResp: NewProxyResp{},
|
TypeNewProxyResp: NewProxyResp{},
|
||||||
TypeCloseProxy: CloseProxy{},
|
TypeCloseProxy: CloseProxy{},
|
||||||
TypeNewWorkConn: NewWorkConn{},
|
TypeNewWorkConn: NewWorkConn{},
|
||||||
TypeReqWorkConn: ReqWorkConn{},
|
TypeReqWorkConn: ReqWorkConn{},
|
||||||
TypeStartWorkConn: StartWorkConn{},
|
TypeStartWorkConn: StartWorkConn{},
|
||||||
TypeNewVisitorConn: NewVisitorConn{},
|
TypeNewVisitorConn: NewVisitorConn{},
|
||||||
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
||||||
TypePing: Ping{},
|
TypePing: Ping{},
|
||||||
TypePong: Pong{},
|
TypePong: Pong{},
|
||||||
TypeUDPPacket: UDPPacket{},
|
TypeUDPPacket: UDPPacket{},
|
||||||
TypeNatHoleVisitor: NatHoleVisitor{},
|
TypeNatHoleVisitor: NatHoleVisitor{},
|
||||||
TypeNatHoleClient: NatHoleClient{},
|
TypeNatHoleClient: NatHoleClient{},
|
||||||
TypeNatHoleResp: NatHoleResp{},
|
TypeNatHoleResp: NatHoleResp{},
|
||||||
TypeNatHoleClientDetectOK: NatHoleClientDetectOK{},
|
TypeNatHoleSid: NatHoleSid{},
|
||||||
TypeNatHoleSid: NatHoleSid{},
|
TypeNatHoleReport: NatHoleReport{},
|
||||||
TypeNatHoleBinding: NatHoleBinding{},
|
|
||||||
TypeNatHoleBindingResp: NatHoleBindingResp{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var TypeNameNatHoleResp = reflect.TypeOf(&NatHoleResp{}).Elem().Name()
|
||||||
|
|
||||||
// When frpc start, client send this message to login to server.
|
// When frpc start, client send this message to login to server.
|
||||||
type Login struct {
|
type Login struct {
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
@ -175,35 +174,58 @@ type UDPPacket struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NatHoleVisitor struct {
|
type NatHoleVisitor struct {
|
||||||
ProxyName string `json:"proxy_name,omitempty"`
|
TransactionID string `json:"transaction_id,omitempty"`
|
||||||
SignKey string `json:"sign_key,omitempty"`
|
ProxyName string `json:"proxy_name,omitempty"`
|
||||||
Timestamp int64 `json:"timestamp,omitempty"`
|
PreCheck bool `json:"pre_check,omitempty"`
|
||||||
|
Protocol string `json:"protocol,omitempty"`
|
||||||
|
SignKey string `json:"sign_key,omitempty"`
|
||||||
|
Timestamp int64 `json:"timestamp,omitempty"`
|
||||||
|
MappedAddrs []string `json:"mapped_addrs,omitempty"`
|
||||||
|
AssistedAddrs []string `json:"assisted_addrs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NatHoleClient struct {
|
type NatHoleClient struct {
|
||||||
ProxyName string `json:"proxy_name,omitempty"`
|
TransactionID string `json:"transaction_id,omitempty"`
|
||||||
Sid string `json:"sid,omitempty"`
|
ProxyName string `json:"proxy_name,omitempty"`
|
||||||
|
Sid string `json:"sid,omitempty"`
|
||||||
|
MappedAddrs []string `json:"mapped_addrs,omitempty"`
|
||||||
|
AssistedAddrs []string `json:"assisted_addrs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortsRange struct {
|
||||||
|
From int `json:"from,omitempty"`
|
||||||
|
To int `json:"to,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleDetectBehavior struct {
|
||||||
|
Role string `json:"role,omitempty"` // sender or receiver
|
||||||
|
Mode int `json:"mode,omitempty"` // 0, 1, 2...
|
||||||
|
TTL int `json:"ttl,omitempty"`
|
||||||
|
SendDelayMs int `json:"send_delay_ms,omitempty"`
|
||||||
|
ReadTimeoutMs int `json:"read_timeout,omitempty"`
|
||||||
|
CandidatePorts []PortsRange `json:"candidate_ports,omitempty"`
|
||||||
|
SendRandomPorts int `json:"send_random_ports,omitempty"`
|
||||||
|
ListenRandomPorts int `json:"listen_random_ports,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NatHoleResp struct {
|
type NatHoleResp struct {
|
||||||
Sid string `json:"sid,omitempty"`
|
TransactionID string `json:"transaction_id,omitempty"`
|
||||||
VisitorAddr string `json:"visitor_addr,omitempty"`
|
Sid string `json:"sid,omitempty"`
|
||||||
ClientAddr string `json:"client_addr,omitempty"`
|
Protocol string `json:"protocol,omitempty"`
|
||||||
Error string `json:"error,omitempty"`
|
CandidateAddrs []string `json:"candidate_addrs,omitempty"`
|
||||||
|
AssistedAddrs []string `json:"assisted_addrs,omitempty"`
|
||||||
|
DetectBehavior NatHoleDetectBehavior `json:"detect_behavior,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NatHoleClientDetectOK struct{}
|
|
||||||
|
|
||||||
type NatHoleSid struct {
|
type NatHoleSid struct {
|
||||||
Sid string `json:"sid,omitempty"`
|
TransactionID string `json:"transaction_id,omitempty"`
|
||||||
|
Sid string `json:"sid,omitempty"`
|
||||||
|
Response bool `json:"response,omitempty"`
|
||||||
|
Nonce string `json:"nonce,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NatHoleBinding struct {
|
type NatHoleReport struct {
|
||||||
TransactionID string `json:"transaction_id,omitempty"`
|
Sid string `json:"sid,omitempty"`
|
||||||
}
|
Success bool `json:"success,omitempty"`
|
||||||
|
|
||||||
type NatHoleBindingResp struct {
|
|
||||||
TransactionID string `json:"transaction_id,omitempty"`
|
|
||||||
Address string `json:"address,omitempty"`
|
|
||||||
Error string `json:"error,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
328
pkg/nathole/analysis.go
Normal file
328
pkg/nathole/analysis.go
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package nathole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// mode 0, both EasyNAT, PublicNetwork is always receiver
|
||||||
|
// sender | receiver, ttl 7
|
||||||
|
// receiver, ttl 7 | sender
|
||||||
|
// sender | receiver, ttl 4
|
||||||
|
// receiver, ttl 4 | sender
|
||||||
|
// sender | receiver
|
||||||
|
// receiver | sender
|
||||||
|
// sender, sendDelayMs 5000 | receiver
|
||||||
|
// sender, sendDelayMs 10000 | receiver
|
||||||
|
// receiver | sender, sendDelayMs 5000
|
||||||
|
// receiver | sender, sendDelayMs 10000
|
||||||
|
mode0Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 7}, RecommandBehavior{Role: DetectRoleSender}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 4}, RecommandBehavior{Role: DetectRoleSender}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver}, RecommandBehavior{Role: DetectRoleSender}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 5000}, RecommandBehavior{Role: DetectRoleReceiver}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 10000}, RecommandBehavior{Role: DetectRoleReceiver}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver}, RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 5000}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver}, RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 10000}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode 1, HardNAT is sender, EasyNAT is receiver, port changes is regular
|
||||||
|
// sender | receiver, ttl 7, portsRangeNumber max 10
|
||||||
|
// sender, sendDelayMs 2000 | receiver, ttl 7, portsRangeNumber max 10
|
||||||
|
// sender | receiver, ttl 4, portsRangeNumber max 10
|
||||||
|
// sender, sendDelayMs 2000 | receiver, ttl 4, portsRangeNumber max 10
|
||||||
|
// sender | receiver, portsRangeNumber max 10
|
||||||
|
// sender, sendDelayMs 2000 | receiver, portsRangeNumber max 10
|
||||||
|
mode1Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender}, RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, SendDelayMs: 2000}, RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode 2, HardNAT is receiver, EasyNAT is sender
|
||||||
|
// sender, portsRandomNumber 1000, sendDelayMs 2000 | receiver, listen 256 ports, ttl 7
|
||||||
|
// sender, portsRandomNumber 1000, sendDelayMs 2000 | receiver, listen 256 ports, ttl 4
|
||||||
|
// sender, portsRandomNumber 1000, sendDelayMs 2000 | receiver, listen 256 ports
|
||||||
|
mode2Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 7},
|
||||||
|
),
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 4},
|
||||||
|
),
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode 3, For HardNAT & HardNAT, both changes in the ports are regular
|
||||||
|
// sender, portsRangeNumber 10 | receiver, ttl 7, portsRangeNumber 10
|
||||||
|
// sender, portsRangeNumber 10 | receiver, ttl 4, portsRangeNumber 10
|
||||||
|
// sender, portsRangeNumber 10 | receiver, portsRangeNumber 10
|
||||||
|
// receiver, ttl 7, portsRangeNumber 10 | sender, portsRangeNumber 10
|
||||||
|
// receiver, ttl 4, portsRangeNumber 10 | sender, portsRangeNumber 10
|
||||||
|
// receiver, portsRangeNumber 10 | sender, portsRangeNumber 10
|
||||||
|
mode3Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 7, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver, TTL: 4, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}),
|
||||||
|
lo.T2(RecommandBehavior{Role: DetectRoleReceiver, PortsRangeNumber: 10}, RecommandBehavior{Role: DetectRoleSender, PortsRangeNumber: 10}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode 4, Regular ports changes are usually the sender.
|
||||||
|
// sender, portsRandomNumber 1000, sendDelayMs: 2000 | receiver, listen 256 ports, ttl 7, portsRangeNumber 10
|
||||||
|
// sender, portsRandomNumber 1000, sendDelayMs: 2000 | receiver, listen 256 ports, ttl 4, portsRangeNumber 10
|
||||||
|
// sender, portsRandomNumber 1000, SendDelayMs: 2000 | receiver, listen 256 ports, portsRangeNumber 10
|
||||||
|
mode4Behaviors = []lo.Tuple2[RecommandBehavior, RecommandBehavior]{
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 7, PortsRangeNumber: 10},
|
||||||
|
),
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, TTL: 4, PortsRangeNumber: 10},
|
||||||
|
),
|
||||||
|
lo.T2(
|
||||||
|
RecommandBehavior{Role: DetectRoleSender, PortsRandomNumber: 1000, SendDelayMs: 2000},
|
||||||
|
RecommandBehavior{Role: DetectRoleReceiver, ListenRandomPorts: 256, PortsRangeNumber: 10},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func getBehaviorByMode(mode int) []lo.Tuple2[RecommandBehavior, RecommandBehavior] {
|
||||||
|
switch mode {
|
||||||
|
case 0:
|
||||||
|
return mode0Behaviors
|
||||||
|
case 1:
|
||||||
|
return mode1Behaviors
|
||||||
|
case 2:
|
||||||
|
return mode2Behaviors
|
||||||
|
case 3:
|
||||||
|
return mode3Behaviors
|
||||||
|
case 4:
|
||||||
|
return mode4Behaviors
|
||||||
|
}
|
||||||
|
// default
|
||||||
|
return mode0Behaviors
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBehaviorByModeAndIndex(mode int, index int) (RecommandBehavior, RecommandBehavior) {
|
||||||
|
behaviors := getBehaviorByMode(mode)
|
||||||
|
if index >= len(behaviors) {
|
||||||
|
return RecommandBehavior{}, RecommandBehavior{}
|
||||||
|
}
|
||||||
|
return behaviors[index].A, behaviors[index].B
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBehaviorScoresByMode(mode int, defaultScore int) []*BehaviorScore {
|
||||||
|
return getBehaviorScoresByMode2(mode, defaultScore, defaultScore)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBehaviorScoresByMode2(mode int, senderScore, receiverScore int) []*BehaviorScore {
|
||||||
|
behaviors := getBehaviorByMode(mode)
|
||||||
|
scores := make([]*BehaviorScore, 0, len(behaviors))
|
||||||
|
for i := 0; i < len(behaviors); i++ {
|
||||||
|
score := receiverScore
|
||||||
|
if behaviors[i].A.Role == DetectRoleSender {
|
||||||
|
score = senderScore
|
||||||
|
}
|
||||||
|
scores = append(scores, &BehaviorScore{Mode: mode, Index: i, Score: score})
|
||||||
|
}
|
||||||
|
return scores
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecommandBehavior struct {
|
||||||
|
Role string
|
||||||
|
TTL int
|
||||||
|
SendDelayMs int
|
||||||
|
PortsRangeNumber int
|
||||||
|
PortsRandomNumber int
|
||||||
|
ListenRandomPorts int
|
||||||
|
}
|
||||||
|
|
||||||
|
type MakeHoleRecords struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
scores []*BehaviorScore
|
||||||
|
LastUpdateTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMakeHoleRecords(c, v *NatFeature) *MakeHoleRecords {
|
||||||
|
scores := []*BehaviorScore{}
|
||||||
|
easyCount, hardCount, portsChangedRegularCount := ClassifyFeatureCount([]*NatFeature{c, v})
|
||||||
|
appendMode0 := func() {
|
||||||
|
switch {
|
||||||
|
case c.PublicNetwork:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode2(DetectMode0, 0, 1)...)
|
||||||
|
case v.PublicNetwork:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode2(DetectMode0, 1, 0)...)
|
||||||
|
default:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode0, 0)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case easyCount == 2:
|
||||||
|
appendMode0()
|
||||||
|
case hardCount == 1 && portsChangedRegularCount == 1:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode1, 0)...)
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode2, 0)...)
|
||||||
|
appendMode0()
|
||||||
|
case hardCount == 1 && portsChangedRegularCount == 0:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode2, 0)...)
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode1, 0)...)
|
||||||
|
appendMode0()
|
||||||
|
case hardCount == 2 && portsChangedRegularCount == 2:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode3, 0)...)
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode4, 0)...)
|
||||||
|
case hardCount == 2 && portsChangedRegularCount == 1:
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode4, 0)...)
|
||||||
|
default:
|
||||||
|
// hard to make hole, just trying it out.
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode0, 1)...)
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode1, 1)...)
|
||||||
|
scores = append(scores, getBehaviorScoresByMode(DetectMode3, 1)...)
|
||||||
|
}
|
||||||
|
return &MakeHoleRecords{scores: scores, LastUpdateTime: time.Now()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mhr *MakeHoleRecords) ReportSuccess(mode int, index int) {
|
||||||
|
mhr.mu.Lock()
|
||||||
|
defer mhr.mu.Unlock()
|
||||||
|
mhr.LastUpdateTime = time.Now()
|
||||||
|
for i := range mhr.scores {
|
||||||
|
score := mhr.scores[i]
|
||||||
|
if score.Mode != mode || score.Index != index {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
score.Score += 2
|
||||||
|
score.Score = lo.Min([]int{score.Score, 10})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mhr *MakeHoleRecords) Recommand() (mode, index int) {
|
||||||
|
mhr.mu.Lock()
|
||||||
|
defer mhr.mu.Unlock()
|
||||||
|
|
||||||
|
maxScore := lo.MaxBy(mhr.scores, func(item, max *BehaviorScore) bool {
|
||||||
|
return item.Score > max.Score
|
||||||
|
})
|
||||||
|
if maxScore == nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
maxScore.Score--
|
||||||
|
mhr.LastUpdateTime = time.Now()
|
||||||
|
return maxScore.Mode, maxScore.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
type BehaviorScore struct {
|
||||||
|
Mode int
|
||||||
|
Index int
|
||||||
|
// between -10 and 10
|
||||||
|
Score int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Analyzer struct {
|
||||||
|
// key is client ip + visitor ip
|
||||||
|
records map[string]*MakeHoleRecords
|
||||||
|
dataReserveDuration time.Duration
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnalyzer(dataReserveDuration time.Duration) *Analyzer {
|
||||||
|
return &Analyzer{
|
||||||
|
records: make(map[string]*MakeHoleRecords),
|
||||||
|
dataReserveDuration: dataReserveDuration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) GetRecommandBehaviors(key string, c, v *NatFeature) (mode, index int, _ RecommandBehavior, _ RecommandBehavior) {
|
||||||
|
a.mu.Lock()
|
||||||
|
records, ok := a.records[key]
|
||||||
|
if !ok {
|
||||||
|
records = NewMakeHoleRecords(c, v)
|
||||||
|
a.records[key] = records
|
||||||
|
}
|
||||||
|
a.mu.Unlock()
|
||||||
|
|
||||||
|
mode, index = records.Recommand()
|
||||||
|
cBehavior, vBehavior := getBehaviorByModeAndIndex(mode, index)
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case DetectMode1:
|
||||||
|
// HardNAT is always the sender
|
||||||
|
if c.NatType == EasyNAT {
|
||||||
|
cBehavior, vBehavior = vBehavior, cBehavior
|
||||||
|
}
|
||||||
|
case DetectMode2:
|
||||||
|
// HardNAT is always the receiver
|
||||||
|
if c.NatType == HardNAT {
|
||||||
|
cBehavior, vBehavior = vBehavior, cBehavior
|
||||||
|
}
|
||||||
|
case DetectMode4:
|
||||||
|
// Regular ports changes is always the sender
|
||||||
|
if !c.RegularPortsChange {
|
||||||
|
cBehavior, vBehavior = vBehavior, cBehavior
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mode, index, cBehavior, vBehavior
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) ReportSuccess(key string, mode, index int) {
|
||||||
|
a.mu.Lock()
|
||||||
|
records, ok := a.records[key]
|
||||||
|
a.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
records.ReportSuccess(mode, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Analyzer) Clean() (int, int) {
|
||||||
|
now := time.Now()
|
||||||
|
total := 0
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
// cleanup 10w records may take 5ms
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
total = len(a.records)
|
||||||
|
// clean up records that have not been used for a period of time.
|
||||||
|
for key, records := range a.records {
|
||||||
|
if now.Sub(records.LastUpdateTime) > a.dataReserveDuration {
|
||||||
|
delete(a.records, key)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count, total
|
||||||
|
}
|
@ -17,6 +17,9 @@ package nathole
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -29,46 +32,96 @@ const (
|
|||||||
BehaviorBothChanged = "BehaviorBothChanged"
|
BehaviorBothChanged = "BehaviorBothChanged"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClassifyNATType classify NAT type by given addresses.
|
type NatFeature struct {
|
||||||
func ClassifyNATType(addresses []string) (string, string, error) {
|
NatType string
|
||||||
|
Behavior string
|
||||||
|
PortsDifference int
|
||||||
|
RegularPortsChange bool
|
||||||
|
PublicNetwork bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClassifyNATFeature(addresses []string, localIPs []string) (*NatFeature, error) {
|
||||||
if len(addresses) <= 1 {
|
if len(addresses) <= 1 {
|
||||||
return "", "", fmt.Errorf("not enough addresses")
|
return nil, fmt.Errorf("not enough addresses")
|
||||||
}
|
}
|
||||||
|
natFeature := &NatFeature{}
|
||||||
ipChanged := false
|
ipChanged := false
|
||||||
portChanged := false
|
portChanged := false
|
||||||
|
|
||||||
var baseIP, basePort string
|
var baseIP, basePort string
|
||||||
|
var portMax, portMin int
|
||||||
for _, addr := range addresses {
|
for _, addr := range addresses {
|
||||||
ip, port, err := net.SplitHostPort(addr)
|
ip, port, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
portNum, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if lo.Contains(localIPs, ip) {
|
||||||
|
natFeature.PublicNetwork = true
|
||||||
|
}
|
||||||
|
|
||||||
if baseIP == "" {
|
if baseIP == "" {
|
||||||
baseIP = ip
|
baseIP = ip
|
||||||
basePort = port
|
basePort = port
|
||||||
|
portMax = portNum
|
||||||
|
portMin = portNum
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if portNum > portMax {
|
||||||
|
portMax = portNum
|
||||||
|
}
|
||||||
|
if portNum < portMin {
|
||||||
|
portMin = portNum
|
||||||
|
}
|
||||||
if baseIP != ip {
|
if baseIP != ip {
|
||||||
ipChanged = true
|
ipChanged = true
|
||||||
}
|
}
|
||||||
if basePort != port {
|
if basePort != port {
|
||||||
portChanged = true
|
portChanged = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ipChanged && portChanged {
|
natFeature.PortsDifference = portMax - portMin
|
||||||
break
|
if natFeature.PortsDifference <= 10 && natFeature.PortsDifference >= 1 {
|
||||||
}
|
natFeature.RegularPortsChange = true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case ipChanged && portChanged:
|
case ipChanged && portChanged:
|
||||||
return HardNAT, BehaviorBothChanged, nil
|
natFeature.NatType = HardNAT
|
||||||
|
natFeature.Behavior = BehaviorBothChanged
|
||||||
case ipChanged:
|
case ipChanged:
|
||||||
return HardNAT, BehaviorIPChanged, nil
|
natFeature.NatType = HardNAT
|
||||||
|
natFeature.Behavior = BehaviorIPChanged
|
||||||
case portChanged:
|
case portChanged:
|
||||||
return HardNAT, BehaviorPortChanged, nil
|
natFeature.NatType = HardNAT
|
||||||
|
natFeature.Behavior = BehaviorPortChanged
|
||||||
default:
|
default:
|
||||||
return EasyNAT, BehaviorNoChange, nil
|
natFeature.NatType = EasyNAT
|
||||||
|
natFeature.Behavior = BehaviorNoChange
|
||||||
}
|
}
|
||||||
|
return natFeature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClassifyFeatureCount(features []*NatFeature) (int, int, int) {
|
||||||
|
easyCount := 0
|
||||||
|
hardCount := 0
|
||||||
|
// for HardNAT
|
||||||
|
portsChangedRegularCount := 0
|
||||||
|
for _, feature := range features {
|
||||||
|
if feature.NatType == EasyNAT {
|
||||||
|
easyCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hardCount++
|
||||||
|
if feature.RegularPortsChange {
|
||||||
|
portsChangedRegularCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return easyCount, hardCount, portsChangedRegularCount
|
||||||
}
|
}
|
||||||
|
382
pkg/nathole/controller.go
Normal file
382
pkg/nathole/controller.go
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package nathole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NatHoleTimeout seconds.
|
||||||
|
var NatHoleTimeout int64 = 10
|
||||||
|
|
||||||
|
func NewTransactionID() string {
|
||||||
|
id, _ := util.RandID()
|
||||||
|
return fmt.Sprintf("%d%s", time.Now().Unix(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientCfg struct {
|
||||||
|
name string
|
||||||
|
sk string
|
||||||
|
sidCh chan string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
sid string
|
||||||
|
analysisKey string
|
||||||
|
recommandMode int
|
||||||
|
recommandIndex int
|
||||||
|
|
||||||
|
visitorMsg *msg.NatHoleVisitor
|
||||||
|
visitorTransporter transport.MessageTransporter
|
||||||
|
vResp *msg.NatHoleResp
|
||||||
|
vNatFeature *NatFeature
|
||||||
|
vBehavior RecommandBehavior
|
||||||
|
|
||||||
|
clientMsg *msg.NatHoleClient
|
||||||
|
clientTransporter transport.MessageTransporter
|
||||||
|
cResp *msg.NatHoleResp
|
||||||
|
cNatFeature *NatFeature
|
||||||
|
cBehavior RecommandBehavior
|
||||||
|
|
||||||
|
notifyCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) genAnalysisKey() {
|
||||||
|
hash := md5.New()
|
||||||
|
vIPs := lo.Uniq(parseIPs(s.visitorMsg.MappedAddrs))
|
||||||
|
if len(vIPs) > 0 {
|
||||||
|
hash.Write([]byte(vIPs[0]))
|
||||||
|
}
|
||||||
|
hash.Write([]byte(s.vNatFeature.NatType))
|
||||||
|
hash.Write([]byte(s.vNatFeature.Behavior))
|
||||||
|
hash.Write([]byte(strconv.FormatBool(s.vNatFeature.RegularPortsChange)))
|
||||||
|
|
||||||
|
cIPs := lo.Uniq(parseIPs(s.clientMsg.MappedAddrs))
|
||||||
|
if len(cIPs) > 0 {
|
||||||
|
hash.Write([]byte(cIPs[0]))
|
||||||
|
}
|
||||||
|
hash.Write([]byte(s.cNatFeature.NatType))
|
||||||
|
hash.Write([]byte(s.cNatFeature.Behavior))
|
||||||
|
hash.Write([]byte(strconv.FormatBool(s.cNatFeature.RegularPortsChange)))
|
||||||
|
s.analysisKey = hex.EncodeToString(hash.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
clientCfgs map[string]*ClientCfg
|
||||||
|
sessions map[string]*Session
|
||||||
|
analyzer *Analyzer
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(analysisDataReserveDuration time.Duration) (*Controller, error) {
|
||||||
|
return &Controller{
|
||||||
|
clientCfgs: make(map[string]*ClientCfg),
|
||||||
|
sessions: make(map[string]*Session),
|
||||||
|
analyzer: NewAnalyzer(analysisDataReserveDuration),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) CleanWorker(ctx context.Context) {
|
||||||
|
ticker := time.NewTicker(time.Hour)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
start := time.Now()
|
||||||
|
count, total := c.analyzer.Clean()
|
||||||
|
log.Trace("clean %d/%d nathole analysis data, cost %v", count, total, time.Since(start))
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ListenClient(name string, sk string) chan string {
|
||||||
|
cfg := &ClientCfg{
|
||||||
|
name: name,
|
||||||
|
sk: sk,
|
||||||
|
sidCh: make(chan string),
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.clientCfgs[name] = cfg
|
||||||
|
return cfg.sidCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) CloseClient(name string) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
delete(c.clientCfgs, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) GenSid() string {
|
||||||
|
t := time.Now().Unix()
|
||||||
|
id, _ := util.RandID()
|
||||||
|
return fmt.Sprintf("%d%s", t, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) HandleVisitor(m *msg.NatHoleVisitor, transporter transport.MessageTransporter) {
|
||||||
|
if m.PreCheck {
|
||||||
|
_, ok := c.clientCfgs[m.ProxyName]
|
||||||
|
if !ok {
|
||||||
|
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)))
|
||||||
|
} else {
|
||||||
|
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, ""))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := c.GenSid()
|
||||||
|
session := &Session{
|
||||||
|
sid: sid,
|
||||||
|
visitorMsg: m,
|
||||||
|
visitorTransporter: transporter,
|
||||||
|
notifyCh: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
clientCfg *ClientCfg
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
err := func() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
clientCfg, ok = c.clientCfgs[m.ProxyName]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("xtcp server for [%s] doesn't exist", m.ProxyName)
|
||||||
|
}
|
||||||
|
if m.SignKey != util.GetAuthKey(clientCfg.sk, m.Timestamp) {
|
||||||
|
return fmt.Errorf("xtcp connection of [%s] auth failed", m.ProxyName)
|
||||||
|
}
|
||||||
|
c.sessions[sid] = session
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("handle visitorMsg error: %v", err)
|
||||||
|
_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("handle visitor message, sid [%s]", sid)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
delete(c.sessions, sid)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := errors.PanicToError(func() {
|
||||||
|
clientCfg.sidCh <- sid
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for NatHoleClient message
|
||||||
|
select {
|
||||||
|
case <-session.notifyCh:
|
||||||
|
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
|
||||||
|
log.Debug("wait for NatHoleClient message timeout, sid [%s]", sid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make hole-punching decisions based on the NAT information of the client and visitor.
|
||||||
|
vResp, cResp, err := c.analysis(session)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("sid [%s] analysis error: %v", err)
|
||||||
|
vResp = c.GenNatHoleResponse(session.visitorMsg.TransactionID, nil, err.Error())
|
||||||
|
cResp = c.GenNatHoleResponse(session.clientMsg.TransactionID, nil, err.Error())
|
||||||
|
}
|
||||||
|
session.cResp = cResp
|
||||||
|
session.vResp = vResp
|
||||||
|
|
||||||
|
// send response to visitor and client
|
||||||
|
var g errgroup.Group
|
||||||
|
g.Go(func() error {
|
||||||
|
// if it's sender, wait for a while to make sure the client has send the detect messages
|
||||||
|
if vResp.DetectBehavior.Role == "sender" {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
_ = session.visitorTransporter.Send(vResp)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
g.Go(func() error {
|
||||||
|
// if it's sender, wait for a while to make sure the client has send the detect messages
|
||||||
|
if cResp.DetectBehavior.Role == "sender" {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
_ = session.clientTransporter.Send(cResp)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
_ = g.Wait()
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(cResp.DetectBehavior.ReadTimeoutMs+30000) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) HandleClient(m *msg.NatHoleClient, transporter transport.MessageTransporter) {
|
||||||
|
c.mu.RLock()
|
||||||
|
session, ok := c.sessions[m.Sid]
|
||||||
|
c.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("handle client message, sid [%s]", session.sid)
|
||||||
|
session.clientMsg = m
|
||||||
|
session.clientTransporter = transporter
|
||||||
|
select {
|
||||||
|
case session.notifyCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) HandleReport(m *msg.NatHoleReport) {
|
||||||
|
c.mu.RLock()
|
||||||
|
session, ok := c.sessions[m.Sid]
|
||||||
|
c.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
log.Trace("sid [%s] report make hole success: %v, but session not found", m.Sid, m.Success)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m.Success {
|
||||||
|
c.analyzer.ReportSuccess(session.analysisKey, session.recommandMode, session.recommandIndex)
|
||||||
|
}
|
||||||
|
log.Info("sid [%s] report make hole success: %v, mode %v, index %v",
|
||||||
|
m.Sid, m.Success, session.recommandMode, session.recommandIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) GenNatHoleResponse(transactionID string, session *Session, errInfo string) *msg.NatHoleResp {
|
||||||
|
var sid string
|
||||||
|
if session != nil {
|
||||||
|
sid = session.sid
|
||||||
|
}
|
||||||
|
return &msg.NatHoleResp{
|
||||||
|
TransactionID: transactionID,
|
||||||
|
Sid: sid,
|
||||||
|
Error: errInfo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// analysis analyzes the NAT type and behavior of the visitor and client, then makes hole-punching decisions.
|
||||||
|
// return the response to the visitor and client.
|
||||||
|
func (c *Controller) analysis(session *Session) (*msg.NatHoleResp, *msg.NatHoleResp, error) {
|
||||||
|
cm := session.clientMsg
|
||||||
|
vm := session.visitorMsg
|
||||||
|
|
||||||
|
cNatFeature, err := ClassifyNATFeature(cm.MappedAddrs, parseIPs(cm.AssistedAddrs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("classify client nat feature error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vNatFeature, err := ClassifyNATFeature(vm.MappedAddrs, parseIPs(vm.AssistedAddrs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("classify visitor nat feature error: %v", err)
|
||||||
|
}
|
||||||
|
session.cNatFeature = cNatFeature
|
||||||
|
session.vNatFeature = vNatFeature
|
||||||
|
session.genAnalysisKey()
|
||||||
|
|
||||||
|
mode, index, cBehavior, vBehavior := c.analyzer.GetRecommandBehaviors(session.analysisKey, cNatFeature, vNatFeature)
|
||||||
|
session.recommandMode = mode
|
||||||
|
session.recommandIndex = index
|
||||||
|
session.cBehavior = cBehavior
|
||||||
|
session.vBehavior = vBehavior
|
||||||
|
|
||||||
|
timeoutMs := lo.Max([]int{cBehavior.SendDelayMs, vBehavior.SendDelayMs}) + 5000
|
||||||
|
if cBehavior.ListenRandomPorts > 0 || vBehavior.ListenRandomPorts > 0 {
|
||||||
|
timeoutMs += 30000
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol := vm.Protocol
|
||||||
|
vResp := &msg.NatHoleResp{
|
||||||
|
TransactionID: vm.TransactionID,
|
||||||
|
Sid: session.sid,
|
||||||
|
Protocol: protocol,
|
||||||
|
CandidateAddrs: lo.Uniq(cm.MappedAddrs),
|
||||||
|
AssistedAddrs: lo.Uniq(cm.AssistedAddrs),
|
||||||
|
DetectBehavior: msg.NatHoleDetectBehavior{
|
||||||
|
Mode: mode,
|
||||||
|
Role: vBehavior.Role,
|
||||||
|
TTL: vBehavior.TTL,
|
||||||
|
SendDelayMs: vBehavior.SendDelayMs,
|
||||||
|
ReadTimeoutMs: timeoutMs - vBehavior.SendDelayMs,
|
||||||
|
SendRandomPorts: vBehavior.PortsRandomNumber,
|
||||||
|
ListenRandomPorts: vBehavior.ListenRandomPorts,
|
||||||
|
CandidatePorts: getRangePorts(cm.MappedAddrs, cNatFeature.PortsDifference, vBehavior.PortsRangeNumber),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cResp := &msg.NatHoleResp{
|
||||||
|
TransactionID: cm.TransactionID,
|
||||||
|
Sid: session.sid,
|
||||||
|
Protocol: protocol,
|
||||||
|
CandidateAddrs: lo.Uniq(vm.MappedAddrs),
|
||||||
|
AssistedAddrs: lo.Uniq(vm.AssistedAddrs),
|
||||||
|
DetectBehavior: msg.NatHoleDetectBehavior{
|
||||||
|
Mode: mode,
|
||||||
|
Role: cBehavior.Role,
|
||||||
|
TTL: cBehavior.TTL,
|
||||||
|
SendDelayMs: cBehavior.SendDelayMs,
|
||||||
|
ReadTimeoutMs: timeoutMs - cBehavior.SendDelayMs,
|
||||||
|
SendRandomPorts: cBehavior.PortsRandomNumber,
|
||||||
|
ListenRandomPorts: cBehavior.ListenRandomPorts,
|
||||||
|
CandidatePorts: getRangePorts(vm.MappedAddrs, vNatFeature.PortsDifference, cBehavior.PortsRangeNumber),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("sid [%s] visitor nat: %+v, candidateAddrs: %v; client nat: %+v, candidateAddrs: %v, protocol: %s",
|
||||||
|
session.sid, *vNatFeature, vm.MappedAddrs, *cNatFeature, cm.MappedAddrs, protocol)
|
||||||
|
log.Debug("sid [%s] visitor detect behavior: %+v", session.sid, vResp.DetectBehavior)
|
||||||
|
log.Debug("sid [%s] client detect behavior: %+v", session.sid, cResp.DetectBehavior)
|
||||||
|
return vResp, cResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRangePorts(addrs []string, difference, maxNumber int) []msg.PortsRange {
|
||||||
|
if maxNumber <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := lo.Last(addrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var ports []msg.PortsRange
|
||||||
|
_, portStr, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ports = append(ports, msg.PortsRange{
|
||||||
|
From: lo.Max([]int{port - difference - 5, port - maxNumber, 1}),
|
||||||
|
To: lo.Min([]int{port + difference + 5, port + maxNumber, 65535}),
|
||||||
|
})
|
||||||
|
return ports
|
||||||
|
}
|
@ -20,8 +20,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/stun"
|
"github.com/pion/stun"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var responseTimeout = 3 * time.Second
|
var responseTimeout = 3 * time.Second
|
||||||
@ -31,35 +29,27 @@ type Message struct {
|
|||||||
Addr string
|
Addr string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Discover(serverAddress string, stunServers []string, key []byte) ([]string, error) {
|
// If the localAddr is empty, it will listen on a random port.
|
||||||
|
func Discover(stunServers []string, localAddr string) ([]string, net.Addr, error) {
|
||||||
// create a discoverConn and get response from messageChan
|
// create a discoverConn and get response from messageChan
|
||||||
discoverConn, err := listen()
|
discoverConn, err := listen(localAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
defer discoverConn.Close()
|
defer discoverConn.Close()
|
||||||
|
|
||||||
go discoverConn.readLoop()
|
go discoverConn.readLoop()
|
||||||
|
|
||||||
addresses := make([]string, 0, len(stunServers)+1)
|
addresses := make([]string, 0, len(stunServers))
|
||||||
if serverAddress != "" {
|
|
||||||
// get external address from frp server
|
|
||||||
externalAddr, err := discoverConn.discoverFromServer(serverAddress, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
addresses = append(addresses, externalAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range stunServers {
|
for _, addr := range stunServers {
|
||||||
// get external address from stun server
|
// get external address from stun server
|
||||||
externalAddrs, err := discoverConn.discoverFromStunServer(addr)
|
externalAddrs, err := discoverConn.discoverFromStunServer(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
addresses = append(addresses, externalAddrs...)
|
addresses = append(addresses, externalAddrs...)
|
||||||
}
|
}
|
||||||
return addresses, nil
|
return addresses, discoverConn.localAddr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type stunResponse struct {
|
type stunResponse struct {
|
||||||
@ -74,8 +64,16 @@ type discoverConn struct {
|
|||||||
messageChan chan *Message
|
messageChan chan *Message
|
||||||
}
|
}
|
||||||
|
|
||||||
func listen() (*discoverConn, error) {
|
func listen(localAddr string) (*discoverConn, error) {
|
||||||
conn, err := net.ListenUDP("udp4", nil)
|
var local *net.UDPAddr
|
||||||
|
if localAddr != "" {
|
||||||
|
addr, err := net.ResolveUDPAddr("udp4", localAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
local = addr
|
||||||
|
}
|
||||||
|
conn, err := net.ListenUDP("udp4", local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -159,43 +157,6 @@ func (c *discoverConn) doSTUNRequest(addr string) (*stunResponse, error) {
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *discoverConn) discoverFromServer(serverAddress string, key []byte) (string, error) {
|
|
||||||
addr, err := net.ResolveUDPAddr("udp4", serverAddress)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
m := &msg.NatHoleBinding{
|
|
||||||
TransactionID: NewTransactionID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := EncodeMessage(m, key)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := c.conn.WriteTo(buf, addr); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var respMsg msg.NatHoleBindingResp
|
|
||||||
select {
|
|
||||||
case rawMsg := <-c.messageChan:
|
|
||||||
if err := DecodeMessageInto(rawMsg.Body, key, &respMsg); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
case <-time.After(responseTimeout):
|
|
||||||
return "", fmt.Errorf("wait response from frp server timeout")
|
|
||||||
}
|
|
||||||
|
|
||||||
if respMsg.TransactionID == "" {
|
|
||||||
return "", fmt.Errorf("error format: no transaction id found")
|
|
||||||
}
|
|
||||||
if respMsg.Error != "" {
|
|
||||||
return "", fmt.Errorf("get externalAddr from frp server error: %s", respMsg.Error)
|
|
||||||
}
|
|
||||||
return respMsg.Address, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *discoverConn) discoverFromStunServer(addr string) ([]string, error) {
|
func (c *discoverConn) discoverFromStunServer(addr string) ([]string, error) {
|
||||||
resp, err := c.doSTUNRequest(addr)
|
resp, err := c.doSTUNRequest(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -15,249 +15,426 @@
|
|||||||
package nathole
|
package nathole
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/golib/crypto"
|
|
||||||
"github.com/fatedier/golib/errors"
|
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NatHoleTimeout seconds.
|
var (
|
||||||
var NatHoleTimeout int64 = 10
|
// mode 0: simple detect mode, usually for both EasyNAT or HardNAT & EasyNAT(Public Network)
|
||||||
|
// a. receiver sends detect message with low TTL
|
||||||
|
// b. sender sends normal detect message to receiver
|
||||||
|
// c. receiver receives detect message and sends back a message to sender
|
||||||
|
//
|
||||||
|
// mode 1: For HardNAT & EasyNAT, send detect messages to multiple guessed ports.
|
||||||
|
// Usually applicable to scenarios where port changes are regular.
|
||||||
|
// Most of the steps are the same as mode 0, but EasyNAT is fixed as the receiver and will send detect messages
|
||||||
|
// with low TTL to multiple guessed ports of the sender.
|
||||||
|
//
|
||||||
|
// mode 2: For HardNAT & EasyNAT, ports changes are not regular.
|
||||||
|
// a. HardNAT machine will listen on multiple ports and send detect messages with low TTL to EasyNAT machine
|
||||||
|
// b. EasyNAT machine will send detect messages to random ports of HardNAT machine.
|
||||||
|
//
|
||||||
|
// mode 3: For HardNAT & HardNAT, both changes in the ports are regular.
|
||||||
|
// Most of the steps are the same as mode 1, but the sender also needs to send detect messages to multiple guessed
|
||||||
|
// ports of the receiver.
|
||||||
|
//
|
||||||
|
// mode 4: For HardNAT & HardNAT, one of the changes in the ports is regular.
|
||||||
|
// Regular port changes are usually on the sender side.
|
||||||
|
// a. Receiver listens on multiple ports and sends detect messages with low TTL to the sender's guessed range ports.
|
||||||
|
// b. Sender sends detect messages to random ports of the receiver.
|
||||||
|
SupportedModes = []int{DetectMode0, DetectMode1, DetectMode2, DetectMode3, DetectMode4}
|
||||||
|
SupportedRoles = []string{DetectRoleSender, DetectRoleReceiver}
|
||||||
|
|
||||||
func NewTransactionID() string {
|
DetectMode0 = 0
|
||||||
id, _ := util.RandID()
|
DetectMode1 = 1
|
||||||
return fmt.Sprintf("%d%s", time.Now().Unix(), id)
|
DetectMode2 = 2
|
||||||
|
DetectMode3 = 3
|
||||||
|
DetectMode4 = 4
|
||||||
|
DetectRoleSender = "sender"
|
||||||
|
DetectRoleReceiver = "receiver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrepareResult struct {
|
||||||
|
Addrs []string
|
||||||
|
AssistedAddrs []string
|
||||||
|
ListenConn *net.UDPConn
|
||||||
|
NatType string
|
||||||
|
Behavior string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SidRequest struct {
|
// PreCheck is used to check if the proxy is ready for penetration.
|
||||||
Sid string
|
// Call this function before calling Prepare to avoid unnecessary preparation work.
|
||||||
NotifyCh chan struct{}
|
func PreCheck(
|
||||||
}
|
ctx context.Context, transporter transport.MessageTransporter,
|
||||||
|
proxyName string, timeout time.Duration,
|
||||||
|
) error {
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
type Controller struct {
|
var natHoleRespMsg *msg.NatHoleResp
|
||||||
listener *net.UDPConn
|
transactionID := NewTransactionID()
|
||||||
|
m, err := transporter.Do(timeoutCtx, &msg.NatHoleVisitor{
|
||||||
clientCfgs map[string]*ClientCfg
|
TransactionID: transactionID,
|
||||||
sessions map[string]*Session
|
ProxyName: proxyName,
|
||||||
|
PreCheck: true,
|
||||||
encryptionKey []byte
|
}, transactionID, msg.TypeNameNatHoleResp)
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewController(udpBindAddr string, encryptionKey []byte) (nc *Controller, err error) {
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", udpBindAddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return fmt.Errorf("get natHoleRespMsg error: %v", err)
|
||||||
}
|
}
|
||||||
lconn, err := net.ListenUDP("udp", addr)
|
mm, ok := m.(*msg.NatHoleResp)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("get natHoleRespMsg error: invalid message type")
|
||||||
|
}
|
||||||
|
natHoleRespMsg = mm
|
||||||
|
|
||||||
|
if natHoleRespMsg.Error != "" {
|
||||||
|
return fmt.Errorf("%s", natHoleRespMsg.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare is used to do some preparation work before penetration.
|
||||||
|
func Prepare(stunServers []string) (*PrepareResult, error) {
|
||||||
|
// discover for Nat type
|
||||||
|
addrs, localAddr, err := Discover(stunServers, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("discover error: %v", err)
|
||||||
}
|
}
|
||||||
nc = &Controller{
|
if len(addrs) < 2 {
|
||||||
listener: lconn,
|
return nil, fmt.Errorf("discover error: not enough addresses")
|
||||||
clientCfgs: make(map[string]*ClientCfg),
|
|
||||||
sessions: make(map[string]*Session),
|
|
||||||
encryptionKey: encryptionKey,
|
|
||||||
}
|
}
|
||||||
return nc, nil
|
|
||||||
|
localIPs, _ := ListLocalIPsForNatHole(10)
|
||||||
|
natFeature, err := ClassifyNATFeature(addrs, localIPs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("classify nat feature error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
laddr, err := net.ResolveUDPAddr("udp4", localAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolve local udp addr error: %v", err)
|
||||||
|
}
|
||||||
|
listenConn, err := net.ListenUDP("udp4", laddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listen local udp addr error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assistedAddrs := make([]string, 0, len(localIPs))
|
||||||
|
for _, ip := range localIPs {
|
||||||
|
assistedAddrs = append(assistedAddrs, net.JoinHostPort(ip, strconv.Itoa(laddr.Port)))
|
||||||
|
}
|
||||||
|
return &PrepareResult{
|
||||||
|
Addrs: addrs,
|
||||||
|
AssistedAddrs: assistedAddrs,
|
||||||
|
ListenConn: listenConn,
|
||||||
|
NatType: natFeature.NatType,
|
||||||
|
Behavior: natFeature.Behavior,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *Controller) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
|
// ExchangeInfo is used to exchange information between client and visitor.
|
||||||
clientCfg := &ClientCfg{
|
// 1. Send input message to server by msgTransporter.
|
||||||
Name: name,
|
// 2. Server will gather information from client and visitor and analyze it. Then send back a NatHoleResp message to them to tell them how to do next.
|
||||||
Sk: sk,
|
// 3. Receive NatHoleResp message from server.
|
||||||
SidCh: make(chan *SidRequest),
|
func ExchangeInfo(
|
||||||
|
ctx context.Context, transporter transport.MessageTransporter,
|
||||||
|
laneKey string, m msg.Message, timeout time.Duration,
|
||||||
|
) (*msg.NatHoleResp, error) {
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var natHoleRespMsg *msg.NatHoleResp
|
||||||
|
m, err := transporter.Do(timeoutCtx, m, laneKey, msg.TypeNameNatHoleResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get natHoleRespMsg error: %v", err)
|
||||||
}
|
}
|
||||||
nc.mu.Lock()
|
mm, ok := m.(*msg.NatHoleResp)
|
||||||
nc.clientCfgs[name] = clientCfg
|
if !ok {
|
||||||
nc.mu.Unlock()
|
return nil, fmt.Errorf("get natHoleRespMsg error: invalid message type")
|
||||||
return clientCfg.SidCh
|
}
|
||||||
|
natHoleRespMsg = mm
|
||||||
|
|
||||||
|
if natHoleRespMsg.Error != "" {
|
||||||
|
return nil, fmt.Errorf("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
|
||||||
|
}
|
||||||
|
if len(natHoleRespMsg.CandidateAddrs) == 0 {
|
||||||
|
return nil, fmt.Errorf("natHoleRespMsg get empty candidate addresses")
|
||||||
|
}
|
||||||
|
return natHoleRespMsg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *Controller) CloseClient(name string) {
|
// MakeHole is used to make a NAT hole between client and visitor.
|
||||||
nc.mu.Lock()
|
func MakeHole(ctx context.Context, listenConn *net.UDPConn, m *msg.NatHoleResp, key []byte) (*net.UDPConn, *net.UDPAddr, error) {
|
||||||
defer nc.mu.Unlock()
|
xl := xlog.FromContextSafe(ctx)
|
||||||
delete(nc.clientCfgs, name)
|
transactionID := NewTransactionID()
|
||||||
|
sendToRangePortsFunc := func(conn *net.UDPConn, addr string) error {
|
||||||
|
return sendSidMessage(ctx, conn, m.Sid, transactionID, addr, key, m.DetectBehavior.TTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
listenConns := []*net.UDPConn{listenConn}
|
||||||
|
var detectAddrs []string
|
||||||
|
if m.DetectBehavior.Role == DetectRoleSender {
|
||||||
|
// sender
|
||||||
|
if m.DetectBehavior.SendDelayMs > 0 {
|
||||||
|
time.Sleep(time.Duration(m.DetectBehavior.SendDelayMs) * time.Millisecond)
|
||||||
|
}
|
||||||
|
detectAddrs = m.AssistedAddrs
|
||||||
|
detectAddrs = append(detectAddrs, m.CandidateAddrs...)
|
||||||
|
} else {
|
||||||
|
// receiver
|
||||||
|
if len(m.DetectBehavior.CandidatePorts) == 0 {
|
||||||
|
detectAddrs = m.CandidateAddrs
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.DetectBehavior.ListenRandomPorts > 0 {
|
||||||
|
for i := 0; i < m.DetectBehavior.ListenRandomPorts; i++ {
|
||||||
|
tmpConn, err := net.ListenUDP("udp4", nil)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("listen random udp addr error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
listenConns = append(listenConns, tmpConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
detectAddrs = lo.Uniq(detectAddrs)
|
||||||
|
for _, detectAddr := range detectAddrs {
|
||||||
|
for _, conn := range listenConns {
|
||||||
|
if err := sendSidMessage(ctx, conn, m.Sid, transactionID, detectAddr, key, m.DetectBehavior.TTL); err != nil {
|
||||||
|
xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(m.DetectBehavior.CandidatePorts) > 0 {
|
||||||
|
for _, conn := range listenConns {
|
||||||
|
sendSidMessageToRangePorts(ctx, conn, m.CandidateAddrs, m.DetectBehavior.CandidatePorts, sendToRangePortsFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.DetectBehavior.SendRandomPorts > 0 {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
for i := range listenConns {
|
||||||
|
go sendSidMessageToRandomPorts(ctx, listenConns[i], m.CandidateAddrs, m.DetectBehavior.SendRandomPorts, sendToRangePortsFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := 5 * time.Second
|
||||||
|
if m.DetectBehavior.ReadTimeoutMs > 0 {
|
||||||
|
timeout = time.Duration(m.DetectBehavior.ReadTimeoutMs) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(listenConns) == 1 {
|
||||||
|
raddr, err := waitDetectMessage(ctx, listenConns[0], m.Sid, key, timeout, m.DetectBehavior.Role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("wait detect message error: %v", err)
|
||||||
|
}
|
||||||
|
return listenConns[0], raddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
lConn *net.UDPConn
|
||||||
|
raddr *net.UDPAddr
|
||||||
|
}
|
||||||
|
resultCh := make(chan result)
|
||||||
|
for _, conn := range listenConns {
|
||||||
|
go func(lConn *net.UDPConn) {
|
||||||
|
addr, err := waitDetectMessage(ctx, lConn, m.Sid, key, timeout, m.DetectBehavior.Role)
|
||||||
|
if err != nil {
|
||||||
|
lConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case resultCh <- result{lConn: lConn, raddr: addr}:
|
||||||
|
default:
|
||||||
|
lConn.Close()
|
||||||
|
}
|
||||||
|
}(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result := <-resultCh:
|
||||||
|
return result.lConn, result.raddr, nil
|
||||||
|
case <-time.After(timeout):
|
||||||
|
return nil, nil, fmt.Errorf("wait detect message timeout")
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, nil, fmt.Errorf("wait detect message canceled")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *Controller) Run() {
|
func waitDetectMessage(
|
||||||
|
ctx context.Context, conn *net.UDPConn, sid string, key []byte,
|
||||||
|
timeout time.Duration, role string,
|
||||||
|
) (*net.UDPAddr, error) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
for {
|
for {
|
||||||
buf := pool.GetBuf(1024)
|
buf := pool.GetBuf(1024)
|
||||||
n, raddr, err := nc.listener.ReadFromUDP(buf)
|
_ = conn.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
n, raddr, err := conn.ReadFromUDP(buf)
|
||||||
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("nat hole listener read from udp error: %v", err)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
plain, err := crypto.Decode(buf[:n], nc.encryptionKey)
|
xl.Debug("get udp message local %s, from %s", conn.LocalAddr(), raddr)
|
||||||
if err != nil {
|
var m msg.NatHoleSid
|
||||||
log.Warn("nathole listener decode from %s error: %v", raddr.String(), err)
|
if err := DecodeMessageInto(buf[:n], key, &m); err != nil {
|
||||||
continue
|
xl.Warn("decode sid message error: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
rawMsg, err := msg.ReadMsg(bytes.NewReader(plain))
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("read nat hole message error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch m := rawMsg.(type) {
|
|
||||||
case *msg.NatHoleBinding:
|
|
||||||
go nc.HandleBinding(m, raddr)
|
|
||||||
case *msg.NatHoleVisitor:
|
|
||||||
go nc.HandleVisitor(m, raddr)
|
|
||||||
case *msg.NatHoleClient:
|
|
||||||
go nc.HandleClient(m, raddr)
|
|
||||||
default:
|
|
||||||
log.Trace("unknown nat hole message type")
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pool.PutBuf(buf)
|
pool.PutBuf(buf)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nc *Controller) GenSid() string {
|
if m.Sid != sid {
|
||||||
t := time.Now().Unix()
|
xl.Warn("get sid message with wrong sid: %s, expect: %s", m.Sid, sid)
|
||||||
id, _ := util.RandID()
|
continue
|
||||||
return fmt.Sprintf("%d%s", t, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nc *Controller) HandleBinding(m *msg.NatHoleBinding, raddr *net.UDPAddr) {
|
|
||||||
log.Trace("handle binding message from %s", raddr.String())
|
|
||||||
resp := &msg.NatHoleBindingResp{
|
|
||||||
TransactionID: m.TransactionID,
|
|
||||||
Address: raddr.String(),
|
|
||||||
}
|
|
||||||
plain, err := msg.Pack(resp)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("pack nat hole binding response error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf, err := crypto.Encode(plain, nc.encryptionKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("encode nat hole binding response error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = nc.listener.WriteToUDP(buf, raddr)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("write nat hole binding response to %s error: %v", raddr.String(), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nc *Controller) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
|
|
||||||
sid := nc.GenSid()
|
|
||||||
session := &Session{
|
|
||||||
Sid: sid,
|
|
||||||
VisitorAddr: raddr,
|
|
||||||
NotifyCh: make(chan struct{}),
|
|
||||||
}
|
|
||||||
nc.mu.Lock()
|
|
||||||
clientCfg, ok := nc.clientCfgs[m.ProxyName]
|
|
||||||
if !ok {
|
|
||||||
nc.mu.Unlock()
|
|
||||||
errInfo := fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)
|
|
||||||
log.Debug(errInfo)
|
|
||||||
_, _ = nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
|
|
||||||
nc.mu.Unlock()
|
|
||||||
errInfo := fmt.Sprintf("xtcp connection of [%s] auth failed", m.ProxyName)
|
|
||||||
log.Debug(errInfo)
|
|
||||||
_, _ = nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nc.sessions[sid] = session
|
|
||||||
nc.mu.Unlock()
|
|
||||||
log.Trace("handle visitor message, sid [%s]", sid)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
nc.mu.Lock()
|
|
||||||
delete(nc.sessions, sid)
|
|
||||||
nc.mu.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
err := errors.PanicToError(func() {
|
|
||||||
clientCfg.SidCh <- &SidRequest{
|
|
||||||
Sid: sid,
|
|
||||||
NotifyCh: session.NotifyCh,
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
if !m.Response {
|
||||||
|
// only wait for response messages if we are a sender
|
||||||
|
if role == DetectRoleSender {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Response = true
|
||||||
|
buf2, err := EncodeMessage(&m, key)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("encode sid message error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, _ = conn.WriteToUDP(buf2, raddr)
|
||||||
|
}
|
||||||
|
return raddr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendSidMessage(
|
||||||
|
ctx context.Context, conn *net.UDPConn,
|
||||||
|
sid string, transactionID string, addr string, key []byte, ttl int,
|
||||||
|
) error {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
ttlStr := ""
|
||||||
|
if ttl > 0 {
|
||||||
|
ttlStr = fmt.Sprintf(" with ttl %d", ttl)
|
||||||
|
}
|
||||||
|
xl.Trace("send sid message from %s to %s%s", conn.LocalAddr(), addr, ttlStr)
|
||||||
|
raddr, err := net.ResolveUDPAddr("udp4", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
if transactionID == "" {
|
||||||
// Wait client connections.
|
transactionID = NewTransactionID()
|
||||||
select {
|
|
||||||
case <-session.NotifyCh:
|
|
||||||
resp := nc.GenNatHoleResponse(session, "")
|
|
||||||
log.Trace("send nat hole response to visitor")
|
|
||||||
_, _ = nc.listener.WriteToUDP(resp, raddr)
|
|
||||||
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
m := &msg.NatHoleSid{
|
||||||
|
TransactionID: transactionID,
|
||||||
func (nc *Controller) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
|
Sid: sid,
|
||||||
nc.mu.RLock()
|
Response: false,
|
||||||
session, ok := nc.sessions[m.Sid]
|
Nonce: strings.Repeat("0", rand.Intn(20)),
|
||||||
nc.mu.RUnlock()
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
log.Trace("handle client message, sid [%s]", session.Sid)
|
buf, err := EncodeMessage(m, key)
|
||||||
session.ClientAddr = raddr
|
|
||||||
|
|
||||||
resp := nc.GenNatHoleResponse(session, "")
|
|
||||||
log.Trace("send nat hole response to client")
|
|
||||||
_, _ = nc.listener.WriteToUDP(resp, raddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nc *Controller) GenNatHoleResponse(session *Session, errInfo string) []byte {
|
|
||||||
var (
|
|
||||||
sid string
|
|
||||||
visitorAddr string
|
|
||||||
clientAddr string
|
|
||||||
)
|
|
||||||
if session != nil {
|
|
||||||
sid = session.Sid
|
|
||||||
visitorAddr = session.VisitorAddr.String()
|
|
||||||
clientAddr = session.ClientAddr.String()
|
|
||||||
}
|
|
||||||
m := &msg.NatHoleResp{
|
|
||||||
Sid: sid,
|
|
||||||
VisitorAddr: visitorAddr,
|
|
||||||
ClientAddr: clientAddr,
|
|
||||||
Error: errInfo,
|
|
||||||
}
|
|
||||||
b := bytes.NewBuffer(nil)
|
|
||||||
err := msg.WriteMsg(b, m)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte("")
|
return err
|
||||||
}
|
}
|
||||||
return b.Bytes()
|
if ttl > 0 {
|
||||||
|
uConn := ipv4.NewConn(conn)
|
||||||
|
original, err := uConn.TTL()
|
||||||
|
if err != nil {
|
||||||
|
xl.Trace("get ttl error %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
xl.Trace("original ttl %d", original)
|
||||||
|
|
||||||
|
err = uConn.SetTTL(ttl)
|
||||||
|
if err != nil {
|
||||||
|
xl.Trace("set ttl error %v", err)
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
_ = uConn.SetTTL(original)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.WriteToUDP(buf, raddr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session struct {
|
func sendSidMessageToRangePorts(
|
||||||
Sid string
|
ctx context.Context, conn *net.UDPConn, addrs []string, ports []msg.PortsRange,
|
||||||
VisitorAddr *net.UDPAddr
|
sendFunc func(*net.UDPConn, string) error,
|
||||||
ClientAddr *net.UDPAddr
|
) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
NotifyCh chan struct{}
|
for _, ip := range lo.Uniq(parseIPs(addrs)) {
|
||||||
|
for _, portsRange := range ports {
|
||||||
|
for i := portsRange.From; i <= portsRange.To; i++ {
|
||||||
|
detectAddr := net.JoinHostPort(ip, strconv.Itoa(i))
|
||||||
|
if err := sendFunc(conn, detectAddr); err != nil {
|
||||||
|
xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
||||||
|
}
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientCfg struct {
|
func sendSidMessageToRandomPorts(
|
||||||
Name string
|
ctx context.Context, conn *net.UDPConn, addrs []string, count int,
|
||||||
Sk string
|
sendFunc func(*net.UDPConn, string) error,
|
||||||
SidCh chan *SidRequest
|
) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
used := sets.New[int]()
|
||||||
|
getUnusedPort := func() int {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
port := rand.Intn(65535-1024) + 1024
|
||||||
|
if !used.Has(port) {
|
||||||
|
used.Insert(port)
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
port := getUnusedPort()
|
||||||
|
if port == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range lo.Uniq(parseIPs(addrs)) {
|
||||||
|
detectAddr := net.JoinHostPort(ip, strconv.Itoa(port))
|
||||||
|
if err := sendFunc(conn, detectAddr); err != nil {
|
||||||
|
xl.Trace("send sid message from %s to %s error: %v", conn.LocalAddr(), detectAddr, err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIPs(addrs []string) []string {
|
||||||
|
var ips []string
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if ip, _, err := net.SplitHostPort(addr); err == nil {
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package nathole
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@ -63,3 +64,49 @@ func (s *ChangedAddress) GetFrom(m *stun.Message) error {
|
|||||||
func (s *ChangedAddress) String() string {
|
func (s *ChangedAddress) String() string {
|
||||||
return net.JoinHostPort(s.IP.String(), strconv.Itoa(s.Port))
|
return net.JoinHostPort(s.IP.String(), strconv.Itoa(s.Port))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListAllLocalIPs() ([]net.IP, error) {
|
||||||
|
addrs, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ips := make([]net.IP, 0, len(addrs))
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ip, _, err := net.ParseCIDR(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListLocalIPsForNatHole(max int) ([]string, error) {
|
||||||
|
if max <= 0 {
|
||||||
|
return nil, fmt.Errorf("max must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
ips, err := ListAllLocalIPs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := make([]string, 0, max)
|
||||||
|
for _, ip := range ips {
|
||||||
|
if len(filtered) >= max {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore ipv6 address
|
||||||
|
if ip.To4() == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// ignore localhost IP
|
||||||
|
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = append(filtered, ip.String())
|
||||||
|
}
|
||||||
|
return filtered, nil
|
||||||
|
}
|
||||||
|
119
pkg/transport/message.go
Normal file
119
pkg/transport/message.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MessageTransporter interface {
|
||||||
|
Send(msg.Message) error
|
||||||
|
// Recv(ctx context.Context, laneKey string, msgType string) (Message, error)
|
||||||
|
// Do will first send msg, then recv msg with the same laneKey and specified msgType.
|
||||||
|
Do(ctx context.Context, req msg.Message, laneKey, recvMsgType string) (msg.Message, error)
|
||||||
|
Dispatch(m msg.Message, laneKey string) bool
|
||||||
|
DispatchWithType(m msg.Message, msgType, laneKey string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMessageTransporter(sendCh chan msg.Message) MessageTransporter {
|
||||||
|
return &transporterImpl{
|
||||||
|
sendCh: sendCh,
|
||||||
|
registry: make(map[string]map[string]chan msg.Message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type transporterImpl struct {
|
||||||
|
sendCh chan msg.Message
|
||||||
|
|
||||||
|
// First key is message type and second key is lane key.
|
||||||
|
// Dispatch will dispatch message to releated channel by its message type
|
||||||
|
// and lane key.
|
||||||
|
registry map[string]map[string]chan msg.Message
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (impl *transporterImpl) Send(m msg.Message) error {
|
||||||
|
return errors.PanicToError(func() {
|
||||||
|
impl.sendCh <- m
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (impl *transporterImpl) Do(ctx context.Context, req msg.Message, laneKey, recvMsgType string) (msg.Message, error) {
|
||||||
|
ch := make(chan msg.Message, 1)
|
||||||
|
defer close(ch)
|
||||||
|
unregisterFn := impl.registerMsgChan(ch, laneKey, recvMsgType)
|
||||||
|
defer unregisterFn()
|
||||||
|
|
||||||
|
if err := impl.Send(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case resp := <-ch:
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (impl *transporterImpl) DispatchWithType(m msg.Message, msgType, laneKey string) bool {
|
||||||
|
var ch chan msg.Message
|
||||||
|
impl.mu.RLock()
|
||||||
|
byLaneKey, ok := impl.registry[msgType]
|
||||||
|
if ok {
|
||||||
|
ch = byLaneKey[laneKey]
|
||||||
|
}
|
||||||
|
impl.mu.RUnlock()
|
||||||
|
|
||||||
|
if ch == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := errors.PanicToError(func() {
|
||||||
|
ch <- m
|
||||||
|
}); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (impl *transporterImpl) Dispatch(m msg.Message, laneKey string) bool {
|
||||||
|
msgType := reflect.TypeOf(m).Elem().Name()
|
||||||
|
return impl.DispatchWithType(m, msgType, laneKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (impl *transporterImpl) registerMsgChan(recvCh chan msg.Message, laneKey string, msgType string) (unregister func()) {
|
||||||
|
impl.mu.Lock()
|
||||||
|
byLaneKey, ok := impl.registry[msgType]
|
||||||
|
if !ok {
|
||||||
|
byLaneKey = make(map[string]chan msg.Message)
|
||||||
|
impl.registry[msgType] = byLaneKey
|
||||||
|
}
|
||||||
|
byLaneKey[laneKey] = recvCh
|
||||||
|
impl.mu.Unlock()
|
||||||
|
|
||||||
|
unregister = func() {
|
||||||
|
impl.mu.Lock()
|
||||||
|
delete(byLaneKey, laneKey)
|
||||||
|
impl.mu.Unlock()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -1,3 +1,17 @@
|
|||||||
|
// Copyright 2023 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package transport
|
package transport
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -256,3 +256,11 @@ func (l *UDPListener) Close() error {
|
|||||||
func (l *UDPListener) Addr() net.Addr {
|
func (l *UDPListener) Addr() net.Addr {
|
||||||
return l.addr
|
return l.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConnectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls
|
||||||
|
// to Write syscalls that are 4 times faster on some OS'es. This should only be
|
||||||
|
// used for connections that were produced by a net.Dial* call.
|
||||||
|
type ConnectedUDPConn struct{ *net.UDPConn }
|
||||||
|
|
||||||
|
// WriteTo redirects all writes to the Write syscall, which is 4 times faster.
|
||||||
|
func (c *ConnectedUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { return c.Write(b) }
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
func InSlice[T comparable](v T, s []T) bool {
|
|
||||||
for _, vv := range s {
|
|
||||||
if v == vv {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func InSliceAny[T any](v T, s []T, equalFn func(a, b T) bool) bool {
|
|
||||||
for _, vv := range s {
|
|
||||||
if equalFn(v, vv) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func InSliceAnyFunc[T any](equalFn func(a, b T) bool) func(v T, s []T) bool {
|
|
||||||
return func(v T, s []T) bool {
|
|
||||||
return InSliceAny(v, s, equalFn)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInSlice(t *testing.T) {
|
|
||||||
require := require.New(t)
|
|
||||||
require.True(InSlice(1, []int{1, 2, 3}))
|
|
||||||
require.False(InSlice(0, []int{1, 2, 3}))
|
|
||||||
require.True(InSlice("foo", []string{"foo", "bar"}))
|
|
||||||
require.False(InSlice("not exist", []string{"foo", "bar"}))
|
|
||||||
}
|
|
||||||
|
|
||||||
type testStructA struct {
|
|
||||||
Name string
|
|
||||||
Age int
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInSliceAny(t *testing.T) {
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
a := testStructA{Name: "foo", Age: 20}
|
|
||||||
b := testStructA{Name: "foo", Age: 30}
|
|
||||||
c := testStructA{Name: "bar", Age: 20}
|
|
||||||
|
|
||||||
equalFn := func(o, p testStructA) bool {
|
|
||||||
return o.Name == p.Name
|
|
||||||
}
|
|
||||||
require.True(InSliceAny(a, []testStructA{b, c}, equalFn))
|
|
||||||
require.False(InSliceAny(c, []testStructA{a, b}, equalFn))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInSliceAnyFunc(t *testing.T) {
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
a := testStructA{Name: "foo", Age: 20}
|
|
||||||
b := testStructA{Name: "foo", Age: 30}
|
|
||||||
c := testStructA{Name: "bar", Age: 20}
|
|
||||||
|
|
||||||
equalFn := func(o, p testStructA) bool {
|
|
||||||
return o.Name == p.Name
|
|
||||||
}
|
|
||||||
testStructAInSlice := InSliceAnyFunc(equalFn)
|
|
||||||
require.True(testStructAInSlice(a, []testStructA{b, c}))
|
|
||||||
require.False(testStructAInSlice(c, []testStructA{a, b}))
|
|
||||||
}
|
|
@ -28,19 +28,32 @@ import (
|
|||||||
|
|
||||||
// RandID return a rand string used in frp.
|
// RandID return a rand string used in frp.
|
||||||
func RandID() (id string, err error) {
|
func RandID() (id string, err error) {
|
||||||
return RandIDWithLen(8)
|
return RandIDWithLen(16)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RandIDWithLen return a rand string with idLen length.
|
// RandIDWithLen return a rand string with idLen length.
|
||||||
func RandIDWithLen(idLen int) (id string, err error) {
|
func RandIDWithLen(idLen int) (id string, err error) {
|
||||||
b := make([]byte, idLen)
|
if idLen <= 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
b := make([]byte, idLen/2+1)
|
||||||
_, err = rand.Read(b)
|
_, err = rand.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id = fmt.Sprintf("%x", b)
|
id = fmt.Sprintf("%x", b)
|
||||||
return
|
return id[:idLen], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandIDWithRandLen return a rand string with length between [start, end).
|
||||||
|
func RandIDWithRandLen(start, end int) (id string, err error) {
|
||||||
|
if start >= end {
|
||||||
|
err = fmt.Errorf("start should be less than end")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
idLen := mathrand.Intn(end-start) + start
|
||||||
|
return RandIDWithLen(idLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAuthKey(token string, timestamp int64) (key string) {
|
func GetAuthKey(token string, timestamp int64) (key string) {
|
||||||
|
@ -14,10 +14,51 @@ func TestRandId(t *testing.T) {
|
|||||||
assert.Equal(16, len(id))
|
assert.Equal(16, len(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRandIDWithRandLen(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
start int
|
||||||
|
end int
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "start and end are equal",
|
||||||
|
start: 5,
|
||||||
|
end: 5,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "start is less than end",
|
||||||
|
start: 5,
|
||||||
|
end: 10,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "start is greater than end",
|
||||||
|
start: 10,
|
||||||
|
end: 5,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
id, err := RandIDWithRandLen(tt.start, tt.end)
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.GreaterOrEqual(len(id), tt.start)
|
||||||
|
assert.Less(len(id), tt.end)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetAuthKey(t *testing.T) {
|
func TestGetAuthKey(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
key := GetAuthKey("1234", 1488720000)
|
key := GetAuthKey("1234", 1488720000)
|
||||||
t.Log(key)
|
|
||||||
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
|
assert.Equal("6df41a43725f0c770fd56379e12acf8c", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
frpErr "github.com/fatedier/frp/pkg/errors"
|
frpErr "github.com/fatedier/frp/pkg/errors"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
"github.com/fatedier/frp/pkg/util/version"
|
"github.com/fatedier/frp/pkg/util/version"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
@ -82,6 +83,16 @@ func (cm *ControlManager) GetByID(runID string) (ctl *Control, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cm *ControlManager) Close() error {
|
||||||
|
cm.mu.Lock()
|
||||||
|
defer cm.mu.Unlock()
|
||||||
|
for _, ctl := range cm.ctlsByRunID {
|
||||||
|
ctl.Close()
|
||||||
|
}
|
||||||
|
cm.ctlsByRunID = make(map[string]*Control)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Control struct {
|
type Control struct {
|
||||||
// all resource managers and controllers
|
// all resource managers and controllers
|
||||||
rc *controller.ResourceController
|
rc *controller.ResourceController
|
||||||
@ -95,6 +106,9 @@ type Control struct {
|
|||||||
// verifies authentication based on selected method
|
// verifies authentication based on selected method
|
||||||
authVerifier auth.Verifier
|
authVerifier auth.Verifier
|
||||||
|
|
||||||
|
// other components can use this to communicate with client
|
||||||
|
msgTransporter transport.MessageTransporter
|
||||||
|
|
||||||
// login message
|
// login message
|
||||||
loginMsg *msg.Login
|
loginMsg *msg.Login
|
||||||
|
|
||||||
@ -158,7 +172,7 @@ func NewControl(
|
|||||||
if poolCount > int(serverCfg.MaxPoolCount) {
|
if poolCount > int(serverCfg.MaxPoolCount) {
|
||||||
poolCount = int(serverCfg.MaxPoolCount)
|
poolCount = int(serverCfg.MaxPoolCount)
|
||||||
}
|
}
|
||||||
return &Control{
|
ctl := &Control{
|
||||||
rc: rc,
|
rc: rc,
|
||||||
pxyManager: pxyManager,
|
pxyManager: pxyManager,
|
||||||
pluginManager: pluginManager,
|
pluginManager: pluginManager,
|
||||||
@ -182,6 +196,8 @@ func NewControl(
|
|||||||
xl: xlog.FromContextSafe(ctx),
|
xl: xlog.FromContextSafe(ctx),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
|
ctl.msgTransporter = transport.NewMessageTransporter(ctl.sendCh)
|
||||||
|
return ctl
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start send a login success message to client and start working.
|
// Start send a login success message to client and start working.
|
||||||
@ -204,6 +220,18 @@ func (ctl *Control) Start() {
|
|||||||
go ctl.stoper()
|
go ctl.stoper()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) Close() error {
|
||||||
|
ctl.allShutdown.Start()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) Replaced(newCtl *Control) {
|
||||||
|
xl := ctl.xl
|
||||||
|
xl.Info("Replaced by client [%s]", newCtl.runID)
|
||||||
|
ctl.runID = ""
|
||||||
|
ctl.allShutdown.Start()
|
||||||
|
}
|
||||||
|
|
||||||
func (ctl *Control) RegisterWorkConn(conn net.Conn) error {
|
func (ctl *Control) RegisterWorkConn(conn net.Conn) error {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -275,13 +303,6 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) Replaced(newCtl *Control) {
|
|
||||||
xl := ctl.xl
|
|
||||||
xl.Info("Replaced by client [%s]", newCtl.runID)
|
|
||||||
ctl.runID = ""
|
|
||||||
ctl.allShutdown.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctl *Control) writer() {
|
func (ctl *Control) writer() {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -465,6 +486,12 @@ func (ctl *Control) manager() {
|
|||||||
metrics.Server.NewProxy(m.ProxyName, m.ProxyType)
|
metrics.Server.NewProxy(m.ProxyName, m.ProxyType)
|
||||||
}
|
}
|
||||||
ctl.sendCh <- resp
|
ctl.sendCh <- resp
|
||||||
|
case *msg.NatHoleVisitor:
|
||||||
|
go ctl.HandleNatHoleVisitor(m)
|
||||||
|
case *msg.NatHoleClient:
|
||||||
|
go ctl.HandleNatHoleClient(m)
|
||||||
|
case *msg.NatHoleReport:
|
||||||
|
go ctl.HandleNatHoleReport(m)
|
||||||
case *msg.CloseProxy:
|
case *msg.CloseProxy:
|
||||||
_ = ctl.CloseProxy(m)
|
_ = ctl.CloseProxy(m)
|
||||||
xl.Info("close proxy [%s] success", m.ProxyName)
|
xl.Info("close proxy [%s] success", m.ProxyName)
|
||||||
@ -497,6 +524,18 @@ func (ctl *Control) manager() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) HandleNatHoleVisitor(m *msg.NatHoleVisitor) {
|
||||||
|
ctl.rc.NatHoleController.HandleVisitor(m, ctl.msgTransporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) HandleNatHoleClient(m *msg.NatHoleClient) {
|
||||||
|
ctl.rc.NatHoleController.HandleClient(m, ctl.msgTransporter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) HandleNatHoleReport(m *msg.NatHoleReport) {
|
||||||
|
ctl.rc.NatHoleController.HandleReport(m)
|
||||||
|
}
|
||||||
|
|
||||||
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
|
||||||
var pxyConf config.ProxyConf
|
var pxyConf config.ProxyConf
|
||||||
// Load configures from NewProxy message and check.
|
// Load configures from NewProxy message and check.
|
||||||
|
@ -44,41 +44,20 @@ func (pxy *XTCPProxy) Run() (remoteAddr string, err error) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-pxy.closeCh:
|
case <-pxy.closeCh:
|
||||||
break
|
return
|
||||||
case sidRequest := <-sidCh:
|
case sid := <-sidCh:
|
||||||
sr := sidRequest
|
|
||||||
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
|
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m := &msg.NatHoleSid{
|
m := &msg.NatHoleSid{
|
||||||
Sid: sr.Sid,
|
Sid: sid,
|
||||||
}
|
}
|
||||||
errRet = msg.WriteMsg(workConn, m)
|
errRet = msg.WriteMsg(workConn, m)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
xl.Warn("write nat hole sid package error, %v", errRet)
|
xl.Warn("write nat hole sid package error, %v", errRet)
|
||||||
workConn.Close()
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
workConn.Close()
|
||||||
go func() {
|
|
||||||
raw, errRet := msg.ReadMsg(workConn)
|
|
||||||
if errRet != nil {
|
|
||||||
xl.Warn("read nat hole client ok package error: %v", errRet)
|
|
||||||
workConn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
|
|
||||||
xl.Warn("read nat hole client ok package format error")
|
|
||||||
workConn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case sr.NotifyCh <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -99,6 +99,11 @@ type Service struct {
|
|||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
|
|
||||||
cfg config.ServerCommonConf
|
cfg config.ServerCommonConf
|
||||||
|
|
||||||
|
// service context
|
||||||
|
ctx context.Context
|
||||||
|
// call cancel to stop service
|
||||||
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||||
@ -110,6 +115,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
ctlManager: NewControlManager(),
|
ctlManager: NewControlManager(),
|
||||||
pxyManager: proxy.NewManager(),
|
pxyManager: proxy.NewManager(),
|
||||||
@ -123,6 +129,8 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
authVerifier: auth.NewAuthVerifier(cfg.ServerConfig),
|
authVerifier: auth.NewAuthVerifier(cfg.ServerConfig),
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create tcpmux httpconnect multiplexer.
|
// Create tcpmux httpconnect multiplexer.
|
||||||
@ -290,17 +298,12 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Create nat hole controller.
|
// Create nat hole controller.
|
||||||
if cfg.BindUDPPort > 0 {
|
nc, err := nathole.NewController(time.Duration(cfg.NatHoleAnalysisDataReserveHours) * time.Hour)
|
||||||
var nc *nathole.Controller
|
if err != nil {
|
||||||
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.BindUDPPort))
|
err = fmt.Errorf("create nat hole controller error, %v", err)
|
||||||
nc, err = nathole.NewController(address, []byte(cfg.Token))
|
return
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("create nat hole controller error, %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
svr.rc.NatHoleController = nc
|
|
||||||
log.Info("nat hole udp service listen on %s", address)
|
|
||||||
}
|
}
|
||||||
|
svr.rc.NatHoleController = nc
|
||||||
|
|
||||||
var statsEnable bool
|
var statsEnable bool
|
||||||
// Create dashboard web server.
|
// Create dashboard web server.
|
||||||
@ -327,22 +330,43 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) Run() {
|
func (svr *Service) Run() {
|
||||||
if svr.rc.NatHoleController != nil {
|
|
||||||
go svr.rc.NatHoleController.Run()
|
|
||||||
}
|
|
||||||
if svr.kcpListener != nil {
|
if svr.kcpListener != nil {
|
||||||
go svr.HandleListener(svr.kcpListener)
|
go svr.HandleListener(svr.kcpListener)
|
||||||
}
|
}
|
||||||
if svr.quicListener != nil {
|
if svr.quicListener != nil {
|
||||||
go svr.HandleQUICListener(svr.quicListener)
|
go svr.HandleQUICListener(svr.quicListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
go svr.HandleListener(svr.websocketListener)
|
go svr.HandleListener(svr.websocketListener)
|
||||||
go svr.HandleListener(svr.tlsListener)
|
go svr.HandleListener(svr.tlsListener)
|
||||||
|
|
||||||
|
if svr.rc.NatHoleController != nil {
|
||||||
|
go svr.rc.NatHoleController.CleanWorker(svr.ctx)
|
||||||
|
}
|
||||||
svr.HandleListener(svr.listener)
|
svr.HandleListener(svr.listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svr *Service) Close() error {
|
||||||
|
if svr.kcpListener != nil {
|
||||||
|
svr.kcpListener.Close()
|
||||||
|
}
|
||||||
|
if svr.quicListener != nil {
|
||||||
|
svr.quicListener.Close()
|
||||||
|
}
|
||||||
|
if svr.websocketListener != nil {
|
||||||
|
svr.websocketListener.Close()
|
||||||
|
}
|
||||||
|
if svr.tlsListener != nil {
|
||||||
|
svr.tlsListener.Close()
|
||||||
|
}
|
||||||
|
if svr.listener != nil {
|
||||||
|
svr.listener.Close()
|
||||||
|
}
|
||||||
|
svr.cancel()
|
||||||
|
|
||||||
|
svr.ctlManager.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (svr *Service) handleConnection(ctx context.Context, conn net.Conn) {
|
func (svr *Service) handleConnection(ctx context.Context, conn net.Conn) {
|
||||||
xl := xlog.FromContextSafe(ctx)
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user