问题描述
当用户启动与RDP的远程连接时,我正在开发在远程服务器(Windows Server 2016-2019,Windows 10)上运行的应用程序。我正在使用C ++和Win API。
我正在尝试获取远程桌面客户端的公共IP 地址。 我使用方法WTSQuerySessionInformationW,并将WTSInfoClass设置为WTSClientAddress。不幸的是,此功能看起来返回了客户端计算机的本地ip ,例如192.168.1.10,而不是公共计算机。
这种情况是客户端正在从世界上的任何地方访问远程桌面(不仅是从本地网络访问)。 在窗口的事件查看器中的“应用程序和服务日志”->“ Microsoft”->“ Windows”->“ TerminalServices-LocalSessionManager”下,我可以看到公共IP地址(源网络地址)。
解决方法
当我们读到有关WTS_CLIENT_ADDRESS
结构的信息时
客户端网络地址在连接到服务器时由RDP客户端自身报告。 这可能与不同 实际连接到服务器的地址。例如,假设 客户端和服务器之间存在NAT。客户可以 报告自己的IP地址,但实际连接的IP地址 到服务器的是NAT地址。对于VPN连接,IP地址 可能无法被客户端发现。如果找不到 客户端可以报告其拥有的唯一IP地址,可能是ISP 分配的地址。因为该地址可能不是实际的网络 地址,则不应将其用作客户端身份验证的形式。
要从服务器视图中获取实际的网络地址,我们可以将WinStationQueryInformationW
与WinStationRemoteAddress
一起使用-返回WINSTATIONREMOTEADDRESS
。
您可以复制粘贴此声明或全部winsta.h(由于未知原因未粘贴到sdk中)
首先,我们可以确定WINSTATIONREMOTEADDRESS
与SOCKADDR
(SOCKADDR_IN
和SOCKADDR_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 来访问远程桌面相关信息。