mirror of https://github.com/fatedier/frp
commit
2406ecdfea
|
@ -88,6 +88,7 @@ type StatusResp struct {
|
||||||
Https []ProxyStatusResp `json:"https"`
|
Https []ProxyStatusResp `json:"https"`
|
||||||
Stcp []ProxyStatusResp `json:"stcp"`
|
Stcp []ProxyStatusResp `json:"stcp"`
|
||||||
Xtcp []ProxyStatusResp `json:"xtcp"`
|
Xtcp []ProxyStatusResp `json:"xtcp"`
|
||||||
|
Sudp []ProxyStatusResp `json:"sudp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyStatusResp struct {
|
type ProxyStatusResp struct {
|
||||||
|
@ -155,6 +156,11 @@ func NewProxyStatusResp(status *proxy.ProxyStatus, serverAddr string) ProxyStatu
|
||||||
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
}
|
}
|
||||||
psr.Plugin = cfg.Plugin
|
psr.Plugin = cfg.Plugin
|
||||||
|
case *config.SudpProxyConf:
|
||||||
|
if cfg.LocalPort != 0 {
|
||||||
|
psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
|
||||||
|
}
|
||||||
|
psr.Plugin = cfg.Plugin
|
||||||
}
|
}
|
||||||
return psr
|
return psr
|
||||||
}
|
}
|
||||||
|
@ -171,6 +177,7 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
res.Https = make([]ProxyStatusResp, 0)
|
res.Https = make([]ProxyStatusResp, 0)
|
||||||
res.Stcp = make([]ProxyStatusResp, 0)
|
res.Stcp = make([]ProxyStatusResp, 0)
|
||||||
res.Xtcp = make([]ProxyStatusResp, 0)
|
res.Xtcp = make([]ProxyStatusResp, 0)
|
||||||
|
res.Sudp = make([]ProxyStatusResp, 0)
|
||||||
|
|
||||||
log.Info("Http request [/api/status]")
|
log.Info("Http request [/api/status]")
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -194,6 +201,8 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
case "xtcp":
|
case "xtcp":
|
||||||
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
|
case "sudp":
|
||||||
|
res.Sudp = append(res.Sudp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Sort(ByProxyStatusResp(res.Tcp))
|
sort.Sort(ByProxyStatusResp(res.Tcp))
|
||||||
|
@ -202,6 +211,7 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
sort.Sort(ByProxyStatusResp(res.Https))
|
sort.Sort(ByProxyStatusResp(res.Https))
|
||||||
sort.Sort(ByProxyStatusResp(res.Stcp))
|
sort.Sort(ByProxyStatusResp(res.Stcp))
|
||||||
sort.Sort(ByProxyStatusResp(res.Xtcp))
|
sort.Sort(ByProxyStatusResp(res.Xtcp))
|
||||||
|
sort.Sort(ByProxyStatusResp(res.Sudp))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,12 @@ func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.Cl
|
||||||
BaseProxy: &baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
case *config.SudpProxyConf:
|
||||||
|
pxy = &SudpProxy{
|
||||||
|
BaseProxy: &baseProxy,
|
||||||
|
cfg: cfg,
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -540,6 +546,151 @@ func (pxy *UdpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||||
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh)
|
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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", fmt.Sprintf("%s:%d", pxy.cfg.LocalIp, 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())
|
||||||
|
|
||||||
|
if pxy.limiter != nil {
|
||||||
|
rwc := frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error {
|
||||||
|
return conn.Close()
|
||||||
|
})
|
||||||
|
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(conn net.Conn, 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(workConn, sendCh)
|
||||||
|
|
||||||
|
udp.Forwarder(pxy.localAddr, readCh, sendCh)
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|
|
@ -26,10 +26,12 @@ import (
|
||||||
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/models/proto/udp"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
"github.com/fatedier/frp/utils/util"
|
"github.com/fatedier/frp/utils/util"
|
||||||
"github.com/fatedier/frp/utils/xlog"
|
"github.com/fatedier/frp/utils/xlog"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/errors"
|
||||||
frpIo "github.com/fatedier/golib/io"
|
frpIo "github.com/fatedier/golib/io"
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
fmux "github.com/hashicorp/yamux"
|
fmux "github.com/hashicorp/yamux"
|
||||||
|
@ -58,6 +60,12 @@ func NewVisitor(ctx context.Context, ctl *Control, cfg config.VisitorConf) (visi
|
||||||
BaseVisitor: &baseVisitor,
|
BaseVisitor: &baseVisitor,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
case *config.SudpVisitorConf:
|
||||||
|
visitor = &SudpVisitor{
|
||||||
|
BaseVisitor: &baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
checkCloseCh: make(chan struct{}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -328,3 +336,204 @@ func (sv *XtcpVisitor) handleConn(userConn net.Conn) {
|
||||||
frpIo.Join(userConn, muxConnRWCloser)
|
frpIo.Join(userConn, muxConnRWCloser)
|
||||||
xl.Debug("join connections closed")
|
xl.Debug("join connections closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, 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")
|
||||||
|
|
||||||
|
go sv.dispatcher()
|
||||||
|
go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SudpVisitor) dispatcher() {
|
||||||
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// loop for get frpc to frps tcp conn
|
||||||
|
// setup worker
|
||||||
|
// wait worker to finished
|
||||||
|
// retry or exit
|
||||||
|
visitorConn, err := sv.getNewVisitorConn()
|
||||||
|
if err != nil {
|
||||||
|
// check if proxy is closed
|
||||||
|
// if checkCloseCh is close, we will return, other case we will continue to reconnect
|
||||||
|
select {
|
||||||
|
case <-sv.checkCloseCh:
|
||||||
|
xl.Info("frpc sudp visitor proxy is closed")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sv.worker(visitorConn)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sv.checkCloseCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SudpVisitor) worker(workConn net.Conn) {
|
||||||
|
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 frpc")
|
||||||
|
}); 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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
case <-closeCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go workConnReaderFn(workConn)
|
||||||
|
go workConnSenderFn(workConn)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
xl.Info("sudp worker is closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *SudpVisitor) getNewVisitorConn() (visitorConn net.Conn, err error) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright 2018 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 sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||||
|
sudpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
|
||||||
|
sudpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
|
||||||
|
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key")
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name")
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
|
||||||
|
sudpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port")
|
||||||
|
sudpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr")
|
||||||
|
sudpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
|
||||||
|
sudpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
|
||||||
|
sudpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(sudpCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sudpCmd = &cobra.Command{
|
||||||
|
Use: "sudp",
|
||||||
|
Short: "Run frpc with a single sudp proxy",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfs := make(map[string]config.ProxyConf)
|
||||||
|
visitorConfs := make(map[string]config.VisitorConf)
|
||||||
|
|
||||||
|
var prefix string
|
||||||
|
if user != "" {
|
||||||
|
prefix = user + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
if role == "server" {
|
||||||
|
cfg := &config.SudpProxyConf{}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.SudpProxy
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
cfg.Role = role
|
||||||
|
cfg.Sk = sk
|
||||||
|
cfg.LocalIp = localIp
|
||||||
|
cfg.LocalPort = localPort
|
||||||
|
err = cfg.CheckForCli()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
proxyConfs[cfg.ProxyName] = cfg
|
||||||
|
} else if role == "visitor" {
|
||||||
|
cfg := &config.SudpVisitorConf{}
|
||||||
|
cfg.ProxyName = prefix + proxyName
|
||||||
|
cfg.ProxyType = consts.SudpProxy
|
||||||
|
cfg.UseEncryption = useEncryption
|
||||||
|
cfg.UseCompression = useCompression
|
||||||
|
cfg.Role = role
|
||||||
|
cfg.Sk = sk
|
||||||
|
cfg.ServerName = serverName
|
||||||
|
cfg.BindAddr = bindAddr
|
||||||
|
cfg.BindPort = bindPort
|
||||||
|
err = cfg.Check()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
visitorConfs[cfg.ProxyName] = cfg
|
||||||
|
} else {
|
||||||
|
fmt.Println("invalid role")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = startService(clientCfg, proxyConfs, visitorConfs, "")
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
|
@ -70,7 +70,7 @@ The response can look like any of the following:
|
||||||
|
|
||||||
### Operation
|
### Operation
|
||||||
|
|
||||||
Currently `Login`, `NewProxy`, `Ping` and `NewWorkConn` operations are supported.
|
Currently `Login`, `NewProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported.
|
||||||
|
|
||||||
#### Login
|
#### Login
|
||||||
|
|
||||||
|
@ -172,6 +172,25 @@ New work connection received from frpc (RPC sent after `run_id` is matched with
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### NewUserConn
|
||||||
|
|
||||||
|
New user connection received from proxy (support `tcp`, `stcp`, `https` and `tcpmux`) .
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"user": {
|
||||||
|
"user": <string>,
|
||||||
|
"metas": map<string>string
|
||||||
|
"run_id": <string>
|
||||||
|
},
|
||||||
|
"proxy_name": <string>,
|
||||||
|
"proxy_type": <string>,
|
||||||
|
"remote_addr": <string>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Server Plugin Configuration
|
### Server Plugin Configuration
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
|
|
|
@ -69,7 +69,7 @@ Response
|
||||||
|
|
||||||
### 操作类型
|
### 操作类型
|
||||||
|
|
||||||
目前插件支持管理的操作类型有 `Login` 和 `NewProxy`。
|
目前插件支持管理的操作类型有 `Login`、`NewProxy`、`Ping`、`NewWorkConn` 和 `NewUserConn`。
|
||||||
|
|
||||||
#### Login
|
#### Login
|
||||||
|
|
||||||
|
@ -127,6 +127,63 @@ Response
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Ping
|
||||||
|
|
||||||
|
心跳相关信息
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"user": {
|
||||||
|
"user": <string>,
|
||||||
|
"metas": map<string>string
|
||||||
|
"run_id": <string>
|
||||||
|
},
|
||||||
|
"timestamp": <int64>,
|
||||||
|
"privilege_key": <string>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### NewWorkConn
|
||||||
|
|
||||||
|
新增 `frpc` 连接相关信息
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"user": {
|
||||||
|
"user": <string>,
|
||||||
|
"metas": map<string>string
|
||||||
|
"run_id": <string>
|
||||||
|
},
|
||||||
|
"run_id": <string>
|
||||||
|
"timestamp": <int64>,
|
||||||
|
"privilege_key": <string>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### NewUserConn
|
||||||
|
|
||||||
|
新增 `proxy` 连接相关信息 (支持 `tcp`、`stcp`、`https` 和 `tcpmux` 协议)。
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"user": {
|
||||||
|
"user": <string>,
|
||||||
|
"metas": map<string>string
|
||||||
|
"run_id": <string>
|
||||||
|
},
|
||||||
|
"proxy_name": <string>,
|
||||||
|
"proxy_type": <string>,
|
||||||
|
"remote_addr": <string>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### frps 中插件配置
|
### frps 中插件配置
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
|
|
|
@ -81,7 +81,7 @@ func (auth *TokenAuthSetterVerifier) SetPing(pingMsg *msg.Ping) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
func (auth *TokenAuthSetterVerifier) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) error {
|
||||||
if !auth.AuthenticateHeartBeats {
|
if !auth.AuthenticateNewWorkConns {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ func init() {
|
||||||
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
||||||
proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
|
proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
|
||||||
proxyConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpProxyConf{})
|
proxyConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpProxyConf{})
|
||||||
|
proxyConfTypeMap[consts.SudpProxy] = reflect.TypeOf(SudpProxyConf{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfByType creates a empty ProxyConf object by proxyType.
|
// NewConfByType creates a empty ProxyConf object by proxyType.
|
||||||
|
@ -875,6 +876,72 @@ func (cfg *HttpsProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SUDP
|
||||||
|
type SudpProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
|
||||||
|
Role string `json:"role"`
|
||||||
|
Sk string `json:"sk"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *SudpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*SudpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
cfg.Role != cmpConf.Role ||
|
||||||
|
cfg.Sk != cmpConf.Sk {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *SudpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Role = section["role"]
|
||||||
|
if cfg.Role != "server" {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Sk = section["sk"]
|
||||||
|
|
||||||
|
if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *SudpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.MarshalToMsg(pMsg)
|
||||||
|
pMsg.Sk = cfg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *SudpProxyConf) CheckForCli() (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.checkForCli(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cfg.Role != "server" {
|
||||||
|
err = fmt.Errorf("role should be 'server'")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *SudpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for role server.
|
||||||
|
func (cfg *SudpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnmarshalFromMsg(pMsg)
|
||||||
|
cfg.Sk = pMsg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
// STCP
|
// STCP
|
||||||
type StcpProxyConf struct {
|
type StcpProxyConf struct {
|
||||||
BaseProxyConf
|
BaseProxyConf
|
||||||
|
|
|
@ -32,6 +32,7 @@ func init() {
|
||||||
visitorConfTypeMap = make(map[string]reflect.Type)
|
visitorConfTypeMap = make(map[string]reflect.Type)
|
||||||
visitorConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpVisitorConf{})
|
visitorConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpVisitorConf{})
|
||||||
visitorConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpVisitorConf{})
|
visitorConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpVisitorConf{})
|
||||||
|
visitorConfTypeMap[consts.SudpProxy] = reflect.TypeOf(SudpVisitorConf{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type VisitorConf interface {
|
type VisitorConf interface {
|
||||||
|
@ -152,6 +153,36 @@ func (cfg *BaseVisitorConf) UnmarshalFromIni(prefix string, name string, section
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SudpVisitorConf struct {
|
||||||
|
BaseVisitorConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *SudpVisitorConf) Compare(cmp VisitorConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*SudpVisitorConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *SudpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *SudpVisitorConf) Check() (err error) {
|
||||||
|
if err = cfg.BaseVisitorConf.check(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type StcpVisitorConf struct {
|
type StcpVisitorConf struct {
|
||||||
BaseVisitorConf
|
BaseVisitorConf
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ var (
|
||||||
HttpsProxy string = "https"
|
HttpsProxy string = "https"
|
||||||
StcpProxy string = "stcp"
|
StcpProxy string = "stcp"
|
||||||
XtcpProxy string = "xtcp"
|
XtcpProxy string = "xtcp"
|
||||||
|
SudpProxy string = "sudp"
|
||||||
|
|
||||||
// authentication method
|
// authentication method
|
||||||
TokenAuthMethod string = "token"
|
TokenAuthMethod string = "token"
|
||||||
|
|
|
@ -28,6 +28,7 @@ type Manager struct {
|
||||||
newProxyPlugins []Plugin
|
newProxyPlugins []Plugin
|
||||||
pingPlugins []Plugin
|
pingPlugins []Plugin
|
||||||
newWorkConnPlugins []Plugin
|
newWorkConnPlugins []Plugin
|
||||||
|
newUserConnPlugins []Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager() *Manager {
|
func NewManager() *Manager {
|
||||||
|
@ -36,6 +37,7 @@ func NewManager() *Manager {
|
||||||
newProxyPlugins: make([]Plugin, 0),
|
newProxyPlugins: make([]Plugin, 0),
|
||||||
pingPlugins: make([]Plugin, 0),
|
pingPlugins: make([]Plugin, 0),
|
||||||
newWorkConnPlugins: make([]Plugin, 0),
|
newWorkConnPlugins: make([]Plugin, 0),
|
||||||
|
newUserConnPlugins: make([]Plugin, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +54,9 @@ func (m *Manager) Register(p Plugin) {
|
||||||
if p.IsSupport(OpNewWorkConn) {
|
if p.IsSupport(OpNewWorkConn) {
|
||||||
m.pingPlugins = append(m.pingPlugins, p)
|
m.pingPlugins = append(m.pingPlugins, p)
|
||||||
}
|
}
|
||||||
|
if p.IsSupport(OpNewUserConn) {
|
||||||
|
m.newUserConnPlugins = append(m.newUserConnPlugins, p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Login(content *LoginContent) (*LoginContent, error) {
|
func (m *Manager) Login(content *LoginContent) (*LoginContent, error) {
|
||||||
|
@ -189,3 +194,37 @@ func (m *Manager) NewWorkConn(content *NewWorkConnContent) (*NewWorkConnContent,
|
||||||
}
|
}
|
||||||
return content, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) NewUserConn(content *NewUserConnContent) (*NewUserConnContent, error) {
|
||||||
|
if len(m.newUserConnPlugins) == 0 {
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
res = &Response{
|
||||||
|
Reject: false,
|
||||||
|
Unchange: true,
|
||||||
|
}
|
||||||
|
retContent interface{}
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
reqid, _ := util.RandId()
|
||||||
|
xl := xlog.New().AppendPrefix("reqid: " + reqid)
|
||||||
|
ctx := xlog.NewContext(context.Background(), xl)
|
||||||
|
ctx = NewReqidContext(ctx, reqid)
|
||||||
|
|
||||||
|
for _, p := range m.newUserConnPlugins {
|
||||||
|
res, retContent, err = p.Handle(ctx, OpNewUserConn, *content)
|
||||||
|
if err != nil {
|
||||||
|
xl.Info("send NewUserConn request to plugin [%s] error: %v", p.Name(), err)
|
||||||
|
return nil, errors.New("send NewUserConn request to plugin error")
|
||||||
|
}
|
||||||
|
if res.Reject {
|
||||||
|
return nil, fmt.Errorf("%s", res.RejectReason)
|
||||||
|
}
|
||||||
|
if !res.Unchange {
|
||||||
|
content = retContent.(*NewUserConnContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ const (
|
||||||
OpNewProxy = "NewProxy"
|
OpNewProxy = "NewProxy"
|
||||||
OpPing = "Ping"
|
OpPing = "Ping"
|
||||||
OpNewWorkConn = "NewWorkConn"
|
OpNewWorkConn = "NewWorkConn"
|
||||||
|
OpNewUserConn = "NewUserConn"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
|
|
|
@ -55,3 +55,10 @@ type NewWorkConnContent struct {
|
||||||
User UserInfo `json:"user"`
|
User UserInfo `json:"user"`
|
||||||
msg.NewWorkConn
|
msg.NewWorkConn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NewUserConnContent struct {
|
||||||
|
User UserInfo `json:"user"`
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
ProxyType string `json:"proxy_type"`
|
||||||
|
RemoteAddr string `json:"remote_addr"`
|
||||||
|
}
|
||||||
|
|
|
@ -57,11 +57,11 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
|
||||||
for {
|
for {
|
||||||
n, remoteAddr, err := udpConn.ReadFromUDP(buf)
|
n, remoteAddr, err := udpConn.ReadFromUDP(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
udpConn.Close()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// buf[:n] will be encoded to string, so the bytes can be reused
|
// buf[:n] will be encoded to string, so the bytes can be reused
|
||||||
udpMsg := NewUdpPacket(buf[:n], nil, remoteAddr)
|
udpMsg := NewUdpPacket(buf[:n], nil, remoteAddr)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case sendCh <- udpMsg:
|
case sendCh <- udpMsg:
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# compile for version
|
# compile for version
|
||||||
#make
|
make
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "make error"
|
echo "make error"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -9,7 +9,7 @@ frp_version=`./bin/frps --version`
|
||||||
echo "build version: $frp_version"
|
echo "build version: $frp_version"
|
||||||
|
|
||||||
# cross_compiles
|
# cross_compiles
|
||||||
#make -f ./Makefile.cross-compiles
|
make -f ./Makefile.cross-compiles
|
||||||
|
|
||||||
rm -rf ./release/packages
|
rm -rf ./release/packages
|
||||||
mkdir -p ./release/packages
|
mkdir -p ./release/packages
|
||||||
|
|
|
@ -486,9 +486,16 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User info
|
||||||
|
userInfo := plugin.UserInfo{
|
||||||
|
User: ctl.loginMsg.User,
|
||||||
|
Metas: ctl.loginMsg.Metas,
|
||||||
|
RunId: ctl.runId,
|
||||||
|
}
|
||||||
|
|
||||||
// NewProxy will return a interface Proxy.
|
// NewProxy will return a interface Proxy.
|
||||||
// In fact it create different proxies by different proxy type, we just call run() here.
|
// In fact it create different proxies by different proxy type, we just call run() here.
|
||||||
pxy, err := proxy.NewProxy(ctl.ctx, ctl.runId, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
|
pxy, err := proxy.NewProxy(ctl.ctx, userInfo, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return remoteAddr, err
|
return remoteAddr, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/fatedier/frp/models/nathole"
|
"github.com/fatedier/frp/models/nathole"
|
||||||
|
plugin "github.com/fatedier/frp/models/plugin/server"
|
||||||
"github.com/fatedier/frp/server/group"
|
"github.com/fatedier/frp/server/group"
|
||||||
"github.com/fatedier/frp/server/ports"
|
"github.com/fatedier/frp/server/ports"
|
||||||
"github.com/fatedier/frp/utils/tcpmux"
|
"github.com/fatedier/frp/utils/tcpmux"
|
||||||
|
@ -33,6 +34,9 @@ type ResourceController struct {
|
||||||
// HTTP Group Controller
|
// HTTP Group Controller
|
||||||
HTTPGroupCtl *group.HTTPGroupController
|
HTTPGroupCtl *group.HTTPGroupController
|
||||||
|
|
||||||
|
// TCP Mux Group Controller
|
||||||
|
TcpMuxGroupCtl *group.TcpMuxGroupCtl
|
||||||
|
|
||||||
// Manage all tcp ports
|
// Manage all tcp ports
|
||||||
TcpPortManager *ports.PortManager
|
TcpPortManager *ports.PortManager
|
||||||
|
|
||||||
|
@ -50,4 +54,7 @@ type ResourceController struct {
|
||||||
|
|
||||||
// TcpMux HTTP CONNECT multiplexer
|
// TcpMux HTTP CONNECT multiplexer
|
||||||
TcpMuxHttpConnectMuxer *tcpmux.HttpConnectTcpMuxer
|
TcpMuxHttpConnectMuxer *tcpmux.HttpConnectTcpMuxer
|
||||||
|
|
||||||
|
// All server manager plugin
|
||||||
|
PluginManager *plugin.Manager
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,218 @@
|
||||||
|
// Copyright 2020 guylewin, guy@lewin.co.il
|
||||||
|
//
|
||||||
|
// 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 group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/consts"
|
||||||
|
"github.com/fatedier/frp/utils/tcpmux"
|
||||||
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
|
|
||||||
|
gerr "github.com/fatedier/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TcpMuxGroupCtl manage all TcpMuxGroups
|
||||||
|
type TcpMuxGroupCtl struct {
|
||||||
|
groups map[string]*TcpMuxGroup
|
||||||
|
|
||||||
|
// portManager is used to manage port
|
||||||
|
tcpMuxHttpConnectMuxer *tcpmux.HttpConnectTcpMuxer
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTcpMuxGroupCtl return a new TcpMuxGroupCtl
|
||||||
|
func NewTcpMuxGroupCtl(tcpMuxHttpConnectMuxer *tcpmux.HttpConnectTcpMuxer) *TcpMuxGroupCtl {
|
||||||
|
return &TcpMuxGroupCtl{
|
||||||
|
groups: make(map[string]*TcpMuxGroup),
|
||||||
|
tcpMuxHttpConnectMuxer: tcpMuxHttpConnectMuxer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen is the wrapper for TcpMuxGroup's Listen
|
||||||
|
// If there are no group, we will create one here
|
||||||
|
func (tmgc *TcpMuxGroupCtl) Listen(multiplexer string, group string, groupKey string,
|
||||||
|
domain string, ctx context.Context) (l net.Listener, err error) {
|
||||||
|
tmgc.mu.Lock()
|
||||||
|
tcpMuxGroup, ok := tmgc.groups[group]
|
||||||
|
if !ok {
|
||||||
|
tcpMuxGroup = NewTcpMuxGroup(tmgc)
|
||||||
|
tmgc.groups[group] = tcpMuxGroup
|
||||||
|
}
|
||||||
|
tmgc.mu.Unlock()
|
||||||
|
|
||||||
|
switch multiplexer {
|
||||||
|
case consts.HttpConnectTcpMultiplexer:
|
||||||
|
return tcpMuxGroup.HttpConnectListen(group, groupKey, domain, ctx)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unknown multiplexer [%s]", multiplexer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveGroup remove TcpMuxGroup from controller
|
||||||
|
func (tmgc *TcpMuxGroupCtl) RemoveGroup(group string) {
|
||||||
|
tmgc.mu.Lock()
|
||||||
|
defer tmgc.mu.Unlock()
|
||||||
|
delete(tmgc.groups, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TcpMuxGroup route connections to different proxies
|
||||||
|
type TcpMuxGroup struct {
|
||||||
|
group string
|
||||||
|
groupKey string
|
||||||
|
domain string
|
||||||
|
|
||||||
|
acceptCh chan net.Conn
|
||||||
|
index uint64
|
||||||
|
tcpMuxLn net.Listener
|
||||||
|
lns []*TcpMuxGroupListener
|
||||||
|
ctl *TcpMuxGroupCtl
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTcpMuxGroup return a new TcpMuxGroup
|
||||||
|
func NewTcpMuxGroup(ctl *TcpMuxGroupCtl) *TcpMuxGroup {
|
||||||
|
return &TcpMuxGroup{
|
||||||
|
lns: make([]*TcpMuxGroupListener, 0),
|
||||||
|
ctl: ctl,
|
||||||
|
acceptCh: make(chan net.Conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen will return a new TcpMuxGroupListener
|
||||||
|
// if TcpMuxGroup already has a listener, just add a new TcpMuxGroupListener to the queues
|
||||||
|
// otherwise, listen on the real address
|
||||||
|
func (tmg *TcpMuxGroup) HttpConnectListen(group string, groupKey string, domain string, context context.Context) (ln *TcpMuxGroupListener, err error) {
|
||||||
|
tmg.mu.Lock()
|
||||||
|
defer tmg.mu.Unlock()
|
||||||
|
if len(tmg.lns) == 0 {
|
||||||
|
// the first listener, listen on the real address
|
||||||
|
routeConfig := &vhost.VhostRouteConfig{
|
||||||
|
Domain: domain,
|
||||||
|
}
|
||||||
|
tcpMuxLn, errRet := tmg.ctl.tcpMuxHttpConnectMuxer.Listen(context, routeConfig)
|
||||||
|
if errRet != nil {
|
||||||
|
return nil, errRet
|
||||||
|
}
|
||||||
|
ln = newTcpMuxGroupListener(group, tmg, tcpMuxLn.Addr())
|
||||||
|
|
||||||
|
tmg.group = group
|
||||||
|
tmg.groupKey = groupKey
|
||||||
|
tmg.domain = domain
|
||||||
|
tmg.tcpMuxLn = tcpMuxLn
|
||||||
|
tmg.lns = append(tmg.lns, ln)
|
||||||
|
if tmg.acceptCh == nil {
|
||||||
|
tmg.acceptCh = make(chan net.Conn)
|
||||||
|
}
|
||||||
|
go tmg.worker()
|
||||||
|
} else {
|
||||||
|
// domain in the same group must be equal
|
||||||
|
if tmg.group != group || tmg.domain != domain {
|
||||||
|
return nil, ErrGroupParamsInvalid
|
||||||
|
}
|
||||||
|
if tmg.groupKey != groupKey {
|
||||||
|
return nil, ErrGroupAuthFailed
|
||||||
|
}
|
||||||
|
ln = newTcpMuxGroupListener(group, tmg, tmg.lns[0].Addr())
|
||||||
|
tmg.lns = append(tmg.lns, ln)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// worker is called when the real tcp listener has been created
|
||||||
|
func (tmg *TcpMuxGroup) worker() {
|
||||||
|
for {
|
||||||
|
c, err := tmg.tcpMuxLn.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = gerr.PanicToError(func() {
|
||||||
|
tmg.acceptCh <- c
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tmg *TcpMuxGroup) Accept() <-chan net.Conn {
|
||||||
|
return tmg.acceptCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseListener remove the TcpMuxGroupListener from the TcpMuxGroup
|
||||||
|
func (tmg *TcpMuxGroup) CloseListener(ln *TcpMuxGroupListener) {
|
||||||
|
tmg.mu.Lock()
|
||||||
|
defer tmg.mu.Unlock()
|
||||||
|
for i, tmpLn := range tmg.lns {
|
||||||
|
if tmpLn == ln {
|
||||||
|
tmg.lns = append(tmg.lns[:i], tmg.lns[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(tmg.lns) == 0 {
|
||||||
|
close(tmg.acceptCh)
|
||||||
|
tmg.tcpMuxLn.Close()
|
||||||
|
tmg.ctl.RemoveGroup(tmg.group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TcpMuxGroupListener
|
||||||
|
type TcpMuxGroupListener struct {
|
||||||
|
groupName string
|
||||||
|
group *TcpMuxGroup
|
||||||
|
|
||||||
|
addr net.Addr
|
||||||
|
closeCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTcpMuxGroupListener(name string, group *TcpMuxGroup, addr net.Addr) *TcpMuxGroupListener {
|
||||||
|
return &TcpMuxGroupListener{
|
||||||
|
groupName: name,
|
||||||
|
group: group,
|
||||||
|
addr: addr,
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept will accept connections from TcpMuxGroup
|
||||||
|
func (ln *TcpMuxGroupListener) Accept() (c net.Conn, err error) {
|
||||||
|
var ok bool
|
||||||
|
select {
|
||||||
|
case <-ln.closeCh:
|
||||||
|
return nil, ErrListenerClosed
|
||||||
|
case c, ok = <-ln.group.Accept():
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrListenerClosed
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *TcpMuxGroupListener) Addr() net.Addr {
|
||||||
|
return ln.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close close the listener
|
||||||
|
func (ln *TcpMuxGroupListener) Close() (err error) {
|
||||||
|
close(ln.closeCh)
|
||||||
|
|
||||||
|
// remove self from TcpMuxGroup
|
||||||
|
ln.group.CloseListener(ln)
|
||||||
|
return
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import (
|
||||||
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
plugin "github.com/fatedier/frp/models/plugin/server"
|
||||||
"github.com/fatedier/frp/server/controller"
|
"github.com/fatedier/frp/server/controller"
|
||||||
"github.com/fatedier/frp/server/metrics"
|
"github.com/fatedier/frp/server/metrics"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
@ -41,6 +42,8 @@ type Proxy interface {
|
||||||
GetConf() config.ProxyConf
|
GetConf() config.ProxyConf
|
||||||
GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error)
|
GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error)
|
||||||
GetUsedPortsNum() int
|
GetUsedPortsNum() int
|
||||||
|
GetResourceController() *controller.ResourceController
|
||||||
|
GetUserInfo() plugin.UserInfo
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +55,7 @@ type BaseProxy struct {
|
||||||
poolCount int
|
poolCount int
|
||||||
getWorkConnFn GetWorkConnFn
|
getWorkConnFn GetWorkConnFn
|
||||||
serverCfg config.ServerCommonConf
|
serverCfg config.ServerCommonConf
|
||||||
|
userInfo plugin.UserInfo
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
xl *xlog.Logger
|
xl *xlog.Logger
|
||||||
|
@ -70,6 +74,14 @@ func (pxy *BaseProxy) GetUsedPortsNum() int {
|
||||||
return pxy.usedPortsNum
|
return pxy.usedPortsNum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pxy *BaseProxy) GetResourceController() *controller.ResourceController {
|
||||||
|
return pxy.rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *BaseProxy) GetUserInfo() plugin.UserInfo {
|
||||||
|
return pxy.userInfo
|
||||||
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) Close() {
|
func (pxy *BaseProxy) Close() {
|
||||||
xl := xlog.FromContextSafe(pxy.ctx)
|
xl := xlog.FromContextSafe(pxy.ctx)
|
||||||
xl.Info("proxy closing")
|
xl.Info("proxy closing")
|
||||||
|
@ -154,7 +166,7 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(ctx context.Context, runId string, rc *controller.ResourceController, poolCount int,
|
func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.ResourceController, poolCount int,
|
||||||
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (pxy Proxy, err error) {
|
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (pxy Proxy, err error) {
|
||||||
|
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseInfo().ProxyName)
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseInfo().ProxyName)
|
||||||
|
@ -167,6 +179,7 @@ func NewProxy(ctx context.Context, runId string, rc *controller.ResourceControll
|
||||||
serverCfg: serverCfg,
|
serverCfg: serverCfg,
|
||||||
xl: xl,
|
xl: xl,
|
||||||
ctx: xlog.NewContext(ctx, xl),
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
|
userInfo: userInfo,
|
||||||
}
|
}
|
||||||
switch cfg := pxyConf.(type) {
|
switch cfg := pxyConf.(type) {
|
||||||
case *config.TcpProxyConf:
|
case *config.TcpProxyConf:
|
||||||
|
@ -206,6 +219,11 @@ func NewProxy(ctx context.Context, runId string, rc *controller.ResourceControll
|
||||||
BaseProxy: &basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
case *config.SudpProxyConf:
|
||||||
|
pxy = &SudpProxy{
|
||||||
|
BaseProxy: &basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return pxy, fmt.Errorf("proxy type not support")
|
return pxy, fmt.Errorf("proxy type not support")
|
||||||
}
|
}
|
||||||
|
@ -218,6 +236,20 @@ func HandleUserTcpConnection(pxy Proxy, userConn net.Conn, serverCfg config.Serv
|
||||||
xl := xlog.FromContextSafe(pxy.Context())
|
xl := xlog.FromContextSafe(pxy.Context())
|
||||||
defer userConn.Close()
|
defer userConn.Close()
|
||||||
|
|
||||||
|
// server plugin hook
|
||||||
|
rc := pxy.GetResourceController()
|
||||||
|
content := &plugin.NewUserConnContent{
|
||||||
|
User: pxy.GetUserInfo(),
|
||||||
|
ProxyName: pxy.GetName(),
|
||||||
|
ProxyType: pxy.GetConf().GetBaseInfo().ProxyType,
|
||||||
|
RemoteAddr: userConn.RemoteAddr().String(),
|
||||||
|
}
|
||||||
|
_, err := rc.PluginManager.NewUserConn(content)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("the user conn [%s] was rejected, err:%v", content.RemoteAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// try all connections from the pool
|
// try all connections from the pool
|
||||||
workConn, err := pxy.GetWorkConnFromPool(userConn.RemoteAddr(), userConn.LocalAddr())
|
workConn, err := pxy.GetWorkConnFromPool(userConn.RemoteAddr(), userConn.LocalAddr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright 2019 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 proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SudpProxy struct {
|
||||||
|
*BaseProxy
|
||||||
|
cfg *config.SudpProxyConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *SudpProxy) Run() (remoteAddr string, err error) {
|
||||||
|
xl := pxy.xl
|
||||||
|
|
||||||
|
listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pxy.listeners = append(pxy.listeners, listener)
|
||||||
|
xl.Info("sudp proxy custom listen success")
|
||||||
|
|
||||||
|
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *SudpProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *SudpProxy) Close() {
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
pxy.rc.VisitorManager.CloseListener(pxy.GetName())
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
|
@ -27,21 +28,24 @@ import (
|
||||||
type TcpMuxProxy struct {
|
type TcpMuxProxy struct {
|
||||||
*BaseProxy
|
*BaseProxy
|
||||||
cfg *config.TcpMuxProxyConf
|
cfg *config.TcpMuxProxyConf
|
||||||
|
|
||||||
realPort int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TcpMuxProxy) httpConnectListen(domain string, addrs []string) ([]string, error) {
|
func (pxy *TcpMuxProxy) httpConnectListen(domain string, addrs []string) (_ []string, err error) {
|
||||||
routeConfig := &vhost.VhostRouteConfig{
|
var l net.Listener
|
||||||
Domain: domain,
|
if pxy.cfg.Group != "" {
|
||||||
|
l, err = pxy.rc.TcpMuxGroupCtl.Listen(pxy.cfg.Multiplexer, pxy.cfg.Group, pxy.cfg.GroupKey, domain, pxy.ctx)
|
||||||
|
} else {
|
||||||
|
routeConfig := &vhost.VhostRouteConfig{
|
||||||
|
Domain: domain,
|
||||||
|
}
|
||||||
|
l, err = pxy.rc.TcpMuxHttpConnectMuxer.Listen(pxy.ctx, routeConfig)
|
||||||
}
|
}
|
||||||
l, err := pxy.rc.TcpMuxHttpConnectMuxer.Listen(pxy.ctx, routeConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pxy.xl.Info("tcpmux httpconnect multiplexer listens for host [%s]", routeConfig.Domain)
|
pxy.xl.Info("tcpmux httpconnect multiplexer listens for host [%s]", domain)
|
||||||
pxy.listeners = append(pxy.listeners, l)
|
pxy.listeners = append(pxy.listeners, l)
|
||||||
return append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.TcpMuxHttpConnectPort)), nil
|
return append(addrs, util.CanonicalAddr(domain, pxy.serverCfg.TcpMuxHttpConnectPort)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TcpMuxProxy) httpConnectRun() (remoteAddr string, err error) {
|
func (pxy *TcpMuxProxy) httpConnectRun() (remoteAddr string, err error) {
|
||||||
|
@ -89,7 +93,4 @@ func (pxy *TcpMuxProxy) GetConf() config.ProxyConf {
|
||||||
|
|
||||||
func (pxy *TcpMuxProxy) Close() {
|
func (pxy *TcpMuxProxy) Close() {
|
||||||
pxy.BaseProxy.Close()
|
pxy.BaseProxy.Close()
|
||||||
if pxy.cfg.Group == "" {
|
|
||||||
pxy.rc.TcpPortManager.Release(pxy.realPort)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,11 +114,29 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create tcpmux httpconnect multiplexer.
|
||||||
|
if cfg.TcpMuxHttpConnectPort > 0 {
|
||||||
|
var l net.Listener
|
||||||
|
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.TcpMuxHttpConnectPort))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Create server listener error, %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.rc.TcpMuxHttpConnectMuxer, err = tcpmux.NewHttpConnectTcpMuxer(l, vhostReadWriteTimeout)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Create vhost tcpMuxer error, %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("tcpmux httpconnect multiplexer listen on %s:%d", cfg.ProxyBindAddr, cfg.TcpMuxHttpConnectPort)
|
||||||
|
}
|
||||||
|
|
||||||
// Init all plugins
|
// Init all plugins
|
||||||
for name, options := range cfg.HTTPPlugins {
|
for name, options := range cfg.HTTPPlugins {
|
||||||
svr.pluginManager.Register(plugin.NewHTTPPluginOptions(options))
|
svr.pluginManager.Register(plugin.NewHTTPPluginOptions(options))
|
||||||
log.Info("plugin [%s] has been registered", name)
|
log.Info("plugin [%s] has been registered", name)
|
||||||
}
|
}
|
||||||
|
svr.rc.PluginManager = svr.pluginManager
|
||||||
|
|
||||||
// Init group controller
|
// Init group controller
|
||||||
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
||||||
|
@ -126,6 +144,9 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||||
// Init HTTP group controller
|
// Init HTTP group controller
|
||||||
svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
|
svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
|
||||||
|
|
||||||
|
// Init TCP mux group controller
|
||||||
|
svr.rc.TcpMuxGroupCtl = group.NewTcpMuxGroupCtl(svr.rc.TcpMuxHttpConnectMuxer)
|
||||||
|
|
||||||
// Init 404 not found page
|
// Init 404 not found page
|
||||||
vhost.NotFoundPagePath = cfg.Custom404Page
|
vhost.NotFoundPagePath = cfg.Custom404Page
|
||||||
|
|
||||||
|
@ -220,23 +241,6 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
|
||||||
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create tcpmux httpconnect multiplexer.
|
|
||||||
if cfg.TcpMuxHttpConnectPort > 0 {
|
|
||||||
var l net.Listener
|
|
||||||
l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.TcpMuxHttpConnectPort))
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Create server listener error, %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
svr.rc.TcpMuxHttpConnectMuxer, err = tcpmux.NewHttpConnectTcpMuxer(l, vhostReadWriteTimeout)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Create vhost tcpMuxer error, %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Info("tcpmux httpconnect multiplexer listen on %s:%d", cfg.ProxyBindAddr, cfg.TcpMuxHttpConnectPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
// frp tls listener
|
// frp tls listener
|
||||||
svr.tlsListener = svr.muxer.Listen(1, 1, func(data []byte) bool {
|
svr.tlsListener = svr.muxer.Listen(1, 1, func(data []byte) bool {
|
||||||
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
|
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
|
||||||
|
@ -296,6 +300,68 @@ func (svr *Service) Run() {
|
||||||
svr.HandleListener(svr.listener)
|
svr.HandleListener(svr.listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svr *Service) handleConnection(ctx context.Context, conn net.Conn) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rawMsg msg.Message
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
||||||
|
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
||||||
|
log.Trace("Failed to read message: %v", err)
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.Login:
|
||||||
|
// server plugin hook
|
||||||
|
content := &plugin.LoginContent{
|
||||||
|
Login: *m,
|
||||||
|
}
|
||||||
|
retContent, err := svr.pluginManager.Login(content)
|
||||||
|
if err == nil {
|
||||||
|
m = &retContent.Login
|
||||||
|
err = svr.RegisterControl(conn, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If login failed, send error message there.
|
||||||
|
// Otherwise send success message in control's work goroutine.
|
||||||
|
if err != nil {
|
||||||
|
xl.Warn("register control error: %v", err)
|
||||||
|
msg.WriteMsg(conn, &msg.LoginResp{
|
||||||
|
Version: version.Full(),
|
||||||
|
Error: util.GenerateResponseErrorString("register control error", err, svr.cfg.DetailedErrorsToClient),
|
||||||
|
})
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
case *msg.NewWorkConn:
|
||||||
|
if err := svr.RegisterWorkConn(conn, m); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
case *msg.NewVisitorConn:
|
||||||
|
if err = svr.RegisterVisitorConn(conn, m); err != nil {
|
||||||
|
xl.Warn("register visitor conn error: %v", err)
|
||||||
|
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
|
||||||
|
ProxyName: m.ProxyName,
|
||||||
|
Error: util.GenerateResponseErrorString("register visitor conn error", err, svr.cfg.DetailedErrorsToClient),
|
||||||
|
})
|
||||||
|
conn.Close()
|
||||||
|
} else {
|
||||||
|
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
|
||||||
|
ProxyName: m.ProxyName,
|
||||||
|
Error: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Warn("Error message type for the new connection [%s]", conn.RemoteAddr().String())
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (svr *Service) HandleListener(l net.Listener) {
|
func (svr *Service) HandleListener(l net.Listener) {
|
||||||
// Listen for incoming connections from client.
|
// Listen for incoming connections from client.
|
||||||
for {
|
for {
|
||||||
|
@ -306,7 +372,9 @@ func (svr *Service) HandleListener(l net.Listener) {
|
||||||
}
|
}
|
||||||
// inject xlog object into net.Conn context
|
// inject xlog object into net.Conn context
|
||||||
xl := xlog.New()
|
xl := xlog.New()
|
||||||
c = frpNet.NewContextConn(c, xlog.NewContext(context.Background(), xl))
|
ctx := context.Background()
|
||||||
|
|
||||||
|
c = frpNet.NewContextConn(c, xlog.NewContext(ctx, xl))
|
||||||
|
|
||||||
log.Trace("start check TLS connection...")
|
log.Trace("start check TLS connection...")
|
||||||
originConn := c
|
originConn := c
|
||||||
|
@ -319,63 +387,7 @@ func (svr *Service) HandleListener(l net.Listener) {
|
||||||
log.Trace("success check TLS connection")
|
log.Trace("success check TLS connection")
|
||||||
|
|
||||||
// Start a new goroutine for dealing connections.
|
// Start a new goroutine for dealing connections.
|
||||||
go func(frpConn net.Conn) {
|
go func(ctx context.Context, frpConn net.Conn) {
|
||||||
dealFn := func(conn net.Conn) {
|
|
||||||
var rawMsg msg.Message
|
|
||||||
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
|
||||||
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
|
||||||
log.Trace("Failed to read message: %v", err)
|
|
||||||
conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
conn.SetReadDeadline(time.Time{})
|
|
||||||
|
|
||||||
switch m := rawMsg.(type) {
|
|
||||||
case *msg.Login:
|
|
||||||
// server plugin hook
|
|
||||||
content := &plugin.LoginContent{
|
|
||||||
Login: *m,
|
|
||||||
}
|
|
||||||
retContent, err := svr.pluginManager.Login(content)
|
|
||||||
if err == nil {
|
|
||||||
m = &retContent.Login
|
|
||||||
err = svr.RegisterControl(conn, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If login failed, send error message there.
|
|
||||||
// Otherwise send success message in control's work goroutine.
|
|
||||||
if err != nil {
|
|
||||||
xl.Warn("register control error: %v", err)
|
|
||||||
msg.WriteMsg(conn, &msg.LoginResp{
|
|
||||||
Version: version.Full(),
|
|
||||||
Error: util.GenerateResponseErrorString("register control error", err, svr.cfg.DetailedErrorsToClient),
|
|
||||||
})
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
case *msg.NewWorkConn:
|
|
||||||
if err := svr.RegisterWorkConn(conn, m); err != nil {
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
case *msg.NewVisitorConn:
|
|
||||||
if err = svr.RegisterVisitorConn(conn, m); err != nil {
|
|
||||||
xl.Warn("register visitor conn error: %v", err)
|
|
||||||
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
|
|
||||||
ProxyName: m.ProxyName,
|
|
||||||
Error: util.GenerateResponseErrorString("register visitor conn error", err, svr.cfg.DetailedErrorsToClient),
|
|
||||||
})
|
|
||||||
conn.Close()
|
|
||||||
} else {
|
|
||||||
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
|
|
||||||
ProxyName: m.ProxyName,
|
|
||||||
Error: "",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Warn("Error message type for the new connection [%s]", conn.RemoteAddr().String())
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if svr.cfg.TcpMux {
|
if svr.cfg.TcpMux {
|
||||||
fmuxCfg := fmux.DefaultConfig()
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
fmuxCfg.KeepAliveInterval = 20 * time.Second
|
||||||
|
@ -394,12 +406,12 @@ func (svr *Service) HandleListener(l net.Listener) {
|
||||||
session.Close()
|
session.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go dealFn(stream)
|
go svr.handleConnection(ctx, stream)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dealFn(frpConn)
|
svr.handleConnection(ctx, frpConn)
|
||||||
}
|
}
|
||||||
}(c)
|
}(ctx, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,12 @@ local_port = 10701
|
||||||
use_encryption = true
|
use_encryption = true
|
||||||
use_compression = true
|
use_compression = true
|
||||||
|
|
||||||
|
[sudp]
|
||||||
|
type = sudp
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 10702
|
||||||
|
|
||||||
[web01]
|
[web01]
|
||||||
type = http
|
type = http
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
|
|
|
@ -23,3 +23,12 @@ bind_addr = 127.0.0.1
|
||||||
bind_port = 10905
|
bind_port = 10905
|
||||||
use_encryption = true
|
use_encryption = true
|
||||||
use_compression = true
|
use_compression = true
|
||||||
|
|
||||||
|
|
||||||
|
[sudp_visitor]
|
||||||
|
type = sudp
|
||||||
|
role = visitor
|
||||||
|
server_name = sudp
|
||||||
|
sk = abcdefg
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 10816
|
||||||
|
|
|
@ -118,6 +118,16 @@ func TestStcp(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSudp(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
// Normal
|
||||||
|
addr := fmt.Sprintf("127.0.0.1:%d", consts.TEST_SUDP_FRP_PORT)
|
||||||
|
res, err := util.SendUdpMsg(addr, consts.TEST_SUDP_ECHO_STR)
|
||||||
|
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(consts.TEST_SUDP_ECHO_STR, res)
|
||||||
|
}
|
||||||
|
|
||||||
func TestHttp(t *testing.T) {
|
func TestHttp(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
// web01
|
// web01
|
||||||
|
|
|
@ -46,6 +46,9 @@ var (
|
||||||
TEST_STCP_EC_FRP_PORT int = 10905
|
TEST_STCP_EC_FRP_PORT int = 10905
|
||||||
TEST_STCP_ECHO_STR string = "stcp type:" + TEST_STR
|
TEST_STCP_ECHO_STR string = "stcp type:" + TEST_STR
|
||||||
|
|
||||||
|
TEST_SUDP_FRP_PORT int = 10816
|
||||||
|
TEST_SUDP_ECHO_STR string = "sudp type:" + TEST_STR
|
||||||
|
|
||||||
ProxyTcpPortNotAllowed string = "tcp_port_not_allowed"
|
ProxyTcpPortNotAllowed string = "tcp_port_not_allowed"
|
||||||
ProxyTcpPortUnavailable string = "tcp_port_unavailable"
|
ProxyTcpPortUnavailable string = "tcp_port_unavailable"
|
||||||
ProxyTcpPortNormal string = "tcp_port_normal"
|
ProxyTcpPortNormal string = "tcp_port_normal"
|
||||||
|
|
|
@ -71,6 +71,11 @@ func GetProxyStatus(statusAddr string, user string, passwd string, name string)
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, s := range allStatus.Sudp {
|
||||||
|
if s.Name == name {
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return status, errors.New("no proxy status found")
|
return status, errors.New("no proxy status found")
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.32.1"
|
var version string = "0.33.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
|
Loading…
Reference in New Issue