Time based iRule example
Problem this snippet solves: Here's a simple example of taking action in an iRule based on a time window. We use the clock scan and clock seconds commands to convert a timestamp in a user-friendly format to UTC (time since the Unix epoch of 0.0.1970) and check it against the current time. Tcl clock wiki page: http://www.tcl.tk/man/tcl8.4/TclCmd/clock.htm Contribution Hoolio plus help with others in the forums Code : when RULE_INIT { # Start of maintenance window in YYYY-mm-dd HH:MM format set static::start_date "2011-05-29 18:45" # End of maintenance window in YYYY-mm-dd HH:MM format set static::end_date "2011-05-29 18:50" # Convert start/end times to seconds from the epoch for easier date comparisons set static::start [clock scan $static::start_date] set static::end [clock scan $static::end_date] } when CLIENT_ACCEPTED { # Get the current time in seconds since the Unix epoch of 0-0-1970 set now [clock seconds] # Check if the current time is between the start and end times if {$now > $static::start and $now < $static::end}{ pool MAINT_POOL } # Default action is to use the virtual server default pool }4.1KViews2likes4CommentsSNAT pool persistence
Problem this snippet solves: This example shows how select the same SNAT address from the SNAT pool for a given client IP address over multiple connections without tracking the selection in memory. The crc32 hash of the client IP address is used to select a SNAT address. See the iRule comments for further details. Note: If this iRule is utilized on a virtual server that uses OneConnect, set the OneConnect profile's source mask to 255.255.255.255 to ensure the right SNAT pool IP address is used. To work around an issue where the SNAT pick may be lost (ID374067), apply SNAT for each request. For instance, if using HTTP and OneConnect, you could change CLIENT_ACCEPTED to HTTP_REQUEST. Contact F5 Support references ID374067 for additional information. Code : # For v10 and higher, use the static namespace to store the SNAT addresses. Just change "set static::snatpool_name my_snat_pool" in the RULE_INIT event to the name of the SNAT pool for this virtual server. when RULE_INIT { # The only configuration needed is to set the name of the SNAT pool as $static::snatpool_name # Configure the name of the SNAT pool here set static::snatpool_name "my_snat_pool" # Hide the members command from the iRule parser (BZ381099 comment 7) set static::members_cmd "members -list $static::snatpool_name" # Clear any pre-existing array of the same name unset -nocomplain static::snat_ips # Initialize a counter for the number of SNAT pool members set static::i 0 # Loop through the SNAT pool members and add them to an array for faster access # If the SNAT pool is modified, the RULE_INIT code needs to be re-run to re-read the SNAT pool # Make a simple change like adding a space to a comment to force a re-run of RULE_INIT. foreach static::snat_ip [eval $static::members_cmd] { set static::snat_ips($static::i) [lindex $static::snat_ip 0] incr static::i } # Save the number of SNAT IPs to avoid getting the count on every connection set static::array_size [array size static::snat_ips] log local0. "Loaded $static::array_size SNAT IPs from $static::snatpool_name: [array get static::snat_ips]" # Clear the variables we will not use anymore unset static::snatpool_name static::members_cmd static::i static::snat_ip } when HTTP_REQUEST { # Use HTTP_REQUEST instead of CLIENT_ACCEPTED to avoid issue from BZ374067/SOL14098 # Calculate the crc32 checksum of the client IP # Use the modulo of the checksum and the number of SNAT IPs to choose from to select a SNAT IP snat $static::snat_ips([expr {[crc32 [IP::client_addr]] % $static::array_size}]) } # Another variation using the source port of the client to determine the SNAT (Tested on v10, v11) - Bhattman when RULE_INIT { # The only configuration needed is to set the name of the SNAT pool as $static::snatpool_name # Configure the name of the SNAT pool here set static::snatpool_name "my_snat_pool" # Hide the members command from the iRule parser (BZ381099 comment 7) set static::members_cmd "members -list $static::snatpool_name" # Clear any pre-existing array of the same name unset -nocomplain static::snat_ips # Initialize a counter for the number of SNAT pool members set static::i 0 # Loop through the SNAT pool members and add them to an array for faster access # If the SNAT pool is modified, the RULE_INIT code needs to be re-run to re-read the SNAT pool # Make a simple change like adding a space to a comment to force a re-run of RULE_INIT. foreach static::snat_ip [eval $static::members_cmd] { set static::snat_ips($static::i) [lindex $static::snat_ip 0] incr static::i } # Save the number of SNAT IPs to avoid getting the count on every connection set static::array_size [array size static::snat_ips] log local0. "Loaded $static::array_size SNAT IPs from $static::snatpool_name: [array get static::snat_ips]" # Clear the variables we will not use anymore unset static::snatpool_name static::members_cmd static::i static::snat_ip } when HTTP_REQUEST { # Use HTTP_REQUEST instead of CLIENT_ACCEPTED to avoid issue from BZ374067/SOL14098 # Calculate the crc32 checksum of the client IP # Use the modulo of the checksum and the number of SNAT IPs to choose from to select a SNAT IP snat $static::snat_ips([expr {[crc32 [IP::client_addr][TCP::remote_port]] % $static::array_size}]) } # For v9, use a local array to store the SNAT addresses: when CLIENT_ACCEPTED { # Use a local array to configure SNAT addresses. # These addresses need to be defined in a SNAT pool to ensure TMM sends gratuitous ARPs during a failover. # In this example, we use 10 addresses of any subnet. You will probably want to change these to be in the same subnet. # Any number of addresses can be used. set snat_ips(0) 1.1.1.1 set snat_ips(1) 2.2.2.2 set snat_ips(2) 3.3.3.3 set snat_ips(3) 4.4.4.4 set snat_ips(4) 5.5.5.5 set snat_ips(5) 5.5.5.5 set snat_ips(6) 6.6.6.6 set snat_ips(7) 7.7.7.7 set snat_ips(8) 8.8.8.8 set snat_ips(9) 9.9.9.9 # Calculate the crc32 checksum of the client IP # Use the modulo of the checksum and the number of SNAT IPs to choose from to select a SNAT IP snat $snat_ips([expr {[crc32 [IP::client_addr]] % [array size snat_ips]}]) } # For v9, use a local array to store the SNAT addresses using client's source port - Bhattman when CLIENT_ACCEPTED { # Use a local array to configure SNAT addresses. # These addresses need to be defined in a SNAT pool to ensure TMM sends gratuitous ARPs during a failover. # In this example, we use 10 addresses of any subnet. You will probably want to change these to be in the same subnet. # Any number of addresses can be used. set snat_ips(0) 1.1.1.1 set snat_ips(1) 2.2.2.2 set snat_ips(2) 3.3.3.3 set snat_ips(3) 4.4.4.4 set snat_ips(4) 5.5.5.5 set snat_ips(5) 5.5.5.5 set snat_ips(6) 6.6.6.6 set snat_ips(7) 7.7.7.7 set snat_ips(8) 8.8.8.8 set snat_ips(9) 9.9.9.9 # Calculate the crc32 checksum of the client IP # Use the modulo of the checksum and the number of SNAT IPs to choose from to select a SNAT IP snat $snat_ips([expr {[crc32 [IP::client_addr][TCP::remote_port]] % [array size snat_ips]}]) } # Here is a simple test iRule that shows the distribution of the SNAT addresses selected: when RULE_INIT { #========================================================================================= # Logic Test only in RULE_INIT # Use a local array to configure SNAT addresses. # These addresses need to be defined in a SNAT pool to ensure TMM sends gratuitous ARPs during a failover. set snat_ips(0) 1.1.1.1 set snat_ips(1) 2.2.2.2 set snat_ips(2) 3.3.3.3 set snat_ips(3) 4.4.4.4 set snat_ips(4) 5.5.5.5 set snat_ips(5) 5.5.5.5 set snat_ips(6) 6.6.6.6 set snat_ips(7) 7.7.7.7 set snat_ips(8) 8.8.8.8 set snat_ips(9) 9.9.9.9 # Test the distribution of SNAT addresses by tracking the number of hits for each SNAT pool member. # Initialize one variable per SNAT array member for {set j 0} {$j < [array size snat_ips]} {incr j}{ set [set j] 0 } # Loop through a subnet of host addresses for {set i 0} {$i < 256} {incr i}{ # Set a test client IP set ip 10.11.12.$i # Calculate the crc32 checksum of the client IP set hash [crc32 $ip] #log local0. "crc32: $hash" # Use the crc32 hash of the client IP to select a SNAT IP from the array set ip_number [expr {[crc32 $ip] % [array size snat_ips]}] #log local0. "\$ip: $ip, \$snat_ips($ip_number): $snat_ips($ip_number)" # Track which SNAT array member was selected incr $ip_number #log local0. "$ip_number, [set $ip_number]" } log local0. "Results for distribution across the SNAT array members:" for {set j 0} {$j < [array size snat_ips]} {incr j}{ log local0. "$j: [set $j]" } } # Sample log output: : Results for distribution across the SNAT array members: : 0: 27 : 1: 28 : 2: 26 : 3: 29 : 4: 27 : 5: 23 : 6: 20 : 7: 20 : 8: 28 : 9: 282.9KViews1like10CommentsVirtual server connection limit with HTTP response
Problem this snippet solves: This rule allows administrators to configure a maximum TCP connection limit for a virtual server. When the limit is reached, LTM sends a static HTML response. The iRule maintains a count of active connections using a global variable. The rule assumes a connection count of zero to start with. To ensure the iRule starts with a valid connection count, it would be advisable to add the iRule to the virtual server when there are no active connections to the virtual server. Code : when RULE_INIT { # Set a global max for number of concurrent TCP connections set ::max_connections 2 # Set an HTML response to sent to clients who make a request while the VIP is over the max connection count set ::html_content "over limit" # Print debug messages to /var/log/ltm? 1=yes, 0=no set ::debug 1 # Initialize a counter for active connections (don't modify this) set ::active_connections 0 } when HTTP_REQUEST { # If we're over the limit for this connection, send a response if {$::active_connections > $::max_connections}{ # Send a response HTTP::respond 200 content $::html_content # Close the connection TCP::close # Log a message to /var/log/ltm if debug is enabled if {$::debug}{log local0. "Over limit (current/max: $::active_connections/$::max_connections). Sent response to [IP::client_addr]"} # We're not over the limit, so check if this is the first HTTP request on the TCP connection. } elseif {[HTTP::request_num] == 1}{ set validrequest 1 # Increment the TCP connection count. incr ::active_connections 1 } } when CLIENT_CLOSED { # A connection was closed, so decrement the global counter if {$validrequest == 1}{ incr ::active_connections -1 } }2.2KViews0likes8Commentsvirtual server connection rate limit with tables
Problem this snippet solves: Summary: Limit the rate of connections to a virtual server to prevent overloading of pool members iRule Methodology: For a given time interval, ensure that the rate of connections doesn't exceed a configurable threshold. Uses the table command to track number of new connections per second. Code : # # Name: virtual server connection rate limiting rule # # Purpose: Limit the rate of connections to a virtual server to prevent overloading of pool members # # Methodology: For a given time interval, ensure the rate of connections doesn't exceed a configurable threshold # # Requirements: # - LTM v10.1 or higher to use the table command # - IP address datagroup containing a whitelist of IPs/subnets that should not be limited/counted (vsratelimit_whitelist_class) # - Addition of this iRule to the virtual server. # # Warning: Due to a bug (fixed in 10.2.2), TMM can crash if this iRule is reconfigured or removed # from the virtual server while traffic is pending. For more info see: # Changing an iRule assignment on a virtual server may cause TMM to panic. # http://support.f5.com/kb/en-us/solutions/public/11000/100/sol11149.html # when RULE_INIT { # Log debug to /var/log/ltm? 1=yes, 0=no. set static::conn_debug 1 # Maximum connection rate (new connections / time interval) # To avoid the bug described in SOL11149, you could move this configuration to a datagroup set static::conn_rate 10 # Time interval (in seconds) # This shouldn't need to be changed. # We track connections opened in the last X seconds set static::interval 1 log local0. "Configured to enforce a rate of [expr {$static::conn_rate / $static::interval}]\ cps ($static::conn_rate connections / $static::interval second)" # IP datagroup name which contains IP addresses/subnets to not track/rate limit set static::whitelist_class vsratelimit_whitelist_class # Generate a subtable name unique to this iRule and virtual server # We use the virtual server name with this prefix (vsratelimit_ ) set static::tbl "vsratelimit" } when CLIENT_ACCEPTED { # Don't process any of this iRule if the client IP is in the whitelist datagroup if {[class match [IP::client_addr] equals vsratelimit_whitelist_class]}{ # Exit this event from this iRule return } # Track this connection in the subtable using the client IP:port as a key set key "[IP::client_addr]:[TCP::client_port]" # Save the virtual server-specific subtable name (vsratelimit_ ) set tbl ${static::tbl}_[virtual name] # Check if we're under the connection limit for the virtual server set current [table keys -subtable $tbl -count] if { $current >= $static::conn_rate } { # We're over the rate limit, so reset the connection if { $static::conn_debug }{ log local0. "$key: Connection to [IP::local_addr]:[TCP::local_port]\ ([virtual name]). At limit, rejecting (current: $current / max: $static::conn_rate)" } #reject # apachebench stops when it gets a reset, so you can test with TCP::close to send a FIN and close the connection cleanly. TCP::close } else { # We're under the virtual server connection rate limit, # so add an entry for this client IP:port with a lifetime of X seconds table set -subtable $tbl $key " " indefinite $static::interval if { $static::conn_debug }{ log local0. "$key: Connection to [IP::local_addr]:[TCP::local_port]\ ([virtual name]). Under limit, allowing (current: [table keys -subtable $tbl -count] / max: $static::conn_rate)" } } }2.1KViews0likes4CommentsHTTP Forward Proxy - v3.2
Problem this snippet solves: = This iRule will act as a forward proxy for HTTP requests. Set the virtual server that this iRule is connected to as the proxy server for your web browser. It can handle any HTTP request and also HTTPS requests through the CONNECT method. Contribution There have been several contributors to this iRule over the years, but I believe a bulk of the work was done by Pat Chang. Feel free to update this if you contributed at some stage. Code : ## HTTP_Proxy_v3.2 ## ## This iRule will act as a forward proxy for HTTP requests ## Set the virtual server that this iRule is connected to as the proxy ## server for your web browser. This can handle any HTTP request and also ## HTTPS requests through the CONNECT method. ## ## CMP compatible: Yes ## ## This rule requires: ## Just modify the DNS server and apply it to a Virtual Server ## ## This rule developed on: ## TMOS v11.2 (though should be backward compatible to 10.1) ## LTM ## ## Sizing data: ## Not collected when RULE_INIT { set static::DEBUG 1 set static::dns "139.134.5.51" } when HTTP_REQUEST { set DNS_ERROR 0 set HTTP_ERROR503_DNS " Error: Host not found. Please check the website address. HTTP_Proxy/v2.0 " # This is required to avoid errors with session statements later - change test_pool to whatever exists on your system # This pool is never really used #pool gw_pool set method_connect [expr { [HTTP::method] eq "CONNECT"} ] set proto [string tolower [getfield [HTTP::uri] ":" 1]] if { $static::DEBUG } { log local0.info "HTTP::method: [HTTP::method]: HTTP::request : [HTTP::request]"} if { $method_connect } { set host [string tolower [getfield [HTTP::uri] ":" 1]] set port [getfield [HTTP::uri] ":" 2] if {$port eq ""}{ set port 443 } set new_path [HTTP::uri] } else { set hostport [findstr [HTTP::uri] "//" 2 "/"] set host [string tolower [getfield $hostport ":" 1]] set port [getfield $hostport ":" 2] set prefix "$proto://$hostport" set new_path [findstr [HTTP::uri] $prefix [string length $prefix] ] if {$port eq ""}{ set port 80 } # I forgot the next line at one point, and guess what? A lot of pages did load just fine!!! including google.com/new HTTP::uri $new_path } # TODO: what other headers should be removed or edited? HTTP::header remove "Proxy-Connection" # prepare access log: # 68.9.59.127 - - [16/May/2008:23:40:18 -0400] "GET /yourshows/8/editorLogin.swf HTTP/1.1" 200 86686 set request_log_line "[HTTP::request_num] [HTTP::host] [IP::remote_addr] \"[HTTP::method] " set request_log_line "$request_log_line $new_path [HTTP::version]\"" if { ! [catch {IP::addr $host mask 255.255.255.255}] } { set got_ipaddress 1 set _ipaddress $host } else { # set _ipaddress [session lookup uie $host] set _ipaddress [table lookup -subtable dnscache $host] set got_ipaddress [expr { $_ipaddress ne "" }] } if { $method_connect } { # TODO: We really should connect first before sending 200 code, but it works this way. TCP::respond "HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\n\r\n" TCP::payload replace 0 [TCP::payload length] "" TCP::collect HTTP::disable discard if { $got_ipaddress } { node $_ipaddress $port } else { set ips [RESOLV::lookup @$static::dns $host] if { $static::DEBUG } {log local0.info "$host NAME::response: $ips"} set _ipaddress [lindex $ips 0] if { $_ipaddress eq "" } { set DNS_ERROR 1 if { $static::DEBUG } { log local0.info "DNS resolution failed for hostname: $host. Sending HTTP response error" } catch {TCP::respond "HTTP/1.1 503 Error: Host not found. Please check the website address. HTTP_Proxy/v2.0 \r\n\r\n"} } else { # Add to cache with 60 second timeout # session add uie $host $_ipaddress 60 table add -subtable dnscache $host $_ipaddress 60 node $_ipaddress $port } } } elseif { $got_ipaddress } { if { $static::DEBUG } { log local0.info "Got an IP address, using it: $host/$_ipaddress" } node $_ipaddress $port } else { if { $static::DEBUG } { log local0.info "Got hostname: $host, calling resolver..." } set ips [RESOLV::lookup @$static::dns $host] if { $static::DEBUG } {log local0.info "$host NAME::response: $ips"} set _ipaddress [lindex $ips 0] if { $_ipaddress eq "" } { set DNS_ERROR 1 if { $static::DEBUG } { log local0.info "DNS resolution failed for hostname: $host. Sending HTTP response error" } catch {TCP::respond "HTTP/1.1 503 Error: Host not found. Please check the website address. HTTP_Proxy/v2.0 \r\n\r\n"} } else { # Add to cache with 60 second timeout # session add uie $host $_ipaddress 60 table add -subtable dnscache $host $_ipaddress 60 node $_ipaddress $port } } } when HTTP_RESPONSE { set request_log_line "$request_log_line [HTTP::status] [HTTP::payload length]" # Via = "Via" ":" 1#( received-protocol received-by [ comment ] ) # received-protocol = [ protocol-name "/" ] protocol-version # protocol-name = token # protocol-version = token # received-by = ( host [ ":" port ] ) | pseudonym # pseudonym = token # Via header value hard coded - change as desired HTTP::header insert "Via" "F5ProxyiRule" # log 192.168.74.123 local0.info "ACCESS: $request_log_line" } when SERVER_CONNECTED { if { $static::DEBUG } { log local0.info "Server connected." } if { $method_connect } { TCP::payload replace 0 [TCP::payload length] "" if { $static::DEBUG } { log local0.info "Sending client data: [b64encode [clientside { TCP::payload }] ]" } # TODO: without this disable, we get: # http_process_state_prepend - Invalid action EV_EGRESS_DATA during ST_HTTP_PREPEND_HEADERS (Client side: vip=http_proxy_vs profile=http addr=71.6.193.125 port=443 vlan=0) clientside { HTTP::disable discard } TCP::respond [clientside { TCP::payload }] clientside { TCP::payload replace 0 [string length [TCP::payload]] "" TCP::release } } } when SERVER_CLOSED { # TODO: did it work, or did it not? if { $static::DEBUG } { log local0.info "Server closed." } } when LB_FAILED { set HTTP_ERROR503_SRV " Error: Connection to server failed. HTTP_Proxy/v2.0 " if { $method_connect } { if { $static::DEBUG } { log local0.info "Server connection failed. Closing client connection."} clientside {TCP::close} } else { if { $DNS_ERROR } { if { $static::DEBUG } { log local0.info "Server connection failed, DNS error."} } else { if { $static::DEBUG } { log local0.info "Server connection failed. Sending HTTP response error"} clientside { HTTP::release # TODO: This raises an error when respond was already called, in NAME_RESOLVED for example, despite the if and the catch!!!! catch {TCP::respond "HTTP/1.1 503 Error: Host not found. Please check the website address. HTTP_Proxy/v2.0 \r\n\r\n"} } } } } when CLIENT_CLOSED { if { $static::DEBUG } { log local0.info "client closed." } }2KViews1like10CommentsSelect pool member based on HTTP query string parameter
Problem this snippet solves: The iRule allows clients to select a pool member based on a parameter set in the HTTP query string. The manual selection can be specified on any URI by appending member=1 to the query string. On responses, a session cookie is set to ensure the client requests are manually persisted to the same server as long as the browser is kept open. To have LTM clear the cookie, the member number can be set in the URI to 0. For versions lower than 10.0, you could use a shell script run nightly to create a datagroup containing all of the pool members, regardless of state. This could be done on any LTM version. Then replace [active_members -list ..] with [lindex [lsort $::my_pool_members_class] $member_num] for 9.x or [lindex [lsort [class -get my_pool_members_class]] $member_num] for 10.x. Code : # # Select pool member based on HTTP query string parameter # v0.1 - 2010-03-15 - Aaron Hooley - hooleylists at gmail dot com # v0.2 - 2010-12-29 - Aaron Hooley - hooleylists at gmail dot com - used 'members' command added in 10.0 instead of active_members # # - Allow a client to select the pool member based on a parameter set in the query string # - Uses a digit to select the 0th, 1st, 2nd, etc pool member from the active members of a pool # # See below for an alternative method for statically mapping 1 to the first pool membrer regardless of state. # - Sets a session cookie so that the client will be persisted to the same pool member for the duration # that the browser is kept open. # - If the parameter is set to 0, then remove the cookie and load balance the request # # Requires 10.0+ to use members -list commnad # http://devcentral.f5.com/s/wiki/default.aspx/iRules/members # # For versions lower than 10.0 you could use a shell script run nightly to create a datagroup containing # all of the pool members. This could be done on any LTM version. # Then replace [members -list] with [lindex [lsort $::my_pool_members_class] $member_num] for 9.x # or [lindex [lsort [class -get my_pool_members_class]] $member_num] for 10.x # when HTTP_REQUEST { # Log debug to /var/log/ltm? 1=yes, 0=no. set member_debug 1 # Name of the URI parameter name used to manually select a specific pool member # Clients can append the member parameter to a query string in the format of: # www.example.com/index.html?member=2 # where member is the parameter name set member_param "member" # Track whether a member has been found in the query string or cookie set member_num "" # Debug logging if {$member_debug}{ log local0. "[IP::client_addr]:[TCP::client_port]: Debug enabled on [HTTP::method] request for [HTTP::host][HTTP::uri]" log local0. "[IP::client_addr]:[TCP::client_port]: Members for pool [LB::server pool] (sorted):\ [lsort [members -list [LB::server pool]]]" } # Check if query string contains "member=" before evaluating the value # Could replace this with a check of the URI that is only used when # manually selecting a specific pool member. # Also check for a previously set cookie indicating a manually selected pool member. if {[HTTP::query] contains $member_param or [HTTP::cookie $member_param] ne ""}{ # Have the query string parameter take precedence over the cookie. # So set the member_num based on the cookie and then overwrite it if the param value is set. set member_num [HTTP::cookie $member_param] if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Query contained the member parameter or member cookie was present.\ Parsed member cookie value: $member_num"} # Parse the value of the parameter to get the pool member number being selected # Use a workaround to handle a bug with URI::query, described in: # CR137465: http://devcentral.f5.com/s/Default.aspx?tabid=53&forumid=5&tpage=1&view=topic&postid=1168257#1145270 set query_member_num [URI::query "?&[HTTP::query]" "&${member_param}"] if {$query_member_num ne ""}{ set member_num $query_member_num if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Using member number from URI: \$member_num: $member_num"} } # Check member number value switch -glob $member_num { "0" { # Exact match for 0 (client wants to clear cookie) # Delete the cookie in the response # Save the cookie value so we can delete it in the response set cookie_val [HTTP::cookie $member_param] if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Member 0 specified, will remove cookie in response."} return } "[0-9]*" { # The parameter had a value starting with a digit, # Use lindex to get the nth -1 pool member "IP port" (lindex is 0 based) # Use scan to parse the IP and port to separate variables (the pool command doesn't seem to handle them together) # Use catch to handle any errors trying to parse the pool member if {[catch {scan [lindex [lsort [members -list [LB::server pool]]] [expr {$member_num - 1}]] {%[^ ] %d} ip port} result]}{ if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Error parsing pool member from $member_num"} } elseif {$result == 2}{ if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Parsed IP port: $ip $port"} # Use catch to handle any errors trying to select the pool member if {not [catch {pool [LB::server pool] member $ip $port}]}{ # Selecting pool member succeeded so exit this event of this iRule if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Successfully selected: $ip $port"} return } } } } # If we're still in the iRule there was no match or there were errors trying to select the pool member # so load balance the request pool [LB::server pool] unset member_num if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: No/invalid member_num parsed, load balancing request"} } } when HTTP_RESPONSE { # Check if $member_num is set and has a value if {[info exists member_num] and $member_num ne ""}{ switch $member_num { 0 { if {$cookie_val ne ""}{ # Expire the cookie by inserting the name/value with an expire time in the past if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Expiring cookie $member_param=$cookie_val"} HTTP::cookie insert name $member_param value $cookie_val path "/" HTTP::cookie expires $member_param 0 absolute } } default { if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Inserting cookie $member_param=$member_num"} HTTP::cookie insert name $member_param value $member_num path "/" } } } } # Debug events which can be removed once testing is complete when LB_SELECTED { if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Selected IP:port: [LB::server]"} } when SERVER_CONNECTED { if {$member_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Server IP:port: [IP::server_addr]:[TCP::server_port]"} }1.9KViews0likes1CommentSelect pool based on HTTP host header
Problem this snippet solves: This rule was designed for a customer that had many websites hosted on one VIP. This rule will dynamically assign a pool name based on the host header sent. In this case, the admin will only have to add a pool named, for example, www.test.com_pool instead of adding every possible host header value to a switch or if/then statement in the Irule. Code : when HTTP_REQUEST { set hostpool [string tolower [HTTP::host]]_pool # Show us what the client sent in the host header - Turn this OFF in production log local0. "pool is $hostpool" # Use if/then with "Catch" to account for any bad host headers - otherwise TCL will throw an error to the client if { [catch {pool $hostpool} exc] } { # If a client sends a host header that does not match a pool, send to default pool pool P3 } }1.8KViews0likes2CommentsSet Uri To Lower Case
Problem this snippet solves: This iRule sets the path in an HTTP request to lower case. It does not modify the query string, if present in the request. The iRule expects the requested object and query string to be separated by a question mark. Example: Original request: http://www.example.com/myDirectory/Index.html?Param1=Value1&Param2=Value2 Modified request: http://www.example.com/mydirectory/index.html?Param1=Value1&Param2=Value2 Code : when HTTP_REQUEST { # Check if there is a query string if {[HTTP::query] eq ""}{ # No query string, so set the entire URI to lower case HTTP::uri [string tolower [HTTP::uri]] } else { # Set the path to lower case and append the string to it HTTP::uri "[string tolower [HTTP::path]]?[HTTP::query]" } } # This iRule is also described in SOL5694: BIG-IP ASM security policy elements are case sensitive on AskF5.com. # Note: This rule can be simplified using the HTTP::path command: when HTTP_REQUEST { HTTP::path [string tolower [HTTP::path]] } # Note: Due to a bug in v10.0 - 10.2.1, HTTP::path truncates the HTTP query string if present. The first rule above can be used as a workaround. This is described in CR142756 and is fixed in 10.2.1HF1.1.8KViews1like0CommentsHTTP method conversion
Problem this snippet solves: This is one way that allows you to convert HTTP method from GET to POST or POST to GET transparently by LTM. Why? you want to hide something from users. (make it simpler for them) create meta proxy that also proxy auth and does auto-logon (or limited SSO) I don't know. I think it is fun. What technique are used here? vip targeting vip HTTP::retry HTTP::respond How does it work? configure 2 virtual servers one for normal HTTP load balance and another one to receive http request from virtual www1 and return back converted request. For example virtual www1 listens on 10.10.10.10:80 and point to pool webpool. Virtual www2 listen on 10.10.10.10:81 and has no pool assigned. virtual www1 receives http request from external clients and send selected request to virtual www2 virtual www2 converts http request (for GET-to-POST, turn GET uri query string to POST data and for POST-to-GET, turn POST data to GET uri query string) virtual www2 responds back with "converted request" as its content virtual www1 receives HTTP_RESPONSE and do HTTP::retry with this "converted request" Note: * This iRule is not perfect. It needs more development if you would like to use it. Feel free to put comment. Code : # rule for virtual www1 when HTTP_REQUEST { log local0. "[HTTP::request]" # send httprequest which you want to convert to another virtual # if [HTTP::uri] match what I want virtual www2 # else do nothing } when HTTP_RESPONSE { # use special status and/or remote_addr to identify # whether it comes back from real server or virtual www2 if { [HTTP::status] == 555 } { HTTP::collect [HTTP::header Content-Length] set reselect 1 } } when HTTP_RESPONSE_DATA { # probably add routine to verify # send converted http_request to real server log local0. "[HTTP::payload]" HTTP::retry [HTTP::payload] } when LB_SELECTED { # if http_retry is called, same destination may be picked (which is virtual www2) # change the destination to real server (use node or pool command) if { [info exists reselect ] } { LB::reselect node 10.10.72.1 80 unset reselect } log local0. "[LB::server]" } # rule for virtual www2 when HTTP_REQUEST { switch [HTTP::method] { GET { log local0. "uri = [HTTP::uri]" log local0. "query = [HTTP::query]" set query [HTTP::query] # strip off query string HTTP::uri [HTTP::path] # add Content-Type and Content-Length header HTTP::header insert Content-Type "application/x-www-form-urlencoded" HTTP::header insert "Content-Length" [string length $query] # strip off "GET " set request [substr [HTTP::request] 4] # create new request starts with POST # follow by original header (with modified uri) # and POST data (derived from query string) # according to http encoding, some characters may need attention # for example, space is %20 in GET uri but + in POST data HTTP::respond 555 content "POST $request$query" } POST { log local0. "len = [HTTP::header Content-Length]" log local0. "req = [HTTP::request]" # collect POST data HTTP::collect [HTTP::header Content-Length] } } } when HTTP_REQUEST_DATA { # prepare GET uri query string set query [HTTP::payload] # according to http encoding, some characters may need attention # for example, space is %20 in GET uri but + in POST data HTTP::uri "[HTTP::uri]?$query" # we don't need Content-Type and Content-Length anymore HTTP::header remove Content-Type HTTP::header remove Content-Length # remove POST data HTTP::payload replace 0 [HTTP::payload length] "" # strip off "POST " set request [substr [HTTP::request] 5] HTTP::respond 555 content "GET $request" }1.6KViews0likes2CommentsHTTP request cloning
Problem this snippet solves: These iRules send a copy of HTTP request headers and payloads to one or more pool members These are the current iRule versions of the example from Colin's article. Code : ########### # First Rule # ########### rule http_request_clone_one_pool { # Clone HTTP requests to one clone pool when RULE_INIT { # Log debug locally to /var/log/ltm? 1=yes, 0=no set static::hsl_debug 1 # Pool name to clone requests to set static::hsl_pool "my_syslog_pool" } when CLIENT_ACCEPTED { if {[active_members $static::hsl_pool]==0}{ log "[IP::client_addr]:[TCP::client_port]: [virtual name] $static::hsl_pool down, not logging" set bypass 1 return } else { set bypass 0 } # Open a new HSL connection if one is not available set hsl [HSL::open -proto TCP -pool $static::hsl_pool] if {$static::hsl_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: New hsl handle: $hsl"} } when HTTP_REQUEST { # If the HSL pool is down, do not run more code here if {$bypass}{ return } # Insert an XFF header if one is not inserted already # So the client IP can be tracked for the duplicated traffic HTTP::header insert X-Forwarded-For [IP::client_addr] # Check for POST requests if {[HTTP::method] eq "POST"}{ # Check for Content-Length between 1b and 1Mb if { [HTTP::header Content-Length] >= 1 and [HTTP::header Content-Length] < 1048576 }{ HTTP::collect [HTTP::header Content-Length] } elseif {[HTTP::header Content-Length] == 0}{ # POST with 0 content-length, so just send the headers HSL::send $hsl "[HTTP::request]\n" if {$static::hsl_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Sending [HTTP::request]"} } } else { # Request with no payload, so send just the HTTP headers to the clone pool HSL::send $hsl "[HTTP::request]\n" if {$static::hsl_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Sending [HTTP::request]"} } } when HTTP_REQUEST_DATA { # The parser does not allow HTTP::request in this event, but it works set request_cmd "HTTP::request" if {$static::hsl_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Collected [HTTP::payload length] bytes,\ sending [expr {[string length [eval $request_cmd]] + [HTTP::payload length]}] bytes total"} HSL::send $hsl "[eval $request_cmd][HTTP::payload]\nf" } } ############# # Second Rule # ############# rule http_request_close_xnum_pools { # Clone HTTP requests to X clone pools when RULE_INIT { # Set up an array of pool names to clone the traffic to # Each pool should be one server that will get a copy of each HTTP request set static::clone_pools(0) http_clone_pool1 set static::clone_pools(1) http_clone_pool2 set static::clone_pools(2) http_clone_pool3 set static::clone_pools(3) http_clone_pool4 # Log debug messages to /var/log/ltm? 0=no, 1=yes set static::clone_debug 1 set static::pool_count [array size static::clone_pools] for {set i 0}{$i < $static::pool_count}{incr i}{ log local0. "Configured for cloning to pool $clone_pools($i)" } } when CLIENT_ACCEPTED { # Open a new HSL connection to each clone pool if one is not available for {set i 0}{$i < $static::pool_count}{incr i}{ set hsl($i) [HSL::open -proto TCP -pool $static::clone_pools($i)] if {$static::clone_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: hsl handle ($i) for $static::clone_pools($i): $hsl($i)"} } } when HTTP_REQUEST { # Insert an XFF header if one is not inserted already # So the client IP can be tracked for the duplicated traffic HTTP::header insert X-Forwarded-For [IP::client_addr] # Check for POST requests if {[HTTP::method] eq "POST"}{ # Check for Content-Length between 1b and 1Mb if { [HTTP::header Content-Length] >= 1 and [HTTP::header Content-Length] < 1048576 }{ HTTP::collect [HTTP::header Content-Length] } elseif {[HTTP::header Content-Length] == 0}{ # POST with 0 content-length, so just send the headers for {set i 0}{$i < $static::pool_count}{incr i}{ HSL::send $hsl($i) "[HTTP::request]\n" if {$static::clone_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Sending to $static::clone_pools($i), request: [HTTP::request]"} } } } else { # Request with no payload, so send just the HTTP headers to the clone pool for {set i 0}{$i < $static::pool_count}{incr i}{ HSL::send $hsl($i) [HTTP::request] if {$static::clone_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Sending to $static::clone_pools($i), request: [HTTP::request]"} } } } when HTTP_REQUEST_DATA { # The parser does not allow HTTP::request in this event, but it works set request_cmd "HTTP::request" for {set i 0}{$i < $static::pool_count}{incr i}{ if {$static::clone_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Collected [HTTP::payload length] bytes,\ sending [expr {[string length [eval $request_cmd]] + [HTTP::payload length]}] bytes total\ to $static::clone_pools($i), request: [eval $request_cmd][HTTP::payload]"} HSL::send $hsl($i) "[eval $request_cmd][HTTP::payload]\n" } } }1.5KViews0likes5Comments