// 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" "fmt" "io" "io/ioutil" "net" "net/http" "os" "regexp" "strconv" "time" "github.com/fatedier/frp/extend/api" "github.com/fatedier/frp/pkg/auth" v1 "github.com/fatedier/frp/pkg/config/v1" 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/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/metrics" "github.com/fatedier/frp/server/ports" "github.com/fatedier/frp/server/proxy" "github.com/fatedier/frp/server/visitor" "github.com/fatedier/golib/crypto" "github.com/fatedier/golib/net/mux" fmux "github.com/hashicorp/yamux" quic "github.com/quic-go/quic-go" "github.com/samber/lo" ) const ( connReadTimeout time.Duration = 10 * time.Second vhostReadWriteTimeout time.Duration = 30 * time.Second ) 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 // 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 sshTunnelGateway *ssh.Gateway // Verifies authentication based on selected method authVerifier auth.Verifier tlsConfig *tls.Config cfg *v1.ServerConfig // service context ctx context.Context // call cancel to stop service cancel context.CancelFunc ctl *Control } 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 } var webServer *httppkg.Server if cfg.WebServer.Port > 0 { ws, err := httppkg.NewServer(cfg.WebServer) if err != nil { return nil, err } webServer = ws modelmetrics.EnableMem() if cfg.EnablePrometheus { modelmetrics.EnablePrometheus() } } svr := &Service{ ctlManager: NewControlManager(), pxyManager: proxy.NewManager(), pluginManager: plugin.NewManager(), rc: &controller.ResourceController{ VisitorManager: visitor.NewManager(), TCPPortManager: ports.NewManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts), UDPPortManager: ports.NewManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts), }, sshTunnelListener: netpkg.NewInternalListener(), httpVhostRouter: vhost.NewRouters(), authVerifier: auth.NewAuthVerifier(cfg.Auth), webServer: webServer, tlsConfig: tlsConfig, cfg: cfg, ctx: context.Background(), } if webServer != nil { webServer.RouteRegister(svr.registerRouteHandlers) } // 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("[HayFrp] 创建服务器监听时发送错误, %v", err) } svr.rc.TCPMuxHTTPConnectMuxer, err = tcpmux.NewHTTPConnectTCPMuxer(l, cfg.TCPMuxPassthrough, vhostReadWriteTimeout) if err != nil { return nil, fmt.Errorf("[HayFrp] 创建Vhost TcpMux时错误, %v", err) } log.Infof("[HayFrp] TcpMux HttpConnect多路复用器正在 %s 上监听, 通过: %v", address, cfg.TCPMuxPassthrough) } // Init all plugins for _, p := range cfg.HTTPPlugins { svr.pluginManager.Register(plugin.NewHTTPPluginOptions(p)) log.Infof("[HayFrp] 插件 [%s] 已注册", p.Name) } svr.rc.PluginManager = svr.pluginManager // Init group controller svr.rc.TCPGroupCtl = group.NewTCPGroupCtl(svr.rc.TCPPortManager) // 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("[HayFrp] 创建服务监听时发生错误, %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("[HayFrp] TCP链接端口已开放在 %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("[HayFrp] 开放KCP监听在UDP端口 %s 时发生错误: %v", address, err) } log.Infof("[HayFrp] KCP监听已开放在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("[HayFrp] 收听QUIC UDP地址 %s 时发生错误: %v", address, err) } log.Infof("[HayFrp] QUIC已在监听于 %s", address) } if cfg.SSHTunnelGateway.BindPort > 0 { sshGateway, err := ssh.NewGateway(cfg.SSHTunnelGateway, cfg.ProxyBindAddr, svr.sshTunnelListener) if err != nil { return nil, fmt.Errorf("[HayFrp] 创建SSH网关时发生错误: %v", err) } svr.sshTunnelGateway = sshGateway log.Infof("[HayFrp] SSH隧道网关已成功监听于端口 %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)) server := &http.Server{ Addr: address, Handler: rp, ReadHeaderTimeout: 60 * time.Second, } 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("[HayFrp] 创建Vhost HTTP时发生错误, %v", err) } } go func() { _ = server.Serve(l) }() log.Infof("[HayFrp] HTTP协议已成功监听于端口 %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("[HayFrp] 创建服务监听时发生错误, %v", err) } log.Infof("[HayFrp] HTTPS协议已成功监听于端口 %s", address) } svr.rc.VhostHTTPSMuxer, err = vhost.NewHTTPSMuxer(l, vhostReadWriteTimeout) if err != nil { return nil, fmt.Errorf("[HayFrp] 创建Vhost HttpsMuxer时发生错误, %v", err) } } // 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("[HayFrp] 创建NAT打洞控制器错误, %v", err) } svr.rc.NatHoleController = nc return svr, nil } func (svr *Service) Run(ctx context.Context) { ctx, cancel := context.WithCancel(ctx) svr.ctx = ctx svr.cancel = cancel // run dashboard web server. if svr.webServer != nil { go func() { log.Infof("[HayFrp] 仪表盘(节点API)已监听于端口 %s", svr.webServer.Address()) if err := svr.webServer.Run(); err != nil { log.Warnf("[HayFrp] 仪表盘(节点API)已退出: %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() // service context may not be canceled by svr.Close(), we should call it here to release resources if svr.listener != nil { svr.Close() } } func (svr *Service) Close() error { if svr.kcpListener != nil { svr.kcpListener.Close() svr.kcpListener = nil } if svr.quicListener != nil { svr.quicListener.Close() svr.quicListener = nil } if svr.websocketListener != nil { svr.websocketListener.Close() svr.websocketListener = nil } if svr.tlsListener != nil { svr.tlsListener.Close() svr.tlsConfig = nil } if svr.listener != nil { svr.listener.Close() svr.listener = nil } svr.ctlManager.Close() if svr.cancel != nil { svr.cancel() } return nil } func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, internal bool) { 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.Tracef("[HayFrp] 无法读取信息: %v", err) conn.Close() return } _ = conn.SetReadDeadline(time.Time{}) switch m := rawMsg.(type) { case *msg.Login: // server plugin hook content := &plugin.LoginContent{ Login: *m, ClientAddress: conn.RemoteAddr().String(), } retContent, err := svr.pluginManager.Login(content) if err == nil { m = &retContent.Login err = svr.RegisterControl(conn, m, internal) } // If login failed, send error message there. // Otherwise send success message in control's work goroutine. if err != nil { xl.Warnf("[HayFrp] 注册控制器时发生错误: %v", err) _ = msg.WriteMsg(conn, &msg.LoginResp{ Version: version.Full(), Error: util.GenerateResponseErrorString("[HayFrp] 注册控制器时发生错误", err, lo.FromPtr(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.Warnf("[HayFrp] 注册访问者连接错误: %v", err) _ = msg.WriteMsg(conn, &msg.NewVisitorConnResp{ ProxyName: m.ProxyName, Error: util.GenerateResponseErrorString("[HayFrp] 注册访问者连接错误", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), }) conn.Close() } else { _ = msg.WriteMsg(conn, &msg.NewVisitorConnResp{ ProxyName: m.ProxyName, Error: "", }) } default: log.Warnf("[HayFrp] 新连接 [%s] 发送错误的消息类型", conn.RemoteAddr().String()) conn.Close() } } // 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("[HayFrp] 客户端传入连接的侦听器已关闭") 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("[HayFrp] 开始检查TLS链接...") 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("[HayFrp] 检查与启用TLS服务器链接与超时 发生错误: %v", err) originConn.Close() continue } log.Tracef("[HayFrp] 检查TLS链接成功, 是TLS: %v 自定义: %v 内部: %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 fmuxCfg.LogOutput = io.Discard fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024 session, err := fmux.Server(frpConn, fmuxCfg) if err != nil { log.Warnf("[HayFrp] 创建多路复用器连接失败: %v", err) frpConn.Close() return } for { stream, err := session.AcceptStream() if err != nil { log.Debugf("[HayFrp] 接受新的多路复用流错误: %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("[HayFrp] 客户端传入连接的QUIC监听已关闭") return } // Start a new goroutine to handle connection. go func(ctx context.Context, frpConn quic.Connection) { for { stream, err := frpConn.AcceptStream(context.Background()) if err != nil { log.Debugf("[HayFrp] 接受新的QUIC多路复用流错误: %v", err) _ = frpConn.CloseWithError(0, "") return } go svr.handleConnection(ctx, netpkg.QuicStreamToNetConn(stream, frpConn), false) } }(context.Background(), c) } } func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, internal bool) error { // 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 // 请求一堆API if loginMsg.RunID == "" { randid, err := util.RandID() if err != nil { return err } serverTime := time.Now() greeting := "" hour := serverTime.Hour() if hour >= 0 && hour < 6 { greeting = "午夜好,又在修仙呢......" } else if hour >= 6 && hour < 9 { greeting = "早上好,美好的一天开始了~" } else if hour >= 9 && hour < 12 { greeting = "中午好,饱饱地睡一觉~" } else if hour >= 12 && hour < 18 { greeting = "下午好,运动运动,活动筋骨~" } else if hour >= 18 && hour < 24 { greeting = "晚上好,别修仙了~" } t := time.Now() layout := "2006-01-02 15:04:05.888" str := t.Format(layout) loginMsg.RunID = "HayFrpToken:" + loginMsg.User + "," + "ConnectToken:" + randid + "] Getting data from HayFrp API......" + "\n" + str + " [I] [server/hayfrp.go:102] [HayFrp] " + greeting + "\n" + str + " [I] [server/hayfrp.go:145] [HayFrp] 已将HayFrp终端介入客户端Frpc!" + "\n" + str + " [W] [server/hayfrp.go:187] [HayFrp] 友情提示:若需要分享错误日志,请为Token打码,否则可能导致信息泄露!" + "\n" + str + " [I] [server/hayfrp.go:245] [HayFrp] 当前服务器准时: " + serverTime.String() + "\n" + str + " [I] [server/hayfrp.go:199] [HayFrp] 您已成功连接至HayFrp云服务" + "\n" + str + " [W] [server/hayfrp.go:425] [HayFrp] 检测到您正在" + loginMsg.Os + "-" + loginMsg.Arch + "系统下运行客户端" + "\n" + str + " [W] [server/hayfrp.go:665] [HayFrp] 检测到您的客户端版本为" + loginMsg.Version + "\n" // 发起 GET 请求获取 API 返回的内容(API服务状态查询) resp, err := http.Get("https://api.hayfrp.org/") if err != nil { return err } defer resp.Body.Close() // 读取 API 返回的内容 body, err := ioutil.ReadAll(resp.Body) if err != nil { return err } // 将 API 返回的内容添加到 loginMsg.RunID 后面 loginMsg.RunID += str + " [I] [api/hayfrp.go:423] [HayFrp] " + string(body) + "\n" // 发起 GET 请求获取 API 返回的内容(今日启动获取服务) resp, err = http.Get("https://api.hayfrp.org/NodeAPI?type=userlogin&utoken=" + loginMsg.User + "&token=" + svr.cfg.ApiToken + "&system=" + loginMsg.Os + "&ver=" + loginMsg.Version + "&arch=" + loginMsg.Arch) if err != nil { return err } defer resp.Body.Close() // 读取 API 返回的内容 body, err = ioutil.ReadAll(resp.Body) if err != nil { return err } // 将 API 返回的内容添加到 loginMsg.RunII 后面 loginMsg.RunID += str + " [I] [hayfrp.go:507] [HayFrp] " + string(body) + "\n" + str + " [I] [root.go:490] [HayFrp" } ctx := netpkg.NewContextFromConn(ctlConn) xl := xlog.FromContextSafe(ctx) xl.AppendPrefix(loginMsg.RunID) ctx = xlog.NewContext(ctx, xl) xl.Infof("[HayFrp] 客户端登录信息: IP地址 [%s] 客户端版本 [%s] 主机名 [%s] 系统 [%s] 架构 [%s]", ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch) // Check auth. authVerifier := svr.authVerifier if internal && loginMsg.ClientSpec.AlwaysAuthPass { authVerifier = auth.AlwaysPassVerifier } if err := authVerifier.VerifyLogin(loginMsg); err != nil { return err } var ( inLimit uint64 outLimit uint64 ) if svr.cfg.EnableApi { nowTime := time.Now().Unix() s, err := api.NewService(svr.cfg.ApiBaseUrl) if err != nil { return err } r := regexp.MustCompile(`^[A-Za-z0-9]{1,32}$`) mm := r.FindAllStringSubmatch(loginMsg.User, -1) if len(mm) < 1 { return fmt.Errorf("[HayFrp] 未知用户名") } // Connect to API server and verify the user. valid, err := s.CheckToken(loginMsg.User, loginMsg.PrivilegeKey, nowTime, svr.cfg.ApiToken) if err != nil { return err } if !valid { return fmt.Errorf("[HayFrp] 认证失败") } inLimit, outLimit, err = s.GetProxyLimit(loginMsg.User, nowTime, svr.cfg.ApiToken) if err != nil { return err } xl.Debugf("[HayFrp] %s 客户端速率: %dKB/s (上行) / %dKB/s (下行)", loginMsg.User, inLimit, outLimit) } // TODO(fatedier): use SessionContext ctl, err := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, authVerifier, ctlConn, !internal, loginMsg, svr.cfg) if err != nil { xl.Warnf("[HayFrp] 创建新控制器错误: %v", err) // don't return detailed errors to client return fmt.Errorf("[HayFrp] 创建新控制器时出现意外错误") } if oldCtl := svr.ctlManager.Add(loginMsg.RunID, ctl); oldCtl != nil { oldCtl.WaitClosed() } ctl.Start() // for statistics metrics.Server.NewClient() go func() { // block until control closed ctl.WaitClosed() svr.ctlManager.Del(loginMsg.RunID, ctl) }() return nil } // RegisterWorkConn register a new work connection to control and proxies need it. func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) error { xl := netpkg.NewLogFromConn(workConn) ctl, exist := svr.ctlManager.GetByID(newMsg.RunID) if !exist { xl.Warnf("[HayFrp] 未找到运行ID [%s] 的客户端控件", newMsg.RunID) return fmt.Errorf("[HayFrp] 未找到运行ID [%s] 的客户端控件", newMsg.RunID) } // server plugin hook content := &plugin.NewWorkConnContent{ User: plugin.UserInfo{ User: ctl.loginMsg.User, Metas: ctl.loginMsg.Metas, RunID: ctl.loginMsg.RunID, }, NewWorkConn: *newMsg, } retContent, err := svr.pluginManager.NewWorkConn(content) if err == nil { newMsg = &retContent.NewWorkConn // Check auth. err = ctl.authVerifier.VerifyNewWorkConn(newMsg) } if err != nil { xl.Warnf("[HayFrp] 运行ID为 [%s] 的新活动链接无效", newMsg.RunID) _ = msg.WriteMsg(workConn, &msg.StartWorkConn{ Error: util.GenerateResponseErrorString("[HayFrp] 无效 新活动链接", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), }) return fmt.Errorf("[HayFrp] 运行ID为 [%s] 的新活动链接无效", newMsg.RunID) } return ctl.RegisterWorkConn(workConn) } func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVisitorConn) error { visitorUser := "" // 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 != "" { ctl, exist := svr.ctlManager.GetByID(newMsg.RunID) if !exist { return fmt.Errorf("[HayFrp] 未找到运行ID为 [%s] 的客户端控件", newMsg.RunID) } visitorUser = ctl.loginMsg.User } return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey, newMsg.UseEncryption, newMsg.UseCompression, visitorUser) } func (svr *Service) CloseUser(user string) error { ctl, ok := svr.ctlManager.SearchByID(user) if !ok { return fmt.Errorf("[HayFrp] 客户端用户没有登录") } ctl.conn.Close() return nil }