diff --git a/src/main/java/com/tongran/agent/client/utils/AgentUtil.java b/src/main/java/com/tongran/agent/client/utils/AgentUtil.java index a763b29..21407dd 100644 --- a/src/main/java/com/tongran/agent/client/utils/AgentUtil.java +++ b/src/main/java/com/tongran/agent/client/utils/AgentUtil.java @@ -26,6 +26,11 @@ import java.util.stream.Collectors; public class AgentUtil { + // 存储网关信息:接口名 -> 网关IP + private static Map gatewayMap = new HashMap<>(); + + // 正则表达式匹配:当前 IP:xxx.xxx.xxx.xxx 来自于:中国 江苏省 南京市 电信 + private static final String REGEX = "来自于:(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)"; @Resource private GlobalConfig globalConfig; @@ -252,25 +257,62 @@ public class AgentUtil { return swapped; } - // 存储网关信息:接口名 -> 网关IP - private static Map gatewayMap = new HashMap<>(); + /** + * 检查有效网卡是否只有一个 + * @return + */ + public static boolean hasOnlyOneValidNetworkInterface() { + try { + int validInterfaceCount = 0; + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); - // 正则表达式匹配:当前 IP:xxx.xxx.xxx.xxx 来自于:中国 江苏省 南京市 电信 - private static final String REGEX = "来自于:(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)"; + while (interfaces.hasMoreElements()) { + NetworkInterface ni = interfaces.nextElement(); + String name = ni.getName(); + + // 1. 跳过回环、虚拟、关闭的接口 + if (ni.isLoopback() || ni.isVirtual() || !ni.isUp()) { + continue; + } + + // 2. 判断是否为 Ethernet + if (!isEthernetInterface(name)) { + continue; + } + + // 3. 检查物理链路状态 + if (!isInterfaceConnected(name)) { + continue; + } + + // 4. 检查是否有IPv4地址 + String ipv4 = getIPv4Address(ni); + if ("N/A".equals(ipv4)) { + continue; + } + + // 有效接口计数+1 + validInterfaceCount++; + + // 如果已经超过1个,直接返回false + if (validInterfaceCount > 1) { + return false; + } + } + + // 只有一个有效接口 + return validInterfaceCount == 1; + + } catch (Exception e) { + AssertLog.error("判断有效网络接口数量失败:{}", e.getMessage()); + return false; + } + } /** * 收集所有 Ethernet 类型网卡信息 */ public static List collectNetworkInfo() throws Exception { List result = new ArrayList<>(); -// String publicIp = getPublicIp(); // 获取公网 IP(全局出口) -// String ipInfo = PublicIpFetcher.getPublicIp(); -// String publicIp = PublicIpFetcher.extractIp(ipInfo); -// if (publicIp != null) { -// System.out.println("公网 IP: " + publicIp); -// } else { -// System.out.println("未能提取 IP 地址"); -// } - Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { @@ -293,27 +335,35 @@ public class AgentUtil { // 检查IP4信息 if (ipv4.equals("N/A")) continue; - - String gateway = getGatewayAddress(); + String gateway = null; + if(hasOnlyOneValidNetworkInterface()){ + gateway = getGatewayAddress(); + }else{ + gateway = getGateway(name, "www.baidu.com"); + } AssertLog.info("ipv4={},网关={}",ipv4,gateway); // 检查IP4信息 - if (gateway.equals("N/A")) continue; + if ("N/A".equals(gateway)) continue; // 添加临时路由 过滤没有ipv4的接口 - boolean flag = addTempRoute(ipv4, name, gateway); - if (!flag) continue; +// boolean flag = addTempRoute(ipv4, name, gateway); +// if (!flag) continue; - String ipInfo = PublicIpFetcher.getPublicIp(); - String publicIp = PublicIpFetcher.extractIp(ipInfo); - if (publicIp != null) { - System.out.println("公网 IP: " + publicIp); - } else { - System.out.println("未能提取 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)) continue; } - if (StringUtils.isBlank(publicIp)) continue; // 删除临时路由 - delTempRoute(ipv4, name, gateway); +// delTempRoute(ipv4, name, gateway); NetworkInterfaceInfo info = NetworkInterfaceInfo.builder() @@ -328,24 +378,29 @@ public class AgentUtil { // 如果有公网 IP,查询归属地 if (publicIp != null && !publicIp.isEmpty()) { - Pattern pattern = Pattern.compile(REGEX); - Matcher matcher = pattern.matcher(ipInfo); - if (matcher.find()) { + // 提取归属地信息 + PublicIpFetcher.IpLocationInfo location = PublicIpFetcher.extractLocation(ipInfo); + info.setProvince(location.getProvince()); + info.setCity(location.getCity()); + info.setCarrier(location.getCarrier()); +// Pattern pattern = Pattern.compile(REGEX); +// Matcher matcher = pattern.matcher(ipInfo); +// if (matcher.find()) { // group(1): 国家(通常是“中国”) // group(2): 省份 // group(3): 城市 // group(4): 运营商 - String province = matcher.group(2); - String city = matcher.group(3); - String isp = matcher.group(4); - // 去掉可能的标点符号(如句号) - province = province.replaceAll("[。]", ""); - city = city.replaceAll("[。]", ""); - isp = isp.replaceAll("[。]", ""); - info.setCarrier(isp); - info.setProvince(province); - info.setCity(city); - } +// String province = matcher.group(2); +// String city = matcher.group(3); +// String isp = matcher.group(4); +// // 去掉可能的标点符号(如句号) +// province = province.replaceAll("[。]", ""); +// city = city.replaceAll("[。]", ""); +// isp = isp.replaceAll("[。]", ""); +// info.setCarrier(isp); +// info.setProvince(province); +// info.setCity(city); +// } // ipInfo.substring(ipInfo.indexOf("来自于:")+4,ipInfo.length()) // JSONObject location = queryIpLocation(publicIp); // if (location != null) { @@ -491,6 +546,55 @@ public class AgentUtil { } return "N/A"; } + public static String getGateway(String interfaceName, String target) { + try { + Process process = Runtime.getRuntime().exec( + new String[]{"traceroute", "-i", interfaceName, "-m", "1", target} + ); + process.waitFor(); + + BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream()) + ); + String line; + while ((line = reader.readLine()) != null) { + if (line.trim().startsWith("1")) { + // 检查是否全是星号 + if (line.contains("* * *")) { + AssertLog.info("traceroute 探测完成,网卡名称:{},无网关", interfaceName); + 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); + + if (matcher.find()) { + String gatewayIp = matcher.group(0); + AssertLog.info("traceroute 探测完成,网卡名称:{},网关IP:{}", interfaceName, gatewayIp); + return gatewayIp; + } + + // 如果没有匹配到IP格式,尝试查找主机名 + // 行格式可能是: "1 gateway.local (192.168.1.1) 0.5 ms" + String[] parts = line.trim().split("\\s+"); + if (parts.length >= 2 && !parts[1].equals("*")) { + String gateway = parts[1]; + AssertLog.info("traceroute 探测完成,网卡名称:{},网关:{}", interfaceName, gateway); + return gateway; + } + } + } + + AssertLog.info("traceroute 探测完成,网卡名称:{},未找到网关信息", interfaceName); + return null; + + } catch (Exception e) { + AssertLog.info("traceroute 探测失败,网卡名称:{},详情:{}", interfaceName, e.getMessage()); + return null; + } + } // 检查接口是否“已连接”(物理链路状态) private static boolean isInterfaceConnected(String interfaceName) { diff --git a/src/main/java/com/tongran/agent/client/utils/PublicIpFetcher.java b/src/main/java/com/tongran/agent/client/utils/PublicIpFetcher.java index 1fc6e4d..3692735 100644 --- a/src/main/java/com/tongran/agent/client/utils/PublicIpFetcher.java +++ b/src/main/java/com/tongran/agent/client/utils/PublicIpFetcher.java @@ -4,37 +4,141 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; +import java.net.InetAddress; import java.net.URL; +import java.net.UnknownHostException; +import java.util.concurrent.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class PublicIpFetcher { - private static final String IP_SERVICE_URL = "https://myip.ipip.net"; - private static final int TIMEOUT_MS = 10_000; // 10秒超时 + // 主备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 IPV4_PATTERN = "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})"; + private static final Pattern IP_PATTERN = Pattern.compile(IPV4_PATTERN); + private static final Pattern CIPCC_PATTERN = Pattern.compile( + "IP\\s*:\\s*" + IPV4_PATTERN + "\\s*地址\\s*:\\s*([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s*运营商\\s*:\\s*([^\\r\\n]+)" + ); + private static final Pattern IPIP_PATTERN = Pattern.compile( + "当前 IP:\\s*" + IPV4_PATTERN + "\\s*来自于:([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)" + ); /** - * 获取公网 IP(包含归属地信息) - * - * @return IP 信息字符串,如 "当前 IP:112.96.15.145,来自于:中国 广东省 深圳市 联通" - * @throws IOException 如果网络请求失败 + * 获取公网 IP 信息(带降级策略) + * 1. 首先尝试 myip.ipip.net + * 2. 如果超时或失败,执行 curl cip.cc + * 3. 如果都失败,返回本地IP */ - public static String getPublicIp() throws IOException { - URL url = new URL(IP_SERVICE_URL); + public static String getPublicIp() { + // 方法1: 尝试主服务 + String ipInfo = tryPrimaryService(); + if (ipInfo != null && !ipInfo.isEmpty()) { + return ipInfo; + } + + // 方法2: 尝试备用服务 + ipInfo = tryFallbackService(); + if (ipInfo != null && !ipInfo.isEmpty()) { + return ipInfo; + } + + // 方法3: 返回本地IP + return getLocalIpInfo(); + } + + /** + * 尝试主服务 (myip.ipip.net) + */ + private static String tryPrimaryService() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit(() -> { + try { + AssertLog.info("myip.ipip.net采集IP信息成功"); + return fetchFromUrl(PRIMARY_IP_SERVICE, "UTF-8"); + } catch (Exception e) { + AssertLog.error("主服务请求失败: {}", e.getMessage()); + return null; + } + }); + + try { + return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + AssertLog.error("主服务请求超时"); + future.cancel(true); + } catch (Exception e) { + AssertLog.error("主服务执行异常: {}", e.getMessage()); + } finally { + executor.shutdownNow(); + } + return null; + } + + /** + * 尝试备用服务 (curl cip.cc) + */ + private static String tryFallbackService() { + try { + Process process = new ProcessBuilder("curl", "-s", "--max-time", "5", FALLBACK_IP_SERVICE) + .redirectErrorStream(true) + .start(); + + // 等待进程完成 + boolean finished = process.waitFor(CURL_TIMEOUT_MS, TimeUnit.MILLISECONDS); + + if (finished && process.exitValue() == 0) { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream()))) { + StringBuilder result = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + result.append(line).append("\n"); + } + AssertLog.info("curl cip.cc采集IP信息成功"); + 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()); + } + return null; + } + + /** + * 从HTTP URL获取IP信息 + */ + 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); - // 发起请求 int responseCode = connection.getResponseCode(); if (responseCode != 200) { - throw new IOException("HTTP " + responseCode + " from " + IP_SERVICE_URL); + throw new IOException("HTTP " + responseCode + " from " + urlString); } - // 读取响应(注意:myip.ipip.net 返回的是 GBK 编码) try (BufferedReader reader = new BufferedReader( - new InputStreamReader(connection.getInputStream(), "UTF-8"))) { // 使用 GBK 解码 + new InputStreamReader(connection.getInputStream(), charset))) { StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { @@ -45,42 +149,142 @@ public class PublicIpFetcher { } /** - * 从返回文本中提取纯 IP 地址(如 112.96.15.145) - * - * @param ipInfo 来自 getPublicIp() 的完整信息 - * @return 纯 IP 字符串,提取失败返回 null + * 解析cip.cc的结果 + */ + private static String parseCipCcResult(String result) { + if (result == null || result.isEmpty()) { + return null; + } + + Matcher matcher = CIPCC_PATTERN.matcher(result); + if (matcher.find()) { + String ip = matcher.group(1); + String country = matcher.group(2); + String province = matcher.group(3); + 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; // 返回原始结果 + } + + /** + * 获取本地IP信息 + */ + private static String getLocalIpInfo() { + try { + String localIp = InetAddress.getLocalHost().getHostAddress(); + AssertLog.info("当前 IP:{} 来自于:本地网络", localIp); + return localIp; + } catch (UnknownHostException e) { + AssertLog.error("当前IP获取失败:{}", e.getMessage()); + return null; + } + } + + /** + * 从IP信息中提取IP地址 */ public static String extractIp(String ipInfo) { if (ipInfo == null || ipInfo.isEmpty()) return null; - // 匹配 IP 地址的正则 - java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})"); - java.util.regex.Matcher matcher = pattern.matcher(ipInfo); + Matcher matcher = IP_PATTERN.matcher(ipInfo); return matcher.find() ? matcher.group(1) : null; } - // === 使用示例 === + /** + * 从IP信息中提取归属地信息 + */ + public static IpLocationInfo extractLocation(String ipInfo) { + IpLocationInfo info = new IpLocationInfo(); + + if (ipInfo == null || ipInfo.isEmpty()) { + return info; + } + + // 尝试匹配ipip.net格式 + Matcher ipipMatcher = IPIP_PATTERN.matcher(ipInfo); + if (ipipMatcher.find()) { + info.ip = ipipMatcher.group(1); + info.country = ipipMatcher.group(2); + info.province = ipipMatcher.group(3); + info.city = ipipMatcher.group(4); + info.carrier = ipipMatcher.group(5); + return info; + } + + // 尝试匹配cip.cc格式 + Matcher cipMatcher = CIPCC_PATTERN.matcher(ipInfo); + if (cipMatcher.find()) { + info.ip = cipMatcher.group(1); + info.country = cipMatcher.group(2); + info.province = cipMatcher.group(3); + info.city = cipMatcher.group(4); + info.carrier = cipMatcher.group(5); + return info; + } + + // 如果都不匹配,只提取IP + String ip = extractIp(ipInfo); + if (ip != null) { + info.ip = ip; + } + + return info; + } + + /** + * 完整的归属地信息 + */ + public static class IpLocationInfo { + public String ip; + public String country; + public String province; + public String city; + public String carrier; + + @Override + public String toString() { + return String.format("IP: %s, 国家: %s, 省份: %s, 城市: %s, 运营商: %s", + ip, country, province, city, carrier); + } + + public String getProvince() { + return province != null ? province.replaceAll("[。\\s]", "") : ""; + } + + public String getCity() { + return city != null ? city.replaceAll("[。\\s]", "") : ""; + } + + public String getCarrier() { + return carrier != null ? carrier.replaceAll("[。\\s]", "") : ""; + } + } + + /** + * 使用示例 + */ public static void main(String[] args) { - try { - String ipInfo = getPublicIp(); - System.out.println("完整信息: " + ipInfo); + System.out.println("开始获取公网IP信息..."); - String pureIp = extractIp(ipInfo); - if (pureIp != null) { - System.out.println("公网 IP: " + pureIp); - } else { - System.out.println("未能提取 IP 地址"); - } - String province = ""; - String city = ""; - String carrier = ""; + String ipInfo = getPublicIp(); + System.out.println("完整信息: " + ipInfo); - System.out.println(ipInfo.substring(ipInfo.indexOf("来自于:")+4,ipInfo.length())); + String pureIp = extractIp(ipInfo); + System.out.println("公网 IP: " + (pureIp != null ? pureIp : "获取失败")); + IpLocationInfo location = extractLocation(ipInfo); + System.out.println(location); - } catch (IOException e) { - System.err.println("获取公网 IP 失败: " + e.getMessage()); - e.printStackTrace(); + // 提取需要的字段 + if (location.ip != null) { + System.out.println("省份: " + location.getProvince()); + System.out.println("城市: " + location.getCity()); + System.out.println("运营商: " + location.getCarrier()); } } } \ No newline at end of file