Forum Discussion

marcusovsky_577's avatar
marcusovsky_577
Icon for Nimbostratus rankNimbostratus
Aug 23, 2011

LTM as a proxy

Hello, this is my first post so hope you can assist me ...

 

 

This is my scenario (using LTM version 9.4.8):

 

 

I need my LTM to configure as a full proxy for the following traffic flow:

 

 

Currently I have a VIP on port 81 configured on F5. The traffic is being loadbalanced to the pool behind the F5.

 

 

I need to divert some of the traffic (7 URL's) straight to the Internet bypassing the servers and configure my F5 to act as a full proxy (traffic needs to come back) before it returns back to the source.

 

 

I have VIP and SNAT configured fine but I am having some issues with the iRule,

 

 

I need to make sure that the traffic coming on port 81 will be inspected (using HTTP REQUEST) and if the specific URI string will be found (let's assume it is facebook.com) then this traffic needs to be forwarded on port 80 straight to facebook.

 

 

I was thinking to create a pool with a pool member for each of the URL's but I don't want to use the IP address for the nodes, i would have to use hostnames. Not really sure how to do that.

 

 

Can you please advise? Or maybe there is a better solution I could use?

 

 

Thanks,

 

Mark

 

 

 

 

 

 

  • Hi Mark,

    The LTM can't really become a full proxy in the sense of proxy systems such as squid and Ironport or bluecoat.

    There is a limited proxy iRule that Hoolio had written up (it's not HTTP Connect method and it doesn't HTTPS)

    
    
      web proxy example 
      
      This is a simple, incomplete example web proxy iRule. 
      It only supports limited proxy functionality of converting the requested host  
         (from an absolute URI or the Host header) to an IP address and sending the request on.   
      It doesn't support CONNECT/HTTPS or most other RFC2616 requirements for a web proxy. 
      
     when HTTP_REQUEST { 
      
        log local0. "[IP::client_addr]:[TCP::client_port]: New HTTP [HTTP::method] request to [HTTP::host], [HTTP::uri]" 
      
         Check if the URI is absolute and http:// 
        if {[string tolower [HTTP::uri]] starts_with "http://"}{ 
      
            Parse the host value from the URI 
           set host [URI::host [HTTP::uri]] 
           log local0. "[IP::client_addr]:[TCP::client_port]: Parsed $host from URI [HTTP::uri]" 
      
        } else { 
           set host [HTTP::host] 
        } 
      
         Check if host header has a port 
        if {$host contains ":"}{ 
      
            Scan the host header to parse the host and port 
           if {[scan $host {%[^:]:%s} host port] == 2}{ 
      
              log local0. "[IP::client_addr]:[TCP::client_port]: Parsed \$host:\$port: $host:$port" 
      
           } else { 
               Host value was host: without a port. Use the requested port. 
              set port [TCP::local_port] 
           } 
        } else { 
      
            Host header didn't have a port. Use the requested port. 
           set port [TCP::local_port] 
        } 
         Check if the host header isn't an IP address (ie, it contains an alpha character) 
        if {[string match {*[a-zA-Z]*} $host]}{ 
      
           log local0. "[IP::client_addr]:[TCP::client_port]: Host value not an IP: $host" 
      
            Perform a DNS lookup of the hostname 
           NAME::lookup $host 
      
            Hold the request until name resolution completes 
           HTTP::collect 
      
        } elseif {[catch {IP::addr $host mask 255.255.255.255}]==0}{ 
      
           log local0. "[IP::client_addr]:[TCP::client_port]: Host is an IP: [HTTP::host]" 
      
            Request was to a valid IP address, so use that as the destination 
           node $host $port 
      
        } else { 
      
            Couldn't parse host header.  Could use the destination IP address as the destination? 
           HTTP::respond 400 content "Invalid Host header" 
           log local0. "[IP::client_addr]:[TCP::client_port]: Invalid host header: [HTTP::host]" 
        } 
     } 
     when NAME_RESOLVED { 
      
        set response [NAME::response] 
      
        log local0. "[IP::client_addr]:[TCP::client_port]: Resolution response: $response (elements: [llength $response])" 
      
         Check if there is a resolution answer and it's an IP address 
        switch [llength $response] { 
      
           0 { 
               No response, or response wasn't an IP address 
              log local0. "[IP::client_addr]:[TCP::client_port]: Non-existent/invalid response: $response" 
      HTTP::respond 500 content "Couldn't process request" 
           } 
           default { 
      
               Response was one or more list entries.  Use the first list element.  Check if it's an IP address. 
              if {[catch "IP::addr [lindex $response 0] mask 255.255.255.255"]==0}{ 
      
                  Request was to a valid IP address, so use that as the destination 
                 if {$port != "" and [string is integer $port]}{ 
                    log local0. "[IP::client_addr]:[TCP::client_port]: Using destination with parsed port [lindex $response 0]:$port" 
                    node [lindex $response 0] $port 
                 } else { 
                    log local0. "[IP::client_addr]:[TCP::client_port]: Using destination with default port $response:[TCP::local_port]" 
                    node [lindex $response 0] $::default_port 
                 } 
              } else { 
                  No response, or response wasn't an IP address 
                 log local0. "[IP::client_addr]:[TCP::client_port]: Non-existent/invalid response: $response" 
         HTTP::respond 500 content "Couldn't process request" 
              } 
           } 
        } 
         Release the request 
        HTTP::release 
     } 
     
    

    You will probably need to do the following to enable the DNS lookups

    http://ask.f5.com/kb/en-us/solutions/public/8000/400/sol8437.html

    I hope this helps

    Bhattman

  • thanks for the advice,

     

     

    i am having issues with the 'when NAME_RESOLVED'. The F5 is forwarding the request to itself (127.0.0.1) even I set recursion to ON and setup the forwarder to my DNS's set up on the F5 as I found in one of the other devcentral posts.

     

     

    For some reason it is still not forwarding the request off the box to my DNS's.

     

     

    Can you please advise?

     

     

    Thanks,

     

    Mark
  • Hi Mark,

    I don't have answer to your question.

    I just show you alternative iRule which can be example how to handle CONNECT method. (It is not well-written, btw)

    - the part that handles CONNECT method is in client_data (you need to have tcp::collect in client_accepted so client_data can be fired)

    - the idea is to detect if it starts with word "CONNECT", then resolve IP, then HTTP::disable

    a few things to note

    - this iRule does not contains any error handling (it needs extra work)

    - I have this running with pre-v11 and never test with older version (sorry)

    - this irule use new RESOLV::lookup command in v10. you might have to port this to use NAME::lookup if you want to stay in v9

    
    when CLIENT_ACCEPTED {
        snat automap
        TCP::collect
    }
    when CLIENT_DATA {
        if { [TCP::payload] starts_with "CONNECT" } {
            set uri [getfield [TCP::payload] " " 2]
            set host [getfield $uri ":" 1]
            set port [getfield $uri ":" 2]
            if { [catch {IP::addr $host mask 255.255.255.255} ] } {
                set ips [RESOLV::lookup @1.2.3.4 -a $host]
                set host [lindex $ips 0]
            }
            log local0.alert "[IP::client_addr] CONNECT: node $host $port"
            log local0.alert "[IP::client_addr] PAYLOAD: [TCP::payload]"
            node $host $port
            HTTP::disable
            TCP::respond "HTTP/1.0 200 Connection Established\r\n\r\n"
            TCP::payload replace 0 [TCP::payload length] ""
        }
        TCP::release
    }
    when HTTP_REQUEST {
        set method [HTTP::method]
        set host  [URI::host  [HTTP::uri]]
        set port  [URI::port  [HTTP::uri]]
        regexp {http://[^/]+(/[^?]*)?.*} [HTTP::uri] match path
        set query [URI::query [HTTP::uri]]
        if { $port eq "" } { set port 80 }
        log local0.alert "$method [HTTP::uri] => $host"
        if { [catch {IP::addr $host mask 255.255.255.255} ] } {
            set ips [RESOLV::lookup @1.2.3.4 -a $host]
            set host [lindex $ips 0]
        }
        if { $host eq "" } { 
            log local0.alert "unknown host"
            reject
            HTTP::respond 200 content "unknown host [HTTP::host]"
            return
        }
        if { $query eq "" } {
            HTTP::uri "${path}"
        } else {
            HTTP::uri "${path}?${query}"
        }
        log local0.alert "node $host $port uri=[HTTP::uri]"
        node $host $port
    }