GTM return LDNS IP to client
Problem this snippet solves: We do a lot of our load balancing based on topology rules, so it's often very useful to know where the DNS request is actually coming from rather than just the client's IP and the DNS servers they have configured. Especially if they're behind an ADSL router doing NAT or some other similar set up. This rule simply returns the IP address of the LDNS that eventually made the query to the GTM device in the response to a lookup for the WideIP using the rule, as well as logging the response and perceived location. Code : rule "DNS_debug" partition "Common" { when DNS_REQUEST { host [IP::client_addr] log local0.err "Debug address : [IP::client_addr] [whereis [IP::client_addr]]" } }817Views1like2CommentsDNS Tunneling Protection
Problem this snippet solves: This snippet helps blocking DNS tunneling that may be observed in modern enterprise networks. There is a Data Group List that allows you to specify DNS domain names that are known to be used for DNS tunneling and you do not want to receive email alerts for them. There is one known DNS domain used for tunneling - *.mac.sophosxl.net. We saw DNS tunnels from MacBooks with Sophos Antivirus installed. How to use this snippet: Just add this iRule to your Virtual Server (VS) that serves as DNS for your clients. This VS must have an DNS profile attached, so you need a BIG-IP DNS license. Do not forget to add Data Group List, like this ltm data-group internal DGL-DNS-TUNNEL { records { mac.sophosxl.net { } } type string } If you need to configure email alerts add this code to /config/user_alert.conf: alert SECPOL_DNS_TUNNEL "Detected DNS tunneling for (.*)" { snmptrap OID=".1.3.6.1.4.1.3375.2.4.0.302"; email toaddress="$ALERT_ADDRESS" fromaddress="$ADC_ADDRESS" body="Security Policy: Detected DNS tunneling" } Replace $ALERT_ADDRESS and $ADC_ADDRESS with appropriate email addresses that are valid in your environment Code : when DNS_REQUEST { if {[string length [DNS::question name]] > 231} { if {[class match [DNS::question name] ends_with DGL-DNS-TUNNEL]} { log local0. "Detected possible DNS tunneling for [IP::remote_addr]. Request [DNS::question name] dropped" } else { log local0. "Detected DNS tunneling for [IP::remote_addr]. Request [DNS::question name] dropped" } DNS::drop } } Tested this on version: 13.01.3KViews0likes3CommentsDNS Tunnel Mitigation v2
Problem this snippet solves: (Solution from Pedro Haoa) Due to some people attempt DNS tunneling to pass data frames inside of DNS records to the Internet and the lack of information around here, I'm going to share with you some basic code for DNS Tunnel Mitigation on the BIG-IPs. This irule put some overhead in your CPU so check it with caution. The idea is to improve this code (I'm looking for less overhead) here in DevCentral and try to build a better base solution for the most common techniques. You can use the DNS Protocol Security, DNS Anti-DDoS and IP Intelligence features to get the most comprehensive solution. Remember that there are a lot of DNS Tunneling utilities with a wide range of capabilities and options, so this is one of many forms to mitigate some of the attacks. How to use this snippet: LTM + AFM + DNS Services BIG-IP AFM (Protocol Security) In Security ›› Protocol Security : Security Profiles : DNS ›› Create a New Security Profile and exclude obsolete record types like MD, MF, MAILA, NULL, HINFO, SPF, etc. Then apply to your DNS profile associated with your Listener. BIG-IP DNS (LTM Data Groups and iRule) Creating DNS Tunnel Query type Data Group BIG-IP AFM (Protocol Security) In Security ›› Protocol Security : Security Profiles : DNS ›› Create a New Security Profile and exclude obsolete record types like MD, MF, MAILA, NULL, HINFO, SPF, etc. Then apply to your DNS profile associated with your Listener. BIG-IP DNS (LTM Data Groups and iRule) Creating DNS Tunnel Query type Data Group create ltm data-group internal TunnelType records replace-all-with { CNAME { } } type string modify ltm data-group internal TunnelType records add { TXT { } } modify ltm data-group internal TunnelType records add { SRV { } } modify ltm data-group internal TunnelType records add { KEY { } } Creating Whitelist Data Group create ltm data-group internal Dominios_Lista_Blanca records replace-all-with { facebook.com { data facebook.com } } type string modify ltm data-group internal Dominios_Lista_Blanca records add { instagram.com { data instagram.com } } modify ltm data-group internal Dominios_Lista_Blanca records add { fbcdn.net { data fbcdn.net } } modify ltm data-group internal Dominios_Lista_Blanca records add { google.com { data google.com } } modify ltm data-group internal Dominios_Lista_Blanca records add { googleapis.com { data googleapis.com } } Creating Blacklist Data Group create ltm data-group internal Dominios_Lista_Negra records replace-all-with { dnstunnel.de { data dnstunnel.de } } type string modify ltm data-group internal Dominios_Lista_Negra records add { cutheatergroup.cn { data cutheatergroup.cn } } modify ltm data-group internal Dominios_Lista_Negra records add { demodomain.cz { data demodomain.cz } } modify ltm data-group internal Dominios_Lista_Negra records add { buo.cc { data buo.cc } } modify ltm data-group internal Dominios_Lista_Negra records add { pdk.lcn.cc { data pdk.lcn.cc } } Code : when RULE_INIT { # Max DNS queries during detection period per source IP / destination domain set static::maxq 180 # Detection & Blocking Period set static::btime 60 } when DNS_REQUEST { set srcip [IP::remote_addr] set qtype [DNS::question type] set DomOrigen [domain [DNS::question name] 4] set key "$srcip:$DomOrigen" if { ([class match $qtype equals TunnelType]) and [DNS::len] > 512 } { if {[class match $DomOrigen ends_with Dominios_Lista_Blanca] }{ return } elseif {[class match $DomOrigen ends_with Dominios_Lista_Negra] }{ DNS::drop return } elseif {[table lookup $key] ne ""} { set count [table incr $key] if {$count > $static::maxq} { DNS::drop return } } else { table add $key 1 indef $static::btime } } } Tested this on version: No Version Found2.4KViews2likes6CommentsGTM Monitors internal LTM, but I need Public IP as Answer
Problem this snippet solves: A WideIP is linked to an LTM Virtual Server that uses Internal IP Addresses. the DNS should reply with External IP addresses. although its possible via gui, its quiet tricky to get the monitors and the translation right. for your convenience , here is an Irule that does just that. How to use this snippet: cut and past this code into a new Irule under DNS->Delivery->Irules->Irule List and then add it to the DNS Listener.this Irule fixes 2 A records. a.a.a.a = internal ip address#1 aaa.aaa.com. = the A record#1 b.b.b.b = external ip address#1 c.c.c.c = internal ip address#2 ccc.ccc.com. = the A record#2 d.d.d.d = external ip address#2 Code : when DNS_RESPONSE { set rrs [DNS::answer] foreach rr $rrs { if { ([DNS::rdata $rr] eq "a.a.a.a")} { DNS::answer clear DNS::answer insert [DNS::rr "aaa.aaa.com. IN A b.b.b.b"] } elseif { ([DNS::rdata $rr] eq "c.c.c.c")} { DNS::answer clear DNS::answer insert [DNS::rr "ccc.ccc.com. IN A d.d.d.d"] } } }771Views1like5CommentsExport GTM/DNS Configuration in CSV - tmsh cli script
Problem this snippet solves: This is a simple cli script used to collect all the WideIP, LB Method, Status, State, Pool Name, Pool LB, Pool Members, Pool Fall back, Last Resort pool info in CSV format. A sample output would be like below, One can customize the code to extract other fields available too. Check out my other codeshare of LTM report. Note: The codeshare may get multiple version, use the latest version alone. The reason to keep the other versions is for end users to understand & compare, thus helping them to modify to their own requirements. Hope it helps. How to use this snippet: Login to the GTM/DNS, create your script by running the below commands and paste the code provided in snippet, tmsh create cli script gtm-config-parser Delete the proc blocks, so it looks something like below, create script gtm-config-parser { ## PASTE THE CODE HERE ## } and paste the code provided in the snippet. Note: When you paste it, the indentation may be realigned, it shouldn't cause any errors, but the list output would show improperly aligned. Feel free to delete the tab spaces in the code snippet & paste it while creating, so indentation is aligned properly. And you can run the script like below, tmsh run cli script gtm-config-parser > /var/tmp/gtm-config-parser-output.csv And get the output from the saved file, open it on excel. Format it & use it for audit & reporting. cat /var/tmp/gtm-config-parser-output.csv Feel free to add more elements as per your requirements. For version 13.x & higher, there requires a small change in the code. Refer the comments section. Thanks to @azblaster Code : proc script::run {} { puts "WIP,LB-MODE,WIP-STATUS,WIP-STATE,POOL-NAME,POOL-LB,POOL-MEMBERS,POOL-FB,LASTRESORT-POOL" foreach { obj } [tmsh::get_config gtm wideip all-properties] { set wipname [tmsh::get_name $obj] set wippools [tmsh::get_field_value $obj pools] set lbmode [tmsh::get_field_value $obj "pool-lb-mode"] set lastresort [tmsh::get_field_value $obj "last-resort-pool"] foreach { status } [tmsh::get_status gtm wideip $wipname] { set wipstatus [tmsh::get_field_value $status "status.availability-state"] set wipstate [tmsh::get_field_value $status "status.enabled-state"] } foreach wippool $wippools { set pool_name [tmsh::get_name $wippool] set pool_configs [tmsh::get_config /gtm pool $pool_name all-properties] foreach pool_config $pool_configs { set pool_lb [tmsh::get_field_value $pool_config "load-balancing-mode"] set pool_fb [tmsh::get_field_value $pool_config "fallback-mode"] if { [catch { set member_name [tmsh::get_field_value $pool_config "members" ]} err] } { set pool_member $err } else { set pool_member "" set member_name [tmsh::get_field_value $pool_config "members"] foreach member $member_name { append pool_member "[lindex $member 1] " } } puts "$wipname,$lbmode,$wipstatus,$wipstate,$pool_name,$pool_lb,$pool_member,$pool_fb,$lastresort" } } } } Tested this on version: 11.63.6KViews2likes6CommentsExport GTM/DNS Virtual Servers Configuration in CSV - tmsh cli script
Problem this snippet solves: This is a simple cli script used to collect all the virtual-servers name, its destination created in a server or ltm server. A sample output would be like below, How to use this snippet: This is similar to my other share - https://devcentral.f5.com/s/articles/Export-GTM-DNS-Configuration-in-CSV-tmsh-cli-script Login to the GTM/DNS, create your script by running the below commands and paste the code provided in snippet, tmsh create cli script gtm-vs Delete the proc blocks, so it looks something like below, create script gtm-vs { ## PASTE THE CODE HERE ## } and paste the code provided in the snippet. Note: When you paste it, the indentation may be realigned, it shouldn't cause any errors, but the list output would show improperly aligned. Feel free to delete the tab spaces in the code snippet & paste it while creating, so indentation is aligned properly. And you can run the script like below, tmsh run cli script gtm-vs > /var/tmp/gtm-vs-output.csv And get the output from the saved file, open it on excel. Format it & use it for audit & reporting. cat /var/tmp/gtm-vs-output.csv Feel free to add more elements as per your requirements. Code : proc script::run {} { puts "Server,Virtual-Server,Destination" foreach { obj } [tmsh::get_config gtm server] { set server [tmsh::get_name $obj] foreach { vss } [tmsh::get_config gtm server $server virtual-servers] { set vs_set [tmsh::get_field_value $vss virtual-servers] foreach vs $vs_set { set vs_name [tmsh::get_name $vs] puts $server,$vs_name,[tmsh::get_field_value $vs destination] } } } } Tested this on version: 13.11.4KViews3likes2CommentsDisable DNS Express to allow recursion of a delegated sub-domain
Problem this snippet solves: If you are using GTM to act both a authoritative slave with DNS Express and as a recursive cache, recursion will not work if a request is made for a delegated sub-domain if the parent domain exists in DNS Express. i.e. domain.com exists in DNS Express but has delegated the dev.domain.com sub-domain to a different set of name server. Any request to dev.domain.com will just get a referral rather than being recursed. This is because of the order of operations in GTM, https://support.f5.com/kb/en-us/solutions/public/14000/500/sol14510.html. Recursion is the very last process that could happen and since DNS Express makes an authoritative referral response no recursion will occur. How to use this snippet: To use this your listener and corresponding DNS profile need to have DNS Express configured and recursion enabled(cache). Then the iRule just needs to be attached to the listener. Code : when DNS_REQUEST { #query DNS Express to look for a sub-domain delegation set rrr [DNS::query dnsx [DNS::question name] [DNS::question type]] #evaluate if the queried zone is defined in DNS Express #empty response indicates DNS Express does not have the requested domain #so we should exit and continue to recursion if {$rrr equals "{} {} {}"}{return} #check if DNS Express response is a delegated sub-domain referral if { [lindex $rrr 0] equals "" && [DNS::type [lindex [lindex $rrr 1] 0]] equals "NS"} { #no ANSWER was returned AND AUTHORITY is an NS record(not a SOA) #this is a referral so we should disble DNS Express to allow for the subdomain to be recursed DNS::disable dnsx } } Tested this on version: 11.61.4KViews0likes6CommentsBIG-IP DNS Forwarder Support via iRule
Problem this snippet solves: The BIG-IP DNS Product does not currently (without named) support DNS forwarders. Some customers wish to avoid exposing named but want forwarder support. How to use this snippet: Apply this iRule to your BIG-IP DNS Listener and configure a datagroup Code : when DNS_REQUEST { set forwarded 0 if {[class match [DNS::question name] ends_with forwarded_zones]}{ set count [table incr [class match -element [DNS::question name] ends_with forwarded_zones]] log "Count for [class match -element [DNS::question name] ends_with forwarded_zones]]: $count" DNS::disable dns-express cache bind gtm set forwarders [class match -value [DNS::question name] ends_with forwarded_zones] log "Query question: [DNS::question name] Type: [DNS::question type] - Forwarders: $forwarders" set forwarder_list [split $forwarders] set forwarder [lindex $forwarder_list [expr $count % [llength $forwarder_list]]] log "Forwarder: $forwarder" translate address enable snat automap node $forwarder } } Tested this on version: 13.01.2KViews0likes4CommentsBIG-IP DNS Express - Private Zone Blocker
Problem this snippet solves: With BIG-IP DNS; you cannot enable/disable configured DNS Express Zones on a per-listener basis. This makes scenarios where a single BIG-IP DNS system has listeners exposed to internal networks with RFC1918 addresses and public Internet networks. DNS Express doesn't support DNS Views, in short. This iRule allows you to configure a datagroup containing "disabled_zones" and the iRule will validate if the query matches a zone listed in disabled_zones. If it gets a match, it simply returns nothing. Additionally, the iRule examines all responses and checks that resource records in the response do not contain RFC1918 addresses and if it finds them, it removes those Resource Records. All code in the "DNS_RESPONSE" event can be commented or deleted if this behavior isn't desired. How to use this snippet: enable iRule on DNS listener (most likely a listener available only to private network clients) and configure "disabled_zones" data group as shown in example. Log lines can be deleted or commented out once proper operation of the rule is confirmed and understood or retained for purposes of auditing queries that are dropped/blocked. Code : when DNS_REQUEST { log "Got request from: [IP::remote_addr] for [DNS::question name]" if {[class match [DNS::question name] ends_with disabled_zones]}{ log "Query for [DNS::question name] is for a disabled zone - Dropping" DNS::return } } when DNS_RESPONSE { log "Got Response = [DNS::answer]" set rrs [DNS::answer] set privateresponse 0 foreach rr $rrs { log "DNS Response rr: $rr" if {[DNS::type $rr] == "A"}{ if {[class match [DNS::rdata $rr] equals private_net]}{ set privateresponse 1 DNS::answer remove $rr } log "DNS RR data: [DNS::rdata $rr]" } } if {$privateresponse}{ log "DNS response contains private addresses" } } Tested this on version: 13.0497Views0likes1CommentImplementing Client Subnet DNS Requests
Problem this snippet solves: Update 2018-10-23: As of BIG-IP DNS 14.0 there is now a checkbox feature for edns-client-subnet. Please see: Using Client Subnet in DNS Requests. The following is still useful if you want to customize your responses. Original post: Using an iRule and edns-client-subnet (ECS) we can improve the accuracy of F5 GTM’s topology load balancing. DevCentral Article: Implementing Client Subnet DNS Requests How to use this snippet: There are two different iRules. One is an LTM iRule and the second is a GTM iRule. These should be deployed separately. Code : # # LTM iRule # # comply with draft to respond with ECS when DNS_REQUEST { if { [DNS::edns0 exists] &! [catch { DNS::edns0 subnet address }] } { set ecs_address [DNS::edns0 subnet address] set ecs_source [DNS::edns0 subnet source] set ecs_scope [DNS::edns0 subnet scope] log local0. "Received EDNS request from [IP::client_addr]:$ecs_address/$ecs_source/$ecs_scope" } } when DNS_RESPONSE { if { [info exists ecs_address] } { DNS::edns0 subnet address $ecs_address DNS::edns0 subnet source $ecs_source #DNS::edns0 subnet scope $ecs_scope # hardcode the desired scope to be /24, not sure this is OK DNS::edns0 subnet scope 24 } } # # GTM iRule # when DNS_REQUEST { set ldns [IP::client_addr] log local0. "LDNS LOC: $ldns [whereis $ldns]" if { [DNS::edns0 exists] &! [catch { DNS::edns0 subnet address }] } { set gtm_ecs_address [DNS::edns0 subnet address] set gtm_ecs_source [DNS::edns0 subnet source] set gtm_ecs_scope [DNS::edns0 subnet scope] set ldns $gtm_ecs_address log local0. "ECS LOC: $gtm_ecs_address [whereis $ldns]" } set loc [whereis $ldns] if { $loc contains "NA" } { log local0. "NA" } elseif { $loc contains "AS" } { log local0. "Asia" } elseif { $loc contains "EU" } { log local0. "Europe" } else { log local0. "All other" } } Tested this on version: 11.62.6KViews1like16Comments