WindowsAPI|每天了解几个winAPI接口之网络配置相关文档Iphlpapi.h详细分析15

Windows API 深度解析

上一篇:WindowsAPI|每天了解几个winAPI接口之网络配置相关文档Iphlpapi.h详细分析14

本文继续对 Windows 网络接口相关的时间戳管理 API 进行解析,科普与参考为主,如有错误欢迎指正。

C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\um\iphlpapi.h


文章目录


GetIpErrorString:将 IP_STATUS 错误码转为可读字符串

cpp 复制代码
#if (NTDDI_VERSION >= NTDDI_VISTA)

IPHLPAPI_DLL_LINKAGE
DWORD
WINAPI
GetIpErrorString(
    _In_    IP_STATUS ErrorCode,
    _Out_writes_opt_(*Size + 1) PWSTR Buffer,
    _Inout_ PDWORD Size
    );

GetIpErrorString 用于将 IP Helper API 或 ICMP 相关函数返回的 IP_STATUS 错误码转换成更友好的文本描述。典型错误例如 IP_REQ_TIMED_OUTIP_BAD_ROUTE 等。使用该函数可以在日志或调试输出中得到更可读的错误解释。

参数说明

ErrorCode

需要转换的 IP 错误码,通常来自 ICMP 或 IP Helper 返回值,例如 IcmpSendEchoGetBestRoute 等。

Buffer

输出缓冲区,用于存放 Unicode 字符串。如果传入 nullptr,函数会将所需空间大小写入 Size 中。

Size

输入输出参数。

输入:Buffer 的大小(以 WCHAR 数量计)。

输出:实际写入(或所需)的大小。


返回值说明

返回 DWORD

  • NO_ERROR (0):成功将错误码转换为可读字符串。
  • ERROR_INSUFFICIENT_BUFFERBuffer 不够大,需要将 Size 调整为给定值后重新调用。
  • ERROR_INVALID_PARAMETER:传参无效或结构体未正确初始化。
  • 其他错误码 :可能表示系统不识别该 IP_STATUS

简易示例

cpp 复制代码
#include <windows.h>
#include <iphlpapi.h>
#include <iostream>
#pragma comment(lib, "iphlpapi.lib")

int main()
{
    DWORD size = 0;
    // 第一次调用:获取所需长度
    GetIpErrorString(IP_REQ_TIMED_OUT, nullptr, &size);

    std::wstring buf(size, L'\0');
    DWORD ret = GetIpErrorString(IP_REQ_TIMED_OUT, buf.data(), &size);

    if (ret == NO_ERROR) {
        std::wcout << L"Error string: " << buf.c_str() << std::endl;
    }
    return 0;
}

ResolveNeighbor:解析邻居的物理地址(ARP/ND 类接口)

cpp 复制代码
#if (NTDDI_VERSION >= NTDDI_VISTA)
#ifdef _WS2DEF_
IPHLPAPI_DLL_LINKAGE
ULONG
WINAPI
ResolveNeighbor(
    _In_    SOCKADDR *NetworkAddress,
    _Out_writes_bytes_(*PhysicalAddressLength) PVOID PhysicalAddress,
    _Inout_ PULONG PhysicalAddressLength
    );
#endif
#endif

ResolveNeighbor 是 Windows Vista 及之后提供的 API,用于直接解析某个网络地址(IPv4 或 IPv6)对应的物理地址(MAC),类似于底层的 ARP(IPv4)或 邻居发现 ND(IPv6)。

它比传统的 SendARP 更通用,可同时适用于 IPv4 和 IPv6,并使用现代的 SOCKADDR 结构。

参数说明

NetworkAddress

输入网络地址,必须是一个完整设置了 family、port、addr 的 SOCKADDR_IN(IPv4)或 SOCKADDR_IN6(IPv6)。

PhysicalAddress

输出目标的物理地址缓冲区,可以是 6 字节(MAC)、8 字节(某些虚拟接口)、或更长。

必须由调用者提供,系统只写入数据。

PhysicalAddressLength

输入:指定 PhysicalAddress 的最大容量。

输出:成功时为实际使用的字节数。若空间不够,返回 ERROR_INSUFFICIENT_BUFFER,并告知所需大小。


返回值说明

返回 ULONG

  • NO_ERROR (0):成功解析到邻居 MAC。
  • ERROR_NOT_FOUND:邻居不存在(ARP 未命中,或系统没有建立 ND 条目)。
  • ERROR_INSUFFICIENT_BUFFER:缓冲区容量不足。
  • ERROR_INVALID_PARAMETER:输入的地址结构不正确。
  • ERROR_GEN_FAILURE / ERROR_BAD_NET_NAME:网络不可达或接口不匹配。

该 API 不会强制发送 ARP 请求,它通常依赖系统已有的邻居缓存。如果缓存中不存在,结果可能是没找到。


简易示例:解析 IPv4 地址的 MAC

cpp 复制代码
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <iostream>

#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")

int main()
{
    sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr);

    BYTE mac[32] = {0};
    ULONG macLen = sizeof(mac);

    ULONG ret = ResolveNeighbor((SOCKADDR*)&addr, mac, &macLen);
    if (ret == NO_ERROR) {
        std::cout << "MAC: ";
        for (ULONG i = 0; i < macLen; ++i)
            std::cout << std::hex << (int)mac[i] << " ";
        std::cout << std::endl;
    } else {
        std::cout << "ResolveNeighbor failed. Code = " << ret << "\n";
    }
    return 0;
}

CreatePersistentTcpPortReservation:在系统内部"锁定"一段 TCP 端口

cpp 复制代码
IPHLPAPI_DLL_LINKAGE
ULONG
WINAPI
CreatePersistentTcpPortReservation(
    _In_  USHORT StartPort,
    _In_  USHORT NumberOfPorts,
    _Out_ PULONG64 Token
    );

这是 Windows TCP Stack 提供的持久端口预留 API。用于在系统内部"锁定"一段 TCP 端口,使得:

  • 其他进程不能随意 Bind 这些端口
  • 系统不会把它们当作动态端口分配
  • 预留信息写入注册表,在重启后仍然生效

适用于需要保证端口长期可用的服务(VPN、专网组件、内核态驱动等)。


参数解释

  • StartPort

    你要预留的起始 TCP 端口(必须是主机字节序)。

  • NumberOfPorts

    预留多少连续端口。例如 StartPort=5000, NumberOfPorts=10 → 5000--5009。

  • Token

    输出的"预留块 Token",实际上是注册表内部的一种标识。
    删除预留 要用 Token 调用 DeletePersistentTcpPortReservation()


常见返回值(失败原因说明)

返回值是 ULONG,等同于 Win32 Error Code。

常见错误列表:

返回值 含义 说明
NO_ERROR (0) 成功
ERROR_ACCESS_DENIED (5) 权限不足 需要管理员权限
ERROR_INVALID_PARAMETER (87) 参数错误 端口范围错误或 NumberOfPorts 为 0
ERROR_ALREADY_EXISTS (183) 端口已被预留 系统里已存在同样范围
ERROR_INVALID_DATA (13) 数据无效 Token == NULL
ERROR_NOT_ENOUGH_MEMORY (8) 资源不足 非常少见
ERROR_SHARING_VIOLATION (32) 端口已占用 其他服务已 bind or 保留

小型可编译示例(C / Win32)

你可以保存成 reserve.c 并用 cl reserve.c /link iphlpapi.lib 编译。

c 复制代码
#include <windows.h>
#include <stdio.h>
#include <iphlpapi.h>
#pragma comment(lib, "iphlpapi.lib")

int main() {
    USHORT startPort = 5000;
    USHORT count = 5;
    ULONG64 token = 0;

    ULONG ret = CreatePersistentTcpPortReservation(startPort, count, &token);
    if (ret == NO_ERROR) {
        printf("Port reservation created.\n");
        printf("Start: %u Count: %u Token: %llu\n",
               startPort, count, token);
    } else {
        printf("CreatePersistentTcpPortReservation failed: %lu\n", ret);
        return 1;
    }

    // 想删除预留,使用 DeletePersistentTcpPortReservation
    ULONG ret2 = DeletePersistentTcpPortReservation(startPort, count, token);
    if (ret2 == NO_ERROR) {
        printf("Reservation removed.\n");
    } else {
        printf("DeletePersistentTcpPortReservation failed: %lu\n", ret2);
    }

    return 0;
}

编译后必须在管理员权限 下运行,否则会收到 ERROR_ACCESS_DENIED


使用注意点

这个 API 本质是操作注册表:

复制代码
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\ReservedPorts

因此:

  • 必须管理员权限
  • 端口范围不允许交叉
  • 预留后,其他程序 Bind 那段端口会失败(WSAEACCES)

CreatePersistentUdpPortReservation:UDP 持久端口预留

cpp 复制代码
IPHLPAPI_DLL_LINKAGE
ULONG
WINAPI
CreatePersistentUdpPortReservation(
    _In_  USHORT StartPort,
    _In_  USHORT NumberOfPorts,
    _Out_ PULONG64 Token
    );

CreatePersistentUdpPortReservation 用于在 Windows 系统中创建 UDP 端口的持久预留(Persistent Port Reservation)

被预留的端口段会从系统的动态端口池中剔除,并且在系统重启后依然有效,从而保证某些关键 UDP 服务始终拥有稳定、可用的端口资源。

该接口常用于对端口稳定性要求较高的场景,如通信中间件、VPN、专用协议栈、工业控制或长期运行的后台服务。


参数说明

StartPort

要预留的起始 UDP 端口号(主机字节序)。端口必须位于合法范围内(1--65535)。

NumberOfPorts

连续预留的端口数量。

例如 StartPort = 6000NumberOfPorts = 10,则预留 6000--6009

Token

输出参数,用于接收系统生成的预留标识(Token)。

该 Token 在释放端口预留时必须使用,对应的释放接口为:

c 复制代码
DeletePersistentUdpPortReservation()

返回值与常见错误

返回值类型为 ULONG,与 Win32 错误码保持一致。

常见返回值如下:

返回值 含义 说明
NO_ERROR (0) 成功 UDP 端口预留创建成功
ERROR_ACCESS_DENIED (5) 权限不足 必须以管理员权限运行
ERROR_INVALID_PARAMETER (87) 参数非法 端口范围错误或数量为 0
ERROR_ALREADY_EXISTS (183) 已存在 端口段已被预留
ERROR_SHARING_VIOLATION (32) 冲突 端口正在被其他服务占用
ERROR_NOT_ENOUGH_MEMORY (8) 资源不足 极少发生

如果返回非 0,说明系统拒绝了该预留请求。


简易示例代码(UDP)

c 复制代码
#include <windows.h>
#include <stdio.h>
#include <iphlpapi.h>
#pragma comment(lib, "iphlpapi.lib")

int main()
{
    USHORT startPort = 7000;
    USHORT count = 4;
    ULONG64 token = 0;

    ULONG ret = CreatePersistentUdpPortReservation(
        startPort,
        count,
        &token
    );

    if (ret != NO_ERROR) {
        printf("CreatePersistentUdpPortReservation failed: %lu\n", ret);
        return 1;
    }

    printf("UDP port reservation created.\n");
    printf("StartPort=%u Count=%u Token=%llu\n",
           startPort, count, token);

    // 用完后可调用:
    // DeletePersistentUdpPortReservation(startPort, count, token);

    return 0;
}

程序必须在 管理员权限 下执行,否则会返回 ERROR_ACCESS_DENIED


行为特性与注意事项

  • UDP 端口预留与 TCP 完全独立,互不影响。

  • 预留后的端口:

    • 不会被系统动态分配
    • 普通应用 Bind 时会失败
  • 预留信息持久存储于系统配置中,重启不会丢失

  • 建议在程序卸载或服务停止时显式调用删除接口,避免"僵尸预留"。


DeletePersistentTcpPortReservation:删除 TCP 持久端口预留

cpp 复制代码
IPHLPAPI_DLL_LINKAGE
ULONG
WINAPI
DeletePersistentTcpPortReservation(
    _In_ USHORT StartPort,
    _In_ USHORT NumberOfPorts
    );

DeletePersistentTcpPortReservation 用于删除已创建的 TCP 持久端口预留

一旦成功调用,对应端口段会从系统的"预留端口列表"中移除,随后这些端口将重新回到系统可分配状态,可被其他进程正常使用。


参数说明

StartPort

需要删除预留的起始 TCP 端口号,必须与创建预留时的起始端口完全一致。

NumberOfPorts

需要删除的连续端口数量,必须与创建时的端口数量一致。

端口范围不支持"部分删除",只能整段释放。


返回值说明

返回值类型为 ULONG,语义与 Win32 错误码一致。

常见返回值如下:

返回值 含义 说明
NO_ERROR (0) 成功 端口预留已删除
ERROR_ACCESS_DENIED (5) 权限不足 必须以管理员权限运行
ERROR_NOT_FOUND (1168) 未找到 指定端口段不存在预留
ERROR_INVALID_PARAMETER (87) 参数非法 端口号或数量错误
ERROR_SHARING_VIOLATION (32) 正在使用 某些服务仍在使用该端口

如果返回 ERROR_NOT_FOUND,通常意味着该端口段从未被预留,或已经被删除。


简易示例代码

c 复制代码
#include <windows.h>
#include <stdio.h>
#include <iphlpapi.h>
#pragma comment(lib, "iphlpapi.lib")

int main()
{
    USHORT startPort = 5000;
    USHORT count = 5;

    ULONG ret = DeletePersistentTcpPortReservation(startPort, count);
    if (ret != NO_ERROR) {
        printf("DeletePersistentTcpPortReservation failed: %lu\n", ret);
        return 1;
    }

    printf("TCP port reservation removed.\n");
    return 0;
}

示例中假定 5000--5004 端口之前已经被成功预留。


使用注意事项

  • 删除操作同样需要 管理员权限

  • 端口段必须与创建时完全一致,否则系统会认为"找不到预留"。

  • 删除后,端口立即恢复为系统可用状态,但:

    • 已经 Bind 的 Socket 不会被强制关闭
    • 新建 Socket 可以重新使用这些端口

实战建议

在实际工程中,推荐遵循以下原则:

  • 创建与删除成对出现:服务启动 → 创建预留,服务卸载或停止 → 删除预留。
  • 避免在调试阶段频繁创建但忘记删除,否则会造成系统长期端口占用。
  • 对于运维或调试,可配合 netsh int ipv4 show excludedportrange protocol=tcp 查看系统当前的端口预留状态。

DeletePersistentUdpPortReservation:删除 UDP 持久端口预留

cpp 复制代码
IPHLPAPI_DLL_LINKAGE
ULONG
WINAPI
DeletePersistentUdpPortReservation(
    _In_ USHORT StartPort,
    _In_ USHORT NumberOfPorts
    );

DeletePersistentUdpPortReservation 用于删除已创建的 UDP 持久端口预留

当该函数成功执行后,对应的 UDP 端口范围会从系统的预留列表中移除,这些端口将重新回到可被系统或应用程序使用的状态。


参数说明

StartPort

要删除预留的起始 UDP 端口号,必须与创建预留时使用的起始端口完全一致。

NumberOfPorts

要删除的连续端口数量,也必须与创建时的数量一致。

该接口不支持部分删除,只能整段释放。


返回值说明

返回值类型为 ULONG,采用标准 Win32 错误码语义。

常见返回值如下:

返回值 含义 说明
NO_ERROR (0) 成功 UDP 端口预留已删除
ERROR_ACCESS_DENIED (5) 权限不足 需要管理员权限
ERROR_NOT_FOUND (1168) 未找到 指定端口段未被预留
ERROR_INVALID_PARAMETER (87) 参数非法 端口范围或数量错误
ERROR_SHARING_VIOLATION (32) 正在使用 某些组件仍占用该端口

当返回 ERROR_NOT_FOUND 时,通常表示该端口段不存在于系统的 UDP 预留列表中,或者已经被删除。


简易示例代码

c 复制代码
#include <windows.h>
#include <stdio.h>
#include <iphlpapi.h>
#pragma comment(lib, "iphlpapi.lib")

int main()
{
    USHORT startPort = 7000;
    USHORT count = 4;

    ULONG ret = DeletePersistentUdpPortReservation(startPort, count);
    if (ret != NO_ERROR) {
        printf("DeletePersistentUdpPortReservation failed: %lu\n", ret);
        return 1;
    }

    printf("UDP port reservation removed.\n");
    return 0;
}

示例假设 7000--7003 端口此前已经通过
CreatePersistentUdpPortReservation 成功创建预留。


使用注意事项

  • 删除操作同样需要 管理员权限

  • 必须确保删除的端口范围与创建时完全一致,否则系统无法匹配对应的预留记录。

  • 删除后:

    • 已经存在的 Socket 不会被系统强制关闭
    • 新建 Socket 可立即使用这些端口

工程实践建议

在工程实践中,UDP 端口预留通常用于长期服务或系统组件,建议:

  • 将端口预留与服务生命周期绑定
  • 在服务卸载或异常退出时显式清理预留
  • 调试或运维阶段,可通过
    netsh int ipv4 show excludedportrange protocol=udp
    查看当前系统的 UDP 端口预留情况

LookupPersistentTcpPortReservation:查询 TCP 持久端口预留

cpp 复制代码
IPHLPAPI_DLL_LINKAGE
ULONG
WINAPI
LookupPersistentTcpPortReservation(
    _In_  USHORT StartPort,
    _In_  USHORT NumberOfPorts,
    _Out_ PULONG64 Token
    );

LookupPersistentTcpPortReservation 用于查询指定 TCP 端口范围是否已经存在持久端口预留 ,并在存在时返回对应的预留 Token。

它不会创建或修改系统状态,仅用于"查证事实",非常适合在服务启动或安装阶段做前置校验。


参数说明

StartPort

要查询的起始 TCP 端口号,必须与预留创建时的起始端口一致。

NumberOfPorts

要查询的连续端口数量,也必须与创建预留时的数量一致。

查询粒度是"整段",不支持部分匹配。

Token

输出参数。

如果指定端口段存在预留,系统会返回对应的 Token;如果不存在,则该值不保证有效。


返回值说明

返回值类型为 ULONG,遵循 Win32 错误码语义。

常见返回值如下:

返回值 含义 说明
NO_ERROR (0) 存在预留 Token 返回成功
ERROR_NOT_FOUND (1168) 不存在 该端口段未被预留
ERROR_INVALID_PARAMETER (87) 参数非法 端口范围或指针错误
ERROR_ACCESS_DENIED (5) 权限不足 通常仍需管理员权限

当返回 ERROR_NOT_FOUND 时,表示该端口范围当前没有 TCP 持久端口预留。


简易示例代码

c 复制代码
#include <windows.h>
#include <stdio.h>
#include <iphlpapi.h>
#pragma comment(lib, "iphlpapi.lib")

int main()
{
    USHORT startPort = 5000;
    USHORT count = 5;
    ULONG64 token = 0;

    ULONG ret = LookupPersistentTcpPortReservation(
        startPort,
        count,
        &token
    );

    if (ret == NO_ERROR) {
        printf("TCP port reservation exists.\n");
        printf("Start=%u Count=%u Token=%llu\n",
               startPort, count, token);
    } else if (ret == ERROR_NOT_FOUND) {
        printf("TCP port reservation not found.\n");
    } else {
        printf("LookupPersistentTcpPortReservation failed: %lu\n", ret);
    }

    return 0;
}

典型使用场景

  • 服务启动前检查:避免重复创建端口预留。
  • 升级或重装程序:判断历史预留是否仍然存在。
  • 运维工具:配合 Delete 接口,实现"只删存在的预留"。

通常的安全流程是:

Lookup → 若不存在则 Create → 若存在则复用或校验


注意事项

  • 查询必须使用与创建时完全一致的端口范围,否则系统会认为"不存在"。
  • 返回的 Token 可用于逻辑校验或日志记录,但 删除预留不需要 Token(TCP 删除接口仅依赖端口范围)。
  • 查询操作不会影响系统端口分配行为。

LookupPersistentUdpPortReservation:查询一段 UDP 端口区间是否已经被系统做了持久端口保留

cpp 复制代码
IPHLPAPI_DLL_LINKAGE
ULONG
WINAPI
LookupPersistentUdpPortReservation(
    _In_  USHORT StartPort,
    _In_  USHORT NumberOfPorts,
    _Out_ PULONG64 Token
    );

LookupPersistentUdpPortReservation 用来查询一段 UDP 端口区间是否已经被系统做了"持久端口保留" ,如果存在,就返回对应的 Reservation Token

不会创建、不修改任何东西,只是查。

参数逐一解释(精确语义)

StartPort

  • 起始 UDP 端口号
  • 合法范围:1 ~ 65535
  • 表示你要查询的第一个端口

NumberOfPorts

  • 连续端口数量

  • 查询区间是:

    [StartPort, StartPort + NumberOfPorts - 1]

  • 不能为 0

  • StartPort + NumberOfPorts - 1 不能超过 65535


Token(输出)

  • 如果查询成功:

    • 返回该端口保留区间的 Reservation Token
  • 如果失败:

    • 内容未定义,不要使用

这个 Token 的用途是:

  • 作为"标识"证明这个端口区间属于同一个 reservation
  • 不是权限凭证
  • 删除端口保留 不需要 Token

返回值(最关键的部分)

返回值类型是 ULONG,遵循 Win32 Error Code 语义

✅ 成功

c 复制代码
NO_ERROR (0)

含义:

  • 这段 UDP 端口区间 已经存在持久保留
  • Token 有效

❌ 常见失败返回值(实际工程会遇到的)

ERROR_NOT_FOUND (1168)

最常见。

含义:

  • 这段端口区间 没有任何持久 UDP 端口保留
  • 不是错误状态,只是"查无此项"

工程含义:

👉 可以安全地 CreatePersistentUdpPortReservation


ERROR_ACCESS_DENIED (5)

含义:

  • 当前进程 没有管理员权限

这个 API 必须以管理员身份运行


ERROR_INVALID_PARAMETER (87)

常见触发原因:

  • StartPort == 0
  • NumberOfPorts == 0
  • 端口范围越界
  • Token == NULL

最小可用示例(只做查询)

cpp 复制代码
#include <windows.h>
#include <iphlpapi.h>
#include <stdio.h>

#pragma comment(lib, "iphlpapi.lib")

void LookupUdpReservation()
{
    USHORT startPort = 50000;
    USHORT portCount = 10;
    ULONG64 token = 0;

    ULONG ret = LookupPersistentUdpPortReservation(
        startPort,
        portCount,
        &token
    );

    if (ret == NO_ERROR)
    {
        printf("UDP port reserved\n");
        printf("Token = %llu\n", token);
    }
    else if (ret == ERROR_NOT_FOUND)
    {
        printf("UDP port NOT reserved\n");
    }
    else
    {
        printf("Lookup failed, error = %lu\n", ret);
    }
}

工程层面的"正确理解方式"

  • 查的是"系统级声明"

    • 与当前是否有 socket bind 无关
  • 查不到 ≠ 端口一定可用

    • 只是"没有被系统保留"
  • 查到 ≠ 端口正在被占用

    • 只是"被预留给特定服务"

一句话总结:

LookupPersistentUdpPortReservation
回答的问题是:
"系统有没有声明过:这段 UDP 端口以后要留给某个东西用?"

相关推荐
非凡ghost7 小时前
FlexiPDF(专业PDF编辑软件)
windows·学习·pdf·软件需求
习惯就好zz7 小时前
Godot GDExtension 4.5 windows编译记录
windows·godot·cpp·gdextension
野生风长7 小时前
从零开始的c语言:初步理解指针—从底层到入门(上)指针概念及语法,指针运算, 传地址和传值
c语言·开发语言·windows·visual studio
陈小于20 小时前
windows(x86-x64)下编译JCEF
windows
网络研究院21 小时前
Firefox 146 为 Windows 用户引入了加密本地备份功能
前端·windows·firefox
FL162386312921 小时前
打开事件查看器提示MMC无法创建管理单元的解决思路
windows
꧁坚持很酷꧂1 天前
Windows安装Qt Creator5.15.2(图文详解)
开发语言·windows·qt
Heart_to_Yang1 天前
Telnet 调试屏幕输出信息卡死问题解决
网络·windows·经验分享
杼蛘1 天前
XXL-Job工具使用操作记录
linux·windows·python·jdk·kettle·xxl-job