获取远程桌面客户端的公共IP地址

问题描述

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

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

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

我可以使用哪些功能或机制来获取该IP地址?

解决方法

当我们读到有关WTS_CLIENT_ADDRESS结构的信息时

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

要从服务器视图中获取实际的网络地址,我们可以将WinStationQueryInformationWWinStationRemoteAddress一起使用-返回WINSTATIONREMOTEADDRESS

您可以复制粘贴此声明或全部winsta.h(由于未知原因未粘贴到sdk中)

首先,我们可以确定WINSTATIONREMOTEADDRESSSOCKADDRSOCKADDR_INSOCKADDR_IN6)的布局相同。并且我们可以将投射指针从WINSTATIONREMOTEADDRESS重新解释为SOCKADDR

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

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,AddressString,&dwAddressStringLength) == NOERROR)
            {
                DbgPrint("client ip is %s\n",AddressString);
            }
            else
            {
                dwError = WSAGetLastError();
            }
        }
    }
    else
    {
        dwError = GetLastError();
    }

    return dwError;
}
,

如果您正在为 .NET 编程,则可以使用 cassia Nuget Packages。 https://github.com/danports/cassia

它提供了一个 API 来访问远程桌面相关信息。