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
Published Jan 25, 2016
Version 1.0

Was this article helpful?

No CommentsBe the first to comment