Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology

iRule Choose pool base on SNI and disable ssl base on SNI


We have the need to select pool via irule but we don't want to decrypt all HTTPS traffic.

Can we do this? (This is outbound traffic)

  1. We have list of URL in iRule Datagroup

  2. if user access HTTPS website -> F5 detect SNI and check if that URL is in datagroup or not.

    • if it's in datagroup -> Load balance to pool A.

    • if it's not in datagroup -> disable http profile, ssl profile and other profile (if any) to just forward HTTPS traffic only. don't decrypt/encrypt anything.

Is this possible?

Thank you

Rate this Question
Comments on this Question
Comment made 06-Feb-2018 by kridsana 641

Or we have to only rely on SSL Forward Proxy feature ?

Comment made 07-Feb-2018 by surgeon

hmm, interesting. If you do no want offload ssl on server side than you need somehow to re-initiate ssl handshake between a client and back-end server, since ssl handshake is already finished with big-ip when iRule checks for URL

SSL forward proxy bypass looks reasonable. big-ip should bypass ssl, based on host name in the server cert If ssl forward proxy is enabled then ssl handshake on the client side will not finish until big-ip receives certificate from the back-end server.

Comment made 07-Feb-2018 by kridsana 641

Hi Surgeon

From irule stanislas provide below . Is the result is the same as SSL Forward proxy ?

I mean just choose pool base on Hostname. We don't want to do other than this.



Answers to this Question


I changed the Kevin Stewart's irule to solve your issue (not tested, developed with tcl shell out of box)

Updated to add unset command and remove useless temp variables

    set default_pool [LB::server pool]
    set payload [TCP::payload]
    set payloadlen [TCP::payload length]

    # - tls_record_content_type : Reccord layer content-type     (1 byte)
    #    Handshake value is 22 (required for CLIENT_HELLO packet)
    # - tls_major_version : SSL / TLS version SSLv3 and TLS share major version value 3 (1 byte)
    # - tls_minor_version : SSL / TLS minor version. (1 byte)
    #    SSLv3 value is 0 (doesn't support SNI, not valid in first condition)
    #    TLS_1.0 value is 1
    #    TLS_1.1 value is 2
    #    TLS_1.2 value is 3
    # - tls_recordlen : Reccord layer content length (2 bytes) : must match payload length
    #       TLS Hanshake protocol
    #       - tls_action : Handshake action (1 byte) : CLIENT_HELLO = 1
    #       - handshake length not stored in a variable (3 bytes)
    #       -  SSL / TLS handshake major version not stored in a variable (1 byte)
    #       -  SSL / TLS handshake minor version not stored in a variable (1 byte)
    #       - hanshake random not stored in a variable (32 bytes)
    #       - tls_sessidlen : handshake sessionID length (1 byte)
    #       - handshake sessionID (length defined by sessionID length value, max 32-bit)
    #       - CipherSuites length (2 bytes)
    #       - CipherSuites (length defined by CipherSuites length value)
    #       - Compression length (2 bytes)
    #       - Compression methods (length defined by Compression length value)
    #       - Extensions 
    #           - Extension length (2 bytes)
    #           - list of Extensions records (length defined by extension length value)
    #               - extension record type (2 bytes)
    #               - extension record length (2 bytes)
    #               - extension data (length defined by extension record length value)
    #   SNI extension data format:
    #       - SNI record length (2 bytes)
    #       - SNI record data (length defined by SNI record length value)
    #           - SNI record type (1 byte)
    #           - SNI record value length (2 bytes)
    #           - SNI record value (length defined by SNI record value length value)

    # If valid TLS 1.X CLIENT_HELLO handshake packet
    if { [binary scan $payload cccScx37c tls_record_content_type tls_major_version tls_minor_version tls_recordlen tls_action tls_sessidlen] == 6 && \
        ($tls_record_content_type == 22) && \
        ($tls_major_version == 3) && ($tls_minor_version > 0) && \
        ($tls_action == 1) && \
        ($payloadlen == $tls_recordlen+5)} {

        # skip past the session id
        set record_offset [expr {44 + $tls_sessidlen}]

        # skip past the cipher list
        binary scan $payload @${record_offset}S tls_ciphlen
        set record_offset [expr {$record_offset + 2 + $tls_ciphlen}]

        # skip past the compression list
        binary scan $payload @${record_offset}c tls_complen
        set record_offset [expr {$record_offset + 1 + $tls_complen}]

        # check for the existence of ssl extensions
        if { ($payloadlen > $record_offset) } {
            # skip to the start of the first extension
            binary scan $payload @${record_offset}S tls_extenlen
            set record_offset [expr {$record_offset + 2}]
            # Check if extension length + offset equals payload length
            if {$record_offset + $tls_extenlen == $payloadlen} {
                # for each extension
                while { $record_offset < $payloadlen } {
                    binary scan $payload @${record_offset}SSx3S etype elen erlen
                    if { ($etype == 0) } {
                        # if it's a servername extension read the servername
                        # SNI record value start after extension type (2 bytes), extension record length (2 bytes), record type (2 bytes), record type (1 byte), record value length (2 bytes) = 9 bytes
                        binary scan $payload @[expr {$record_offset + 9}]A${erlen} tls_servername
                        #set record_offset [expr {$record_offset + $elen + 4}]
                    } else {
                        # skip over other extensions
                        set record_offset [expr {$record_offset + $elen + 4}]
        } else {
        log local0. "packet is not a valid TLS 1.X CLIENT_HELLO handshake"
    unset -nocomplain payload payloadlen tls_record_content_type tls_major_version tls_minor_version tls_recordlen tls_action tls_sessidlen record_offset tls_ciphlen tls_complen tls_extenlen etype elen erlen 

    if { [info exists tls_servername] } {
        log local0. "tls_servername = ${tls_servername}"
        switch [string tolower $tls_servername] {
            "app1.company.com" {pool pool1}
            "app2.company.com" {pool pool2}
            default {pool $default_pool}
    } else {
        log local0. "packet is a valid TLS 1.X CLIENT_HELLO handshake but doesn't contain server name extension"
        pool $default_pool

Comments on this Answer
Comment made 07-Feb-2018 by kridsana 641

Thank you very much Stanislas

I've test this and it's working fine. :)

May I ask more question?

Is this type of iRule (collect payload, check it and then process traffic) require many performance? and make it slower.

(Cause I would like to use this on LTM which act as Internet Gateway.)

Thank you

Comment made 07-Feb-2018 by Stanislas Piron 10677

CLIENT_ACCEPTED only occurs once after TCP handshake and there is no more collect command in CLIENT_DATA event and it finish with TCP::release to prevent loop on client data.

As described in TCP::collect wiki page CLIENT_DATA event will be triggered for every received packet, which means it will collect up to 1460 bytes (TCP MSS when MTU set to 1500).

So the payload max size is 1460 bytes if there is a DOS attack and if not detected as SSL with right size, it leaves the irule.

A good solution is to add a unset payload payloadlen command at the end of the irule to release the variable and prevent memory usage.

Edit : the code was updated above to unset payload variable.

Comment made 11-Feb-2018 by Stanislas Piron 10677

I updated this code and posted it on Devcentral codeshare section.