XFF Universal Persistence iRule

Problem this snippet solves:

Simple iRule to read the XFF header on an incoming HTTP Request and use a Universal Persistence ID. Orginal iRule found to have an issue with multiple IP addresses in the XFF header for changed to only pass the first XFF IP.

I have updated the iRule line to account for systems where multiple 'X-Forwarded-For' headers have been added.

persist uie [lindex  [ split [HTTP::header X-Forwarded-For] "," ] 0]

to

persist uie [lindex [ split [lindex [HTTP::header values X-Forwarded-For] 0] "," ] 0]

thanks to the advice from Yann Desmarest. This could also be done with the 'getfield' command see Yann's comments below.

How to use this snippet:

Create iRule using following code (mine is named 'persist_xff_uie') Create Universal Persistence Profile with iRule set to 'persist_xff_uie' (or what ever name you assign to the iRule) Assign Universal Persistence Profile to Virtual Server (ensure virtual server has HTTP profile assigned)

Code :

# Name: persist_xff_uie
#
# To be used with UIE Persistence Profile
#
# Checks HTTP Request for 'X-Forwarded-For' header and if exists takes the first 'X-Forwarded-For' IP address as sets as 
# Persist identifier.
# If the 'X-Forwarded-For' header does not exist then the client IP address is set as Persist identifier.

when HTTP_REQUEST { 
    if {[HTTP::header X-Forwarded-For] != ""} then {
persist uie [lindex [ split [lindex [HTTP::header values X-Forwarded-For] 0] "," ] 0]
    } else {
persist uie [IP::client_addr]
}
}

Tested this on version:

11.5
Updated Jun 06, 2023
Version 2.0

Was this article helpful?

7 Comments

  • Hi Amg Will this also need One Connect as mandatory? How is this different from the below code I am using? ********************************************************************* when HTTP_REQUEST { set xff [HTTP::header "x-forwarded-for"] persist uie [HTTP::header "x-forwarded-for"] log local0. "[IP::client_addr]:[TCP::client_port]: XFF: $xff" } *********************************************************************
  • Hi Sumanta, Issue found was when XFF header contained multiple IP addresses so my code splits up the header and only uses the first IP address, this should be the first XFF header applied. If this is not an issue for you, either you happy to use the full XFF header or only ever has a single address in the XFF header, then your code looks fine. Finally no you do not need to use with OneConnect for this to work but in my situation we were using OneConnect as was being applied to an application virtual server where the client was a server using multiplexing. Also one of the reasons we needed this as the full XFF headers changed depending on the which of the many servers sent the request so we needed to strip it back to a common identifier.
  • Hi,

    I recommand you to change :

    [lindex  [ split [HTTP::header X-Forwarded-For] "," ] 0]

    by

    [getfield [lindex  [HTTP::header values X-Forwarded-For]  0] "," 1]

    If the request contains multiple X-Forwarded-For headers, your code just take the latest header value. Not the first one.

  • Yann, You are correct for most headers where multiple headers exist with the same name however the XFF header gets appended, i.e. Format is;

     

    X-Forwarded-For: client, proxy1, proxy2
    

    So there should only ever be a single X-Forwarded-For header within a HTTP request with the first IP address being closest to the original. I could do the following:

     

    [getfield [HTTP::header X-Forwarded-For] "," 1]
    

    However I test a lot of code directly in TCL so the getfield commend is not valid but this is just down to personal preference.

     

  • Hi AMG,

     

    I'm not 100% sure, but AFAIK, the RFC allow the request to contain more than one HTTP header with the same name. There is nothing specific to X-Forwarded-For header, so I assume the limitations are the same as other headers.

     

    Moreover, in real life, I regularly face this kind of request containing several X-Forwarded-For headers. That's why I suggested to change the code to take into account this possibility.