Created
December 8, 2022 23:15
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Author: Jeremy Melanson <[email protected]> | |
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | |
# | |
# 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