on 16-Jun-2015 05:28
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.
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.6Hi,
is there a way to tamper with the DNS response based on this information? We are running GTM. When the listener is receiving a DNS query, I would like to decide which virtual server out of the selected pool is used and posted back in the dns response depending on both location (based on the EDNS information) AND availability of the respective resource.
So, ideally, in analogy to the GSLB "Topology Record Builder" settings, I would like to set the datacenter information, but not based on the Client-IP in the Layer3 Header, but based on the EDNS client information, as shown in the code snippet.
Is this possible? Thanks!
Regards, Matt
Matt, if you have a second LTM/GTM device it is possible to SNAT the connection to match the ecs_address. You end up with something like:
when DNS_REQUEST {
...
snat $ecs_address
...
}
This works by using a feature of LTM/GTM of auto last hop to route the snat'd traffic back to the original device that received the request. It assumes that both LTM/GTM devices are L2 adjacent.
The config is
Device 1: LTM listener with DNS profile/LTM iRule performing SNAT. Pool is GTM listener on Device 2. Device 2: GTM listener with DNS profile (vanilla GTM)
Traffic flow:
Internet (LDNS IP) -> Device 1 (SNAT to ECS IP) -> Device 2 -> Device 1 -> Internet
Be warned that you are trusting the ECS records to intentionally spoof traffic to Device 2. I have only tested this in a lab environment and have noted that it does some weird things to the EDNS records that get passed along to Device 2.
As an FYI for anyone testing, using a recent DIG version (eg 9.10), the "+subnet" is used to send a ENDS0 request , eg:
$ dig @10.1.10.53 +subnet=205.168.2.3/32
The response should show
;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096
; CLIENT-SUBNET: 205.168.2.3/32/0
;; QUESTION SECTION:
hello, is it possible to configure persistency based on the source IP captured via the eDNS header ?
@alex. Not sure if you saw that this is now a feature in BIG-IP DNS 14.0 (I just updated this post to mention that as well). Using Client Subnet in DNS Requests.
 
If you are not running BIG-IP DNS 14.0; then you could potentially use a custom persist method with an iRule: K7392 talks about using HTTP URI, but you could use the string value from eDNS in theory. I haven't thought it through fully, so not sure that would work.
 
hi Eric, i have the feeling version 14.0 + 14.1 are not stable and recommended versions yet. i am right to saythe irule in the K7392 only applies to LTM and not GTM ?
thanks in advance.
Thanks for the article. Actually it would be helpful to get the address familiy information as well to differentiate between IPv4 and IPv6 addresses provided in the EDNS0-ECS extension.
According to RFC7871 the client indicates the address family, the prefix lenght, scope and the part of the IP address matching the prefix length. Trailing zeros wont be transmitted. So a network of 64.64.64.0/24 would be send with an IP address familiy of x00 x01 (IPv4), a hex string of x18 (Prefix Len of 24) and a pattern of x40 x40 x40 indicating the network. Bits after the prefix wont be send as truncating according to prefix len is mandatory. Dumping the [DNS::edns0 subnet address] returns an address of 64.64.64.254, which is obviously wrong. Problems appear when trying to apply a binary scan to the result. To work around the issue I need to pad the missing zeros to convert the pattern into a proper IP address for further processing. Without information about the address family I cannot differentiate between IPv4 and IPv6. Is there a solution planned? Thanks in advance, Stephan
Stephan, it's been a long time since I looked at this code. Since this became a feature in 14.1.x I'd recommending using that version if possible. Otherwise I'm not sure the best way to pad the number to deal with the issue that you're describing.
Hi Eric,
thanks for the quick reply. My client is running v12 and the same behaviour with the fuction was observed in v14 unfortunately.
Actually my client needs to handle specific DNS requests in the listener iRule completely without forwarding them internally to gtmd/bind.
So we cannot use the built-in feature.
My concern is, that the function always parses 4 bytes even the query contains only the network address according to the prefix length.
This might result in bad memory allocation ...
To handle the EDNS0-ECS information properly for IPv4 I worked around as follows:
if {[DNS::edns0 exists] &! [catch { DNS::edns0 subnet address }]} {
set ecs_prefix [DNS::edns0 subnet source]
set ecs_scope [DNS::edns0 subnet scope]
# generate a string equivalent to prefix length (expr (pow) not implemented); missing bits padded by zeros) and scan to vars
binary scan [binary format B32 [string repeat 1 ${ecs_prefix}]] cccc maskd1 maskd2 maskd3 maskd4
#log local0. "\[DNS::edns0 subnet address\]: [DNS::edns0 subnet address] (\[DNS::edns0 subnet source\]: [DNS::edns0 subnet source])"
# mask provided IPv4 address with prefix
set ecs_subnet_masked [IP::addr [DNS::edns0 subnet address] mask [expr {${maskd1} & 0xff}].[expr {${maskd2} & 0xff}].[expr {${maskd3} & 0xff}].[expr {${maskd4} & 0xff}]]
unset maskd1 maskd2 maskd3 maskd4
log local0. "masked EDNS0-ECS subnet: ${ecs_subnet_masked}/${ecs_prefix} ([whereis ${ecs_subnet_masked} country])"
}
I will do some further testing with EDNS0-ECS providing IPv6 information.
As mentioned before, it would be great to get an extension to the DNS::edns0 subnet function to check the address family value.
Otherwise I need to implement an additional test for the address type.
(Perhaps its already there and just not documented?)
I will open a service request with the F5 support team and keep you posted.
Cheers, Stephan
In service request C3152801 the F5 support confirmed a bug (ID832769: [DNS::edns0 subnet address] printing random octets when edns client subnet mask is /24 or lower) for TMOS v12 which is fixed in TMOS v14.
In case you are using DNS::return in your TMOS v12 iRule, the EDNS0-ECS information will be inserted automatically. So there is no need to insert this information in the context of DNS_RESPONSE.
But in case the request is handled by gtmd or bind you will need to insert the EDNS0-ECS information the DNS_RESPONSE context.
TMOS v14 has changes in behaviour and fixes. In case you are upgrading, review your iRules, please.