Forum Discussion

Kuerten_772's avatar
Kuerten_772
Icon for Nimbostratus rankNimbostratus
Feb 13, 2014

APM Irule multiple access profile for one virtual server

Hello F5 Team,

 

i was asking if is possible to create irule to use multiple profile for one virtual, currently i have one access profile for one vs apm . Thank you for your help. Ahmed

 

  • Baron_of_Strath's avatar
    Baron_of_Strath
    Historic F5 Account

    Since I am apparently not able to edit, there was a small point of clarification about assigning the apm policy to the virtual servers which would be called from this irule:

     

    What you are asking is quite easy to do. I cannot take full credit for it completely. I took the SNI irule published by Joel Moses a few years back and changed it to simply extract the SNI and put that info into the variable called "tls_servername"

     

    If you look at the very bottom, you will see some generic logic to make a virtual call to the value of tls_servername and if that is unsuccessful, to look up the mapping in a datagroup called "Content_Switch_dg" which is simply a mapping of requested hostname to virtualserver name. As above, you would then create and apply your APM policy to the appropriate virtuals and everything would be nicely published behind a single public IP.

     

        when CLIENT_ACCEPTED {
    
                     We have a clientssl profile attached to this VIP but we need
                     to find an SNI record in the client handshake. To do so, we'll
                     disable SSL processing and collect the initial TCP payload.
    
            set default_tls_pool [LB::server pool]
                    set detect_handshake 1
                    TCP::collect
    
    }
    
    when CLIENT_DATA {
            set detect_handshake 1  
            TCP::collect  
            if { ($detect_handshake) } {
    
                     If we're in a handshake detection, look for an SSL/TLS header.
    
                    binary scan [TCP::payload] cSS tls_xacttype tls_version tls_recordlen
    
                     TLS is the only thing we want to process because it's the only
                     version that allows the servername extension to be present. When we
                     find a supported TLS version, we'll check to make sure we're getting
                     only a Client Hello transaction -- those are the only ones we can pull
                     the servername from prior to connection establishment.
    
                    switch $tls_version {
                            "769" -
                            "770" -
                            "771" {
                                    if { ($tls_xacttype == 22) } {
                                            binary scan [TCP::payload] @5c tls_action
                                            if { not (($tls_action == 1) && ([TCP::payload length] > $tls_recordlen)) } {
                                                    set detect_handshake 0
                                            }
                                    }
                            }
                            default {
                                    set detect_handshake 0
                            }
                    }
    
                    if { ($detect_handshake) } {
    
                     If we made it this far, we're still processing a TLS client hello.
                    
                     Skip the TLS header (43 bytes in) and process the record body. For TLS/1.0 we
                     expect this to contain only the session ID, cipher list, and compression
                     list. All but the cipher list will be null since we're handling a new transaction
                     (client hello) here. We have to determine how far out to parse the initial record
                     so we can find the TLS extensions if they exist.
    
                            set record_offset 43
                            binary scan [TCP::payload] @${record_offset}c tls_sessidlen
                            set record_offset [expr {$record_offset + 1 + $tls_sessidlen}]
                            binary scan [TCP::payload] @${record_offset}S tls_ciphlen
                            set record_offset [expr {$record_offset + 2 + $tls_ciphlen}]
                            binary scan [TCP::payload] @${record_offset}c tls_complen
                            set record_offset [expr {$record_offset + 1 + $tls_complen}]
    
                     If we're in TLS and we've not parsed all the payload in the record
                     at this point, then we have TLS extensions to process. We will detect
                     the TLS extension package and parse each record individually.
    
                            if { ([TCP::payload length] > $record_offset) } {
                                    binary scan [TCP::payload] @${record_offset}S tls_extenlen
                                    set record_offset [expr {$record_offset + 2}]
                                    binary scan [TCP::payload] @${record_offset}a* tls_extensions
    
                     Loop through the TLS extension data looking for a type 00 extension
                     record. This is the IANA code for server_name in the TLS transaction.
    
                                    for { set x 0 } { $x < $tls_extenlen } { incr x 4 } {
                                            set start [expr {$x}]
                                            binary scan $tls_extensions @${start}SS etype elen
                                            if { ($etype == "00") } {
    
                     A servername record is present. Pull this value out of the packet data
                     and save it for later use. We start 9 bytes into the record to bypass
                     type, length, and SNI encoding header (which is itself 5 bytes long), and
                     capture the servername text (minus the header).
    
                                                    set grabstart [expr {$start + 9}]
                                                    set grabend [expr {$elen - 5}]
                                                    binary scan $tls_extensions @${grabstart}A${grabend} tls_servername
                                                    set start [expr {$start + $elen}]
                                            } else {
    
                     Bypass all other TLS extensions.
    
                                                    set start [expr {$start + $elen}]
                                            }
                                            set x $start
                                    }
    
                     Check to see whether we got a servername indication from TLS. If so,
                     make the appropriate changes.
    
                                    if { ([info exists tls_servername] ) }      {
                                        log local0. "Requested host: $tls_servername"
    
                                        if { [catch { virtual $tls_servername } ]} {
                                            if { $::DEBUG == 1 } { log local0. "Virtual Server $tls_servername doesn't exist...Searching Datagroup"}
                                            if { [class match $tls_servername equals Content_Switch_dg]} {
                                                if { $::DEBUG == 1 } { log local0. "Server found in Datagroup" }
                                                virtual [class lookup $tls_servername Content_Switch_dg]
                                                TCP::release
                                            }
                                        } else {
                                            if { $::DEBUG == 1 } { log local0. "switching to virtual server $tls_servername" } 
                                               virtual $tls_servername
                                               TCP::release
                                            }
                                        }                        
    
                                      } else {
                                          Servername not found
                                         log local0. "Servername could not be determined"
                                      }                                  
    }
    }
    }
    • Jeppe_Koefoed's avatar
      Jeppe_Koefoed
      Icon for Employee rankEmployee
      The SNI part can be done easier: when CLIENTSSL_HANDSHAKE { set sni_exists [SSL::extensions exists -type 0] log local0.info "SSL extension type 0 exists: $sni_exists" if {$sni_exists} { set scan [binary scan [SSL::extensions -type 0] S1S1@9A* ext_type ext_len sni] log local0.info "SSL extension type 0 (SNI): $sni" } }
  • Not sure if anyone will stumble across this, but I was recently trying to figure this out for a Edge client setup. I wanted a single VS hosting the sign in, but then the policy and connectivity assignment were throwing me for a loop.

    What I ended up doing was using an iRule to check the landinguri the client was sending, and then I used branches on the iRule event to assign different Webtops and Network Access assignments based on the requested location.

    when ACCESS_POLICY_AGENT_EVENT {
    	if { [ACCESS::policy agent_id] eq "VPN_Resource_Check" } {
            log local0. "Current virtual server name: [virtual name]"
            log local0. "Current virtual server name: [HTTP::host]"
            log local0. "Current virtual server name: [SSL::sni name]"
            log local0. "[ACCESS::session data get session.server.landinguri]"
            switch [string tolower [ACCESS::session data get session.server.landinguri]] {
                "/nursing" { ACCESS::session data set session.custom.vpn "nursing" }
            }
        }
    }