服務器常擁有多塊網卡(物理、虛擬、Docker、VPN),單純 InetAddress.getLocalHost() 往往返回 127.0.0.1 或錯誤綁定。需遍歷 NetworkInterface,過濾回環、非 UP、虛擬介面,精準定位業務 IP 與對應 MAC。
public final class NetworkIdentityUtil {
private static final Pattern IPV4_PATTERN = Pattern.compile("(\\d{1,3}\\.){3}\\d{1,3}");
private NetworkIdentityUtil() {}
/** 獲取首個合適的 IPv4 地址(非回環、非鏈路本地) */
public static String getPrimaryIpv4() {
return getAllIpv4s().stream().findFirst().orElse(null);
}
/** 獲取所有合適的 IPv4 列表 */
public static List<String> getAllIpv4s() {
List<String> ips = new ArrayList<>();
try {
Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
while (nis.hasMoreElements()) {
NetworkInterface ni = nis.nextElement();
if (!isValidInterface(ni)) continue;
Enumeration<InetAddress> ias = ni.getInetAddresses();
while (ias.hasMoreElements()) {
InetAddress ia = ias.nextElement();
if (isValidIpv4(ia)) {
ips.add(ia.getHostAddress());
}
}
}
} catch (SocketException e) {
log.warn("獲取網卡信息失敗", e);
}
return ips;
}
/** 獲取首個有效網卡的 MAC */
public static String getPrimaryMac() {
return getAllMacs().stream().findFirst().orElse(null);
}
/** 獲取所有有效網卡 MAC 列表 */
public static List<String> getAllMacs() {
List<String> macs = new ArrayList<>();
try {
Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
while (nis.hasMoreElements()) {
NetworkInterface ni = nis.nextElement();
if (!isValidInterface(ni)) continue;
byte[] hwAddr = ni.getHardwareAddress();
if (hwAddr != null && hwAddr.length > 0) {
macs.add(formatMac(hwAddr));
}
}
} catch (SocketException e) {
log.warn("獲取 MAC 失敗", e);
}
return macs;
}
/** 校驗網卡有效性:UP、非回環、非虛擬、非點對點 */
private static boolean isValidInterface(NetworkInterface ni) throws SocketException {
return ni.isUp()
&& !ni.isLoopback()
&& !ni.isVirtual()
&& !ni.isPointToPoint()
&& !ni.getName().matches(".*(docker|veth|br-|vmnet|vboxnet|tun|tap).*"); // 常見虛擬網卡前綴
}
/** 校驗 IPv4:非回環、非鏈路本地、匹配模式 */
private static boolean isValidIpv4(InetAddress ia) {
return !ia.isLoopbackAddress()
&& !ia.isLinkLocalAddress()
&& IPV4_PATTERN.matcher(ia.getHostAddress()).matches();
}
/** 格式化 MAC:XX-XX-XX-XX-XX-XX 大寫 */
private static String formatMac(byte[] bytes) {
StringBuilder sb = new StringBuilder(17);
for (int i = 0; i < bytes.length; i++) {
if (i > 0) sb.append('-');
int v = bytes[i] & 0xFF;
sb.append(String.format("%02X", v));
}
return sb.toString();
}
}
| 決策點 | 方案 | 理由 |
|---|---|---|
| IPv4 正則 | (\\d{1,3}\\.){3}\\d{1,3} | 快速過濾 IPv6、主機名,避免 InetAddress 解析開銷 |
| 虛擬網卡過濾 | 名稱黑名單 + isVirtual() | Docker、VMware、VPN 等虛擬介面無業務意義,且 MAC 常全零或隨機 |
| 多 IP 策略 | 返回列表,調用方按優先級選(如優先 10.x、172.x、192.168.x) | 業務場景不同,避免工具類硬編碼優先級 |
| MAC 格式 | 連字符分隔大寫 | 符合多數 CMDB、資產系統錄入規範 |
| OS | 測試場景 | 結果 |
|---|---|---|
| Linux (CentOS/Ubuntu) | 雙網卡綁定、VLAN 子介面、Docker 橋接 | ✅ 正確返回物理網卡 IP/MAC |
| Windows Server 2019 | Hyper-V 虛擬交換器、多網卡團隊 | ✅ 過濾虛擬介面,獲取物理網卡 |
| macOS (M1/M2) | Wi-Fi + Thunderbolt 以太網 | ✅ 正常 |
| 容器 (K8s Pod) | hostNetwork=true/false | ⚠️ hostNetwork=false 只見 Pod 內部介面,需配合 Downward API 獲取宿主信息 |
K8s 中若需宿主機 IP/MAC,推薦:
host-local-ip-daemonset)。/sys/class/net/ 宿主網卡信息。遍歷 NetworkInterface、多維度過濾(狀態、類型、名稱)、返回集合而非單值,是獲取服務器網絡身份的穩健姿勢。工具類零依賴、易擴展,可直接集成至微服務啟動檢查、分布式鎖節點標識、審計日誌上下文等場景。