IP Helper API 简介

来源:互联网 发布:华为联创软件 编辑:程序博客网 时间:2024/05/14 12:59

IP Helper API 简介
 
 
其中最主要的两个函数是GetNumberOfInterfaces和GetInterfaceInfo,前者指出网络接口的个数,后者提取网络接口的信息。对于第一个函数要说明的一点是它好象并没有返回正确的值,因为据MSDN描述:一个网络接口是网卡的逻辑抽象,它们是一对一的关系。而实际情况是我的机器上只有一张网卡,这个函数却返回了2。实际上,因为每个系统都附加有一个调试用的网络接口,这个接口的IP地址是127.0.0.1子网掩码是255.0.0.0。这个结果可以从程序的输出看出来。由GetInterfaceInfo返回的IP_INTERFACE_INFO结构中也有一个NumAdapters整型的数据域记录了正确的网卡。然后对于GetInterfaceInfo要注意的是它也必须被调用两次,第一次获取缓冲大小,第二次才是取值。然后再次让我感到其怪的是GetInterfaceInfo返回的IP_INTERFACE_INFO不象上面的结构是用链表,而是用的动态数组的方法(到现在什么线性结构都用上了),所以遍历其中每一个元素的代码变成:


for(int i=0;iNumAdapters;i++)
{
  cout<<"Adapter index:"<Adapter[i].Index<Adapter[i].Name< IP_INTERFACE_INFO结构的解释如下:


typedef struct _IP_INTERFACE_INFO {
  LONG NumAdapters;                
// 动态数组中网络接口元素的个数,通过它来遍历数
//组
  IP_ADAPTER_INDEX_MAP Adapter[1]; 
// 网络接口数据数组
} IP_INTERFACE_INFO,*PIP_INTERFACE_INFO;
其中的IP_ADAPTER_INDEX_MAP结构如下:


typedef struct _IP_ADAPTER_INDEX_MAP {
  ULONG Index;                    
// 网卡索引
  WCHAR Name[MAX_ADAPTER_NAME];   
// 网卡名
} IP_ADAPTER_INDEX_MAP, * PIP_ADAPTER_INDEX_MAP;
提取IP信息
这部分显然和提取网络接口信息部分是相同的。

设置本地网络
设置的过程与提取过程其实是换汤不换药。我不想在这里演示每一个函数的用法,所以只使用了一个(我认为)最常用的函数AddIPAddress。这个函数能够设置本地网络的IP。但不幸的是,这个IP是临时的,当系统重新启动或者发生其它的PNP事件的时候这个IP就不存在了。那么有人会问这有什么用呢?实际上,每个网络接口卡都可以绑定多个IP,所以在网络环境恶劣的情况下(如校园网)同时预备多个IP以防断线是有必要的。必须指出的是这种临时的IP在网络通讯时可能导致的问题现在还没有测试过,诸如CODEGURU,CSDN,MSDN,或者TECHREPUBLIC对这套API也没有详细的解释(虽然它很有用)。

其它的API函数
这些函数能够让你察看或者设置网络数据报文方面的信息。比方GetIpStatistics、GetIcmpStatistics函数能够让你查看当前IP数据报和ICMP数据报的流量,以及废弃的数据报数量等等。使用这些函数你可以构建自己的网络监控程序检察网络中的故障。你也可以使用SetIpStatistics 函数来设置相应的IP协议栈属性,缩短或者延长IP数据报的缺省TTL值。然后你也可以使用GetIpForwardTable、CreateIpForwardEntry 、DeleteIpForwardEntry、SetIpForwardEntry来分别获取IP路由表的信息,创建路由表项,删除路由表项和修改路由表项。也可以用GetBestRoute、 GetBestInterface获得到达指定IP的最好的路由点和网络接口。

事实上,通过这些函数我们可以得到许多MIB变量(《使用TCP/IP协议实现网际互联》第二卷),通过这些MIB变量,我们可以非常快速的制作一个网络管理软件。

未涉及的部分
我们没有涉及IP Helper API中SendARP函数的使用。实际上,这个函数是我使用这套API的主要原因,它能够简单的发送ARP数据包并返回目标机器的MAC地址。我使用这个函数制作了另一个简单的IP查看程序,它能够查看局域网上哪些IP正在使用,并能够显示它们的MAC地址。如果把这些信息记录在文件中,我就可以统计出一天中哪些家伙的上网时间最长以及其上网的习性并能够知道哪些人更换了他们的网卡,也可以分析出这个网络的使用情况(我在学校的高峰时段统计出一个网段的254个IP中竟有236个正在使用,可见增加网段的必要了)。我将在后面的文章中说明这个程序的制作。我们也没有涉及这套API提供的当网络设置改变时向应用程序发出消息的异步通知功能。因为它们非常简单,与WINSOCK中WSAAsyncSelect的使用方法是一样的,就不再说明了。

Windows下程序修改IP的三种方法   以下讨论的平台依据是Window XP + SP1, 不考虑Windows其它版本的兼容性问题, 但对NT系列的系统, 理论上是通用的方法一: 网卡重启
    更改Windows网卡属性选项中IP地址, 通过对比前后注册表, 可以发现以下几处发生变化
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters/Interfaces/{97EFDAD8-EB2D-4F40-9B07-0FCD706FCB6D}]
"IPAddress"
"SubnetMask"
"DefaultGateway"
"NameServer"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/{97EFDAD8-EB2D-4F40-9B07-0FCD706FCB6D}/Parameters/Tcpip]
"IPAddress"
"SubnetMask"
"DefaultGateway"
[HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Services/Tcpip/Parameters/Interfaces/{97EFDAD8-EB2D-4F40-9B07-0FCD706FCB6D}]
"IPAddress"
"SubnetMask"
"DefaultGateway"
"NameServer"
[HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Services/{97EFDAD8-EB2D-4F40-9B07-0FCD706FCB6D}/Parameters/Tcpip]
"IPAddress"
"SubnetMask"
"DefaultGateway"
    其中{97EFDAD8-EB2D-4F40-9B07-0FCD706FCB6D}是网卡名称(AdapterName), 不同的网卡, 不同的接入位置, 不同的接入的时间, 对应的值都不一样, 它的值是第一次接入系统时, 由系统生成的GUID值.
    此处CurrentControlSet实际是ControlSet001的别名.    
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters/Interfaces/{97EFDAD8-EB2D-4F40-9B07-0FCD706FCB6D}]
"IPAddress"
"SubnetMask"
"DefaultGateway"
"NameServer"
    是主要的设置处.
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/{97EFDAD8-EB2D-4F40-9B07-0FCD706FCB6D}/Parameters/Tcpip]
"IPAddress"
"SubnetMask"
"DefaultGateway"
    对一些服务有影响, 如不设置, 用netstat可以看到原来的IP地址仍处于监听状态(?).
    但为了使设置生效, 还有很重要的一步, 即重启网卡.
    更改网卡的配置, 一般而言需要重启网卡, 如
    Linux系统, 只需运行
        #ifconfig eth0 down
        #ifconfig eht0 up
    就可以实现网卡的重启.
    Windows环境下的步骤与之类似: 先禁用本地连接(网卡), 再启用本地连接(网卡). 但没有相应的命令或者直接的API. 所幸的是DDK提供一套设备安装函数, 用于控制系统设备, 包括控制设备的状态改变.
/****************************************************************************************
 Purpose:    change state of the selected device
 Input    :    hDevInfo    device info set    
            pDeviceInfoData        selected device info
            NewState    one of enable/disable
 Output    :    TRUE for success, FALSE for failed
 ****************************************************************************************/
BOOL ChangeDeviceState(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDeviceInfoData, DWORD NewState)
{
    SP_PROPCHANGE_PARAMS PropChangeParams = {sizeof(SP_CLASSINSTALL_HEADER)};
    SP_DEVINSTALL_PARAMS devParams;
    if (!pDeviceInfoData) {
        return FALSE;
    }
    PropChangeParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
    PropChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
    PropChangeParams.Scope = DICS_FLAG_CONFIGSPECIFIC;
    PropChangeParams.StateChange = NewState; 
    PropChangeParams.HwProfile = 0;
    if (!SetupDiSetClassInstallParams(hDevInfo,pDeviceInfoData,
      (SP_CLASSINSTALL_HEADER *)&PropChangeParams,sizeof(PropChangeParams))
      || !SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,hDevInfo,pDeviceInfoData))    {
        return FALSE;
    }
    reutrn TRUE;
}
/* hDevInfo如何得到***********************************************************/
    m_hDevInfo = SetupDiGetClassDevs(
      (LPGUID) &GUID_DEVCLASS_NET,    /* GUID_DEVCLASS_NET表示仅列出网络设备 */
      NULL, 
      this->m_hWnd, 
      DIGCF_PRESENT);
    if (INVALID_HANDLE_VALUE == m_hDevInfo) {
        return FALSE;
    }
/* pDeviceInfoData如何得到**************************************************/
    k = 0;
    while (SetupDiEnumDeviceInfo(m_hDevInfo, k ,&DeviceInfoData))    {
        k++;
        if (CR_SUCCESS != CM_Get_DevNode_Status(&Status, &Problem, 
          DeviceInfoData.DevInst,0)) {
            continue;
        }
        if ((Status & DN_NO_SHOW_IN_DM)) {
            continue;
        }
        if (GetRegistryProperty(m_hDevInfo,
          &DeviceInfoData,
          SPDRP_FRIENDLYNAME,
          &pBuffer,
          &Length)) {
            m_Adapter[adapter_num].index = k - 1;        /* 当前网卡在设备信息集中的索引 */
            _tcscpy(m_Adapter[adapter_num].desc, pBuffer);    /* 当前网卡 */
            GetRegistryProperty(m_hDevInfo,
              &DeviceInfoData,
              SPDRP_DRIVER,
              &pBuffer,
              &Length);
            _tcscpy(m_Adapter[adapter_num].driver, pBuffer);
            adapter_num++;
        }
    }
/* GetRegistryProperty是对SetupDiGetDeviceRegistryProperty封装***************/
BOOL GetRegistryProperty(HDEVINFO  DeviceInfoSet,
                         PSP_DEVINFO_DATA  DeviceInfoData,
                         ULONG Property,
                         LPTSTR* Buffer,
                         PULONG Length)
{
    while (!SetupDiGetDeviceRegistryProperty(
        DeviceInfoSet,
        DeviceInfoData,
        Property,
        NULL,
        (PBYTE)(*Buffer),
        *Length,
        Length
        ))
    {
        if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
            if (*(LPTSTR *)Buffer) 
                LocalFree(*(LPTSTR *)Buffer);
            *Buffer = (LPTSTR)LocalAlloc(LPTR, *Length);
        }else {
            return FALSE;
        }            
    }
    return TRUE;
}
/* m_Adapter的数据结构 */
typedef struct adapter_info_s {
    char name[NAME_LEN];        /* 内部设备名, UUID的字符串形式 */
    char desc[NAME_LEN];        /* 网卡描述 */
    char driver[NAME_LEN];        /* 网卡在注册表中的位置, 如{4D36E972-E325-11CE-BFC1-08002BE10318}/0011 
    实际完整的键名为System//CurrentControlSet//Control//Class/{4D36E972-E325-11CE-BFC1-08002BE10318}/0011 
    该键包含的内容与SetupDiGetDeviceRegistryProperty得到的设备属性基本相同
    如NetCfgInstanceId即为内部设备名 DriverDesc为设备描述    */
    int index;
}adapter_info_t;
/*****************************************************************************
 用何名称区分不同的网卡
 有如下名称可供选择
    本地连接名, 这是系统使用的方法, 调用的是netman.dll中的未公开函数HrLanConnectionNameFromGuidOrPath(其原型笔者正在调试之中, 成功之后会另行撰文); 其实也可从注册表中获得HKLM/System/CurrentControlSet/Control/Network/{4D36E972-E325-11CE-BFC1-08002BE10318}/{97EFDAD8-EB2D-4F40-9B07-0FCD706FCB6D}/Connection/Name
    网卡类型描述
    设备友好名        它与网卡类型描述基本相同, 当存在同种类型的网卡, 它会加#n(n = 2, 3, 4...)以示区分
    如本程序中笔者即以设备友好名区分网卡
 *****************************************************************************/
/* 重启网卡的过程************************************************************/
    k = pAdapter->GetCurSel();        /* m_Adapter[k]即当前网卡 */
    if (SetupDiEnumDeviceInfo(m_hDevInfo, m_Adapter[k].index ,&DeviceInfoData))
    {
        hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));        
        ChangeDeviceState(m_hDevInfo, &DeviceInfoData, DICS_DISABLE);    /* 禁用网卡 */        
        ChangeDeviceState(m_hDevInfo, &DeviceInfoData, DICS_ENABLE);    /* 启用网卡 */        
        /* 重启网卡, 一般情况下, 以下命令相当于前两命令的组合. 但我仍建议使用前者 */    
        //    ChangeDeviceState(m_hDevInfo, &DeviceInfoData, DICS_PROPCHANGE);
        SetCursor(hCursor);    
    }
/* 扫尾工作 */
    SetupDiDestroyDeviceInfoList(m_hDevInfo);
    总结: 通过网卡重启更改IP的方法有两个步骤: 修改注册表, 重启网卡. 重启网卡的全过程上面已作描述.  注册表修改的内容为文中列出四个主要项, 如{97EFDAD8-EB2D-4F40-9B07-0FCD706FCB6D}的网卡名称即是内部设备名, 在adapter结构中已给出. 整个注册表修改的过程比较简单, 本文不加叙述.
方法二:未公开函数
    Windows系统中, 更改Windows网卡属性选项中IP地址, 可以即时使更改生效, 并且没有重启网卡的过程. 系统自带的netsh也能通过命令行或脚本文件的形式, 完成更改IP的功能时, 也不需要重启网卡
    同时也有很多共享软件, 可以实现同样的功能, 常见IP地址更改软件有IPFreeSet, IPChanger, IPProfile, IPHelp, IPSet, SNet等.
    笔者通过分析netsh发现一个未公开函数, 即用netcfgx.dll封装的dhcpcsvc.dll中DhcpNotifyConfigChange函数
    具体的方法参见VCKB 25期 王骏先生的 "不重起Windows直接更改IP地址", 他得到的函数原型比我准确, 思路也很清晰.
分析上述共享软件时, 发现其技术要点不外乎三种: 使用未公开函数, 调用netsh命令, 重启网卡硬件. 调用netsh命令的实质还是使用未公开函数
使用未公开函数的有: IPFreeSet, IPChanger
调用netsh命令的有 : IPHelp, IPSet. 两者都是用Delphi开发的.
重启网卡硬件: IPSwitcher
速度比较: 因为netsh本身的实现是调用netcfgx.dll, netcfgx.dll封装了对未公开函数的使用, 故效率相对较低. 在一台CPU:PIII500/RAM:256/XP的系统中, IPHelp需要6~7秒才能完成, 而IPFreeSet只需要1~2秒.
方法三:一卡多IP
    除以上两个方法外, 笔者再介绍一种方法. 无论是在Windows下还是在Linux下, 一块网卡都可同时具有多个IP地址. 根据TCP/IP原理, 在网络层标识通信节点是IP地址, 在链路层上的则是MAC地址. 只要通过ARP, 将多个IP与一个MAC对应起来, 就可实现一网卡多IP(其实是一MAC多IP). 系统本身也有相应的设置选项, 如windows是通过TCP/IP属性的高级选项添加的, Linux下可由ifconfig命令添加.
    iphlpapi提供AddIPAddress和DelIPAddress. 如果能先加入新的IP, 再去除原来的IP, 即可实现IP地址的更改.

 

使用Ip Helper API得到当前系统的Tcp所有打开端口及IP地址[转]
[ 2007-03-29 11:01:31 | 作者: Admin ]
字体大小: 大 | 中 | 小
//GetTcpTable函数单元
unit untIPHLPAPI;

interface

uses
Windows, sysutils, WinSock;

type
EIpHlpError = class(Exception);
//----------------TCP结构------------------------------------------------
PTMibTCPRow = ^TMibTCPRow;
TMibTCPRow = packed record
dwState : DWORD;//状态
dwLocalAddr : DWORD;//本地IP地址
dwLocalPort : DWORD;//本地端口号
dwRemoteAddr: DWORD;//远程IP地址
dwRemotePort: DWORD;//远程端口号
end;
//
PTMibTCPTable = ^TMibTCPTable;
TMibTCPTable = packed record
dwNumEntries : DWORD; //Tcp打开的数量
Table : array[0..0] of TMibTCPRow;
end;

//------------------从IPHLPAPI.DLL输入的API函数----------------------------
function GetTcpTable(pTcpTable: PTMibTCPTable; var pdwSize: DWORD;
bOrder: BOOL): DWORD; stdcall;

//转换成IP地址形式
function IpAddressToString(Addr: DWORD): string;
//GetTcpTable的实现过程
procedure VVGetTcpTable(var pTcpTable: PTMibTCPTable; var dwSize: DWORD;
const bOrder: BOOL);
//转换成端口号
Function GetTcpPortNumber(aDWord: DWord): Longint;

implementation

resourcestring
sNotImplemented = 'Function %s is not implemented.';
sInvalidParameter = 'Function %s. Invalid parameter';
sNoData = 'Function %s. No adapter information exists for the local computer.';
sNotSupported = 'Function %s is not supported by the operating system.';
const
iphlpapilib = 'iphlpapi.dll';

function GetTcpTable; external iphlpapilib name 'GetTcpTable';

function IpAddressToString(Addr: DWORD): string;
var
InAddr: TInAddr;
begin
InAddr.S_addr := Addr;
Result := inet_ntoa(InAddr);
end;

procedure IpHlpError(const FunctionName: string; ErrorCode: DWORD);
begin
case ErrorCode of
ERROR_INVALID_PARAMETER :
raise EIpHlpError.CreateFmt(sInvalidParameter, [FunctionName]);
ERROR_NO_DATA :
raise EIpHlpError.CreateFmt(sNoData, [FunctionName]);
ERROR_NOT_SUPPORTED :
raise EIpHlpError.CreateFmt(sNotSupported, [FunctionName]);
else ;
RaiseLastWin32Error;
end;
end;

procedure VVGetTcpTable(var pTcpTable: PTMibTCPTable; var dwSize: DWORD;
const bOrder: BOOL);
var
Res: DWORD;
begin
pTcpTable := Nil;
dwSize := 0;
if @GetTcpTable = Nil then
raise EIpHlpError.CreateFmt(sNotImplemented, ['GetTcpTable']);
Res := GetTcpTable(pTcpTable, dwSize, bOrder);
if Res = ERROR_INSUFFICIENT_BUFFER then
begin
Getmem(pTcpTable, dwSize);
FillChar(pTcpTable^, dwSize, #0);
Res := GetTcpTable(pTcpTable, dwSize, bOrder);
end;
if Res <> NO_ERROR then
IpHlpError('GetTcpTable', Res);
end;

Function GetTcpPortNumber(aDWord: DWord): Longint;
begin
Result := Trunc(aDWord / 256 + (aDWord Mod 256) * 256);
end;

end.

2003-10-7 16:08:00
发表评语&raquo;&raquo;&raquo;

2003-10-7 16:10:45 //演示例子:procedure TFmIpTest.BtGetTcpTableClick(Sender: TObject);
var
pTcpTable: PMibTcpTable;
dwSize: DWORD;
i: integer;
begin
Memo1.Lines.Add('GetTcpTable');
VVGetTcpTable(pTcpTable, dwSize, False);
if pTcpTable <> nil then
try
Memo1.Lines.Add(' NumEntries: ' + IntToStr(pTcpTable^.dwNumEntries));
Memo1.Lines.Add(' Local Address Port Remote Address Port State');
for i := 0 to pTcpTable^.dwNumEntries do
with pTcpTable^.table[i], Memo1.Lines do
begin
Add(Format(' %15s %5d %15s %5d %5d', [IpAddressToString(dwLocalAddr),
GetTcpPortNumber(dwLocalPort), IpAddressToString(dwRemoteAddr),
GetTcpPortNumber(dwRemotePort), dwState]));
end;
finally
Freemem(pTcpTable);
end;
end;


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/kl222/archive/2007/06/27/1668836.aspx

原创粉丝点击