Search code examples
javawindowswindows-7jpcap

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


I am trying to do the following:

  1. Display a list of human-readable network interface names and their IP addresses to the user.
  2. Start a jpcap packet capture on the interface the user selects.

However, the following points are giving me trouble:

  • jpcap only provides PacketCapture.lookupDevices(), which returns a list of Windows' NPF driver device paths to the interfaces (e.g. \Device\NPF_{39966C4C-3728-4368-AE92-1D36ACAF6634}) and a rather bland display string (e.g. Microsoft), and no other info. So I cannot use it to construct the UI interface list.
  • NetworkInterface.getNetworkInterfaces() provides a list of interfaces on the system with all the info I need for the UI, but NetworkInterface does not provide the NDF driver device path, only display names, and device names such as "net5", "lo", etc.
  • jpcap's PacketCapture#open() only accepts device paths.

The list of NetworkInterfaces that are both up and not loopback do correspond to the list of devices returned by jpcap, although they are not in the same order.

So, I can't find anything in NetworkInterface that can be passed to PacketCapture#open(), and I don't know how to get UI-appropriate info from the device paths returned by PacketCapture#lookupDevices(). PacketCapture does not accept NetworkInterface#getName(). Therefore, I'm stuck.

I have not tried this on Linux. I suspect the problem is unique to Windows, where NetworkInterface#getName() does not correspond to the device paths recognized by PacketCapture#open().

How can I get the information that jpcap needs to open the device from a NetworkInterface (or the other way around - get a NetworkInterface given a device path), or is there another approach that will allow me to just get a nice display name and IP address for each device directly from jpcap?


Windows' Registry: I've been doing some digging and have at least found information about NPF devices in the registry. Given a jpcap device path, and using either one of the techniques here or a native library, a nice adapter name (equivalent to the ones NetworkInterface returns) and the current IP address can be obtained from the registry as follows:

  1. Extract GUID from path (e.g. {39966C4C-3728-4368-AE92-1D36ACAF6634} from above example). Leave the curly braces and call this .
  2. HKLM\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\<guid> contains current IP address for device as well as some other configuration info.
  3. HKLM\SYSTEM\CurrentControlSet\services\<guid>\Parameters\Tcpip contains similar information.
  4. 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.

I do not know how IPv6 factors into the above (there are a few registry areas with a separate Tcpip6 block of info). I also do not know if these keys are the same outside of Windows 7, but I suspect they are. I will convert the above to an answer, with example code, if no better answers are presented. I am still looking for a more direct (ideally platform-independent and registry-free) way.


Solution

  • Platform-Independent, NetworkInterface

    Here is an alternate solution that should be platform independent although only provides info for interfaces that are up. The registry solution was my first attempt, it works well, but I believe this is a better solution as long as information about down interfaces is not required.

    Method

    1. PacketCapture can provide a network address and subnet mask given a device string (it's an instance method, not a static method, though). For each device string in PacketCapture.lookupDevices():
    2. Get it's network address and mask from a PacketCapture instance (capture does not need to be open).
    3. Search through all network interfaces returned by NetworkInterface.getNetworkInterfaces() and find one that has an address that is on the same network given by the network address and mask that jpcap returned for the device.
    4. That NetworkInterface (probably) corresponds to the device string.

    Implementation

    Prerequisites:

    • No dependencies other than jpcap. Tested with version 0.01.16.

    Issues:

    • While platform-independent, unlike the registry-based solution this can only find interfaces that are up.
    • Byte ordering is weird. I can't make much sense of the jpcap discussion forum on SourceForge but somebody did seem to point it out. Therefore I suppose it's always subject to change in the future.
    • There are probably a lot of edge cases that will cause this to return incorrect results that I have not tested for.

    Code

    Code is below. Usage is free under SO's CC attribution-sharealike license. It's self-contained so I did not put it on 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;
            }
    
        }
    
    
    }
    

    Example

    Here is an example:

    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);
    
        }
    
    }
    

    On my machine (same setup as registry solution), this outputs:

    \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)
    

    I did not make the output as pretty as the other solution. Note that the "virtual wifi miniport adapter" (the first one) has a null NetworkInterface because, since it is not up, a match could not be found (an IP address and network address was not present).