An RDP session needs a client address, and the obvious sources disagree. CLIENTNAME gives a computer name, the registry holds values from other sessions, and a socket peer may be a gateway or NAT address rather than the client. Windows does expose a reported client address per session, but you have to ask for the right session and decode the reply correctly.

Anchor the Lookup to One Session

Begin with the session, not the person. A Windows RDP host can carry several sessions for one account, and an address seen before a reconnect may no longer match the live link. Record the session ID, the user and the observation time together. The session ID is the key the API expects, and it is the value that keeps two sessions from being confused.

On the host, the installed session tooling lists what exists; use the administrative view only when you are entitled to it. Confirm the chosen ID immediately before the query, because a recycled ID returns a different session's data.

qwinsta

Call WTSQuerySessionInformationW for That ID

The Terminal Services API answers the question with information class 14, WTSClientAddress. A small ctypes wrapper is enough on Windows, and it avoids reading another user's hive. The code below takes an explicit session ID and asks the local host.

import ctypes
import ipaddress
import socket
from ctypes import wintypes

class WTS_CLIENT_ADDRESS(ctypes.Structure):
    _fields_ = [("AddressFamily", wintypes.DWORD),
                ("Address", ctypes.c_ubyte * 20)]

wts = ctypes.WinDLL("Wtsapi32", use_last_error=True)
query = wts.WTSQuerySessionInformationW
query.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.c_int,
                  ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(wintypes.DWORD)]
query.restype = wintypes.BOOL
free = wts.WTSFreeMemory
free.argtypes = [ctypes.c_void_p]

def client_address(session_id):
    buf = ctypes.c_void_p()
    size = wintypes.DWORD()
    if not query(None, session_id, 14, ctypes.byref(buf), ctypes.byref(size)):
        raise ctypes.WinError(ctypes.get_last_error())
    try:
        if size.value < ctypes.sizeof(WTS_CLIENT_ADDRESS):
            raise RuntimeError("short WTS buffer")
        value = ctypes.cast(buf, ctypes.POINTER(WTS_CLIENT_ADDRESS)).contents
        raw = bytes(value.Address)
        if value.AddressFamily == socket.AF_INET:
            addr = ipaddress.ip_address(raw[2:6])
        elif value.AddressFamily == socket.AF_INET6:
            addr = ipaddress.ip_address(raw[:16])
        else:
            return None
        return None if addr.is_unspecified else str(addr)
    finally:
        free(buf)

Pass the integer session ID you just verified, and keep the null server handle for the local machine. Treat a failed call as an error rather than as an absent address, because the two lead to different conclusions. Querying another user's session requires the appropriate Query Information permission, so run the tool under an account that has been granted it instead of escalating ad hoc. The returned buffer belongs to the API and must be released even when decoding fails.

Decode the Family, Then Release the Buffer

The structure is not an IP string. Its family field decides how the twenty address bytes are read: IPv4 starts at offset two, while IPv6 starts at offset zero. The unspecified address means the client did not report one, which is not the same as an empty string. Remote Desktop Gateway connections are documented as a case where the address is unavailable, so a gateway-mediated session reports nothing useful for the client's real address. Correlation then has to move to the gateway's own records.

Print the family alongside the address while you debug, because a mismatch between the two is the usual cause of a plausible but wrong value. Release the buffer with WTSFreeMemory in a finally block so an exception does not leak it.

Separate the Reported Address From Proof

A reported client address is a client-supplied statement, not a trusted observation. It is not the TCP peer, not the VPN gateway's lease table, and not the client's public address. When the real question is which VPN lease belongs to a person, correlate the VPN server's authenticated session record at the same time. When the question is which source reached the listener, read the connection table or the permitted server-side logs.

Keep these values separately labelled in any support tool. A missing WTS result should stay missing rather than fall back to an arbitrary registry value from a different session. Log the session ID next to the address, because the same user can reconnect and receive a new value. Neither the client name nor the reported address is sufficient evidence for granting access.

Allakkat attuumassuteqartut