Skip to content

Instantly share code, notes, and snippets.

@zish
Created December 8, 2022 23:15
Show Gist options
  • Save zish/6782a6de7cabd488532d281f6e66f33d to your computer and use it in GitHub Desktop.
Save zish/6782a6de7cabd488532d281f6e66f33d to your computer and use it in GitHub Desktop.
Greylisting iRule for F5 load-balancers, to incrementally throttle requests to matching URIs.
# Author: Jeremy Melanson <1080872+zish@users.noreply.github.com>
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#
# This is an iRule for the F5 Big-IP family of load-balancers. It creates
# a "greylisting" mechanism for individual URIs, by incrementally delaying
# the time it # takes to send the backend's response.
#
# This can be particularly useful for API calls that might contain a higher
# potential for denial-of-service attacks, without restricting non-matching
# requests.
#
# Configuration requires a "String" Data Group to be defined. The name
# of the Data Group should be added ad the value of
# "static::throttle_data_group", declared below.
#
# Each item key is a URI value, with corresponding throttling settings.
# Throttling settings are configured as follows:
#
# "TO:[timeout_seconds],MR:[max_requests],IN:[delay_increment],MS:[delay_multiplier_ms]".
# [timeout_seconds]: The time in seconds when a recorded connection is removed.
# [max_requests]: Maxium number of requests before the a 503 error is returned.
# [delay_increment]: Length of applied delay increases by recorded connection count at this increement.
# [delay_multiplier_ms]: Milliseconds by which the delay is increased per delay_increment.
# [up-front_delay]: (OPTIONAL) Up-front delay to apply to all requests matching the given URI.
#
# Examples:
#
# TO:10,MR:10,IN:3,MS:200 -
# Delay (in milliseconds) will be Integer ([num_connections] / 3) * 200 .
# Over a 10 second period:
# 0-2 connections will result in no delay.
# 3-5 connections will result in 200ms delay.
# 6-8 connections will result in 400ms delay.
# 9 connections will result in 600ms delay.
# 10 connections or more will cause subsequent connections to receive the 503 error.
#
# TO:20,MR:30,IN:5,MS:100,UF:500 -
# Delay (in milliseconds) will be Integer ([num_connections] / 2) * 100 .
# Over a 20 second period:
# 0-4 connections will result in no delay.
# 5-9 connections will result in 100ms delay.
# 10-14 connections will result in 200ms delay.
# 15-19 connections will result in 300ms delay.
# 20-24 connections will result in 400ms delay.
# 25-29 connections will result in 500ms delay.
# 30 connections or more will cause subsequent connections to receive the 503 error.
#
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#--- This makes us log at a maximum interval of 1 entry every 2 seconds,
# unless the status is different than the one previously recorded.
#
proc log_status { client_table status log_msg } {
#-- Set an entry corresponding to "connlimit:[HTTP::uri]:[IP::client_addr]" in the "udt:lt" subtable.
# Timeout after 2 seconds. This ensures that we log no more than once every 2 seconds (unless state changes).
#
if { $status != [table lookup -subtable "udt:lt" $client_table] } {
table set -subtable "udt:lt" $client_table $status 2
log local0. $log_msg
}
}
#--- Some persistent settings for the iRule can be configured here..
#
when RULE_INIT {
#-- Set an HTML response to sent to clients who make a request while the VIP is over the max connection count
set static::block_response_content "<html>Maximum requests exceeded</html>\n"
set static::block_response_code 503
set static::throttle_data_group "b2b_delay_throttle"
}
when HTTP_REQUEST {
set uri_dg $static::throttle_data_group
#-- List of throttled URIs can be found in the "uri_delay_throttle" Data Group.
if { [matchclass [HTTP::uri] starts_with $uri_dg] } {
set up_front_delay 0
#- Pull the value from the Data Group. Format is "TO:[timeout_seconds],MR:[max_requests],IN:[delay_increment],MS:[delay_multiplier_ms]".
scan [class match -value [HTTP::uri] starts_with $uri_dg] TO:%d,MR:%d,IN:%d,MS:%d,UF:%d timeout max_requests t_increment multiplier up_front_delay
#- Individual tables are maintained for each URI/Client IP match.
set client_table "connlimit:[HTTP::uri]:[IP::client_addr]"
set client_port "[TCP::client_port]"
set conn_count [table keys -subtable $client_table -count]
#- Incrementally increase the amout of response delay.
set delay [expr {($conn_count / $t_increment) * $multiplier}]
set log_msg "Turi=[HTTP::uri] Tcip=[IP::client_addr] Tdlyms=$delay Tmaxdly=[expr {($max_requests / $t_increment) * $multiplier}] Tincr=$t_increment "
#- Apply up-front-delay, if the URI's settings include it.
if { $up_front_delay != 0 } {
set log_msg "$log_msg Tufdly=$up_front_delay "
after $up_front_delay
}
#- If we've hit the max request limit, don't add another entry to the table.
# Return a notification, if the maximum connection limit was exceeded. Don't bother the back-end.
# Rule /Common/uri_delay_throttle <HTTP_REQUEST>: turi=/jeremy tcip=10.10.60.211 Tdlyms=500 tcurconn=XXX tmaxdly=3000 tIncr=3 act=[throttle|drop]
#
if { $conn_count >= $max_requests } {
call log_status $client_table "$delay:drop" "$log_msg Tact=drop"
#- We still delay on denials.
after $delay
HTTP::respond $static::block_response_code content $static::block_response_content
} else {
#- Add port connection to "connlimit:[HTTP::uri]:[IP::client_addr]"
table set -subtable $client_table $client_port 1 $timeout
#- This is just for debugging the iRule, hence being commented out.
#set debug_response "Tablename: $client_table\n"
#set debug_response "$debug_response ConnCount: $conn_count\n"
#set debug_response "$debug_response Upfront Delay: $up_front_delay\n"
#set debug_response "$debug_response Timeout: $timeout\n"
#set debug_response "$debug_response Max Req: $max_requests\n"
#set debug_response "$debug_response Incr: $t_increment\n"
#set debug_response "$debug_response Mult: $multiplier\n"
#set debug_response "$debug_response Delay: $delay\n"
#set debug_response "$debug_response LogMsg: $log_msg\n\n"
#HTTP::respond 200 content $debug_response
#- Log and delay, but only of the delay value is nonzero.
if { $delay > 0 } {
call log_status $client_table "$delay:throttle" "$log_msg Tact=throttle"
#- Delay for calculated interval before servimg the page.
after $delay
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment