Introduction
I'm working on a commercial software related to an industrial field that happens to require specific network settings.
For reliability, we aim our software to work only with an ethernet adapter on a harsh environment but we don't really control the kind of computer our customers are using.
Nowadays, computers come with a variety of connection devices besides an ethernet card, such as bluetooth or wi-fi adapters. To make things worse, some programs like WMWare create virtual adapters for their own use. We need to automatically find a physical ethernet card and enforce a given set of parameters for our program to work properly.
The industrial field runs at a very different pace than software development and change happens slowly. That makes it necessary to support older OS like Windows XP as they are seldom updated, since most of the times the computers are not even connected to the internet.
As the code changes system settings, administrator privileges are required.
Background
The code below is a compilation of the examples shown on MSDN for GetAdaptersAddresses
, WlanOpenHandle
and WlanEnumInterfaces
plus several ideas taken from different blogs.
Using the Code
The code is self explanatory. All dependencies are explicitly included in the cpp file. Additionally, this project relies on MFC for simple string
manipulation with CString
and convenient data handling with CMap
.
For a non-MFC project, it should be simple to get rid of MFC classes by using C or C++ standard string
manipulation routines. It should be necessary to get rid of CRegKey
class and replace it with standard APIs too.
The first step is to retrieve all network adapters with IPv4 compatibility using GetAdaptersAddresses
, as shown in the MSDN.
CMapStringToString mapAdapters;
ULONG nFlags = GAA_FLAG_INCLUDE_PREFIX;
ULONG nFamily = AF_INET;
PIP_ADAPTER_ADDRESSES pAddresses = NULL;
PIP_ADAPTER_ADDRESSES pCurrAddress = NULL;
ULONG nBufLen = 16324, nTries = 0, nMaxTries = 3;
DWORD dwResult;
do
{
pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(nBufLen);
if (pAddresses == NULL)
{
_tprintf(_T("Memory allocation failed for IP_ADAPTER_ADDRESSES struct\n"));
return SNA_ERROR_MALLOC;
}
dwResult = GetAdaptersAddresses(nFamily, nFlags, NULL, pAddresses, &nBufLen);
if (dwResult == ERROR_BUFFER_OVERFLOW)
{
FREE(pAddresses);
pAddresses = NULL;
}
else
break;
nTries++;
}
while (dwResult == ERROR_BUFFER_OVERFLOW && nTries < nMaxTries);
if (dwResult != NO_ERROR)
{
_tprintf(_T("Call to GetAdaptersAddresses failed with error: %d\n"), dwResult);
FREE(pAddresses);
return SNA_ERROR_GETADAPTERSADDRESS;
}
pCurrAddress = pAddresses;
while (pCurrAddress)
{
if (pCurrAddress->IfType == IF_TYPE_ETHERNET_CSMACD)
{
USES_CONVERSION;
CString cKey = A2T(pCurrAddress->AdapterName);
mapAdapters[cKey] = pCurrAddress-&FriendlyName;
}
pCurrAddress = pCurrAddress-&Next;
}
FREE(pAddresses);
if (mapAdapters.GetCount() == 0)
{
_tprintf(_T("No ethernet adapter found\n"));
return SNA_ERROR_NOADAPTERFOUND;
}
On older OSes like Windows XP, wireless network cards are reported as IF_TYPE_ETHERNET_CSMACD
.
Gather identifiers of wireless adapters and remove them for the list generated in the step above.
OSVERSIONINFO osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
if (osvi.dwMajorVersion < 6)
{
_tprintf(_T("Compatibility mode\n"));
HANDLE hClient = NULL;
DWORD dwMaxClient = 2;
DWORD dwCurVersion = 0;
PWLAN_INTERFACE_INFO_LIST pIfList = NULL;
PWLAN_INTERFACE_INFO pIfInfo = NULL;
dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwCurVersion, &hClient);
if (dwResult == ERROR_SUCCESS)
{
dwResult = WlanEnumInterfaces(hClient, NULL, &pIfList);
if (dwResult == ERROR_SUCCESS)
{
WCHAR szWlanGuid[256] = { 0 };
int nMaxLen = 256;
for (int i = 0; i < (int) pIfList->dwNumberOfItems; i++)
{
pIfInfo = (WLAN_INTERFACE_INFO *) &pIfList->InterfaceInfo[i];
if (StringFromGUID2(pIfInfo->InterfaceGuid,
(LPOLESTR) szWlanGuid, nMaxLen) > 0)
{
CString cKey = szWlanGuid;
CString cValue;
if (mapAdapters.Lookup(cKey, cValue))
mapAdapters.RemoveKey(szWlanGuid);
}
}
}
else
_tprintf(_T("WlanEnumInterfaces failed with error: %u\n"), dwResult);
WlanCloseHandle(hClient, NULL);
}
else
_tprintf(_T("WlanOpenHandle failed with error: %u\n"), dwResult);
}
Use registry entries in order to distinguish between physical and virtual adapters.
CString sFriendlyName;
CRegKey oRegKey;
TCHAR szSubkey[1024];
DWORD dwNameLength;
CString sBaseKey = _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards");
if (ERROR_SUCCESS == oRegKey.Open(HKEY_LOCAL_MACHINE, sBaseKey, KEY_READ |
KEY_ENUMERATE_SUB_KEYS))
{
DWORD dwIndex = 0;
while (TRUE)
{
dwNameLength = 1024;
if (ERROR_NO_MORE_ITEMS == oRegKey.EnumKey
(dwIndex++, szSubkey, &dwNameLength, NULL))
break;
CRegKey oRegKeyCard;
if (ERROR_SUCCESS == oRegKeyCard.Open
(HKEY_LOCAL_MACHINE, sBaseKey + _T("\\") + szSubkey, KEY_READ))
{
TCHAR szGuid[256];
DWORD dwGuidLength = 256;
CString cGuid;
if (ERROR_SUCCESS == oRegKeyCard.QueryStringValue
(_T("ServiceName"), szGuid, &dwGuidLength))
{
CString cValue;
cGuid = szGuid;
cGuid.Trim();
if (mapAdapters.Lookup(cGuid, cValue))
sFriendlyName = cValue;
}
oRegKeyCard.Close();
if (!sFriendlyName.IsEmpty())
break;
}
}
oRegKey.Close();
}
if (sFriendlyName.IsEmpty())
{
CString sKey;
POSITION pos = mapAdapters.GetStartPosition();
if (pos != NULL)
mapAdapters.GetNextAssoc(pos, sKey, sFriendlyName);
}
if (sFriendlyName.IsEmpty())
{
_tprintf(_T("No ethernet adapter found\n"));
return SNA_ERROR_NOADAPTERFOUND;
}
We have already chosen an adapter, so the last step is pretty straightforward. Just set IP, mask and gateway using netsh
.
This last step requires administrator privileges:
CString sIp = _T("192.168.1.2");
CString sMask = _T("255.255.255.0");
CString sGateway = _T("192.168.1.1");
CString sNetshIp;
sNetshIp.Format(_T("netsh interface ip set address name=\"%s\" static %s %s %s"),
sFriendlyName, sIp, sMask, sGateway);
sNetshIp.Trim();
CString sDns = _T("8.8.8.8");
CString sNetshDns;
sNetshDns.Format(_T("netsh interface ip set dns name=\"%s\" static %s"),
sFriendlyName, sDns);
_tprintf(_T("Running: %s\n"), sNetshIp);
::ShellExecute(NULL, NULL, _T("cmd.exe"), CString(_T("/C ")) +
sNetshIp, NULL, SW_HIDE);
_tprintf(_T("Running: %s\n"), sNetshDns);
::ShellExecute(NULL, NULL, _T("cmd.exe"), CString(_T("/C ")) +
sNetshDns, NULL, SW_HIDE);
return 0;
References
- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915%28v=vs.85%29.aspx
- https://msdn.microsoft.com/en-us/library/windows/desktop/ms706716%28v=vs.85%29.aspx
- http://www.howtogeek.com/103190/change-your-ip-address-from-the-command-prompt/
History
- February 29th, 2016 - Initial version