c++ - 获取远程桌面客户端的公共(public) IP 地址

标签 c++ winapi rdp

当用户启动与 RDP 的远程连接时,我正在开发一个在远程服务器(Windows Server 2016-2019、Windows 10)上运行的应用程序。我正在使用 C++ 和 Win API。
我正在尝试获取 公网IP 远程桌面客户端的地址。
我用的方法WTSQuerySessionInformationW将 WTSInfoClass 设置为 WTSClientAddress。不幸的是,看起来这个函数返回 本地ip 客户端计算机的实例 192.168.1.10 不是公共(public)计算机。
该场景是客户端正在从世界任何地方访问远程桌面(因此不仅来自本地网络)。
在应用程序和服务日志 -> Microsoft -> Windows -> TerminalServices-LocalSessionManager 下的窗口事件查看器中,我可以看到公共(public) IP 地址(源网络地址)。
我可以使用哪个函数或机制来获取此 IP 地址?

最佳答案

当我们读到 WTS_CLIENT_ADDRESS 结构体

The client network address is reported by the RDP client itself when it connects to the server. This could be different than the address that actually connected to the server. For example, suppose there is a NAT between the client and the server. The client can report its own IP address, but the IP address that actually connects to the server is the NAT address. For VPN connections, the IP address might not be discoverable by the client. If it cannot be discovered, the client can report the only IP address it has, which may be the ISP assigned address. Because the address may not be the actual network address, it should not be used as a form of client authentication.


要从服务器 View 获取实际网络地址,我们可以使用 WinStationQueryInformationW WinStationRemoteAddress - 它返回 WINSTATIONREMOTEADDRESS .
您可以复制粘贴此声明的或全部 winsta.h (由于未知原因未包含在 sdk 中)
乍一看,我们可以决定 WINSTATIONREMOTEADDRESS SOCKADDR 相同( SOCKADDR_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, 0, AddressString, &dwAddressStringLength) == NOERROR)
            {
                DbgPrint("client ip is %s\n", AddressString);
            }
            else
            {
                dwError = WSAGetLastError();
            }
        }
    }
    else
    {
        dwError = GetLastError();
    }

    return dwError;
}

关于c++ - 获取远程桌面客户端的公共(public) IP 地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63493633/

相关文章:

rdp - 远程桌面协议(protocol)提供程序 API

rdp - 有关如何编写自己的 RDP 客户端的引用

c++ - 当数组的第一个元素最高时,Findmax() 方法返回第二高的值

c++ - SystemParametersInfo 将背景设置为纯色而不是实际设置图片

c++ - WIN API ReadFile() 返回 GetLastError() ERROR_INVALID_PARAMETER

c++ - 关于使用 C++ 使用命令行参数调用 CreateProcessAsUser 的说明

RDP 端口更改后无法访问 Azure 虚拟机

C++ : Suggest names for mutating and non-mutating versions of a member function

c++ - 将模板 lambda 转换为 `void *` 是否安全?

c++ - iOS中箭头成员选择算子的使用