上一篇:WindowsAPI|每天了解几个winAPI接口之网络配置相关文档Iphlpapi.h详细分析七
如果有错误欢迎指正批评,在此只作为科普和参考。
C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\um\iphlpapi.h
文章目录
- [SetTcpEntry:强制断开(reset)一条已存在的 IPv4 TCP 连接](#SetTcpEntry:强制断开(reset)一条已存在的 IPv4 TCP 连接)
-
-
- [1. 函数原型](#1. 函数原型)
- [2. MIB_TCPROW 关键字段](#2. MIB_TCPROW 关键字段)
- [3. 典型使用步骤](#3. 典型使用步骤)
- [4. 最小示例](#4. 最小示例)
- [5. 注意事项](#5. 注意事项)
-
- 什么是五元组?
- [GetInterfaceInfo: 拿到本机所有"具备 IPv4 能力的网络接口"的一张简表](#GetInterfaceInfo: 拿到本机所有“具备 IPv4 能力的网络接口”的一张简表)
- [GetUniDirectionalAdapterInfo:枚举本机中所有 仅支持"单向发送"(Unidirectional / Send-only)的 IPv4 网络接口](#GetUniDirectionalAdapterInfo:枚举本机中所有 仅支持“单向发送”(Unidirectional / Send-only)的 IPv4 网络接口)
-
-
- [1. 什么是"单向发送"接口?](#1. 什么是“单向发送”接口?)
- [2. 函数原型](#2. 函数原型)
- [3. 返回的数据结构](#3. 返回的数据结构)
- [4. 典型用法](#4. 典型用法)
- [5. 常见返回值](#5. 常见返回值)
-
- NhpAllocateAndGetInterfaceInfoFromStack:在指定的堆上分配一块内存,把"接口+名字+索引"的完整表拷出来,并返回元素个数(未公开函数)
-
-
- [1. 作用一句话](#1. 作用一句话)
- [2. 函数签名](#2. 函数签名)
- [3. 返回的数据结构](#3. 返回的数据结构)
- [4. 典型内部用法(仅供理解)](#4. 典型内部用法(仅供理解))
- [5. 风险与建议](#5. 风险与建议)
-
- [GetBestInterface:给定一个目标 IPv4 地址,告诉我本机应该把包从哪块网卡发出去](#GetBestInterface:给定一个目标 IPv4 地址,告诉我本机应该把包从哪块网卡发出去)
-
-
- [1. 函数原型](#1. 函数原型)
- [2. 工作原理](#2. 工作原理)
- [3. 最小示例](#3. 最小示例)
- [4. 常见用途](#4. 常见用途)
- [5. 与相关 API 的区别](#5. 与相关 API 的区别)
-
- 一些预处理器指令
-
-
- [1. 关键宏](#1. 关键宏)
- [2. 语句逐句拆解](#2. 语句逐句拆解)
- [3. 对开发者的影响](#3. 对开发者的影响)
-
SetTcpEntry:强制断开(reset)一条已存在的 IPv4 TCP 连接
cpp
//////////////////////////////////////////////////////////////////////////////
// //
// Used to set the state of a TCP Connection. The only state that it can be //
// set to is MIB_TCP_STATE_DELETE_TCB. The complete MIB_TCPROW structure //
// MUST BE SPECIFIED //
// //
//////////////////////////////////////////////////////////////////////////////
IPHLPAPI_DLL_LINKAGE
DWORD
WINAPI
SetTcpEntry(
_In_ PMIB_TCPROW pTcpRow
);
SetTcpEntry 用来强制断开(reset)一条已存在的 IPv4 TCP 连接 ,其唯一合法操作就是把连接状态设为 MIB_TCP_STATE_DELETE_TCB
(数值 12)。
换句话说,它等价于在命令行里对某个套接字执行 tcpkill
或 netsh interface ipv4 delete address
,但粒度精确到"一条五元组"。
1. 函数原型
c
DWORD WINAPI SetTcpEntry(
_In_ PMIB_TCPROW pTcpRow // 必须完整填写一条现有 TCP 连接的信息
);
- 返回值
NO_ERROR
(0) ------ 连接已被内核立即拆除ERROR_INVALID_PARAMETER
------ 结构填写不完整或状态不是DELETE_TCB
ERROR_NOT_FOUND
------ 找不到对应五元组的连接ERROR_ACCESS_DENIED
------ 权限不足(需管理员)
2. MIB_TCPROW 关键字段
c
typedef struct _MIB_TCPROW {
DWORD dwState; // 必须设为 MIB_TCP_STATE_DELETE_TCB (12)
DWORD dwLocalAddr; // 本地 IPv4 地址(网络字节序)
DWORD dwLocalPort; // 本地端口(网络字节序)
DWORD dwRemoteAddr; // 远端 IPv4 地址(网络字节序)
DWORD dwRemotePort; // 远端端口(网络字节序)
DWORD dwOwningPid; // 拥有该套接字的进程 PID(可填 0 表示不关心)
} MIB_TCPROW, *PMIB_TCPROW;
- 五个字段(协议固定为 TCP,不在结构里出现)共同构成 五元组 ;必须和
GetExtendedTcpTable
或GetTcpTable
返回的数据完全一致才能匹配成功。
3. 典型使用步骤
- 用
GetExtendedTcpTable
(或旧版GetTcpTable
)枚举所有 TCP 连接,找到目标连接。 - 把对应的 完整
MIB_TCPROW
拷出来 ,仅把dwState
改成MIB_TCP_STATE_DELETE_TCB
。 - 调用
SetTcpEntry(&row)
。 - 内核立即向两端发送 RST,连接进入
TIME_WAIT
→ 消失。
4. 最小示例
cpp
#include <iphlpapi.h>
#pragma comment(lib, "iphlpapi.lib")
// 假设已用 GetExtendedTcpTable 取得要断开的连接 row
MIB_TCPROW killRow = {0};
killRow.dwState = MIB_TCP_STATE_DELETE_TCB;
killRow.dwLocalAddr = inet_addr("192.168.1.50");
killRow.dwLocalPort = htons(54321);
killRow.dwRemoteAddr = inet_addr("203.0.113.7");
killRow.dwRemotePort = htons(80);
killRow.dwOwningPid = 0; // 不校验 PID
DWORD err = SetTcpEntry(&killRow);
if (err == NO_ERROR)
printf("Connection killed.\n");
else
printf("SetTcpEntry failed: %lu\n", err);
5. 注意事项
- 仅 IPv4 TCP ;IPv6 需用
SetTcpEntry2
+MIB_TCP6ROW
。 - 本地连接才能被本机拆除;不能跨主机操作。
- 对监听套接字(
dwState = LISTEN
)调用会关闭监听端口。 - 某些安全软件或防火墙驱动可能会拦截 RST,导致看似"杀死"失败。
一句话总结:
SetTcpEntry
是"管理员级"的 TCP 连接杀手------只要给出精确的五元组,就能把那条连接瞬间 reset 掉。
什么是五元组?
在网络通信里,五元组 (5-tuple)就是一条连接在传输层用来唯一标识自己的 五个字段:
- 源 IP 地址
- 源端口号
- 目的 IP 地址
- 目的端口号
- 传输层协议(TCP、UDP、ICMP、SCTP ...)
只要这五个值都相同,系统就认为它们是同一条流(flow / connection);任何一个值不同,就是另一条独立流。
举例
192.168.1.50 54321 → 203.0.113.7 80 TCP
- 源 IP:192.168.1.50
- 源端口:54321
- 目的 IP:203.0.113.7
- 目的端口:80
- 协议:TCP
这五个字段一起构成了这条 HTTP 会话的五元组。
为什么重要
- NAT/防火墙 用五元组做会话表项。
- 负载均衡 按五元组做哈希分流。
- TCP 连接跟踪(Linux conntrack、Windows TCPIP.sys)都拿它当主键。
- 刚才的
SetTcpEntry
也必须提供准确的五元组才能定位到那一条 TCP 连接。
一句话:
五元组就是"一条网络连接的身份证明"。
GetInterfaceInfo: 拿到本机所有"具备 IPv4 能力的网络接口"的一张简表
cpp
IPHLPAPI_DLL_LINKAGE
DWORD
WINAPI
GetInterfaceInfo(
_Out_writes_bytes_opt_(*dwOutBufLen) PIP_INTERFACE_INFO pIfTable,
_Inout_ PULONG dwOutBufLen
);
GetInterfaceInfo
用来拿到本机 所有"具备 IPv4 能力的网络接口"的一张简表 ,表里只包含接口编号和对应的 友好名称 (即注册表里 Name
字段,形如 "Ethernet"、"Wi-Fi")。
它不会像 GetIfTable
/GetIfEntry2
那样给出速率、MTU、统计等详细信息,而是"最小可打印列表"。
函数原型
c
DWORD WINAPI GetInterfaceInfo(
_Out_writes_bytes_opt_(*dwOutBufLen) PIP_INTERFACE_INFO pIfTable,
_Inout_ PULONG dwOutBufLen
);
-
pIfTable
指向调用者分配的缓冲区;如果传
NULL
,函数会把 所需字节数 写到*dwOutBufLen
并返回ERROR_INSUFFICIENT_BUFFER
,这是典型的"两次调用"套路。 -
dwOutBufLen
输入时表示
pIfTable
缓冲区大小;输出时返回实际需要的字节数。
返回的 IP_INTERFACE_INFO
c
typedef struct _IP_INTERFACE_INFO {
LONG NumAdapters; // 表中条目数
IP_ADAPTER_INDEX_MAP Adapter[1]; // 柔性数组
} IP_INTERFACE_INFO, *PIP_INTERFACE_INFO;
typedef struct _IP_ADAPTER_INDEX_MAP {
ULONG Index; // 接口索引(ifIndex)
WCHAR Name[MAX_ADAPTER_NAME]; // Unicode 友好名,如 L"Ethernet"
} IP_ADAPTER_INDEX_MAP;
- 只列出 IPv4 已启用的接口;禁用的、无 IPv4 的不会返回。
- 名称是本地化的,比如中文系统可能是 "以太网"。
典型用法(两步)
cpp
ULONG bufLen = 0;
GetInterfaceInfo(nullptr, &bufLen); // 第一次:拿大小
auto pInfo = (PIP_INTERFACE_INFO)new BYTE[bufLen];
DWORD ret = GetInterfaceInfo(pInfo, &bufLen); // 第二次:真正取数据
if (ret == NO_ERROR) {
for (LONG i = 0; i < pInfo->NumAdapters; ++i) {
wprintf(L"Index=%lu Name=%s\n",
pInfo->Adapter[i].Index,
pInfo->Adapter[i].Name);
}
}
delete[] (BYTE*)pInfo;
常见错误码
值 | 含义 |
---|---|
ERROR_INSUFFICIENT_BUFFER |
提供的缓冲区太小(第一次调用时正常) |
ERROR_NO_DATA |
本机没有 IPv4 接口 |
ERROR_NOT_SUPPORTED |
系统版本不支持(极旧) |
与 GetIfTable 的区别
API | 返回内容 | 接口类型 | 名称 |
---|---|---|---|
GetInterfaceInfo | 索引 + 友好名 | 仅 IPv4 启用接口 | 正确 Unicode 名称 |
GetIfTable | 索引 + 类型 + MTU + 统计 | 所有接口 | 内部描述符(可能不是用户友好名) |
一句话总结:
GetInterfaceInfo
就是"给我一个干净列表,告诉我这台机器有哪些 IPv4 网卡、它们各自的索引和友好名字"。
GetUniDirectionalAdapterInfo:枚举本机中所有 仅支持"单向发送"(Unidirectional / Send-only)的 IPv4 网络接口
cpp
IPHLPAPI_DLL_LINKAGE
DWORD
WINAPI
GetUniDirectionalAdapterInfo(
_Out_writes_bytes_opt_(*dwOutBufLen) PIP_UNIDIRECTIONAL_ADAPTER_ADDRESS pIPIfInfo,
_Inout_ PULONG dwOutBufLen
);
GetUniDirectionalAdapterInfo
用来枚举本机中所有 仅支持 "单向发送"(Unidirectional / Send-only )的 IPv4 网络接口,并给出这些接口各自绑定的 IPv4 地址。
1. 什么是"单向发送"接口?
- 只能 向外发包 ,不能接收 入站流量;
- 通常见于:
− 卫星上行链路、某些单向广播网卡;
− 早期电视回传通道、单向 GPRS 发射模块;
− 为了安全而人为做成"只写"的实验或军工网络。 - 在 Windows 里这类接口会被标记
IF_TYPE_OTHER
且驱动声明 接收能力为 0。
2. 函数原型
c
DWORD WINAPI GetUniDirectionalAdapterInfo(
_Out_writes_bytes_opt_(*dwOutBufLen)
PIP_UNIDIRECTIONAL_ADAPTER_ADDRESS pIPIfInfo,
_Inout_ PULONG dwOutBufLen
);
- pIPIfInfo
指向调用者分配的缓冲区;可传NULL
做"两次调用"套路:第一次返回所需字节数。 - dwOutBufLen
输入时给出缓冲区大小;输出时返回实际需要的字节数。
3. 返回的数据结构
c
typedef struct _IP_UNIDIRECTIONAL_ADAPTER_ADDRESS {
ULONG NumAdapters; // 单向接口数量
IPAddr Address[1]; // 柔性数组,每项是 in_addr 值(网络字节序)
} IP_UNIDIRECTIONAL_ADAPTER_ADDRESS, *PIP_UNIDIRECTIONAL_ADAPTER_ADDRESS;
- 只有 IPv4 地址;没有接口索引、没有掩码、没有名称。
- 如果本机没有这种适配器,
NumAdapters == 0
,函数返回ERROR_NO_DATA
。
4. 典型用法
cpp
ULONG bufLen = 0;
GetUniDirectionalAdapterInfo(nullptr, &bufLen); // 第一次拿大小
auto pInfo = (PIP_UNIDIRECTIONAL_ADAPTER_ADDRESS)new BYTE[bufLen];
DWORD ret = GetUniDirectionalAdapterInfo(pInfo, &bufLen);
if (ret == NO_ERROR) {
for (ULONG i = 0; i < pInfo->NumAdapters; ++i) {
struct in_addr in; in.S_un.S_addr = pInfo->Address[i];
printf("Send-only adapter: %s\n", inet_ntoa(in));
}
} else if (ret == ERROR_NO_DATA) {
printf("No unidirectional adapters.\n");
}
delete[] (BYTE*)pInfo;
5. 常见返回值
值 | 含义 |
---|---|
NO_ERROR (0) |
成功 |
ERROR_INSUFFICIENT_BUFFER |
缓冲区太小 |
ERROR_NO_DATA |
本机没有单向发送接口 |
ERROR_NOT_SUPPORTED |
极老系统未实现 |
一句话总结:
GetUniDirectionalAdapterInfo
就是"告诉我这台电脑有哪些只能发不能收的网卡,以及它们现在绑定的 IPv4 地址"。
NhpAllocateAndGetInterfaceInfoFromStack:在指定的堆上分配一块内存,把"接口+名字+索引"的完整表拷出来,并返回元素个数(未公开函数)
cpp
#if (NTDDI_VERSION >= NTDDI_WIN2KSP1)
#ifndef NHPALLOCATEANDGETINTERFACEINFOFROMSTACK_DEFINED
#define NHPALLOCATEANDGETINTERFACEINFOFROMSTACK_DEFINED
IPHLPAPI_DLL_LINKAGE
DWORD
WINAPI
NhpAllocateAndGetInterfaceInfoFromStack(
_Outptr_ IP_INTERFACE_NAME_INFO **ppTable,
_Out_ PDWORD pdwCount,
_In_ BOOL bOrder,
_In_ HANDLE hHeap,
_In_ DWORD dwFlags
);
#endif
#endif // (NTDDI_VERSION >= NTDDI_WIN2KSP1)
NhpAllocateAndGetInterfaceInfoFromStack
是一个 iphlpapi.dll 的未公开(undocumented / internal)辅助函数 ,从 Windows 2000 SP1 开始存在,仅供 TCP/IP 协议栈内部或微软自家组件调用,不保证向后兼容,也不应被第三方代码直接使用。
1. 作用一句话
在指定的堆上分配一块内存,把"接口+名字+索引"的完整表拷出来,并返回元素个数。
2. 函数签名
c
DWORD WINAPI NhpAllocateAndGetInterfaceInfoFromStack(
_Outptr_ IP_INTERFACE_NAME_INFO **ppTable, // 返回的表首地址
_Out_ PDWORD pdwCount, // 数组元素个数
_In_ BOOL bOrder, // TRUE = 按接口索引升序
_In_ HANDLE hHeap, // 要使用的堆句柄
_In_ DWORD dwFlags // 0 或 HEAP_GENERATE_EXCEPTIONS
);
3. 返回的数据结构
c
typedef struct _IP_INTERFACE_NAME_INFO {
DWORD Index; // 接口索引 (ifIndex)
DWORD MediaType; // NDIS_MEDIUM 值,如 NdisMedium802_3
UCHAR Name[MAX_ADAPTER_NAME]; // ANSI/UTF-8 接口名
ULONG NameLength; // 名字实际长度
UCHAR Pad[4]; // 对齐填充
} IP_INTERFACE_NAME_INFO, *PIP_INTERFACE_NAME_INFO;
- 数组长度 =
*pdwCount
- 内存由函数在
hHeap
上分配,调用方负责HeapFree(hHeap, 0, *ppTable)
。
4. 典型内部用法(仅供理解)
cpp
HANDLE hHeap = GetProcessHeap();
IP_INTERFACE_NAME_INFO *pTable;
DWORD cnt = 0;
DWORD err = NhpAllocateAndGetInterfaceInfoFromStack(
&pTable, &cnt, TRUE, hHeap, 0);
if (err == NO_ERROR) {
for (DWORD i = 0; i < cnt; ++i) {
printf("Idx=%lu Name=%s\n",
pTable[i].Index, pTable[i].Name);
}
HeapFree(hHeap, 0, pTable);
}
5. 风险与建议
- 未公开 :函数名以
Nhp
(Network Helper Private)开头,随时可能改名或消失。 - 替代 API :公开、稳定的版本请用
GetInterfaceInfo
(仅 IPv4)GetAdaptersAddresses
(推荐,IPv4/IPv6、GUID、友好名、网关、DNS 等一网打尽)。
一句话总结:
NhpAllocateAndGetInterfaceInfoFromStack
是 Windows 内部用的"堆上分配接口表"私有函数;它能用,但不应该用 ------写正式代码请改用公开且长期兼容的 GetAdaptersAddresses
/GetInterfaceInfo
。
GetBestInterface:给定一个目标 IPv4 地址,告诉我本机应该把包从哪块网卡发出去
cpp
IPHLPAPI_DLL_LINKAGE
DWORD
WINAPI
GetBestInterface(
_In_ IPAddr dwDestAddr,
_Out_ PDWORD pdwBestIfIndex
);
#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES) */
#pragma endregion
GetBestInterface 就是一句话:
"给定一个目标 IPv4 地址,告诉我本机应该把包从哪块网卡发出去。"
1. 函数原型
c
DWORD WINAPI GetBestInterface(
_In_ IPAddr dwDestAddr, // 目标 IPv4 地址(网络字节序)
_Out_ PDWORD pdwBestIfIndex // 返回最佳接口的索引
);
- 返回值
NO_ERROR
(0) ------ 成功ERROR_INVALID_PARAMETER
------ 地址全 0 或全 1ERROR_NO_DATA
------ 找不到可用路由(例如路由表为空或所有路由都失效)
2. 工作原理
内核按如下顺序决策:
- 查 本地 IPv4 路由表 (与
route print
看到的一致)。 - 选出"最长前缀匹配"+"最低 Metric"的那条路由。
- 该路由所绑定的接口索引就是"最佳接口"。
如果目标就是本机地址,返回 loopback 接口 (通常是 Index = 1
)。
3. 最小示例
cpp
#include <iphlpapi.h>
#pragma comment(lib, "iphlpapi.lib")
DWORD BestIdx;
DWORD ret = GetBestInterface(inet_addr("8.8.8.8"), &BestIdx);
if (ret == NO_ERROR)
printf("Best interface for 8.8.8.8 = %lu\n", BestIdx);
else
printf("GetBestInterface failed: %lu\n", ret);
4. 常见用途
- 网络诊断工具:快速判断流量会走哪块网卡。
- VPN/代理程序:在插入路由前,先探测"真实"出口。
- 负载均衡:动态选择最优链路。
5. 与相关 API 的区别
API | 额外信息 | 协议族 |
---|---|---|
GetBestInterface | 仅返回接口索引 | IPv4 |
GetBestInterfaceEx | 同时可返回 IPv6 | IPv4/IPv6 |
GetBestRoute | 返回完整 MIB_IPFORWARDROW (网关、掩码等) |
IPv4 |
一句话总结:
GetBestInterface
让程序在代码里做"路由查询"------给定目标 IP,立刻知道数据包会从哪块网卡出去。
一些预处理器指令
cpp
#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES) */
#pragma endregion
#pragma region Application Family or OneCore Family or Games Family
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES)
这些 #pragma region ... #endif
只是 Windows SDK 中的"分区"(partition) 预处理器指令 ,用来把同一份头文件切成 不同 API 可见性范围,从而:
- 让 UWP/商店应用 、传统 Win32 桌面程序 、Xbox/游戏 、驱动/系统组件 各自只看到 自己允许调用的 API。
- 防止你在 UWP 里误用了仅限 桌面 的函数(编译期就会报错)。
1. 关键宏
宏 | 含义 |
---|---|
WINAPI_PARTITION_DESKTOP |
经典 Win32 桌面/服务进程 |
WINAPI_PARTITION_APP |
UWP/商店应用 |
WINAPI_PARTITION_SYSTEM |
NT 服务、驱动、系统组件 |
WINAPI_PARTITION_GAMES |
Xbox、游戏主机或 Win32 游戏 |
2. 语句逐句拆解
c
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | \
WINAPI_PARTITION_SYSTEM | \
WINAPI_PARTITION_GAMES)
/* 这之间的声明只对桌面/系统/游戏可见,UWP 看不到 */
#endif /* WINAPI_FAMILY_PARTITION(...) */
#pragma region Application Family or OneCore Family or Games Family
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | \
WINAPI_PARTITION_SYSTEM | \
WINAPI_PARTITION_GAMES)
/* 这之间的声明 UWP + 系统 + 游戏可见,传统桌面也能看到 */
#endif
- 逻辑是"位掩码或"------只要 当前工程设置的
WINAPI_FAMILY
与任一掩码匹配,代码就保留;否则整段被预处理器剔除。 - 在 VS 工程里,
WINAPI_FAMILY
由 目标平台 自动设定:- 桌面 EXE →
WINAPI_FAMILY_DESKTOP_APP
- UWP →
WINAPI_FAMILY_PC_APP
- Xbox →
WINAPI_FAMILY_GAMES
- 桌面 EXE →
3. 对开发者的影响
- 写桌面程序:这些宏基本透明,你无需理会。
- 写 UWP :如果编译提示 "未声明标识符",大概率因为该 API 被分区保护;只能换公开给
WINAPI_PARTITION_APP
的函数。 - 查看头文件 :遇到
#pragma region
就知道"下面这段 API 仅对某类应用开放"。
一句话总结:
它们只是 微软用来"按平台裁剪 API"的开关,确保不同形态的应用程序只能调用自己被允许的函数,防止越权。