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