Forum Discussion
Universal Persistence with X-forwarder
Hi Experts
Can I use Universal persistence using x-forwarder with i-rule? I would have each x-forwarded IP stick to the same back-end pool member. Will this work? Can you please share code? Any other options to be enabled on the virtual server, I already have type set as standard vs.
Regards,
Sumanta.
A formatted version of the "Per VS" rate limiting. You can apply the same irule to all standard VS using UIE persistence.
when RULE_INIT { set static::maxReqs 3; set static::timeout 60; } when HTTP_REQUEST { set vs [URI::basename [virtual]] if { [HTTP::header exists "X-Forwarded-For"] } { set client_IP_addr [getfield [lindex [HTTP::header values "X-Forwarded-For"] 0] "," 1] } else { set client_IP_addr [IP::client_addr] } if { ([HTTP::method] eq "GET") and ([class match [string tolower [HTTP::uri]] ends_with $vs_URI_LIST_TO_LIMIT] ) } { whitelist if { [class match [IP::client_addr] equals $vs_ips_whitelist] }{ return } set getcount [table lookup -notouch "$vs_$client_IP_addr:[HTTP::uri]"] if { $getcount equals "" } { table set "$vs_$client_IP_addr:[HTTP::uri]" "1" $static::timeout $static::timeout } else { if { $getcount < $static::maxReqs } { table incr -notouch "$vs_$client_IP_addr:[HTTP::uri]" } else { reject } } } persist uie $clientip } when HTTP_RESPONSE { persist add uie $clientip }
Hi Sumanta,
to [persist uie] your clients by either using an existing X-Forwarded-For header (prefered method) or by using the [IP::client_addr] of the underlying TCP session (fallback method), you may take a look to the rather simple iRule below...
when HTTP_REQUEST { if { [set orig_ip [HTTP::header value "X-Forwarded-For"]] eq "" } { set orig_ip [getfield [IP::client_addr] "%" 1] } Tweak the persistence table timeout as needed. 1800 is the configured duration in seconds. persist uie $orig_ip 1800 }
Note: Issue a tmsh show /ltm persistence persist-records mode universal to list the currently active uie persistence records.
Cheers, Kai
Hi Yann
Can you please assist with the error debug I pasted? Sorry to bother you so much.
01070151:3: Rule [/Common/iRule_rate_limit] error: Unable to find value_list (URI_LIST_TO_LIMIT) referenced at line 7: [class match [string tolower [HTTP::uri]] ends_with URI_LIST_TO_LIMIT]
Regards,
Sumanta.
Hi,
You need to create this DATAGROUP :
tmsh create ltm data-group internal URI_LIST_TO_LIMIT type string records add { "/uri" }
This is a datagroup used to define the URIs you would like to filter.
This irule works on a Standard VS with HTTP profile applied. HTTP_REQUEST event doesn't works for L4 VS.
Hi Yann
Thanks, I'll create the data group. But will this prevent when I test using continuous telnet to IP/port 443, using a perl script? My intention is to limit connections below 20K, from each source IP, to that specific VS, running on port 443.
So, will your code only allow and rate limit URIs and reject everything else such as telnet sessions? The assumption is URIs are valid and legitimate and any other connection attempt on Layer 4 maybe considered as a DOS, right?
Regards,
Sumanta.
Hi Yann
What about this one? Is it same as your code logic or different?
From http://devcentral.f5.com/wiki/iRules.table.ashx
Limit each client IP address to 20K concurrent connections
when CLIENT_ACCEPTED { Max connections per client IP set limit 20000 Set a subtable name with a standard prefix and the client IP set tbl "connlimit:[IP::client_addr]" Use a key of the client IP:port set key "[IP::client_addr][TCP::client_port]" Check if the subtable has over X entries if { [table keys -subtable $tbl -count] >= $limit } { log local0. "[IP::client_addr]:[TCP::client_port]: Rejecting connection ([table keys -subtable $tbl -count] connections / limit: $limit)" reject } else { Add the client IP:port to the client IP-specific subtable with a max lifetime of 1260 seconds (30min), matched with L4 profile table set -subtable $tbl $key "ignored" 1260 log local0. "[IP::client_addr]:[TCP::client_port]: Allowing connection ([table keys -subtable $tbl -count] connections / limit: $limit)" } } when CLIENT_CLOSED { When the client connection is closed, remove the table entry table delete -subtable $tbl $key log local0. "[IP::client_addr]:[TCP::client_port]: Decrementing ([table keys -subtable $tbl -count] connections / limit: $limit)" }
You can modify the previously provided irule to this :
when RULE_INIT { set static::maxReqs 20000; set static::timeout 1800; } when CLIENT_ACCEPTED { set client_IP_addr [IP::client_addr] set getcount [table lookup -notouch "$client_IP_addr:[TCP::client_port]"] if { $getcount equals "" } { table set "$client_IP_addr:[TCP::client_port]" "1" $static::timeout $static::timeout } else { if { $getcount < $static::maxReqs } { table incr -notouch "$client_IP_addr" } else { reject } } }
In this case, the rate limit is really by connection, not only by IP address.
Hi Yann
I applied this, iRule is working, but the connection limit does not work at all. I tried to open 20K from a single source IP, but it exceeded 25K. See output below.
[root@VSESITE-99-LB01:Active:In Sync] config tmsh show ltm virtual SE_HTTPS_VS | grep Current Current Connections 26.5K 0 - Current SYN Cache 1 [root@VSESITE-99-LB01:Active:In Sync] config tmsh show ltm virtual SE_HTTPS_VS | grep Current Current Connections 27.4K 0 - Current SYN Cache 0
Any ideas? Does it at all work on F5 LTM? Or do we need any other module, like ASM/AFM for it? Usually TAC does not assist in custom iRules.
Regards,
Sumanta.
The iRule you have just tested allows "20000" connection establishments for each single TCP::client_port of a given [IP::client_addr] in a specific timeframe. Once the block has happend it block each TCP::client_port individually...
Note: See the iRule posted a few minutes ago, to get a TCP connection or HTTP request based rate limiter as needed...
Cheers, Kai
Hi Yann
Now the URI i rule with XFF is giving error while loading into the F5. Is there any syntax validation system online so that we can verify the rules before pasting?
Aug 17 11:50:12 VSESITE-99-LB01 err mcpd[8325]: 01070151:3: Rule [/Common/iRule_rate_limit-uie] error: /Common/iRule_rate_limit-uie:10: error: [missing a script after "if"][ ] Aug 17 11:50:37 VSESITE-99-LB01 err mcpd[8325]: 01070151:3: Rule [/Common/iRule_rate_limit-uie] error: /Common/iRule_rate_limit-uie:10: error: [missing a script after "if"][ ] Aug 17 11:51:51 VSESITE-99-LB01 err mcpd[8325]: 0107167d:3: Data publisher not found or not implemented when processing request (unknown request), tag (20594). Aug 17 11:52:51 VSESITE-99-LB01 err mcpd[8325]: 01070151:3: Rule [/Common/iRule_rate_limit-uie] error: /Common/iRule_rate_limit-uie:5: error: [parse error: missing close-brace][{ if { [HTTP::header exists X-forwarded-for] } { set client_IP_addr [getfield [lindex [HTTP::header values X-Forwarded-For] 0] "," 1] } set client_IP_addr [IP::client_addr] if { ([HTTP::method] eq "GET") and ([class match [string tolower [HTTP::uri]] ends_with URI_LIST_TO_LIMIT] ) } { set getcount [table lookup -notouch "$client_IP_addr:[HTTP::uri]"] if { $getcount equals "" } { table set "$client_IP_addr:[HTTP::uri]" "1" $static::timeout $static::timeout } else { if { $getcount < $static::maxReqs } { table incr -notouch "$client_IP_addr:[HTTP::uri]" } else { reject } } }] /Common/iRule_rate_limit-uie:6: error: [command is not valid in the current scope][if { [HTTP::header exists X-forwarded-for] } {
Hi Yann
I finally got the URI related iRule working as well. Only thing left is the testing for both virtual servers against DOS attacks. Since I am using real IP via XFF, so I lowered the maxReqs to 200.
ltm rule iRule_rate_limit-uie { when RULE_INIT { set static::maxReqs 200; set static::timeout 3600; } when HTTP_REQUEST { if { [HTTP::header exists X-Forwarded-For] } { set client_IP_addr [getfield [lindex [HTTP::header values X-Forwarded-For] 0] "," 1] } else { set client_IP_addr [IP::client_addr] } if { ([HTTP::method] eq "GET") and ([class match [string tolower [HTTP::uri]] ends_with URI_LIST_TO_LIMIT] ) } { if { [class match [IP::client_addr] equals ips_whitelist] }{ return } set getcount [table lookup -notouch "$client_IP_addr:[HTTP::uri]"] if { $getcount equals "" } { table set "$client_IP_addr:[HTTP::uri]" "1" $static::timeout $static::timeout } else { if { $getcount < $static::maxReqs } { table incr -notouch "$client_IP_addr:[HTTP::uri]" } else { reject } } } } }
Hi Sumanta,
you may try one of the iRules below. They will either track the HTTP request or TCP connections per timeframe by just using a single table call. The expected RAM consumption should be very little...
iRule to track the TCP connection / timeframe
when RULE_INIT { set static::maxReqs 20000; set static::timeout 1800; } when CLIENT_ACCEPTED { if { [set count [table incr -mustexist "DoS_[IP::client_addr]" "1"]] ne "" } then { if { $count < $static::maxReqs } then { Allow the request } elseif { $count == $static::maxReqs } then { log -noname local0.debug "DoS Protection: Client \"[IP::client_addr]\" has reached its HTTP request limits. Blocking the client for the next \"[table lifetime -remaining "DoS_[IP::client_addr]"]\" seconds." reject } else { reject } } else { table set "DoS_[IP::client_addr]" "1" indefinite $static::timeout } }
iRule to track the HTTP request / timeframe
when RULE_INIT { set static::maxReqs 20000; set static::timeout 1800; } when HTTP_REQUEST { if { [set count [table incr -mustexist "DoS_[IP::client_addr]" "1"]] ne "" } then { if { $count < $static::maxReqs } then { Allow the request } elseif { $count == $static::maxReqs } then { log -noname local0.debug "DoS Protection: Client \"[IP::client_addr]\" has reached its HTTP request limits. Blocking the client for the next \"[table lifetime -remaining "DoS_[IP::client_addr]"]\" seconds." reject } else { reject } } else { table set "DoS_[IP::client_addr]" "1" indefinite $static::timeout } }
Cheers, Kai
Hi Kai
Thanks for the updated codes. I got some reference from DC and used the below code. Till now it appears to work fine and meet my requirements.
when CLIENT_ACCEPTED { set tbl "connlimit:[IP::client_addr]" set key "[TCP::client_port]" table set -subtable $tbl $key "ignored" 180 if { [table keys -subtable $tbl -count] > 30000 } { table delete -subtable $tbl $key event CLIENT_CLOSED disable reject } else { set timer [after 1260 -periodic { table lookup -subtable $tbl $key }] } } when CLIENT_CLOSED { after cancel $timer table delete -subtable $tbl $key } }
Let me see how far it lasts the DOS attack tests. Probably, I'll get to test your code later on.
Regards,
Sumanta.
Hi Sumanta,
This code implements a tracking of concurrent TCP sessions from a single client. It counts every new TCP session in a subtable, refreshes the table entries on a periodic interval and finally removes the table entries if a session is closed.
In my optinion this code shouldn't be used to track high numbers of concurrent connections, since each single TCP connection will be stored in the session table (requires RAM) and will be refreshed on a periodic interval (requires CPU). In addition this iRule seems to have way to agressive periodic refresh intervals for maintaning the existing session table entires!? (aka. every 1280 milliseconds for every single TCP connection)
A maximum of 30.000 concurrent TCP connections for each single spammer, will cause your LTM to execute additional ~24.000 table calls each second... Multiply this during a typical DDoS scenario and it will even have the potential to create a nuclear fission in your data center... :-(
In the end it strongly depends what kind of DoS protection are you looking for (e.g. tracking of conncurrent connections, new connections per timeframe, tracking on each individual URI, etc.)?
Cheers, Kai
Hi Kai
Thanks for your feedback. I didn't know that timer was in millisec. I matched with L4 profile timer values at 1260 sec. Probably, I'll use your code and test.
Regards,
Sumanta.
Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com