Load Balancing MS RDP with a Session Broker

Visibility: Open to anyone

A Traffic Script for load baancing MS Terminal Services when the Session Broker service is being used as discussed here

 

This will parse the x.224 RDP Routing Token that is handed back by the Terminal Server when the user has a brokered session still running on a different host.  When the Stingray Traffic Manager gets one of these x.224 Routing tokens, it will honour it and send the client to the appropriate server in the Terminal Server Pool.

 

There is a very good description of how MS Terminal Services uses the x.224 header here.

 

I have tested it pretty thouroughly in my AWS EC2 lab environment, but I am pretty keen for any feedback from anyone using it.

 

# Traffic Script for load balancing Microsoft Terminal Services Farms.
# This configuration requires the TS Farm to be set up using a server with the
# session broker role installed.  See MS TechNet article for details of how
# to set this up: http://technet.microsoft.com/en-us/library/cc772418.aspx


# Note, in this configuration, you will not need to set up Round Robin DNS,
# instead, just point the farm name DNS entry at the Traffic Group IP you 
# bind to the Virtual Server on the Stingray.


# NB: this configuration requires you to create a Persistence Class of
# "Named Node session persistence" and bind it to the pool.  Put the name of 
# your persistence pool into the "myPersistenceProfile" variable below


##########################################
$myPersistenceProfile = "tslb_namedNode"; #
##########################################


### NOTHING TO EDIT BELOW THIS LINE
# Check to see if this is a new connection or traffic from an existing flow
if ( !connection.data.get("first_run") ){
    
    # Read TPKT Header, 4 bytes
    $tpkt_head = request.get(4); $tpkt_head = string.skip($tpkt_head,2);
    
    # Bytes 3/4 are High/Low header length bits
    $rest_head = string.bytesToInt($tpkt_head) - 4;
    $total = $tpkt_head;
    
        if( $rest_head > 0 ){
               # Read in rest of header
               $header = request.get(string.bytesToInt($total));
               string.skip($header,13);
    


                $pos = string.find( $header, string.hexdecode("0D0A") );
       if( $pos > 0 ){
          $poss_token = string.left( $header, $pos );
          if( string.regexmatch( $poss_token, "[C|c]ookie:\\s(msts|mstshash)=(.+)$" ) ){
             $kval = $1; $vval = $2;
             # We have a cookie to parse
             if( $kval == "mstshash" ){
                # If we have an mstshash cookie, let the session be load balanced normally.
             } else if( $kval == "msts" && string.regexmatch( $vval, "^(\\d+)\\.(\\d+)\\.\\d+$" ) ) {
                log.info("ip: ".$1);
                log.info("port: ".$2);
                    
                # Here we parse the captured "msts=" cookie to extract the back end node info to route the connection
                $ip = string.bytesToDotted( string.reverse( string.intToBytes( $1, 4 ) ) );
                $port = string.bytesToInt( string.reverse( string.intToBytes( $2, 2 ) ) );
                $node = $ip.":".$port; # NB: not IPv6 safe
                connection.setPersistence( $myPersistenceProfile);
                connection.setPersistenceNode( $node );
             } else {
                log.info("discard");
                connection.discard();
             }
          } 
       }
    }
    # Set the connection run flag to prevent us from running this rule again on traffic from the same flow
    connection.data.set("first_run","yes");
 }