Forum Discussion

Ricardo_Marinho's avatar
Ricardo_Marinho
Icon for Nimbostratus rankNimbostratus
Jan 28, 2010

HTTPRequestThrottle Issues

Hi,

 

 

We are using iRule HTTPRequestThrottle from (http://devcentral.f5.com/wiki/default.aspx/iRules/HTTPRequestThrottle.html).

 

 

Sometimes I get following errors on ltm log:

 

TCL error: HTTPRequestThrottle - can't use empty string as operand of "-" while executing "expr {$curr_time - $start_time}"

 

 

TCL error: HTTPRequestThrottle - Operation not supported (line 1) invoked from within "HTTP::header insert "X-RequestLimit-Class" "normal""

 

 

We have made some adjustments, but nothing really serious only log statements.

 

 

Our code is:

 

when RULE_INIT { 
     Expiration for tracking IPs not yet throttled (seconds) 
    set ::standard_expiration_time 360 
     Expiration for tracking throttled clients (seconds) 
    set ::throttled_expiration_time 3600 
     Throttle clients that exceed this number of requests/min 
    set ::throttle_activate_rate 400 
     Once throttled, limit clients to this number of requests/min 
    set ::throttle_limit_rate 40 
     Limit blacklisted clients to this number of requests/min 
    set ::blacklist_limit_rate 1 
     Message to be displayed when requests are dropped 
    set ::drop_msg "Too many requests, please try again later!" 
 } 
  
 when HTTP_REQUEST { 
    HTTP::header insert "X-RequestLimit-Class" "normal" 
    set client_ip [IP::remote_addr] 
    if {[matchclass [IP::remote_addr] equals $::RequestLimit_Whitelist]} { 
        Remote IP is in the whitelist, do not throttle. 
       HTTP::header replace "X-RequestLimit-Class" "whitelisted" 
       return 
    } 
     If we are still here, the client is not whitelisted 
  
     Initialize variables 
    set curr_time [clock seconds]  
    set blacklisted 0 
  
     Define the keys that we use to store the data in the session table 
     The key throttle_starttime_W.X.Y.Z tracks the start time (in seconds) when we started throttling the requests from the client 
    set throttle_timekey throttle_starttime_$client_ip 
     The key throttle_reqcount_W.X.Y.Z tracks the number of restricted requests received since the start throttling tracking time 
    set throttle_reqkey throttle_reqcount_$client_ip 
  
  
    set throttle_request_count [session lookup uie $throttle_reqkey] 
 log local0.info  "[IP::remote_addr] $throttle_request_count"    
    if {[matchclass [HTTP::host] equals $::RequestLimit_Blacklist]} { 
        Remote IP is in the blacklist, note that the client is blacklisted and start tracking their request rate. 
 set blacklisted 1 
  
       if {$throttle_request_count <= 0} { 
          session add uie $throttle_timekey [expr {$curr_time - 2}] [expr {$::throttled_expiration_time + 2}] 
          set throttle_request_count $::blacklist_limit_rate 
       } 
    } 
    if {$throttle_request_count > 0} { 
        If this is greater than 0, we are already throttling this client. 
  
        Increment and re-store the throttled request count 
       incr throttle_request_count 
       session add uie $throttle_reqkey $throttle_request_count $::throttled_expiration_time 
  
        Calculate elapsed time 
       set throttle_start_time [session lookup uie $throttle_timekey] 
       session add uie $throttle_timekey $throttle_start_time $::throttled_expiration_time 
       set elapsed_time [expr {$curr_time - $throttle_start_time}] 
       if {$elapsed_time < 60} { 
          set elapsed_time 60 
       } 
  
        Calculate number of requests/minute currently 
       set curr_rate [expr {$throttle_request_count / ($elapsed_time/60)} ] 
  
        Determine if this client is blacklisted or just throttled and set limit 
        rate, header, stats variable appropriately 
       if {$blacklisted} { 
          HTTP::header replace "X-RequestLimit-Class" "blacklisted" 
          set limit_rate $::blacklist_limit_rate 
          set statsvar "blacklisted_dropped_requests" 
       } else { 
          HTTP::header replace "X-RequestLimit-Class" "throttled" 
          set limit_rate $::throttle_limit_rate 
          set statsvar "throttled_dropped_requests" 
       } 
  
        Now see if they are still over their throttled rate.  If so, respond with an error. 
       if {$curr_rate > $limit_rate} { 
           Throttle this request 
          HTTP::respond 500 content $::drop_msg 
          STATS::incr Request_Limit_Stats $statsvar 
          log local0. " BLACK LIST ******** Site : [HTTP::host] RequestLimit: $client_ip: rate=$curr_rate, limit_rate=$limit_rate, dropping request!" 
       } 
       return 
    } 
  
     If we are still here, the client is not whitelisted, blacklisted, or 
     throttled.  Now we need to track them to make sure we don't need to start 
     thottling.  Are they accessing a restricted URL? 
 log local0.info "Antes do if" 
 log local0.info [HTTP::host] 
 if {[matchclass [HTTP::host] contains $::RequestLimit_URLlist]} { 
    log local0.info "Depois do if" 
        Define the keys to track the start time and request count for this client 
       set timekey starttime_$client_ip 
       set reqkey reqcount_$client_ip 
  
        Lookup the current request count 
       set request_count [session lookup uie $reqkey]     
       if {$request_count > 0} { 
           We have seen this client before.  Increment and re-store the request count. 
          incr request_count 
          session add uie $reqkey $request_count $::standard_expiration_time 
          HTTP::header insert "X-Request-Count" $request_count 
  
           Now calculate the elapsed time 
          set start_time [session lookup uie $timekey] 
          session add uie $timekey $start_time $::standard_expiration_time 
          set elapsed_time [expr {$curr_time - $start_time}] 
          if {$elapsed_time < 60} { 
             set elapsed_time 60 
          } 
          HTTP::header insert "X-Elapsed-Time" $elapsed_time 
  
           Now calculate the current request rate (for the restricted objects only) 
          set curr_rate [expr $request_count / ($elapsed_time / 60)] 
          HTTP::header insert "X-Current-Rate" $curr_rate 
           log local0. "[HTTP::host]([IP::remote_addr]): request count=$request_count, elapsed time=$elapsed_time, curr rate=$curr_rate" 
          if {$curr_rate >= $::throttle_activate_rate} { 
              Throttle this client after this request (add them to throttle list) 
             session add uie $throttle_timekey $start_time $::throttled_expiration_time 
             session add uie $throttle_reqkey $request_count $::throttled_expiration_time 
             log local0. "LimitReached **** : Site : [HTTP::host] RequestLimit: !THROTTLED! $client_ip: rate=$curr_rate, request_count=$request_count, elapsed_time=$elapsed_time" 
             STATS::incr Request_Limit_Stats throttled_clients 
          } 
       } else { 
           This is a new client, start tracking them for potential throttling 
          session add uie $timekey $curr_time [expr {$::standard_expiration_time + 2}] 
          session add uie $reqkey 1 $::standard_expiration_time 
       } 
    } 
 }

 

 

Our big ip are running version 9.4.6 Build 401.0 Final.

 

 

Thanks in advance!

 

 

Regards,
  • hoolio's avatar
    hoolio
    Icon for Cirrostratus rankCirrostratus
    Hi Ricardo,

     

     

    Do the two errors always occur together?

     

     

    I haven't read through the entire flow of the rule, but I would guess that the error occurs when no session table entry is found in this lookup:

     

     

    Now calculate the elapsed time

     

    set start_time [session lookup uie $timekey]

     

     

    You could add logic between the set of start_time and the expr command to log details of the request when no session table entry exists for $timekey ($start_time eq ""). It might help to enable and inspect the log output leading up to the error.

     

     

    Aaron
  • hoolio's avatar
    hoolio
    Icon for Cirrostratus rankCirrostratus
    I think one scenario I've seen the 'operation not supported' error on a header insert is when a request is being redirected in one part of iRule code and another part is trying to do a header insert. Do you have any other iRules enabled on the same VIP?

     

     

    Aaron
  • hoolio's avatar
    hoolio
    Icon for Cirrostratus rankCirrostratus
    You would want to add some kind of logic to either or both iRules to ensure that if you're redirecting the client to the maintenance page that you don't try to insert an HTTP header in the request. One option is to use a local variable to track that you're sent a redirect:

     
      Maint page lite rule 
     when HTTP_REQUEST priority 400 { 
      
      Run this rule event before other HTTP_REQUEST events so we can allow this rule to send a redirect 
     set redirected 0 
      
     ... 
      
         If the pool_testLB is down, redirect to the maintenance page 
        if { [active_members pool_testLB] < 1 } { 
           set redirected 1 
           HTTP::redirect "http://$host/maintenance" 
           return 
        } 
     

    Then in the HTTP request throttling rule, before you send a redirect or insert an HTTP header, you could check to see that $redirected is 0.

     
     if {$redirected==0} 
         Insert HTTP header or send client a redirect 
     } 
     

    Aaron