diff --git a/CHANGES.md b/CHANGES.md index c03c1be504..5c7be33657 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,6 +20,7 @@ Features * [#959](https://github.com/java-native-access/jna/pull/959): Added `GetProcessTimes` and `GetProcessIoCounters` to `com.sun.jna.platform.win32.Kernel32` - [@dbwiddis](https://github.com/dbwiddis). * [#952](https://github.com/java-native-access/jna/issues/952): Added `CreateMutex`, `OpenMutex` and `ReleaseMutex` to `com.sun.jna.platform.win32.Kernel32` - [@matthiasblaesing](https://github.com/matthiasblaesing). * [#973](https://github.com/java-native-access/jna/issues/973): Added `PdhLookupPerfNameByIndex`, `PdhLookupPerfIndexByName`, and `PdhEnumObjectItems` to `c.s.j.platform.win32.Pdh` and a `c.s.j.platform.win32.PdhUtil` class to access them - [@dbwiddis](https://github.com/dbwiddis). +* [#983](https://github.com/java-native-access/jna/issues/983): Added `GetIfEntry`, `GetIfEntry2`, and `GetNetworkParams` and supporting structures `MIB_IFROW`, `MIB_IF_ROW2`, and `FIXED_INFO` to `c.s.j.platform.win32.IPHlpAPI.java` - [@dbwiddis](https://github.com/dbwiddis). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/IPHlpAPI.java b/contrib/platform/src/com/sun/jna/platform/win32/IPHlpAPI.java new file mode 100644 index 0000000000..ef3ad94481 --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/win32/IPHlpAPI.java @@ -0,0 +1,280 @@ +/* Copyright (c) 2018 Daniel Widdis, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna.platform.win32; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.Structure.FieldOrder; +import com.sun.jna.platform.win32.Guid.GUID; +import com.sun.jna.platform.win32.WinDef.UCHAR; +import com.sun.jna.platform.win32.WinDef.UINT; +import com.sun.jna.platform.win32.WinDef.ULONG; +import com.sun.jna.platform.win32.WinDef.ULONGByReference; +import com.sun.jna.win32.W32APIOptions; + +/** + * Windows IP Helper API + * + * @see IP + * Helper Reference + */ +public interface IPHlpAPI extends Library { + IPHlpAPI INSTANCE = Native.load("IPHlpAPI", IPHlpAPI.class, W32APIOptions.DEFAULT_OPTIONS); + + int IF_MAX_STRING_SIZE = 256; + int IF_MAX_PHYS_ADDRESS_LENGTH = 32; + int MAX_INTERFACE_NAME_LEN = 256; + int MAXLEN_IFDESCR = 256; + int MAXLEN_PHYSADDR = 8; + int MAX_HOSTNAME_LEN = 128; + int MAX_DOMAIN_NAME_LEN = 128; + int MAX_SCOPE_ID_LEN = 256; + + /** + * The MIB_IFROW structure stores information about a particular interface. + * + * @see MIB_IFROW + */ + @FieldOrder({ "wszName", "dwIndex", "dwType", "dwMtu", "dwSpeed", "dwPhysAddrLen", "bPhysAddr", "dwAdminStatus", + "dwOperStatus", "dwLastChange", "dwInOctets", "dwInUcastPkts", "dwInNUcastPkts", "dwInDiscards", + "dwInErrors", "dwInUnknownProtos", "dwOutOctets", "dwOutUcastPkts", "dwOutNUcastPkts", "dwOutDiscards", + "dwOutErrors", "dwOutQLen", "dwDescrLen", "bDescr" }) + class MIB_IFROW extends Structure { + public char[] wszName = new char[MAX_INTERFACE_NAME_LEN]; + public int dwIndex; + public int dwType; + public int dwMtu; + public int dwSpeed; + public int dwPhysAddrLen; + public byte[] bPhysAddr = new byte[MAXLEN_PHYSADDR]; + public int dwAdminStatus; + public int dwOperStatus; + public int dwLastChange; + public int dwInOctets; + public int dwInUcastPkts; + public int dwInNUcastPkts; + public int dwInDiscards; + public int dwInErrors; + public int dwInUnknownProtos; + public int dwOutOctets; + public int dwOutUcastPkts; + public int dwOutNUcastPkts; + public int dwOutDiscards; + public int dwOutErrors; + public int dwOutQLen; + public int dwDescrLen; + public byte[] bDescr = new byte[MAXLEN_IFDESCR]; + } + + /** + * The MIB_IF_ROW2 structure stores information about a particular + * interface. + * + * @see MIB_IF_ROW2 + */ + @FieldOrder({ "InterfaceLuid", "InterfaceIndex", "InterfaceGuid", "Alias", "Description", "PhysicalAddressLength", + "PhysicalAddress", "PermanentPhysicalAddress", "Mtu", "Type", "TunnelType", "MediaType", + "PhysicalMediumType", "AccessType", "DirectionType", "InterfaceAndOperStatusFlags", "OperStatus", + "AdminStatus", "MediaConnectState", "NetworkGuid", "ConnectionType", "TransmitLinkSpeed", + "ReceiveLinkSpeed", "InOctets", "InUcastPkts", "InNUcastPkts", "InDiscards", "InErrors", "InUnknownProtos", + "InUcastOctets", "InMulticastOctets", "InBroadcastOctets", "OutOctets", "OutUcastPkts", "OutNUcastPkts", + "OutDiscards", "OutErrors", "OutUcastOctets", "OutMulticastOctets", "OutBroadcastOctets", "OutQLen" }) + class MIB_IF_ROW2 extends Structure { + public long InterfaceLuid; // 64-bit union + public ULONG InterfaceIndex; + public GUID InterfaceGuid; + public char[] Alias = new char[IF_MAX_STRING_SIZE + 1]; + public char[] Description = new char[IF_MAX_STRING_SIZE + 1]; + public ULONG PhysicalAddressLength; + public UCHAR[] PhysicalAddress = new UCHAR[IF_MAX_PHYS_ADDRESS_LENGTH]; + public UCHAR[] PermanentPhysicalAddress = new UCHAR[IF_MAX_PHYS_ADDRESS_LENGTH]; + public ULONG Mtu; + public ULONG Type; + // enums + public int TunnelType; + public int MediaType; + public int PhysicalMediumType; + public int AccessType; + public int DirectionType; + // 8-bit structure + public byte InterfaceAndOperStatusFlags; + // enums + public int OperStatus; + public int AdminStatus; + public int MediaConnectState; + public GUID NetworkGuid; + public int ConnectionType; + public long TransmitLinkSpeed; + public long ReceiveLinkSpeed; + public long InOctets; + public long InUcastPkts; + public long InNUcastPkts; + public long InDiscards; + public long InErrors; + public long InUnknownProtos; + public long InUcastOctets; + public long InMulticastOctets; + public long InBroadcastOctets; + public long OutOctets; + public long OutUcastPkts; + public long OutNUcastPkts; + public long OutDiscards; + public long OutErrors; + public long OutUcastOctets; + public long OutMulticastOctets; + public long OutBroadcastOctets; + public long OutQLen; + } + + /** + * The IP_ADDRESS_STRING structure stores an IPv4 address in dotted decimal + * notation. The IP_ADDRESS_STRING structure definition is also the type + * definition for the IP_MASK_STRING structure. + * + * @see IP_ADDRESS_STRING + */ + @FieldOrder({ "String" }) + class IP_ADDRESS_STRING extends Structure { + // Null terminated string + // up to 3 chars (decimal 0-255) and dot + // ending with null + public byte[] String = new byte[16]; + } + + /** + * The IP_ADDR_STRING structure represents a node in a linked-list of IPv4 + * addresses. + * + * @see IP_ADDR_STRING + */ + @FieldOrder({ "Next", "IpAddress", "IpMask", "Context" }) + class IP_ADDR_STRING extends Structure { + public ByReference Next; + public IP_ADDRESS_STRING IpAddress; + public IP_ADDRESS_STRING IpMask; + public int Context; + + public static class ByReference extends IP_ADDR_STRING implements Structure.ByReference { + } + } + + /** + * The FIXED_INFO structure contains information that is the same across all + * the interfaces on a computer. + * + * @see FIXED_INFO + */ + @FieldOrder({ "HostName", "DomainName", "CurrentDnsServer", "DnsServerList", "NodeType", "ScopeId", "EnableRouting", + "EnableProxy", "EnableDns" }) + class FIXED_INFO extends Structure { + public byte[] HostName = new byte[MAX_HOSTNAME_LEN + 4]; + public byte[] DomainName = new byte[MAX_DOMAIN_NAME_LEN + 4]; + public IP_ADDR_STRING.ByReference CurrentDnsServer; // IP_ADDR_STRING + public IP_ADDR_STRING DnsServerList; + public UINT NodeType; + public byte[] ScopeId = new byte[MAX_SCOPE_ID_LEN + 4]; + public UINT EnableRouting; + public UINT EnableProxy; + public UINT EnableDns; + + public FIXED_INFO(Pointer p) { + super(p); + read(); + } + + public FIXED_INFO() { + super(); + } + } + + /** + * The GetIfEntry function retrieves information for the specified interface + * on the local computer. + * + * The dwIndex member in the MIB_IFROW structure pointed to by the pIfRow + * parameter must be initialized to a valid network interface index + * retrieved by a previous call to the GetIfTable, GetIfTable2, or + * GetIfTable2Ex function. The GetIfEntry function will fail if the dwIndex + * member of the MIB_IFROW pointed to by the pIfRow parameter does not match + * an existing interface index on the local computer. + * + * @param pIfRow + * A pointer to a MIB_IFROW structure that, on successful return, + * receives information for an interface on the local computer. + * On input, set the dwIndex member of MIB_IFROW to the index of + * the interface for which to retrieve information. + * @return If the function succeeds, the return value is NO_ERROR. + */ + int GetIfEntry(MIB_IFROW pIfRow); + + /** + * The GetIfEntry2 function retrieves information for the specified + * interface on the local computer. + * + * On input, at least one of the following members in the MIB_IF_ROW2 + * structure passed in the Row parameter must be initialized: InterfaceLuid + * or InterfaceIndex. The fields are used in the order listed above. So if + * the InterfaceLuid is specified, then this member is used to determine the + * interface. If no value was set for the InterfaceLuid member (the value of + * this member was set to zero), then the InterfaceIndex member is next used + * to determine the interface. On output, the remaining fields of the + * MIB_IF_ROW2 structure pointed to by the Row parameter are filled in. + * + * @param pIfRow2 + * A pointer to a MIB_IF_ROW2 structure that, on successful + * return, receives information for an interface on the local + * computer. On input, the InterfaceLuid or the InterfaceIndex + * member of the MIB_IF_ROW2 must be set to the interface for + * which to retrieve information. + * @return If the function succeeds, the return value is NO_ERROR. + */ + int GetIfEntry2(MIB_IF_ROW2 pIfRow2); + + /** + * The GetNetworkParams function retrieves network parameters for the local + * computer. + * + * @param pFixedInfo + * A pointer to a buffer that contains a FIXED_INFO structure + * that receives the network parameters for the local computer, + * if the function was successful. This buffer must be allocated + * by the caller prior to calling the GetNetworkParams function. + * @param pOutBufLen + * A pointer to a ULONG variable that specifies the size of the + * FIXED_INFO structure. If this size is insufficient to hold the + * information, GetNetworkParams fills in this variable with the + * required size, and returns an error code of + * ERROR_BUFFER_OVERFLOW. + * @return If the function succeeds, the return value is ERROR_SUCCESS. + */ + int GetNetworkParams(FIXED_INFO pFixedInfo, ULONGByReference pOutBufLen); +} diff --git a/contrib/platform/test/com/sun/jna/platform/win32/IPHlpAPITest.java b/contrib/platform/test/com/sun/jna/platform/win32/IPHlpAPITest.java new file mode 100644 index 0000000000..eacf58b256 --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/win32/IPHlpAPITest.java @@ -0,0 +1,137 @@ +/* Copyright (c) 2018 Daniel Widdis, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna.platform.win32; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.math.BigInteger; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.regex.Pattern; + +import org.junit.Test; + +import com.sun.jna.Memory; +import com.sun.jna.platform.win32.IPHlpAPI.FIXED_INFO; +import com.sun.jna.platform.win32.IPHlpAPI.MIB_IFROW; +import com.sun.jna.platform.win32.IPHlpAPI.MIB_IF_ROW2; +import com.sun.jna.platform.win32.WinDef.ULONG; + +public class IPHlpAPITest { + + @Test + public void testGetIfEntry() throws SocketException { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface netint : Collections.list(interfaces)) { + if (!netint.isLoopback() && netint.getHardwareAddress() != null) { + // Create new MIB_IFROW, set index to this interface index + MIB_IFROW ifRow = new MIB_IFROW(); + ifRow.dwIndex = netint.getIndex(); + assertEquals(WinError.NO_ERROR, IPHlpAPI.INSTANCE.GetIfEntry(ifRow)); + // Bytes should exceed packets + // These originate from unsigned ints. Use standard Java + // widening conversion to long which does sign-extension, + // then drop any copies of the sign bit, to prevent the value + // being considered a negative one by Java if it is set + long bytesSent = (ifRow.dwOutOctets) & 0xffffffffL; + long packetsSent = (ifRow.dwOutUcastPkts) & 0xffffffffL; + if (packetsSent > 0) { + assertTrue(bytesSent > packetsSent); + } else { + assertEquals(0, bytesSent); + } + long bytesRecv = (ifRow.dwInOctets) & 0xffffffffL; + long packetsRecv = (ifRow.dwInUcastPkts) & 0xffffffffL; + if (packetsRecv > 0) { + assertTrue(bytesRecv > packetsRecv); + } else { + assertEquals(0, bytesRecv); + } + } + } + + } + + @Test + public void testGetIfEntry2() throws SocketException { + byte majorVersion = Kernel32.INSTANCE.GetVersion().getLow().byteValue(); + if (majorVersion >= 6) { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface netint : Collections.list(interfaces)) { + if (!netint.isLoopback() && netint.getHardwareAddress() != null) { + // Create new MIB_IFROW2, set index to this interface index + MIB_IF_ROW2 ifRow = new MIB_IF_ROW2(); + ifRow.InterfaceIndex = new ULONG(netint.getIndex()); + assertEquals(WinError.NO_ERROR, IPHlpAPI.INSTANCE.GetIfEntry2(ifRow)); + // Bytes should exceed packets + // These originate from unsigned longs. + BigInteger bytesSent = new BigInteger(Long.toHexString(ifRow.OutOctets), 16); + BigInteger packetsSent = new BigInteger(Long.toHexString(ifRow.OutUcastPkts), 16); + if (packetsSent.longValue() > 0) { + assertEquals(1, bytesSent.compareTo(packetsSent)); + } else { + assertEquals(0, bytesSent.compareTo(packetsSent)); + } + BigInteger bytesRecv = new BigInteger(Long.toHexString(ifRow.InOctets), 16); + BigInteger packetsRecv = new BigInteger(Long.toHexString(ifRow.InUcastPkts), 16); + if (packetsRecv.longValue() > 0) { + assertEquals(1, bytesRecv.compareTo(packetsRecv)); + } else { + assertEquals(0, bytesRecv.compareTo(packetsRecv)); + } + } + } + } else { + System.err.println("testGetIfEntery2 test can only be run on Vista or later."); + } + } + + @Test + public void testGetNetworkParams() { + Pattern ValidIP = Pattern.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"); + + WinDef.ULONGByReference bufferSize = new WinDef.ULONGByReference(); + assertEquals(WinError.ERROR_BUFFER_OVERFLOW, IPHlpAPI.INSTANCE.GetNetworkParams(null, bufferSize)); + FIXED_INFO buffer = new FIXED_INFO(new Memory(bufferSize.getValue().longValue())); + assertEquals(WinError.ERROR_SUCCESS, IPHlpAPI.INSTANCE.GetNetworkParams(buffer, bufferSize)); + + // Check all DNS servers are valid IPs + IPHlpAPI.IP_ADDR_STRING dns = buffer.DnsServerList; + while (dns != null) { + // Start with 16-char byte array + String addr = new String(dns.IpAddress.String); + // addr String will have length 16; trim off trailing null(s) + int nullPos = addr.indexOf(0); + if (nullPos != -1) { + addr = addr.substring(0, nullPos); + } + // addr is now a dotted-notation IP string. Test valid + assertTrue(ValidIP.matcher(addr).matches()); + dns = dns.Next; + } + } +}