问题
I'm developing an application which runs on the remote server (Windows Server 2016-2019, Windows 10) when user is initiating a remote connection with RDP. I'm using C++ and Win API.
I'm trying to get public IP address of the Remote Desktop Client. I used method WTSQuerySessionInformationW with WTSInfoClass set to WTSClientAddress. Unfortunately it looks as this function returns local ip of the client's computer for instance 192.168.1.10 not the public one.
The scenario is that client is accessing a remote desktop from anywhere in the world (so not only from a local network). In the Window's Event Viewer under Applications and Services Logs -> Microsoft -> Windows -> TerminalServices-LocalSessionManager I can see the public ip address (Source Network Address).
Which function or mechanism can I use to get this ip address?
回答1:
when we read about WTS_CLIENT_ADDRESS structure
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.
for get actual network address from server view we can use WinStationQueryInformationW with WinStationRemoteAddress- it return WINSTATIONREMOTEADDRESS.
you can copy paste this declaration`s or all winsta.h (by unknown reason in not included to sdk)
for first look we can decide that WINSTATIONREMOTEADDRESS is the same as SOCKADDR (SOCKADDR_IN
and SOCKADDR_IN6
) by layout. and we can do reinterpret cast pointer from WINSTATIONREMOTEADDRESS to SOCKADDR.
but this is critical error. structures have different alignment !
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);
for use WinStationQueryInformationW
need link with winsta.lib or get it in runtime from winsta.dll
so final code can be next:
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;
}
来源:https://stackoverflow.com/questions/63493633/getting-public-ip-address-of-a-remotes-desktop-client