Radius Aware Load Balancing via iRules

RADIUS is very popular authentication method that is being widely used in amongst some of the top service providers around the world, not to mention in deployments ranging from enterprise corporate environments to clustered mail systems and back. As these systems continue to grow and the demands on the RADIUS systems for authentication responses grows, along with the complexity of the deployments involving these systems, there becomes more and more demand for systems that are “RADIUS-aware”.

In particular we’ve seen and heard more call for RADIUS-aware Load Balancing and persistence from some of the larger, more resource intensive markets and users that are always looking for new ways to stretch not only their resources, but the limits of what they can provide their users. These people don’t want to use simple IP based persistence or routing, they want to dig into the RADIUS information itself to make a truly meaningful decision for their persistence, load balancing, etc.

This isn’t just looking for a set of ascii characters and load balancing based off of that. In this example we’re looking at being able to dig into the actual binary information in the header code, match up that value against a list of pre-determined values, intelligently select what to do with it, then perform whatever action is necessary based on the message that ID equates to. Not only that, but we're able inspect both inbound and outbound traffic to perform these inspections.

This is a great example of the power that you get when combining all the power of TMoS with a robust toolset provided to you via the iRules language. With this kind of fine-grained, application aware control, it really goes to show how you can influence and customize your environment and your users’ experience via iRules that you just couldn't achieve without the combination of tools the LTM delivers, iRules included.

Code:

when RULE_INIT {
  array set ::msg_types {
    1 "Access-Request"
    2 "Access-Accept"
    3 "Access-Reject"
    4 "Accounting-Request"
    5 "Accounting-Response"
    11 "Access-Challenge"
    12 "Status-Server"
    13 "Status-Client"
    255 "Reserved"
  }

  array set ::attr_types {
    1 "RADIUS_ATTR_USER_NAME"
    2 "RADIUS_ATTR_USER_PASSWORD"
    4 "RADIUS_ATTR_NAS_IP_ADDRESS"
    5 "RADIUS_ATTR_NAS_PORT"
    6 "Service-Type"
    7 "Framed-Protocol"
    8 "Framed-IP-Address"
    9 "Framed-IP-Netmask"
    10 "Framed-Routing"
    11 "Filter-Id"
    12 "RADIUS_ATTR_FRAMED_MTU"
    13 "Framed-Compression"
    14 "Login-IP-Host"
    15 "Login-Service"
    16 "Login-TCP-Port"
    24 "RADIUS_ATTR_STATE"
    25 "RADIUS_ATTR_CLASS"
    26 "RADIUS_ATTR_VENDOR_SPECIFIC"
    27 "RADIUS_ATTR_SESSION_TIMEOUT"
    28 "RADIUS_ATTR_IDLE_TIMEOUT"
    29 "RADIUS_ATTR_TERMINATION_ACTION"
    30 "RADIUS_ATTR_CALLED_STATION_ID"
    31 "RADIUS_ATTR_CALLING_STATION_ID"
    32 "RADIUS_ATTR_NAS_IDENTIFIER"
    40 "RADIUS_ATTR_ACCT_STATUS_TYPE"
    41 "RADIUS_ATTR_ACCT_DELAY_TIME"
    42 "RADIUS_ATTR_ACCT_INPUT_OCTETS"
    43 "RADIUS_ATTR_ACCT_OUTPUT_OCTETS"
    44 "RADIUS_ATTR_ACCT_SESSION_ID"
    45 "RADIUS_ATTR_ACCT_AUTHENTIC"
    46 "RADIUS_ATTR_ACCT_SESSION_TIME"
    47 "RADIUS_ATTR_ACCT_INPUT_PACKETS"
    48 "RADIUS_ATTR_ACCT_OUTPUT_PACKETS"
    49 "RADIUS_ATTR_ACCT_TERMINATE_CAUSE"
    50 "RADIUS_ATTR_ACCT_MULTI_SESSION_ID"
    51 "RADIUS_ATTR_ACCT_LINK_COUNT"
    52 "RADIUS_ATTR_ACCT_INPUT_GIGAWORDS"
    53 "RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS"
    55 "RADIUS_ATTR_EVENT_TIMESTAMP"
    61 "RADIUS_ATTR_NAS_PORT_TYPE"
    64 "RADIUS_ATTR_TUNNEL_TYPE"
    65 "RADIUS_ATTR_TUNNEL_MEDIUM_TYPE"
    77 "RADIUS_ATTR_CONNECT_INFO"
    79 "RADIUS_ATTR_EAP_MESSAGE"
    80 "RADIUS_ATTR_MESSAGE_AUTHENTICATOR"
    81 "RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID"
    85 "RADIUS_ATTR_ACCT_INTERIM_INTERVAL"
    95 "RADIUS_ATTR_NAS_IPV6_ADDRESS"
  }
}

when CLIENT_DATA {
  if { [UDP::payload length] > 4 } {
    #log "UDP::payload length [UDP::payload length]"
    binary scan [UDP::payload] c hdr_code
    log "radius type  $::msg_types($hdr_code)"
    binary scan [UDP::payload] @20a* rest_string
    while { [string length $rest_string] >4} {
      binary scan $rest_string cca* attr_id attr_length rest_string
      scan $attr_length  %i length
      set ff [format "a%da*" [expr {$length} - 2]]
      switch $attr_id {
1 { #if the type of attrbuite is RADIUS_ATTR_USER_NAME
  binary scan $rest_string $ff attr_value rest_string
          log "attribute id:  $::attr_types($attr_id); attribute length: $length; value: $attr_value"
  persist uie $attr_value
}
4 { #if the type of attrbuite is RADIUS_ATTR_NAS_IP_ADDRESS
  binary scan $rest_string c4a* IPtmp rest_string
  set IP {}
  foreach num $IPtmp {
    lappend IP [expr ($num + 0x100) % 0x100]
  }
  set attr_value [join $IP .]
  log "$::attr_types($attr_id) value $attr_value"
}
default {
  binary scan $rest_string $ff attr_value rest_string
                 #log "attribute id:  $::attr_types($attr_id); attribute length: $length; filed value: $attr_value"
}
            }   
}
    } 
}

when SERVER_DATA {
  if { [UDP::payload length] > 4 } {
    #log "UDP::payload length [UDP::payload length]"
    binary scan [UDP::payload] c hdr_code
    log "radius type  $::msg_types($hdr_code)"
    binary scan [UDP::payload] @20a* rest_string
    while { [string length $rest_string] >4} {
      binary scan $rest_string cca* attr_id attr_length rest_string
      scan $attr_length  %i length
      set ff [format "a%da*" [expr {$length} - 2]]
  switch $attr_id {
1 { #if the type of attrbuite is RADIUS_ATTR_USER_NAME
  binary scan $rest_string $ff attr_value rest_string
                  log "attribute id:  $::attr_types($attr_id); attribute length: $length; value: $attr_value"
}
4 - 
8 - 
9 { # 4 "RADIUS_ATTR_NAS_IP_ADDRESS" 8 "Framed-IP-Address"  9 "Framed-IP-Netmask"
  binary scan $rest_string c4a* IPtmp rest_string
  set IP {}
  foreach num $IPtmp {
     lappend IP [expr ($num + 0x100) % 0x100]
  }
  set attr_value [join $IP .]
  log "$::attr_types($attr_id) value $attr_value"
}
default {
  binary scan $rest_string $ff attr_value rest_string
                 #log "attribute id:  $::attr_types($attr_id); attribute length: $length; filed value: $attr_value"
}
            }   
}
    } 
}

Contributed by David Wang

Published Jan 17, 2008
Version 1.0

Was this article helpful?

1 Comment

  • Tao_Liu_90341's avatar
    Tao_Liu_90341
    Historic F5 Account
    One deficiency in this rule is that rest_string got copied over and over. An alternative is that using an offset var and build a format string "@???".