使用 Windows 注册表的间接解决方案
我至少在注册表中找到了有关 NPF 设备的信息,并且正在将我的问题的最后一点扩展到答案。
方法
给定一个 jpcap 设备路径,一个不错的适配器名称(相当于 NetworkInterface 返回的那些)和当前 IP 地址可以从注册表中获取,如下所示:
- 从路径中提取 GUID(例如,上面示例中的
39966C4C-3728-4368-AE92-1D36ACAF6634)。
-
HKLM\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\{<guid>} 包含设备的当前 IP 地址以及其他一些配置信息。
- 搜索
HKLM\SYSTEM\CurrentControlSet\Control\Class\中子键的所有子键。如果找到包含值为 {} 的键 NetCfgInstanceId 的子键,则其中的其余键将包含驱动程序信息 - 漂亮的显示名称、供应商信息等。
实施
先决条件:
问题:
-
java.util.prefs.WindowsPreferences(因此WinRegistry)只能读取字符串键,不能读取整数。因此,下面的代码无法可靠地确定是否启用了 DHCP。作为 hack,使用的逻辑是检查静态 IP/掩码,如果为空,则使用 DHCP IP/掩码(值单独存储在注册表中)。
- IP 地址是 REG_MULTI_SZ,大概也是为了说明 IPv6 寻址(验证?)。下面的代码很简单,不考虑这一点。我还没有测试过 IPv6 + IPv4。
- 我没有在 Windows 7 以外的任何其他版本的 Windows 上进行测试(Windows 8,有人验证吗?)。
- 针对jpcap 0.01.16 返回的设备字符串进行了测试。
- Linux / OSX 实现留给读者作为练习。
代码
代码如下。完整的代码,包括 WinRegistry(不在下面),也是 available on github。在 SO 的CC attribution-sharealike license 下免费使用。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Gets information about network interface given a jpcap device string, on Windows. Makes
* use of WinRegistry class from https://stackoverflow.com/a/6163701/616460. This is tested
* against jpcap 0.01.16, which is available for download at http://sourceforge.net/projects/jpcap/.
*
* All getters return empty strings rather than null if the information is unavailable.
*
* @author https://stackoverflow.com/users/616460/jason-c
*/
public class NetworkDeviceInfo {
private static final int DRIVER_CLASS_ROOT = WinRegistry.HKEY_LOCAL_MACHINE;
private static final String DRIVER_CLASS_PATH = "SYSTEM\\CurrentControlSet\\Control\\Class";
private static final String NETCFG_INSTANCE_KEY = "NetCfgInstanceId";
private static final int IFACE_ROOT = WinRegistry.HKEY_LOCAL_MACHINE;
private static final String IFACE_PATH = "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces";
private final String jpcapDeviceName;
private final String jpcapDisplayName;
private final String guid;
private final String driverName;
private final String driverVendor;
private final String interfaceAddress;
private final String interfaceSubnetMask;
/**
* Construct from a jpcap device string.
* @param jpcapDeviceString Device string from jpcap.
* @throws IllegalArgumentException If the device string could not be parsed.
* @throws UnsupportedOperationException If the Windows registry could not be read.
*/
public NetworkDeviceInfo (String jpcapDeviceString) throws IllegalArgumentException, UnsupportedOperationException {
// extract jpcap device and display name, and guid, from jpcap device string
String[] jpcapParts = jpcapDeviceString.split("\n", 2);
jpcapDeviceName = (jpcapParts.length > 0) ? jpcapParts[0].trim() : "";
jpcapDisplayName = (jpcapParts.length > 1) ? jpcapParts[1].replaceAll("\n", " ").trim() : "";
Matcher matcher = Pattern.compile("\\{(\\S*)\\}").matcher(jpcapDeviceName);
guid = matcher.find() ? matcher.group(1) : null;
if (guid == null)
throw new IllegalArgumentException("Could not parse GUID from jpcap device name '" + jpcapDeviceName + "'");
try {
// search registry for driver details:
// Search all subkeys of subkeys in HKLM\SYSTEM\CurrentControlSet\Control\Class\. If a subkey
// is found that contains a key NetCfgInstanceId whose value is {guid}, then the rest of the keys
// there will contain driver info - the nice display name, vendor info, etc.
String theDriverName = "";
String theDriverVendor = "";
for (String driverClassSubkey : WinRegistry.readStringSubKeys(DRIVER_CLASS_ROOT, DRIVER_CLASS_PATH)) {
for (String driverSubkey : WinRegistry.readStringSubKeys(DRIVER_CLASS_ROOT, DRIVER_CLASS_PATH + "\\" + driverClassSubkey)) {
String path = DRIVER_CLASS_PATH + "\\" + driverClassSubkey + "\\" + driverSubkey;
String netCfgInstanceId = WinRegistry.readString(DRIVER_CLASS_ROOT, path, NETCFG_INSTANCE_KEY);
if (netCfgInstanceId != null && netCfgInstanceId.equalsIgnoreCase("{" + guid + "}")) {
theDriverName = trimOrDefault(WinRegistry.readString(DRIVER_CLASS_ROOT, path, "DriverDesc"), "");
theDriverVendor = trimOrDefault(WinRegistry.readString(DRIVER_CLASS_ROOT, path, "ProviderName"), "");
// other interesting keys: DriverVersion, DriverDate
break;
}
}
if (!theDriverName.isEmpty())
break;
}
driverName = trimOrDefault(theDriverName, jpcapDisplayName);
driverVendor = trimOrDefault(theDriverVendor, "Unknown");
// read tcp/ip configuration details (HKLM\SYSTEM\CCS\services\Tcpip\Parameters\Interfaces\{guid})
// there is an integer key EnableDHCP, but java.util.prefs.WindowsPreferences (and therefore
// WinRegistry) supports reading string keys only, therefore we'll have to hack it to decide on
// DHCP vs. static IP address and hope it's correct.
// also note the ip addresses are REG_MULTI_SZ, presumably to also hold ipv6 addresses. the results
// here may not be quite correct, then. that's why I'm leaving addresses as strings instead of
// converting them to InetAddresses.
String ifPath = IFACE_PATH + "\\{" + guid + "}";
String dhcpIp = trimOrDefault(WinRegistry.readString(IFACE_ROOT, ifPath, "DhcpIPAddress"), "");
String dhcpMask = trimOrDefault(WinRegistry.readString(IFACE_ROOT, ifPath, "DhcpSubnetMask"), "");
// if static set, use it, otherwise use dhcp
interfaceAddress = trimOrDefault(WinRegistry.readString(IFACE_ROOT, ifPath, "IPAddress"), dhcpIp);
interfaceSubnetMask = trimOrDefault(WinRegistry.readString(IFACE_ROOT, ifPath, "SubnetMask"), dhcpMask);
} catch (Exception x) {
throw new UnsupportedOperationException("Information could not be read from the Windows registry.", x);
}
}
/**
* @param str A string.
* @param def A default string.
* @return Returns def if str is null or empty (after trim), otherwise returns str, trimmed.
*/
private final static String trimOrDefault (String str, String def) {
str = (str == null) ? "" : str.trim();
return str.isEmpty() ? def : str;
}
/**
* Gets the jpcap device name, which can be passed to PacketCapture.
* @return Device name from jpcap. Pass this string to PacketCapture to specify this device.
*/
public final String getJpcapDeviceName () {
return jpcapDeviceName;
}
/**
* Gets the jpcap display name. Usually this is pretty bland.
* @return Display name from jpcap.
*/
public final String getJpcapDisplayName () {
return jpcapDisplayName;
}
/**
* Gets the interface GUID.
* @return Interface GUID.
*/
public final String getGuid () {
return guid;
}
/**
* Get a nice display name for the interface driver. Display this in GUIs.
* @return Interface driver name.
*/
public final String getDriverName () {
return driverName;
}
/**
* Get the interface driver vendor name. Could be displayed in GUIs.
* @return Interface driver vendor name.
*/
public final String getDriverVendor () {
return driverVendor;
}
/**
* Get the interface's IP address.
* @return Interface's IP address.
* @bug This may not be correct for interfaces with multiple IP addresses. For this reason, it is
* left as a raw string rather than being converted to an InetAddress.
*/
public final String getInterfaceAddress () {
return interfaceAddress;
}
/**
* Get the interface's subnet mask.
* @return Interface's subnet mask.
* @bug Same issue as getInterfaceAddress().
*/
public final String getInterfaceSubnetMask () {
return interfaceSubnetMask;
}
/**
* Get a display string, for debugging.
* @return Display string, for debugging.
*/
@Override public String toString () {
return String.format("%s (%s) {%s} @ %s/%s", driverName, driverVendor, guid, interfaceAddress, interfaceSubnetMask);
}
}
示例
这是一个例子:
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.jpcap.capture.PacketCapture;
public class NetworkDeviceInfoTest {
public static void main (String[] args) throws Exception {
List<NetworkDeviceInfo> infos = new ArrayList<NetworkDeviceInfo>();
// Info can be queried from jpcap device string.
for (String jpcapDevice : PacketCapture.lookupDevices())
infos.add(new NetworkDeviceInfo(jpcapDevice));
// Info can be displayed.
for (NetworkDeviceInfo info : infos) {
System.out.println(info.getJpcapDeviceName() + ":");
System.out.println(" Description: " + info.getDriverName());
System.out.println(" Vendor: " + info.getDriverVendor());
System.out.println(" Address: " + info.getInterfaceAddress());
System.out.println(" Subnet Mask: " + info.getInterfaceSubnetMask());
System.out.println(" jpcap Display: " + info.getJpcapDisplayName());
System.out.println(" GUID: " + info.getGuid());
}
// Device names from NetworkDeviceInfo can be passed directly to jpcap:
NetworkDeviceInfo selected = infos.get(0);
PacketCapture capture = new PacketCapture();
capture.open(selected.getJpcapDeviceName(), true);
}
}
在我的机器上输出:
PacketCapture:加载本机库 jpcap .. 好的
\设备\NPF_{691D289D-7EE5-4BD8-B5C1-3C4729A852D5}:
描述:Microsoft 虚拟 WiFi 微型端口适配器
供应商:微软
地址:0.0.0.0
子网掩码:255.0.0.0
jpcap 显示:微软
GUID:691D289D-7EE5-4BD8-B5C1-3C4729A852D5
\设备\NPF_{39966C4C-3728-4368-AE92-1D36ACAF6634}:
描述:1x1 11b/g/n 无线 LAN PCI Express 半迷你卡适配器
供应商:瑞昱半导体公司
地址:192.168.1.23
子网掩码:255.255.255.0
jpcap 显示:微软
GUID:39966C4C-3728-4368-AE92-1D36ACAF6634
希望这会有所帮助。欢迎改进。也欢迎提供不使用注册表的更直接方法的更好建议。