Получение имени/адреса интерфейса из (или сопоставление NetworkInterface с) путей устройства jpcap

Я пытаюсь сделать следующее:

  1. Отобразить пользователю список удобочитаемых имен сетевых интерфейсов и их IP-адресов.
  2. Запустите захват пакета jpcap на выбранном пользователем интерфейсе.

Однако следующие моменты вызывают у меня затруднения:

  • jpcap предоставляет только PacketCapture.lookupDevices(), который возвращает список путей устройств драйвера Windows NPF к интерфейсам (например, \Device\NPF_{39966C4C-3728-4368-AE92-1D36ACAF6634}) и довольно мягкую отображаемую строку (например, Microsoft), и никакой другой информации. Поэтому я не могу использовать его для создания списка интерфейсов пользовательского интерфейса.
  • NetworkInterface.getNetworkInterfaces() предоставляет список интерфейсов в системе со всей информацией, необходимой для пользовательского интерфейса, но NetworkInterface не предоставляет путь к устройству драйвера NDF, а только отображаемые имена и имена устройств, такие как «net5», «lo» и т. д.
  • PacketCapture#open() jpcap принимает только пути устройств.

Список NetworkInterface, которые работают и не замыкаются на себя, соответствует списку устройств, возвращаемому jpcap, хотя они не в том же порядке.

Итак, я не могу найти в NetworkInterface ничего, что можно было бы передать в PacketCapture#open(), и я не знаю, как получить подходящую для пользовательского интерфейса информацию из путей к устройствам, возвращаемых PacketCapture#lookupDevices(). 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\. Если найден подраздел, содержащий ключ NetCfgInstanceId со значением ‹guid›, то остальные ключи будут содержать информацию о драйвере — красивое отображаемое имя, информацию о поставщике и т. д.

Я не знаю, как IPv6 влияет на вышеизложенное (есть несколько областей реестра с отдельным блоком информации Tcpip6). Я также не знаю, одинаковы ли эти ключи за пределами Windows 7, но подозреваю, что да. Я преобразую приведенное выше в ответ с примером кода, если не будет представлено лучших ответов. Я все еще ищу более прямой (в идеале независимый от платформы и свободный от реестра) способ.


person Jason C    schedule 08.12.2014    source источник
comment
Если кто-то сможет подтвердить, что указанные выше разделы реестра присутствуют в Windows 8, я удалю windows-7.   -  person Jason C    schedule 08.12.2014


Ответы (2)


Косвенное решение с реестром 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›}, то остальные ключи будут содержать информацию о драйвере — красивое отображаемое имя, информацию о поставщике и т. д.

Реализация

Предпосылки:

  • Для приведенного ниже кода требуется WinRegistry из https://stackoverflow.com/a/6163701/616460. Вы можете скопировать и вставить его оттуда.
  • Никаких нативных библиотек не требуется.

Проблемы:

  • java.util.prefs.WindowsPreferences (и, следовательно, WinRegistry) может читать только строковые ключи, а не целые числа. Поэтому приведенный ниже код не может надежно определить, включен ли DHCP. В качестве хака используется логика проверки статического IP-адреса/маски и, если она пуста, возврата к IP-адресу/маске DHCP (значения хранятся отдельно в реестре).
  • IP-адрес — REG_MULTI_SZ, предположительно для учета адресации IPv6 (проверить?). Код ниже прост и не учитывает это. Я не тестировал IPv6 + IPv4.
  • Я не тестировал ни на какой другой версии Windows, кроме Windows 7 (Windows 8, кто-нибудь проверял?).
  • Протестировано для строк устройств, возвращаемых jpcap 0.01.16.
  • Реализации Linux/OSX оставляем читателю в качестве упражнения.

Код

Код ниже. Полный код, включая WinRegistry (отсутствует ниже), также доступен на github. Использование бесплатно в соответствии с лицензией SO с указанием авторства.

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

Надеюсь, это будет полезно. Улучшения приветствуются. Также приветствуется лучшее предложение более прямого пути без использования реестра.

person Jason C    schedule 08.12.2014

Независимый от платформы, сетевой интерфейс

Вот альтернативное решение, которое должно быть независимым от платформы, хотя и предоставляет информацию только для работающих интерфейсов. Решение с реестром было моей первой попыткой, оно работает хорошо, но я считаю, что это лучшее решение, если не требуется информация об интерфейсах down.

Метод

  1. PacketCapture может предоставить сетевой адрес и маску подсети с учетом строки устройства (однако это метод экземпляра, а не статический метод). Для каждой строки устройства в PacketCapture.lookupDevices():
  2. Получите его сетевой адрес и маску от экземпляра PacketCapture (захват не обязательно должен быть открытым).
  3. Найдите все сетевые интерфейсы, возвращенные NetworkInterface.getNetworkInterfaces(), и найдите тот, у которого есть адрес, который находится в той же сети, которая задана сетевым адресом и маской, которую jpcap вернул для устройства.
  4. Это NetworkInterface (вероятно) соответствует строке устройства.

Реализация

Предпосылки:

  • Никаких зависимостей, кроме jpcap. Протестировано с версией 0.01.16.

Проблемы:

  • Несмотря на независимость от платформы, в отличие от решения на основе реестра, оно может находить только работающие интерфейсы.
  • Порядок байтов странный. Я не очень понимаю дискуссионный форум jpcap на SourceForge, но кто-то, кажется, указал на это. Поэтому я полагаю, что это всегда может измениться в будущем.
  • Вероятно, есть много пограничных случаев, которые заставят это возвращать неверные результаты, которые я не тестировал.

Код

Код ниже. Использование бесплатно в соответствии с лицензией SO с указанием авторства и общим доступом. Он автономный, поэтому я не размещал его на 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)

Я не сделал вывод таким красивым, как другое решение. Обратите внимание, что «адаптер виртуального мини-порта Wi-Fi» (первый) имеет нулевой NetworkInterface, потому что, поскольку он не работает, совпадение найти не удалось (IP-адрес и сетевой адрес отсутствовали).

person Jason C    schedule 09.12.2014