cancel
Showing results for 
Search instead for 
Did you mean: 

VMware View AlwaysOn Desktop Reference Architecture - TrafficScript Rule

The following is a Traffic Script Rule for load-balancing VMware View Servers in an AlwaysOn Architecture serving all VMware clients.

 

This Rule is used both as a Request and a Response rule. More details on the usage and configurations of the Brocade Virtual Traffic Manager can be found in the Deployment Guide (link below).

 

This link is used as a reference in the Brocade Virtual Traffic Manager and VMware Horizon View Servers Deployment Guide

 

 

#// TS Rule for LB VMWare View

  

#// Set the Debug Level (possible values: 0,1,2,3)

#// 0 = Logging Off

#// 1 = Informational logging

#// 2 = Full Debug

#// 3 = Full Debug INCLUDING PASSWORDS - USE WITH EXTREME CARE

$debug = 2;

  

#// Rule to Direct Traffic Based on AD Group Membership 

#// Please declare the names of the pools you have configured, and ensure 

#// that the trafficscript!variable_pool_use Global setting is set to 'yes' 

 

#// What is the name of your VTM AD Authenticator

#// This can be setup under "Catalogs > Authenticators" in the Traffic Manager GUI.

$authenticator = "AD_AUTHENTICATOR"; 

 

#// What are the names of your pools to differentiate between Site A and Site B?

#// These are setup under "Services > Pools" in the Traffic Manager GUI.

$view_siteA_pool = "SITE-A-POOL";

$view_siteB_pool = "SITE-B-POOL";

 

#// Following are some View Specific variables that we need.

#// 'server' is the name of your view Connection Server

#// 'dns' is the DNS search suffix (single suffix only) used in your setup

#// 'domain' is the NT4 style name of your active directory

#// 'guid' is the GUID of the view connection server, and can be found by checking

#//     'HKLM\SOFTWARE\VMware, Inc.\VMware VDM\Node Manager\ConnectionServer Cluster GUID' with regedit.exe

#//    on your connection server.  

$view_info = 

    [ "guid" => "648a7ef8-dcba-abcd-efgh-58973ad94085", 

      "server" => "rvbdvTMs1", 

      "dns" => "brocade.local", 

      "domain" => "brocade" ]; 

 

 

#// Here we need to know about the names of the AD Groups to map to each Site

$siteA_AD_Groupname = "brcd_DC0";

$siteB_AD_Groupname = "brcd_DC1";

 

#// Here we need to know about the IP addresses of your SITEB Traffic Manager.  

#// Site B VTM IP addresses (up to two supported. If your setup requires more, please

#// Contact your Brocade sales representative to arrange for Brocade Professional #// Services to provide a quote for customising this deployment guide.    

 

$site_B_VTM1_IP = "10.10.10.6"; 

$site_B_VTM2_IP = "";   

  

#// Is this a request or response rule?

$rulestate = rule.getstate();

 

#// What is the Client's IP address - useful for logging:

$client_ip = request.getRemoteIP(); 

 

if ($rulestate == "REQUEST" ){

if( $debug > 0 ) {log.info("#*#* Start Reqeust Section *#*#");}

#//We need to extract the tunnelID for persistence purposes then break out of the rule to let the tunnel through

$path = http.getPath();

if (string.startswith($path, "/ice/tunnel")){

    $tunnelID = http.getQueryString();

    if ($tunnelID){

      if ($debug > 0){log.info("Request from client: " . $client_ip . " Using tunnelID:". $tunnelID . " as persistence value");}

      connection.setPersistenceKey( $tunnelID );

      $choose_pool = data.get( $tunnelID );

      if( $debug > 0 ) { log.info( "Matching this to pool: ".$choose_pool ); }

      if( $choose_pool ) {

        pool.select( $choose_pool );

      }

    } else {

      log.error("Request from client: " . $client_ip . " Found no tunnelID:". $tunnelID . " in request for /ice/tunnel: This shouldn't happen, contact support.");

    }

    if ($debug > 1){log.info("Request from client: " . $client_ip . " Request is for /ice/tunnel - exiting rule to let it through");}

    #//Bypass Script if the path is for "/ice/tunnel" to let the desktop session start

    break;

}

 

 

$JSESSIONID = http.getCookie("JSESSIONID");

if($JSESSIONID){

  if ($debug > 0) {log.info("Request from client: " . $client_ip . " JSESSIONID=" .$JSESSIONID." found in the request...");}

  connection.setPersistenceKey($JSESSIONID);

  $choose_pool = data.get( $JSESSIONID );

  if( $debug > 0 ) { log.info( "Matching this to pool: ".$choose_pool ); }

  if( $choose_pool ) {

    pool.select( $choose_pool );

  }

} else {

  if ($debug > 0) {log.info("Request from client: " . $client_ip . " No JSESSIONID found in the request...");}

}

 

#//Bypass Script if the path is for / as it could be GLB health monitor 

if( $path == "/") { break;}

 

#// Collect the HTTP request headers and body

$req_headers = http.getRequest();

if(!http.getHeader("Expect")=="100-continue"){

      $req_body = http.getBody();

}

if ($debug > 1){log.info("Request from client: " . $client_ip . " HTTP Request is:\n" . $req_headers);}

if ($debug > 1){log.info("Request from client: " . $client_ip . " HTTP BODY is:\n" . $req_body);}

 

 

 

#// Reset flags to the needed defaults.

$username = ''; 

$password = ''; 

 

#// Inspect the request and see if it is something we are interested in:

#// syntax is xml.xpath.matchNodeSet( doc, nspacemap, query )

$is_xml = false;

if (!string.regexmatch($req_body, '\<\?xml',"i")){

  #//Document is _not_ an XML

                  if ($debug > 1){log.info("Request from client: " . $client_ip . " Request is NOT an XML document - exiting");}

                  $is_xml = false;

                  break;

} else {

  #//Document is an XML Doc

  $is_xml = true;

  if ($debug > 0){log.info("Request from client: " . $client_ip . " Request is an XML document");}

}

 

#// test to see if we have been sent a "<get-configuration>" request

$get_configuration = xml.xpath.matchNodeCount( $req_body, "", "//broker/get-configuration" );

if ($debug > 0){log.info("Request from client: " . $client_ip . " get-config is:" . $get_configuration);}

 

#// test to see if we have been sent a "<get-tunnel-connection>" request

$get_tunnel_connection = xml.xpath.matchNodeCount( $req_body, "", "//broker/get-tunnel-connection" );

if ($debug > 0){log.warn("Request from client: " . $client_ip . " get-tunnel-connection is:" . $get_tunnel_connection);}

 

#// test to see if we have been sent a "<do-submit-authentication>" request

$do_submit_authentication = xml.xpath.matchNodeCount( $req_body, "", "//broker/do-submit-authentication" );

if ($debug > 0){log.info("Request from client: " . $client_ip . " do-submit-authentication is:" . $do_submit_authentication);}

 

#// test to see if we have been sent a "<do-logout>" request

$do_logout = xml.xpath.matchNodeCount( $req_body, "", "//broker/do-logout" );

if ($debug > 0){log.info("Request from client: " . $client_ip . " do-logout is:" . $do_logout);}

 

#// test to see if we have been sent a "<get-tunnel-connection>" request

if ($get_tunnel_connection == 1 && $is_xml == true){

  if( $debug > 0 ){ log.info( "Request from client: " . $client_ip . " <get-tunnel-connection> identified from: " . $client_ip ); }

    connection.data.set("connection_state", "get-tunnel-connection");

 }

#// If we have a <get-configuration> query, we will send the first response and exit

if ($get_configuration == 1 && $is_xml == true){

  if( $debug > 0 ){ log.info( "Request from client: " . $client_ip . " <get-configuration> response - Sending first_response to client: " . $client_ip ); }

  sendFirstResponse($view_info, $debug);

  break;

}

#// If we have been sent authentication credentials, we will go to work

if ($do_submit_authentication == 1 && $is_xml == true){

  if( $debug > 0 ){ log.info( "Request from client: " . $client_ip . " <do-submit-authentication> identified from: " . $client_ip ); }

 

  $xml_user_credentials_data = xml.xpath.matchNodeSet($req_body,"","//broker/do-submit-authentication/screen/params/param/values/value/text()");

  $xml_user_credentials_fieldnames = xml.xpath.matchNodeSet($req_body,"","//broker/do-submit-authentication/screen/params/param/name/text()");

 

  #// we check that $xml_user_credentials_fieldnames contains "username, domain, password":

  if(string.regexmatch( $xml_user_credentials_fieldnames, "username, domain, password")){   

    if ($debug > 0){log.info("Request from client: " . $client_ip . " <do-submit-authentication>: extracted username, domain, password fields in submitted request.");}

    if ($debug > 1){log.info("Request from client: " . $client_ip . " <do-submit-authentication> extracted XML Fields: " .$xml_user_credentials_fieldnames);}

    if ($debug > 2){log.info("Request from client: " . $client_ip . " <do-submit-authentication> extracted XML Values: " .$xml_user_credentials_data);}

 

    #//lets extract the username and password:

    $credentials = string.split($xml_user_credentials_data,",");

    $username = $credentials[0];

    $password = $credentials[2];

    #// Currently we don't need the domain name, so we won't extract it, but it is here for future use if needed

    #//$cred_domain = $credentials[1];

    

    $auth = auth.query( $authenticator, $username, $password ); 

    #// We should check to ensure auth.query returned successfully:

    #/// If $auth returns 'Error' then it means something is wrong with the authenticator and the admin needs to investigate

    if( $auth['Error'] ) { 

        log.error( "Request from client: " . $client_ip ." Error with authenticator " . $authenticator . ": " . $auth['Error'] );

    }

   

    #// Lets extract the list of groups the user is a 'memberOf'

    $groups = $auth['memberOf']; 

    #// If there is only one group, "$auth['memberOf']" will return a string,

    #// not an array, so we need to force the $groups value to be an array

    $group_isArray = lang.isarray($groups);

    if ($group_isArray != true){

      if ($debug > 1){log.info("Connection From: " . $client_ip .": $auth['memberOf'] returned a single group, forcing $group to be an array");}

      $groups = lang.toArray($groups);

    }

 

    if ($debug > 1){log.info("Request from client: " . $client_ip ." Full Auth Info: " . lang.dump($auth));}

    if ($debug > 1){log.info("Request from client: " . $client_ip ." Group Info: " . lang.dump($groups));}

    

 

    #// Map Site B users to the Site B pool of servers

    foreach ( $group in $groups){

      if( $debug > 0 ) {log.info("$group is" . lang.dump($group));}

      

       if( string.contains( $group, $siteB_AD_Groupname ) ){ 

          if( $debug > 0) { log.info( "Request from client: " . $client_ip ." User: ".$username." member of SiteB Users group" );} 

          pool.select( $view_siteB_pool ); 

          break; 

       } else{

        if( $debug > 0) { log.info( "Request from client: " . $client_ip ." User: ".$username." is NOT a member of SiteB Users group" );} 

 

       }

    }

    #// Map Site A users to the Site A pool of servers

    foreach ( $group in $groups){

       if( string.contains( $group, $siteA_AD_Groupname ) ) { 

          if( $debug > 0 ) { log.info( "Request from client: " . $client_ip ." User: ".$username." member of Default SiteA Users group" ) ;} 

          pool.select( $view_siteA_pool );

          break; 

       } else{

        if( $debug > 0) { log.info( "Request from client: " . $client_ip ." User: ".$username." is NOT a member of SiteA Users group" );} 

 

       }

    }

  }

} #//end do-submit-authentication

 

 if( $debug > 0 ) {log.info("#*#* End Reqeust Section *#*#");}

} #// End of REQUEST Section

 

sub sendFirstResponse( $info, $debug )  { 

 #//$first_response = '<?xml version="1.0"?><broker version="7.0"><configuration><result>ok</result><broker-guid>'.$info["guid"].'</broker-guid><broker-service-principal><type>kerberos</type><name>'.$info["server"].'[email protected]'.$info["dns"].'</name></broker-service-principal><authentication><screen><name>windows-password</name><params><param><name>domain</name><values><value>'.$info["domain"].'</value></values></param></params></screen></authentication></configuration></broker>';

$first_response = '<?xml version="1.0"?><broker version="7.0"><set-locale><result>ok</result></set-locale><configuration><result>ok</result><broker-guid>'.$info["guid"].'</broker-guid><broker-service-principal><type>kerberos</type><name>'.$info["server"].'[email protected]'.$info["dns"].'</name></broker-service-principal><authentication><screen><name>windows-password</name><params><param><name>domain</name><values><value>'.$info["domain"].'</value></values></param></params></screen></authentication></configuration></broker>';

  if( $debug > 1 ){ log.info( "first_response data&colon;\n" .$first_response); } 

  http.sendResponse( "200 OK", "text/xml;charset=UTF-8", $first_response, "XFF: VTM_SiteA" ); 

}

###// AK: should be ==

if ($rulestate == "RESPONSE" ){

if( $debug > 0 ) {log.info("#*#* Start Response Section *#*#");}

$debug_node = connection.getNode();

$debug_pool = connection.getPool();

$debug_http_response_code=http.getResponseCode();

 

$resp_headers = http.getResponseHeaders();

if ($debug > 1){log.info("Response to: " . $client_ip . " HTTP Response Headers are:" . lang.dump($resp_headers));}

 

#//$resp_body = http.getResponseBody( 96 );  #// What is the nature of the response?

$content_type = http.getResponseHeader( "Content-Type" );

if ($debug > 1) { log.info("Content type of response is" . $content_type ); }

if( string.startsWith( $content_type, "text/xml" ) ) {

  #// TODO: What if the response doesn't contain a </broker> tag?

  $resp_body = http.stream.readResponse( 4096, "</broker>" ); #// Limit was arbitrarily chosen

} else {

    if( $debug > 0 ) {log.warn( "This response was not XML - not extracting content for logging." );}

}

 

#// ASSUMPTION: Any XML response we care about (i.e. the one with the session ID in it) is less than 4096 bytes.

#//             If it's longer, then we'll just stream it to the client.

if( string.length( $content_type ) < 4096 ) {

 

 if ($debug > 1) { log.info( "Grabbed response body:\n" . $resp_body ); }

 

 if( $debug > 0 ) {log.info("RESPONSE: connection was sent to node:" . $debug_node);}

 if( $debug > 0 ) {log.info("RESPONSE: connection was sent to pool:" . $debug_pool);}

 if( $debug > 0 ) {log.info("RESPONSE: Server Responded with code:" . $debug_http_response_code);}

   $response_cookies = http.getResponseCookies();

   

  if($response_cookies){

    if($response_cookies["JSESSIONID"]){

      $JSESSIONID = $response_cookies["JSESSIONID"];

      #// need to bypasS for <set-locale> message

       if (string.regexmatch($resp_body, '\<\?xml',"i")){

          if( $debug > 0 ) {log.info("#### PARSING XML RESPONSE ###");}

          $set_locale = xml.xpath.matchNodeCount($resp_body,"","//broker/set-locale/result");

          if( $debug > 0 ) {log.info("#### SETLOCALE IS: ". $set_locale . " ###");}

          if (!$set_locale){

             connection.setPersistenceKey( $JSESSIONID );

             data.set( $JSESSIONID, $debug_pool );

             if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " Response set JSESSIONID=" . $JSESSIONID .". Extracting and using for persistence key");}

          } else {

             if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " Response set JSESSIONID=" . $JSESSIONID .". _NOT_ extracting and using for persistence key");}

          }

       } else {

          if( $debug > 0 ) {log.warn( "Response data did not seem to be XML." );}

      }

    }

  } else {

    if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " No JSESSIONID found in response...");}

  }

  

  if (connection.data.get("connection_state") ==  "get-tunnel-connection"){

    if ($debug > 0){log.info("Response to: " . $client_ip . " HTTP Response Headers are:" . lang.dump($resp_headers));}

    if ($debug > 0){log.info("Response to: " . $client_ip . " HTTP Response BODY is:" . $resp_body);}

        

    #// Inspect the response and see if it is something we are interested in:

    #// syntax is xml.xpath.matchNodeSet( doc, nspacemap, query )

    if (string.regexmatch($resp_body, '\<\?xml',"i")){

      #//Document is an XML Doc

      if ($debug > 0){log.info("Request from client: " . $client_ip . " Response to <get-tunnel-connection> is an XML document");}

      $tunnelID = xml.xpath.matchNodeSet($resp_body,"","//broker/tunnel-connection/connection-id/text()");

      if ($tunnelID){

        if ($debug > 0) {log.info("Response Rule - Request from client: " . $client_ip . " Response set tunnelID=" . $tunnelID .". Extracting and using for persistence key");}

        connection.setPersistenceKey( $tunnelID );

        data.set( $tunnelID, $debug_pool );

      } 

    } else {

       lang.warn( "Didn't think response body was XML." );

    } 

  } #// End of connection_state == get-tunnel-connection

 

  if ($debug > 1){log.info("Response to: " . $client_ip . " HTTP Response Headers are:" . lang.dump($resp_headers));}

  if ($debug > 1){log.info("Response to: " . $client_ip . " HTTP Response BODY is:" . $resp_body);}

}

 

if( $resp_body ) {

   http.stream.startResponse( http.getResponseCode(), $content_type, "" );

   http.stream.writeResponse( $resp_body );

   http.stream.continueFromBackend();

}

 

if( $debug > 0 ) {log.info("#*#* End Response Section *#*#");}

} ##/// End of RESPONSE Section#//

 

Version history
Revision #:
2 of 2
Last update:
‎01-11-2021 04:44:PM
Updated by:
 
Contributors