Merge pull request #26 from Sagit-chu/opencode/hidden-pixel

fix(diagnose): parallelize TCP ping diagnostics to prevent timeout ca…
This commit is contained in:
sagit
2026-02-05 12:57:10 +08:00
committed by GitHub
2 changed files with 164 additions and 89 deletions
@@ -22,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
@@ -513,10 +514,10 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
.filter(ct -> ct.getChainType() == 3)
.toList();
List<DiagnosisResult> results = new ArrayList<>();
List<CompletableFuture<DiagnosisResult>> futures = new ArrayList<>();
String[] remoteAddresses = forward.getRemoteAddr().split(",");
// 根据隧道类型执行不同的诊断策略
// 根据隧道类型执行不同的诊断策略(并行执行所有诊断任务)
if (tunnel.getType() == 1) {
// 端口转发:入口节点直接TCP ping目标地址
for (ChainTunnel inNode : inNodes) {
@@ -526,12 +527,18 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
String targetIp = extractIpFromAddress(remoteAddress);
int targetPort = extractPortFromAddress(remoteAddress);
if (targetIp != null && targetPort != -1) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
node, targetIp, targetPort,
"入口(" + node.getName() + ")->目标(" + remoteAddress + ")"
);
result.setFromChainType(1);
results.add(result);
final Node finalNode = node;
final String finalTargetIp = targetIp;
final int finalTargetPort = targetPort;
final String finalRemoteAddress = remoteAddress;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalNode, finalTargetIp, finalTargetPort,
"入口(" + finalNode.getName() + ")->目标(" + finalRemoteAddress + ")"
);
result.setFromChainType(1);
return result;
}));
}
}
}
@@ -547,27 +554,37 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
for (ChainTunnel firstChainNode : chainNodesList.getFirst()) {
Node toNode = nodeService.getById(firstChainNode.getNodeId());
if (toNode != null) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
fromNode, GostUtil.selectDialHost(fromNode, toNode), firstChainNode.getPort(),
"入口(" + fromNode.getName() + ")->第1跳(" + toNode.getName() + ")"
);
result.setFromChainType(1);
result.setToChainType(2);
result.setToInx(firstChainNode.getInx());
results.add(result);
final Node finalFromNode = fromNode;
final Node finalToNode = toNode;
final ChainTunnel finalFirstChainNode = firstChainNode;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalFirstChainNode.getPort(),
"入口(" + finalFromNode.getName() + ")->第1跳(" + finalToNode.getName() + ")"
);
result.setFromChainType(1);
result.setToChainType(2);
result.setToInx(finalFirstChainNode.getInx());
return result;
}));
}
}
} else if (!outNodes.isEmpty()) {
for (ChainTunnel outNode : outNodes) {
Node toNode = nodeService.getById(outNode.getNodeId());
if (toNode != null) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
fromNode, GostUtil.selectDialHost(fromNode, toNode), outNode.getPort(),
"入口(" + fromNode.getName() + ")->出口(" + toNode.getName() + ")"
);
result.setFromChainType(1);
result.setToChainType(3);
results.add(result);
final Node finalFromNode = fromNode;
final Node finalToNode = toNode;
final ChainTunnel finalOutNode = outNode;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalOutNode.getPort(),
"入口(" + finalFromNode.getName() + ")->出口(" + finalToNode.getName() + ")"
);
result.setFromChainType(1);
result.setToChainType(3);
return result;
}));
}
}
}
@@ -577,6 +594,7 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
// 2. 链路测试
for (int i = 0; i < chainNodesList.size(); i++) {
List<ChainTunnel> currentHop = chainNodesList.get(i);
final int hopIndex = i;
for (ChainTunnel currentNode : currentHop) {
Node fromNode = nodeService.getById(currentNode.getNodeId());
@@ -586,29 +604,41 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
for (ChainTunnel nextNode : chainNodesList.get(i + 1)) {
Node toNode = nodeService.getById(nextNode.getNodeId());
if (toNode != null) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
fromNode, GostUtil.selectDialHost(fromNode, toNode), nextNode.getPort(),
"第" + (i + 1) + "跳(" + fromNode.getName() + ")->第" + (i + 2) + "跳(" + toNode.getName() + ")"
);
result.setFromChainType(2);
result.setFromInx(currentNode.getInx());
result.setToChainType(2);
result.setToInx(nextNode.getInx());
results.add(result);
final Node finalFromNode = fromNode;
final Node finalToNode = toNode;
final ChainTunnel finalCurrentNode = currentNode;
final ChainTunnel finalNextNode = nextNode;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalNextNode.getPort(),
"第" + (hopIndex + 1) + "跳(" + finalFromNode.getName() + ")->第" + (hopIndex + 2) + "跳(" + finalToNode.getName() + ")"
);
result.setFromChainType(2);
result.setFromInx(finalCurrentNode.getInx());
result.setToChainType(2);
result.setToInx(finalNextNode.getInx());
return result;
}));
}
}
} else if (!outNodes.isEmpty()) {
for (ChainTunnel outNode : outNodes) {
Node toNode = nodeService.getById(outNode.getNodeId());
if (toNode != null) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
fromNode, GostUtil.selectDialHost(fromNode, toNode), outNode.getPort(),
"第" + (i + 1) + "跳(" + fromNode.getName() + ")->出口(" + toNode.getName() + ")"
);
result.setFromChainType(2);
result.setFromInx(currentNode.getInx());
result.setToChainType(3);
results.add(result);
final Node finalFromNode = fromNode;
final Node finalToNode = toNode;
final ChainTunnel finalCurrentNode = currentNode;
final ChainTunnel finalOutNode = outNode;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalOutNode.getPort(),
"第" + (hopIndex + 1) + "跳(" + finalFromNode.getName() + ")->出口(" + finalToNode.getName() + ")"
);
result.setFromChainType(2);
result.setFromInx(finalCurrentNode.getInx());
result.setToChainType(3);
return result;
}));
}
}
}
@@ -624,18 +654,29 @@ public class ForwardServiceImpl extends ServiceImpl<ForwardMapper, Forward> impl
String targetIp = extractIpFromAddress(remoteAddress);
int targetPort = extractPortFromAddress(remoteAddress);
if (targetIp != null && targetPort != -1) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
node, targetIp, targetPort,
"出口(" + node.getName() + ")->目标(" + remoteAddress + ")"
);
result.setFromChainType(3);
results.add(result);
final Node finalNode = node;
final String finalTargetIp = targetIp;
final int finalTargetPort = targetPort;
final String finalRemoteAddress = remoteAddress;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalNode, finalTargetIp, finalTargetPort,
"出口(" + finalNode.getName() + ")->目标(" + finalRemoteAddress + ")"
);
result.setFromChainType(3);
return result;
}));
}
}
}
}
}
// 等待所有诊断任务完成并收集结果
List<DiagnosisResult> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
// 构建诊断报告
Map<String, Object> diagnosisReport = new HashMap<>();
diagnosisReport.put("forwardId", id);
@@ -22,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
@@ -669,17 +670,20 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
.filter(ct -> ct.getChainType() == 3)
.toList();
List<DiagnosisResult> results = new ArrayList<>();
List<CompletableFuture<DiagnosisResult>> futures = new ArrayList<>();
if (tunnel.getType() == 1) {
for (ChainTunnel inNode : inNodes) {
Node node = nodeService.getById(inNode.getNodeId());
if (node != null) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
node, "www.google.com", 443, "入口(" + node.getName() + ")->外网"
);
result.setFromChainType(1); // 入口
results.add(result);
final Node finalNode = node;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalNode, "www.google.com", 443, "入口(" + finalNode.getName() + ")->外网"
);
result.setFromChainType(1);
return result;
}));
}
}
} else if (tunnel.getType() == 2) {
@@ -691,27 +695,37 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
for (ChainTunnel firstChainNode : chainNodesList.getFirst()) {
Node toNode = nodeService.getById(firstChainNode.getNodeId());
if (toNode != null) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
fromNode, GostUtil.selectDialHost(fromNode, toNode), firstChainNode.getPort(),
"入口(" + fromNode.getName() + ")->第1跳(" + toNode.getName() + ")"
);
result.setFromChainType(1); // 入口
result.setToChainType(2); // 链
result.setToInx(firstChainNode.getInx());
results.add(result);
final Node finalFromNode = fromNode;
final Node finalToNode = toNode;
final ChainTunnel finalFirstChainNode = firstChainNode;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalFirstChainNode.getPort(),
"入口(" + finalFromNode.getName() + ")->第1跳(" + finalToNode.getName() + ")"
);
result.setFromChainType(1);
result.setToChainType(2);
result.setToInx(finalFirstChainNode.getInx());
return result;
}));
}
}
} else if (!outNodes.isEmpty()) {
for (ChainTunnel outNode : outNodes) {
Node toNode = nodeService.getById(outNode.getNodeId());
if (toNode != null) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
fromNode, GostUtil.selectDialHost(fromNode, toNode), outNode.getPort(),
"入口(" + fromNode.getName() + ")->出口(" + toNode.getName() + ")"
);
result.setFromChainType(1);
result.setToChainType(3);
results.add(result);
final Node finalFromNode = fromNode;
final Node finalToNode = toNode;
final ChainTunnel finalOutNode = outNode;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalOutNode.getPort(),
"入口(" + finalFromNode.getName() + ")->出口(" + finalToNode.getName() + ")"
);
result.setFromChainType(1);
result.setToChainType(3);
return result;
}));
}
}
}
@@ -720,6 +734,7 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
for (int i = 0; i < chainNodesList.size(); i++) {
List<ChainTunnel> currentHop = chainNodesList.get(i);
final int hopIndex = i;
for (ChainTunnel currentNode : currentHop) {
Node fromNode = nodeService.getById(currentNode.getNodeId());
@@ -729,29 +744,41 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
for (ChainTunnel nextNode : chainNodesList.get(i + 1)) {
Node toNode = nodeService.getById(nextNode.getNodeId());
if (toNode != null) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
fromNode, GostUtil.selectDialHost(fromNode, toNode), nextNode.getPort(),
"第" + (i + 1) + "跳(" + fromNode.getName() + ")->第" + (i + 2) + "跳(" + toNode.getName() + ")"
);
result.setFromChainType(2);
result.setFromInx(currentNode.getInx());
result.setToChainType(2);
result.setToInx(nextNode.getInx());
results.add(result);
final Node finalFromNode = fromNode;
final Node finalToNode = toNode;
final ChainTunnel finalCurrentNode = currentNode;
final ChainTunnel finalNextNode = nextNode;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalNextNode.getPort(),
"第" + (hopIndex + 1) + "跳(" + finalFromNode.getName() + ")->第" + (hopIndex + 2) + "跳(" + finalToNode.getName() + ")"
);
result.setFromChainType(2);
result.setFromInx(finalCurrentNode.getInx());
result.setToChainType(2);
result.setToInx(finalNextNode.getInx());
return result;
}));
}
}
} else if (!outNodes.isEmpty()) {
for (ChainTunnel outNode : outNodes) {
Node toNode = nodeService.getById(outNode.getNodeId());
if (toNode != null) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
fromNode, GostUtil.selectDialHost(fromNode, toNode), outNode.getPort(),
"第" + (i + 1) + "跳(" + fromNode.getName() + ")->出口(" + toNode.getName() + ")"
);
result.setFromChainType(2);
result.setFromInx(currentNode.getInx());
result.setToChainType(3);
results.add(result);
final Node finalFromNode = fromNode;
final Node finalToNode = toNode;
final ChainTunnel finalCurrentNode = currentNode;
final ChainTunnel finalOutNode = outNode;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalFromNode, GostUtil.selectDialHost(finalFromNode, finalToNode), finalOutNode.getPort(),
"第" + (hopIndex + 1) + "跳(" + finalFromNode.getName() + ")->出口(" + finalToNode.getName() + ")"
);
result.setFromChainType(2);
result.setFromInx(finalCurrentNode.getInx());
result.setToChainType(3);
return result;
}));
}
}
}
@@ -761,15 +788,22 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
for (ChainTunnel outNode : outNodes) {
Node node = nodeService.getById(outNode.getNodeId());
if (node != null) {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
node, "www.google.com", 443, "出口(" + node.getName() + ")->外网"
);
result.setFromChainType(3);
results.add(result);
final Node finalNode = node;
futures.add(CompletableFuture.supplyAsync(() -> {
DiagnosisResult result = performTcpPingDiagnosisWithConnectionCheck(
finalNode, "www.google.com", 443, "出口(" + finalNode.getName() + ")->外网"
);
result.setFromChainType(3);
return result;
}));
}
}
}
List<DiagnosisResult> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
Map<String, Object> diagnosisReport = new HashMap<>();
diagnosisReport.put("tunnelId", tunnelId);
diagnosisReport.put("tunnelName", tunnel.getName());