Server Name Indication (SNI) based RDP Proxy
Problem this snippet solves:
The outlined iRule can be used to access multiple TLS enabled Remote Desktop Hosts behind a single Virtual Server.
The code below, will emulate the initial RDP connection handshake to the client, till the client sends the TLS based SNI record (via Start-TLS message).
It would then use a data-group to compare the SNI information with valid FQDNs. If a valid FQDN is found, it would used the data-group value to set up the backend RDP connection. It would then emulate the initial RDP connection handshake to the backend server and finally forward the original Start-TLS message to the selected backend server.
If a valid FQDN couldn't be found in the Start-TLS message it would reject the RDP connection...
Cheers, Kai
How to use this snippet:
- Setup a standard Virtual Server for TCP:3389.
- Apply SNAT and TCP profiles as needed.
- You don't need to add a default_pool.
- Create the DG_RDP_SERVER data-group to resolves the valid FQDNs to their Nodes.
Code :
ltm data-group internal DG_RDP_SERVER { records { server1.itacs.de { data "192.168.1.1 3389" } server2.itacs.de { data "192.168.1.2 3389" } } type string } when CLIENT_ACCEPTED { # Init packet counter set rdp_packet 0 # Collect client side RDP data TCP::collect } when CLIENT_DATA { if { [incr rdp_packet] == 1 } then { # Directly respond to the intial RDP protocol negotiation (using fixed payload) TCP::respond [b64decode AwAAEw7QAAASNAACDwgACAAAAA==] # Drop the received client payload and collect addtional payload TCP::payload replace 0 [TCP::payload length] "" TCP::collect } elseif { $rdp_packet == 2 } then { # Match the second RDP packet (aka. Start-TLS) for known SNI records using a datagroup. if { [set node [class match -value [string tolower [TCP::payload]] contains DG_RDP_SERVER]] ne "" } then { # The Start-TLS packet contains a known SNI value. Using the datagroup result for node selection. node $node # Store the Start-TLS packet for later use. set tls_start [TCP::payload] # Replace the payload to negotiate the server side RDP connection. (using fixed payload) TCP::payload replace 0 [TCP::payload length] [b64decode AwAAEw7gAAAAAAABAAgACwAAAA==] # Release the request TCP::release } else { # Start-TLS packet didn't contain a known FQDN. Rejecting the connection... TCP::release reject } } } when SERVER_CONNECTED { # Collect server side RDP data TCP::collect } when SERVER_DATA { if { [info exist tls_start] } then { # Server Side RDP / TLS negotiation is in progress. Drop the initial RDP connection handshake, since the client has already established the connection. TCP::payload replace 0 [TCP::payload length] "" # Replay the stored Start-TLS playload to the server side. TCP::respond $tls_start unset -nocomplain tls_start } else { # Server Side RDP / TLS negotiation has completed. # Release the request TCP::release } }
Tested this on version:
12.0