Note: The actual IP and MAC addresses have been replaced by fictitious values for safety reasons.
Introduction
This article describes how to get the MAC or physical address of a network adapter, and how to tell if an adapter is the primary adapter on the system given its adapter (system) index. Finally, it shows how to get the (array) index of the primary adapter. It is based on an article by Joseph Dempsey: The "New ipconfig" and the IP Helper API.
Background
While "ipconfig /all" can be used to obtain detailed information about the IP configuration of a system, from the Windows command prompt, sometimes, we need this information in an application. There are many ways to do this, one of which may be to send the "ipconfig /all" command from your application and then parse the results for specific information you are looking for; however, this method gives you just the information and no real control over the network adapter.
The CNetworkAdapter
class does both, it not only provides you with information but also lets you perform useful network configuration tasks such as renewing or releasing a lease on the network adapter. The "NetCfg -- The Sequel" project in Joseph's original article provided all of the above functionality. I needed to get the MAC address of a network adapter as well as find out if it was the primary adapter on a multi-homed system (a system with more than one Network Interface Card). Therefore, I decided to modify the CNetworkAdapter
class by adding this new functionality. I also had to modify the CNetCfgDlg
class to add functions to find if an adapter with a given index was the primary adapter, and to find out the index of the primary adapter and show its information.
Using the code
The groundwork for getting the MAC address of a network adapter was already laid out in Joseph's article. I had to modify the BOOL CNetworkAdapter::SetupAdapterInfo(IP_ADAPTER_INFO* pAdaptInfo )
function by first setting the adapter address length, and then copying each byte from pAdaptInfo->Address
(the MAC address) to the adapter address structure as shown below:
BOOL CNetworkAdapter::SetupAdapterInfo(IP_ADAPTER_INFO* pAdaptInfo )
.....
m_ucAddress.nLen = pAdaptInfo->AddressLength;
for (int i = 0; i < (int) m_ucAddress.nLen; i++)
{
m_ucAddress.ucAddress[i] = pAdaptInfo->Address[i];
}
.....
Getting the MAC Address
I wrote two overloaded functions to get the MAC address: one returns the address formatted as a standard MAC address in HEX, punctuated with hyphens, e.g., "00-12-34-AB-CD-EF".
tstring CNetworkAdapter::GetAdapterAddress(void)
{
tstring sAddr = _T("");
CString sTemp = _T("");
for (unsigned int i = 0; i < m_ucAddress.nLen; i++)
{
if(i > 0)
{
sAddr += "-";
}
sTemp.Format("%02X", m_ucAddress.ucAddress[i]);
sAddr += sTemp;
}
return sAddr;
}
The other copies the raw MAC address bytes into a CByteArray
passed into the function.
void CNetworkAdapter::GetAdapterAddress(CByteArray& pAdapterAddress)
{
for (unsigned int i = 0; i < m_ucAddress.nLen; i++)
{
pAdapterAddress.Add(m_ucAddress.ucAddress[i]);
}
}
Finding out if the adapter with the given index is the primary adapter
In order to find out if the adapter with the given index is the primary adapter, I had to add a function to the dialog class CNetCfgDlg
. This code iterates over the m_pAdapters
array, comparing the given adapter index with the index for each adapter in the array. If the given adapter index is equal to the smallest index of all adapters in the array, then it is the primary adapter:
bool CNetCfgDlg::IsPrimaryAdapter(DWORD dwIndex)
{
bool bIsPrimaryAdapter = false;
if( m_pAdapters )
{
CNetworkAdapter* pAdapt = NULL;
DWORD dwMinIndex = m_pAdapters->GetAdapterIndex() ;
for (unsigned int i = 0; i < m_nCount; i++)
{
pAdapt = &m_pAdapters[ i ];
if(pAdapt->GetAdapterIndex() < dwMinIndex)
{
dwMinIndex = pAdapt->GetAdapterIndex();
}
}
if (dwIndex == dwMinIndex)
bIsPrimaryAdapter = true;
}
return bIsPrimaryAdapter;
}
If the currently displayed/selected adapter is the primary adapter, the application GUI indicates that with a "YES", if not the GUI shows a "NO" and also makes the information for the primary adapter visible.
Getting the index of the primary adapter
In order to get information for the primary adapter, I had to add another function to CNetCfgDlg
:
int CNetCfgDlg::GetPrimaryAdapterIndex(void)
{
int nPrimaryAdapterIndex = -1;
if( m_pAdapters )
{
CNetworkAdapter* pAdapt = NULL;
DWORD dwMinIndex = m_pAdapters->GetAdapterIndex() ;
nPrimaryAdapterIndex = 0;
for (unsigned int i = 0; i < m_nCount; i++)
{
pAdapt = &m_pAdapters[ i ];
if(pAdapt->GetAdapterIndex() < dwMinIndex)
{
dwMinIndex = pAdapt->GetAdapterIndex();
nPrimaryAdapterIndex = i;
}
}
}
return nPrimaryAdapterIndex;
}
Once we have the index of the primary adapter, we can find all information about it from the array m_pAdapters
.
Points of Interest
Getting the MAC address of a network adapter using the CNetworkAdapter
class was simple. Finding which adapter is the primary on a computer system required some research on the topic. I could not find an authoritative answer to this question. It made sense that the first adapter listed in the response to the ipconfig /all command would be the primary, but I had no way to confirm this. The closest thing I found as an answer was an article by CISCO.
So it appeared that my assumption above was correct. I consistently found that the first adapter listed in response to ipconfig /all had the lowest index in the array m_pAdapters
. The functions listed above to find out if the adapter with the given index was the primary and to find the index of the primary adapter are based on this assumption.
For instance, on a system with two network adapters, NIC1 and NIC2, let's say the adapter index for NIC1 is 65540 whereas the index for NIC2 is 65539, thus, based on the assumption I just listed, NIC2 is the primary adapter. When I talk about the index of the primary adapter, I do not mean the adapter's index on the computer system (65539 or 65540, in this case) but rather in the array m_pAdapters
(0 or 1 in this case, since we only have two adapters). In this example, the index for NIC2 is 1 even though it is the primary adapter. It sounds strange but that is how the adapters get added to the array m_pAdapters
, and that is why we need the functions listed above to find all this information.
If someone has a definite answer to "how to find the primary adapter on a multi-homed system?", I would love to hear about it.
That's it! Please go ahead and experiment, suggest other ways to accomplish the same. Enjoy!