mtr探测优化、临时路由添加异常完善
This commit is contained in:
@@ -9,11 +9,9 @@ import java.util.List;
|
||||
public class MtrVO {
|
||||
private String targetIp; // 目标IP
|
||||
private String clientId; // 对应的clientId
|
||||
private Long policyId; // 策略ID
|
||||
private double firstLossPercent; // 第一次探测丢包率
|
||||
private double finalLossPercent; // 最终丢包率
|
||||
private long timestamp; // 探测时间戳
|
||||
private boolean hasRetry; // 是否重试
|
||||
private String errorMsg; // 错误信息
|
||||
private List<HopInfoVO> hopInfos; // 每跳路由信息列表
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.tongran.agent.client.scheduler.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* MTR线程池配置(优化版)
|
||||
*/
|
||||
@Configuration
|
||||
public class MtrThreadPoolConfig {
|
||||
|
||||
@Bean("mtrExecutor")
|
||||
public ExecutorService mtrExecutor() {
|
||||
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2; // 根据CPU核心数动态设置
|
||||
int maxPoolSize = corePoolSize * 2;
|
||||
int queueCapacity = 200;
|
||||
long keepAliveTime = 30L; // 缩短空闲线程存活时间
|
||||
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||
corePoolSize,
|
||||
maxPoolSize,
|
||||
keepAliveTime,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(queueCapacity),
|
||||
new MtrThreadFactory(),
|
||||
new MtrRejectedExecutionHandler() // 自定义拒绝策略
|
||||
);
|
||||
|
||||
// 允许核心线程超时销毁
|
||||
executor.allowCoreThreadTimeOut(true);
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* MTR线程工厂
|
||||
*/
|
||||
private static class MtrThreadFactory implements ThreadFactory {
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
private final String namePrefix = "mtr-worker-";
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread thread = new Thread(r, namePrefix + threadNumber.getAndIncrement());
|
||||
thread.setDaemon(false); // 设置为非守护线程
|
||||
thread.setPriority(Thread.NORM_PRIORITY);
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MTR拒绝策略
|
||||
*/
|
||||
private static class MtrRejectedExecutionHandler implements RejectedExecutionHandler {
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||
// 记录拒绝的线程信息
|
||||
System.err.println("MTR任务被拒绝,线程池状态: " +
|
||||
"活跃线程数: " + executor.getActiveCount() +
|
||||
", 队列大小: " + executor.getQueue().size());
|
||||
|
||||
// 使用调用者线程执行,避免任务丢失
|
||||
if (!executor.isShutdown()) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,57 @@
|
||||
package com.tongran.agent.client.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.tongran.agent.client.core.eo.MtrPolicyConfigEO;
|
||||
import com.tongran.agent.client.core.vo.HopInfoVO;
|
||||
import com.tongran.agent.client.core.vo.MtrVO;
|
||||
import com.tongran.agent.client.core.vo.NetVO;
|
||||
import com.tongran.agent.client.service.MtrService;
|
||||
import com.tongran.agent.client.utils.AgentUtil;
|
||||
import com.tongran.agent.client.utils.AssertLog;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.hardware.NetworkIF;
|
||||
import oshi.util.FormatUtil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* MTR探测服务实现类
|
||||
* 支持异步批量探测,解析每一跳的路由信息
|
||||
*/
|
||||
@Service
|
||||
public class MtrServiceImpl implements MtrService {
|
||||
|
||||
private static final int MTR_TIMEOUT_MINUTES = 3;
|
||||
private static final int RETRY_DELAY_MS = 30000;
|
||||
private static final double LOSS_PERCENT_THRESHOLD = 5.0;
|
||||
private static final int MTR_PACKET_COUNT = 10;
|
||||
|
||||
// 内网IP地址段正则表达式
|
||||
private static final Pattern PRIVATE_IP_PATTERN = Pattern.compile(
|
||||
"^(10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.|127\\.|169\\.254\\.|0\\.|255\\.)"
|
||||
);
|
||||
|
||||
private final OnlineIPQueryService onlineIPQueryService;
|
||||
private final ExecutorService mtrExecutor;
|
||||
|
||||
// 通过构造器注入线程池
|
||||
public MtrServiceImpl(OnlineIPQueryService onlineIPQueryService,
|
||||
@Qualifier("mtrExecutor") ExecutorService mtrExecutor) {
|
||||
this.onlineIPQueryService = onlineIPQueryService;
|
||||
this.mtrExecutor = mtrExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行MTR探测列表 - 异步版本
|
||||
* @param timestamp 探测时间戳
|
||||
* @param mtrPolicyConfigEOList 探测策略配置列表
|
||||
* @return MTR探测结果列表
|
||||
*/
|
||||
@Override
|
||||
public List<MtrVO> mtrList(long timestamp, List<MtrPolicyConfigEO> mtrPolicyConfigEOList) {
|
||||
List<CompletableFuture<MtrVO>> futures = new ArrayList<>();
|
||||
List<MtrVO> mtrResults = new ArrayList<>();
|
||||
@@ -37,37 +61,23 @@ public class MtrServiceImpl implements MtrService {
|
||||
return mtrResults;
|
||||
}
|
||||
|
||||
// 为每个IP创建异步任务
|
||||
// 为每个IP创建异步探测任务
|
||||
for (MtrPolicyConfigEO policy : mtrPolicyConfigEOList) {
|
||||
if (!isPolicyTimeValid(policy, timestamp)) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, List<String>> entry : policy.getClientIdToIpsMap().entrySet()) {
|
||||
String clientId = entry.getKey();
|
||||
List<String> ips = entry.getValue();
|
||||
if (ips != null) {
|
||||
for (String ip : ips) {
|
||||
String targetIp = ip.trim();
|
||||
if (!targetIp.isEmpty()) {
|
||||
// 异步执行每个IP的探测
|
||||
CompletableFuture<MtrVO> future = CompletableFuture.supplyAsync(() ->
|
||||
executeSingleMtrProbe(targetIp, clientId, timestamp)
|
||||
);
|
||||
futures.add(future);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
createMtrTasksForPolicy(policy, timestamp, futures);
|
||||
}
|
||||
|
||||
// 等待所有任务完成
|
||||
// 等待所有异步任务完成
|
||||
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
|
||||
futures.toArray(new CompletableFuture[0])
|
||||
);
|
||||
|
||||
// 获取所有结果(设置超时时间,比如2分钟)
|
||||
allFutures.get(2, TimeUnit.MINUTES);
|
||||
// 设置总超时时间
|
||||
allFutures.get(MTR_TIMEOUT_MINUTES, TimeUnit.MINUTES);
|
||||
|
||||
// 收集所有结果
|
||||
for (CompletableFuture<MtrVO> future : futures) {
|
||||
MtrVO result = future.get();
|
||||
if (result != null) {
|
||||
@@ -82,20 +92,59 @@ public class MtrServiceImpl implements MtrService {
|
||||
return mtrResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为策略创建MTR探测任务
|
||||
*/
|
||||
private void createMtrTasksForPolicy(MtrPolicyConfigEO policy, long timestamp,
|
||||
List<CompletableFuture<MtrVO>> futures) {
|
||||
for (Map.Entry<String, List<String>> entry : policy.getClientIdToIpsMap().entrySet()) {
|
||||
String clientId = entry.getKey();
|
||||
List<String> ips = entry.getValue();
|
||||
|
||||
if (ips != null) {
|
||||
for (String ip : ips) {
|
||||
String targetIp = ip.trim();
|
||||
if (!targetIp.isEmpty() && !isPrivateIp(targetIp)) {
|
||||
CompletableFuture<MtrVO> future = CompletableFuture.supplyAsync(() ->
|
||||
executeSingleMtrProbe(targetIp, clientId, timestamp), mtrExecutor
|
||||
);
|
||||
futures.add(future);
|
||||
} else if (isPrivateIp(targetIp)) {
|
||||
AssertLog.info("跳过内网IP探测: {}", targetIp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为内网IP
|
||||
*/
|
||||
private boolean isPrivateIp(String ip) {
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查常见的非IP地址情况
|
||||
if (ip.equals("???") || ip.equals("*") || ip.equals("localhost")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 使用正则表达式匹配内网IP段
|
||||
return PRIVATE_IP_PATTERN.matcher(ip).find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查策略时间是否有效
|
||||
*/
|
||||
private boolean isPolicyTimeValid(MtrPolicyConfigEO policy, long currentTimestamp) {
|
||||
try {
|
||||
// 将时间戳转换为Date对象进行比较
|
||||
Date currentTime = new Date(currentTimestamp * 1000);
|
||||
Date startTime = policy.getStartTime();
|
||||
Date endTime = policy.getEndTime();
|
||||
|
||||
// 开始时间为空表示立即开始
|
||||
// 开始时间为空表示立即开始,结束时间为空表示永不过期
|
||||
boolean startValid = startTime == null || !currentTime.before(startTime);
|
||||
// 结束时间为空表示永不过期
|
||||
boolean endValid = endTime == null || !currentTime.after(endTime);
|
||||
|
||||
return startValid && endValid;
|
||||
@@ -109,83 +158,73 @@ public class MtrServiceImpl implements MtrService {
|
||||
* 执行单个IP的MTR探测
|
||||
*/
|
||||
private MtrVO executeSingleMtrProbe(String targetIp, String clientId, long timestamp) {
|
||||
// 构建IP到clientId的反向映射
|
||||
MtrVO mtrVO = new MtrVO();
|
||||
mtrVO.setClientId(clientId);
|
||||
mtrVO.setTargetIp(targetIp);
|
||||
mtrVO.setTimestamp(timestamp);
|
||||
MtrVO mtrVO = createMtrVO(targetIp, clientId, timestamp);
|
||||
|
||||
try {
|
||||
// 第一次MTR探测
|
||||
double firstLossPercent = executeMtrCommand(targetIp);
|
||||
List<HopInfoVO> firstHops = executeMtrCommand(targetIp);
|
||||
double firstLossPercent = calculateFinalLossPercent(firstHops);
|
||||
mtrVO.setFirstLossPercent(firstLossPercent);
|
||||
mtrVO.setHopInfos(firstHops);
|
||||
|
||||
// 如果丢包率超过5%,30秒后重试
|
||||
if (firstLossPercent > 5.0) {
|
||||
AssertLog.info("目标IP {} 丢包率 {}% > 5%,30秒后重试探测", targetIp, firstLossPercent);
|
||||
// 如果丢包率超过阈值,进行重试探测
|
||||
if (firstLossPercent > LOSS_PERCENT_THRESHOLD) {
|
||||
AssertLog.info("目标IP {} 丢包率 {}% > {}%,{}秒后重试探测",
|
||||
targetIp, firstLossPercent, LOSS_PERCENT_THRESHOLD, RETRY_DELAY_MS/1000);
|
||||
|
||||
// 等待30秒
|
||||
Thread.sleep(30000);
|
||||
Thread.sleep(RETRY_DELAY_MS);
|
||||
|
||||
// 第二次MTR探测
|
||||
double secondLossPercent = executeMtrCommand(targetIp);
|
||||
mtrVO.setFinalLossPercent(secondLossPercent);
|
||||
mtrVO.setHasRetry(true);
|
||||
List<HopInfoVO> finalHops = executeMtrCommand(targetIp);
|
||||
double finalLossPercent = calculateFinalLossPercent(finalHops);
|
||||
mtrVO.setFinalLossPercent(finalLossPercent);
|
||||
mtrVO.setHopInfos(finalHops);
|
||||
|
||||
AssertLog.info("目标IP {} 重试探测完成,最终丢包率: {}%", targetIp, secondLossPercent);
|
||||
AssertLog.info("目标IP {} 重试探测完成,最终丢包率: {}%", targetIp, finalLossPercent);
|
||||
} else {
|
||||
mtrVO.setFinalLossPercent(firstLossPercent);
|
||||
mtrVO.setHasRetry(false);
|
||||
AssertLog.info("目标IP {} 丢包率 {}%,无需重试", targetIp, firstLossPercent);
|
||||
}
|
||||
return mtrVO;
|
||||
|
||||
} catch (Exception e) {
|
||||
AssertLog.error("MTR探测异常 - IP: {}", targetIp, e);
|
||||
mtrVO.setFinalLossPercent(-1.0); // -1表示探测失败
|
||||
mtrVO.setErrorMsg(e.getMessage());
|
||||
return mtrVO;
|
||||
handleMtrProbeError(mtrVO, targetIp, e);
|
||||
}
|
||||
|
||||
return mtrVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行mtr -r命令并解析结果
|
||||
* 创建MTR结果对象
|
||||
*/
|
||||
private double executeMtrCommand(String targetIp) {
|
||||
private MtrVO createMtrVO(String targetIp, String clientId, long timestamp) {
|
||||
MtrVO mtrVO = new MtrVO();
|
||||
mtrVO.setTargetIp(targetIp);
|
||||
mtrVO.setClientId(clientId);
|
||||
mtrVO.setTimestamp(timestamp);
|
||||
return mtrVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理MTR探测异常
|
||||
*/
|
||||
private void handleMtrProbeError(MtrVO mtrVO, String targetIp, Exception e) {
|
||||
AssertLog.error("MTR探测异常 - IP: {}", targetIp, e);
|
||||
mtrVO.setFinalLossPercent(-1.0);
|
||||
mtrVO.setErrorMsg(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行mtr命令并解析结果
|
||||
* @return 跳数信息列表
|
||||
*/
|
||||
private List<HopInfoVO> executeMtrCommand(String targetIp) {
|
||||
Process process = null;
|
||||
try {
|
||||
// 执行mtr -r -c 10命令
|
||||
ProcessBuilder processBuilder = new ProcessBuilder("mtr", "-r", "-c", "10", targetIp);
|
||||
ProcessBuilder processBuilder = new ProcessBuilder("mtr", "-r", "-c",
|
||||
String.valueOf(MTR_PACKET_COUNT), targetIp);
|
||||
process = processBuilder.start();
|
||||
|
||||
// 读取命令输出
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
String lastValidLine = null;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// 跳过标题行,记录最后一条有效数据行
|
||||
if (!line.trim().isEmpty() &&
|
||||
!line.startsWith("Start:") &&
|
||||
!line.startsWith("HOST:") &&
|
||||
Character.isDigit(line.trim().charAt(0))) {
|
||||
lastValidLine = line;
|
||||
}
|
||||
}
|
||||
|
||||
// 等待命令执行完成
|
||||
boolean finished = process.waitFor(2, TimeUnit.MINUTES);
|
||||
if (!finished) {
|
||||
process.destroy();
|
||||
throw new RuntimeException("MTR命令执行超时");
|
||||
}
|
||||
|
||||
if (lastValidLine == null) {
|
||||
throw new RuntimeException("MTR命令无有效输出");
|
||||
}
|
||||
|
||||
// 解析丢包率
|
||||
return parseLossPercentFromMtrOutput(lastValidLine);
|
||||
return parseMtrOutput(process);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("MTR命令执行异常: " + e.getMessage());
|
||||
@@ -197,21 +236,167 @@ public class MtrServiceImpl implements MtrService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从MTR输出中解析丢包率
|
||||
* 解析MTR命令输出
|
||||
*/
|
||||
private double parseLossPercentFromMtrOutput(String mtrOutputLine) {
|
||||
private List<HopInfoVO> parseMtrOutput(Process process) throws Exception {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
List<HopInfoVO> hops = new ArrayList<>();
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (isValidHopLine(line)) {
|
||||
HopInfoVO hop = parseHopLine(line);
|
||||
if (hop != null) {
|
||||
hops.add(hop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等待命令执行完成
|
||||
boolean finished = process.waitFor(MTR_TIMEOUT_MINUTES, TimeUnit.MINUTES);
|
||||
if (!finished) {
|
||||
process.destroy();
|
||||
throw new RuntimeException("MTR命令执行超时");
|
||||
}
|
||||
|
||||
if (hops.isEmpty()) {
|
||||
throw new RuntimeException("MTR命令无有效输出");
|
||||
}
|
||||
|
||||
return hops;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为有效的跳数行
|
||||
*/
|
||||
private boolean isValidHopLine(String line) {
|
||||
return !line.trim().isEmpty() &&
|
||||
!line.startsWith("Start:") &&
|
||||
!line.startsWith("HOST:") &&
|
||||
Character.isDigit(line.trim().charAt(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单跳数据行
|
||||
*/
|
||||
private HopInfoVO parseHopLine(String line) {
|
||||
try {
|
||||
// 使用正则表达式匹配Loss%列
|
||||
Pattern pattern = Pattern.compile("(\\d+\\.?\\d*)%");
|
||||
Matcher matcher = pattern.matcher(mtrOutputLine);
|
||||
// 匹配格式: "1.|-- 26.10.194.38 20.0% 10 1.6 1.5 1.3 1.8 0.0"
|
||||
Pattern pattern = Pattern.compile(
|
||||
"^\\s*(\\d+)\\.\\|--\\s+(\\S+)\\s+(\\d+\\.?\\d*)%\\s+(\\d+)\\s+(\\d+\\.\\d+)\\s+(\\d+\\.\\d+).*"
|
||||
);
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (matcher.find()) {
|
||||
return Double.parseDouble(matcher.group(1));
|
||||
HopInfoVO hop = new HopInfoVO();
|
||||
hop.setHopNumber(Integer.parseInt(matcher.group(1)));
|
||||
|
||||
String host = matcher.group(2);
|
||||
setHostInfo(hop, host);
|
||||
|
||||
hop.setLossPercent(Double.parseDouble(matcher.group(3)));
|
||||
hop.setAvgLatency(Double.parseDouble(matcher.group(6))); // 平均延迟
|
||||
|
||||
return hop;
|
||||
}
|
||||
|
||||
throw new RuntimeException("未找到Loss%数据: " + mtrOutputLine);
|
||||
// 处理未知主机的情况(如???)
|
||||
return parseUnknownHostLine(line);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("解析Loss%失败: " + e.getMessage());
|
||||
throw new RuntimeException("解析MTR跳数数据失败: " + e.getMessage() + ", 行: " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主机信息(IP地址解析和地理位置查询)
|
||||
*/
|
||||
private void setHostInfo(HopInfoVO hop, String host) {
|
||||
if (isValidIpAddress(host)) {
|
||||
hop.setIpAddress(host);
|
||||
// 如果是内网IP,不进行地理位置查询
|
||||
if (!isPrivateIp(host)) {
|
||||
queryIpLocationInfo(hop, host);
|
||||
} else {
|
||||
setPrivateIpLocationInfo(hop, host);
|
||||
}
|
||||
} else {
|
||||
hop.setHostname(host);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为有效的IP地址
|
||||
*/
|
||||
private boolean isValidIpAddress(String host) {
|
||||
return host.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}") && !host.equals("???");
|
||||
}
|
||||
|
||||
/**
|
||||
* 为内网IP设置特殊的地理位置信息
|
||||
*/
|
||||
private void setPrivateIpLocationInfo(HopInfoVO hop, String ipAddress) {
|
||||
hop.setCountry("内网");
|
||||
hop.setProvince("内网");
|
||||
hop.setCity("内网");
|
||||
hop.setIsp("内网");
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用在线服务查询IP地理位置信息
|
||||
*/
|
||||
private void queryIpLocationInfo(HopInfoVO hop, String ipAddress) {
|
||||
try {
|
||||
Map<String, String> locationInfo = onlineIPQueryService.queryIPLocation(ipAddress);
|
||||
if (locationInfo != null) {
|
||||
hop.setCountry(locationInfo.get("country"));
|
||||
hop.setProvince(locationInfo.get("province"));
|
||||
hop.setCity(locationInfo.get("city"));
|
||||
hop.setIsp(locationInfo.get("isp"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
AssertLog.error("查询IP地理位置信息失败: {}", ipAddress, e);
|
||||
// 设置默认值
|
||||
setDefaultLocationInfo(hop);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认地理位置信息
|
||||
*/
|
||||
private void setDefaultLocationInfo(HopInfoVO hop) {
|
||||
hop.setCountry("未知");
|
||||
hop.setProvince("未知");
|
||||
hop.setCity("未知");
|
||||
hop.setIsp("未知");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析未知主机行
|
||||
*/
|
||||
private HopInfoVO parseUnknownHostLine(String line) {
|
||||
Pattern pattern = Pattern.compile("^\\s*(\\d+)\\.\\|--\\s+(\\S+).*");
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (matcher.find()) {
|
||||
HopInfoVO hop = new HopInfoVO();
|
||||
hop.setHopNumber(Integer.parseInt(matcher.group(1)));
|
||||
hop.setHostname(matcher.group(2));
|
||||
hop.setLossPercent(0.0);
|
||||
hop.setAvgLatency(0.0);
|
||||
return hop;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算最终丢包率(取最后一跳的丢包率)
|
||||
*/
|
||||
private double calculateFinalLossPercent(List<HopInfoVO> hops) {
|
||||
if (CollectionUtil.isEmpty(hops)) {
|
||||
return 0.0;
|
||||
}
|
||||
return hops.get(hops.size() - 1).getLossPercent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package com.tongran.agent.client.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 在线IP查询服务
|
||||
*/
|
||||
@Component
|
||||
public class OnlineIPQueryService {
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
// 内网IP地址段正则表达式
|
||||
private static final Pattern PRIVATE_IP_PATTERN = Pattern.compile(
|
||||
"^(10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.|127\\.|169\\.254\\.|0\\.|255\\.)"
|
||||
);
|
||||
|
||||
public OnlineIPQueryService() {
|
||||
this.restTemplate = new RestTemplate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询IP地理位置信息 - 使用ip-api.com(免费无需key)
|
||||
*/
|
||||
public Map<String, String> queryIPLocation(String ip) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
// 如果是内网IP,直接返回内网标识
|
||||
if (isPrivateIp(ip)) {
|
||||
return createPrivateIpResult(ip);
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用ip-api.com免费服务
|
||||
String url = "http://ip-api.com/json/" + ip + "?lang=zh-CN";
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("User-Agent", "Mozilla/5.0");
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
url, HttpMethod.GET, entity, String.class);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
||||
return parseIpApiResponse(response.getBody(), ip);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// 如果ip-api.com失败,尝试备用服务
|
||||
return queryBackupService(ip);
|
||||
}
|
||||
|
||||
return createUnknownResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为内网IP
|
||||
*/
|
||||
private boolean isPrivateIp(String ip) {
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查常见的非IP地址情况
|
||||
if (ip.equals("???") || ip.equals("*") || ip.equals("localhost")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 使用正则表达式匹配内网IP段
|
||||
return PRIVATE_IP_PATTERN.matcher(ip).find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析ip-api.com响应
|
||||
*/
|
||||
private Map<String, String> parseIpApiResponse(String response, String ip) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
JSONObject json = JSON.parseObject(response);
|
||||
|
||||
if (!"success".equals(json.getString("status"))) {
|
||||
return createUnknownResult();
|
||||
}
|
||||
|
||||
result.put("country", json.getString("country"));
|
||||
result.put("province", json.getString("regionName"));
|
||||
result.put("city", json.getString("city"));
|
||||
result.put("isp", parseISP(json.getString("isp")));
|
||||
result.put("query", ip);
|
||||
result.put("source", "ip-api.com");
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
return createUnknownResult();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 备用查询服务
|
||||
*/
|
||||
private Map<String, String> queryBackupService(String ip) {
|
||||
try {
|
||||
// 使用IP.SB作为备用服务
|
||||
String url = "https://api.ip.sb/geoip/" + ip;
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("User-Agent", "Mozilla/5.0");
|
||||
headers.set("Accept", "application/json");
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
url, HttpMethod.GET, entity, String.class);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
||||
return parseIpSbResponse(response.getBody(), ip);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// 备用服务也失败
|
||||
}
|
||||
|
||||
return createUnknownResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析IP.SB响应
|
||||
*/
|
||||
private Map<String, String> parseIpSbResponse(String response, String ip) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
JSONObject json = JSON.parseObject(response);
|
||||
|
||||
result.put("country", json.getString("country"));
|
||||
result.put("province", json.getString("region"));
|
||||
result.put("city", json.getString("city"));
|
||||
result.put("isp", json.getString("isp"));
|
||||
result.put("query", ip);
|
||||
result.put("source", "ip.sb");
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
return createUnknownResult();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建内网IP结果
|
||||
*/
|
||||
private Map<String, String> createPrivateIpResult(String ip) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("country", "内网");
|
||||
result.put("province", "内网");
|
||||
result.put("city", "内网");
|
||||
result.put("isp", "内网");
|
||||
result.put("query", ip);
|
||||
result.put("source", "private");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化运营商名称
|
||||
*/
|
||||
private String parseISP(String isp) {
|
||||
if (isp == null) return "未知";
|
||||
|
||||
if (isp.contains("电信") || isp.contains("China Telecom")) return "中国电信";
|
||||
if (isp.contains("联通") || isp.contains("China Unicom")) return "中国联通";
|
||||
if (isp.contains("移动") || isp.contains("China Mobile")) return "中国移动";
|
||||
if (isp.contains("铁通")) return "中国铁通";
|
||||
if (isp.contains("教育网")) return "教育网";
|
||||
|
||||
return isp;
|
||||
}
|
||||
|
||||
private Map<String, String> createUnknownResult() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("country", "未知");
|
||||
result.put("province", "未知");
|
||||
result.put("city", "未知");
|
||||
result.put("isp", "未知");
|
||||
result.put("source", "unknown");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -557,11 +557,51 @@ public class AgentUtil {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* dmidecode -s system-serial-number命令获取SN序列号
|
||||
* @return SN序列号,如果获取失败则返回空字符串
|
||||
*/
|
||||
public static String getDeviceSN() {
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
// 执行dmidecode命令获取系统序列号
|
||||
Process process = Runtime.getRuntime().exec("dmidecode -s system-serial-number");
|
||||
|
||||
// 读取命令输出
|
||||
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String sn = reader.readLine();
|
||||
|
||||
// 等待命令执行完成
|
||||
int exitCode = process.waitFor();
|
||||
|
||||
// 检查命令是否执行成功且输出不为空
|
||||
if (exitCode == 0 && sn != null && !sn.trim().isEmpty()) {
|
||||
return sn.trim();
|
||||
} else {
|
||||
AssertLog.error("Failed to get SN: Command exited with code " + exitCode);
|
||||
return "";
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// 处理其他未知异常
|
||||
AssertLog.error("Unexpected error while getting device SN: " + e.getMessage());
|
||||
return "";
|
||||
} finally {
|
||||
// 确保关闭资源
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
AssertLog.error("Error closing reader: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取 /etc/issue 文件的第二行内容
|
||||
* @return 第二行字符串,如果不存在则返回 null
|
||||
*/
|
||||
public static String getDeviceSN() {
|
||||
public static String getDeviceSNBak() {
|
||||
Path issuePath = Paths.get("/etc/issue");
|
||||
if (!Files.exists(issuePath)) {
|
||||
System.err.println("文件不存在: /etc/issue");
|
||||
|
||||
@@ -1,19 +1,46 @@
|
||||
package com.tongran.agent.client.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class NetworkUtil {
|
||||
public static boolean addRoute(String ip, String prefix, String gateway, String dev) {
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder("ip", "route", "add",
|
||||
ip + "/" + prefix, "via", gateway, "dev", dev);
|
||||
ip + "/" + prefix, "via", gateway, "dev", dev);
|
||||
pb.redirectErrorStream(true);
|
||||
Process p = pb.start();
|
||||
return p.waitFor() == 0;
|
||||
|
||||
String output = readProcessOutput(p);
|
||||
int exitCode = p.waitFor();
|
||||
|
||||
if (exitCode == 0) {
|
||||
return true;
|
||||
} else {
|
||||
// 检查是否是路由已存在的错误
|
||||
if (output != null && (output.contains("File exists") || output.contains("RTNETLINK answers: File exists"))) {
|
||||
AssertLog.info("Route already exists");
|
||||
return true;
|
||||
}
|
||||
AssertLog.error("Failed to add route: " + output);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
AssertLog.error("Exception while adding route: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String readProcessOutput(Process p) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
|
||||
return reader.lines().collect(Collectors.joining("\n"));
|
||||
} catch (IOException e) {
|
||||
return "Error reading output: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deleteRoute(String ip, String prefix, String gateway, String dev) {
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder("ip", "route", "del",
|
||||
|
||||
Reference in New Issue
Block a user