首页 > 解决方案 > 获取远程桌面客户端的公共 IP 地址

问题描述

当用户启动与 RDP 的远程连接时,我正在开发一个在远程服务器(Windows Server 2016-2019、Windows 10)上运行的应用程序。我正在使用 C++ 和 Win API。

我正在尝试获取远程桌面客户端的公共 IP地址。我使用了WTSQuerySessionInformationW方法,其中 WTSInfoClass 设置为 WTSClientAddress。不幸的是,看起来这个函数返回客户端计算机的本地 IP,例如 192.168.1.10 而不是公共 IP。

该场景是客户端正在从世界任何地方访问远程桌面(因此不仅来自本地网络)。在应用程序和服务日志 -> Microsoft -> Windows -> TerminalServices-LocalSessionManager 下的窗口事件查看器中,我可以看到公共 IP 地址(源网络地址)。

我可以使用哪个函数或机制来获取此 IP 地址?

标签: c++winapirdp

解决方案


当我们阅读WTS_CLIENT_ADDRESS结构时

客户端网络地址由 RDP 客户端在连接到服务器时自行报告。这可能与 实际连接到服务器的地址不同。例如,假设客户端和服务器之间有一个 NAT。客户端可以上报自己的IP地址,但实际连接服务器的IP地址是NAT地址。对于 VPN 连接,客户端可能无法发现 IP 地址。如果无法发现,客户端可以报告它拥有的唯一 IP 地址,这可能是 ISP 分配的地址。由于该地址可能不是实际的网络地址,因此不应将其用作客户端身份验证的一种形式。

为了从服务器视图中获取实际的网络地址,我们可以使用WinStationQueryInformationW-it WinStationRemoteAddressreturn WINSTATIONREMOTEADDRESS

您可以复制粘贴此声明或所有winsta.h(由于未知原因未包含在 sdk 中)

乍一看,我们可以通过布局确定它与(和)WINSTATIONREMOTEADDRESS相同。我们可以重新解释从to的转换指针。SOCKADDRSOCKADDR_INSOCKADDR_IN6WINSTATIONREMOTEADDRESSSOCKADDR

但这是严重错误。结构有不同的对齐方式

C_ASSERT(FIELD_OFFSET(SOCKADDR_IN, sin_port) == 2);
C_ASSERT(FIELD_OFFSET(WINSTATIONREMOTEADDRESS, ipv4.sin_port) == 4);
C_ASSERT(FIELD_OFFSET(SOCKADDR_IN, sin_addr) == 4);
C_ASSERT(FIELD_OFFSET(WINSTATIONREMOTEADDRESS, ipv4.in_addr) == 8);

使用WinStationQueryInformationW需要与winsta.lib链接或从winsta.dll在运行时获取它

所以最终代码可以是下一个:

typedef enum _WINSTATIONINFOCLASS {
    // ...
    WinStationRemoteAddress = 29,
    // ...
} WINSTATIONINFOCLASS;

#define LOGONID_CURRENT     ((ULONG)-1)

typedef struct {
    unsigned short sin_family;
    union {
        struct {
            USHORT sin_port;
            ULONG in_addr;
            UCHAR sin_zero[8];
        } ipv4;
        struct {
            USHORT sin6_port;
            ULONG sin6_flowinfo;
            USHORT sin6_addr[8];
            ULONG sin6_scope_id;
        } ipv6;
    };
} WINSTATIONREMOTEADDRESS,
*PWINSTATIONREMOTEADDRESS;

EXTERN_C
DECLSPEC_IMPORT
BOOLEAN
WINAPI
WinStationQueryInformationW(
                            _In_opt_ HANDLE hServer,
                            _In_ ULONG SessionId,
                            _In_ WINSTATIONINFOCLASS WinStationInformationClass,
                            _Out_writes_bytes_(WinStationInformationLength) PVOID pWinStationInformation,
                            _In_ ULONG WinStationInformationLength,
                            _Out_ PULONG pReturnLength
                            );

ULONG GetRdpClientAddressFromServerView()
{
    ULONG dwError = NOERROR;
    ULONG cb;

    union {
        SOCKADDR sa;
        SOCKADDR_IN sa4;
        SOCKADDR_IN6 sa6;
    };

    WINSTATIONREMOTEADDRESS ra;

    if (WinStationQueryInformationW(0, LOGONID_CURRENT, WinStationRemoteAddress, &ra, sizeof(ra), &cb))
    {
        switch (sa.sa_family = ra.sin_family)
        {
        case AF_INET:
            sa4.sin_port = ra.ipv4.sin_port;
            sa4.sin_addr.S_un.S_addr = ra.ipv4.in_addr;
            RtlZeroMemory(sa4.sin_zero, sizeof(sa4.sin_zero));
            cb = sizeof(SOCKADDR_IN);
            break;
        case AF_INET6:
            sa6.sin6_port = ra.ipv6.sin6_port;
            sa6.sin6_flowinfo = ra.ipv6.sin6_flowinfo;
            memcpy(&sa6.sin6_addr, &ra.ipv6.sin6_addr, sizeof(in6_addr));
            sa6.sin6_scope_id = ra.ipv6.sin6_scope_id;
            cb = sizeof(SOCKADDR_IN6);
            break;
        default:
            dwError = ERROR_GEN_FAILURE;
        }

        if (dwError == NOERROR)
        {
            // assume that WSAStartup already called
            // WSADATA wd;
            // WSAStartup(WINSOCK_VERSION, &wd);

            char AddressString[64];
            ULONG dwAddressStringLength = _countof(AddressString);
            if (WSAAddressToStringA(&sa, cb, 0, AddressString, &dwAddressStringLength) == NOERROR)
            {
                DbgPrint("client ip is %s\n", AddressString);
            }
            else
            {
                dwError = WSAGetLastError();
            }
        }
    }
    else
    {
        dwError = GetLastError();
    }

    return dwError;
}

推荐阅读