【问题标题】:Getting public IP address of a remote's desktop client获取远程桌面客户端的公共 IP 地址
【发布时间】:2020-08-19 19:19:31
【问题描述】:

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

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

该场景是客户端从世界任何地方访问远程桌面(因此不仅仅是从本地网络)。 在 Applications and Services Logs -> Microsoft -> Windows -> TerminalServices-LocalSessionManager 窗口的事件查看器中,我可以看到公共 ip 地址(源网络地址)。

我可以使用什么函数或机制来获取这个ip地址?

【问题讨论】:

  • 你需要使用WinStationQueryInformationWWinStationRemoteAddress
  • 不清楚你在找哪个公网IP。运行 RDP 会话的计算机的公共 IP,或连接到 RDP 会话的客户端的公共 IP。 WTSClientAddress 返回连接到会话的客户端的远程 IP,如果通过 Internet 建立 RDP 连接,它将是客户端的公共 IP。否则它将是客户端的 LAN IP。
  • @RbMm WinStationQueryInformationW() 已弃用。 WinStationRemoteAddressWTSQuerySessionInformation() 中的 WTSClientAddress 有何不同?从服务器端调用时,它们都返回客户端的远程 IP。
  • @RemyLebeau - WinStationQueryInformationW 返回 another 结果而不是 WTSQuerySessionInformation。我确切地知道这一点,用于自我解决方案。和WinStationQueryInformationW 返回实际的客户地址。
  • @RbMm 在今天之前我从未听说过WinStationQueryInformationW(),我无法通过 Internet 对其进行测试,只能通过 LAN 进行测试。您能否举例说明WinStationRemoteAddressWTSClientAddress 如何报告不同的结果?

标签: c++ winapi rdp


【解决方案1】:

当我们读到 WTS_CLIENT_ADDRESS 结构时

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

为了从服务器视图中获取实际网络地址,我们可以使用WinStationQueryInformationWWinStationRemoteAddress——它返回WINSTATIONREMOTEADDRESS

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

首先我们可以通过布局确定WINSTATIONREMOTEADDRESSSOCKADDRSOCKADDR_INSOCKADDR_IN6)相同。我们可以重新解释从WINSTATIONREMOTEADDRESSSOCKADDR的转换指针。

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

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;
}

【讨论】:

  • 感谢您的详细回答和示例代码。 WinStationQueryInformationW 是否支持 Windows 2016、2019 和 Windows 10?你在那里试过吗。我在文档中读到仅支持 Windows 2012。
  • @PrzemSzer - 我不知道你在“支持”下是什么意思,但是这个 api 存在并且在所有 Windows 版本上都可以正常工作,包括最新的 win10 版本。文档经常骗你
  • 非常感谢。这行得通!您是否愿意为此进行付费咨询,例如审查我们的代码?如果有可能,请发送电子邮件至 psz@astec.net。谢谢!
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 2018-04-30
    • 1970-01-01
    • 2014-12-08
    • 1970-01-01
    • 2023-03-19
    • 2019-01-14
    • 2019-11-13
    • 1970-01-01
    • 2015-04-26
    相关资源
    最近更新 更多