GTM return LDNS IP to client
Problem this snippet solves: We do a lot of our load balancing based on topology rules, so it's often very useful to know where the DNS request is actually coming from rather than just the client's IP and the DNS servers they have configured. Especially if they're behind an ADSL router doing NAT or some other similar set up. This rule simply returns the IP address of the LDNS that eventually made the query to the GTM device in the response to a lookup for the WideIP using the rule, as well as logging the response and perceived location. Code : rule "DNS_debug" partition "Common" { when DNS_REQUEST { host [IP::client_addr] log local0.err "Debug address : [IP::client_addr] [whereis [IP::client_addr]]" } }853Views1like2CommentsSession Table Export
Problem this snippet solves: This sample goes along with the Tech Tip titled Session Table Exporting With iRules . It creates a mechanism for you to export the data from your session tables for archiving or external reporting. NOTE: This functionality is included in the Session Table Control iRule and is partially rendering here so it has been removed.725Views0likes3CommentsCSV Tabular Data Sideband Importer
Problem this snippet solves: This iRule adds the ability to import CSV-formatted tabular data to a table via an HTTP sideband connection. The implementation is described in George Watkins' article: Populating Tables With CSV Data Via Sideband Connections The iRule can be added to any virtual server that makes use of tables. If during the CLIENT_ACCEPTED event, the iRule detects a missing or expired table, it will initiate an HTTP sideband connection to a server containing the data. The data will then be parsed and inserted into a table and the connection will proceed as normal. No additional sideband connections will be made until the data expires as defined in the settings at the top of the iRule. Here are a list of all the configurable options: db_host - hostname of HTTP server hosting CSV-formatted data db_path - path of CSV file on server db_line_delimiter - defines the CSV file line delimiter; LF = Unix, CR = old pre-OS X Macs, CR+LF = Windows (Notepad) db_cache_timeout - length of time (in seconds) that cached data from table should be used before refreshing dns_server - IP address of DNS server if using DNS resolution for db_host Code : when RULE_INIT { # HTTP server holding the CSV-formatted database set static::db_host mydbhost.testnet.local # HTTP path for the CSV-formatted database set static::db_path "/redirects.csv" # CSV database line delimiter: CR = \r, LF = \n, CR+LF = \r\n set static::db_line_delimiter "\n" # DNS server if using DNS resolution (optional) set static::dns_server 10.0.0.1 # Timeout for database cached in a table set static::db_cache_timeout 3600 } when CLIENT_ACCEPTED { # table to cacha CSV-formatted database set db_cache_table "db_cache_[virtual]" # table to track when to refresh the database's contents set db_cache_state_table "db_cache_timeout_[virtual]" set last_refresh [table lookup -subtable $db_cache_state_table last_refresh] if { $last_refresh eq "" } { set last_refresh 0 } if { [expr [clock seconds]-$last_refresh] > $static::db_cache_timeout } { set db_ip [lindex [RESOLV::lookup @$static::dns_server -a $static::db_host] 0] if { $db_ip ne "" } { if { [table lookup -subtable $db_cache_state_table lock] != 1 } { # lock table modifications so that multiple instances don't attempt to update the table table set -subtable $db_cache_state_table lock 1 $static::db_cache_timeout $static::db_cache_timeout log local0. "Locking table" # establish connection to server set conn [connect -timeout 1000 -idle 30 $db_ip:80] # build request to send to HTTP server hosting DB set request "GET $static::db_path HTTP/1.1\r\nHost: $static::db_host\r\n\r\n" # send request to server send -timeout 1000 -status send_status $conn $request # receive response and place in variable set db_contents [getfield [recv -timeout 1000 -status recv_info $conn] "\r\n\r\n" 2] if { $db_contents ne "" } { # update last refresh time in timeout table table set -subtable $db_cache_state_table last_refresh [clock seconds] indef indef # grab a list of old keys so we can remove them from cache if not in new DB copy set old_keys [table keys -subtable $db_cache_table] foreach field [split [string map [list $static::db_line_delimiter \uffff] $db_contents] \uffff] { if { ($field contains ",") && !($field starts_with "#") } { set sep_offset [string first "," $field] set key [string range $field 0 [expr $sep_offset - 1]] set value [string range $field [expr $sep_offset + 1] end] lappend new_keys $key # add key/value pairs to DB cache table table set -subtable $db_cache_table $key $value indef indef if { [lsearch $old_keys $key] >= 0 } { log local0. "Updating \"$key\" = \"$value\" in DB cache table" } else { log local0. "Adding \"$key\" = \"$value\" to DB cache table" } } } foreach old_key $old_keys { if { [lsearch $new_keys $old_key] < 0 } { # remove any keys which don't exist in new DB copy table delete -subtable $db_cache_table $old_key log local0. "Deleting \"$old_key\" from DB cache table, key doesn't exist in new DB copy" } } } close $conn table delete -subtable $db_cache_state_table lock log local0. "Unlocking table" } } else { log local0. "Could not get valid IP for the DB server. Check the hostname and nameserver settings." } } } when HTTP_REQUEST { set redirect_path [table lookup -subtable $db_cache_table [string tolower [HTTP::path]]] if { $redirect_path ne "" } { HTTP::redirect http://[HTTP::host]$redirect_path } }794Views0likes1Commentratio load balancing using rand function
Problem this snippet solves: Summary: Use a pseudo random number to set a ratio for any iRule logic. This avoids using a global counter mechanism to track past selections. iRule Methodology: This iRule uses the rand function of expr to generate a pseudo random number and effectively generate a ratio for any iRule logic. The first example below sends 2% of connections to a separate pool. The remaining 98% of connections are sent to the virtual server's default pool. The second example selects a separate pool for 2% of requests to a specific set of URIs. The original idea was from Jarvil in this forum post. rand() man page section from the expr TCL wiki page: Code : http://www.tcl.tk/man/tcl8.4/TclCmd/expr.htm#M38 rand() Returns a pseudo-random floating-point value in the range (0,1). The generator algorithm is a simple linear congruential generator that is not cryptographically secure. Each result from rand completely determines all future results from subsequent calls to rand, so rand should not be used to generate a sequence of secrets, such as one-time passwords. The seed of the generator is initialized from the internal clock of the machine or may be set with the srand function. # Example 1 - Send 2% of connections to a different pool than the virtual server's default pool when CLIENT_ACCEPTED { # Send 2% of connections to a separate pool if { rand() < 0.02 } { pool other_pool } } # Example 2 - Send 2% of requests to a specific URI to a different pool than the primary pool when HTTP_REQUEST { # Check for a specific URI or set of URIs switch -glob [HTTP::uri] { "/uri1*" - "/uri2*" - "/uri3*" { # Send 2% of connections to a separate pool if { rand() < 0.02 } { pool other_pool # Exit from this event in this iRule return } } } # If we're still executing in this iRule, select the primary pool pool primary_pool } # Example 3 - Send 90% of requests to poolA, 5% to poolB and 5% to poolC. Use a session cookie to persist clients to the same pool over their HTTP session when HTTP_REQUEST { # Check if there is a pool selector cookie in the request # Use a switch statement to ensure only valid pool names are present # instead of accepting any pool name from the user supplied cookie switch [HTTP::cookie pool_cookie] { poolA - poolB - poolC { # Select the pool from the cookie pool [HTTP::cookie pool_cookie] set selected "" } default { # No pool selector cookie, so randomaly choose a pool # Save a random number between 0 and 1 set rand [expr { rand() }] if { $rand < .90 } { pool poolA set selected poolA } elseif { $rand < .95 }{ pool poolB set selected poolB } else { pool poolC set selected poolC } } } } when HTTP_RESPONSE { # Set a pool selector cookie if a pool was selected for this request if {$selected ne ""}{ HTTP::cookie insert name pool_cookie value $selected path "/" } }1.2KViews0likes2Commentsoffloading content with ifiles
Problem this snippet solves: This irule allows to serve parts of a website directly from BIG-IP LTM. Only URIs configured in the data class my_to_serve_files will be served via this irule Have a look here how to manage ifiles : https://devcentral.f5.com/Tutorials/TechTips/tabid/63/articleType/ArticleView/articleId/1086514/v111ndashExternal-File-Access-from-iRules-via-iFiles.aspx Code : when RULE_INIT { # Debug Logging set static::debug 1 # this irule is serving ifiles to specific URIs provisioned in the dataclass my_to_serve_files # v 0.1 christian@f5.com # # Current limitations: # - the unique identifier is the file is its name, no similar named files are allowed } when HTTP_REQUEST { # provision uri to served in the data class my_to_serve_files if { [class match [HTTP::uri] equals my_to_serve_files ] } { #extract filename from request set my_ifile "/Common/[URI::basename [HTTP::uri]]" if { $static::debug } {log local0. "URI match, opening file is $my_ifile" } # try to open ifile with the name of $my_ifile if { [ catch {set ifileContent [ifile get $my_ifile] } fid] } { # ifile could not be opened, sending a http 404 status code set ifileContent "File not found" if { $static::debug } {log local0. "ifile $my_ifile not found" } HTTP::respond 404 content $ifileContent } else { # ifile could be opened, sending content in a http response with a 200 status code HTTP::respond 200 content $ifileContent } unset my_ifile unset ifileContent } else { #this code only gets called when the URI is not provisioned in the data class, so the request will sent through to the webserver if { $static::debug } {log local0. "no match for [HTTP::uri], passing to webserver" } } } # Data Group ltm data-group internal /Common/my_to_serve_files { records { /my/path/Sample.png { } } type string }528Views0likes2CommentsSNAT 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.2KViews0likes8CommentsSSL Cert expiration Tracker
Problem this snippet solves: Script is useful for large F5 LTM infrastructure. Instead of checking certs being expired on individual LTM, just list all your LB's in single file and script will check and create a report. How to use this snippet: This is made up of 3 parts: 1- hosts file (hosts.txt) 2- The Script 3- Report (bigip.data) Make sure you have Python 3 installed on your system. Install F5 SDK - i.e "pip install f5-sdk" and other modules such as dateutil, getpass, datetime etc. Create Text file "hosts.txt" and keep it in same directory as script. List all your LB's in "hosts.txt", each on new line. That's all! Run the script. If you face any issue let me know. Please provide any suggestions. Code : 92601975Views0likes3CommentsHTTP 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." } }2.1KViews1like10CommentsHTTP 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