diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09bdfea --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml \ No newline at end of file diff --git a/src/main/java/com/tongran/agent/client/utils/NetworkInterfaceUtil.java b/src/main/java/com/tongran/agent/client/utils/NetworkInterfaceUtil.java new file mode 100644 index 0000000..09b7840 --- /dev/null +++ b/src/main/java/com/tongran/agent/client/utils/NetworkInterfaceUtil.java @@ -0,0 +1,411 @@ +package com.tongran.agent.client.utils; + +import com.tongran.agent.client.core.vo.NetworkInterfaceInfo; +import org.apache.commons.lang3.StringUtils; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 网络接口工具类(通过 ip 命令获取) + */ +public class NetworkInterfaceUtil { + + /** + * 收集所有 Ethernet 类型网卡信息 + */ + public static List collectNetworkInfo() throws Exception { + List result = new ArrayList<>(); + Map parentMap = new HashMap<>(); + List childTempList = new ArrayList<>(); + + // 获取所有网卡详细信息 + List allInterfaces = getNetworkInterfacesFromIpLink(); + AssertLog.info("从 ip link 获取到 {} 个网络接口", allInterfaces.size()); + + for (NetworkInterfaceDetails interfaceDetail : allInterfaces) { + String name = interfaceDetail.name; + String realName = interfaceDetail.realName; + boolean isUp = "UP".equals(interfaceDetail.state); + + // 调试信息 + AssertLog.info("开始处理网卡: {}", name); + AssertLog.info(" 状态: {}, 是否虚拟: {}", + isUp ? "UP" : "DOWN", interfaceDetail.isVirtual); + + // 跳过回环、关闭的接口 + if (name.equals("lo") || !isUp) { + AssertLog.info(" 跳过网卡 {}: 回环或未启用", name); + continue; + } + + // 判断是否为 Ethernet(通过名称约定) + if (!isEthernetInterface(interfaceDetail)) { + AssertLog.info(" 跳过网卡 {}: 非以太网接口", name); + continue; + } + + // 获取IP地址 + String ipv4 = getIPv4AddressFromIpAddr(name); + String ipv6 = getIPv6AddressFromIpAddr(name); + AssertLog.info(" IPv4: {}, IPv6: {}", ipv4, ipv6); + + // 检查IP4信息 +// if (ipv4.equals("N/A")) { +// AssertLog.info(" 跳过网卡 {}: 无IPv4地址", name); +// continue; +// } + + // 检查物理链路状态 + boolean isConnected = isInterfaceConnected(name); + if (!isConnected) { + AssertLog.info(" 跳过网卡 {}: 物理链路未连接", name); + continue; + } + + // 获取网关 + String gateway = AgentUtil.getGateway(name, "www.baidu.com"); + AssertLog.info(" 网关: {}", gateway); + + if ("N/A".equals(gateway)) { + AssertLog.info(" 跳过网卡 {}: 无法获取网关", name); + continue; + } + + // 获取公网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; + } + } + + // 创建网卡信息对象 + NetworkInterfaceInfo info = NetworkInterfaceInfo.builder() + .name(name) + .type("Ethernet") + .mac(interfaceDetail.macAddress) + .ipv4(ipv4) + .ipv6(ipv6) + .gateway(gateway) + .publicIp(publicIp) + .build(); + + // 如果有公网 IP,查询归属地 + if (publicIp != null && !publicIp.isEmpty() && ipInfo != null) { + // 提取归属地信息 + PublicIpFetcher.IpLocationInfo location = PublicIpFetcher.extractLocation(ipInfo); + info.setProvince(location.getProvince()); + info.setCity(location.getCity()); + info.setCarrier(location.getCarrier()); + } + + // 判断父子关系 + if (interfaceDetail.parent != null && !interfaceDetail.parent.isEmpty()) { + // 子接口 + AssertLog.info(" 识别为子接口: {}", name); + String parentName = interfaceDetail.parent; + + // 递归查找最终的父网卡 + parentName = findRootParentInterface(parentName, allInterfaces); + if (!interfaceDetail.parent.equals(parentName)) { + AssertLog.info(" {} 的最终父接口是: {} (原始父接口: {})", + name, parentName, interfaceDetail.parent); + } + + info.setParentInterface(parentName); + + // 查找父接口 + NetworkInterfaceInfo parent = parentMap.get(parentName); + if (parent != null) { + // 设置父子关系 + if (parent.getSubInterfaces() == null) { + parent.setSubInterfaces(new ArrayList<>()); + } + parent.getSubInterfaces().add(info); + AssertLog.info(" 添加到父接口 {} 的子接口列表", parentName); + } else { + // 父接口还未处理,先放到临时列表 + childTempList.add(info); + AssertLog.info(" 父接口 {} 还未处理,放入临时列表", parentName); + } + } else { + // 父接口 + AssertLog.info(" 识别为父接口: {}", name); + parentMap.put(name, info); + } + } + + // 处理父接口加入结果 + for (NetworkInterfaceInfo parent : parentMap.values()) { + result.add(parent); + AssertLog.info("父接口 {} 加入结果列表", parent.getName()); + } + + // 处理子接口(父接口未出现的情况) + for (NetworkInterfaceInfo child : childTempList) { + String parentName = child.getParentInterface(); + NetworkInterfaceInfo parent = parentMap.get(parentName); + + if (parent != null) { + // 建立父子关系 + if (parent.getSubInterfaces() == null) { + parent.setSubInterfaces(new ArrayList<>()); + } + parent.getSubInterfaces().add(child); + AssertLog.info("子接口 {} 关联到父接口 {}", child.getName(), parentName); + } else { + // 确实没有父接口,作为独立接口 + result.add(child); + AssertLog.info("子接口 {} 无父接口,作为独立接口", child.getName()); + } + } + + AssertLog.info("共收集到 {} 个有效网卡接口", result.size()); + return result; + } + /** + * 递归查找最终的父网卡 + */ + private static String findRootParentInterface(String currentParent, List allInterfaces) { + // 查找当前父网卡的详细信息 + for (NetworkInterfaceDetails iface : allInterfaces) { + if (iface.name.equals(currentParent) && iface.parent != null && !iface.parent.isEmpty()) { + // 如果当前父网卡还有父网卡,继续递归查找 + return findRootParentInterface(iface.parent, allInterfaces); + } + } + // 返回最终的父网卡名称 + return currentParent; + } + + /** + * 通过 ip link 命令获取网卡详细信息 + */ + private static List getNetworkInterfacesFromIpLink() throws Exception { + List interfaces = new ArrayList<>(); + + Process process = Runtime.getRuntime().exec("ip -d link show"); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + + NetworkInterfaceDetails currentInterface = null; + + Pattern interfacePattern = Pattern.compile("^(\\d+):\\s+(\\S+?)(?:@(\\S+))?\\s*:"); + Pattern linkPattern = Pattern.compile("\\s+link/(\\S+)\\s+([0-9a-f:]+)"); + Pattern statePattern = Pattern.compile("state\\s+(UP|DOWN|UNKNOWN)"); + + while ((line = reader.readLine()) != null) { + Matcher interfaceMatcher = interfacePattern.matcher(line); + + if (interfaceMatcher.find()) { + // 保存上一个接口 + if (currentInterface != null) { + interfaces.add(currentInterface); + } + + // 创建新的接口 + currentInterface = new NetworkInterfaceDetails(); + currentInterface.index = Integer.parseInt(interfaceMatcher.group(1)); + currentInterface.name = interfaceMatcher.group(2).trim(); + + // 提取父接口(如果有@符号) + if (interfaceMatcher.group(3) != null) { + currentInterface.parent = interfaceMatcher.group(3).trim(); + currentInterface.realName = currentInterface.name + "@" + currentInterface.parent; + } + + // 检查是否是虚拟接口 + String ifaceName = currentInterface.name; + if (ifaceName.contains(".") || ifaceName.startsWith("xdbvl") || + ifaceName.startsWith("docker") || ifaceName.startsWith("veth") || + ifaceName.startsWith("br-") || ifaceName.startsWith("virbr")) { + currentInterface.isVirtual = true; + } + } + + if (currentInterface != null) { + // 解析状态 + Matcher stateMatcher = statePattern.matcher(line); + if (stateMatcher.find()) { + currentInterface.state = stateMatcher.group(1); + } + + // 解析MAC地址 + Matcher linkMatcher = linkPattern.matcher(line); + if (linkMatcher.find()) { + currentInterface.macAddress = linkMatcher.group(2); + } + } + } + + // 添加最后一个接口 + if (currentInterface != null) { + interfaces.add(currentInterface); + } + + reader.close(); + process.waitFor(); + + // 补充父接口信息 + Map interfaceNames = new HashMap<>(); + for (NetworkInterfaceDetails iface : interfaces) { + interfaceNames.put(iface.name, iface.name); + } + + // 确保所有父接口都存在 + for (NetworkInterfaceDetails iface : interfaces) { + if (iface.parent != null && !interfaceNames.containsKey(iface.parent)) { + // 如果父接口不存在,可能是类似 eth0.203 的情况 + if (iface.parent.contains(".")) { + String[] parts = iface.parent.split("\\."); + if (parts.length > 0) { + iface.parent = parts[0]; + } + } + } + } + + return interfaces; + } + + /** + * 从 ip addr 命令获取IPv4地址 + */ + private static String getIPv4AddressFromIpAddr(String interfaceName) throws Exception { + String command = String.format("ip -4 addr show %s 2>/dev/null || echo ''", interfaceName); + Process process = Runtime.getRuntime().exec(new String[]{"sh", "-c", command}); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + + while ((line = reader.readLine()) != null) { + if (line.contains("inet ")) { + String[] parts = line.trim().split("\\s+"); + if (parts.length >= 2) { + String ipWithMask = parts[1]; + reader.close(); + process.waitFor(); + return ipWithMask.split("/")[0]; + } + } + } + + reader.close(); + process.waitFor(); + return "N/A"; + } + + /** + * 从 ip addr 命令获取IPv6地址 + */ + private static String getIPv6AddressFromIpAddr(String interfaceName) throws Exception { + String command = String.format("ip -6 addr show %s 2>/dev/null | grep -v scope.link || echo ''", interfaceName); + Process process = Runtime.getRuntime().exec(new String[]{"sh", "-c", command}); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + + while ((line = reader.readLine()) != null) { + if (line.contains("inet6 ")) { + String[] parts = line.trim().split("\\s+"); + if (parts.length >= 2) { + String ipWithMask = parts[1]; + reader.close(); + process.waitFor(); + return ipWithMask.split("/")[0]; + } + } + } + + reader.close(); + process.waitFor(); + return "N/A"; + } + + /** + * 判断是否为以太网接口 + */ + private static boolean isEthernetInterface(NetworkInterfaceDetails interfaceDetail) { + String name = interfaceDetail.name; + // 判断是否为以太网接口 + if (name.startsWith("eth") || // Linux 传统 + name.startsWith("en") || // systemd 命名 (enp3s0) + name.startsWith("em") // 有些主板网卡 + ) { + return true; + } + + // 如果有父接口,也可能是有效的子接口 + if (interfaceDetail.parent != null && !interfaceDetail.parent.isEmpty()) { + return true; + } + + return false; + } + + /** + * 检查物理链路状态 + */ + private static boolean isInterfaceConnected(String interfaceName) { + try { + String os = System.getProperty("os.name").toLowerCase(); + Process process; + + if (os.contains("win")) { + // Windows: 使用 wmic 检查网卡是否启用 + process = Runtime.getRuntime().exec( + "wmic nic where \"NetEnabled=true\" get Name"); + } else { + // Linux: 检查 operstate + process = Runtime.getRuntime().exec( + "cat /sys/class/net/" + interfaceName + "/operstate"); + } + + BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + if (os.contains("win")) { + if (line.trim().equalsIgnoreCase(interfaceName)) { + return true; + } + } else { + return "up".equals(line.trim()); + } + } + reader.close(); + return false; + } catch (Exception e) { + System.err.println("无法检查接口状态: " + interfaceName); + return false; // 保守处理 + } + } + + /** + * 内部类:网卡详情 + */ + static class NetworkInterfaceDetails { + int index; + String name; + + String realName; + String state = "UNKNOWN"; + String macAddress = ""; + String parent = null; + boolean isVirtual = false; + } +} \ No newline at end of file