feat: 信令跟踪功能接口

This commit is contained in:
TsMask
2024-09-27 10:08:06 +08:00
parent 4a4968a8c6
commit f119706694
3 changed files with 331 additions and 0 deletions

View File

@@ -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))
}

View File

@@ -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
}

View File

@@ -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")
{