2013年3月22日星期五

修改IP、DNS、MAC工具VC源码实现

实验室IP和MAC绑定,而且经常来回于各个实验室和宿舍,频繁的地址切换,带来了相当的烦恼。想做这样一个工具是很久以前的想法,可到现在都没有做;没有行动的想法都是空谈,抱着锻炼自己行动力的决心,完成了这个小工具。

一.工具介绍

    工具界面展示:
    
    本工具主要功能:
    (1)能够找出所有的网卡适配器,并显示适配器的IP、MAC等信息
    (2)能够修改IP、DNS、MAC等地址信息
    (3)能够通过配置文件增加/删除网卡信息配置方案,并且能够根据方案进行地址切换,配置文件如下图所示:


 

二.IP、DNS、MAC的显示功能

2.1如何获取IP、DNS、MAC信息

主要利用Iphlpapi库获取网卡信息,Iphlpapi中IP_ADAPTER_INFO包含了IP、MAC信息但并没有包含DNS:
  1. //获取IP地址信息  
  2. void CIpChangeDlg::GetIpAddrsInfo()  
  3. {  
  4.     /******************************************* 
  5.     *通过Iphlpapi库获取网卡信息和个数 
  6.     ********************************************/  
  7.     PIP_ADAPTER_INFO pIpAdapterInfo = new IP_ADAPTER_INFO();  
  8.     ULONG stSize = sizeof(IP_ADAPTER_INFO);  
  9.     int nRel = GetAdaptersInfo(pIpAdapterInfo, &stSize);    //获得其大小  
  10.   
  11.     if (ERROR_BUFFER_OVERFLOW == nRel)                      //重新申请所需要的空间  
  12.     {  
  13.         delete pIpAdapterInfo;  
  14.         pIpAdapterInfo = (PIP_ADAPTER_INFO) new BYTE[stSize];  
  15.         nRel=GetAdaptersInfo(pIpAdapterInfo, &stSize);   
  16.     }  
  17.   
  18.   
  19.     if (ERROR_SUCCESS == nRel)                              //获取信息成功  
  20.     {  
  21.         m_AdapterInfo.pIpAdapterInfo = pIpAdapterInfo;  
  22.         m_AdapterInfo.iCount = 0;  
  23.         while (pIpAdapterInfo)                          //获取网卡个数  
  24.         {  
  25.             m_AdapterInfo.iCount++;  
  26.             pIpAdapterInfo = pIpAdapterInfo->Next;  
  27.         }  
  28.     }  
  29. }  
根据IP_ADAPTER_INFO中的Index,获取相应网卡所使用的DNS信息:
  1. //DNS,pIpAdapterInfo为网卡适配器信息结构PIP_ADAPTER_INFO  
  2.     IP_PER_ADAPTER_INFO* pPerAdapt  = NULL;  
  3.     ULONG ulLen = 0;  
  4.     int err = GetPerAdapterInfo( pIpAdapterInfo->Index, pPerAdapt, &ulLen);  
  5.     if( err == ERROR_BUFFER_OVERFLOW ) {  
  6.         pPerAdapt = ( IP_PER_ADAPTER_INFO* ) HeapAlloc(GetProcessHeap(),   
  7.                                                        HEAP_ZERO_MEMORY, ulLen);  
  8.         err = GetPerAdapterInfo( pIpAdapterInfo->Index, pPerAdapt, &ulLen );  
  9.   
  10.         if( err == ERROR_SUCCESS ) {  
  11.             IP_ADDR_STRING* pNext = &( pPerAdapt->DnsServerList );  
  12.             if (pNext && strcmp(pNext->IpAddress.String, "") != 0)  
  13.             {  
  14.                 m_DNSDHCPBUTTON.SetCheck(FALSE);  
  15.                 m_PrimaryDnsCtl.EnableWindow(TRUE);  
  16.                 m_BackupDnsCtl.EnableWindow(TRUE);  
  17.                 m_PrimaryDnsCtl.SetWindowText(pNext->IpAddress.String);  
  18.                 if (pNext = pNext->Next)  
  19.                     m_BackupDnsCtl.SetWindowText(pNext->IpAddress.String);  
  20.             }                 
  21.             else  
  22.             {  
  23.                 m_DNSDHCPBUTTON.SetCheck(TRUE);  
  24.                 m_PrimaryDnsCtl.EnableWindow(FALSE);  
  25.                 m_BackupDnsCtl.EnableWindow(FALSE);  
  26.                 m_PrimaryDnsCtl.SetWindowText("");  
  27.                 m_BackupDnsCtl.SetWindowText("");  
  28.             }  
  29.         }  
  30.   
  31.         HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, pPerAdapt);  
  32.     }     

2.2如何获得网络连接名称?

对一个网络适配器的名称有三种描述:
(1)适配器对应的注册表中的一个注册表项值,称其为AdapterName;这个信息将在修改MAC的时使用;
(2)适配器描述名称,称其为AdapterDestription,比如本人主机的某一个网卡描述为:“Microsoft Virtual WiFi Miniport Adapter”;
(3)连接名称,就是在操作系统的网络连接中看到的连接名,如"本地连接"等;这个信息将在修改IP/DNS的时使用;
本工具采用netsh命令行方式进行IP/DNS的修改,命令行中进行修改的时候要指定连接名称,如"本地连接",但利用Iphlpapi库并不能获取连接名称,通过Mprapi库中的API和IP_ADAPTER_INFO中的Index 得到网卡所对应的连接名称。
  1. void CIpChangeDlg::GetConnectNames()  
  2. {  
  3.     /******************************************* 
  4.     *通过mprapi库获取连接名称 
  5.     *并通过index将网卡信息和连接名称相关联 
  6.     ********************************************/  
  7.     HANDLE   hMprConfig;                    //连接信息的句柄  
  8.     DWORD   dwRet=0;                        //返回值  
  9.     PIP_INTERFACE_INFO   plfTable = NULL;   //接口信息表  
  10.     DWORD   dwBufferSize=0;                 //接口信息表空间大小  
  11.   
  12.     m_AdapterInfo.csConnectName = new char [m_AdapterInfo.iCount] [256];  //申请空间存储连接名  
  13.   
  14.     dwRet = MprConfigServerConnect(NULL, &hMprConfig);  //获得句柄  
  15.     dwRet = GetInterfaceInfo(NULL, &dwBufferSize);      //获得接口信息表大小  
  16.   
  17.     if(dwRet == ERROR_INSUFFICIENT_BUFFER)              //获得接口信息  
  18.     {   
  19.         plfTable = (PIP_INTERFACE_INFO)HeapAlloc(GetProcessHeap(),   
  20.                                                   HEAP_ZERO_MEMORY, dwBufferSize);   
  21.         GetInterfaceInfo(plfTable, &dwBufferSize);   
  22.     }   
  23.   
  24.   
  25.     TCHAR   szFriendName[256];                   //接口名称  
  26.     DWORD   tchSize = sizeof(TCHAR) * 256;   
  27.     ZeroMemory(&szFriendName, tchSize);    
  28.   
  29.     for (UINT i = 0; i < plfTable-> NumAdapters; i++)   
  30.     {   
  31.         IP_ADAPTER_INDEX_MAP   AdaptMap;         //接口信息  
  32.         AdaptMap = plfTable->Adapter[i];   
  33.   
  34.         dwRet = MprConfigGetFriendlyName(hMprConfig, AdaptMap.Name,  
  35.             (PWCHAR)szFriendName, tchSize);      //获得连接名称unicode   
  36.         USES_CONVERSION;  
  37.         char* pName = W2A((PWCHAR)szFriendName);                           //转换为ansi  
  38.   
  39.         InsertConnectName(AdaptMap.Index, pName);                          //根据Index存储名字信息                                           
  40.     }   
  41.     HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, plfTable);  
  42. }  

三.IP、DNS修改

第二章节中以及提到,工具采用命令行方式进行修改;
修改IP命令:
  1. netsh interface ip set address "连接名称" dhcp   //dhcp方式  
  2. netsh interface ip set address "连接名称" static IP地址 子网掩码 网关   //静态IP方式  
修改DNS命令:
  1. netsh interface ip set dnsservers "连接名称" dhcp      //自动获取方式  
  2. netsh interface ip set dnsservers "连接名称" static IP地址  //设置主 DNS  
  3. netsh interface ip add dnsservers "连接名称"  IP地址      //设置从属 DNS  

四.MAC修改

(1)第一步寻找适配器MAC所在的注册表位置
在注册表"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE- BFC1-08002bE10318}"目录下遍历寻找 与 AdapterName(2.2节中所提AdatpterName)相同的注册表项NetCfgInstanceId,则相应的MAC地址存储在相同目录 项中的NetworkAddress中。

(2)修改MAC
修改NetworkAddress中的值
(3)重启网卡
重启网卡也采用命令行方式:
  1. netsh interface set interface "连接名称" DISABLE  //使网卡失效  
  2. netsh interface set interface "连接名称" ENABLE   //启动网卡  

五.细节问题处理

5.1 命令行方式修改产生的console窗口问题

采用命令行方式来修改IP,首先想到的是 system("命令");但这种方式执行指令的时候会弹出console窗口,显然用户体验降低;那如何屏蔽窗口呢? 采用ShellExcuteEx,可以创建一个新的进程去执行修改命令,并且在程序中适用WaitForSingleObject等待进程结束。
  1. //启动cmd执行 netsh命令,并等待命令结束  
  2. void CIpChangeDlg::ExcuteCommand(char* pCommandParam)  
  3. {  
  4.     //初始化shellexe信息  
  5.     SHELLEXECUTEINFO   ExeInfo;   
  6.     ZeroMemory(&ExeInfo, sizeof(SHELLEXECUTEINFO));   
  7.     ExeInfo.cbSize = sizeof(SHELLEXECUTEINFO);   
  8.     ExeInfo.lpFile = "cmd.exe";   
  9.     ExeInfo.lpParameters = pCommandParam;  
  10.     ExeInfo.fMask = SEE_MASK_NOCLOSEPROCESS;   
  11.     ExeInfo.nShow = SW_HIDE;   
  12.     ExeInfo.hwnd = NULL;  
  13.     ExeInfo.lpVerb = NULL;  
  14.     ExeInfo.lpDirectory = NULL;  
  15.     ExeInfo.hInstApp = NULL;      
  16.   
  17.     //执行命令  
  18.     ShellExecuteEx(&ExeInfo);  
  19.   
  20.     //等待进程结束  
  21.     WaitForSingleObject(ExeInfo.hProcess, INFINITE);   
  22. }  

5.2 窗口假死问题

如果直接在按钮按下响应消息中执行命令,命令执行期间窗口将假死(因为消息循环因命令执行而阻塞);通过创建新的线程去进行修改操作,并在线程执行结束后,发送一个自定义消息WM_MSG_MODIFY_COMPLETE(表示命令执行结束)。

没有评论:

发表评论