diff --git a/src/assets/dependency/iperf/deb/iperf_2.0.13+dfsg1-1build1_amd64.deb b/src/assets/dependency/iperf/deb/iperf_2.0.13+dfsg1-1build1_amd64.deb new file mode 100644 index 00000000..323ae584 Binary files /dev/null and b/src/assets/dependency/iperf/deb/iperf_2.0.13+dfsg1-1build1_amd64.deb differ diff --git a/src/assets/dependency/iperf/rpm/iperf3-3.6-6.ky10.aarch64.rpm b/src/assets/dependency/iperf/rpm/iperf3-3.6-6.ky10.aarch64.rpm new file mode 100644 index 00000000..b5d5a4a5 Binary files /dev/null and b/src/assets/dependency/iperf/rpm/iperf3-3.6-6.ky10.aarch64.rpm differ diff --git a/src/modules/tool/controller/iperf.go b/src/modules/tool/controller/iperf.go index 71413d6b..c6849748 100644 --- a/src/modules/tool/controller/iperf.go +++ b/src/modules/tool/controller/iperf.go @@ -37,15 +37,16 @@ type IPerfController struct { func (s *IPerfController) Version(c *gin.Context) { language := ctx.AcceptLanguage(c) var query struct { - NeType string `form:"neType" binding:"required"` // 网元类型 - NeID string `form:"neId" binding:"required"` // 网元ID + NeType string `form:"neType" binding:"required"` // 网元类型 + NeId string `form:"neId" binding:"required"` // 网元ID + Version string `form:"version" binding:"required,oneof=V2 V3"` // 版本 } if err := c.ShouldBindQuery(&query); err != nil { c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) return } - output, err := s.iperfService.Version(query.NeType, query.NeID) + output, err := s.iperfService.Version(query.NeType, query.NeId, query.Version) if err != nil { c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) return @@ -60,15 +61,16 @@ func (s *IPerfController) Version(c *gin.Context) { func (s *IPerfController) Install(c *gin.Context) { language := ctx.AcceptLanguage(c) var body struct { - NeType string `json:"neType" binding:"required"` // 网元类型 - NeID string `json:"neId" binding:"required"` // 网元ID + NeType string `json:"neType" binding:"required"` // 网元类型 + NeId string `json:"neId" binding:"required"` // 网元ID + Version string `form:"version" binding:"required,oneof=V2 V3"` // 版本 } if err := c.ShouldBindBodyWithJSON(&body); err != nil { c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) return } - if err := s.iperfService.Install(body.NeType, body.NeID); err != nil { + if err := s.iperfService.Install(body.NeType, body.NeId, body.Version); err != nil { c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) return } diff --git a/src/modules/tool/service/iperf.go b/src/modules/tool/service/iperf.go index 75ffaaeb..21caa482 100644 --- a/src/modules/tool/service/iperf.go +++ b/src/modules/tool/service/iperf.go @@ -22,17 +22,39 @@ var NewIPerf = &IPerf{} type IPerf struct{} // Version 查询版本信息 -func (s *IPerf) Version(meType, neId string) (string, error) { - // 检查是否安装iperf3 - output, err := neService.NewNeInfo.NeRunSSHCmd(meType, neId, "iperf3 --version") +func (s *IPerf) Version(meType, neId, version string) (string, error) { + if version != "V2" && version != "V3" { + return "", fmt.Errorf("iperf version is required V2 or V3") + } + cmd := "iperf3 --version" + if version == "V2" { + cmd = "iperf -v" + } + + // 网元主机的SSH客户端 + sshClient, err := neService.NewNeInfo.NeRunSSHClient(meType, neId) if err != nil { - return "", fmt.Errorf("iperf3 not installed") + return "", err + } + defer sshClient.Close() + + // 检查是否安装iperf + output, err := sshClient.RunCMD(cmd) + if err != nil { + if version == "V2" && strings.HasSuffix(err.Error(), "status 1") { // V2 版本 + return strings.TrimSpace(output), nil + } + return "", fmt.Errorf("iperf %s not installed", version) } return strings.TrimSpace(output), err } // Install 安装iperf3 -func (s *IPerf) Install(meType, neId string) error { +func (s *IPerf) Install(meType, neId, version string) error { + if version != "V2" && version != "V3" { + return fmt.Errorf("iperf version is required V2 or V3") + } + // 网元主机的SSH客户端 sshClient, err := neService.NewNeInfo.NeRunSSHClient(meType, neId) if err != nil { @@ -59,6 +81,13 @@ func (s *IPerf) Install(meType, neId string) error { depPkg = "sudo rpm -Uvh --force" depDir = "assets/dependency/iperf3/rpm" // yum remove iperf3 iperf3-help.noarch + } else { + return fmt.Errorf("iperf %s not supported install", version) + } + + // V2版本和V3版本的安装包路径不同 + if version == "V2" { + depDir = strings.Replace(depDir, "iperf3", "iperf", 1) } // 从 embed.FS 中读取默认配置文件内容 @@ -72,19 +101,19 @@ func (s *IPerf) Install(meType, neId string) error { // 打开本地文件 localFile, err := assetsDir.Open(fmt.Sprintf("%s/%s", depDir, d.Name())) if err != nil { - return fmt.Errorf("iperf3 file local error") + return fmt.Errorf("iperf %s file local error", version) } defer localFile.Close() // 创建远程文件 remotePath := fmt.Sprintf("%s/%s", nePath, d.Name()) remoteFile, err := sftpClient.Client.Create(remotePath) if err != nil { - return fmt.Errorf("iperf3 file remote error") + return fmt.Errorf("iperf %s file remote error", version) } defer remoteFile.Close() // 使用 io.Copy 将嵌入的文件内容复制到目标文件 if _, err := io.Copy(remoteFile, localFile); err != nil { - return fmt.Errorf("iperf3 file copy error") + return fmt.Errorf("iperf %s file copy error", version) } neFilePaths = append(neFilePaths, remotePath) } @@ -98,7 +127,7 @@ func (s *IPerf) Install(meType, neId string) error { // 安装软件包 pkgInstall := fmt.Sprintf("%s %s", depPkg, strings.Join(neFilePaths, " ")) if _, err := sshClient.RunCMD(pkgInstall); err != nil { - return fmt.Errorf("iperf3 install error") + return fmt.Errorf("iperf %s install error", version) } return err } @@ -108,7 +137,7 @@ func (s *IPerf) Run(client *wsModel.WSClient, reqMsg wsModel.WSRequest) { // 必传requestId确认消息 if reqMsg.RequestID == "" { msg := "message requestId is required" - logger.Infof("ws IPerf3 Run UID %s err: %s", client.BindUid, msg) + logger.Infof("ws IPerf Run UID %s err: %s", client.BindUid, msg) msgByte, _ := json.Marshal(result.ErrMsg(msg)) client.MsgChan <- msgByte return @@ -126,7 +155,7 @@ func (s *IPerf) Run(client *wsModel.WSClient, reqMsg wsModel.WSRequest) { time.Sleep(1 * time.Second) client.StopChan <- struct{}{} return - case "iperf3": + case "iperf": // SSH会话消息接收写入会话 var command string command, err = s.parseOptions(reqMsg.Data) @@ -155,7 +184,7 @@ func (s *IPerf) Run(client *wsModel.WSClient, reqMsg wsModel.WSRequest) { } if err != nil { - logger.Warnf("ws IPerf3 Run UID %s err: %s", client.BindUid, err.Error()) + logger.Warnf("ws IPerf Run UID %s err: %s", client.BindUid, err.Error()) msgByte, _ := json.Marshal(result.ErrMsg(err.Error())) client.MsgChan <- msgByte if err == io.EOF { @@ -175,7 +204,8 @@ func (s *IPerf) parseOptions(reqData any) (string, error) { msgByte, _ := json.Marshal(reqData) var data struct { Command string `json:"command"` // 命令字符串 - Client bool `json:"client"` // 服务端或客户端,默认服务端 + Version string `json:"version"` // 服务版本,默认V3 + Mode string `json:"mode"` // 服务端或客户端,默认客户端client Host string `json:"host"` // 客户端连接到的服务端IP地址 // Server or Client Port int `json:"port"` // 服务端口 @@ -183,17 +213,25 @@ func (s *IPerf) parseOptions(reqData any) (string, error) { // Server OneOff bool `json:"oneOff"` // 只进行一次连接 // Client - UDP bool `json:"udp"` // use UDP rather than TCP - Time int `json:"time"` // 以秒为单位的传输时间(默认为 10 秒) - Reverse bool `json:"reverse"` // 以反向模式运行(服务器发送,客户端接收) - Window string `json:"window"` // 设置窗口大小/套接字缓冲区大小 + UDP bool `json:"udp"` // use UDP rather than TCP + Time int `json:"time"` // 以秒为单位的传输时间(默认为 10 秒) + Reverse bool `json:"reverse"` // 以反向模式运行(服务器发送,客户端接收) + Window string `json:"window"` // 设置窗口大小/套接字缓冲区大小 + Parallel int `json:"parallel"` // 运行的并行客户端流数量 + Bitrate int `json:"bitrate"` // 以比特/秒为单位(0 表示无限制) } if err := json.Unmarshal(msgByte, &data); err != nil { logger.Warnf("ws processor parseClient err: %s", err.Error()) return "", fmt.Errorf("query data structure error") } + if data.Version != "V3" && data.Version != "V2" { + return "", fmt.Errorf("query data version support V3 or V2") + } command := []string{"iperf3"} + if data.Version == "V2" { + command = []string{"iperf"} + } // 命令字符串高优先级 if data.Command != "" { command = append(command, data.Command) @@ -201,16 +239,14 @@ func (s *IPerf) parseOptions(reqData any) (string, error) { return strings.Join(command, " "), nil } - if data.Client && data.Host == "" { + if data.Mode != "client" && data.Mode != "server" { + return "", fmt.Errorf("query data mode support client or server") + } + if data.Mode == "client" && data.Host == "" { return "", fmt.Errorf("query data client host empty") } - if !data.Client { - command = append(command, "-s") - // Server - if data.OneOff { - command = append(command, "-1") - } - } else { + + if data.Mode == "client" { command = append(command, "-c") command = append(command, data.Host) // Client @@ -220,6 +256,12 @@ func (s *IPerf) parseOptions(reqData any) (string, error) { if data.Time > 0 { command = append(command, fmt.Sprintf("-t %d", data.Time)) } + if data.Bitrate > 0 { + command = append(command, fmt.Sprintf("-b %d", data.Bitrate)) + } + if data.Parallel > 0 { + command = append(command, fmt.Sprintf("-P %d", data.Parallel)) + } if data.Reverse { command = append(command, "-R") } @@ -227,6 +269,13 @@ func (s *IPerf) parseOptions(reqData any) (string, error) { command = append(command, fmt.Sprintf("-w %s", data.Window)) } } + if data.Mode == "server" { + command = append(command, "-s") + // Server + if data.OneOff { + command = append(command, "-1") + } + } // Server or Client if data.Port > 0 {