Introduction
The main class presented here actually came about secondary to my desire to have a GUI replacement for the console ipconfig utility. Awhile back I was doing a lot of network related stuff and got tired of continually bringing up the command prompt which I would always close again without fail. After a few different versions the one I am actually happy with was born. Behold NetCfg-The Sequel.
What the Primary Control Class Provides
- Programmatic Renew/Release Feature
- Network Adapter Enumeration
- Network Adapter Details such as lease information, DHCP Servers, WINS Servers, subnet info, etc..
- Adapter Description Retrieval.
What the Application Provides ( A Manifestation of Above )
- Allows reviewing of multiple adapters on a given system.
- Shows how IP addresses are obtained.
- Shows adapter information such as the WINS, DHCP and DNS Servers.
- Show Lease Times ( obtained and expires )
- Allows and adapter to be released and renewed.
- Can be minimized to the system tray where a tooltip is display when hovering that contains IP data.
The Network Adapter Class
First a quick note: In my personal library this class exists as an MFC/ATL based class by which I mean it uses many classes in the new VS.NET are supported in non MFC projects but would not be supported in VS6 such as CTime and CString. I also used other constructs for the arrays such as CAtlArray rather than all of the STL based classes that are in this project. I used the STL here to minimize portability issues for those wishing to use this class in an application developed in the VS6 environment. Anyone wanting the MFC version ( for some reason ) may feel free to contact me about it.
Now onto the goodies.
class CNetworkAdapter {
public:
CNetworkAdapter();
~CNetworkAdapter();
BOOL SetupAdapterInfo( IP_ADAPTER_INFO* pAdaptInfo );
tstring GetAdapterName() const;
tstring GetAdapterDescription() const;
time_t GetLeaseObtained() const;
time_t GetLeaseExpired() const;
SIZE_T GetNumIpAddrs() const;
SIZE_T GetNumDnsAddrs() const;
tstring GetIpAddr( int nIp = 0 ) const;
tstring GetSubnetForIpAddr( int nIp = 0 ) const;
tstring GetDnsAddr( int nDns = 0 ) const;
tstring GetCurrentIpAddress() const;
BOOL IsDhcpUsed() const;
tstring GetDchpAddr() const;
BOOL IsWinsUsed() const;
tstring GetPrimaryWinsServer() const;
tstring GetSecondaryWinsServer() const;
tstring GetGatewayAddr( int nGateway = DEFAULT_GATEWAY_ADDR) const;
SIZE_T GetNumGatewayAddrs() const;
static tstring GetAdapterTypeString( UINT nType );
UINT GetAdapterType() const;
DWORD GetAdapterIndex() const;
BOOL ReleaseAddress();
BOOL RenewAddress();
protected:
:
private:
:
};
Aside from the class interface itself there is one other thing that must be mentioned in order for this class to be used effectively and that is the function used to enumerate the adapters on the computer. This function looks like:
DWORD EnumNetworkAdapters( CNetworkAdapter* lpBuffer, ULONG ulSzBuf, LPDWORD lpdwOutSzBuf );
This function works like many of the standard windows functions that enumerate items. You must pass it a buffer and the total size of that buffer in bytes along with the parameter used to return to you the actual amount of bytes needed. If your buffer is too small ERROR_INSUFFICIENT_BUFFER
is returned.
Operations
Enumeration
To do anything with this class the first thing you need to do is actually set one up. While you can feel free to do the enumeration yourself and call the setup function I have provided an enumeration function that will make your life a bit easier should you wish it. This is where much of the actual work happens.
DWORD EnumNetworkAdapters( CNetworkAdapter* pAdapters,
ULONG ulSzBuf,LPDWORD lpdwOutSzBuf ) {
IP_ADAPTER_INFO* pAdptInfo = NULL;
IP_ADAPTER_INFO* pNextAd = NULL;
ULONG ulLen = 0;
int nCnt = 0;
CWinErr erradapt;
erradapt = ::GetAdaptersInfo( pAdptInfo, &ulLen );
if( erradapt == ERROR_BUFFER_OVERFLOW ) {
pAdptInfo = ( IP_ADAPTER_INFO* )ALLOCATE_FROM_PROCESS_HEAP( ulLen );
erradapt = ::GetAdaptersInfo( pAdptInfo, &ulLen );
}
pNextAd = pAdptInfo;
while( pNextAd ) {
nCnt++;
pNextAd = pNextAd->Next;
}
*lpdwOutSzBuf = nCnt * sizeof( CNetworkAdapter );
if( ulSzBuf < *lpdwOutSzBuf ) {
DEALLOCATE_FROM_PROCESS_HEAP( pAdptInfo );
return ERROR_INSUFFICIENT_BUFFER;
}
nCnt = 0;
if( erradapt == ERROR_SUCCESS ) {
pNextAd = pAdptInfo;
while( pNextAd ) {
pAdapters[ nCnt ].SetupAdapterInfo( pNextAd );
pNextAd = pNextAd->Next;
nCnt++;
}
}
DEALLOCATE_FROM_PROCESS_HEAP( pAdptInfo );
return ERROR_SUCCESS;
}
In order to get the adapter information the first thing we do is call into the IP function GetAdaptersInfo.
Once we have that list we are going to have to make sure if we got enough space from the caller to store the new list of adapters in. If we don't have a large enough buffer we cleanup the memory we allocated and return the error back to the caller along with the total amount of space that is needed to complete the operation so that the user can resize his buffer and perform the call again. If the buffer we received was large enough than we can now iterate through the entire list of adapters setting up each adapter in sequence. To do this the member function SetupAdapterInfo
is called.
BOOL CNetworkAdapter::SetupAdapterInfo( IP_ADAPTER_INFO* pAdaptInfo ) {
BOOL bSetupPassed = FALSE;
IP_ADDR_STRING* pNext = NULL;
IP_PER_ADAPTER_INFO* pPerAdapt = NULL;
ULONG ulLen = 0;
CWinErr err;
_IPINFO iphold;
if( pAdaptInfo ) {
#ifndef _UNICODE
m_sName = pAdaptInfo->AdapterName;
m_sDesc = pAdaptInfo->Description;
#else
USES_CONVERSION;
m_sName = A2W( pAdaptInfo->AdapterName );
m_sDesc = A2W( pAdaptInfo->Description );
#endif
m_sPriWins = pAdaptInfo->PrimaryWinsServer.IpAddress.String;
m_sSecWins = pAdaptInfo->SecondaryWinsServer.IpAddress.String;
m_dwIndex = pAdaptInfo->Index;
m_nAdapterType = pAdaptInfo->Type;
m_bDhcpUsed = pAdaptInfo->DhcpEnabled;
m_bWinsUsed = pAdaptInfo->HaveWins;
m_tLeaseObtained= pAdaptInfo->LeaseObtained;
m_tLeaseExpires = pAdaptInfo->LeaseExpires;
m_sDhcpAddr = pAdaptInfo->DhcpServer.IpAddress.String;
if( pAdaptInfo->CurrentIpAddress ) {
m_sCurIpAddr.sIp = pAdaptInfo->CurrentIpAddress->IpAddress.String;
m_sCurIpAddr.sSubnet = pAdaptInfo->CurrentIpAddress->IpMask.String;
}else{
m_sCurIpAddr.sIp = _T("0.0.0.0");
m_sCurIpAddr.sSubnet = _T("0.0.0.0");
}
pNext = &( pAdaptInfo->IpAddressList );
while( pNext ) {
iphold.sIp = pNext->IpAddress.String;
iphold.sSubnet = pNext->IpMask.String;
m_IpAddresses.push_back( iphold );
pNext = pNext->Next;
}
pNext = &( pAdaptInfo->GatewayList );
while( pNext )
{ m_GatewayList.push_back(pNext->IpAddress.String);
pNext = pNext->Next;
}
err = ::GetPerAdapterInfo( m_dwIndex, pPerAdapt, &ulLen );
if( err == ERROR_BUFFER_OVERFLOW )
{
pPerAdapt = (IP_PER_ADAPTER_INFO*)
ALLOCATE_FROM_PROCESS_HEAP( ulLen );
err = ::GetPerAdapterInfo( m_dwIndex, pPerAdapt, &ulLen );
if( err == ERROR_SUCCESS )
{
pNext = &(pPerAdapt->DnsServerList);
while(pNext)
{
m_DnsAddresses.push_back( pNext->IpAddress.String );
pNext = pNext->Next;
}
bSetupPassed = TRUE; }
DEALLOCATE_FROM_PROCESS_HEAP(pPerAdapt);
}
}
return bSetupPassed;
}
While just a bit lengthy this function is very simple in nature. It simply transfers much of the data from the IP_ADAPTER_INFO
structure to our member variables so they can be used and retrieved later. This is also where we ensure that we account for the possibility of having more than one IP address for local IP address and Gateways. (multiple IPs and Gateways are supported in this class but are not used in the demo application.) The next thing we do here is collect DNS information for the adapter which is given to us in the IP_PER_ADAPTER_INFO
structure. To get this information we must call the ::GetPerAdapterInfo
API function. It doesn't make a lot of sense to me why the DNS information was not kept in the adapter structure like everything else but none the less it isn't. Finally, we just need to cleanup our allocated memory and exit the function.
Renew & Release
While there are separate functions in the interface used to release or renew an adapter this is really only one function that we use. We can do this because the operations required to renew or release an address are the exact same except for the actual API call to either renew or release. To solve this we use a single function that is passed a pointer to the proper API function for the renew/release operations.
BOOL CNetworkAdapter::DoRenewRelease(DWORD ( __stdcall *func)
( PIP_ADAPTER_INDEX_MAP AdapterInfo ) ) {
IP_INTERFACE_INFO* pInfo = NULL;
BOOL bDidIt = FALSE;
ULONG ulLen = 0;
int nNumInterfaces = 0;
int nCnt = 0;
CWinErr err;
err = ::GetInterfaceInfo( pInfo, &ulLen );
if( err == ERROR_INSUFFICIENT_BUFFER ) {
pInfo = ( IP_INTERFACE_INFO* ) ALLOCATE_FROM_PROCESS_HEAP( ulLen );
err = ::GetInterfaceInfo( pInfo, &ulLen );
if( err != NO_ERROR ) {
return FALSE;
}
}
nNumInterfaces = ulLen / sizeof( IP_INTERFACE_INFO );
for( nCnt = 0; nCnt < nNumInterfaces; nCnt++ ) {
if( pInfo[ nCnt ].Adapter[ 0 ].Index == m_dwIndex ) {
err = func( &pInfo[ nCnt ].Adapter[ 0 ] );
DEALLOCATE_FROM_PROCESS_HEAP( pInfo );
bDidIt = ( err == NO_ERROR );
if( ! bDidIt ) {
return FALSE;
}
break;
}
}
return bDidIt;
}
The first that that we do when we get here is to enumerate the system's adapters and iterate the list that we get back until we find the entry that identify the same adapter that we represent. Once we have this we can that call whatever IP function was passed to us with that data and the operation will be performed returning to us the error code. All that is left to do is deallocate our memory and return back.
Note about deallocation: Looking back I suppose it would have been safer to put the deallocate block outside of the loop so that it is guaranteed to be deleted however, the adapter is always going to be found because we our enumerating hardware. The only way the adapter itself could not be found during this loop is if the application was started, the adapter was removed and then someone tried to release the adapter. That would require hotswapable cards (since the hardware would be running) which means this is running on a high end server which was not my target audience.
Misc
There are a lot of other functions here that you can feel free to review on your own. They are very simple and really don't warrant any real attention here as they are mainly accessor function for data retrieved from the adapter and per adapter info structures.
Finishing Up
Well, I hope some people learn something useful from this code and for those who don't I at least hope you now have access to a nice little utility that will replace the command line version. The one sad note about this utility is that is doesn't support NT4 because of the iphlpapi.lib requirement. This hasn't been a real problem for me as I don't use NT4 anymore but it still would have been nice.
Demo Notes
The only real note I want to make about the demo is about machine with more that one adapter and my alleged support for it. For anyone who doesn't have more than one adapter you will not get to see how this works but trust me it is there. For those of you who have multiple NICs, there is a small spinner next to the adapter name which can be used to move through your list of adapters on the computer.
Future Improvements
The one thing on my plate right now as an improvement to this software is to take hold of the MAC address. I have made some provisions for this in the code present here but it is not done as of yet. This is mainly because this code itself is pretty old for me. I just dusted it off and presented here recently. One of those thing meant to do a long while ago but kept getting pushed off. Shame how that works. Feel free to send me suggestions/comments/concerns/requests about this application or class in general.
History
Initial Release
Credit
Davide Calabro's CXPStyleButtonST
and associated classes were used in the demo application. Thanks Davide!