package service import ( "fmt" "path/filepath" "regexp" "strings" "sync" "time" "be.ems/src/framework/logger" "be.ems/src/framework/ssh" 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 := sshClient.RunCMD("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(neInfo.NeType), neInfo.NeId, taskCode) sshClient.RunCMD(fmt.Sprintf("sudo mkdir -p %s && sudo chmod 777 -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) } sshClient.RunCMD(fmt.Sprintf("sudo touch %s && sudo chmod o+w %s", logPath, logPath)) 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 := sshClient.RunCMD(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, sshClient) PIDMap["neType"] = neInfo.NeType PIDMap["neId"] = neInfo.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(neInfo.NeType), neInfo.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(neInfo.NeType), neInfo.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), sshClient) // 存放文件目录 /usr/local/omc/tcpdump/udm/001/20240817104241 neDirTemp := fmt.Sprintf("/usr/local/omc/tcpdump/%s/%s/%s", strings.ToLower(neInfo.NeType), neInfo.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 := sshClient.RunCMD(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, sshClient *ssh.ConnSSH) 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 = 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, sshClient *ssh.ConnSSH) 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 777 -R /usr/local/omc/tcpdump", neDirTemp)) lastLineMap := s.logFileLastLine(neType, sshClient) 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) 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() telnetClient.RunCMD("") // 再次排空信息 // 命令拼装 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[1] // 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 }