【问题标题】:Getting interface name/address from (or mapping NetworkInterface to) jpcap device paths从(或将 NetworkInterface 映射到)jpcap 设备路径获取接口名称/地址
【发布时间】:2015-02-05 20:38:26
【问题描述】:

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

  1. 向用户显示人类可读的网络接口名称及其 IP 地址列表。
  2. 在用户选择的接口上启动 jpcap 数据包捕获。

但是,以下几点给我带来了麻烦:

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

NetworkInterfaces 的列表既是 up 又不是 loopback 确实对应于 jpcap 返回的设备列表,尽管它们的顺序不同。

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

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

如何从NetworkInterface 获取 jpcap 打开设备所需的信息(或相反 - 在给定设备路径的情况下获取NetworkInterface),或者是否有另一种方法可以让我只直接从 jpcap 获取每个设备的漂亮显示名称和 IP 地址?


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

  1. 从路径中提取 GUID(例如,上面示例中的 {39966C4C-3728-4368-AE92-1D36ACAF6634})。留下花括号并将其命名为
  2. HKLM\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\<guid> 包含设备的当前 IP 地址以及一些其他配置信息。
  3. HKLM\SYSTEM\CurrentControlSet\services\<guid>\Parameters\Tcpip 包含类似信息。
  4. 搜索HKLM\SYSTEM\CurrentControlSet\Control\Class\中子键的所有子键。如果找到包含值为 的键 NetCfgInstanceId 的子键,则其中的其余键将包含驱动程序信息 - 漂亮的显示名称、供应商信息等。李>

我不知道 IPv6 是如何影响上述内容的(有一些注册表区域带有单独的 Tcpip6 信息块)。我也不知道这些键在 Windows 7 之外是否相同,但我怀疑它们是相同的。如果没有提供更好的答案,我将使用示例代码将上述内容转换为答案。我仍在寻找更直接(理想情况下独立于平台和无注册表)的方式。

【问题讨论】:

  • 如果有人可以确认上述注册表项存在于 Windows 8 上,我将删除 windows-7

标签: java windows windows-7 jpcap


【解决方案1】:

使用 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\中子键的所有子键。如果找到包含值为 {} 的键 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

希望这会有所帮助。欢迎改进。也欢迎提供不使用注册表的更直接方法的更好建议。

【讨论】:

    【解决方案2】:

    平台无关,网络接口

    这是一个替代解决方案,它应该是独立于平台的,尽管只提供启动接口的信息。注册表解决方案是我的第一次尝试,效果很好,但我相信这是一个更好的解决方案,只要不需要有关 down 接口的信息。

    方法

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

    实施

    先决条件:

    • 除了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}:空 \Device\NPF_{39966C4C-3728-4368-AE92-1D36ACAF6634}:名称:net5(1x1 11b/g/n 无线 LAN PCI Express 半迷你卡适配器)

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

    【讨论】:

      猜你喜欢
      • 2020-09-07
      • 1970-01-01
      • 1970-01-01
      • 2013-06-21
      • 1970-01-01
      • 2012-08-02
      • 2020-09-02
      • 1970-01-01
      • 2011-11-27
      相关资源
      最近更新 更多