From f11970669415f82d166ab76355bc653b7d52a48e Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Fri, 27 Sep 2024 10:08:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=A1=E4=BB=A4=E8=B7=9F=E8=B8=AA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/trace/controller/packet.go | 111 ++++++++++++++ src/modules/trace/service/packet.go | 191 +++++++++++++++++++++++++ src/modules/trace/trace.go | 29 ++++ 3 files changed, 331 insertions(+) create mode 100644 src/modules/trace/controller/packet.go create mode 100644 src/modules/trace/service/packet.go diff --git a/src/modules/trace/controller/packet.go b/src/modules/trace/controller/packet.go new file mode 100644 index 00000000..a89cacc6 --- /dev/null +++ b/src/modules/trace/controller/packet.go @@ -0,0 +1,111 @@ +package controller + +import ( + "be.ems/src/framework/i18n" + "be.ems/src/framework/utils/ctx" + "be.ems/src/framework/vo/result" + traceService "be.ems/src/modules/trace/service" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 PacketController 结构体 +var NewPacket = &PacketController{ + packetService: traceService.NewPacket, +} + +// 信令跟踪 +// +// PATH /trace/packet +type PacketController struct { + packetService *traceService.Packet // 信令跟踪服务 +} + +// 网元跟踪网卡设备列表 +// +// GET /devices +func (s *PacketController) Devices(c *gin.Context) { + data := s.packetService.NetworkDevices() + c.JSON(200, result.OkData(data)) +} + +// 网元跟踪开始 +// +// POST /start +func (s *PacketController) Start(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var body struct { + Device string `json:"device" binding:"required"` // 网卡设备 + TaskNo string `json:"taskNo" binding:"required"` // 任务编号 + Output string `json:"output" ` // 输出PCAP文件路径,为空则不输出 + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + msg, err := s.packetService.LiveStart(body.TaskNo, body.Device, body.Output) + if err != nil { + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) + return + } + c.JSON(200, result.OkData(msg)) +} + +// 网元跟踪结束 +// +// POST /stop +func (s *PacketController) Stop(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var body struct { + TaskNo string `json:"taskNo" binding:"required"` // 任务编号 + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + if err := s.packetService.LiveStop(body.TaskNo); err != nil { + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) + return + } + c.JSON(200, result.Ok(nil)) +} + +// 网元跟踪过滤 +// +// PUT /filter +func (s *PacketController) Filter(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var body struct { + TaskNo string `json:"taskNo" binding:"required"` // 任务编号 + Expr string `json:"expr" binding:"required"` // 过滤表达式(port 33030 or 33040) + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + if err := s.packetService.LiveFilter(body.TaskNo, body.Expr); err != nil { + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) + return + } + c.JSON(200, result.Ok(nil)) +} + +// 网元跟踪续期保活 +// +// PUT /keep-alive +func (s *PacketController) KeepAlive(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var body struct { + IntervalIn int `json:"intervalIn" ` // 服务续约的频率,默认设置为60秒 + Duration int `json:"duration" ` // 服务失效的时间,默认设置为120秒 + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + c.JSON(200, result.Ok(nil)) +} diff --git a/src/modules/trace/service/packet.go b/src/modules/trace/service/packet.go new file mode 100644 index 00000000..9b6e8f8a --- /dev/null +++ b/src/modules/trace/service/packet.go @@ -0,0 +1,191 @@ +package service + +import ( + "context" + "fmt" + "os" + "strings" + "sync" + "time" + + "be.ems/src/framework/logger" + "be.ems/src/framework/vo" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/pcap" + "github.com/gopacket/gopacket/pcapgo" +) + +// 实例化服务层 Packet 结构体 +var NewPacket = &Packet{ + taskMap: sync.Map{}, +} + +// 信令跟踪 服务层处理 +type Packet struct { + taskMap sync.Map // 捕获任务 +} + +// task 任务信息 +type task struct { + TaskNo string // 任务编号 + Handle *pcap.Handle // 捕获句柄 + File *os.File // 捕获信息输出文件句柄 + Writer *pcapgo.Writer // 捕获信息输出句柄 + Expire time.Time // 过期时间 + context context.Context // 上下文 控制完成结束 +} + +// NetworkDevices 获取网卡设备信息 +func (s *Packet) NetworkDevices() []vo.TreeSelect { + arr := make([]vo.TreeSelect, 0) + devices, err := pcap.FindAllDevs() + if err != nil { + logger.Errorf("interfaces find all devices err: %s", err.Error()) + return arr + } + + for _, device := range devices { + if len(device.Addresses) == 0 { + continue + } + + lable := device.Description + if lable == "" { + lable = device.Name + } + item := vo.TreeSelect{ + ID: device.Name, + Label: lable, + Children: []vo.TreeSelect{}, + } + + for _, address := range device.Addresses { + if address.IP != nil { + ip := address.IP.String() + item.Children = append(item.Children, vo.TreeSelect{ID: ip, Label: ip}) + } + } + arr = append(arr, item) + } + + return arr +} + +// verifyDevice 检查网卡设备是否存在 +func (s *Packet) verifyDevice(str string) (string, bool) { + devices, err := pcap.FindAllDevs() + if err != nil { + logger.Errorf("interfaces find all devices err: %s", err.Error()) + return "", false + } + for _, device := range devices { + if len(device.Addresses) == 0 { + continue + } + if device.Name == str { + return device.Name, true + } + for _, address := range device.Addresses { + if address.IP.String() == str { + return device.Name, true + } + } + } + return "", false +} + +// LiveStart 开始捕获数据 +func (s *Packet) LiveStart(taskNo, deviceName, outputFile string) (string, error) { + if _, ok := s.taskMap.Load(taskNo); ok { + return "", fmt.Errorf("task no. %s already exist", taskNo) + } + + // Verify the specified network interface exists + device, deviceOk := s.verifyDevice(deviceName) + if !deviceOk { + return "", fmt.Errorf("network device not exist: %s", deviceName) + } + + snapshotLength := 262144 + + // open device + handle, err := pcap.OpenLive(device, int32(snapshotLength), true, pcap.BlockForever) + if err != nil { + logger.Errorf("open live err: %s", err.Error()) + if strings.Contains(err.Error(), "operation not permitted") { + return "", fmt.Errorf("you don't have permission to capture on that/these device(s)") + } + return "", err + } + + // write a new file + var w *pcapgo.Writer + var f *os.File + if outputFile != "" { + f, err = os.Create(outputFile) + if err != nil { + return "", err + } + w = pcapgo.NewWriter(f) + w.WriteFileHeader(uint32(snapshotLength), handle.LinkType()) + } + + // capture packets + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + packetSource.Lazy = false + packetSource.NoCopy = true + packetSource.DecodeStreamsAsDatagrams = true + + // save tasks + ctx := context.Background() + task := &task{ + TaskNo: taskNo, + Handle: handle, + File: f, + Writer: w, + Expire: time.Now().Add(time.Second * 120), + context: ctx, + } + s.taskMap.Store(taskNo, task) + + // start capture + go func() { + for packet := range packetSource.PacketsCtx(ctx) { + if packet == nil { + continue + } + if packet.Metadata().Timestamp.Before(time.Now()) { + continue + } + if w != nil { + w.WritePacket(packet.Metadata().CaptureInfo, packet.Data()) + } + fmt.Println(packet.Dump()) + } + }() + return "task initiated", nil +} + +// LiveFilter 捕获过滤 +func (s *Packet) LiveFilter(taskNo, expr string) error { + info, ok := s.taskMap.Load(taskNo) + if !ok { + return fmt.Errorf("task no. %s not exist", taskNo) + } + return info.(*task).Handle.SetBPFFilter(expr) +} + +// LiveStop 停止捕获数据 +func (s *Packet) LiveStop(taskNo string) error { + info, ok := s.taskMap.Load(taskNo) + if !ok { + return fmt.Errorf("task no. %s not exist", taskNo) + } + info.(*task).context.Done() + info.(*task).Handle.Close() + if info.(task).File != nil { + info.(task).File.Close() + } + s.taskMap.Delete(taskNo) + return nil +} diff --git a/src/modules/trace/trace.go b/src/modules/trace/trace.go index ca95c661..98ff72da 100644 --- a/src/modules/trace/trace.go +++ b/src/modules/trace/trace.go @@ -41,6 +41,35 @@ func Setup(router *gin.Engine) { ) } + // 信令跟踪 + packetGroup := router.Group("/trace/packet") + { + packetGroup.GET("/devices", + middleware.PreAuthorize(nil), + controller.NewPacket.Devices, + ) + packetGroup.POST("/start", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.packet", collectlogs.BUSINESS_TYPE_OTHER)), + controller.NewPacket.Start, + ) + packetGroup.POST("/stop", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.packet", collectlogs.BUSINESS_TYPE_OTHER)), + controller.NewPacket.Stop, + ) + packetGroup.PUT("/filter", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.packet", collectlogs.BUSINESS_TYPE_OTHER)), + controller.NewPacket.Filter, + ) + packetGroup.PUT("/keep-alive", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.packet", collectlogs.BUSINESS_TYPE_OTHER)), + controller.NewPacket.KeepAlive, + ) + } + // 跟踪任务 网元HLR (免登录) taskHLRGroup := router.Group("/trace/task/hlr") {