HTTP 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." } }
Published Mar 18, 2015
Version 1.0

Was this article helpful?

10 Comments

  • I can't manage to have this working, can we have an example of how the Virtual Server should be configured? Thanks,
  • I would also be interested in the details of the Virtual and Pool (understood they are not used) settings to get this working.
  • I Think this can be done now without Irules , HTTP Profile explicit Proxy : https://support.f5.com/kb/en-us/products/big-ip_ltm/manuals/product/ltm-concepts-11-5-0/7.html
  • we are experiencing issues with this iRule, as ssl handshakes are not accepted on the websites we try to reach. http works just fine, but any https request will be met with an RST, ACK by the website.

     

    We just upgraded to 12.1.1, and i understand the DEFAULT CIPHERS have changed, but considering we haven't applied any SSL profile to the virtual server, how does it go about making the https connections work?

     

    this is working for the most part on another environment, but we need https to work here as well.

     

  • This irule appears to be stripping off the SNI in the client hello. Has anyone else experienced this issue?

     

  • Sebastian,

     

    You can set your LTM to resolve DNS and set the iRule to point local allowing you to set as many resolver(s) as you need.

     

    System - Configuration - Device - DNS

    DNS Lookup Server List - 127.0.0.1

    BIND Forward Server List - local or external resolver(s)

    DNS Search Domain List - Set for local domain lookups to resolve local host names.

     

    In the iRule:

    set static::dns "127.0.0.1"

     

    /jeff

  • Hi, I'm new yeah, can I suggest edits to this? There's repetition here and at least two unhandled edge cases. @The DevCentral Team​