Forum Discussion

no-idea-what-im's avatar
no-idea-what-im
Icon for Nimbostratus rankNimbostratus
Jul 02, 2014

Exponential backoff iRule and True-Client-IP HTTP header

Hi,

 

I am working on using the exponential backoff iRule (https://devcentral.f5.com/wiki/irules.POST-Request-Exponential-Backoff.ashx) to help with some suspicious login attempts.

 

The issue I am running into, is that in order to block the client IP, I need to adjust the example iRule to use a custom x-forwarded-for header (true-client-ip). The iRule currently uses the CLIENT_ACCEPT IP::remote_addr to build the session ID, but I need it to use the HTTP::header "True-Client-IP" header, which is not avail in CLIENT_ACCEPTED.

 

Below is the example exponential backoff code, with my comments preceded with a . I am new to iRules and programming, so please accept my apologies in advance.

 

Any help or ideas would be greatly appreciated.

 

when RULE_INIT {
  set static::min_lockout 2
  set static::max_lockout 86400
  set static::logging 1
}

when CLIENT_ACCEPTED {
  set static::session_id "[IP::remote_addr]:[TCP::remote_port]" 
   Ideally, the above line would read something like 
  set static::session_id "[HTTP::header "True-Client-IP"]:[TCP::remote_port]" , but does not work
  set static::state_table "[virtual name]-exp-backoff-state"
}

when HTTP_REQUEST {
  if { [HTTP::method] eq "POST" } {
    set prev_attempts [table lookup -subtable $static::state_table $static::session_id]

    if { $prev_attempts eq "" } { set prev_attempts 0 }

     exponential backoff - http://en.wikipedia.org/wiki/Exponential_backoff
    set new_lockout [expr (1 << ($prev_attempts-1))]

    if { $new_lockout > $static::max_lockout } {
      set new_lockout $static::max_lockout
    } elseif { $new_lockout < $static::min_lockout } {
      set new_lockout $static::min_lockout
    }

    table incr -subtable $static::state_table $static::session_id
    table timeout -subtable $static::state_table $static::session_id $new_lockout

    if { $static::logging > 0 } {
      log local0. "POST request ([expr ($prev_attempts+1)]) from $static::session_id received during lockout period, updating lockout to ${new_lockout}s"
    }

    if { $prev_attempts > 1 } {
       alternatively respond with content - https://devcentral.f5.com/wiki/iRules.HTTP__respond.ashx
      set response "Hold up there!Hold up there!You're"
      append response " posting too quickly. Wait a few moments are try again."

      HTTP::respond 200 content $response
    }
  }
}

9 Replies

  • i am not sure why he uses static global variable for session_id and also include port into it. can you try to create session_id containing only ip in HTTP_REQUEST instead?

      set session_id "[HTTP::header "True-Client-IP"]"
    
  • If I may elaborate, you can move all of the logic in the CLIENT_ACCEPTED event into the HTTP_REQUEST event, and then you have access to the HTTP headers. The beauty of these OSI-based events is that data collected in lower level events is available in upper level events. You can't see HTTP headers in CLIENT_ACCEPTED, because you're still at layer 4. But you can see IP and port information in HTTP_REQUEST because you're at layer 7 and all of the layer 4 data is accessible.

     

    You also should not be using global static variables to hold state_table and session_id values. Those should just be regular variables.

     

  • I appreciate all of the help. It has given me some great ideas.

    Here is what I have so far, in case anyone is interested. It seems to be working for me in my lab environment, but much more testing will need to happen.

    I am sure it can be cleaned up and optimized, so feel free to do so if you are so inclined.

    when RULE_INIT {
      set static::min_lockout 2
      set static::max_lockout 300
      set static::logging 1
    }
    
    priority 100
    when HTTP_REQUEST {
      if { [HTTP::header exists "True-Client-IP"] } {
        set trueclientip [HTTP::header "True-Client-IP"]
    } else {
       set trueclientip [IP::client_addr]
          }
       set static::session_id "$trueclientip"
       set static::state_table "[virtual name]-exp-backoff-state"
      }
    
    priority 200
    when HTTP_REQUEST {
        if { [HTTP::uri] contains "signin" and [HTTP::method] contains "POST"} {
        set prev_attempts [table lookup -subtable $static::state_table $static::session_id]
    
        if { $prev_attempts eq "" } { set prev_attempts 0 }
    
         exponential backoff - http://en.wikipedia.org/wiki/Exponential_backoff
        set new_lockout [expr (1 << ($prev_attempts-1))]
    
        if { $new_lockout > $static::max_lockout } {
          set new_lockout $static::max_lockout
        } elseif { $new_lockout < $static::min_lockout } {
          set new_lockout $static::min_lockout
        }
    
        table incr -subtable $static::state_table $static::session_id
        table timeout -subtable $static::state_table $static::session_id $new_lockout
    
        if { $static::logging > 0 } {
          log local0. "signin POST request ([expr ($prev_attempts+1)]) from $static::session_id received during lockout period, updating lockout to ${new_lockout}s"
        }
       }
    }
    
  • set static::session_id "$trueclientip"

    set static::state_table "[virtual name]-exp-backoff-state"

    i do not think static global variable should be used especially for session_id. as Kevin mentioned, they should be just regular variable instead.

    A namespace for creating global variables to hold constant values in a CMP-compatible fashion. This namespace is intended only for use with values that will not change. While it is possible to alter the value of a global variable in the static namespace, this value will not be propagated to other CMP nodes in the system. Therefore, doing so may lead to unexpected results.
    

    static wiki

    https://devcentral.f5.com/wiki/iRules.static.ashx
    • no-idea-what-im's avatar
      no-idea-what-im
      Icon for Nimbostratus rankNimbostratus
      As I mentioned, I am not much of a coder and new to iRules on top of that. Can you point me to an example of how to write that as a regular variable? Would it be something as simple as set session_id "$trueclientip" ? I am basically reusing the code from the exponential backoff example that was given and that static global variable is what was in the example. Thank you again.
  • set static::session_id "$trueclientip"

    set static::state_table "[virtual name]-exp-backoff-state"

    i do not think static global variable should be used especially for session_id. as Kevin mentioned, they should be just regular variable instead.

    A namespace for creating global variables to hold constant values in a CMP-compatible fashion. This namespace is intended only for use with values that will not change. While it is possible to alter the value of a global variable in the static namespace, this value will not be propagated to other CMP nodes in the system. Therefore, doing so may lead to unexpected results.
    

    static wiki

    https://devcentral.f5.com/wiki/iRules.static.ashx
    • no-idea-what-im's avatar
      no-idea-what-im
      Icon for Nimbostratus rankNimbostratus
      As I mentioned, I am not much of a coder and new to iRules on top of that. Can you point me to an example of how to write that as a regular variable? Would it be something as simple as set session_id "$trueclientip" ? I am basically reusing the code from the exponential backoff example that was given and that static global variable is what was in the example. Thank you again.
  • It'd be as simple as doing this at the top of the HTTP_REQUEST event:

    set session_id "[IP::remote_addr]:[TCP::remote_port]"    
    
    set state_table "[virtual name]-exp-backoff-state"