From: Charles-Henri Bruyand Date: Tue, 17 Apr 2018 05:32:44 +0000 (+0200) Subject: auth: test GeoIP related features of LUA Records X-Git-Tag: dnsdist-1.3.1~136^2~7 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=847e724ee72d782388f10eb86f74698c3feaca9c;p=thirdparty%2Fpdns.git auth: test GeoIP related features of LUA Records --- diff --git a/modules/geoipbackend/regression-tests/GeoLiteCity.mmdb.b64 b/modules/geoipbackend/regression-tests/GeoLiteCity.mmdb.b64 index bee561f26f..60da132432 100644 --- a/modules/geoipbackend/regression-tests/GeoLiteCity.mmdb.b64 +++ b/modules/geoipbackend/regression-tests/GeoLiteCity.mmdb.b64 @@ -13,21 +13,23 @@ AACgAABbAAAAoAAAXAAAAKAAAF0AAACgAABeAAAAoAAAXwAAAKAAAGAAAACgAABhAAAAoAAAYgAA AIEAAGMAAACgAABkAAAAoAAAZQAAAKAAAGYAAACgAABnAAAAoAAAoAAAAGgAAGkAAACgAABqAAAA oAAAawAAAKAAAGwAAACgAABtAAAAoAAAbgAAAKAAAG8AAAB4AACgAAAAcAAAcQAAAKAAAHIAAACg AABzAAAAoAAAdAAAAKAAAHUAAACgAAB2AAAAoAAAdwAAAKAAAKAAAACwAAB5AAAAoAAAegAAAKAA -AHsAAACgAAB8AAAAoAAAfQAAAKAAAH4AAACgAAB/AAAAoAAAoAAAAIAAAKAAAAFsAACgAAAAggAA +AHsAAACgAAB8AAAAoAAAfQAAAKAAAH4AAACgAAB/AAAAoAAAoAAAAIAAAKAAAAG1AACgAAAAggAA oAAAAIMAAKAAAACEAACgAAAAhQAAoAAAAIYAAKAAAACHAACIAAAAoAAAiQAAAKAAAIoAAACgAACL AAAAoAAAjAAAAKAAAI0AAACgAACOAAAAoAAAjwAAAKAAAJAAAACgAACRAAAAoAAAkgAAAKAAAJMA AACgAACUAAAAoAAAlQAAAKAAAJYAAACgAACXAAAAoAAAmAAAAKAAAJkAAACgAACaAAAAoAAAmwAA -AKAAAJwAAACgAACdAAAAoAAAngAAAJ8AAKAAAAHmAAIwAAACbwAAAAAAAAAAAAAAAAAAAADkRGNp -dHniSmdlb25hbWVfaWTDINUmRW5hbWVz4UJlbkhSZXNlYXJjaEljb250aW5lbnTjRGNvZGVCT0Mg -B8Nfci8gFuFCZW5HT2NlYW5pYUdjb3VudHJ54yAHwx+zEEhpc29fY29kZUJBVSAW4UJlbklBdXN0 -cmFsaWFIbG9jYXRpb27jT2FjY3VyYWN5X3JhZGl1c6EBSGxhdGl0dWRlaD/wAAAAAAAASWxvbmdp -dHVkZWg/8AAAAAAAAOQgAeIgB8NYkRIgFuFCZW5ITXVraWx0ZW8gKeMgNEJOQSAHw19yLSAW4UJl -bk1Ob3J0aCBBbWVyaWNhIFDjIAfDX2XhIF9CVVMgFuFCZW5NVW5pdGVkIFN0YXRlcyB74yCFoQEg -l2hAR/TdLxqfviCpaMBek3gDRtxd4yAB4iAHwQEgFuFCZW5CQzEgUOMgB8EBIF9CTzEgFuFCZW5D -TyAxTHN1YmRpdmlzaW9ucwEE4yAHwQEgX0JMMSAW4UJlbkNMIDHjIAHiIAfBAiAW4UJlbkJDMiBQ -4yAHwQIgX0JPMSAW4UJlbkNPIDIhXQEE4yAHwQIgX0JMMiAW4UJlbkNMIDLjIAHiIAfBAyAW4UJl -bkJDMyBQ4yAHwQMgX0JPMSAW4UJlbkNPIDMhXQEE4yAHwQMgX0JMMyAW4UJlbkNMIDOrze9NYXhN -aW5kLmNvbelbYmluYXJ5X2Zvcm1hdF9tYWpvcl92ZXJzaW9uoQJbYmluYXJ5X2Zvcm1hdF9taW5v -cl92ZXJzaW9uoEtidWlsZF9lcG9jaAQCWk1MMk1kYXRhYmFzZV90eXBlTEdlb0NpdHktTGl0ZUtk -ZXNjcmlwdGlvbuFCZW5TTW9jayBnZW9pcCBkYXRhYmFzZUppcF92ZXJzaW9uoQZJbGFuZ3VhZ2Vz -AQRCZW5Kbm9kZV9jb3VudMGgS3JlY29yZF9zaXploRw= +AKAAAJwAAACgAACdAAAAoAAAngAAAJ8AAKAAAAJEAAKOAAACzQAAAAAAAAAAAAAAAAAAAADmWGF1 +dG9ub21vdXNfc3lzdGVtX251bWJlcsIQkl0BYXV0b25vbW91c19zeXN0ZW1fb3JnYW5pemF0aW9u +TFRlc3QgVGVsZWtvbURjaXR54kpnZW9uYW1lX2lkwyDVJkVuYW1lc+FCZW5IUmVzZWFyY2hJY29u +dGluZW5040Rjb2RlQk9DIFDDX3IvIF/hQmVuR09jZWFuaWFHY291bnRyeeMgUMMfsxBIaXNvX2Nv +ZGVCQVUgX+FCZW5JQXVzdHJhbGlhSGxvY2F0aW9u409hY2N1cmFjeV9yYWRpdXOhAUhsYXRpdHVk +ZWg/8AAAAAAAAElsb25naXR1ZGVoP/AAAAAAAADmIAHCDPggHU1UZXN0IE5ldHdvcmtzIEriIFDD +WJESIF/hQmVuSE11a2lsdGVvIHLjIH1CTkEgUMNfci0gX+FCZW5NTm9ydGggQW1lcmljYSCZ4yBQ +w19l4SCoQlVTIF/hQmVuTVVuaXRlZCBTdGF0ZXMgxOMgzqEBIOBoQEf03S8an74g8mjAXpN4A0bc +XeMgSuIgUMEBIF/hQmVuQkMxIJnjIFDBASCoQk8xIF/hQmVuQ08gMUxzdWJkaXZpc2lvbnMBBOMg +UMEBIKhCTDEgX+FCZW5DTCAx4yBK4iBQwQIgX+FCZW5CQzIgmeMgUMECIKhCTzEgX+FCZW5DTyAy +IbsBBOMgUMECIKhCTDIgX+FCZW5DTCAy4yBK4iBQwQMgX+FCZW5CQzMgmeMgUMEDIKhCTzEgX+FC +ZW5DTyAzIbsBBOMgUMEDIKhCTDMgX+FCZW5DTCAzq83vTWF4TWluZC5jb23pW2JpbmFyeV9mb3Jt +YXRfbWFqb3JfdmVyc2lvbqECW2JpbmFyeV9mb3JtYXRfbWlub3JfdmVyc2lvbqBLYnVpbGRfZXBv +Y2gEAlrVhPpNZGF0YWJhc2VfdHlwZUxHZW9DaXR5LUxpdGVLZGVzY3JpcHRpb27hQmVuU01vY2sg +Z2VvaXAgZGF0YWJhc2VKaXBfdmVyc2lvbqEGSWxhbmd1YWdlcwEEQmVuSm5vZGVfY291bnTBoEty +ZWNvcmRfc2l6ZaEc diff --git a/modules/geoipbackend/regression-tests/write-mmdb.pl b/modules/geoipbackend/regression-tests/write-mmdb.pl index 9a7d1c1c66..0b90fbedc6 100644 --- a/modules/geoipbackend/regression-tests/write-mmdb.pl +++ b/modules/geoipbackend/regression-tests/write-mmdb.pl @@ -1,20 +1,22 @@ use MaxMind::DB::Writer::Tree; my %types = ( - city => 'map', - names => 'map', - en => 'utf8_string', - geoname_id => 'uint32', - location => 'map', - latitude => 'double', - longitude => 'double', - accuracy_radius => 'uint16', - continent => 'map', - country => 'map', - code => 'utf8_string', - iso_code => 'utf8_string', - subdivisions => ['array', 'map'], -); + city => 'map', + names => 'map', + en => 'utf8_string', + geoname_id => 'uint32', + location => 'map', + latitude => 'double', + longitude => 'double', + accuracy_radius => 'uint16', + continent => 'map', + country => 'map', + code => 'utf8_string', + iso_code => 'utf8_string', + subdivisions => ['array', 'map'], + autonomous_system_number => 'uint32', + autonomous_system_organization => 'utf8_string', + ); my $tree = MaxMind::DB::Writer::Tree->new( ip_version => 6, @@ -33,6 +35,8 @@ $tree->insert_network( 'continent' => { "code" => "OC", "geoname_id" => 6255151, "names" => { "en" => "Oceania" } }, 'country' => { "geoname_id" => 2077456, "iso_code" => "AU", "names" => { "en" => "Australia" } }, 'location' => { "latitude" => 1.0, "longitude" => 1.0, accuracy_radius => 1 }, + 'autonomous_system_number' => 4242, + 'autonomous_system_organization' => "Test Telekom", } ); @@ -43,6 +47,8 @@ $tree->insert_network( 'continent' => { "code" => "NA", "geoname_id" => 6255149, "names" => { "en" => "North America" } }, 'country' => { "geoname_id" => 6252001, "iso_code" => "US", "names" => { "en" => "United States" } }, 'location' => { "latitude" => 47.913000, "longitude" => -122.304200, accuracy_radius => 1 }, + 'autonomous_system_number' => 3320, + 'autonomous_system_organization' => "Test Networks", } ); diff --git a/regression-tests.auth-py/authtests.py b/regression-tests.auth-py/authtests.py index 9e55a32ced..6594c46db3 100644 --- a/regression-tests.auth-py/authtests.py +++ b/regression-tests.auth-py/authtests.py @@ -106,8 +106,8 @@ query-cache-ttl=0 log-dns-queries=yes log-dns-details=yes loglevel=9 -geoip-zones-file=../modules/geoipbackend/regression-tests/geo.yaml -geoip-database-files=../modules/geoipbackend/regression-tests/GeoLiteCity.dat +geoip-database-files=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb +edns-subnet-processing=yes distributor-threads=1""".format(confdir=confdir, bind_dnssec_db=bind_dnssec_db)) diff --git a/regression-tests.auth-py/clientsubnetoption.py b/regression-tests.auth-py/clientsubnetoption.py new file mode 100644 index 0000000000..c4f78f25d2 --- /dev/null +++ b/regression-tests.auth-py/clientsubnetoption.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python +# +# Copyright (c) 2012 OpenDNS, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the OpenDNS nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL OPENDNS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" Class to implement draft-ietf-dnsop-edns-client-subnet (previously known as +draft-vandergaast-edns-client-subnet. + +The contained class supports both IPv4 and IPv6 addresses. +Requirements: + dnspython (http://www.dnspython.org/) +""" +from __future__ import print_function +from __future__ import division + +import socket +import struct +import dns +import dns.edns +import dns.flags +import dns.message +import dns.query + +__author__ = "bhartvigsen@opendns.com (Brian Hartvigsen)" +__version__ = "2.0.0" + +ASSIGNED_OPTION_CODE = 0x0008 +DRAFT_OPTION_CODE = 0x50FA + +FAMILY_IPV4 = 1 +FAMILY_IPV6 = 2 +SUPPORTED_FAMILIES = (FAMILY_IPV4, FAMILY_IPV6) + + +class ClientSubnetOption(dns.edns.Option): + """Implementation of draft-vandergaast-edns-client-subnet-01. + + Attributes: + family: An integer indicating which address family is being sent + ip: IP address in integer notation + mask: An integer representing the number of relevant bits being sent + scope: An integer representing the number of significant bits used by + the authoritative server. + """ + + def __init__(self, ip, bits=24, scope=0, option=ASSIGNED_OPTION_CODE): + super(ClientSubnetOption, self).__init__(option) + + n = None + f = None + + for family in (socket.AF_INET, socket.AF_INET6): + try: + n = socket.inet_pton(family, ip) + if family == socket.AF_INET6: + f = FAMILY_IPV6 + hi, lo = struct.unpack('!QQ', n) + ip = hi << 64 | lo + elif family == socket.AF_INET: + f = FAMILY_IPV4 + ip = struct.unpack('!L', n)[0] + except Exception: + pass + + if n is None: + raise Exception("%s is an invalid ip" % ip) + + self.family = f + self.ip = ip + self.mask = bits + self.scope = scope + self.option = option + + if self.family == FAMILY_IPV4 and self.mask > 32: + raise Exception("32 bits is the max for IPv4 (%d)" % bits) + if self.family == FAMILY_IPV6 and self.mask > 128: + raise Exception("128 bits is the max for IPv6 (%d)" % bits) + + def calculate_ip(self): + """Calculates the relevant ip address based on the network mask. + + Calculates the relevant bits of the IP address based on network mask. + Sizes up to the nearest octet for use with wire format. + + Returns: + An integer of only the significant bits sized up to the nearest + octect. + """ + + if self.family == FAMILY_IPV4: + bits = 32 + elif self.family == FAMILY_IPV6: + bits = 128 + + ip = self.ip >> bits - self.mask + + if (self.mask % 8 != 0): + ip = ip << 8 - (self.mask % 8) + + return ip + + def is_draft(self): + """" Determines whether this instance is using the draft option code """ + return self.option == DRAFT_OPTION_CODE + + def to_wire(self, file): + """Create EDNS packet as defined in draft-vandergaast-edns-client-subnet-01.""" + + ip = self.calculate_ip() + + mask_bits = self.mask + if mask_bits % 8 != 0: + mask_bits += 8 - (self.mask % 8) + + if self.family == FAMILY_IPV4: + test = struct.pack("!L", ip) + elif self.family == FAMILY_IPV6: + test = struct.pack("!QQ", ip >> 64, ip & (2 ** 64 - 1)) + test = test[-(mask_bits // 8):] + + format = "!HBB%ds" % (mask_bits // 8) + data = struct.pack(format, self.family, self.mask, self.scope, test) + file.write(data) + + def from_wire(cls, otype, wire, current, olen): + """Read EDNS packet as defined in draft-vandergaast-edns-client-subnet-01. + + Returns: + An instance of ClientSubnetOption based on the ENDS packet + """ + + data = wire[current:current + olen] + (family, mask, scope) = struct.unpack("!HBB", data[:4]) + + c_mask = mask + if mask % 8 != 0: + c_mask += 8 - (mask % 8) + + ip = struct.unpack_from("!%ds" % (c_mask // 8), data, 4)[0] + + if (family == FAMILY_IPV4): + ip = ip + b'\0' * ((32 - c_mask) // 8) + ip = socket.inet_ntop(socket.AF_INET, ip) + elif (family == FAMILY_IPV6): + ip = ip + b'\0' * ((128 - c_mask) // 8) + ip = socket.inet_ntop(socket.AF_INET6, ip) + else: + raise Exception("Returned a family other then IPv4 or IPv6") + + return cls(ip, mask, scope, otype) + + from_wire = classmethod(from_wire) + + def __repr__(self): + if self.family == FAMILY_IPV4: + ip = socket.inet_ntop(socket.AF_INET, struct.pack('!L', self.ip)) + elif self.family == FAMILY_IPV6: + ip = socket.inet_ntop(socket.AF_INET6, + struct.pack('!QQ', + self.ip >> 64, + self.ip & (2 ** 64 - 1))) + + return "%s(%s, %s, %s)" % ( + self.__class__.__name__, + ip, + self.mask, + self.scope + ) + + def __eq__(self, other): + """Rich comparison method for equality. + + Two ClientSubnetOptions are equal if their relevant ip bits, mask, and + family are identical. We ignore scope since generally we want to + compare questions to responses and that bit is only relevant when + determining caching behavior. + + Returns: + boolean + """ + + if not isinstance(other, ClientSubnetOption): + return False + if self.calculate_ip() != other.calculate_ip(): + return False + if self.mask != other.mask: + return False + if self.family != other.family: + return False + return True + + def __ne__(self, other): + """Rich comparison method for inequality. + + See notes for __eq__() + + Returns: + boolean + """ + return not self.__eq__(other) + + +dns.edns._type_to_class[DRAFT_OPTION_CODE] = ClientSubnetOption +dns.edns._type_to_class[ASSIGNED_OPTION_CODE] = ClientSubnetOption + +if __name__ == "__main__": + import argparse + import sys + + def CheckForClientSubnetOption(addr, args, option_code=ASSIGNED_OPTION_CODE): + print("Testing for edns-clientsubnet using option code", hex(option_code), file=sys.stderr) + cso = ClientSubnetOption(args.subnet, args.mask, option=option_code) + message = dns.message.make_query(args.rr, args.type) + # Tested authoritative servers seem to use the last code in cases + # where they support both. We make the official code last to allow + # us to check for support of both draft and official + message.use_edns(options=[cso]) + + try: + r = dns.query.udp(message, addr, timeout=args.timeout) + if r.flags & dns.flags.TC: + r = dns.query.tcp(message, addr, timeout=args.timeout) + except dns.exception.Timeout: + print("Timeout: No answer received from %s\n" % args.nameserver, file=sys.stderr) + sys.exit(3) + + error = False + found = False + for options in r.options: + # Have not run into anyone who passes back both codes yet + # but just in case, we want to check all possible options + if isinstance(options, ClientSubnetOption): + found = True + print("Found ClientSubnetOption...", end=None, file=sys.stderr) + if not cso.family == options.family: + error = True + print("\nFailed: returned family (%d) is different from the passed family (%d)" % (options.family, cso.family), file=sys.stderr) + if not cso.calculate_ip() == options.calculate_ip(): + error = True + print("\nFailed: returned ip (%s) is different from the passed ip (%s)." % (options.calculate_ip(), cso.calculate_ip()), file=sys.stderr) + if not options.mask == cso.mask: + error = True + print("\nFailed: returned mask bits (%d) is different from the passed mask bits (%d)" % (options.mask, cso.mask), file=sys.stderr) + if not options.scope != 0: + print("\nWarning: scope indicates edns-clientsubnet data is not used", file=sys.stderr) + if options.is_draft(): + print("\nWarning: detected support for edns-clientsubnet draft code", file=sys.stderr) + + if found and not error: + print("Success", file=sys.stderr) + elif found: + print("Failed: See error messages above", file=sys.stderr) + else: + print("Failed: No ClientSubnetOption returned", file=sys.stderr) + + parser = argparse.ArgumentParser(description='draft-vandergaast-edns-client-subnet-01 tester') + parser.add_argument('nameserver', help='The nameserver to test') + parser.add_argument('rr', help='DNS record that should return an EDNS enabled response') + parser.add_argument('-s', '--subnet', help='Specifies an IP to pass as the client subnet.', default='192.0.2.0') + parser.add_argument('-m', '--mask', type=int, help='CIDR mask to use for subnet') + parser.add_argument('--timeout', type=int, help='Set the timeout for query to TIMEOUT seconds, default=10', default=10) + parser.add_argument('-t', '--type', help='DNS query type, default=A', default='A') + args = parser.parse_args() + + if not args.mask: + if ':' in args.subnet: + args.mask = 48 + else: + args.mask = 24 + + try: + addr = socket.gethostbyname(args.nameserver) + except socket.gaierror: + print("Unable to resolve %s\n" % args.nameserver, file=sys.stderr) + sys.exit(3) + + CheckForClientSubnetOption(addr, args, DRAFT_OPTION_CODE) + print("", file=sys.stderr) + CheckForClientSubnetOption(addr, args, ASSIGNED_OPTION_CODE) diff --git a/regression-tests.auth-py/runtests b/regression-tests.auth-py/runtests index 6c3fb67d4b..a93132b118 100755 --- a/regression-tests.auth-py/runtests +++ b/regression-tests.auth-py/runtests @@ -22,6 +22,11 @@ export RECCONTROL=${RECCONTROL:-${PWD}/../pdns/recursordist/rec_control} export PREFIX=127.0.0 +readonly GEOIP_TESTS_DIR=../modules/geoipbackend/regression-tests +if [ ! -f ${GEOIP_TESTS_DIR}/GeoLiteCity.mmdb ] ; then + base64 -d ${GEOIP_TESTS_DIR}/GeoLiteCity.mmdb.b64 > ${GEOIP_TESTS_DIR}/GeoLiteCity.mmdb +fi + for bin in "$PDNS" "$PDNSUTIL" "$PDNSRECURSOR" "$RECCONTROL"; do if [ -n "$bin" -a ! -e "$bin" ]; then echo "E: Required binary $bin not found. Please install the binary and/or edit ./vars." diff --git a/regression-tests.auth-py/test_LuaRecords.py b/regression-tests.auth-py/test_LuaRecords.py index e9ffdb7017..d93ec21cd3 100644 --- a/regression-tests.auth-py/test_LuaRecords.py +++ b/regression-tests.auth-py/test_LuaRecords.py @@ -4,6 +4,7 @@ import requests import threading import dns import time +import clientsubnetoption from authtests import AuthTest @@ -52,13 +53,13 @@ class TestLuaRecords(AuthTest): * [x] test latlon() * [x] test latlonloc() * [x] test netmask() + * [x] test country() + * [x] test continent() - * [ ] test pickclosest() - * [ ] test country() - * [ ] test continent() - * [ ] test closestMagic() + * [x] test pickclosest() + * [x] test closestMagic() * [x] test view() - * [ ] test asnum() + * [x] test asnum() * [x] rename pickwhashed() and pickwrandom() ? * [x] unify pickrandom() pickwhashed() and pickwrandom() parameters (ComboAddress vs string) * [x] make lua errors SERVFAIL @@ -86,7 +87,7 @@ whashed.example.org. 3600 IN LUA A "pickwhashed({{ {{15, '1.2.3.4'} rand.example.org. 3600 IN LUA A "pickrandom({{'{prefix}.101', '{prefix}.102'}})" v6-bogus.rand.example.org. 3600 IN LUA AAAA "pickrandom({{'{prefix}.101', '{prefix}.102'}})" v6.rand.example.org. 3600 IN LUA AAAA "pickrandom({{'2001:db8:a0b:12f0::1', 'fe80::2a1:9bff:fe9b:f268'}})" -closest 3600 IN LUA A "pickclosest({{'192.0.2.1','192.0.2.2','{prefix}.102', '198.51.100.1'}})" +closest.geo 3600 IN LUA A "pickclosest({{'1.1.1.2','1.2.3.4'}})" empty.rand.example.org. 3600 IN LUA A "pickrandom()" wrand.example.org. 3600 IN LUA A "pickwrandom({{ {{30, '{prefix}.102'}}, {{15, '{prefix}.103'}} }})" @@ -111,6 +112,9 @@ eu-west IN LUA A ( ";include('config') " nl IN LUA A ( ";include('config') " "return ifportup(8081, NLips) ") latlon.geo IN LUA TXT "latlon()" +continent.geo IN LUA TXT ";if(continent('NA')) then return 'true' else return 'false' end" +asnum.geo IN LUA TXT ";if(asnum('4242')) then return 'true' else return 'false' end" +country.geo IN LUA TXT ";if(country('US')) then return 'true' else return 'false' end" latlonloc.geo IN LUA TXT "latlonloc()" true.netmask IN LUA TXT ( ";if(netmask({{ '{prefix}.0/24' }})) " @@ -133,6 +137,8 @@ none.view IN LUA A ("view({{ "{{ {{'192.168.0.0/16'}}, {{'192.168.1.54'}}}}," "{{ {{'1.2.0.0/16'}}, {{'1.2.3.4'}}}}, " " }}) " ) +*.magic IN LUA A "closestMagic()" +www-balanced IN CNAME 1-1-1-3.17-1-2-4.1-2-3-5.magic.example.org. """, } _web_rrsets = [] @@ -225,21 +231,6 @@ none.view IN LUA A ("view({{ self.assertRcodeEqual(res, dns.rcode.NOERROR) self.assertAnyRRsetInAnswer(res, expected) - @unittest.skip - def testClosest(self): - """ - Basic pickClosest() test with a set of A records - """ - expected = [dns.rrset.from_text('wrand.example.org.', 0, dns.rdataclass.IN, 'A', - '{prefix}.103'.format(prefix=self._PREFIX)), - dns.rrset.from_text('wrand.example.org.', 0, dns.rdataclass.IN, 'A', - '{prefix}.102'.format(prefix=self._PREFIX))] - query = dns.message.make_query('closest.example.org', 'A') - - res = self.sendUDPQuery(query) - self.assertRcodeEqual(res, dns.rcode.NOERROR) - self.assertAnyRRsetInAnswer(res, expected) - def testIfportup(self): """ Basic ifportup() test @@ -357,10 +348,12 @@ none.view IN LUA A ("view({{ """ Basic latlon() test """ - expected = dns.rrset.from_text('latlon.geo.example.org.', 0, + name = 'latlon.geo.example.org.' + ecso = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24) + query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096, options=[ecso]) + expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', - '"0.000000 0.000000"') - query = dns.message.make_query('latlon.geo.example.org', 'TXT') + '"47.913000 -122.304200"') res = self.sendUDPQuery(query) self.assertRcodeEqual(res, dns.rcode.NOERROR) @@ -370,15 +363,117 @@ none.view IN LUA A ("view({{ """ Basic latlonloc() test """ - expected = dns.rrset.from_text('latlonloc.geo.example.org.', 0, - dns.rdataclass.IN, 'TXT', - '"0 0 -0 S 0 0 -0 W 0.00m 1.00m 10000.00m 10.00m"') - query = dns.message.make_query('latlonloc.geo.example.org', 'TXT') + name = 'latlonloc.geo.example.org.' + expected = dns.rrset.from_text(name, 0,dns.rdataclass.IN, 'TXT', + '"47 54 46.8 N 122 18 15.12 W 0.00m 1.00m 10000.00m 10.00m"') + ecso = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24) + query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096, options=[ecso]) res = self.sendUDPQuery(query) self.assertRcodeEqual(res, dns.rcode.NOERROR) self.assertRRsetInAnswer(res, expected) + def testClosestMagic(self): + """ + Basic closestMagic() test + """ + name = 'www-balanced.example.org.' + cname = '1-1-1-3.17-1-2-4.1-2-3-5.magic.example.org.' + queries = [ + ('1.1.1.0', 24, '1.1.1.3'), + ('1.2.3.0', 24, '1.2.3.5'), + ('17.1.0.0', 16, '17.1.2.4') + ] + + for (subnet, mask, ip) in queries: + ecso = clientsubnetoption.ClientSubnetOption(subnet, mask) + query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[ecso]) + + response = dns.message.make_response(query) + + response.answer.append(dns.rrset.from_text(name, 0, dns.rdataclass.IN, dns.rdatatype.CNAME, cname)) + response.answer.append(dns.rrset.from_text(cname, 0, dns.rdataclass.IN, 'A', ip)) + + res = self.sendUDPQuery(query) + self.assertRcodeEqual(res, dns.rcode.NOERROR) + self.assertEqual(res.answer, response.answer) + + def testAsnum(self): + """ + Basic asnum() test + """ + queries = [ + ('1.1.1.0', 24, '"true"'), + ('1.2.3.0', 24, '"false"'), + ('17.1.0.0', 16, '"false"') + ] + name = 'asnum.geo.example.org.' + for (subnet, mask, txt) in queries: + ecso = clientsubnetoption.ClientSubnetOption(subnet, mask) + query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096, options=[ecso]) + expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', txt) + + res = self.sendUDPQuery(query) + self.assertRcodeEqual(res, dns.rcode.NOERROR) + self.assertRRsetInAnswer(res, expected) + + def testCountry(self): + """ + Basic country() test + """ + queries = [ + ('1.1.1.0', 24, '"false"'), + ('1.2.3.0', 24, '"true"'), + ('17.1.0.0', 16, '"false"') + ] + name = 'country.geo.example.org.' + for (subnet, mask, txt) in queries: + ecso = clientsubnetoption.ClientSubnetOption(subnet, mask) + query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096, options=[ecso]) + expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', txt) + + res = self.sendUDPQuery(query) + self.assertRcodeEqual(res, dns.rcode.NOERROR) + self.assertRRsetInAnswer(res, expected) + + def testContinent(self): + """ + Basic continent() test + """ + queries = [ + ('1.1.1.0', 24, '"false"'), + ('1.2.3.0', 24, '"true"'), + ('17.1.0.0', 16, '"false"') + ] + name = 'continent.geo.example.org.' + for (subnet, mask, txt) in queries: + ecso = clientsubnetoption.ClientSubnetOption(subnet, mask) + query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096, options=[ecso]) + expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', txt) + + res = self.sendUDPQuery(query) + self.assertRcodeEqual(res, dns.rcode.NOERROR) + self.assertRRsetInAnswer(res, expected) + + def testClosest(self): + """ + Basic pickclosest() test + """ + queries = [ + ('1.1.1.0', 24, '1.1.1.2'), + ('1.2.3.0', 24, '1.2.3.4'), + ('17.1.0.0', 16, '1.1.1.2') + ] + name = 'closest.geo.example.org.' + for (subnet, mask, ip) in queries: + ecso = clientsubnetoption.ClientSubnetOption(subnet, mask) + query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, options=[ecso]) + expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', ip) + + res = self.sendUDPQuery(query) + self.assertRcodeEqual(res, dns.rcode.NOERROR) + self.assertRRsetInAnswer(res, expected) + def testNetmask(self): """ Basic netmask() test