优化公网ip解析。

This commit is contained in:
gaoyutao
2026-01-04 18:11:58 +08:00
parent 0f649a2cfc
commit 8bcc017d22
3 changed files with 289 additions and 90 deletions
@@ -376,7 +376,7 @@ public class AgentUtil {
// 检查IP4信息
if ("N/A".equals(gateway)) continue;
// 添加临时路由 过滤没有ipv4的接口
// 添加临时路由
// boolean flag = addTempRoute(ipv4, name, gateway);
// if (!flag) continue;
@@ -501,7 +501,7 @@ public class AgentUtil {
/**
* 添加临时路由
*/
private static boolean addTempRoute(String ipv4, String name, String gateway) {
public static boolean addTempRoute(String ipv4, String name, String gateway) {
String prefix = "32";
AssertLog.info("添加临时路由,ipv4{}",ipv4);
boolean flag = NetworkUtil.addRoute(ipv4,prefix,gateway,name);
@@ -516,7 +516,7 @@ public class AgentUtil {
/**
* 删除临时路由
*/
private static boolean delTempRoute(String ipv4, String name, String gateway) {
public static boolean delTempRoute(String ipv4, String name, String gateway) {
String prefix = "32";
AssertLog.info("删除临时路由,ipv4{}",ipv4);
boolean flag = NetworkUtil.deleteRoute(ipv4,prefix,gateway,name);
@@ -644,19 +644,34 @@ public class AgentUtil {
return null;
}
// 提取网关IP地址
// 示例行: "1 8.8.8.8 3.737 ms 3.940 ms 4.128 ms"
Pattern pattern = Pattern.compile("\\d+\\.\\d+\\.\\d+\\.\\d+");
Matcher matcher = pattern.matcher(line);
// 情况1:匹配 "1 192.168.9.1 (192.168.9.1)" 这种格式(图片中的格式)
// 先尝试匹配括号中的IP(最可靠)
Pattern bracketPattern = Pattern.compile("\\(([\\d.]+)\\)");
Matcher bracketMatcher = bracketPattern.matcher(line);
if (matcher.find()) {
String gatewayIp = matcher.group(0);
if (bracketMatcher.find()) {
String gatewayIp = bracketMatcher.group(1);
AssertLog.info("traceroute 探测完成,网卡名称:{},网关IP:{}", interfaceName, gatewayIp);
return gatewayIp;
}
// 如果没有匹配到IP格式,尝试查找主机名
// 行格式可能是: "1 gateway.local (192.168.1.1) 0.5 ms"
// 情况2:匹配 "1 8.8.8.8 3.737 ms" 这种纯IP格式(没有括号)
// 使用更精确的正则,只匹配数字开头且包含点号的IP地址
// 确保不匹配到主机名中的点号(如201.108.141.116.adsl-pool.jlccptt.net.cn
Pattern ipPattern = Pattern.compile("^\\s*1\\s+(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})(?:\\s|$)");
Matcher ipMatcher = ipPattern.matcher(line);
if (ipMatcher.find()) {
String gatewayIp = ipMatcher.group(1);
// 验证是否是合法IP(避免匹配到主机名中的数字序列)
if (isValidIP(gatewayIp)) {
AssertLog.info("traceroute 探测完成,网卡名称:{},网关IP:{}", interfaceName, gatewayIp);
return gatewayIp;
}
}
// 情况3:匹配 "1 gateway.local 0.5 ms" 或 "1 201.108.141.116.adsl-pool.jlccptt.net.cn" 这种主机名格式
// 提取第一跳后的第二个字段(跳过序号)
String[] parts = line.trim().split("\\s+");
if (parts.length >= 2 && !parts[1].equals("*")) {
String gateway = parts[1];
@@ -675,6 +690,30 @@ public class AgentUtil {
}
}
// 辅助方法:验证IP地址是否合法
private static boolean isValidIP(String ip) {
if (ip == null || ip.isEmpty()) {
return false;
}
String[] parts = ip.split("\\.");
if (parts.length != 4) {
return false;
}
try {
for (String part : parts) {
int num = Integer.parseInt(part);
if (num < 0 || num > 255) {
return false;
}
}
return true;
} catch (NumberFormatException e) {
return false;
}
}
// 检查接口是否“已连接”(物理链路状态)
private static boolean isInterfaceConnected(String interfaceName) {
try {
@@ -74,21 +74,42 @@ public class NetworkInterfaceUtil {
AssertLog.info(" 跳过网卡 {}: 无法获取网关", name);
continue;
}
boolean flag = false;
if(!"N/A".equals(ipv4) && gateway != null){
flag = true;
}
// 获取公网IP
String publicIp = null;
String ipInfo = null;
if (gateway != null) {
ipInfo = PublicIpFetcher.getPublicIp();
publicIp = PublicIpFetcher.extractIp(ipInfo);
if (publicIp != null) {
AssertLog.info(" 公网 IP: {}", publicIp);
} else {
AssertLog.info(" 未能提取公网 IP 地址");
}
if (StringUtils.isBlank(publicIp)) {
AssertLog.info(" 跳过网卡 {}: 无公网IP", name);
continue;
if (flag) {
List<String> publicIps = PublicIpFetcher.getPublicIps();
if (publicIps != null && !publicIps.isEmpty()) {
AssertLog.info("网卡 {} nslookup获取到 {} 个公网IP: {}", name, publicIps.size(), publicIps);
for (String ip : publicIps) {
if (ip != null && !ip.isEmpty()) {
// 为每个公网IP添加临时路由
boolean routeAdded = AgentUtil.addTempRoute(ip, name, gateway);
}
}
ipInfo = PublicIpFetcher.getPublicIp();
publicIp = PublicIpFetcher.extractIp(ipInfo);
if (publicIp != null) {
AssertLog.info(" 公网 IP: {}", publicIp);
} else {
AssertLog.info(" 未能提取公网 IP 地址");
}
if (StringUtils.isBlank(publicIp)) {
AssertLog.info(" 跳过网卡 {}: 无公网IP", name);
continue;
}
for (String ip : publicIps) {
if (ip != null && !ip.isEmpty()) {
// 删除临时路由
boolean routeAdded = AgentUtil.delTempRoute(ip, name, gateway);
}
}
}
}
@@ -7,6 +7,7 @@ import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -14,11 +15,9 @@ import java.util.regex.Pattern;
public class PublicIpFetcher {
// 主备IP查询服务
private static final String PRIMARY_IP_SERVICE = "https://myip.ipip.net";
private static final String FALLBACK_IP_SERVICE = "http://cip.cc";
private static final int TIMEOUT_MS = 8_000; // 8秒超时
private static final int CURL_TIMEOUT_MS = 5_000; // curl命令执行超时
private static final String PRIMARY_IP_SERVICE = "myip.ipip.net";
private static final String FALLBACK_IP_SERVICE = "cip.cc";
private static final int TIMEOUT_MS = 5_000; // 5秒超时
// 正则表达式匹配
private static final String IPV4_PATTERN = "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})";
@@ -31,50 +30,202 @@ public class PublicIpFetcher {
);
/**
* 获取公网 IP 信息(带降级策略)
* 1. 首先尝试 myip.ipip.net
* 2. 如果超时或失败,执行 curl cip.cc
* 3. 如果都失败,返回本地IP
* 获取公网 IP 列表(带降级策略)
* 1. 首先尝试 nslookup myip.ipip.net
* 2. 如果失败,尝试 nslookup cip.cc
*/
public static String getPublicIp() {
// 方法1: 尝试主服务
String ipInfo = tryPrimaryService();
if (ipInfo != null && !ipInfo.isEmpty()) {
return ipInfo;
public static List<String> getPublicIps() {
List<String> ipList = new ArrayList<>();
// 方法1: 尝试nslookup主服务
ipList = parseNslookupResult(PRIMARY_IP_SERVICE);
if (!ipList.isEmpty()) {
AssertLog.info("nslookup {} 解析到IP: {}", PRIMARY_IP_SERVICE, ipList);
return ipList;
}
// 方法2: 尝试备用服务
ipInfo = tryFallbackService();
if (ipInfo != null && !ipInfo.isEmpty()) {
return ipInfo;
// 方法2: 尝试nslookup备用服务
ipList = parseNslookupResult(FALLBACK_IP_SERVICE);
if (!ipList.isEmpty()) {
AssertLog.info("nslookup {} 解析到IP: {}", FALLBACK_IP_SERVICE, ipList);
return ipList;
}
// 方法3: 返回本地IP
return getLocalIpInfo();
return Collections.emptyList();
}
/**
* 尝试主服务 (myip.ipip.net)
* 执行nslookup并解析结果
*/
private static String tryPrimaryService() {
private static List<String> parseNslookupResult(String domain) {
List<String> ipList = new ArrayList<>();
try {
// 执行nslookup命令
Process process = new ProcessBuilder("nslookup", domain)
.redirectErrorStream(true)
.start();
// 等待命令执行完成
boolean finished = process.waitFor(TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (!finished) {
process.destroyForcibly();
return ipList;
}
int exitCode = process.exitValue();
if (exitCode != 0) {
return ipList;
}
// 读取并解析输出
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
// 1. 查找"Address:"行
if (line.contains("Address:") && !line.contains("#")) {
Matcher matcher = IP_PATTERN.matcher(line);
while (matcher.find()) {
String ip = matcher.group(1);
// 过滤DNS服务器IP和本地回环地址
if (!isDnsServerIp(ip) && !isLocalIp(ip)) {
ipList.add(ip);
}
}
}
// 2. 查找"Name:"行后的IP地址
else if (line.trim().startsWith("Name:")) {
// 读取下一行,通常是Address行
String nextLine = reader.readLine();
if (nextLine != null) {
output.append(nextLine).append("\n");
Matcher matcher = IP_PATTERN.matcher(nextLine);
while (matcher.find()) {
String ip = matcher.group(1);
if (!isDnsServerIp(ip) && !isLocalIp(ip)) {
ipList.add(ip);
}
}
}
}
}
// 3. 如果以上方法都没找到,尝试在整个输出中匹配
if (ipList.isEmpty()) {
String allOutput = output.toString();
Matcher matcher = IP_PATTERN.matcher(allOutput);
while (matcher.find()) {
String ip = matcher.group(1);
// 跳过DNS服务器IP和本地地址
if (!isDnsServerIp(ip) && !isLocalIp(ip)) {
// 检查是否是DNS服务器IP(通常在输出开头)
boolean isDnsLine = false;
String[] lines = allOutput.split("\n");
for (int i = 0; i < Math.min(3, lines.length); i++) {
if (lines[i].contains("Server:") && lines[i].contains(ip)) {
isDnsLine = true;
break;
}
}
if (!isDnsLine) {
ipList.add(ip);
}
}
}
}
}
} catch (IOException e) {
AssertLog.error("执行nslookup命令失败: {}", e.getMessage());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
AssertLog.error("nslookup命令被中断");
} catch (Exception e) {
AssertLog.error("nslookup解析异常: {}", e.getMessage());
}
// 去重
return new ArrayList<>(new LinkedHashSet<>(ipList));
}
/**
* 检查是否为DNS服务器IP
*/
private static boolean isDnsServerIp(String ip) {
// 常见的DNS服务器IP段
return ip.startsWith("8.8.") || // Google DNS
ip.startsWith("114.114.") || // 114DNS
ip.startsWith("119.29.") || // 腾讯DNS
ip.startsWith("223.5.5.") || // 阿里DNS
ip.startsWith("180.76.76.76") || // 百度DNS
ip.startsWith("1.2.4.8") || // CNNIC DNS
ip.startsWith("208.67."); // OpenDNS
}
/**
* 检查是否为本地IP
*/
private static boolean isLocalIp(String ip) {
return ip.startsWith("127.") ||
ip.startsWith("192.168.") ||
ip.startsWith("10.") ||
ip.startsWith("172.16.") ||
ip.startsWith("172.17.") ||
ip.startsWith("172.18.") ||
ip.startsWith("172.19.") ||
ip.startsWith("172.20.") ||
ip.startsWith("172.21.") ||
ip.startsWith("172.22.") ||
ip.startsWith("172.23.") ||
ip.startsWith("172.24.") ||
ip.startsWith("172.25.") ||
ip.startsWith("172.26.") ||
ip.startsWith("172.27.") ||
ip.startsWith("172.28.") ||
ip.startsWith("172.29.") ||
ip.startsWith("172.30.") ||
ip.startsWith("172.31.") ||
ip.equals("0.0.0.0") ||
ip.equals("255.255.255.255");
}
/**
* 获取公网 IP 信息(保持原有逻辑,用于降级)
*/
public static String getPublicIp() {
return getPublicIpInfo();
}
/**
* 原有HTTP查询逻辑
*/
private static String getPublicIpInfo() {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
try {
AssertLog.info("myip.ipip.net采集IP信息成功");
return fetchFromUrl(PRIMARY_IP_SERVICE, "UTF-8");
String result = fetchFromUrl("https://" + PRIMARY_IP_SERVICE, "UTF-8");
AssertLog.info("HTTP {}采集IP信息成功", PRIMARY_IP_SERVICE);
return result;
} catch (Exception e) {
AssertLog.error("主服务请求失败: {}", e.getMessage());
return null;
AssertLog.error("HTTP主服务请求失败: {}", e.getMessage());
return tryHttpFallback();
}
});
try {
return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
AssertLog.error("主服务请求超时");
AssertLog.error("HTTP请求超时");
future.cancel(true);
} catch (Exception e) {
AssertLog.error("主服务执行异常: {}", e.getMessage());
AssertLog.error("HTTP请求异常: {}", e.getMessage());
} finally {
executor.shutdownNow();
}
@@ -82,17 +233,16 @@ public class PublicIpFetcher {
}
/**
* 尝试备用服务 (curl cip.cc)
* HTTP回退查询
*/
private static String tryFallbackService() {
private static String tryHttpFallback() {
try {
Process process = new ProcessBuilder("curl", "-s", "--max-time", "5", FALLBACK_IP_SERVICE)
Process process = new ProcessBuilder("curl", "-s", "--max-time", "5",
"http://" + FALLBACK_IP_SERVICE)
.redirectErrorStream(true)
.start();
// 等待进程完成
boolean finished = process.waitFor(CURL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
boolean finished = process.waitFor(5_000, TimeUnit.MILLISECONDS);
if (finished && process.exitValue() == 0) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
@@ -101,22 +251,12 @@ public class PublicIpFetcher {
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
AssertLog.info("curl cip.cc采集IP信息成功");
AssertLog.info("curl {} 采集IP信息成功", FALLBACK_IP_SERVICE);
return parseCipCcResult(result.toString());
}
} else {
if (!finished) {
process.destroyForcibly();
}
AssertLog.error("curl命令执行失败");
}
} catch (IOException e) {
AssertLog.error("执行curl命令IO异常: {}", e.getMessage());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
AssertLog.error("curl命令被中断");
} catch (Exception e) {
AssertLog.error("备用服务异常: {}", e.getMessage());
AssertLog.error("HTTP回退失败: {}", e.getMessage());
}
return null;
}
@@ -127,7 +267,6 @@ public class PublicIpFetcher {
private static String fetchFromUrl(String urlString, String charset) throws IOException {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(TIMEOUT_MS);
connection.setReadTimeout(TIMEOUT_MS);
@@ -164,17 +303,16 @@ public class PublicIpFetcher {
String city = matcher.group(4);
String carrier = matcher.group(5);
// 构建类似ipip.net的格式
return String.format("当前 IP%s 来自于:%s %s %s %s",
ip, country, province, city, carrier);
}
return result; // 返回原始结果
return result;
}
/**
* 获取本地IP信息
* 获取本地IP
*/
private static String getLocalIpInfo() {
private static String getLocalIp() {
try {
String localIp = InetAddress.getLocalHost().getHostAddress();
AssertLog.info("当前 IP{} 来自于:本地网络", localIp);
@@ -185,12 +323,16 @@ public class PublicIpFetcher {
}
}
private static String getLocalIpInfo() {
String ip = getLocalIp();
return ip != null ? String.format("当前 IP%s 来自于:本地网络", ip) : null;
}
/**
* 从IP信息中提取IP地址
*/
public static String extractIp(String ipInfo) {
if (ipInfo == null || ipInfo.isEmpty()) return null;
Matcher matcher = IP_PATTERN.matcher(ipInfo);
return matcher.find() ? matcher.group(1) : null;
}
@@ -266,25 +408,22 @@ public class PublicIpFetcher {
}
/**
* 使用示例
* 测试方法
*/
public static void main(String[] args) {
System.out.println("开始获取公网IP信息...");
System.out.println("开始获取公网IP列表...");
String ipInfo = getPublicIp();
System.out.println("完整信息: " + ipInfo);
// 测试nslookup解析
System.out.println("测试解析myip.ipip.net:");
List<String> ipList1 = parseNslookupResult(PRIMARY_IP_SERVICE);
System.out.println("解析结果: " + ipList1);
String pureIp = extractIp(ipInfo);
System.out.println("公网 IP: " + (pureIp != null ? pureIp : "获取失败"));
System.out.println("\n测试解析cip.cc:");
List<String> ipList2 = parseNslookupResult(FALLBACK_IP_SERVICE);
System.out.println("解析结果: " + ipList2);
IpLocationInfo location = extractLocation(ipInfo);
System.out.println(location);
// 提取需要的字段
if (location.ip != null) {
System.out.println("省份: " + location.getProvince());
System.out.println("城市: " + location.getCity());
System.out.println("运营商: " + location.getCarrier());
}
// 获取公网IP列表
List<String> allIps = getPublicIps();
System.out.println("\n最终获取到的公网IP列表: " + allIps);
}
}