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
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,
'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",
}
);
'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",
}
);
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))
--- /dev/null
+#!/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)
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."
import threading
import dns
import time
+import clientsubnetoption
from authtests import 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
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'}} }})"
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' }})) "
"{{ {{'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 = []
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
"""
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)
"""
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