
Introduction
Having had the need/desire recently to find out the relationship between an IP address and a MAC address, I found a suite of functions that had previously gone unused by me. When I set out to find information about their usage, the results were paltry at best. Perhaps I did not search correctly or to exhaustion. Nonetheless, I wanted to see how they worked.
Most of what I found kept pointing me to this thing called the Address Resolution protocol (ARP). This is implemented as part of the IP layer of the OSI model. When a packet needs to be delivered from one piece of hardware to another, how do the packets, which contain IP addresses, know the actual machine to deliver to? That's where ARP comes in. The ARP cache on the host contains IP/hardware addresses collected during previous activity. If the cache does not contain a resolution, a broadcast message (i.e. ARP query) is then sent out by the network driver. The host will wait two seconds for a response. If a response is not received, the broadcast message is sent out again with a wait time of four seconds. This continues until a response is received or the timeout is reached. In a DHCP-type network, ARP requests are very gratuitous (i.e., they are sent out quite frequently) so as to keep the cache on each host up-to-date as possible.
Enough about ARP. You can read about it in way more detail elsewhere. What I wanted to do here was to briefly share my experience with the various IP Helper functions, of which ARP is a part of. Note this article is labeled as "beginner" because there are really no surprises (there is a brief mention of threads and events but that is not relevant to the topic). If you are already versed in the ways of networking, this article is not for you. If you know how to call a function and can operate on simple pointers, you'll find nothing new here.
IP Helper functions
GetIpNetTable |
GetIpAddrTable |
GetIpForwardTable |
GetIpStatistics |
GetBestInterface |
GetBestRoute |
NotifyRouteChange |
NotifyAddrChange |
SendARP |
GetUdpTable |
GetIcmpStatistics |
GetInterfaceInfo |
GetNetworkParams |
GetPerAdapterInfo |
GetNumberOfInterfaces |
GetRTTAndHopCount |
GetTcpStatistics |
GetTcpTable |
GetUdpStatistics |
|
|
Most of the functions are straightforward. You supply a big enough buffer for the returned information, and access that information accordingly (e.g. pointer dereferencing). Very few surprises await. I'll go over one of the functions in detail, and leave the rest as an exercise for the interested reader.
GetIpNetTable()
is used to get the IP-to-MAC address translation table. The returned data is a MIB_IPNETTABLE
structure that contains ARP entries. Each entry in the table is a MIB_IPNETROW
structure. Entries in this structure contain the index of the adapter (which is not an index into an array), the adapter's address, and the type of ARP entry.
You can call the function with a stack-based buffer, but doing so with a heap-based buffer (i.e. dynamic allocation) ensures that only the amount needed is allocated. Therefore, two calls are made to the function: one for determining the size of the buffer, and one to get the information. This looks like:
PBYTE pBuffer = NULL;
ULONG ulSize = 0;
GetIpNetTable((PMIB_IPNETTABLE) pBuffer, &ulSize, TRUE);
pBuffer = new BYTE[ulSize];
if (NULL != pBuffer)
{
GetIpNetTable((PMIB_IPNETTABLE) pBuffer, &ulSize, TRUE);
...
delete [] pBuffer;
}
If you did not want to use a "generic" buffer, the following will work also:
PMIB_IPNETTABLE pIPNetTable = NULL;
ULONG ulSize = 0;
GetIpNetTable(pIPNetTable, &ulSize, TRUE);
pIPNetTable = new MIB_IPNETTABLE[ulSize];
if (NULL != pIPNetTable)
{
GetIpNetTable(pIPNetTable, &ulSize, TRUE);
...
delete [] pIPNetTable;
}
Either way is acceptable. I chose the former in the attached demo because the generic buffer is shared between several of the functions.
At this point, a pointer to the data is constructed and the data is accessed. In this example, the data is being formatted into a CString
-type variable, and the result is being added to an edit box. I used a fixed-space font so that everything would line up nicely.
pIPNetTable = (PMIB_IPNETTABLE) m_pBuffer;
for (int x = 0; x < pIPNetTable->dwNumEntries; x++)
{
pIPNetRow = &(pIPNetTable->table[x]);
m_strText.Format(" Index: %lu\r\n", pIPNetRow->dwIndex);
m_edit1.ReplaceSel(m_strText);
m_strText.Format("MAC address length: %lu\r\n", pIPNetRow->dwPhysAddrLen);
m_edit1.ReplaceSel(m_strText);
m_strText.Format(" MAC address: %02x-%02x-%02x-%02x-%02x-%02x\r\n",
pIPNetRow->bPhysAddr[0],
pIPNetRow->bPhysAddr[1],
pIPNetRow->bPhysAddr[2],
pIPNetRow->bPhysAddr[3],
pIPNetRow->bPhysAddr[4],
pIPNetRow->bPhysAddr[5]);
m_edit1.ReplaceSel(m_strText);
ia.S_un.S_addr = pIPNetRow->dwAddr;
m_strText.Format(" IP address: %s\r\n", inet_ntoa(ia));
m_edit1.ReplaceSel(m_strText);
m_strText.Format(" Type: %s\r\n",
szTypes[pIPNetRow->dwType - 1]);
m_edit1.ReplaceSel(m_strText);
m_edit1.ReplaceSel("\r\n");
}
Most of the other IP Helper functions behave in a similar fashion. A few of them don't require the extra call as they operate on a fixed-sized structure. Take GetIpStatistics()
for example. No memory to allocate. Nothing to clean up. It is simply called like:
MIB_IPSTATS IPStats;
if (GetIpStatistics(&IPStats) == NO_ERROR)
{
...
}
Another of the functions that was of interest was SendARP()
. This actually queries the ARP cache for the MAC address of the specified IP address. If a match is not found, the function returns status 31. The function's usage looks something like:
ULONG ulMACAddr[2],
ulSize = 6;
LPBYTE pBuffer;
if (SendARP(inet_addr(_T("207.219.70.31")), 0,
ulMACAddr, &ulSize) == NO_ERROR)
{
pBuffer = (LPBYTE) ulMACAddr;
m_strText.Format("%02X:%02X:%02X:%02X:%02X:%02X\r\n",
pBuffer[0],
pBuffer[1],
pBuffer[2],
pBuffer[3],
pBuffer[4],
pBuffer[5]);
m_edit1.ReplaceSel(m_strText);
}
I'm not quite sure why the third parameter needed to be an array of ULONG
variables. I received the same result when pBuffer
was declared as BYTE pBuffer[6]
, and pBuffer
was sent as the third parameter instead of ulMACAddr
. The address stored in ulMACAddr
looks like:
ulMACAddr[0] = 0x27da5100
ulMACAddr[1] = 0xcccccfe9
in memory (this is all covered in big-endian/little-endian 101). When pBuffer
accesses that one byte at a time, the result is:
pBuffer[0] = 0x00
pBuffer[1] = 0x51
pBuffer[2] = 0xda
pBuffer[3] = 0x27
pBuffer[4] = 0xe9
pBuffer[5] = 0xcf
Thus the MAC address of the machine would be 00-51-da-27-e9-cf. Testing this on my machine using the IP address shown proved a difficult task as my local ARP cache only has two entries in it: one for the gateway, and the other for the DNS server. Unless you have a machine on the other side of the Internet (e.g., router), you're apt to get the same result. You can also use an IP addresses in your local subnet, but depending on your actual environment, may not be any better.
The last function I wanted to look at is NotifyRouteChange()
. This one is used so the caller can be notified of a change to the IP routing table. The Route utility is used from a command prompt to manipulate the IP routing table. Be careful not to delete an entry accidentally! Testing of this function used some constructs not used in other part of the demo application, namely threads and events. In order to use NotifyRouteChange()
, an event must be created in a nonsignaled state that NotifyRouteChange()
can then signal once a change has been detected. This looked something like:
HANDLE h;
bool bDone = false;
m_hEvents[1] = CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL != m_hEvents[1])
{
m_OverLapped.hEvent = m_hEvents[1];
while (! bDone)
{
m_dwResult = NotifyRouteChange(&h, &m_OverLapped);
if (ERROR_IO_PENDING != m_dwResult)
{
m_strText.Format("NotifyRouteChange() failed. Result = %lu\r\n",
m_dwResult);
m_edit1.ReplaceSel(m_strText);
bDone = true;
}
else
{
m_dwResult = WaitForMultipleObjects(2, m_hEvents,
FALSE, INFINITE);
switch(m_dwResult - WAIT_OBJECT_0)
{
case 0:
bDone = true;
break;
case 1:
m_edit1.ReplaceSel("A change was made to the route table.\r\n");
break;
}
}
}
CloseHandle(m_hEvents[1]);
}
CloseHandle(m_hEvents[0]);
While not entirely necessary, I needed to create another event (m_hEvents[0]
) that could be signaled by the UI whenever a tab was changed, or the OK/Cancel button was clicked. This allowed the events to be closed, and the secondary thread to be destroyed, properly.
Notes
Several of the IP Helper functions are discussed in various levels of detail throughout other CP articles. See the References section below for those. This article was mainly targeted at how to use the functions (in a centralized location), rather than create yet another utility that uses them.
References
Getting Addresses IP informations
The 'New ipconfig' and the IP Helper API
IP Helper API - Retrieve TCP/IP/UDP Statistics , Interface details, ARP Table and Route Table
Using IP Helper API�s
Dynamic DNS Web Service
xLANInfo
Getting active TCP/UDP connections on a box