Proxy Protocol Receiver

Problem this snippet solves:

iRule for BIG-IP to receive PROXY protocol (v1 and v2) header in TCP Payload and remove it before forwarding remaining TCP Payload to server side pool member.

How to use this snippet:

Enable iRule on virtual server where upstream proxy will be sending PROXY header.

Testing done using proxied IPv6 and IPv4 HTTP connections from HAProxy using Proxy Protocol v1 and v2. Use of client or server SSL profiles slated for testing and validation since ELB promotes use of Proxy Protocol as a solution for customers that don't want to have ELB terminate HTTPS traffic but do want servers to see original IP addresses.

Code :

#PROXY Protocol Receiver iRule
# c.jenison at f5.com (Chad Jenison)
# v2.0 - Added support for PROXY Protocol v2, control for v1,v2 or lack of proxy via static:: variables set in RULE_INIT
# v2.1 - Fix for skipping bytes in v2 code
when RULE_INIT {
    set static::allowProxyV1 0
    set static::allowProxyV2 1
    set static::allowNoProxy 0
}

when CLIENT_ACCEPTED {
    TCP::collect
}
 
when CLIENT_DATA {
    binary scan [TCP::payload 12] H* v2_protocol_sig
    if {$static::allowProxyV1 && [TCP::payload 0 5] eq "PROXY"} {
        set proxy_string [TCP::payload]
        set proxy_string_length [expr {[string first "\r" [TCP::payload]] + 2}]
        scan $proxy_string {PROXY TCP%s%s%s%s%s} tcpver srcaddr dstaddr srcport dstport
        log "Proxy Protocol v1 conn from [IP::client_addr]:[TCP::client_port] for an IPv$tcpver stream from Src: $srcaddr:$srcport to Dst: $dstaddr:$dstport"
        TCP::payload replace 0 $proxy_string_length ""
    } elseif {$static::allowProxyV2 && $v2_protocol_sig eq "0d0a0d0a000d0a515549540a"}{
        binary scan [TCP::payload] @12H* v2_proxyheaderremainder
        binary scan [TCP::payload] @12H2H* v2_verCommand v2_remainder
        if {$v2_verCommand == 21}{
            binary scan [TCP::payload] @13H2 v2_addressFamilyTransportProtocol
            if {$v2_addressFamilyTransportProtocol == 11} {
                binary scan [TCP::payload] @16ccccccccSS v2_sourceAddress1 v2_sourceAddress2 v2_sourceAddress3 v2_sourceAddress4 v2_destAddress1 v2_destAddress2 v2_destAddress3 v2_destAddress4 v2_sourcePort1 v2_destPort1
                set v2_sourceAddress "[expr {$v2_sourceAddress1 & 0xff}].[expr {$v2_sourceAddress2 & 0xff}].[expr {$v2_sourceAddress3 & 0xff}].[expr {$v2_sourceAddress4 & 0xff}]"
                set v2_destAddress "[expr {$v2_destAddress1 & 0xff}].[expr {$v2_destAddress2 & 0xff}].[expr {$v2_destAddress3 & 0xff}].[expr {$v2_destAddress4 & 0xff}]"
                set v2_sourcePort [expr {$v2_sourcePort1 & 0xffff}]
                set v2_destPort [expr {$v2_destPort1 & 0xffff}]
                log "Proxy Protocol v2 conn from [IP::client_addr]:[TCP::client_port] for an IPv4 Stream from Src: $v2_sourceAddress:$v2_sourcePort to Dst: $v2_destAddress:$v2_destPort"
    			binary scan [TCP::payload] @14S address_size
    			set skip_bytes [expr 16 + $address_size]
                TCP::payload replace 0 $skip_bytes ""
            } elseif {$v2_addressFamilyTransportProtocol == 21} {
                binary scan [TCP::payload] @16H4H4H4H4H4H4H4H4 v2_v6sourceAddress1 v2_v6sourceAddress2 v2_v6sourceAddress3 v2_v6sourceAddress4 v2_v6sourceAddress5 v2_v6sourceAddress6 v2_v6sourceAddress7 v2_v6sourceAddress8
                binary scan [TCP::payload] @32H4H4H4H4H4H4H4H4 v2_v6destAddress1 v2_v6destAddress2 v2_v6destAddress3 v2_v6destAddress4 v2_v6destAddress5 v2_v6destAddress6 v2_v6destAddress7 v2_v6destAddress8
                binary scan [TCP::payload] @48SS v2_v6sourcePort1 v2_v6destPort1
                set v2_v6sourcePort [expr {$v2_v6sourcePort1 & 0xffff}]
                set v2_v6destPort [expr {$v2_v6destPort1 & 0xffff}]
                set v2_v6sourceAddress "$v2_v6sourceAddress1:$v2_v6sourceAddress2:$v2_v6sourceAddress3:$v2_v6sourceAddress4:$v2_v6sourceAddress5:$v2_v6sourceAddress6:$v2_v6sourceAddress7:$v2_v6sourceAddress8"
                set v2_v6destAddress "$v2_v6destAddress1:$v2_v6destAddress2:$v2_v6destAddress3:$v2_v6destAddress4:$v2_v6destAddress5:$v2_v6destAddress6:$v2_v6destAddress7:$v2_v6destAddress8"
                log "Proxy Protocol v2 conn from from [IP::client_addr]:[TCP::client_port] for an IPv6 Stream from Src: $v2_v6sourceAddress:$v2_v6sourcePort to Dst: $v2_v6destAddress:$v2_v6destPort"
    			binary scan [TCP::payload] @14S address_size
    			set skip_bytes [expr 16 + $address_size]
                TCP::payload replace 0 $skip_bytes ""
            } else {
                log "v2_proxy conn from [IP::client_addr]:[TCP::client_port] - possible unknown/malformed transportProtocol or addressFamily"
                reject
            }
        } elseif {$v2_verCommand == 20}{
            log "Proxy Protocol v2 and LOCAL command from [IP::client_addr]:[TCP::client_port]; skipping"
			binary scan [TCP::payload] @14S address_size
			set skip_bytes [expr 16 + $address_size]
            TCP::payload replace 0 $skip_bytes ""
            binary scan [TCP::payload] H* local_remainder
        } else {
            log "Proxy Protocol Protocol Signature Detected from [IP::client_addr]:[TCP::client_port] but protocol version and command not legal; connection reset"
            reject
        }
    } elseif {$static::allowNoProxy} {
        log "Connection from [IP::client_addr]:[TCP::client_port] allowed despite lack of PROXY protocol header"
    } else {
        reject
        log "Connection rejected from [IP::client_addr]:[TCP::client_port] due to lack of PROXY protocol header"
    }
    TCP::release
}

Tested this on version:

12.0
Published Sep 02, 2015
Version 1.0
  • Hello,

    I'm new to irule. So I require to pass Proxy Protocol to F5 and make sure in F5 it will use Source IP from Proxy Protocol.

    Do I need to add a certain rule so Source IP received is from Proxy Protocol instead of Proxy services IP?

    Thank you.

  • zafar's avatar
    zafar
    Icon for Nimbostratus rankNimbostratus

    I have tried using this with HTTPS and see it is breaking the connections. Just wondering if anyone tried this with HTTPS and got it to work?

     

    Appreciate inputs.

     

  • Hi everyone,

    Using F5 on AWS, I encountered an issue using a Network Load Balancer and therefore ProxyProtocol V2. AWS also specified in the header the id of the vpc endpoint therefore, line 34, the shift of 28 octets is wrong. I had to read the content of the 15th and 16th octets which indicates the size of this payload (variable in size):

    binary scan [TCP::payload] @13H2S v2_addressFamilyTransportProtocol v2_remainderLen1

    and add this to the size of the initial payload (16 octets):

     set v2_remainderLen "[expr {$v2_remainderLen1 & 0xffff}]"
     set v2_payloadindex "[expr $v2_remainderLen + 16]"
     log "Shift de : $v2_payloadshift"
     TCP::payload replace 0 $v2_payloadindex ""
    

    Please forgive my TCL, this is my first time 🙂

  • I believe 3) should be as easy as adding the following to the bottom of the code hung off the CLIENT_DATA event, along with a setting of $static::useProxyProtocolSourceForSNAT in "RULE_INIT" to 1.

     

    This syntax validates, but I'll need time to do some testing.

     

    Chad

     

    if {$static::useProxyProtocolSourceForSNAT} {
        if {$srcaddr}{
            snat $srcaddr
        } elseif {$v2_sourceAddress} {
            snat $v2_sourceAddress
        } elseif {$v2_v6sourceAddress} {
            snat $v2_v6sourceAddress
        }
    }
    
  • Would you please share how to implement n.3 ("Spoofing" of Proxy Protocol Source IP)?

     

  • note that there are a few things people might want atop this iRule: 1) Support for Client SSL on the inbound connections that are using proxy protocol 2) Insertion of Proxy Protocol Source IP address into HTTP X-Forwarded-For header on server side 3) "Spoofing" of Proxy Protocol Source IP by BIG-IP (assuming servers are pointed at BIG-IP as default gateway) on server-side connections.

     

    All of these things should be doable and I'm happy to add them.

     

  • Jonathan_Jachn1's avatar
    Jonathan_Jachn1
    Historic F5 Account

    very nice irule. i found a problem on it , i needed to change the proxy_string_length to not all the payload but till the first occurrence of \r. like this : set proxy_string_length [string first "\r" [TCP::payload]] 0

     

    Regards,