Forum Discussion

Ken_B_50116's avatar
Ken_B_50116
Icon for Cirrostratus rankCirrostratus
Jun 12, 2012

How to select a pool based on client's hostname?

I need to write an iRule that will select a pool based on the requesting client's DNS hostname. I know an iRule can perform a reverse DNS lookup to get the client's hostname from the source IP address, but I'm not sure how to put it all together.

 

 

 

This sample shows a reverse DNS lookup:

 

 

https://devcentral.f5.com/wiki/iRules.Block_requests_by_reverse_DNS_record.ashx

 

 

However, being new to iRules I'm not sure the best way to work this into a script to fit my needs. One problem is that the sample code seems to be dependent on HTTP, however I am not concerned with the application layer. My iRule needs to work regardless of the application protocol.

 

 

 

This example assumes there are two data groups (non-prod-list and prod-list), each containing the hostnames of several servers. Does anyone think this will fly? Certainly it has problems.

 

 

when CLIENT_ACCEPTED {

 

 

Trigger a name lookup for new connections

 

set do_lookup 1

 

Check if we haven't done a lookup already on this connection

 

if { $do_lookup }{

 

 

Start a name resolution on the client IP address

 

NAME::lookup -ptr [IP::client_addr]

 

}

 

}

 

 

when NAME_RESOLVED {

 

 

set ptr [string tolower [NAME::response]]

 

 

if { $ptr starts_with $::non-prod-list } {

 

 

pool pool_a }

 

elseif { $ptr starts_with $::prod-list } {

 

 

pool pool_b }

 

 

}

 

else pool default_pool

 

}

 

 

 

 

 

 

 

 

 

10 Replies

  • If you're on 10.2.1 or higher you should use RESOLV::lookup as it's a simpler command which doesn't require coding in the NAME_RESOLVED event. Also, change the data group lookup from

    if { $ptr starts_with $::non-prod-list } {

    to:

    if { [class match $ptr starts_with non-prod-list]} {

    Note the use of the class command and the removal of the $:: prefix. The $:: prefix won't work in 10.x or higher.

    You probably also want to check if the ptr record ends with the data group strings as you might get a PTR record like client-x-y-z.example.com and want to match against example.com in the data group.

    Lastly you can remove the do_lookup variable and CLIENT_ACCEPTED is only triggered when a new client connection is established to the virtual server.

    Here's an untested example:

     Check the config options for tmm.resolv.retry and tmm.resolv.timeout on the RESOLV::lookup wiki page!
     https://devcentral.f5.com/wiki/iRules.resolv__lookup.ashx
    when RULE_INIT {
    
        Log debug to /var/log/ltm? 1=yes, 0=no.
       set static::dns_debug 1
    
        Use a DNS virtual server for redundancy
       set static::dns_server my_dns_vs
    
        Less optimally, hardcode a DNS server IP address
       set static::dns_server 4.2.2.2
    
        Data group name which maps the domain names to a pool name
        The logical data group format is expected to be:
          Name=domain1.org, value=prod_pool
          Name=domain1.com, value=non_prod_pool
        The exact format of the data group depends on the LTM version
        See these articles for details:
          v11 - https://devcentral.f5.com/Tutorials/TechTips/tabid/63/articleType/ArticleView/articleId/1086510/v11-iRules-Data-Group-Updates.aspx
          v10 - https://devcentral.f5.com/Tutorials/TechTips/tabid/63/articleType/ArticleView/articleId/1086448/iRules-Data-Group-Formatting-Rules.aspx
       set static::ptr_to_pool_dg "ptr_to_pool_dg"
    }
    when CLIENT_ACCEPTED {
    
        Get PTR for client's IP address
       set ptr [RESOLV::lookup @$static::dns_server -ptr [IP::client_addr]]
       if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Resolved $ptr"}
    
        Check if a pointer record was returned
       if {$ptr eq ""}{
    
           No PTR, so use the VS default pool
          if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: No PTR!"}
      pool [LB::server pool]
    
       } else {
    
           Check the data group named ptr_to_pool_dg to get the pool name
          set pool [class match -value -- $ptr ends_with $static::ptr_to_pool_dg]
          if {$pool eq ""}{
    
              No match for domain, so use the VS default pool
             if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: No pool found for $ptr in $static::ptr_to_pool_dg"}
             pool [LB::server pool]
    
      } else {
    
      Try to assign the matched pool, but use the default pool if the assignment fails
             if {[catch {pool $pool} result]}{
                if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Error assigning pool $pool. Using default pool [LB::server pool]. Error: $error"}
                pool [LB::server pool]
             }
          }
       }
    }
    

    Aaron
  • Excellent, thanks for the suggestions. My LTMs are running 10.2.1 at this time, so will follow your suggestions. I will review the code and do some testing and post back with the results.

     

     

    Thanks again!

     

     

  • What if I forget the datagroup idea and just use text matching like this:

     

     

    if { [class match $ptr starts_with "WTSAPP" OR "WTSAPPPROD"]} { pool pool_prod }

     

     

    else { pool pool_test }
  • Actually, per BZ360270, the -ptr result isn't cached like other resolution responses, so it makes sense to manually cache the result in the session table. I'm working on a revised iRule now.

     

     

    Also note that you'll need to install HF1 or higher for 10.2.1 for the -ptr resolution to work:

     

    http://support.f5.com/kb/en-us/solutions/public/12000/300/sol12378.html

     

     

    Aaron

     

     

     

  • Posted By hoolio on 06/12/2012 10:27 AM

     

    ...Also note that you'll need to install HF1 or higher for 10.2.1 for the -ptr resolution to work:

     

    http://support.f5.com/kb/en-us/solu...12378.html

     

     

     

    Luckily I'm running BIG-IP 10.2.1 Build 511.0 Hotfix HF3. I'm making the jump to version 11 at some time in the near future.
  • What if I forget the datagroup idea and just use text matching like this...

    You could use a switch statement to evaluate multiple strings that aren't in a data group:

    
    switch -glob -- [string tolower $ptr] {
       "*wtsappprod" {
          pool prod_pool
       }
       "*wtsapp" {
          pool preprod_pool
       }
    }
    

    I should have the caching -ptr example ready in a little while.

    Aaron
  • Here's an untested version that implements caching of the PTR records in the session table. Let me know if you see any issues testing this.

    
     Look up the client IP to map its pointer record suffix to a pool name
     Use a data group to map domain names to pool names
     Manually cache pointer records in the session cache as there is a bug (BZ360270) which prevents TMM from caching PTR records as it should.
    
     Check the config options for tmm.resolv.retry and tmm.resolv.timeout on the RESOLV::lookup wiki page!
     https://devcentral.f5.com/wiki/iRules.resolv__lookup.ashx
    
    when RULE_INIT {
    
     Log debug to /var/log/ltm? 1=yes, 0=no.
    set static::dns_debug 1
    
     Ideally, use a DNS virtual server to perfor lookups against for redundancy
    set static::dns_server my_dns_vs
    
     ...or less optimally, hardcode a DNS server IP address
    set static::dns_server 4.2.2.2
    
     Time (in seconds) to cache successful PTR lookups.
     If the entry is not accessed in this time period a new resolution attempt will be made.
     This is a workaround for BZ360270 which notes that RESOLV::lookup -ptr lookups are not cached like other records are
    set static::cache_time 3600
    
     Data group name which maps the domain names to a pool name
     The logical data group format is expected to be:
    Name=domain1.org, value=prod_pool
    Name=domain1.com, value=non_prod_pool
     The exact format of the data group depends on the LTM version
     See these articles for details:
    v11 - https://devcentral.f5.com/Tutorials/TechTips/tabid/63/articleType/ArticleView/articleId/1086510/v11-iRules-Data-Group-Updates.aspx
    v10 - https://devcentral.f5.com/Tutorials/TechTips/tabid/63/articleType/ArticleView/articleId/1086448/iRules-Data-Group-Formatting-Rules.aspx
    set static::ptr_to_pool_dg "ptr_to_pool_dg"
    
     If the pointer to pool mapping data group does not exist, log an error
    if {not [class exists $static::ptr_to_pool_dg]}{
    log local0.emerg "Required data group $static::ptr_to_pool_dg does not exist! Using VS default pool"
    }
    }
    when CLIENT_ACCEPTED {
    
     If the pointer to pool mapping data group does not exist, log an error and exit
    if {not [class exists $static::ptr_to_pool_dg]}{
    log "[IP::client_addr]:[TCP::client_port]: Required data group $static::ptr_to_pool_dg does not exist! Using VS default pool [LB::server pool]"
    return
    }
     Get pointer record for client IP address. 
     Check the session cache first using ptr_1.1.1.1 as the key, where 1.1.1.1 is the client IP address
    set ptr [table lookup "ptr_[IP::client_addr]"]
    
     Check if a pointer record was returned
    if {$ptr eq ""}{
    
     No cached ptr record so try a new resolution
    if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: No cached ptr, attempting resolution"}
    set ptr [RESOLV::lookup @$static::dns_server -ptr [IP::client_addr]]
    if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Resolved $ptr"}
    
     Check if a pointer record was returned
    if {$ptr eq ""}{
    
     No PTR, so use the VS default pool
    if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: No ptr from resolution, using default pool, [LB::server pool]"}
    pool [LB::server pool]
    
     Stop processing this event in this rule as there is no PTR
    return
    
    } else {
    
     New resolution returned a result so cache it in the session table using ptr_1.1.1.1 as the key
     where 1.1.1.1 is the client IP address
    if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Adding new ptr to cache for $static::cache_time seconds: $ptr"}
    table set "ptr_[IP::client_addr]" $ptr $static::cache_time indefinite
    }
    }
    
     We have a valid PTR either from cache or new resolution.
     Check the data group named ptr_to_pool_dg to get the pool name
    set pool [class match -value -- $ptr ends_with $static::ptr_to_pool_dg]
    if {$pool eq ""}{
    
     No match for domain, so use the VS default pool
    if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: No pool found for $ptr in $static::ptr_to_pool_dg"}
    pool [LB::server pool]
    
    } else {
    
     Try to assign the matched pool, but use the default pool if the assignment fails
    if {[catch {pool $pool} result]}{
    if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Error assigning pool $pool. Using default pool [LB::server pool]. Error: $error"}
    pool [LB::server pool]
    }
    }
    }
    

    Aaron
  • Again, thanks for taking the time to work up that code. For the sake of simplicity, I'm going to try a version that avoids needing any data groups. This iRule is focused on matching the hostname and not the domain name, or the end of the FQDN.

     

     

    when RULE_INIT {
    
        Log debug to /var/log/ltm? 1=yes, 0=no.
       set static::dns_debug 0
       set static::dns_server 10.10.9.8
    }
    
    when CLIENT_ACCEPTED {
    
        Get PTR for client's IP address
       set ptr [RESOLV::lookup @$static::dns_server -ptr [IP::client_addr]]
       if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Resolved $ptr"}
    
        Check if a pointer record was returned
       if {$ptr eq ""}{
           No PTR, so use the VS default pool
          if {$static::dns_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: No PTR!"}
      pool [LB::server pool]
    
       } else {
    
    switch -glob -- [string tolower $ptr] {
       "wtscern*" { pool pool_Multum_prod }
       "wtsc4prod*" { pool pool_Multum_prod }
       "wtsc4test*" { pool pool_Multum_nonprod }
       "wtsc4cert*" { pool pool_Multum_nonprod }
       "wtsc4edu*" { pool pool_Multum_nonprod }
       "vmqa*" { pool pool_Multum_nonprod }
      }
     }
    }
  • Oh, clearly this is important too:

    set static::cache_time 3600

    I will be sure to use that, as there's no need no do a new DNS lookup each time a client connects. Thanks for pointing that out.
  • Just a follow up on this. I did end up using my code above, which relies on some key elements that Aaron provided. The script runs well and testing has shown that clients whose hostnames meet certain criteria are sent to the correct pool. Thanks again for the assistance. Hoepfully this code can be of use to others in the future.