tcpdump策略增加立即执行、优化tcpdump探测
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
package com.tongran.agent.client.core.eo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class TcpdumpEO {
|
||||
/** 开启探测(0:否,1:是) */
|
||||
private Integer detectFlag;
|
||||
|
||||
/** 探测频率1 每天 2立即执行 */
|
||||
private String frequency;
|
||||
|
||||
/** 探测时间列表(多个时间用,分隔) */
|
||||
private String detectTimes;
|
||||
}
|
||||
@@ -52,4 +52,17 @@ public class SchedulerConfig {
|
||||
executor.initialize(); // 重要:必须调用initialize()
|
||||
return executor;
|
||||
}
|
||||
// 在SchedulerConfig中添加专用TCPDUMP线程池
|
||||
@Bean("tcpdumpTaskExecutor")
|
||||
public ThreadPoolTaskExecutor tcpdumpTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(2); // 核心线程数
|
||||
executor.setMaxPoolSize(10); // 最大线程数(根据网卡数量调整)
|
||||
executor.setQueueCapacity(20); // 队列容量
|
||||
executor.setKeepAliveSeconds(300);// 空闲线程存活时间
|
||||
executor.setThreadNamePrefix("tcpdump-worker-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
package com.tongran.agent.client.scheduler.task;
|
||||
|
||||
import com.tongran.agent.client.utils.AssertLog;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.config.CronTask;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
@@ -20,6 +24,9 @@ public class SpecificTimeTaskConfig implements SchedulingConfigurer {
|
||||
private ScheduledTaskRegistrar taskRegistrar;
|
||||
private final Map<String, ScheduledFuture<?>> taskFutures = new ConcurrentHashMap<>();
|
||||
private final Map<String, Object> taskMap = new ConcurrentHashMap<>();
|
||||
@Autowired
|
||||
@Qualifier("tcpdumpTaskExecutor")
|
||||
private ThreadPoolTaskExecutor tcpdumpExecutor;
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
@@ -31,13 +38,36 @@ public class SpecificTimeTaskConfig implements SchedulingConfigurer {
|
||||
*/
|
||||
public void addCronTask(String taskId, Runnable task, String cronExpression) {
|
||||
removeTaskIfExists(taskId);
|
||||
|
||||
|
||||
CronTask cronTask = new CronTask(task, cronExpression);
|
||||
ScheduledFuture<?> future = taskRegistrar.getScheduler().schedule(
|
||||
cronTask.getRunnable(),
|
||||
cronTask.getRunnable(),
|
||||
cronTask.getTrigger()
|
||||
);
|
||||
|
||||
|
||||
taskMap.put(taskId, cronTask);
|
||||
taskFutures.put(taskId, future);
|
||||
}
|
||||
public void addTcpdumpCronTask(String taskId, Runnable task, String cronExpression) {
|
||||
removeTaskIfExists(taskId);
|
||||
|
||||
// 使用轻量级调度,实际任务交给专用线程池
|
||||
CronTask cronTask = new CronTask(() -> {
|
||||
// 异步执行,不阻塞定时任务线程
|
||||
tcpdumpExecutor.execute(() -> {
|
||||
try {
|
||||
task.run();
|
||||
} catch (Exception e) {
|
||||
AssertLog.error("任务执行异常 {}: {}", taskId, e.getMessage());
|
||||
}
|
||||
});
|
||||
}, cronExpression);
|
||||
|
||||
ScheduledFuture<?> future = taskRegistrar.getScheduler().schedule(
|
||||
cronTask.getRunnable(),
|
||||
cronTask.getTrigger()
|
||||
);
|
||||
|
||||
taskMap.put(taskId, cronTask);
|
||||
taskFutures.put(taskId, future);
|
||||
}
|
||||
@@ -80,6 +110,19 @@ public class SpecificTimeTaskConfig implements SchedulingConfigurer {
|
||||
String cronExpression = String.format("%d %d %d * * ?", second, minute, hour);
|
||||
addCronTask(taskId, task, cronExpression);
|
||||
}
|
||||
public void addDailyTimeTcpdumpTask(String taskId, Runnable task, String time) {
|
||||
String[] timeParts = time.split(":");
|
||||
if (timeParts.length < 2) {
|
||||
throw new IllegalArgumentException("时间格式应为 HH:mm 或 HH:mm:ss");
|
||||
}
|
||||
|
||||
int hour = Integer.parseInt(timeParts[0]);
|
||||
int minute = Integer.parseInt(timeParts[1]);
|
||||
int second = timeParts.length > 2 ? Integer.parseInt(timeParts[2]) : 0;
|
||||
|
||||
String cronExpression = String.format("%d %d %d * * ?", second, minute, hour);
|
||||
addTcpdumpCronTask(taskId, task, cronExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加延迟任务
|
||||
|
||||
+152
-61
@@ -18,6 +18,8 @@ import com.tongran.agent.client.utils.AssertLog;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -30,6 +32,7 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@@ -44,6 +47,9 @@ public class SpecificTimeTaskService {
|
||||
|
||||
@Resource
|
||||
private SpecificTimeTaskConfig taskConfig;
|
||||
@Resource
|
||||
@Qualifier("tcpdumpTaskExecutor")
|
||||
private ThreadPoolTaskExecutor tcpdumpExecutor;
|
||||
|
||||
public SpecificTimeTaskService() {
|
||||
this.sessionManager = SessionManager.getInstance();
|
||||
@@ -54,7 +60,7 @@ public class SpecificTimeTaskService {
|
||||
Runnable task = createTcpdumpTaskRunnable(request);
|
||||
try {
|
||||
if (request.getTime() != null) {
|
||||
taskConfig.addDailyTimeTask(taskId, task, request.getTime());
|
||||
taskConfig.addDailyTimeTcpdumpTask(taskId, task, request.getTime());
|
||||
} else {
|
||||
throw new IllegalArgumentException("必须指定一种时间方式");
|
||||
}
|
||||
@@ -63,6 +69,22 @@ public class SpecificTimeTaskService {
|
||||
throw new RuntimeException("创建任务失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
public CompletableFuture<Boolean> executeTcpdumpImmediatelyWithResult(SpecificTimeRequest request) {
|
||||
String taskId = request.getTaskId() != null ?
|
||||
request.getTaskId() : "tcpdump-immediate-" + System.currentTimeMillis();
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
AssertLog.info("立即执行TCPDUMP任务开始: {}", taskId);
|
||||
executeTcpdump(request);
|
||||
AssertLog.info("立即执行TCPDUMP任务完成: {}", taskId);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
AssertLog.error("立即执行TCPDUMP任务异常 {}: {}", taskId, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}, tcpdumpExecutor); // 使用专用线程池
|
||||
}
|
||||
/**
|
||||
* 创建指定时间任务
|
||||
*/
|
||||
@@ -89,15 +111,19 @@ public class SpecificTimeTaskService {
|
||||
throw new RuntimeException("创建任务失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Runnable createTcpdumpTaskRunnable(SpecificTimeRequest request) {
|
||||
return () -> {
|
||||
System.out.println("执行定时任务: " + request.getTaskName());
|
||||
System.out.println("执行时间: " + LocalDateTime.now());
|
||||
System.out.println("-----------------------------------");
|
||||
|
||||
// 具体的业务逻辑
|
||||
executeTcpdump(request);
|
||||
// 直接执行,因为已经在专用线程池中了
|
||||
try {
|
||||
System.out.println("执行定时任务: " + request.getTaskName());
|
||||
System.out.println("执行时间: " + LocalDateTime.now());
|
||||
System.out.println("-----------------------------------");
|
||||
|
||||
executeTcpdump(request);
|
||||
} catch (Exception e) {
|
||||
AssertLog.error("TCPDUMP任务执行异常: {}", e.getMessage(), e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -147,74 +173,139 @@ public class SpecificTimeTaskService {
|
||||
/**
|
||||
* 对单个网卡进行抓包和分析
|
||||
*/
|
||||
private List<IpCount> captureAndAnalyzeInterface(SpecificTimeRequest request, String timestamp, String interfaceName) {
|
||||
// 检查目录
|
||||
if (AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getTempPcapPath())) {
|
||||
String pcapFileName = properties.getTempPcapPath() + timestamp + "_" + interfaceName + ".pcap";
|
||||
List<IpCount> ipCountList = new ArrayList<>();
|
||||
try {
|
||||
// 第一步:执行tcpdump命令捕获数据
|
||||
ProcessBuilder captureBuilder = new ProcessBuilder();
|
||||
captureBuilder.command("timeout", "10", "tcpdump", "-i", interfaceName,
|
||||
"-nn", "-n", "-w", pcapFileName);
|
||||
private List<IpCount> captureAndAnalyzeInterface(SpecificTimeRequest request,
|
||||
String timestamp, String interfaceName) {
|
||||
|
||||
Process captureProcess = captureBuilder.start();
|
||||
int captureExitCode = captureProcess.waitFor();
|
||||
if (!AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getTempPcapPath())) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (captureExitCode != 0 && captureExitCode != 124) {
|
||||
AssertLog.error("Tcpdump pcap包生成失败,网卡名称 {} with exit code: {}", interfaceName, captureExitCode);
|
||||
return ipCountList;
|
||||
String pcapFileName = properties.getTempPcapPath() + timestamp + "_" + interfaceName + ".pcap";
|
||||
List<IpCount> ipCountList = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 第一步:执行tcpdump抓包(带超时控制)
|
||||
CompletableFuture<Boolean> captureFuture = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
ProcessBuilder captureBuilder = new ProcessBuilder();
|
||||
// 设置抓包时间,根据实际需求调整
|
||||
captureBuilder.command("timeout", "10", "tcpdump",
|
||||
"-i", interfaceName, "-nn", "-n", "-w", pcapFileName);
|
||||
|
||||
Process captureProcess = captureBuilder.start();
|
||||
int captureExitCode = captureProcess.waitFor();
|
||||
|
||||
if (captureExitCode != 0 && captureExitCode != 124) {
|
||||
AssertLog.error("Tcpdump抓包失败,网卡: {},退出码: {}", interfaceName, captureExitCode);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
AssertLog.error("抓包过程异常 {}: {}", interfaceName, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// 等待文件写入完成
|
||||
Thread.sleep(20000);
|
||||
// 等待抓包完成(最长7分钟)
|
||||
boolean captureSuccess = captureFuture.get(20, TimeUnit.SECONDS);
|
||||
|
||||
// 检查文件是否存在且不为空
|
||||
File pcapFile = new File(pcapFileName);
|
||||
if (!pcapFile.exists() || pcapFile.length() == 0) {
|
||||
AssertLog.warn("PCAP file is empty or does not exist for interface: {}", interfaceName);
|
||||
return ipCountList;
|
||||
}
|
||||
if (!captureSuccess) {
|
||||
return ipCountList;
|
||||
}
|
||||
|
||||
// 第二步:分析pcap文件并统计目标IP
|
||||
ProcessBuilder analyzeBuilder = new ProcessBuilder();
|
||||
analyzeBuilder.command("bash", "-c",
|
||||
"tcpdump -r " + pcapFileName + " -nn 2>/dev/null | " +
|
||||
"awk '/^[0-9]/ && ($2 == \"IP\" || $2 == \"IP6\") {dest=$5; if(match(dest,/^([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)/)) {dest=substr(dest,RSTART,RLENGTH)} else if(match(dest,/([0-9a-fA-F:]+)/)) {dest=substr(dest,RSTART,RLENGTH); gsub(/:[0-9]+$/,\"\",dest)} print dest}' | " +
|
||||
"sort | uniq -c | sort -rn");
|
||||
// 第二步:分析PCAP文件
|
||||
CompletableFuture<List<IpCount>> analyzeFuture = CompletableFuture.supplyAsync(() -> {
|
||||
return analyzePcapFile(pcapFileName, interfaceName);
|
||||
});
|
||||
|
||||
Process analyzeProcess = analyzeBuilder.start();
|
||||
// 分析阶段超时时间(2分钟应该足够)
|
||||
ipCountList = analyzeFuture.get(2, TimeUnit.MINUTES);
|
||||
|
||||
} catch (TimeoutException e) {
|
||||
AssertLog.warn("网卡 {} 抓包或分析超时,跳过处理", interfaceName);
|
||||
} catch (Exception e) {
|
||||
AssertLog.error("处理网卡 {} 时异常: {}", interfaceName, e.getMessage());
|
||||
} finally {
|
||||
// 确保清理临时文件
|
||||
cleanupTempFile(pcapFileName);
|
||||
}
|
||||
|
||||
return ipCountList;
|
||||
}
|
||||
/**
|
||||
* 分析PCAP文件并统计IP信息
|
||||
*/
|
||||
private List<IpCount> analyzePcapFile(String pcapFileName, String interfaceName) {
|
||||
List<IpCount> ipCountList = new ArrayList<>();
|
||||
|
||||
File pcapFile = new File(pcapFileName);
|
||||
if (!pcapFile.exists() || pcapFile.length() == 0) {
|
||||
AssertLog.warn("PCAP文件不存在或为空: {}", pcapFileName);
|
||||
return ipCountList;
|
||||
}
|
||||
|
||||
try {
|
||||
ProcessBuilder analyzeBuilder = new ProcessBuilder();
|
||||
// 使用更可靠的命令分析PCAP文件
|
||||
analyzeBuilder.command("bash", "-c",
|
||||
"tcpdump -r " + pcapFileName + " -nn 2>/dev/null | " +
|
||||
"awk '/^[0-9]/ && ($2 == \"IP\" || $2 == \"IP6\") { " +
|
||||
" dest=$5; " +
|
||||
" if(match(dest,/^([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)/)) { " +
|
||||
" dest=substr(dest,RSTART,RLENGTH) " +
|
||||
" } else if(match(dest,/([0-9a-fA-F:]+)/)) { " +
|
||||
" dest=substr(dest,RSTART,RLENGTH); gsub(/:[0-9]+$/,\"\",dest) " +
|
||||
" } " +
|
||||
" print dest " +
|
||||
"}' | " +
|
||||
"sort | uniq -c | sort -rn");
|
||||
|
||||
Process analyzeProcess = analyzeBuilder.start();
|
||||
|
||||
// 读取分析结果
|
||||
StringBuilder result = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(analyzeProcess.getInputStream()))) {
|
||||
|
||||
// 读取分析结果
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(analyzeProcess.getInputStream()));
|
||||
StringBuilder result = new StringBuilder();
|
||||
String line;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
result.append(line).append("\n");
|
||||
}
|
||||
|
||||
int analyzeExitCode = analyzeProcess.waitFor();
|
||||
|
||||
if (analyzeExitCode == 0) {
|
||||
AssertLog.debug("Tcpdump analysis completed successfully for interface: {}", interfaceName);
|
||||
ipCountList = parseTcpdumpOutput(result.toString());
|
||||
} else {
|
||||
AssertLog.error("Tcpdump analysis failed for interface {} with exit code: {}", interfaceName, analyzeExitCode);
|
||||
}
|
||||
|
||||
// 清理临时文件
|
||||
if (pcapFile.exists()) {
|
||||
pcapFile.delete();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
AssertLog.error("Error capturing interface {}: {}", interfaceName, e.getMessage());
|
||||
}
|
||||
|
||||
return ipCountList;
|
||||
int analyzeExitCode = analyzeProcess.waitFor();
|
||||
|
||||
if (analyzeExitCode == 0) {
|
||||
AssertLog.debug("PCAP分析完成: {},文件大小: {} bytes", interfaceName, pcapFile.length());
|
||||
ipCountList = parseTcpdumpOutput(result.toString());
|
||||
} else {
|
||||
AssertLog.error("PCAP分析失败: {},退出码: {}", interfaceName, analyzeExitCode);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
AssertLog.error("分析PCAP文件异常 {}: {}", interfaceName, e.getMessage());
|
||||
}
|
||||
|
||||
return ipCountList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理临时文件
|
||||
*/
|
||||
private void cleanupTempFile(String fileName) {
|
||||
try {
|
||||
File file = new File(fileName);
|
||||
if (file.exists()) {
|
||||
boolean deleted = file.delete();
|
||||
if (!deleted) {
|
||||
AssertLog.warn("无法删除临时文件: {}", fileName);
|
||||
} else {
|
||||
AssertLog.debug("已清理临时文件: {}", fileName);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
AssertLog.warn("清理临时文件异常: {}", e.getMessage());
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -940,11 +940,42 @@ public class AgentServiceImpl implements AgentService {
|
||||
}
|
||||
if(jsonObject.containsKey("tcpdumpTimes")){
|
||||
String tcpdumpTimes = jsonObject.getString("tcpdumpTimes");
|
||||
handleTcpdump(tcpdumpTimes);
|
||||
TcpdumpEO tcpdumpEO = JSON.parseObject(tcpdumpTimes, TcpdumpEO.class);
|
||||
if(tcpdumpEO.getDetectFlag() == 1){
|
||||
if("1".equals(tcpdumpEO.getFrequency())){
|
||||
handleTcpdump(tcpdumpEO);
|
||||
}else if("2".equals(tcpdumpEO.getFrequency())){
|
||||
// 立即执行
|
||||
SpecificTimeRequest request = SpecificTimeRequest.builder()
|
||||
.taskId("tcpdumpnow")
|
||||
.taskName("tcpdumpnow")
|
||||
.clientId(GlobalConfig.CLIENT_ID)
|
||||
.build();
|
||||
taskService.executeTcpdumpImmediatelyWithResult(request);
|
||||
}
|
||||
}else{
|
||||
// 1. 将tcpdump探测时间写入配置文件
|
||||
String[] lines = {
|
||||
"# tcpdump探测时间配置文件",
|
||||
"# 生成时间: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),
|
||||
"# tcpdump探测时间arr",
|
||||
"tcpdumpTimes=",
|
||||
};
|
||||
|
||||
// 检查目录并写入配置文件
|
||||
if (AdvancedAsyncDownloader.createSingleDirectoryIfNotExists(properties.getConfPath())) {
|
||||
AgentUtil.bufferedWriter(properties.getConfPath() + "/tcpdumptimes.conf", lines);
|
||||
}
|
||||
|
||||
// 更新全局变量
|
||||
GlobalConfig.TCPDUMP_TIMES = tcpdumpTimes;
|
||||
taskService.cancleTcpdumpSpecificTimeTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTcpdump(String tcpdumpTimes) {
|
||||
private void handleTcpdump(TcpdumpEO tcpdumpEo) {
|
||||
String tcpdumpTimes = tcpdumpEo.getDetectTimes();
|
||||
if (!tcpdumpTimes.equals(GlobalConfig.TCPDUMP_TIMES)) {
|
||||
// 1. 将tcpdump探测时间写入配置文件
|
||||
String[] lines = {
|
||||
@@ -2597,7 +2628,7 @@ public class AgentServiceImpl implements AgentService {
|
||||
props.load(input);
|
||||
|
||||
String tcpdumpTimes = props.getProperty("tcpdumpTimes");
|
||||
if(!"".equals(tcpdumpTimes)){
|
||||
if(tcpdumpTimes != null && !tcpdumpTimes.trim().isEmpty()){
|
||||
String[] tcpdumpTimeArr = tcpdumpTimes.split(",");
|
||||
for (String tcpdumpTime : tcpdumpTimeArr) {
|
||||
SpecificTimeRequest request = SpecificTimeRequest.builder()
|
||||
|
||||
@@ -17,7 +17,7 @@ logging:
|
||||
netty:
|
||||
server:
|
||||
host: 120.211.95.173
|
||||
port: 56620
|
||||
port: 6620
|
||||
client:
|
||||
client-id: client-001
|
||||
reconnect-interval: 5
|
||||
|
||||
@@ -6,7 +6,7 @@ spring:
|
||||
matching-strategy: ant_path_matcher
|
||||
application:
|
||||
name: tr-agent-client
|
||||
version: 1.1.13
|
||||
version: 1.1.14
|
||||
conf-path: /usr/local/tongran/conf
|
||||
script-path: /usr/local/tongran/sbin
|
||||
tmp-path: /usr/local/tongran/tmp
|
||||
|
||||
Reference in New Issue
Block a user