mirror of
https://github.com/fatedier/frp.git
synced 2026-10-19 18:13:28 +08:00
A constructor that binds a port before its fallible preparation leaks the listener when a later step returns an error, and no one can close it because the returned service is nil. pkg/util/http: load the TLS keypair and build the auth middleware before net.Listen, so binding is the last step that can run and a failure leaves no bound port behind. server: create the web server after auth, the tcpmux, main, kcp, quic, ssh gateway and vhost listeners, and the nat hole controller, following what frpc already does. If the web server itself fails, close the resources created so far before returning. The port managers start their cleanup workers on creation, so they are created after every fallible step too, and the metrics collectors keep being enabled only when the web server is configured. Add regression tests for all three failure paths.
953 lines
28 KiB
Go
953 lines
28 KiB
Go
// 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 server
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/fatedier/golib/crypto"
|
|
"github.com/fatedier/golib/net/mux"
|
|
fmux "github.com/fatedier/yamux"
|
|
quic "github.com/quic-go/quic-go"
|
|
"github.com/samber/lo"
|
|
|
|
"github.com/fatedier/frp/pkg/auth"
|
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
|
modelmetrics "github.com/fatedier/frp/pkg/metrics"
|
|
"github.com/fatedier/frp/pkg/msg"
|
|
"github.com/fatedier/frp/pkg/nathole"
|
|
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
|
"github.com/fatedier/frp/pkg/proto/wire"
|
|
"github.com/fatedier/frp/pkg/ssh"
|
|
"github.com/fatedier/frp/pkg/transport"
|
|
httppkg "github.com/fatedier/frp/pkg/util/http"
|
|
"github.com/fatedier/frp/pkg/util/log"
|
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
|
"github.com/fatedier/frp/pkg/util/tcpmux"
|
|
"github.com/fatedier/frp/pkg/util/util"
|
|
"github.com/fatedier/frp/pkg/util/version"
|
|
"github.com/fatedier/frp/pkg/util/vhost"
|
|
"github.com/fatedier/frp/pkg/util/xlog"
|
|
"github.com/fatedier/frp/server/controller"
|
|
"github.com/fatedier/frp/server/group"
|
|
"github.com/fatedier/frp/server/ports"
|
|
"github.com/fatedier/frp/server/proxy"
|
|
"github.com/fatedier/frp/server/registry"
|
|
"github.com/fatedier/frp/server/visitor"
|
|
)
|
|
|
|
const (
|
|
connReadTimeout time.Duration = 10 * time.Second
|
|
connWriteTimeout time.Duration = 5 * time.Second
|
|
vhostReadWriteTimeout time.Duration = 30 * time.Second
|
|
)
|
|
|
|
var errControlReplaced = errors.New("control was replaced during login")
|
|
|
|
func init() {
|
|
crypto.DefaultSalt = "frp"
|
|
// Disable quic-go's receive buffer warning.
|
|
os.Setenv("QUIC_GO_DISABLE_RECEIVE_BUFFER_WARNING", "true")
|
|
// Disable quic-go's ECN support by default. It may cause issues on certain operating systems.
|
|
if os.Getenv("QUIC_GO_DISABLE_ECN") == "" {
|
|
os.Setenv("QUIC_GO_DISABLE_ECN", "true")
|
|
}
|
|
}
|
|
|
|
// Server service
|
|
type Service struct {
|
|
// Dispatch connections to different handlers listen on same port
|
|
muxer *mux.Mux
|
|
|
|
// Accept connections from client
|
|
listener net.Listener
|
|
|
|
// Accept connections using kcp
|
|
kcpListener net.Listener
|
|
|
|
// Accept connections using quic
|
|
quicListener *quic.Listener
|
|
|
|
// Accept connections using websocket
|
|
websocketListener net.Listener
|
|
|
|
// Accept frp tls connections
|
|
tlsListener net.Listener
|
|
|
|
// Accept pipe connections from ssh tunnel gateway
|
|
sshTunnelListener *netpkg.InternalListener
|
|
|
|
// Manage all controllers
|
|
ctlManager *ControlManager
|
|
|
|
// Track logical clients keyed by user.clientID (runID fallback when raw clientID is empty).
|
|
clientRegistry *registry.ClientRegistry
|
|
|
|
// Manage all proxies
|
|
pxyManager *proxy.Manager
|
|
|
|
// Manage all plugins
|
|
pluginManager *plugin.Manager
|
|
|
|
// HTTP vhost router
|
|
httpVhostRouter *vhost.Routers
|
|
|
|
// All resource managers and controllers
|
|
rc *controller.ResourceController
|
|
|
|
// web server for dashboard UI and apis
|
|
webServer *httppkg.Server
|
|
// HTTP vhost resources are started and owned by NewService.
|
|
httpVhostServer *http.Server
|
|
httpVhostListener net.Listener
|
|
|
|
sshTunnelGateway *ssh.Gateway
|
|
|
|
// Auth runtime and encryption materials
|
|
auth *auth.ServerAuth
|
|
|
|
tlsConfig *tls.Config
|
|
|
|
cfg *v1.ServerConfig
|
|
|
|
// service context
|
|
ctx context.Context
|
|
// call cancel to stop service
|
|
cancel context.CancelFunc
|
|
lifecycleMu sync.Mutex
|
|
closed bool
|
|
closeOnce sync.Once
|
|
}
|
|
|
|
func NewService(cfg *v1.ServerConfig) (*Service, error) {
|
|
tlsConfig, err := transport.NewServerTLSConfig(
|
|
cfg.Transport.TLS.CertFile,
|
|
cfg.Transport.TLS.KeyFile,
|
|
cfg.Transport.TLS.TrustedCaFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
authRuntime, err := auth.BuildServerAuth(&cfg.Auth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
clientRegistry := registry.NewClientRegistry()
|
|
svr := &Service{
|
|
ctlManager: NewControlManager(clientRegistry),
|
|
clientRegistry: clientRegistry,
|
|
pxyManager: proxy.NewManager(),
|
|
pluginManager: plugin.NewManager(),
|
|
rc: &controller.ResourceController{
|
|
VisitorManager: visitor.NewManager(),
|
|
},
|
|
sshTunnelListener: netpkg.NewInternalListener(),
|
|
httpVhostRouter: vhost.NewRouters(),
|
|
auth: authRuntime,
|
|
tlsConfig: tlsConfig,
|
|
cfg: cfg,
|
|
ctx: context.Background(),
|
|
}
|
|
|
|
// Create tcpmux httpconnect multiplexer.
|
|
if cfg.TCPMuxHTTPConnectPort > 0 {
|
|
var l net.Listener
|
|
address := net.JoinHostPort(cfg.ProxyBindAddr, strconv.Itoa(cfg.TCPMuxHTTPConnectPort))
|
|
l, err = net.Listen("tcp", address)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create server listener error, %v", err)
|
|
}
|
|
|
|
svr.rc.TCPMuxHTTPConnectMuxer, err = tcpmux.NewHTTPConnectTCPMuxer(l, cfg.TCPMuxPassthrough, vhostReadWriteTimeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create vhost tcpMuxer error, %v", err)
|
|
}
|
|
log.Infof("tcpmux httpconnect multiplexer listen on %s, passthrough: %v", address, cfg.TCPMuxPassthrough)
|
|
}
|
|
|
|
// Init all plugins
|
|
for _, p := range cfg.HTTPPlugins {
|
|
svr.pluginManager.Register(plugin.NewHTTPPluginOptions(p))
|
|
log.Infof("plugin [%s] has been registered", p.Name)
|
|
}
|
|
svr.rc.PluginManager = svr.pluginManager
|
|
|
|
// Init HTTP group controller
|
|
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
|
|
vhost.NotFoundPagePath = cfg.Custom404Page
|
|
|
|
var (
|
|
httpMuxOn bool
|
|
httpsMuxOn bool
|
|
)
|
|
if cfg.BindAddr == cfg.ProxyBindAddr {
|
|
if cfg.BindPort == cfg.VhostHTTPPort {
|
|
httpMuxOn = true
|
|
}
|
|
if cfg.BindPort == cfg.VhostHTTPSPort {
|
|
httpsMuxOn = true
|
|
}
|
|
}
|
|
|
|
// Listen for accepting connections from client.
|
|
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.BindPort))
|
|
ln, err := net.Listen("tcp", address)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create server listener error, %v", err)
|
|
}
|
|
|
|
svr.muxer = mux.NewMux(ln)
|
|
svr.muxer.SetKeepAlive(time.Duration(cfg.Transport.TCPKeepAlive) * time.Second)
|
|
go func() {
|
|
_ = svr.muxer.Serve()
|
|
}()
|
|
ln = svr.muxer.DefaultListener()
|
|
|
|
svr.listener = ln
|
|
log.Infof("frps tcp listen on %s", address)
|
|
|
|
// Listen for accepting connections from client using kcp protocol.
|
|
if cfg.KCPBindPort > 0 {
|
|
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.KCPBindPort))
|
|
svr.kcpListener, err = netpkg.ListenKcp(address)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listen on kcp udp address %s error: %v", address, err)
|
|
}
|
|
log.Infof("frps kcp listen on udp %s", address)
|
|
}
|
|
|
|
if cfg.QUICBindPort > 0 {
|
|
address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.QUICBindPort))
|
|
quicTLSCfg := tlsConfig.Clone()
|
|
quicTLSCfg.NextProtos = []string{"frp"}
|
|
svr.quicListener, err = quic.ListenAddr(address, quicTLSCfg, &quic.Config{
|
|
MaxIdleTimeout: time.Duration(cfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
|
|
MaxIncomingStreams: int64(cfg.Transport.QUIC.MaxIncomingStreams),
|
|
KeepAlivePeriod: time.Duration(cfg.Transport.QUIC.KeepalivePeriod) * time.Second,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listen on quic udp address %s error: %v", address, err)
|
|
}
|
|
log.Infof("frps quic listen on %s", address)
|
|
}
|
|
|
|
if cfg.SSHTunnelGateway.BindPort > 0 {
|
|
sshGateway, err := ssh.NewGateway(cfg.SSHTunnelGateway, cfg.BindAddr, svr.sshTunnelListener)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create ssh gateway error: %v", err)
|
|
}
|
|
svr.sshTunnelGateway = sshGateway
|
|
log.Infof("frps sshTunnelGateway listen on port %d", cfg.SSHTunnelGateway.BindPort)
|
|
}
|
|
|
|
// Listen for accepting connections from client using websocket protocol.
|
|
websocketPrefix := []byte("GET " + netpkg.FrpWebsocketPath)
|
|
websocketLn := svr.muxer.Listen(0, uint32(len(websocketPrefix)), func(data []byte) bool {
|
|
return bytes.Equal(data, websocketPrefix)
|
|
})
|
|
svr.websocketListener = netpkg.NewWebsocketListener(websocketLn)
|
|
|
|
// Create http vhost muxer.
|
|
if cfg.VhostHTTPPort > 0 {
|
|
rp := vhost.NewHTTPReverseProxy(vhost.HTTPReverseProxyOptions{
|
|
ResponseHeaderTimeoutS: cfg.VhostHTTPTimeout,
|
|
}, svr.httpVhostRouter)
|
|
svr.rc.HTTPReverseProxy = rp
|
|
|
|
address := net.JoinHostPort(cfg.ProxyBindAddr, strconv.Itoa(cfg.VhostHTTPPort))
|
|
protocols := new(http.Protocols)
|
|
protocols.SetHTTP1(true)
|
|
protocols.SetUnencryptedHTTP2(true)
|
|
server := &http.Server{
|
|
Addr: address,
|
|
Handler: rp,
|
|
ReadHeaderTimeout: 60 * time.Second,
|
|
Protocols: protocols,
|
|
}
|
|
var l net.Listener
|
|
if httpMuxOn {
|
|
l = svr.muxer.ListenHTTP(1)
|
|
} else {
|
|
l, err = net.Listen("tcp", address)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create vhost http listener error, %v", err)
|
|
}
|
|
}
|
|
svr.httpVhostServer = server
|
|
svr.httpVhostListener = l
|
|
go func() {
|
|
_ = server.Serve(l)
|
|
}()
|
|
log.Infof("http service listen on %s", address)
|
|
}
|
|
|
|
// Create https vhost muxer.
|
|
if cfg.VhostHTTPSPort > 0 {
|
|
var l net.Listener
|
|
if httpsMuxOn {
|
|
l = svr.muxer.ListenHTTPS(1)
|
|
} else {
|
|
address := net.JoinHostPort(cfg.ProxyBindAddr, strconv.Itoa(cfg.VhostHTTPSPort))
|
|
l, err = net.Listen("tcp", address)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create server listener error, %v", err)
|
|
}
|
|
log.Infof("https service listen on %s", address)
|
|
}
|
|
|
|
svr.rc.VhostHTTPSMuxer, err = vhost.NewHTTPSMuxer(l, vhostReadWriteTimeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create vhost httpsMuxer error, %v", err)
|
|
}
|
|
|
|
// Init HTTPS group controller after HTTPSMuxer is created
|
|
svr.rc.HTTPSGroupCtl = group.NewHTTPSGroupController(svr.rc.VhostHTTPSMuxer)
|
|
}
|
|
|
|
// frp tls listener
|
|
svr.tlsListener = svr.muxer.Listen(2, 1, func(data []byte) bool {
|
|
// tls first byte can be 0x16 only when vhost https port is not same with bind port
|
|
return int(data[0]) == netpkg.FRPTLSHeadByte || int(data[0]) == 0x16
|
|
})
|
|
|
|
// Create nat hole controller.
|
|
nc, err := nathole.NewController(time.Duration(cfg.NatHoleAnalysisDataReserveHours) * time.Hour)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create nat hole controller error, %v", err)
|
|
}
|
|
svr.rc.NatHoleController = nc
|
|
|
|
// Create the web server after all fallible steps so its listener is not
|
|
// leaked when an earlier error causes NewService to return.
|
|
if cfg.WebServer.Port > 0 {
|
|
ws, err := httppkg.NewServer(cfg.WebServer)
|
|
if err != nil {
|
|
// All other listeners are already created and must be released on failure.
|
|
_ = svr.Close()
|
|
return nil, err
|
|
}
|
|
svr.webServer = ws
|
|
|
|
modelmetrics.EnableMem()
|
|
if cfg.EnablePrometheus {
|
|
modelmetrics.EnablePrometheus()
|
|
}
|
|
}
|
|
|
|
// Start port-manager cleanup workers only after all fallible steps have succeeded.
|
|
svr.rc.TCPPortManager = ports.NewManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts)
|
|
svr.rc.UDPPortManager = ports.NewManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts)
|
|
svr.rc.TCPGroupCtl = group.NewTCPGroupCtl(svr.rc.TCPPortManager)
|
|
|
|
if svr.webServer != nil {
|
|
svr.webServer.RouteRegister(svr.registerRouteHandlers)
|
|
}
|
|
return svr, nil
|
|
}
|
|
|
|
func (svr *Service) Run(ctx context.Context) {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
svr.lifecycleMu.Lock()
|
|
if svr.closed {
|
|
svr.lifecycleMu.Unlock()
|
|
cancel()
|
|
return
|
|
}
|
|
svr.ctx = ctx
|
|
svr.cancel = cancel
|
|
svr.lifecycleMu.Unlock()
|
|
defer svr.Close()
|
|
if ctx.Err() != nil {
|
|
return
|
|
}
|
|
// Cancellation must unblock the synchronous main listener's Accept.
|
|
stopClose := context.AfterFunc(ctx, func() { _ = svr.Close() })
|
|
defer stopClose()
|
|
|
|
// run dashboard web server.
|
|
if svr.webServer != nil {
|
|
go func() {
|
|
log.Infof("dashboard listen on %s", svr.webServer.Address())
|
|
if err := svr.webServer.Run(); err != nil {
|
|
log.Warnf("dashboard server exit with error: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
go svr.HandleListener(svr.sshTunnelListener, true)
|
|
|
|
if svr.kcpListener != nil {
|
|
go svr.HandleListener(svr.kcpListener, false)
|
|
}
|
|
if svr.quicListener != nil {
|
|
go svr.HandleQUICListener(svr.quicListener)
|
|
}
|
|
go svr.HandleListener(svr.websocketListener, false)
|
|
go svr.HandleListener(svr.tlsListener, false)
|
|
|
|
if svr.rc.NatHoleController != nil {
|
|
go svr.rc.NatHoleController.CleanWorker(svr.ctx)
|
|
}
|
|
|
|
if svr.sshTunnelGateway != nil {
|
|
go svr.sshTunnelGateway.Run()
|
|
}
|
|
|
|
svr.HandleListener(svr.listener, false)
|
|
|
|
<-svr.ctx.Done()
|
|
}
|
|
|
|
func (svr *Service) Close() error {
|
|
svr.lifecycleMu.Lock()
|
|
svr.closed = true
|
|
cancel := svr.cancel
|
|
svr.lifecycleMu.Unlock()
|
|
if cancel != nil {
|
|
cancel()
|
|
}
|
|
svr.closeOnce.Do(svr.closeResources)
|
|
return nil
|
|
}
|
|
|
|
func (svr *Service) closeResources() {
|
|
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.sshTunnelListener != nil {
|
|
svr.sshTunnelListener.Close()
|
|
}
|
|
if svr.listener != nil {
|
|
svr.listener.Close()
|
|
}
|
|
if svr.webServer != nil {
|
|
svr.webServer.Close()
|
|
}
|
|
if svr.httpVhostServer != nil {
|
|
svr.httpVhostServer.Close()
|
|
// Close even if Serve has not registered its listener yet.
|
|
svr.httpVhostListener.Close()
|
|
}
|
|
if svr.sshTunnelGateway != nil {
|
|
svr.sshTunnelGateway.Close()
|
|
}
|
|
svr.rc.Close()
|
|
svr.muxer.Close()
|
|
svr.ctlManager.Close()
|
|
}
|
|
|
|
func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, internal bool) {
|
|
xl := xlog.FromContextSafe(ctx)
|
|
|
|
acceptedConn, err := svr.acceptConnection(ctx, conn)
|
|
if err != nil {
|
|
log.Tracef("failed to accept frp connection: %v", err)
|
|
conn.Close()
|
|
return
|
|
}
|
|
conn = acceptedConn.conn
|
|
|
|
switch m := acceptedConn.firstMsg.(type) {
|
|
case *msg.Login:
|
|
// server plugin hook
|
|
content := &plugin.LoginContent{
|
|
Login: *m,
|
|
ClientAddress: conn.RemoteAddr().String(),
|
|
}
|
|
retContent, err := svr.pluginManager.Login(content)
|
|
var ctl *Control
|
|
if err == nil {
|
|
m = &retContent.Login
|
|
controlConn := acceptedConn.conn
|
|
if !internal {
|
|
var controlRW io.ReadWriter
|
|
controlRW, err = acceptedConn.newControlReadWriter(conn, svr.auth.EncryptionKey())
|
|
if err == nil {
|
|
controlConn = acceptedConn.messageConnFor(controlRW)
|
|
}
|
|
}
|
|
if err == nil {
|
|
ctl, err = svr.RegisterControl(controlConn, m, internal, acceptedConn.wireProtocol, acceptedConn.udpPacketCodec)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
xl.Warnf("register control error: %v", err)
|
|
if ctl != nil {
|
|
svr.ctlManager.Remove(ctl)
|
|
}
|
|
if writeErr := writeWithDeadline(conn, connWriteTimeout, func() error {
|
|
return acceptedConn.conn.WriteMsg(&msg.LoginResp{
|
|
Version: version.Full(),
|
|
Error: util.GenerateResponseErrorString("register control error", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)),
|
|
})
|
|
}); writeErr != nil {
|
|
xl.Warnf("write login error response error: %v", writeErr)
|
|
}
|
|
if ctl != nil {
|
|
_ = ctl.Close()
|
|
} else {
|
|
conn.Close()
|
|
}
|
|
return
|
|
}
|
|
if err = svr.completeControlLogin(ctl, func() error {
|
|
return writeWithDeadline(conn, connWriteTimeout, func() error {
|
|
return acceptedConn.conn.WriteMsg(&msg.LoginResp{
|
|
Version: version.Full(),
|
|
RunID: ctl.runID,
|
|
Error: "",
|
|
})
|
|
})
|
|
}); err != nil {
|
|
xl.Warnf("complete control login error: %v", err)
|
|
svr.ctlManager.Remove(ctl)
|
|
_ = ctl.Close()
|
|
return
|
|
}
|
|
case *msg.NewWorkConn:
|
|
if err := svr.RegisterWorkConn(
|
|
acceptedConn.conn,
|
|
m,
|
|
acceptedConn.wireProtocol,
|
|
acceptedConn.clientHelloPresent,
|
|
); err != nil {
|
|
_ = acceptedConn.conn.WriteMsg(&msg.StartWorkConn{
|
|
Error: util.GenerateResponseErrorString("invalid NewWorkConn", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)),
|
|
})
|
|
conn.Close()
|
|
}
|
|
case *msg.NewVisitorConn:
|
|
if err = svr.RegisterVisitorConn(conn, m, acceptedConn.wireProtocol); err != nil {
|
|
xl.Warnf("register visitor conn error: %v", err)
|
|
_ = acceptedConn.conn.WriteMsg(&msg.NewVisitorConnResp{
|
|
ProxyName: m.ProxyName,
|
|
Error: util.GenerateResponseErrorString("register visitor conn error", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)),
|
|
})
|
|
conn.Close()
|
|
} else {
|
|
_ = acceptedConn.conn.WriteMsg(&msg.NewVisitorConnResp{
|
|
ProxyName: m.ProxyName,
|
|
Error: "",
|
|
})
|
|
}
|
|
default:
|
|
log.Warnf("error message type for the new connection [%s]", conn.RemoteAddr().String())
|
|
conn.Close()
|
|
}
|
|
}
|
|
|
|
func (svr *Service) completeControlLogin(ctl *Control, writeSuccess func() error) error {
|
|
committed, err := svr.ctlManager.completeLogin(ctl, writeSuccess)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !committed {
|
|
return errControlReplaced
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type acceptedConnection struct {
|
|
conn *msg.Conn
|
|
wireProtocol string
|
|
clientHelloPresent bool
|
|
udpPacketCodec string
|
|
cryptoContext *wire.CryptoContext
|
|
firstMsg msg.Message
|
|
}
|
|
|
|
func (svr *Service) acceptConnection(ctx context.Context, conn net.Conn) (*acceptedConnection, error) {
|
|
_ = conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
|
checkedConn, isV2, err := wire.CheckMagic(conn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read wire protocol magic: %w", err)
|
|
}
|
|
|
|
wireProtocol := wire.ProtocolV1
|
|
if isV2 {
|
|
wireProtocol = wire.ProtocolV2
|
|
}
|
|
|
|
conn = netpkg.NewContextConn(ctx, checkedConn)
|
|
acceptedConn := &acceptedConnection{wireProtocol: wireProtocol}
|
|
if isV2 {
|
|
wireConn := wire.NewConn(conn)
|
|
rw := msg.NewV2ReadWriterWithConn(wireConn)
|
|
acceptedConn.conn = msg.NewConn(conn, rw)
|
|
acceptedConn.firstMsg, err = acceptedConn.readFirstV2Msg(conn, wireConn)
|
|
} else {
|
|
rw := msg.NewV1ReadWriter(conn)
|
|
acceptedConn.conn = msg.NewConn(conn, rw)
|
|
acceptedConn.firstMsg, err = acceptedConn.conn.ReadMsg()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_ = conn.SetReadDeadline(time.Time{})
|
|
return acceptedConn, nil
|
|
}
|
|
|
|
func writeWithDeadline(conn net.Conn, timeout time.Duration, writeFn func() error) error {
|
|
_ = conn.SetWriteDeadline(time.Now().Add(timeout))
|
|
defer func() {
|
|
_ = conn.SetWriteDeadline(time.Time{})
|
|
}()
|
|
return writeFn()
|
|
}
|
|
|
|
func (ac *acceptedConnection) messageConnFor(rw io.ReadWriter) *msg.Conn {
|
|
return msg.NewConn(ac.conn, msg.NewReadWriter(rw, ac.wireProtocol))
|
|
}
|
|
|
|
func (ac *acceptedConnection) newControlReadWriter(rw io.ReadWriter, key []byte) (io.ReadWriter, error) {
|
|
if ac.wireProtocol == wire.ProtocolV2 {
|
|
if ac.cryptoContext == nil {
|
|
return nil, fmt.Errorf("missing v2 crypto negotiation")
|
|
}
|
|
return netpkg.NewAEADCryptoReadWriter(
|
|
rw,
|
|
key,
|
|
netpkg.AEADCryptoRoleServer,
|
|
ac.cryptoContext.Algorithm,
|
|
ac.cryptoContext.TranscriptHash,
|
|
)
|
|
}
|
|
return netpkg.NewCryptoReadWriter(rw, key)
|
|
}
|
|
|
|
func (ac *acceptedConnection) readFirstV2Msg(conn net.Conn, wireConn *wire.Conn) (msg.Message, error) {
|
|
frame, err := wireConn.ReadFrame()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read v2 frame: %w", err)
|
|
}
|
|
if frame.Type == wire.FrameTypeClientHello {
|
|
ac.clientHelloPresent = true
|
|
if err := ac.handleClientHello(conn, wireConn, frame); err != nil {
|
|
return nil, err
|
|
}
|
|
frame, err = wireConn.ReadFrame()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read first v2 message frame: %w", err)
|
|
}
|
|
}
|
|
|
|
m, err := msg.DecodeV2MessageFrame(frame)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode v2 message: %w", err)
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func (ac *acceptedConnection) handleClientHello(conn net.Conn, wireConn *wire.Conn, frame *wire.Frame) error {
|
|
var hello wire.ClientHello
|
|
if err := wireConn.UnmarshalFrame(frame, &hello); err != nil {
|
|
return fmt.Errorf("decode ClientHello: %w", err)
|
|
}
|
|
|
|
serverHello, err := wire.NewServerHello(hello)
|
|
if err != nil {
|
|
serverHello = wire.DefaultServerHello()
|
|
serverHello.Error = err.Error()
|
|
if writeErr := writeWithDeadline(conn, connWriteTimeout, func() error {
|
|
return wireConn.WriteJSONFrame(wire.FrameTypeServerHello, serverHello)
|
|
}); writeErr != nil {
|
|
return fmt.Errorf("%w; write ServerHello error: %v", err, writeErr)
|
|
}
|
|
return err
|
|
}
|
|
serverHelloFrame, err := wire.NewJSONFrame(wire.FrameTypeServerHello, serverHello)
|
|
if err != nil {
|
|
return fmt.Errorf("encode ServerHello: %w", err)
|
|
}
|
|
cryptoContext := wire.NewCryptoContext(
|
|
serverHello.Selected.Crypto.Algorithm,
|
|
frame.Payload,
|
|
serverHelloFrame.Payload,
|
|
)
|
|
if err := writeWithDeadline(conn, connWriteTimeout, func() error {
|
|
return wireConn.WriteFrame(serverHelloFrame)
|
|
}); err != nil {
|
|
return fmt.Errorf("write ServerHello: %w", err)
|
|
}
|
|
ac.cryptoContext = cryptoContext
|
|
ac.udpPacketCodec = serverHello.Selected.Message.UDPPacketCodec
|
|
return nil
|
|
}
|
|
|
|
// HandleListener accepts connections from client and call handleConnection to handle them.
|
|
// If internal is true, it means that this listener is used for internal communication like ssh tunnel gateway.
|
|
// TODO(fatedier): Pass some parameters of listener/connection through context to avoid passing too many parameters.
|
|
func (svr *Service) HandleListener(l net.Listener, internal bool) {
|
|
// Listen for incoming connections from client.
|
|
for {
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
log.Warnf("listener for incoming connections from client closed")
|
|
return
|
|
}
|
|
// inject xlog object into net.Conn context
|
|
xl := xlog.New()
|
|
ctx := context.Background()
|
|
|
|
c = netpkg.NewContextConn(xlog.NewContext(ctx, xl), c)
|
|
|
|
if !internal {
|
|
log.Tracef("start check TLS connection...")
|
|
originConn := c
|
|
forceTLS := svr.cfg.Transport.TLS.Force
|
|
var isTLS, custom bool
|
|
c, isTLS, custom, err = netpkg.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, forceTLS, connReadTimeout)
|
|
if err != nil {
|
|
log.Warnf("checkAndEnableTLSServerConnWithTimeout error: %v", err)
|
|
originConn.Close()
|
|
continue
|
|
}
|
|
log.Tracef("check TLS connection success, isTLS: %v custom: %v internal: %v", isTLS, custom, internal)
|
|
}
|
|
|
|
// Start a new goroutine to handle connection.
|
|
go func(ctx context.Context, frpConn net.Conn) {
|
|
if lo.FromPtr(svr.cfg.Transport.TCPMux) && !internal {
|
|
fmuxCfg := fmux.DefaultConfig()
|
|
fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.Transport.TCPMuxKeepaliveInterval) * time.Second
|
|
// Use trace level for yamux logs
|
|
fmuxCfg.LogOutput = xlog.NewTraceWriter(xlog.FromContextSafe(ctx))
|
|
fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024
|
|
session, err := fmux.Server(frpConn, fmuxCfg)
|
|
if err != nil {
|
|
log.Warnf("failed to create mux connection: %v", err)
|
|
frpConn.Close()
|
|
return
|
|
}
|
|
|
|
for {
|
|
stream, err := session.AcceptStream()
|
|
if err != nil {
|
|
log.Debugf("accept new mux stream error: %v", err)
|
|
session.Close()
|
|
return
|
|
}
|
|
go svr.handleConnection(ctx, stream, internal)
|
|
}
|
|
} else {
|
|
svr.handleConnection(ctx, frpConn, internal)
|
|
}
|
|
}(ctx, c)
|
|
}
|
|
}
|
|
|
|
func (svr *Service) HandleQUICListener(l *quic.Listener) {
|
|
// Listen for incoming connections from client.
|
|
for {
|
|
c, err := l.Accept(context.Background())
|
|
if err != nil {
|
|
log.Warnf("quic listener for incoming connections from client closed")
|
|
return
|
|
}
|
|
// Start a new goroutine to handle connection.
|
|
go func(ctx context.Context, frpConn *quic.Conn) {
|
|
for {
|
|
stream, err := frpConn.AcceptStream(context.Background())
|
|
if err != nil {
|
|
log.Debugf("accept new quic mux stream error: %v", err)
|
|
_ = frpConn.CloseWithError(0, "")
|
|
return
|
|
}
|
|
go svr.handleConnection(ctx, netpkg.QuicStreamToNetConn(stream, frpConn), false)
|
|
}
|
|
}(context.Background(), c)
|
|
}
|
|
}
|
|
|
|
func (svr *Service) RegisterControl(
|
|
ctlConn *msg.Conn,
|
|
loginMsg *msg.Login,
|
|
internal bool,
|
|
wireProtocol string,
|
|
udpPacketCodec string,
|
|
) (*Control, error) {
|
|
switch wireProtocol {
|
|
case wire.ProtocolV1:
|
|
if udpPacketCodec != "" {
|
|
return nil, fmt.Errorf("UDP packet codec %q requires wire protocol v2", udpPacketCodec)
|
|
}
|
|
case wire.ProtocolV2:
|
|
if udpPacketCodec != "" && udpPacketCodec != wire.UDPPacketCodecBinary {
|
|
return nil, fmt.Errorf("unsupported UDP packet codec selection: %s", udpPacketCodec)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported wire protocol: %s", wireProtocol)
|
|
}
|
|
// If client's RunID is empty, it's a new client, we just create a new controller.
|
|
// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
|
|
var err error
|
|
if loginMsg.RunID == "" {
|
|
loginMsg.RunID, err = util.RandID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := validation.ValidateRunID(loginMsg.RunID); err != nil {
|
|
return nil, fmt.Errorf("invalid run id: %w", err)
|
|
}
|
|
|
|
ctx := netpkg.NewContextFromConn(ctlConn)
|
|
xl := xlog.FromContextSafe(ctx)
|
|
xl.AppendPrefix(loginMsg.RunID)
|
|
ctx = xlog.NewContext(ctx, xl)
|
|
xl.Infof("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]",
|
|
ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
|
|
|
|
// Check auth.
|
|
authVerifier := svr.auth.Verifier
|
|
if internal && loginMsg.ClientSpec.AlwaysAuthPass {
|
|
authVerifier = auth.AlwaysPassVerifier
|
|
}
|
|
if err := authVerifier.VerifyLogin(loginMsg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctl, err := NewControl(ctx, &SessionContext{
|
|
RC: svr.rc,
|
|
PxyManager: svr.pxyManager,
|
|
PluginManager: svr.pluginManager,
|
|
AuthVerifier: authVerifier,
|
|
EncryptionKey: svr.auth.EncryptionKey(),
|
|
Conn: ctlConn,
|
|
LoginMsg: loginMsg,
|
|
ServerCfg: svr.cfg,
|
|
WireProtocol: wireProtocol,
|
|
UDPPacketCodec: udpPacketCodec,
|
|
})
|
|
if err != nil {
|
|
xl.Warnf("create new controller error: %v", err)
|
|
// don't return detailed errors to client
|
|
return nil, fmt.Errorf("unexpected error when creating new controller")
|
|
}
|
|
|
|
if err := svr.ctlManager.Add(ctl); err != nil {
|
|
return ctl, err
|
|
}
|
|
ctl.WaitForHandoff()
|
|
|
|
active, err := svr.ctlManager.Activate(ctl)
|
|
if err != nil {
|
|
return ctl, err
|
|
}
|
|
if !active {
|
|
return ctl, errControlReplaced
|
|
}
|
|
|
|
return ctl, nil
|
|
}
|
|
|
|
// RegisterWorkConn register a new work connection to control and proxies need it.
|
|
func (svr *Service) RegisterWorkConn(
|
|
workConn *msg.Conn,
|
|
newMsg *msg.NewWorkConn,
|
|
workWireProtocol string,
|
|
workClientHelloPresent bool,
|
|
) error {
|
|
if workClientHelloPresent {
|
|
return fmt.Errorf("ClientHello is not allowed on work connections")
|
|
}
|
|
xl := netpkg.NewLogFromConn(workConn)
|
|
ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
|
|
if !exist {
|
|
xl.Warnf("no client control found for run id [%s]", newMsg.RunID)
|
|
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
|
|
}
|
|
if workWireProtocol != ctl.sessionCtx.WireProtocol {
|
|
return fmt.Errorf("work connection wire protocol mismatch: got %s want %s", workWireProtocol, ctl.sessionCtx.WireProtocol)
|
|
}
|
|
|
|
// server plugin hook
|
|
content := &plugin.NewWorkConnContent{
|
|
User: plugin.UserInfo{
|
|
User: ctl.sessionCtx.LoginMsg.User,
|
|
Metas: ctl.sessionCtx.LoginMsg.Metas,
|
|
RunID: ctl.sessionCtx.LoginMsg.RunID,
|
|
},
|
|
NewWorkConn: *newMsg,
|
|
}
|
|
retContent, err := svr.pluginManager.NewWorkConn(content)
|
|
if err == nil {
|
|
newMsg = &retContent.NewWorkConn
|
|
// Check auth.
|
|
err = ctl.sessionCtx.AuthVerifier.VerifyNewWorkConn(newMsg)
|
|
}
|
|
if err != nil {
|
|
xl.Warnf("invalid NewWorkConn with run id [%s]", newMsg.RunID)
|
|
return err
|
|
}
|
|
return svr.ctlManager.RegisterWorkConn(ctl, proxy.NewWorkConn(workConn))
|
|
}
|
|
|
|
func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVisitorConn, wireProtocol string) error {
|
|
admit := func(visitorUser, visitorWireProtocol, visitorUDPPacketCodec string) error {
|
|
if visitorWireProtocol == "" {
|
|
visitorWireProtocol = wireProtocol
|
|
}
|
|
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
|
newMsg.UseEncryption, newMsg.UseCompression, visitorUser, visitorWireProtocol, visitorUDPPacketCodec)
|
|
}
|
|
// TODO(deprecation): Compatible with old versions, can be without runID, user is empty. In later versions, it will be mandatory to include runID.
|
|
// If runID is required, it is not compatible with versions prior to v0.50.0.
|
|
if newMsg.RunID != "" {
|
|
admitted, err := svr.ctlManager.admitVisitorByRunID(newMsg.RunID, func(visitorUser, controlWireProtocol, controlUDPPacketCodec string) error {
|
|
if wireProtocol != controlWireProtocol {
|
|
return fmt.Errorf("visitor connection wire protocol mismatch: got %s want %s", wireProtocol, controlWireProtocol)
|
|
}
|
|
return admit(visitorUser, controlWireProtocol, controlUDPPacketCodec)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !admitted {
|
|
return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
|
|
}
|
|
return nil
|
|
}
|
|
return admit("", wireProtocol, "")
|
|
}
|