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.1KViews2likes4CommentsGTM 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]]" } }853Views1like2CommentsSNAT 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.9KViews1like10CommentsSet 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.8KViews1like0CommentsMicrosoft Branch Cache Hash Offload
Problem this snippet solves: BranchCache is a technology that Microsoft released with Windows 7 and Server 2008 R2 which can have a profound effect on where users actually source their content and the bandwidth consumed in retrieving that data. It’s fairly common in today’s work world to see global enterprises have multiple branch offices with users who regularly access data from a single centralized datacenter. If you take a look at the data that these users are commonly requesting, you’ll quickly realize that there is often a significant amount of data that is repetitive between the branch users. BranchCache is a technology designed to cut down on the unnecessary round trips from the branch office to the datacenter for data. By keeping an accessible copy of the content in the local branch, there will be a significant reduction in bandwidth used, and users will often be able to get the content faster than retrieving it from the distant datacenter. This iRule will offload the hash calculation from the servers. How to use this snippet: Tested with oneconnect and tcp profiles applied, as well as this http profile: profile http ms-pccrc-http { compress enable compress buffer size 131072 compress http 1.0 disable compress gzip memory level 16k compress gzip window size 64k } Code : when HTTP_REQUEST { set client "[IP::remote_addr]:[TCP::remote_port], URI: [HTTP::uri]" set key "[HTTP::host][HTTP::uri]" persist uie $key if { [HTTP::header "Accept-Encoding"] contains "peerdist" } { set will_peerdist 1 log local0. "$client - peerdist requested" set pcc [session lookup uie $key] if { [llength $pcc] != 0 } { set pl [lrange [persist lookup uie $key] 0 2] if { $pl ne [lindex $pcc 0] } { log local0. "$client - cached peerdist hash does not match persistence: $pl != [lindex $pcc 0]" } else { log local0. "$client - responding with previously cached peerdist hash." HTTP::respond 200 content [binary format a* [lindex $pcc 1]] \ "Content-Encoding" "peerdist" \ "X-P2P-PeerDist" "Version=1.0, ContentLength=[lindex $pcc 2]" \ "ETag" [lindex $pcc 3] \ "Content-Type" [lindex $pcc 4] } unset pl } unset pcc } else { set will_peerdist 0 log local0. "$client - peerdist not requested" } } when HTTP_RESPONSE { # Client can do peerdist, but server response is normal if { $will_peerdist && \ [HTTP::status] == 200 && \ ! [HTTP::header exists "Content-Encoding"] || \ [HTTP::header "Content-Encoding"] eq "identity" } { set clen [HTTP::header "Content-Length"] if { $clen > 0 } { log local0. "[IP::remote_addr]:[TCP::remote_port]->$client - collecting content: $clen" set h_len $clen if { $h_len > 65536 } { set h_len 65536 } HTTP::collect $h_len } else { log local0. "[IP::remote_addr]:[TCP::remote_port]->$client - no content found: $clen, headers: [HTTP::header names]" } } } when HTTP_RESPONSE_DATA { if { ! [info exists offset] } { set offset 0 set blkcnt 0 set blkhashes {} set blkpci {} set segoff 0 set segcnt 0 set seglen 0 set segpci {} set secret [virtual name] HTTP::header replace "Content-Encoding" "peerdist" HTTP::header insert "X-P2P-PeerDist" "Version=1.0, ContentLength=$clen" log local0. "$client - peerdist hashing started" } while { $offset < $clen } { #log local0. "$client - currently collected: [HTTP::payload length], hashing offset: $offset (of $clen)" set h_len [expr {$clen - $offset}] if { $h_len > 65536 } { set h_len 65536 } # if have more to do, but we haven't collected it yet if { [HTTP::payload length] < $h_len } { HTTP::collect $h_len #log local0. "$client - waiting for [expr {$h_len-[HTTP::payload length]}] more, have: [HTTP::payload length]" return } # Hash the current block set blkhashes [binary format a*a* $blkhashes [sha256 [HTTP::payload $h_len]]] # Delete the payload we just hashed since we don't need it anymore HTTP::payload replace 0 $h_len {} incr offset $h_len incr blkcnt # At the Segment boundary if { $blkcnt >= 512 } { # Add a segment hash of block hashes set seglen [expr {$offset - $segoff}] set seghash [sha256 $blkhashes] set seghods [sha256 [binary format a*a* $seghash $secret]] lappend segpci [binary format wiia*a* $segoff $seglen 65536 $seghash $seghods] # Add a block hash lappend blkpci [binary format ia* $blkcnt $blkhashes] set blkhashes {} set blkcnt 0 set segoff $offset incr segcnt log local0. "$client - segment, len: $seglen, cnt: $segcnt, off: $segoff" } } # Didn't end on a block/segment boundary if { $blkcnt > 0 } { # Add a segment hash of block hashes for the last segment set seglen [expr {$offset - $segoff}] set seghash [sha256 $blkhashes] set seghods [sha256 [binary format a*a* $seghash $secret]] lappend segpci [binary format wiia*a* $segoff $seglen 65536 $seghash $seghods] # Add a block hash lappend blkpci [binary format ia* $blkcnt $blkhashes] set blkcnt 0 incr segcnt log local0. "$client - last segment, cnt: $segcnt, len: $seglen" } # Now build the new payload with the hashes set pci [binary format siiii 0x0100 0x800C 0 0 $segcnt] HTTP::payload replace 0 0 $pci set offset [string length $pci] foreach pci $segpci { HTTP::payload replace $offset 0 $pci incr offset [string length $pci] } foreach pci $blkpci { HTTP::payload replace $offset 0 $pci incr offset [string length $pci] } session add uie $key [list [LB::server] [HTTP::payload] $clen [HTTP::header ETag] [HTTP::header Content-Type]] HTTP::release log local0. "$client - content hash completed!" unset -nocomplain pci offset blkcnt blkpci blkhashes secret key unset -nocomplain segoff segcnt seglen segpci seghash seghods }452Views1like1CommentHTTP 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.1KViews1like10Comments