feat: 信令跟踪功能接口
This commit is contained in:
111
src/modules/trace/controller/packet.go
Normal file
111
src/modules/trace/controller/packet.go
Normal 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))
|
||||||
|
}
|
||||||
191
src/modules/trace/service/packet.go
Normal file
191
src/modules/trace/service/packet.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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 (免登录)
|
// 跟踪任务 网元HLR (免登录)
|
||||||
taskHLRGroup := router.Group("/trace/task/hlr")
|
taskHLRGroup := router.Group("/trace/task/hlr")
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user