繁体   English   中英

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

[英]Getting interface name/address from (or mapping NetworkInterface to) jpcap device paths

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

  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()只接受设备路径。

无论是up还是非loopback的NetworkInterface列表都对应于jpcap返回的设备列表,尽管它们的顺序不同。

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

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

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


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\\services\\<guid>\\Parameters\\Tcpip包含类似的信息。
  4. HKLM\\SYSTEM\\CurrentControlSet\\Control\\Class\\搜索子键的所有子键。 如果找到包含键值为<guid>的键NetCfgInstanceId的子键,那么其余键将包含驱动程序信息 - 好的显示名称,供应商信息等。

我不知道IPv6因素如何进入上面(有一些注册表区域有一个单独的Tcpip6信息块)。 我也不知道这些密钥在Windows 7之外是否相同,但我怀疑它们是。 如果没有更好的答案,我将使用示例代码将上述内容转换为答案。 我仍在寻找更直接(理想的平台无关和无注册表)的方式。

具有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的子密钥,其值为{<guid>} ,那么其余的密钥将包含驱动程序信息 - 漂亮的显示名称,供应商信息等。

履行

先决条件:

问题:

  • 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(下面没有),也可以在github上找到 根据SO的CC属性 - 共享许可,使用是免费的。

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

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

与平台无关的NetworkInterface

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

方法

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

履行

先决条件:

  • 除了jpcap之外没有依赖 测试版本0.01.16。

问题:

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

代码如下。 根据SO的CC属性 - 共享许可,使用是免费的。 它是独立的,所以我没有把它放在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地址和网络地址)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM