Implementing 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.6- NabarunNimbostratus
Hi ,
Is it require any extra license for EDNS ? Can support EDNS on 12.1.3.5 version ?
- SWJOCirrostratus
Hi
Can I get sample VIP configuration which LTM i-Rule applied for?
with regards,
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
- Eric_ChenEmployee
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.