Files
be.ems/src/modules/trace/service/tcpdump.go
2025-06-07 16:55:05 +08:00

289 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"fmt"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
"be.ems/src/framework/cmd"
"be.ems/src/framework/logger"
neService "be.ems/src/modules/network_element/service"
)
// 实例化服务层 TCPdump 结构体
var NewTCPdump = &TCPdump{
neInfoService: neService.NewNeInfo,
}
// 信令抓包 服务层处理
type TCPdump struct {
neInfoService *neService.NeInfo // 网元信息服务
}
// 抓包进程PID
var dumpPIDMap sync.Map
// DumpStart 触发tcpdump开始抓包
func (s *TCPdump) DumpStart(neType, neId, cmdStr string) (string, error) {
// 命令检查
if strings.Contains(cmdStr, "-w") {
return "", fmt.Errorf("command cannot contain -w")
}
// // 查询网元获取IP
// neInfo := s.neInfoService.FindByNeTypeAndNeID(neType, neId)
// if neInfo.NeId != neId || neInfo.IP == "" {
// return "", fmt.Errorf("app.common.noNEInfo")
// }
// // 网元主机的SSH客户端
// sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId)
// if err != nil {
// return "", err
// }
// defer sshClient.Close()
// 检查是否安装tcpdump
if msg, err := cmd.Exec("sudo tcpdump --version"); err != nil {
// bash: tcpdump: command not found
msg = strings.TrimSpace(msg)
logger.Errorf("DumpStart err: %s => %s", msg, err.Error())
return "", fmt.Errorf("%s", msg)
}
taskCode := time.Now().Format("20060102150405")
// 存放文件目录 /usr/local/omc/tcpdump/udm/001/20240817104241
neDirTemp := fmt.Sprintf("/usr/local/omc/tcpdump/%s/%s/%s", strings.ToLower(neType), neId, taskCode)
cmd.Exec(fmt.Sprintf("sudo mkdir -p %s && sudo chmod 755 -R /usr/local/omc/tcpdump", neDirTemp))
// 命令拼装
logPath := fmt.Sprintf("%s/tcpdump.log", neDirTemp)
filePath := fmt.Sprintf("%s/part_%s.pcap ", neDirTemp, taskCode)
if strings.Contains(cmdStr, "-G") {
filePath = fmt.Sprintf("%s/part_%%Y%%m%%d%%H%%M%%S.pcap ", neDirTemp)
}
sendCmd := fmt.Sprintf("sudo timeout 60m sudo tcpdump -i any %s -w %s > %s 2>&1 & echo $!", cmdStr, filePath, logPath)
// sudo timeout 60m sudo tcpdump -i any -n -s 0 -v -G 60 -W 6 -w /usr/local/omc/tcpdump/udm/001/20240817104241/part_%Y-%m-%d_%H:%M:%S.pcap > /usr/local/omc/tcpdump/udm/001/20240817104241/tcpdump.log 2>&1 & echo $!
// sudo timeout 60m sudo tcpdump -i any -n -s 0 -v -w /usr/local/omc/tcpdump/udm/001/20240817105440/part_2024-08-17_10:54:40.pcap > /usr/local/omc/tcpdump/udm/001/20240817105440/tcpdump.log 2>&1 & echo $!
//
// timeout 超时60分钟后发送kill命令1分钟后强制终止命令。tcpdump -G 文件轮转间隔时间(秒) -W 文件轮转保留最近数量
// sudo timeout --kill-after=1m 60m sudo tcpdump -i any -n -s 0 -v -G 10 -W 7 -w /tmp/part_%Y%m%d%H%M%S.pcap > /tmp/part.log 2>&1 & echo $!
// sudo kill $(pgrep -P 722729)
outputPID, err := cmd.Exec(sendCmd)
outputPID = strings.TrimSpace(outputPID)
if err != nil || strings.HasPrefix(outputPID, "stderr:") {
logger.Errorf("DumpStart err: %s => %s", outputPID, err.Error())
return "", err
}
// 日志文件行号
PIDMap := s.logFileLastLine(neType)
PIDMap["neType"] = neType
PIDMap["neId"] = neId
PIDMap["taskCode"] = taskCode
PIDMap["pid"] = outputPID
PIDMap["cmd"] = sendCmd
// 检查进程 ps aux | grep tcpdump
// 强杀 sudo pkill tcpdump
pidKey := fmt.Sprintf("%s_%s_%s", strings.ToLower(neType), neId, taskCode)
dumpPIDMap.Store(pidKey, PIDMap)
return taskCode, err
}
// DumpStop 停止已存在抓包句柄
func (s *TCPdump) DumpStop(neType, neId, taskCode string) ([]string, error) {
// // 查询网元获取IP
// neInfo := s.neInfoService.FindByNeTypeAndNeID(neType, neId)
// if neInfo.NeId != neId || neInfo.IP == "" {
// return []string{}, fmt.Errorf("app.common.noNEInfo")
// }
// // 网元主机的SSH客户端
// sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId)
// if err != nil {
// return []string{}, err
// }
// defer sshClient.Close()
// 是否存在执行过的进程
pidKey := fmt.Sprintf("%s_%s_%s", strings.ToLower(neType), neId, taskCode)
PIDMap, ok := dumpPIDMap.Load(pidKey)
if !ok || PIDMap == nil {
return []string{}, fmt.Errorf("tcpdump is not running")
}
pid, ok := PIDMap.(map[string]string)["pid"]
if !ok || pid == "" {
return []string{}, fmt.Errorf("tcpdump is not running")
}
s.logFileLastLineToFile(PIDMap.(map[string]string))
// 存放文件目录 /usr/local/omc/tcpdump/udm/001/20240817104241
neDirTemp := fmt.Sprintf("/usr/local/omc/tcpdump/%s/%s/%s", strings.ToLower(neType), neId, taskCode)
// 命令拼装
sendCmd := fmt.Sprintf("pids=$(pgrep -P %s) && [ -n \"$pids\" ] && sudo kill $pids;sudo timeout 2s ls %s", pid, neDirTemp)
// pids=$(pgrep -P 1914341) && [ -n "$pids" ] && sudo kill $pids;sudo timeout 2s ls /usr/local/omc/tcpdump/udm/001/20240817104241
output, err := cmd.Exec(sendCmd)
output = strings.TrimSpace(output)
if err != nil || strings.HasPrefix(output, "ls: ") {
logger.Errorf("DumpStop err: %s => %s", output, err.Error())
return []string{}, err
}
files := strings.Split(output, "\n")
dumpPIDMap.Delete(pidKey)
return files, nil
}
// logFileLastLine 日志文件最后行号
func (s *TCPdump) logFileLastLine(neType string) map[string]string {
logFileArr := make([]string, 0)
mapFile := make(map[string]string, 0)
// 存放文件目录 /var/log/xxx.log
if neType == "IMS" {
logFileArr = append(logFileArr,
"/var/log/ims/pcscf/pcscf.log",
"/var/log/ims/bgcf/bgcf.log",
"/var/log/ims/bsf/bsf.log",
"/var/log/ims/icscf/icscf.log",
"/var/log/ims/ismc/ismc.log",
"/var/log/ims/mmtel/mmtel.log",
"/var/log/ims/scscf/scscf.log",
"/var/log/ims/iwf/iwf.log",
)
} else {
neLogFile := fmt.Sprintf("/var/log/%s.log", strings.ToLower(neType))
logFileArr = append(logFileArr, neLogFile)
}
for _, v := range logFileArr {
// lastLine, err := sshClient.RunCMD(fmt.Sprintf("sudo sed -n '$=' %s", v))
lastLine, err := cmd.Exec(fmt.Sprintf("sudo sed -n '$=' %s", v))
lastLine = strings.TrimSpace(lastLine)
if err != nil || strings.HasPrefix(lastLine, "sed: can't") {
logger.Errorf("logFileLastLine err: %s => %s", lastLine, err.Error())
continue
}
mapFile[v] = lastLine
}
return mapFile
}
// logFileLastLine 日志文件最后行号
func (s *TCPdump) logFileLastLineToFile(PIDMap map[string]string) error {
// // 网元主机的SSH客户端进行文件传输
// sftpClient, err := sshClient.NewClientSFTP()
// if err != nil {
// return fmt.Errorf("ne info sftp client err")
// }
// defer sftpClient.Close()
neType := PIDMap["neType"]
neId := PIDMap["neId"]
taskCode := PIDMap["taskCode"]
// 存放文件目录 /usr/local/omc/tcpdump/udm/001/20240817104241
neDirTemp := fmt.Sprintf("/usr/local/omc/tcpdump/%s/%s/%s", strings.ToLower(neType), neId, taskCode)
// sshClient.RunCMD(fmt.Sprintf("sudo mkdir -p %s && sudo chmod 755 -R /usr/local/omc/tcpdump", neDirTemp))
cmd.Exec(fmt.Sprintf("sudo mkdir -p %s && sudo chmod 755 -R /usr/local/omc/tcpdump", neDirTemp))
lastLineMap := s.logFileLastLine(neType)
for lastLogFile, lastFileLine := range lastLineMap {
for startLogFile, startFileLine := range PIDMap {
if lastLogFile == startLogFile && lastFileLine != "" {
if startFileLine == "" {
startFileLine = "1" // 起始行号从第一行开始
}
outputFile := fmt.Sprintf("%s/%s", neDirTemp, filepath.Base(lastLogFile))
// sendCmd := fmt.Sprintf("sudo sed -n \"%s,%sp\" \"%s\" | sudo tee \"%s\" > /dev/null", startFileLine, lastFileLine, lastLogFile, outputFile)
// sudo sed -n "1,5p" "/var/log/amf.log" | sudo tee "/usr/local/omc/tcpdump/amf/001/20241008141336/amf.log" > /dev/null
// output, err := sshClient.RunCMD(sendCmd)
sendCmd := fmt.Sprintf("sudo sed -n \"%s,%sp\" \"%s\" | sudo tee \"%s\" > /dev/null", startFileLine, lastFileLine, lastLogFile, outputFile)
output, err := cmd.Exec(sendCmd)
if err != nil || strings.HasPrefix(output, "stderr:") {
logger.Errorf("logFileLastLineToFile err: %s => %s", strings.TrimSpace(output), err.Error())
continue
}
}
}
}
return nil
}
// UPFTrace UPF标准版内部抓包
func (s *TCPdump) UPFTrace(neType, neId, cmdStr string) (string, error) {
// 命令检查
if strings.Contains(cmdStr, "file") {
return "", fmt.Errorf("command cannot contain file")
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID(neType, neId)
if neInfo.NeId != neId || neInfo.IP == "" {
return "", fmt.Errorf("app.common.noNEInfo")
}
// 网元主机的SSH客户端
sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId)
if err != nil {
return "", err
}
defer sshClient.Close()
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UPF", neInfo.NeId, 2)
if err != nil {
return "", err
}
defer telnetClient.Close()
// 命令拼装
fileName := fmt.Sprintf("%s_%s_part_%s.pcap ", neInfo.NeType, neInfo.NeId, time.Now().Format("20060102150405"))
pcapCmd := fmt.Sprintf("%s\r\n", cmdStr)
// 以off结尾是停止抓包不需要写文件
if !strings.Contains(cmdStr, "off") {
// pcap trace rx tx max 100000 intfc any file UPF_001_part_20240817164516.pcap
pcapCmd = fmt.Sprintf("%s file %s\r\n", cmdStr, fileName)
}
// 发送命令 UPF内部默认输出路径/tmp只能写文件名
// pcap trace rx tx max 100000 intfc any file upf_test.pcap
// pcap trace rx tx off
output, err := telnetClient.RunCMD(pcapCmd)
if err != nil {
logger.Warnf("DumpUPF err: %s => %s", output, err.Error())
return "", err
}
// 结果截取
arr := strings.Split(output, "\r\n")
if len(arr) == 2 {
return "", fmt.Errorf("trace pacp run failed")
}
if len(arr) > 3 {
resMsg := arr[2]
// pcap trace: unknown input `f file UPF_001_part_2024-08-19...'
// pcap trace: dispatch trace already enabled...
// pcap trace: dispatch trace already disabled...
// pcap trace: No packets captured...
// Write 100000 packets to /tmp/UPF_001_part_20240817164516.pcap, and stop capture...
if strings.Contains(resMsg, "unknown input") {
return "", fmt.Errorf("trace pacp unknown input")
}
if strings.Contains(resMsg, "already enabled") {
return "", fmt.Errorf("trace pacp already running")
}
if strings.Contains(resMsg, "already disabled") {
return "", fmt.Errorf("trace pacp not running")
}
if strings.Contains(resMsg, "No packets") {
return "", fmt.Errorf("trace pacp not packets")
}
if strings.Contains(resMsg, "packets to") {
matches := regexp.MustCompile(`(/tmp/[^,\s]+)`).FindStringSubmatch(resMsg)
if len(matches) == 0 {
return "", fmt.Errorf("file path not found")
}
return matches[0], nil
}
}
return "trace pacp running", nil
}