从(或将NetworkInterface映射到)jpcap设备路径获取接口名称/地址

时间:2014-12-08 08:07:42

标签: java windows windows-7 jpcap

我正在尝试执行以下操作:

  1. 向用户显示人类可读的网络接口名称及其IP地址列表。
  2. 在用户选择的界面上启动jpcap数据包捕获。
  3. 但是,以下几点给我带来了麻烦:

    • jpcap仅提供PacketCapture.lookupDevices(),它返回Windows的NPF驱动程序设备路径列表(例如\Device\NPF_{39966C4C-3728-4368-AE92-1D36ACAF6634})和相当平淡的显示字符串(例如Microsoft),以及没有其他信息。所以我不能用它来构建UI界面列表。
    • NetworkInterface.getNetworkInterfaces()提供系统上的接口列表,其中包含UI所需的所有信息,但NetworkInterface不提供NDF驱动程序设备路径,仅提供显示名称和设备名称,例如“net5”,“lo”等。
    • jpcap的PacketCapture#open()只接受设备路径。

    同时启动而非环回的NetworkInterface列表对应于jpcap返回的设备列表,尽管它们的顺序不同。

    所以,我在NetworkInterface中找不到任何可以传递给PacketCapture#open()的内容,而且我不知道如何从{{1}返回的设备路径中获取适当的UI信息}}。 PacketCapture#lookupDevices()不接受PacketCapture。因此,我被困住了。

    我没有在Linux上试过这个。我怀疑该问题是Windows独有的,其中NetworkInterface#getName()NetworkInterface#getName()识别的设备路径不对应。​​

    如何从PacketCapture#open()获取jpcap打开设备所需的信息(或者相反,给定设备路径获取NetworkInterface),或者是否有另一种方法可以请允许我直接从jpcap获取每个设备的良好显示名称和IP地址?


    Windows'注册表:我一直在进行一些挖掘工作,至少在注册表中找到了有关NPF设备的信息。给定一个jpcap设备路径,并使用one of the techniques here或本机库,一个不错的适配器名称(相当于那些NetworkInterface返回)和当前的IP地址可以从注册表获得,如下所示: / p>

    1. 从路径中提取GUID(例如,上例中的NetworkInterface)。留下花括号并调用
    2. {39966C4C-3728-4368-AE92-1D36ACAF6634}包含设备的当前IP地址以及其他一些配置信息。
    3. HKLM\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\<guid>包含类似的信息。
    4. HKLM\SYSTEM\CurrentControlSet\services\<guid>\Parameters\Tcpip中搜索子项的所有子项。如果找到包含值为&lt; guid&gt; 的键HKLM\SYSTEM\CurrentControlSet\Control\Class\的子项,那么其余的键将包含驱动程序信息 - 好的显示名称,供应商信息等
    5. 我不知道IPv6因素如何进入上面(有一些注册表区域有一个单独的Tcpip6信息块)。我也不知道这些密钥在Windows 7之外是否相同,但我怀疑它们是。如果没有更好的答案,我将使用示例代码将上述内容转换为答案。我仍在寻找更直接(理想的平台无关和无注册表)方式。

2 个答案:

答案 0 :(得分:3)

带有Windows注册表的间接解决方案

我至少在注册表中找到了有关NPF设备的信息,并且正在将我的问题的最后一点扩展到答案。

方法

给定一个jpcap设备路径,一个不错的适配器名称(相当于那些NetworkInterface返回),并且可以从注册表获取当前的IP地址,如下所示:

  1. 从路径中提取GUID(例如,上例中的39966C4C-3728-4368-AE92-1D36ACAF6634)。
  2. HKLM\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\{<guid>}包含设备的当前IP地址以及其他一些配置信息。
  3. HKLM\SYSTEM\CurrentControlSet\Control\Class\中搜索子项的所有子项。如果找到包含值为{em> {&lt; guid&gt;} 的密钥NetCfgInstanceId的子项,则其余的密钥将包含驱动程序信息 - 良好的显示名称,供应商信息等等。
  4. 实施

    先决条件:

    的问题:

    • java.util.prefs.WindowsPreferences(因此WinRegistry)只能读取字符串键,而不能读取整数。因此,下面的代码无法可靠地确定是否启用了DHCP。作为一个黑客,使用的逻辑是检查静态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: loading native library jpcap.. ok
    \Device\NPF_{691D289D-7EE5-4BD8-B5C1-3C4729A852D5}:
      Description:   Microsoft Virtual WiFi Miniport Adapter
      Vendor:        Microsoft
      Address:       0.0.0.0
      Subnet Mask:   255.0.0.0
      jpcap Display: Microsoft
      GUID:          691D289D-7EE5-4BD8-B5C1-3C4729A852D5
    \Device\NPF_{39966C4C-3728-4368-AE92-1D36ACAF6634}:
      Description:   1x1 11b/g/n Wireless LAN PCI Express Half Mini Card Adapter
      Vendor:        Realtek Semiconductor Corp.
      Address:       192.168.1.23
      Subnet Mask:   255.255.255.0
      jpcap Display: Microsoft
      GUID:          39966C4C-3728-4368-AE92-1D36ACAF6634
    

    希望这很有帮助。欢迎改进。不使用注册表的更直接方式的更好建议也是受欢迎的。

答案 1 :(得分:0)

与平台无关,NetworkInterface

这是一个应该与平台无关的备用解决方案,尽管只提供了启动接口的信息。注册表解决方案是我的第一次尝试,它运行良好,但我相信这是一个更好的解决方案,只要不需要有关 down 接口的信息。

方法

  1. PacketCapture可以在给定设备字符串的情况下提供网络地址和子网掩码(虽然它是实例方法,但不是静态方法)。对于PacketCapture.lookupDevices()中的每个设备字符串:
  2. PacketCapture实例获取网络地址和掩码(捕获不需要打开)。
  3. 搜索NetworkInterface.getNetworkInterfaces()返回的所有网络接口,找到一个地址位于同一网络上的网络地址和jpcap为设备返回的掩码。
  4. NetworkInterface(可能)对应于设备字符串。
  5. 实施

    先决条件:

    • jpcap以外没有依赖项。测试版本0.01.16。

    的问题:

    • 虽然与平台无关,但与基于注册表的解决方案不同,它只能找到已启动的接口。
    • 字节排序很奇怪。我无法理解SourceForge上的jpcap讨论论坛,但有人似乎确实指出了这一点。因此,我认为它将来总是会发生变化。
    • 可能有很多边缘情况会导致返回我未测试的错误结果。

    代码

    代码如下。在SO CC attribution-sharealike license下使用是免费的。它是独立的,所以我没有把它放在github上。

    import java.net.Inet4Address;
    import java.net.InetAddress;
    import java.net.NetworkInterface;
    import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.List;
    
    import net.sourceforge.jpcap.capture.CaptureDeviceLookupException;
    import net.sourceforge.jpcap.capture.PacketCapture;
    
    public class JpcapInterfaceInfo {
    
    
        /**
         * Get a list of interface information for all devices returned by jpcap.
         * @param capture An instance of PacketCapture to use for getting network address and mask info. If null,
         *                a new instance will be created.
         * @return List of information.
         * @throws CaptureDeviceLookupException
         */
        public static List<InterfaceInfo> listInterfaces (PacketCapture capture) throws CaptureDeviceLookupException {
    
            if (capture == null)
                capture = new PacketCapture();
    
            List<InterfaceInfo> infos = new ArrayList<InterfaceInfo>();
            for (String device : PacketCapture.lookupDevices())
                infos.add(getInterfaceInfo(capture, device));
    
            return infos;
    
        }
    
    
        /**
         * Get a list of interface information for all devices returned by jpcap.
         * @return List of information.
         * @throws CaptureDeviceLookupException
         */
        public static List<InterfaceInfo> listInterfaces () throws CaptureDeviceLookupException {
            return listInterfaces(null);
        }
    
    
    
    
        /**
         * Utility to check if an interface address matches a jpcap network address and mask.
         * @param address An InetAddress to check.
         * @param jpcapAddr Network address.
         * @param jpcapMask Network mask.
         * @return True if address is an IPv4 address on the network given by jpcapAddr/jpcapMask,
         *         false otherwise.
         */
        private static boolean networkMatches (InetAddress address, int jpcapAddr, int jpcapMask) {
    
            if (!(address instanceof Inet4Address))
                return false;
    
            byte[] address4 = address.getAddress();
            if (address4.length != 4)
                return false;
    
            int addr = ByteBuffer.wrap(address4).order(ByteOrder.LITTLE_ENDIAN).getInt();        
            return ((addr & jpcapMask) == jpcapAddr);
    
        }
    
    
        /**
         * Get an InterfaceInfo that corresponds to the given jpcap device string. The interface must be
         * up in order to query info about it; if it is not then the NetworkInterface in the returned
         * InterfaceInfo will be null.
         * @param capture A PacketCapture instance used to get network address and mask info.
         * @param jpcapDeviceString String from PacketCapture.lookupDevices().
         * @return InterfaceInfo.
         */
        public static InterfaceInfo getInterfaceInfo (PacketCapture capture, String jpcapDeviceString) {
    
            InterfaceInfo info = null;
            String deviceName = jpcapDeviceString.replaceAll("\n.*", "").trim();
    
            try {
    
                int netAddress = capture.getNetwork(deviceName);
                int netMask = capture.getNetmask(deviceName);
    
                // go through all addresses of all interfaces and try to find a match.
    
                Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
                while (e.hasMoreElements() && info == null) {
                    NetworkInterface iface = e.nextElement();
                    Enumeration<InetAddress> ae = iface.getInetAddresses();
                    while (ae.hasMoreElements() && info == null) {
                        if (networkMatches(ae.nextElement(), netAddress, netMask))
                            info = new InterfaceInfo(iface, deviceName);
                    }
                }
    
            } catch (Exception x) {
    
                System.err.println("While querying info for " + deviceName + ":");
                x.printStackTrace(System.err);
    
            }
    
            if (info == null)
                info = new InterfaceInfo(null, deviceName);
    
            return info;
    
        }
    
    
        /**
         * Information about a network interface for jpcap, which is basically just a NetworkInterface
         * with details, and the jpcap device name for use with PacketCapture.
         */
        public static class InterfaceInfo {
    
            private final NetworkInterface iface;
            private final String deviceName;
    
            InterfaceInfo (NetworkInterface iface, String deviceName) {
                this.iface = iface;
                this.deviceName = deviceName;
            }
    
            /**
             * Get NetworkInterface for this interface.
             * @return May return null if no matching NetworkInterface was found.
             */
            public final NetworkInterface getIface () {
                return iface;
            }
    
            /**
             * Get jpcap device name for this interface. This can be passed to PacketCapture.open().
             * @return Device name for interface.
             */
            public final String getDeviceName () {
                return deviceName;
            }
    
            @Override public final String toString () {
                return deviceName + " : " + iface;
            }
    
        }
    
    
    }
    

    实施例

    以下是一个例子:

    import java.util.List;
    
    import net.sourceforge.jpcap.capture.PacketCapture;
    
    public class JpcapInterfaceInfoTest {
    
        public static void main (String[] args) throws Exception {
    
            // Info can be queried from jpcap device list.
            List<JpcapInterfaceInfo.InterfaceInfo> infos = JpcapInterfaceInfo.listInterfaces();
    
            // Info can be displayed.
            for (JpcapInterfaceInfo.InterfaceInfo info : infos)
                System.out.println(info);
    
            // Device names from InterfaceInfo can be passed directly to jpcap:
            JpcapInterfaceInfo.InterfaceInfo selected = infos.get(0);
            PacketCapture capture = new PacketCapture();
            capture.open(selected.getDeviceName(), true);
    
        }
    
    }
    

    在我的机器上(与注册表解决方案相同的设置),输出:

    \Device\NPF_{691D289D-7EE5-4BD8-B5C1-3C4729A852D5} : null
    \Device\NPF_{39966C4C-3728-4368-AE92-1D36ACAF6634} : name:net5 (1x1 11b/g/n Wireless LAN PCI Express Half Mini Card Adapter)
    

    我没有像其他解决方案那样使输出变得漂亮。请注意,“虚拟wifi微型端口适配器”(第一个)具有空NetworkInterface,因为它未启动,因此无法找到匹配项(不存在IP地址和网络地址)。