feat: 工具模块ping功能
This commit is contained in:
158
src/modules/tool/controller/ping.go
Normal file
158
src/modules/tool/controller/ping.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"be.ems/src/framework/i18n"
|
||||
"be.ems/src/framework/logger"
|
||||
"be.ems/src/framework/utils/ctx"
|
||||
"be.ems/src/framework/vo/result"
|
||||
neService "be.ems/src/modules/network_element/service"
|
||||
"be.ems/src/modules/tool/model"
|
||||
"be.ems/src/modules/tool/service"
|
||||
wsService "be.ems/src/modules/ws/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 实例化控制层 PingController 结构体
|
||||
var NewPing = &PingController{
|
||||
pingService: service.NewPing,
|
||||
wsService: wsService.NewWS,
|
||||
}
|
||||
|
||||
// ping ICMP网络探测工具 https://github.com/prometheus-community/pro-bing
|
||||
//
|
||||
// PATH /tool/ping
|
||||
type PingController struct {
|
||||
pingService *service.Ping // ping ICMP网络探测工具
|
||||
wsService *wsService.WS // WebSocket 服务
|
||||
}
|
||||
|
||||
// ping 基本信息运行
|
||||
//
|
||||
// POST /
|
||||
func (s *PingController) Statistics(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
var body model.Ping
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
return
|
||||
}
|
||||
|
||||
info, err := s.pingService.Statistics(body)
|
||||
if err != nil {
|
||||
c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
|
||||
return
|
||||
}
|
||||
c.JSON(200, result.OkData(info))
|
||||
}
|
||||
|
||||
// ping 传统UNIX运行
|
||||
//
|
||||
// GET /
|
||||
func (s *PingController) StatisticsOn(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
// 登录用户信息
|
||||
loginUser, err := ctx.LoginUser(c)
|
||||
if err != nil {
|
||||
c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
// 将 HTTP 连接升级为 WebSocket 连接
|
||||
wsConn := s.wsService.UpgraderWs(c.Writer, c.Request)
|
||||
if wsConn == nil {
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
wsClient := s.wsService.ClientCreate(loginUser.UserID, nil, wsConn, nil)
|
||||
go s.wsService.ClientWriteListen(wsClient)
|
||||
go s.wsService.ClientReadListen(wsClient, s.pingService.StatisticsOn)
|
||||
|
||||
// 等待停止信号
|
||||
for value := range wsClient.StopChan {
|
||||
s.wsService.ClientClose(wsClient.ID)
|
||||
logger.Infof("ws Stop Client UID %s %s", wsClient.BindUid, value)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ping 网元端UNIX运行
|
||||
//
|
||||
// GET /run
|
||||
func (s *PingController) Run(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
var query struct {
|
||||
NeType string `form:"neType" binding:"required"` // 网元类型
|
||||
NeId string `form:"neId" binding:"required"` // 网元标识id
|
||||
Cols int `form:"cols"` // 终端单行字符数
|
||||
Rows int `form:"rows"` // 终端显示行数
|
||||
}
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
return
|
||||
}
|
||||
|
||||
// 登录用户信息
|
||||
loginUser, err := ctx.LoginUser(c)
|
||||
if err != nil {
|
||||
c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
// 网元主机的SSH客户端
|
||||
sshClient, err := neService.NewNeInfoImpl.NeRunSSHClient(query.NeType, query.NeId)
|
||||
if err != nil {
|
||||
c.JSON(200, result.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
defer sshClient.Close()
|
||||
// ssh连接会话
|
||||
clientSession, err := sshClient.NewClientSession(query.Cols, query.Rows)
|
||||
if err != nil {
|
||||
c.JSON(200, result.ErrMsg("neinfo ssh client session new err"))
|
||||
return
|
||||
}
|
||||
defer clientSession.Close()
|
||||
|
||||
// 将 HTTP 连接升级为 WebSocket 连接
|
||||
wsConn := s.wsService.UpgraderWs(c.Writer, c.Request)
|
||||
if wsConn == nil {
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
wsClient := s.wsService.ClientCreate(loginUser.UserID, nil, wsConn, clientSession)
|
||||
go s.wsService.ClientWriteListen(wsClient)
|
||||
go s.wsService.ClientReadListen(wsClient, s.pingService.RunNE)
|
||||
|
||||
// 等待1秒,排空首次消息
|
||||
time.Sleep(1 * time.Second)
|
||||
_ = clientSession.Read()
|
||||
|
||||
// 实时读取Run消息直接输出
|
||||
msTicker := time.NewTicker(100 * time.Millisecond)
|
||||
defer msTicker.Stop()
|
||||
for {
|
||||
select {
|
||||
case ms := <-msTicker.C:
|
||||
outputByte := clientSession.Read()
|
||||
if len(outputByte) > 0 {
|
||||
outputStr := string(outputByte)
|
||||
msgByte, _ := json.Marshal(result.Ok(map[string]any{
|
||||
"requestId": fmt.Sprintf("ping_%d", ms.UnixMilli()),
|
||||
"data": outputStr,
|
||||
}))
|
||||
wsClient.MsgChan <- msgByte
|
||||
}
|
||||
case <-wsClient.StopChan: // 等待停止信号
|
||||
s.wsService.ClientClose(wsClient.ID)
|
||||
logger.Infof("ws Stop Client UID %s", wsClient.BindUid)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/modules/tool/model/ping.go
Normal file
62
src/modules/tool/model/ping.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
probing "github.com/prometheus-community/pro-bing"
|
||||
)
|
||||
|
||||
// Ping 探针发包参数
|
||||
type Ping struct {
|
||||
DesAddr string `json:"desAddr" binding:"required"` // 目的 IP 地址(字符串类型,必填)
|
||||
SrcAddr string `json:"srcAddr"` // 源 IP 地址(字符串类型,可选)
|
||||
Interval int `json:"interval"` // 发包间隔(整数类型,可选,单位:秒,取值范围:1-60,默认值:1)
|
||||
TTL int `json:"ttl"` // TTL(整数类型,可选,取值范围:1-255,默认值:255)
|
||||
Count int `json:"count"` // 发包数(整数类型,可选,取值范围:1-65535,默认值:5)
|
||||
Size int `json:"size"` // 报文大小(整数类型,可选,取值范围:36-8192,默认值:36)
|
||||
Timeout int `json:"timeout"` // 报文超时时间(整数类型,可选,单位:秒,取值范围:1-60,默认值:2)
|
||||
}
|
||||
|
||||
// setDefaultValue 设置默认值
|
||||
func (p *Ping) setDefaultValue() {
|
||||
if p.Interval < 1 || p.Interval > 10 {
|
||||
p.Interval = 1
|
||||
}
|
||||
if p.TTL < 1 || p.TTL > 255 {
|
||||
p.TTL = 255
|
||||
}
|
||||
if p.Count < 1 || p.Count > 65535 {
|
||||
p.Count = 5
|
||||
}
|
||||
if p.Size < 36 || p.Size > 8192 {
|
||||
p.Size = 36
|
||||
}
|
||||
if p.Timeout < 1 || p.Timeout > 60 {
|
||||
p.Timeout = 2
|
||||
}
|
||||
}
|
||||
|
||||
// NewPinger ping对象
|
||||
func (p *Ping) NewPinger() (*probing.Pinger, error) {
|
||||
p.setDefaultValue()
|
||||
|
||||
pinger, err := probing.NewPinger(p.DesAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.SrcAddr != "" {
|
||||
pinger.Source = p.SrcAddr
|
||||
}
|
||||
pinger.Interval = time.Duration(p.Interval) * time.Second
|
||||
pinger.TTL = p.TTL
|
||||
pinger.Count = p.Count
|
||||
pinger.Size = p.Size
|
||||
pinger.Timeout = time.Duration(p.Timeout) * time.Second
|
||||
|
||||
// 设置特权模式(需要管理员权限)
|
||||
if runtime.GOOS == "windows" {
|
||||
pinger.SetPrivileged(true)
|
||||
}
|
||||
return pinger, nil
|
||||
}
|
||||
256
src/modules/tool/service/ping.go
Normal file
256
src/modules/tool/service/ping.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"be.ems/src/framework/logger"
|
||||
"be.ems/src/framework/utils/ssh"
|
||||
"be.ems/src/framework/vo/result"
|
||||
neService "be.ems/src/modules/network_element/service"
|
||||
"be.ems/src/modules/tool/model"
|
||||
wsModel "be.ems/src/modules/ws/model"
|
||||
probing "github.com/prometheus-community/pro-bing"
|
||||
)
|
||||
|
||||
// 实例化服务层 Ping 结构体
|
||||
var NewPing = &Ping{
|
||||
neInfoService: neService.NewNeInfoImpl,
|
||||
}
|
||||
|
||||
// Ping 网络性能测试工具 服务层处理
|
||||
type Ping struct {
|
||||
// 网元信息服务
|
||||
neInfoService neService.INeInfo
|
||||
}
|
||||
|
||||
// Statistics ping基本信息
|
||||
func (s *Ping) Statistics(ping model.Ping) (map[string]any, error) {
|
||||
pinger, err := ping.NewPinger()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = pinger.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer pinger.Stop()
|
||||
stats := pinger.Statistics()
|
||||
return map[string]any{
|
||||
"minTime": stats.MinRtt.Microseconds(), // 最小时延(整数类型,可选,单位:微秒)
|
||||
"maxTime": stats.MaxRtt.Microseconds(), // 最大时延(整数类型,可选,单位:微秒)
|
||||
"avgTime": stats.AvgRtt.Microseconds(), // 平均时延(整数类型,可选,单位:微秒)
|
||||
"lossRate": int64(stats.PacketLoss), // 丢包率(整数类型,可选,单位:%)
|
||||
"jitter": stats.StdDevRtt.Microseconds(), // 时延抖动(整数类型,可选,单位:微秒)
|
||||
}, nil
|
||||
}
|
||||
|
||||
// StatisticsOn ping模拟传统UNIX
|
||||
func (s *Ping) StatisticsOn(client *wsModel.WSClient, reqMsg wsModel.WSRequest) {
|
||||
// 必传requestId确认消息
|
||||
if reqMsg.RequestID == "" {
|
||||
msg := "message requestId is required"
|
||||
logger.Infof("ws Commont UID %s err: %s", client.BindUid, msg)
|
||||
msgByte, _ := json.Marshal(result.ErrMsg(msg))
|
||||
client.MsgChan <- msgByte
|
||||
return
|
||||
}
|
||||
|
||||
var resByte []byte
|
||||
var err error
|
||||
|
||||
switch reqMsg.Type {
|
||||
case "close":
|
||||
// 主动关闭
|
||||
resultByte, _ := json.Marshal(result.OkMsg("user initiated closure"))
|
||||
client.MsgChan <- resultByte
|
||||
// 等待1s后关闭连接
|
||||
time.Sleep(1 * time.Second)
|
||||
client.StopChan <- struct{}{}
|
||||
return
|
||||
case "ping":
|
||||
msgByte, _ := json.Marshal(reqMsg.Data)
|
||||
var ping model.Ping
|
||||
if errj := json.Unmarshal(msgByte, &ping); errj != nil {
|
||||
err = fmt.Errorf("query data structure error")
|
||||
}
|
||||
var pinger *probing.Pinger
|
||||
pinger, errp := ping.NewPinger()
|
||||
if errp != nil {
|
||||
logger.Warnf("ws pinger new err: %s", errp.Error())
|
||||
err = fmt.Errorf("pinger error")
|
||||
}
|
||||
defer pinger.Stop()
|
||||
|
||||
// 接收的数据包
|
||||
pinger.OnRecv = func(pkt *probing.Packet) {
|
||||
resultByte, _ := json.Marshal(result.Ok(map[string]any{
|
||||
"requestId": reqMsg.RequestID,
|
||||
"data": fmt.Sprintf("%d bytes from %s: icmp_seq=%d time=%v\\r\\n", pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt),
|
||||
}))
|
||||
client.MsgChan <- resultByte
|
||||
}
|
||||
// 已接收过的数据包
|
||||
pinger.OnDuplicateRecv = func(pkt *probing.Packet) {
|
||||
resultByte, _ := json.Marshal(result.Ok(map[string]any{
|
||||
"requestId": reqMsg.RequestID,
|
||||
"data": fmt.Sprintf("%d bytes from %s: icmp_seq=%d time=%v ttl=%v (DUP!)\\r\\n", pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt, pkt.TTL),
|
||||
}))
|
||||
client.MsgChan <- resultByte
|
||||
}
|
||||
// 接收结束
|
||||
pinger.OnFinish = func(stats *probing.Statistics) {
|
||||
end1 := fmt.Sprintf("\\r\\n--- %s ping statistics ---\\r\\n", stats.Addr)
|
||||
end2 := fmt.Sprintf("%d packets transmitted, %d packets received, %v%% packet loss\\r\\n", stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
|
||||
end3 := fmt.Sprintf("round-trip min/avg/max/stddev = %v/%v/%v/%v\\r\\n", stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
|
||||
resultByte, _ := json.Marshal(result.Ok(map[string]any{
|
||||
"requestId": reqMsg.RequestID,
|
||||
"data": fmt.Sprintf("%s%s%s", end1, end2, end3),
|
||||
}))
|
||||
client.MsgChan <- resultByte
|
||||
}
|
||||
resultByte, _ := json.Marshal(result.Ok(map[string]any{
|
||||
"requestId": reqMsg.RequestID,
|
||||
"data": fmt.Sprintf("PING %s (%s) %d bytes of data.\\r\\n", pinger.Addr(), pinger.IPAddr(), pinger.Size),
|
||||
}))
|
||||
client.MsgChan <- resultByte
|
||||
if errp := pinger.Run(); errp != nil {
|
||||
logger.Warnf("ws pinger run err: %s", errp.Error())
|
||||
err = fmt.Errorf("pinger error")
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("message type %s not supported", reqMsg.Type)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Warnf("ws ping run UID %s err: %s", client.BindUid, err.Error())
|
||||
msgByte, _ := json.Marshal(result.ErrMsg(err.Error()))
|
||||
client.MsgChan <- msgByte
|
||||
if err == io.EOF {
|
||||
// 等待1s后关闭连接
|
||||
time.Sleep(1 * time.Second)
|
||||
client.StopChan <- struct{}{}
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(resByte) > 0 {
|
||||
client.MsgChan <- resByte
|
||||
}
|
||||
}
|
||||
|
||||
// RunNE 接收ping终端交互业务处理
|
||||
func (s *Ping) RunNE(client *wsModel.WSClient, reqMsg wsModel.WSRequest) {
|
||||
// 必传requestId确认消息
|
||||
if reqMsg.RequestID == "" {
|
||||
msg := "message requestId is required"
|
||||
logger.Infof("ws ping run UID %s err: %s", client.BindUid, msg)
|
||||
msgByte, _ := json.Marshal(result.ErrMsg(msg))
|
||||
client.MsgChan <- msgByte
|
||||
return
|
||||
}
|
||||
|
||||
var resByte []byte
|
||||
var err error
|
||||
|
||||
switch reqMsg.Type {
|
||||
case "close":
|
||||
// 主动关闭
|
||||
resultByte, _ := json.Marshal(result.OkMsg("user initiated closure"))
|
||||
client.MsgChan <- resultByte
|
||||
// 等待1s后关闭连接
|
||||
time.Sleep(1 * time.Second)
|
||||
client.StopChan <- struct{}{}
|
||||
return
|
||||
case "ping":
|
||||
// SSH会话消息接收写入会话
|
||||
var command string
|
||||
command, err = s.parseOptions(reqMsg.Data)
|
||||
if command != "" && err == nil {
|
||||
sshClientSession := client.ChildConn.(*ssh.SSHClientSession)
|
||||
_, err = sshClientSession.Write(command)
|
||||
}
|
||||
case "ctrl-c":
|
||||
// 模拟按下 Ctrl+C
|
||||
sshClientSession := client.ChildConn.(*ssh.SSHClientSession)
|
||||
_, err = sshClientSession.Write("\u0003\n")
|
||||
case "resize":
|
||||
// 会话窗口重置
|
||||
msgByte, _ := json.Marshal(reqMsg.Data)
|
||||
var data struct {
|
||||
Cols int `json:"cols"`
|
||||
Rows int `json:"rows"`
|
||||
}
|
||||
err = json.Unmarshal(msgByte, &data)
|
||||
if err == nil {
|
||||
sshClientSession := client.ChildConn.(*ssh.SSHClientSession)
|
||||
err = sshClientSession.Session.WindowChange(data.Rows, data.Cols)
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("message type %s not supported", reqMsg.Type)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Warnf("ws ping run UID %s err: %s", client.BindUid, err.Error())
|
||||
msgByte, _ := json.Marshal(result.ErrMsg(err.Error()))
|
||||
client.MsgChan <- msgByte
|
||||
if err == io.EOF {
|
||||
// 等待1s后关闭连接
|
||||
time.Sleep(1 * time.Second)
|
||||
client.StopChan <- struct{}{}
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(resByte) > 0 {
|
||||
client.MsgChan <- resByte
|
||||
}
|
||||
}
|
||||
|
||||
// parseOptions 解析拼装ping命令 ping [options] <destination>
|
||||
func (s *Ping) parseOptions(reqData any) (string, error) {
|
||||
msgByte, _ := json.Marshal(reqData)
|
||||
var data struct {
|
||||
Command string `json:"command"` // 命令字符串
|
||||
DesAddr string `json:"desAddr"` // dns name or ip address
|
||||
// Options
|
||||
Interval int `json:"interval"` // seconds between sending each packet
|
||||
TTL int `json:"ttl"` // define time to live
|
||||
Cunt int `json:"count"` // <count> 次回复后停止
|
||||
Size int `json:"size"` // 使用 <size> 作为要发送的数据字节数
|
||||
Timeout int `json:"timeout"` // time to wait for response
|
||||
}
|
||||
if err := json.Unmarshal(msgByte, &data); err != nil {
|
||||
logger.Warnf("ws processor parseClient err: %s", err.Error())
|
||||
return "", fmt.Errorf("query data structure error")
|
||||
}
|
||||
|
||||
command := []string{"ping"}
|
||||
// 命令字符串高优先级
|
||||
if data.Command != "" {
|
||||
command = append(command, data.Command)
|
||||
command = append(command, "\n")
|
||||
return strings.Join(command, " "), nil
|
||||
}
|
||||
|
||||
// Options
|
||||
if data.Interval > 0 {
|
||||
command = append(command, fmt.Sprintf("-i %d", data.Interval))
|
||||
}
|
||||
if data.TTL > 0 {
|
||||
command = append(command, fmt.Sprintf("-t %d", data.TTL))
|
||||
}
|
||||
if data.Cunt > 0 {
|
||||
command = append(command, fmt.Sprintf("-c %d", data.Cunt))
|
||||
}
|
||||
if data.Size > 0 {
|
||||
command = append(command, fmt.Sprintf("-s %d", data.Size))
|
||||
}
|
||||
if data.Timeout > 0 {
|
||||
command = append(command, fmt.Sprintf("-w %d", data.Timeout))
|
||||
}
|
||||
|
||||
command = append(command, data.DesAddr)
|
||||
command = append(command, "\n")
|
||||
return strings.Join(command, " "), nil
|
||||
}
|
||||
@@ -25,5 +25,29 @@ func Setup(router *gin.Engine) {
|
||||
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.iperf", collectlogs.BUSINESS_TYPE_OTHER)),
|
||||
controller.NewIPerf.Install,
|
||||
)
|
||||
iperfGroup.GET("/run",
|
||||
middleware.PreAuthorize(nil),
|
||||
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.iperf", collectlogs.BUSINESS_TYPE_OTHER)),
|
||||
controller.NewIPerf.Run,
|
||||
)
|
||||
}
|
||||
|
||||
// ping ICMP网络探测工具
|
||||
pingGroup := router.Group("/tool/ping")
|
||||
{
|
||||
pingGroup.POST("",
|
||||
middleware.PreAuthorize(nil),
|
||||
controller.NewPing.Statistics,
|
||||
)
|
||||
pingGroup.GET("",
|
||||
middleware.PreAuthorize(nil),
|
||||
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.ping", collectlogs.BUSINESS_TYPE_OTHER)),
|
||||
controller.NewPing.StatisticsOn,
|
||||
)
|
||||
pingGroup.GET("/run",
|
||||
middleware.PreAuthorize(nil),
|
||||
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.ping", collectlogs.BUSINESS_TYPE_OTHER)),
|
||||
controller.NewPing.Run,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user