@@ -2,36 +2,45 @@ package service
import (
"fmt"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"sync"
"time"
"nms_cxy/src/framework/logger"
"nms_cxy/src/framework/utils/dat e"
"nms_cxy/src/framework/utils/fil e"
neService "nms_cxy/src/modules/network_element/service"
)
// 实例化服务层 TcpdumpImpl 结构体
var NewTcpdumpImpl = & TcpdumpImpl {
neInfoService : neService . NewNeInfoImpl ,
tcpdumpPIDMap : map [ string ] string { } ,
}
// 信令抓包 服务层处理
type TcpdumpImpl struct {
// 网元信息服务
neInfoService neService . INeInfo
// 抓包进程PID
tcpdumpPIDMap map [ string ] string
}
// DumpStart 触发tcpdump开始抓包 filePcapName, err
// 抓包进程PID
var dumpPIDMap sync . Map
// DumpStart 触发tcpdump开始抓包
func ( s * TcpdumpImpl ) DumpStart ( neType , neId , cmdStr string ) ( string , error ) {
// 命令检查
if strings . Contains ( cmdStr , "w" ) {
return "" , fmt . Errorf ( "command cannot contain -w" )
}
// 查询网元获取IP
neInfo := s . neInfoService . SelectNeInfoByNeTypeAndNeID ( neType , neId )
if neInfo . NeId != neId || neInfo . IP == "" {
return "" , fmt . Errorf ( "noData " )
return "" , fmt . Errorf ( "app.common.noNEInfo " )
}
// 网元主机的SSH客户端
sshClient , err := s . neInfoService . NeRunSSHClient ( neInfo . NeType , neInfo . NeId )
if err != nil {
@@ -39,47 +48,53 @@ func (s *TcpdumpImpl) DumpStart(neType, neId, cmdStr string) (string, error) {
}
defer sshClient . Close ( )
// 是否拥有sudo权限并拼接
withSudo : = ""
if _ , err := sshClient . RunCMD ( "sudo -n uname" ) ; err == nil {
withSudo = "sudo "
}
if msg , err := sshClient . RunCMD ( fmt . Sprintf ( "%s tcpdump --version" , withSudo ) ) ; err != nil {
// stderr: bash: tcpdump: 未找到命令 => exit status 127
// 检查是否安装tcpdump
if msg , err := sshClient . RunCMD ( "sudo tcpdump --version" ) ; err ! = nil {
// bash: tcpdump: command not found
msg = strings . TrimSpace ( msg )
logger . Warn f( "DumpStart err: %s => %s" , msg , err . Error ( ) )
logger . Error f( "DumpStart err: %s => %s" , msg , err . Error ( ) )
return "" , fmt . Errorf ( msg )
}
// 拼装命令
neTypeID := fmt . Sprintf ( "%s_%s" , neInfo . NeType , neInfo . NeId )
timeStr := date . ParseDateToStr ( time . Now ( ) , date . YYYYMMDDHHMMSS )
fileName := fmt . Sprintf ( "%s_%s" , timeStr , neTypeID )
sendCmd := fmt . Sprintf ( "cd /tmp \n %s nohup timeout 30m tcpdump -i any %s -s0 -w %s.pcap > %s.log 2>&1 & \necho $!" , withSudo , cmdStr , fileName , fileName )
// cd /tmp
// sudo nohup timeout 60m tcpdump -i any -n -s 0 -v -w -s0 -w 20240115140822_UDM_001.pcap > 20240115140822_UDM_001.log 2>&1 & echo $!
msg , err := sshClie nt. RunCMD ( sendCmd )
msg = strings . TrimSpace ( msg )
if err ! = nil || strings . HasPrefix ( msg , "stderr:" ) {
logger . Warnf ( "DumpStart err: %s => %s" , msg , err . Error ( ) )
taskCode := time . Now ( ) . Format ( "20060102150405" )
// 存放文件目录 /tmp/omc/tcpdump/udm/001/20240817104241
neDirTemp := fmt . Sprintf ( "/tmp/omc/tcpdump/%s/%s/%s" , strings . ToLower ( neInfo . NeType ) , neInfo . NeId , taskCode )
sshClient . RunCMD ( fmt . Sprintf ( "mkdir -p %s && sudo chmod 777 -R /tmp/omc" , neDirTemp ) )
// 命令拼装
logPath := fmt . Sprintf ( "%s/tcpdump.log" , neDirTemp )
filePath := fmt . Spri ntf ( "%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 /tmp/omc/tcpdump/udm/001/20240817104241/part_%Y-%m-%d_%H:%M:%S.pcap > /tmp/omc/tcpdump/udm/001/20240817104241/tcpdump.log 2>&1 & echo $!
// sudo timeout 60m sudo tcpdump -i any -n -s 0 -v -w /tmp/omc/tcpdump/udm/001/20240817105440/part_2024-08-17_10:54:40.pcap > /tmp/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
}
// 检查进程 ps aux | grep tcpdump
// 强杀 sudo pkill tcpdump
s . tcpdumpPIDMap [ n eTypeID ] = msg
return fileName , err
pidKey := fmt . Sprintf ( "%s_%s_%s" , strings . ToLower ( neInfo . N eType) , neInfo . NeId , taskCode )
dumpPIDMap . Store ( pidKey , outputPID )
return taskCode , err
}
// DumpStop 停止已存在抓包句柄
func ( s * TcpdumpImpl ) DumpStop ( neType , neId , fileNam e string ) ( string , error ) {
func ( s * TcpdumpImpl ) DumpStop ( neType , neId , taskCod e string ) ( string , error ) {
// 查询网元获取IP
neInfo := s . neInfoService . SelectNeInfoByNeTypeAndNeID ( neType , neId )
if neInfo . NeId != neId || neInfo . IP == "" {
return "" , fmt . Errorf ( "noData " )
return "" , fmt . Errorf ( "app.common.noNEInfo " )
}
// 网元主机的SSH客户端
sshClient , err := s . neInfoService . NeRunSSHClient ( neInfo . NeType , neInfo . NeId )
if err != nil {
@@ -87,107 +102,147 @@ func (s *TcpdumpImpl) DumpStop(neType, neId, fileName string) (string, error) {
}
defer sshClient . Close ( )
// 是否拥有sudo权限并拼接
withSudo := ""
if _ , err := sshClient . RunCMD ( "sudo -n uname" ) ; err == nil {
withSudo = "sudo "
}
// 是否存在进程
neTypeID := fmt . Sprintf ( "%s_%s" , neInfo . NeType , neInfo . NeId )
pid , ok := s . tcpdumpPIDMap [ neTypeID ]
// 是否存在执行过的进程
pidKey := fmt . Sprintf ( "%s_%s_%s" , strings . ToLower ( neInfo . NeType ) , neInfo . NeId , taskCode )
pid , ok := dumpPIDMap . Load ( pidKey )
if ! ok || pid == "" {
return "" , fmt . Errorf ( "tcpdump is not running" )
}
defer dumpPIDMap . Delete ( pidKey )
// 查看日志
viewLogFile := ""
if fileName != "" && strings . Contains ( fileName , neTypeID ) {
viewLogFile = fmt . Sprintf ( "\n cat %s.log" , fileName )
}
// 拼装命令
sendCmd := fmt . Sprintf ( "cd /tmp \n %s kill %s %s" , withSudo , pid , viewLogFile )
msg , err := sshClient . RunCMD ( sendCmd )
delete ( s . tcpdumpPIDMap , neTypeID )
if err != nil || strings . HasPrefix ( msg , "stderr:" ) {
logger . Warnf ( "DumpStop err: %s => %s" , strings . TrimSpace ( msg ) , err . Error ( ) )
// 存放文件目录 /tmp/omc/tcpdump/udm/001/20240817104241
neDirTemp := fmt . Sprintf ( "/tmp/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 cat %s/tcpdump.log" , pid , neDirTemp )
// pids=$(pgrep -P 1914341) && [ -n "$pids" ] && sudo kill $pids;sudo timeout 2s cat tcpdump.log
output , err := sshClient . RunCMD ( sendCmd )
if err != nil || strings . HasPrefix ( output , "stderr:" ) {
logger . Warnf ( "DumpStop err: %s => %s" , strings . TrimSpace ( output ) , err . Error ( ) )
return "" , err
}
return msg , nil
return output , nil
}
// DumpUPF UPF标准版抓包
func ( s * TcpdumpImpl ) DumpUPF ( neType , neId , cmdStr string ) ( string , string , error ) {
// DumpDownload 抓包文件网元端复制到本地输出zip文件
func ( s * TcpdumpImpl ) DumpDownload ( neType , neId , taskCode string ) ( string , error ) {
// 查询网元获取IP
neInfo := s . neInfoService . SelectNeInfoByNeTypeAndNeID ( neType , neId )
if neInfo . NeId != neId || neInfo . IP == "" {
return "" , "" , fmt . Errorf ( "noData " )
return "" , fmt . Errorf ( "app.common.noNEInfo " )
}
// 网元主机的SSH客户端
sshClient , err := s . neInfoService . NeRunSSHClient ( neInfo . NeType , neInfo . NeId )
if err != nil {
return "" , "" , err
return "" , err
}
defer sshClient . Close ( )
// 网元主机的SSH客户端进行文件传输
sftpClient , err := sshClient . NewClientSFTP ( )
if err != nil {
return "" , fmt . Errorf ( "ne info sftp client err" )
}
defer sftpClient . Close ( )
// 是否拥有sudo权限并拼接
withSudo := ""
if _ , err := sshClient . RunCMD ( "sudo -n uname" ) ; err == nil {
withSudo = "sudo "
neTypeLower := strings . ToLower ( neInfo . NeType )
// 网管本地路径
localDirPath := fmt . Sprintf ( "/tmp/omc/tcpdump/%s/%s" , neTypeLower , neInfo . NeId )
if runtime . GOOS = = "windows" {
localDirPath = fmt . Sprintf ( "C:%s" , localDirPath )
}
if msg , err := sshClient . RunCMD ( fmt . Sprintf ( "%s expect -version" , withSudo ) ) ; err != nil {
// stderr: bash: expect: 未找到命令 => exit status 127
msg = strings . TrimSpace ( msg )
logger . Warnf ( "DumpUPF err: %s => %s" , msg , err . Error ( ) )
return "" , "" , fmt . Errorf ( msg )
// 网元pcap目录 /tmp/omc/tcpdump/udm/001/20240817104241
sshClient . RunCMD ( "mkdir -p /tmp/omc && sudo chmod 777 -R /tmp/omc" )
neDirTemp : = fmt . Sprintf ( "/tmp/omc/tcpdump/%s/%s/%s" , neTypeLower , neInfo . NeId , taskCode )
// 网元端复制到本地
localDirFilePath := filepath . Join ( localDirPath , taskCode )
if err = sftpClient . CopyDirRemoteToLocal ( neDirTemp , localDirFilePath ) ; err != nil {
return "" , fmt . Errorf ( "copy tcpdump file err" )
}
// 拼装命令
neTypeID := fmt . Sprintf ( "%s_%s" , neInfo . NeType , neInfo . NeId )
timeStr := d ate . ParseDateToStr ( time . Now ( ) , date . YYYYMMDDHHMMSS )
fileName := fmt . Sprintf ( "%s_%s" , timeStr , neTypeID )
// UPF标准版本telnet脚本
scriptStr := "set pcapCmd [lindex $argv 0]\nspawn telnet " + neInfo . IP + " 5002\nexpect \"upfd1# \"\nsend \"$pcapCmd\\n\"\nexpect \"upfd1# \"\nsend \"quit\\n\"\nexpect \"eof\""
// scriptStr := "set pcapCmd [lindex $argv 0]\nspawn telnet localhost 5002\nexpect \"upfd1# \"\nsend \"$pcapCmd\\n\"\nexpect \"upfd1# \"\nsend \"quit\\n\"\nexpect \"eof\""
writePcapFile := fmt . Sprintf ( "echo '%s' > pcapUPF.sh\n %s chmod +x pcapUPF.sh" , scriptStr , withSudo )
writeLogFile := fmt . Sprintf ( "> %s.log 2>&1 \ncat %s.log" , fileName , fileName )
// 压缩zip文件名
zipFileName := fmt . Sprintf ( "%s-%s-pcap-%s.zip" , neTypeLower , neInfo . NeId , taskCode )
zipFilePath := filep ath . Join ( localDirPath , zipFileName )
if err := file . CompressZipByDir ( zipFilePath , localDirFilePath ) ; err != nil {
return "" , fmt . Errorf ( "compress zip err" )
}
_ = os . RemoveAll ( localDirFilePath ) // 删除本地临时目录
return zipFilePath , nil
}
// UPFTrace UPF标准版内部抓包
func ( s * TcpdumpImpl ) UPFTrace ( neType , neId , cmdStr string ) ( string , error ) {
// 命令检查
if strings . Contains ( cmdStr , "file" ) {
return "" , fmt . Errorf ( "command cannot contain file" )
}
// 查询网元获取IP
neInfo := s . neInfoService . SelectNeInfoByNeTypeAndNeID ( 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结尾是停止抓包, 不需要写文件
pcapCmd := cmdStr
if ! strings . HasSuffix ( pcapCmd , "off" ) {
pcapCmd = fmt . Sprintf ( "%s file %s.pcap " , cmdStr , fileName )
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 )
}
sendCmd := fmt . Sprintf ( "cd /tmp \n%s\n expect ./pcapUPF.sh '%s' %s" , writePcapFile , pcapCmd , writeLogFile )
// cd /tm p
// echo '' >
// expect ./cap.sh > pcapUPF.sh
// sudo chmod +x pcapUPF.sh
// expect ./cap.sh 'pcap dispatch trace off' > 20240115165701_UDM_001.log 2>&1
// cat 20240115165701_UDM_001.log
msg , err := sshClient . RunCMD ( sendCmd )
msg = strings . TrimSpace ( msg )
if err != nil || strings . HasPrefix ( msg , "stderr:" ) {
logger . Warnf ( "DumpUPF err: %s => %s" , msg , err . Error ( ) )
return "" , "" , err
// 发送命令 UPF内部默认输出路径/tmp只能写文件名
// pcap trace rx tx max 100000 intfc any file upf_test.pca p
// pcap trace rx tx off
output , err := telnetClient . RunCMD ( pcapCmd )
if err != nil {
logger . Warnf ( "DumpUPF err: %s => %s" , output , err . Error ( ) )
return "" , err
}
if strings . Contains ( msg , "Unable to connect to remote host" ) {
return "" , "" , fmt . Errorf ( "connection refused" )
// 结果截取
arr := strings . Split ( output , "\r\n" )
if len ( arr ) == 2 {
return "" , fmt . Errorf ( "trace pacp run failed" )
}
// 以off结尾是停止抓包, 不需要写文件
if strings . HasSuffix ( pcapCmd , "off" ) {
if strings . Contains ( msg , "Write " ) {
lastTmpIndex := strings . LastIndex ( msg , "/tmp/" )
text := msg [ lastTmpIndex + 5 : ]
extensionIndex := strings . LastIndex ( text , ".pcap" )
if extensionIndex != - 1 {
fileName = text [ : extensionIndex ]
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" )
}
} else {
fileName = ""
return matches [ 0 ] , nil
}
}
return fileName , msg , err
return "trace pacp running" , nil
}