diff --git a/LINETIME b/LINETIME
index 516e39f..a3b8624 100644
--- a/LINETIME
+++ b/LINETIME
@@ -1,3 +1,10 @@
+* 21/09/16
+ Added the GUI!!!
+ Install : Install man files
+ Doc : Added new paper (Spanish)
+ Fixed Module : fbt/pr.pop
+ Fixed Import modules installed
+
* 23/08/16
Added New Function : Invoke, to call module within another module.
Added New Dependence : xterm, For open news consoles in other windows
diff --git a/core/Information.py b/core/Information.py
index f1981d7..3461ce0 100644
--- a/core/Information.py
+++ b/core/Information.py
@@ -6,6 +6,6 @@
#######################HEADER#
version="0.0.1.0"
-date="28/07/16:18/08/16"
-build="0063"
+date="18/08/16:21/09/16"
+build="0064"
Type="FREE"
diff --git a/core/modules.xml b/core/modules.xml
index fbd9e5d..dd4f022 100644
--- a/core/modules.xml
+++ b/core/modules.xml
@@ -151,28 +151,28 @@
1.1
LeSZO ZerO
btf
- bruteforcetoftprotol
+ brute_force_to_ftp_protocol
Brute Force to FTprotocol.
1.1
RedToor
btf
- bruteforcetosqlprotocol
+ brute_force_to_sql_protocol
Brute Force to SQL protocol.
1.1
RedToor
btf
- bruteforcetosshprotocol
+ brute_force_to_ssh_protocol
Brute Force to SSH protocol.
1.1
RedToor
btf
- bruteforcetopop3rotocol
+ brute_force_to_pop3_protocol
Brute Force to POP3 protocol.
diff --git a/core/shorts/ktf.gui b/core/shorts/ktf.gui
new file mode 100644
index 0000000..ff21444
--- /dev/null
+++ b/core/shorts/ktf.gui
@@ -0,0 +1,3 @@
+#!/bin/bash
+cd /usr/share/KatanaFramework
+java -jar ktf.gui.jar
\ No newline at end of file
diff --git a/core/version.json b/core/version.json
index 56a78cc..1b1782c 100644
--- a/core/version.json
+++ b/core/version.json
@@ -1 +1 @@
-{"Katana":{"Description":"Katana Updates information","Author": "Redtoor","Version":"0.3","Update":{"Core":"0.0.1.0","Build":"0063","Date":"18/08/16"}}}
+{"Katana":{"Description":"Katana Updates information","Author": "Redtoor","Version":"0.3","Update":{"Core":"0.0.1.0","Build":"0064","Date":"21/09/16"}}}
diff --git a/doc/Paper-ES.pdf b/doc/Paper-ES.pdf
new file mode 100644
index 0000000..feb0713
Binary files /dev/null and b/doc/Paper-ES.pdf differ
diff --git a/install b/install
index c010f46..15d603c 100644
--- a/install
+++ b/install
@@ -2,7 +2,7 @@
#HEAD#########################################################
#
# Katana Framework | install
-# Last Modified: 18/08/2016
+# Last Modified: 21/09/2016
#
#########################################################HEAD#
@@ -50,9 +50,11 @@ ln -f -r -s /usr/share/KatanaFramework/core/shorts/ktf.linker /usr/bin/ktf.link
ln -f -r -s /usr/share/KatanaFramework/core/shorts/ktf.update /usr/bin/ktf.update ;
ln -f -r -s /usr/share/KatanaFramework/core/shorts/ktf.ktf /usr/bin/ktf.ktf ;
ln -f -r -s /usr/share/KatanaFramework/core/shorts/ktf.tool /usr/bin/ktf.tool ;
+ln -f -r -s /usr/share/KatanaFramework/core/shorts/ktf.gui /usr/bin/ktf.gui ;
ln -f -r -s /usr/share/KatanaFramework/core/shorts/ktf.run /usr/bin/ktf.run
"""
Files="""
+cp -r * /usr/share/KatanaFramework/doc/man/ /usr/local/share/man/man1/ ;
cd /usr/share/KatanaFramework/files/ ;
tar xvf /usr/share/KatanaFramework/files/exiftool.tar >/dev/null 2>&1;
tar xvf /usr/share/KatanaFramework/files/facebrok1.8.tar >/dev/null 2>&1;
diff --git a/ktf.gui.jar b/ktf.gui.jar
new file mode 100644
index 0000000..5112067
Binary files /dev/null and b/ktf.gui.jar differ
diff --git a/ktf.linker b/ktf.linker
index bc3beee..44b5949 100644
--- a/ktf.linker
+++ b/ktf.linker
@@ -1,71 +1,7 @@
-
-from scripts.web import whois
-from core import Errors
-
-from core.Function import KatanaCheckActionShowModules,KatanaCheckActionUseModule
-from core.Function import KatanaCheckActionShowOptions
-from core.Function import KatanaCheckActionGetInfo,ShowInformationModule
-from core.Function import ShowOptions,KatanaCheckActionSetValue,UpdateValue,runModule
-from core.Function import KatanaCheckActionisBack,KatanaCheckActionGlobalCommands
-
-import xml.etree.ElementTree as ET
-import importlib
-from core.design import *
-from core import info
-import argparse
-
-CLASS_LINKER=DESIGN()
-tree = ET.parse('core/modules.xml')
-root = tree.getroot()
-parser = argparse.ArgumentParser()
-parser = argparse.ArgumentParser(description='ktf.linker:'+info.version+info.build)
-parser.add_argument("-m", "--module", help="Module")
-parser.add_argument("-t", "--target", help="Target")
-parser.add_argument("-p", "--port", help="Port")
-parser.add_argument("-f", "--file", help="File")
-parser.add_argument("-u", "--user", help="Username")
-parser.add_argument("-ps","--pass", help="Password")
-parser.add_argument("-i","--interface", help="Password")
-
-args = parser.parse_args()
-args = parser.parse_args()
-m=args.module
-
-if __name__=="__main__":
- # For GUI not show this.
- CLASS_LINKER.linker(info.version, info.build)
- for modules in root.findall('module'):
- if m == modules.get('name'):
- Space()
- category = modules.find('category').text
- filename = modules.find('filename').text
- ModuleToStart = importlib.import_module("modules."+category+"."+filename)
- init=ModuleToStart.init()
-
- for option in init.options:
- if init.options[option] == "target":init.options[option]=[args.target ,init.options[option][1],init.options[option][2]]
- if init.options[option] == "port" :init.options[option]=[args.port ,init.options[option][1],init.options[option][2]]
- if init.options[option] == "file" :init.options[option]=[args.file ,init.options[option][1],init.options[option][2]]
- if init.options[option] == "user" :init.options[option]=[args.user ,init.options[option][1],init.options[option][2]]
- if init.options[option] == "pass" :init.options[option]=[args.interface ,init.options[option][1],init.options[option][2]]
-
- actions = "run"
-
- while True:
- try:
- if actions != "run":actions=raw_input(Prompt(init.CodeName))
- if KatanaCheckActionShowOptions(actions) :ShowOptions(init)
- elif KatanaCheckActionSetValue(actions) :init.options=UpdateValue(actions,init.options)
- elif KatanaCheckActionGetInfo(actions) :ShowInformationModule(init)
- elif runModule(actions):
- for Namevalue in init.options:
- init.var.update({Namevalue:init.options[Namevalue][0]})
- ModuleToStart.main(True)
- elif KatanaCheckActionisBack(actions) :printAlert(6,"This Commands just work in console mode.")
- else :
- if a
- ctions != "":KatanaCheckActionGlobalCommands(actions)
- actions = ""
- except:Errors.Errors()
- Space()
- ModuleNotFound(m)
\ No newline at end of file
+#!/usr/bin/env python
+#HEAD#########################################################
+#
+# Katana Framework | ktf.linker
+# Last Modified: 21/09/2016
+#
+#########################################################HEAD#
diff --git a/lib/IPy/build/lib.linux-i686-2.7/IPy.py b/lib/IPy/build/lib.linux-i686-2.7/IPy.py
new file mode 100644
index 0000000..d1ea845
--- /dev/null
+++ b/lib/IPy/build/lib.linux-i686-2.7/IPy.py
@@ -0,0 +1,1652 @@
+"""
+IPy - class and tools for handling of IPv4 and IPv6 addresses and networks.
+See README file for learn how to use IPy.
+
+Further Information might be available at:
+https://github.com/haypo/python-ipy
+"""
+
+__version__ = '0.83'
+
+import bisect
+import collections
+import sys
+import types
+
+# Definition of the Ranges for IPv4 IPs
+# this should include www.iana.org/assignments/ipv4-address-space
+# and www.iana.org/assignments/multicast-addresses
+IPv4ranges = {
+ '0': 'PUBLIC', # fall back
+ '00000000': 'PRIVATE', # 0/8
+ '00001010': 'PRIVATE', # 10/8
+ '0110010001': 'CARRIER_GRADE_NAT', #100.64/10
+ '01111111': 'PRIVATE', # 127.0/8
+ '1': 'PUBLIC', # fall back
+ '1010100111111110': 'PRIVATE', # 169.254/16
+ '101011000001': 'PRIVATE', # 172.16/12
+ '1100000010101000': 'PRIVATE', # 192.168/16
+ '111': 'RESERVED', # 224/3
+ }
+
+# Definition of the Ranges for IPv6 IPs
+# http://www.iana.org/assignments/ipv6-address-space/
+# http://www.iana.org/assignments/ipv6-unicast-address-assignments/
+# http://www.iana.org/assignments/ipv6-multicast-addresses/
+IPv6ranges = {
+ '00000000' : 'RESERVED', # ::/8
+ '0' * 96 : 'RESERVED', # ::/96 Formerly IPV4COMP [RFC4291]
+ '0' * 128 : 'UNSPECIFIED', # ::/128
+ '0' * 127 + '1' : 'LOOPBACK', # ::1/128
+ '0' * 80 + '1' * 16 : 'IPV4MAP', # ::ffff:0:0/96
+ '00000000011001001111111110011011' + '0' * 64 : 'WKP46TRANS', # 0064:ff9b::/96 Well-Known-Prefix [RFC6052]
+ '00000001' : 'UNASSIGNED', # 0100::/8
+ '0000001' : 'RESERVED', # 0200::/7 Formerly NSAP [RFC4048]
+ '0000010' : 'RESERVED', # 0400::/7 Formerly IPX [RFC3513]
+ '0000011' : 'RESERVED', # 0600::/7
+ '00001' : 'RESERVED', # 0800::/5
+ '0001' : 'RESERVED', # 1000::/4
+ '001' : 'GLOBAL-UNICAST', # 2000::/3 [RFC4291]
+ '00100000000000010000000' : 'SPECIALPURPOSE', # 2001::/23 [RFC4773]
+ '00100000000000010000000000000000' : 'TEREDO', # 2001::/32 [RFC4380]
+ '00100000000000010000000000000010' + '0' * 16 : 'BMWG', # 2001:0002::/48 Benchmarking [RFC5180]
+ '0010000000000001000000000001' : 'ORCHID', # 2001:0010::/28 (Temp until 2014-03-21) [RFC4843]
+ '00100000000000010000001' : 'ALLOCATED APNIC', # 2001:0200::/23
+ '00100000000000010000010' : 'ALLOCATED ARIN', # 2001:0400::/23
+ '00100000000000010000011' : 'ALLOCATED RIPE NCC', # 2001:0600::/23
+ '00100000000000010000100' : 'ALLOCATED RIPE NCC', # 2001:0800::/23
+ '00100000000000010000101' : 'ALLOCATED RIPE NCC', # 2001:0a00::/23
+ '00100000000000010000110' : 'ALLOCATED APNIC', # 2001:0c00::/23
+ '00100000000000010000110110111000' : 'DOCUMENTATION', # 2001:0db8::/32 [RFC3849]
+ '00100000000000010000111' : 'ALLOCATED APNIC', # 2001:0e00::/23
+ '00100000000000010001001' : 'ALLOCATED LACNIC', # 2001:1200::/23
+ '00100000000000010001010' : 'ALLOCATED RIPE NCC', # 2001:1400::/23
+ '00100000000000010001011' : 'ALLOCATED RIPE NCC', # 2001:1600::/23
+ '00100000000000010001100' : 'ALLOCATED ARIN', # 2001:1800::/23
+ '00100000000000010001101' : 'ALLOCATED RIPE NCC', # 2001:1a00::/23
+ '0010000000000001000111' : 'ALLOCATED RIPE NCC', # 2001:1c00::/22
+ '00100000000000010010' : 'ALLOCATED RIPE NCC', # 2001:2000::/20
+ '001000000000000100110' : 'ALLOCATED RIPE NCC', # 2001:3000::/21
+ '0010000000000001001110' : 'ALLOCATED RIPE NCC', # 2001:3800::/22
+ '0010000000000001001111' : 'RESERVED', # 2001:3c00::/22 Possible future allocation to RIPE NCC
+ '00100000000000010100000' : 'ALLOCATED RIPE NCC', # 2001:4000::/23
+ '00100000000000010100001' : 'ALLOCATED AFRINIC', # 2001:4200::/23
+ '00100000000000010100010' : 'ALLOCATED APNIC', # 2001:4400::/23
+ '00100000000000010100011' : 'ALLOCATED RIPE NCC', # 2001:4600::/23
+ '00100000000000010100100' : 'ALLOCATED ARIN', # 2001:4800::/23
+ '00100000000000010100101' : 'ALLOCATED RIPE NCC', # 2001:4a00::/23
+ '00100000000000010100110' : 'ALLOCATED RIPE NCC', # 2001:4c00::/23
+ '00100000000000010101' : 'ALLOCATED RIPE NCC', # 2001:5000::/20
+ '0010000000000001100' : 'ALLOCATED APNIC', # 2001:8000::/19
+ '00100000000000011010' : 'ALLOCATED APNIC', # 2001:a000::/20
+ '00100000000000011011' : 'ALLOCATED APNIC', # 2001:b000::/20
+ '0010000000000010' : '6TO4', # 2002::/16 "6to4" [RFC3056]
+ '001000000000001100' : 'ALLOCATED RIPE NCC', # 2003::/18
+ '001001000000' : 'ALLOCATED APNIC', # 2400::/12
+ '001001100000' : 'ALLOCATED ARIN', # 2600::/12
+ '00100110000100000000000' : 'ALLOCATED ARIN', # 2610::/23
+ '00100110001000000000000' : 'ALLOCATED ARIN', # 2620::/23
+ '001010000000' : 'ALLOCATED LACNIC', # 2800::/12
+ '001010100000' : 'ALLOCATED RIPE NCC', # 2a00::/12
+ '001011000000' : 'ALLOCATED AFRINIC', # 2c00::/12
+ '00101101' : 'RESERVED', # 2d00::/8
+ '0010111' : 'RESERVED', # 2e00::/7
+ '0011' : 'RESERVED', # 3000::/4
+ '010' : 'RESERVED', # 4000::/3
+ '011' : 'RESERVED', # 6000::/3
+ '100' : 'RESERVED', # 8000::/3
+ '101' : 'RESERVED', # a000::/3
+ '110' : 'RESERVED', # c000::/3
+ '1110' : 'RESERVED', # e000::/4
+ '11110' : 'RESERVED', # f000::/5
+ '111110' : 'RESERVED', # f800::/6
+ '1111110' : 'ULA', # fc00::/7 [RFC4193]
+ '111111100' : 'RESERVED', # fe00::/9
+ '1111111010' : 'LINKLOCAL', # fe80::/10
+ '1111111011' : 'RESERVED', # fec0::/10 Formerly SITELOCAL [RFC4291]
+ '11111111' : 'MULTICAST', # ff00::/8
+ '1111111100000001' : 'NODE-LOCAL MULTICAST', # ff01::/16
+ '1111111100000010' : 'LINK-LOCAL MULTICAST', # ff02::/16
+ '1111111100000100' : 'ADMIN-LOCAL MULTICAST', # ff04::/16
+ '1111111100000101' : 'SITE-LOCAL MULTICAST', # ff05::/16
+ '1111111100001000' : 'ORG-LOCAL MULTICAST', # ff08::/16
+ '1111111100001110' : 'GLOBAL MULTICAST', # ff0e::/16
+ '1111111100001111' : 'RESERVED MULTICAST', # ff0f::/16
+ '111111110011' : 'PREFIX-BASED MULTICAST', # ff30::/12 [RFC3306]
+ '111111110111' : 'RP-EMBEDDED MULTICAST', # ff70::/12 [RFC3956]
+ }
+
+MAX_IPV4_ADDRESS = 0xffffffff
+MAX_IPV6_ADDRESS = 0xffffffffffffffffffffffffffffffff
+IPV6_TEST_MAP = 0xffffffffffffffffffffffff00000000
+IPV6_MAP_MASK = 0x00000000000000000000ffff00000000
+
+if sys.version_info >= (3,):
+ INT_TYPES = (int,)
+ STR_TYPES = (str,)
+ xrange = range
+else:
+ INT_TYPES = (int, long)
+ STR_TYPES = (str, unicode)
+
+
+class IPint(object):
+ """Handling of IP addresses returning integers.
+
+ Use class IP instead because some features are not implemented for
+ IPint."""
+
+ def __init__(self, data, ipversion=0, make_net=0):
+ """Create an instance of an IP object.
+
+ Data can be a network specification or a single IP. IP
+ addresses can be specified in all forms understood by
+ parseAddress(). The size of a network can be specified as
+
+ /prefixlen a.b.c.0/24 2001:658:22a:cafe::/64
+ -lastIP a.b.c.0-a.b.c.255 2001:658:22a:cafe::-2001:658:22a:cafe:ffff:ffff:ffff:ffff
+ /decimal netmask a.b.c.d/255.255.255.0 not supported for IPv6
+
+ If no size specification is given a size of 1 address (/32 for
+ IPv4 and /128 for IPv6) is assumed.
+
+ If make_net is True, an IP address will be transformed into the network
+ address by applying the specified netmask.
+
+ >>> print(IP('127.0.0.0/8'))
+ 127.0.0.0/8
+ >>> print(IP('127.0.0.0/255.0.0.0'))
+ 127.0.0.0/8
+ >>> print(IP('127.0.0.0-127.255.255.255'))
+ 127.0.0.0/8
+ >>> print(IP('127.0.0.1/255.0.0.0', make_net=True))
+ 127.0.0.0/8
+
+ See module documentation for more examples.
+ """
+
+ # Print no Prefixlen for /32 and /128
+ self.NoPrefixForSingleIp = 1
+
+ # Do we want prefix printed by default? see _printPrefix()
+ self.WantPrefixLen = None
+
+ netbits = 0
+ prefixlen = -1
+
+ # handling of non string values in constructor
+ if isinstance(data, INT_TYPES):
+ self.ip = int(data)
+ if ipversion == 0:
+ if self.ip <= MAX_IPV4_ADDRESS:
+ ipversion = 4
+ else:
+ ipversion = 6
+ if ipversion == 4:
+ if self.ip > MAX_IPV4_ADDRESS:
+ raise ValueError("IPv4 Address can't be larger than %x: %x" % (MAX_IPV4_ADDRESS, self.ip))
+ prefixlen = 32
+ elif ipversion == 6:
+ if self.ip > MAX_IPV6_ADDRESS:
+ raise ValueError("IPv6 Address can't be larger than %x: %x" % (MAX_IPV6_ADDRESS, self.ip))
+ prefixlen = 128
+ else:
+ raise ValueError("only IPv4 and IPv6 supported")
+ self._ipversion = ipversion
+ self._prefixlen = prefixlen
+ # handle IP instance as an parameter
+ elif isinstance(data, IPint):
+ self._ipversion = data._ipversion
+ self._prefixlen = data._prefixlen
+ self.ip = data.ip
+ elif isinstance(data, STR_TYPES):
+ # TODO: refactor me!
+ # splitting of a string into IP and prefixlen et. al.
+ x = data.split('-')
+ if len(x) == 2:
+ # a.b.c.0-a.b.c.255 specification ?
+ (ip, last) = x
+ (self.ip, parsedVersion) = parseAddress(ip)
+ if parsedVersion != 4:
+ raise ValueError("first-last notation only allowed for IPv4")
+ (last, lastversion) = parseAddress(last)
+ if lastversion != 4:
+ raise ValueError("last address should be IPv4, too")
+ if last < self.ip:
+ raise ValueError("last address should be larger than first")
+ size = last - self.ip
+ netbits = _count1Bits(size)
+ # make sure the broadcast is the same as the last ip
+ # otherwise it will return /16 for something like:
+ # 192.168.0.0-192.168.191.255
+ if IP('%s/%s' % (ip, 32-netbits)).broadcast().int() != last:
+ raise ValueError("the range %s is not on a network boundary." % data)
+ elif len(x) == 1:
+ x = data.split('/')
+ # if no prefix is given use defaults
+ if len(x) == 1:
+ ip = x[0]
+ prefixlen = -1
+ elif len(x) > 2:
+ raise ValueError("only one '/' allowed in IP Address")
+ else:
+ (ip, prefixlen) = x
+ if prefixlen.find('.') != -1:
+ # check if the user might have used a netmask like
+ # a.b.c.d/255.255.255.0
+ (netmask, vers) = parseAddress(prefixlen)
+ if vers != 4:
+ raise ValueError("netmask must be IPv4")
+ prefixlen = _netmaskToPrefixlen(netmask)
+ elif len(x) > 2:
+ raise ValueError("only one '-' allowed in IP Address")
+ else:
+ raise ValueError("can't parse")
+
+ (self.ip, parsedVersion) = parseAddress(ip)
+ if ipversion == 0:
+ ipversion = parsedVersion
+ if prefixlen == -1:
+ bits = _ipVersionToLen(ipversion)
+ prefixlen = bits - netbits
+ self._ipversion = ipversion
+ self._prefixlen = int(prefixlen)
+
+ if make_net:
+ self.ip = self.ip & _prefixlenToNetmask(self._prefixlen, self._ipversion)
+
+ if not _checkNetaddrWorksWithPrefixlen(self.ip,
+ self._prefixlen, self._ipversion):
+ raise ValueError("%s has invalid prefix length (%s)" % (repr(self), self._prefixlen))
+ else:
+ raise TypeError("Unsupported data type: %s" % type(data))
+
+ def int(self):
+ """Return the first / base / network addess as an (long) integer.
+
+ The same as IP[0].
+
+ >>> "%X" % IP('10.0.0.0/8').int()
+ 'A000000'
+ """
+ return self.ip
+
+ def version(self):
+ """Return the IP version of this Object.
+
+ >>> IP('10.0.0.0/8').version()
+ 4
+ >>> IP('::1').version()
+ 6
+ """
+ return self._ipversion
+
+ def prefixlen(self):
+ """Returns Network Prefixlen.
+
+ >>> IP('10.0.0.0/8').prefixlen()
+ 8
+ """
+ return self._prefixlen
+
+ def net(self):
+ """
+ Return the base (first) address of a network as an (long) integer.
+ """
+ return self.int()
+
+ def broadcast(self):
+ """
+ Return the broadcast (last) address of a network as an (long) integer.
+
+ The same as IP[-1]."""
+ return self.int() + self.len() - 1
+
+ def _printPrefix(self, want):
+ """Prints Prefixlen/Netmask.
+
+ Not really. In fact it is our universal Netmask/Prefixlen printer.
+ This is considered an internal function.
+
+ want == 0 / None don't return anything 1.2.3.0
+ want == 1 /prefix 1.2.3.0/24
+ want == 2 /netmask 1.2.3.0/255.255.255.0
+ want == 3 -lastip 1.2.3.0-1.2.3.255
+ """
+
+ if (self._ipversion == 4 and self._prefixlen == 32) or \
+ (self._ipversion == 6 and self._prefixlen == 128):
+ if self.NoPrefixForSingleIp:
+ want = 0
+ if want == None:
+ want = self.WantPrefixLen
+ if want == None:
+ want = 1
+ if want:
+ if want == 2:
+ # this should work with IP and IPint
+ netmask = self.netmask()
+ if not isinstance(netmask, INT_TYPES):
+ netmask = netmask.int()
+ return "/%s" % (intToIp(netmask, self._ipversion))
+ elif want == 3:
+ return "-%s" % (intToIp(self.ip + self.len() - 1, self._ipversion))
+ else:
+ # default
+ return "/%d" % (self._prefixlen)
+ else:
+ return ''
+
+ # We have different flavours to convert to:
+ # strFullsize 127.0.0.1 2001:0658:022a:cafe:0200:c0ff:fe8d:08fa
+ # strNormal 127.0.0.1 2001:658:22a:cafe:200:c0ff:fe8d:08fa
+ # strCompressed 127.0.0.1 2001:658:22a:cafe::1
+ # strHex 0x7F000001 0x20010658022ACAFE0200C0FFFE8D08FA
+ # strDec 2130706433 42540616829182469433547974687817795834
+
+ def strBin(self, wantprefixlen = None):
+ """Return a string representation as a binary value.
+
+ >>> print(IP('127.0.0.1').strBin())
+ 01111111000000000000000000000001
+ >>> print(IP('2001:0658:022a:cafe:0200::1').strBin())
+ 00100000000000010000011001011000000000100010101011001010111111100000001000000000000000000000000000000000000000000000000000000001
+ """
+
+ bits = _ipVersionToLen(self._ipversion)
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 0
+ ret = _intToBin(self.ip)
+ return '0' * (bits - len(ret)) + ret + self._printPrefix(wantprefixlen)
+
+ def strCompressed(self, wantprefixlen = None):
+ """Return a string representation in compressed format using '::' Notation.
+
+ >>> IP('127.0.0.1').strCompressed()
+ '127.0.0.1'
+ >>> IP('2001:0658:022a:cafe:0200::1').strCompressed()
+ '2001:658:22a:cafe:200::1'
+ >>> IP('ffff:ffff:ffff:ffff:ffff:f:f:fffc/127').strCompressed()
+ 'ffff:ffff:ffff:ffff:ffff:f:f:fffc/127'
+ """
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 1
+
+ if self._ipversion == 4:
+ return self.strFullsize(wantprefixlen)
+ else:
+ if self.ip >> 32 == 0xffff:
+ ipv4 = intToIp(self.ip & MAX_IPV4_ADDRESS, 4)
+ text = "::ffff:" + ipv4 + self._printPrefix(wantprefixlen)
+ return text
+ # find the longest sequence of '0'
+ hextets = [int(x, 16) for x in self.strFullsize(0).split(':')]
+ # every element of followingzeros will contain the number of zeros
+ # following the corresponding element of hextets
+ followingzeros = [0] * 8
+ for i in xrange(len(hextets)):
+ followingzeros[i] = _countFollowingZeros(hextets[i:])
+ # compressionpos is the position where we can start removing zeros
+ compressionpos = followingzeros.index(max(followingzeros))
+ if max(followingzeros) > 1:
+ # genererate string with the longest number of zeros cut out
+ # now we need hextets as strings
+ hextets = [x for x in self.strNormal(0).split(':')]
+ while compressionpos < len(hextets) and hextets[compressionpos] == '0':
+ del(hextets[compressionpos])
+ hextets.insert(compressionpos, '')
+ if compressionpos + 1 >= len(hextets):
+ hextets.append('')
+ if compressionpos == 0:
+ hextets = [''] + hextets
+ return ':'.join(hextets) + self._printPrefix(wantprefixlen)
+ else:
+ return self.strNormal(0) + self._printPrefix(wantprefixlen)
+
+ def strNormal(self, wantprefixlen = None):
+ """Return a string representation in the usual format.
+
+ >>> print(IP('127.0.0.1').strNormal())
+ 127.0.0.1
+ >>> print(IP('2001:0658:022a:cafe:0200::1').strNormal())
+ 2001:658:22a:cafe:200:0:0:1
+ """
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 1
+
+ if self._ipversion == 4:
+ ret = self.strFullsize(0)
+ elif self._ipversion == 6:
+ ret = ':'.join(["%x" % x for x in [int(x, 16) for x in self.strFullsize(0).split(':')]])
+ else:
+ raise ValueError("only IPv4 and IPv6 supported")
+
+
+
+ return ret + self._printPrefix(wantprefixlen)
+
+ def strFullsize(self, wantprefixlen = None):
+ """Return a string representation in the non-mangled format.
+
+ >>> print(IP('127.0.0.1').strFullsize())
+ 127.0.0.1
+ >>> print(IP('2001:0658:022a:cafe:0200::1').strFullsize())
+ 2001:0658:022a:cafe:0200:0000:0000:0001
+ """
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 1
+
+ return intToIp(self.ip, self._ipversion) + self._printPrefix(wantprefixlen)
+
+ def strHex(self, wantprefixlen = None):
+ """Return a string representation in hex format in lower case.
+
+ >>> print(IP('127.0.0.1').strHex())
+ 0x7f000001
+ >>> print(IP('2001:0658:022a:cafe:0200::1').strHex())
+ 0x20010658022acafe0200000000000001
+ """
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 0
+
+ x = '0x%x' % self.ip
+ return x + self._printPrefix(wantprefixlen)
+
+ def strDec(self, wantprefixlen = None):
+ """Return a string representation in decimal format.
+
+ >>> print(IP('127.0.0.1').strDec())
+ 2130706433
+ >>> print(IP('2001:0658:022a:cafe:0200::1').strDec())
+ 42540616829182469433547762482097946625
+ """
+
+ if self.WantPrefixLen == None and wantprefixlen == None:
+ wantprefixlen = 0
+
+ x = '%d' % self.ip
+ return x + self._printPrefix(wantprefixlen)
+
+ def iptype(self):
+ """Return a description of the IP type ('PRIVATE', 'RESERVED', etc).
+
+ >>> print(IP('127.0.0.1').iptype())
+ PRIVATE
+ >>> print(IP('192.168.1.1').iptype())
+ PRIVATE
+ >>> print(IP('195.185.1.2').iptype())
+ PUBLIC
+ >>> print(IP('::1').iptype())
+ LOOPBACK
+ >>> print(IP('2001:0658:022a:cafe:0200::1').iptype())
+ ALLOCATED RIPE NCC
+
+ The type information for IPv6 is out of sync with reality.
+ """
+
+ # this could be greatly improved
+
+ if self._ipversion == 4:
+ iprange = IPv4ranges
+ elif self._ipversion == 6:
+ iprange = IPv6ranges
+ else:
+ raise ValueError("only IPv4 and IPv6 supported")
+
+ bits = self.strBin()
+ for i in xrange(len(bits), 0, -1):
+ if bits[:i] in iprange:
+ return iprange[bits[:i]]
+ return "unknown"
+
+
+ def netmask(self):
+ """Return netmask as an integer.
+
+ >>> "%X" % IP('195.185.0.0/16').netmask().int()
+ 'FFFF0000'
+ """
+
+ # TODO: unify with prefixlenToNetmask?
+ bits = _ipVersionToLen(self._ipversion)
+ locallen = bits - self._prefixlen
+
+ return ((2 ** self._prefixlen) - 1) << locallen
+
+
+ def strNetmask(self):
+ """Return netmask as an string. Mostly useful for IPv6.
+
+ >>> print(IP('195.185.0.0/16').strNetmask())
+ 255.255.0.0
+ >>> print(IP('2001:0658:022a:cafe::0/64').strNetmask())
+ /64
+ """
+
+ # TODO: unify with prefixlenToNetmask?
+ # Note: call to _ipVersionToLen() also validates version is 4 or 6
+ bits = _ipVersionToLen(self._ipversion)
+ if self._ipversion == 4:
+ locallen = bits - self._prefixlen
+ return intToIp(((2 ** self._prefixlen) - 1) << locallen, 4)
+ elif self._ipversion == 6:
+ return "/%d" % self._prefixlen
+
+ def len(self):
+ """Return the length of a subnet.
+
+ >>> print(IP('195.185.1.0/28').len())
+ 16
+ >>> print(IP('195.185.1.0/24').len())
+ 256
+ """
+
+ bits = _ipVersionToLen(self._ipversion)
+ locallen = bits - self._prefixlen
+ return 2 ** locallen
+
+
+ def __nonzero__(self):
+ """All IPy objects should evaluate to true in boolean context.
+ Ordinarily they do, but if handling a default route expressed as
+ 0.0.0.0/0, the __len__() of the object becomes 0, which is used
+ as the boolean value of the object.
+ """
+ return True
+
+
+ def __len__(self):
+ """
+ Return the length of a subnet.
+
+ Called to implement the built-in function len().
+ It will break with large IPv6 Networks.
+ Use the object's len() instead.
+ """
+ return self.len()
+
+ def __add__(self, other):
+ """Emulate numeric objects through network aggregation"""
+ if self._ipversion != other._ipversion:
+ raise ValueError("Only networks with the same IP version can be added.")
+ if self._prefixlen != other._prefixlen:
+ raise ValueError("Only networks with the same prefixlen can be added.")
+ if self._prefixlen < 1:
+ raise ValueError("Networks with a prefixlen longer than /1 can't be added.")
+ if self > other:
+ # fixed by Skinny Puppy
+ return other.__add__(self)
+ if other.int() - self[-1].int() != 1:
+ raise ValueError("Only adjacent networks can be added together.")
+ ret = IP(self.int(), ipversion=self._ipversion)
+ ret._prefixlen = self.prefixlen() - 1
+ if not _checkNetaddrWorksWithPrefixlen(ret.ip, ret._prefixlen,
+ ret._ipversion):
+ raise ValueError("The resulting %s has invalid prefix length (%s)"
+ % (repr(ret), ret._prefixlen))
+ return ret
+
+ def __sub__(self, other):
+ """Return the prefixes that are in this IP but not in the other"""
+ return _remove_subprefix(self, other)
+
+ def __getitem__(self, key):
+ """Called to implement evaluation of self[key].
+
+ >>> ip=IP('127.0.0.0/30')
+ >>> for x in ip:
+ ... print(repr(x))
+ ...
+ IP('127.0.0.0')
+ IP('127.0.0.1')
+ IP('127.0.0.2')
+ IP('127.0.0.3')
+ >>> ip[2]
+ IP('127.0.0.2')
+ >>> ip[-1]
+ IP('127.0.0.3')
+ """
+
+ if isinstance(key, slice):
+ return [self.ip + int(x) for x in xrange(*key.indices(len(self)))]
+ if not isinstance(key, INT_TYPES):
+ raise TypeError
+ if key < 0:
+ if abs(key) <= self.len():
+ key = self.len() - abs(key)
+ else:
+ raise IndexError
+ else:
+ if key >= self.len():
+ raise IndexError
+
+ return self.ip + int(key)
+
+
+
+ def __contains__(self, item):
+ """Called to implement membership test operators.
+
+ Should return true if item is in self, false otherwise. Item
+ can be other IP-objects, strings or ints.
+
+ >>> IP('195.185.1.1').strHex()
+ '0xc3b90101'
+ >>> 0xC3B90101 in IP('195.185.1.0/24')
+ True
+ >>> '127.0.0.1' in IP('127.0.0.0/24')
+ True
+ >>> IP('127.0.0.0/24') in IP('127.0.0.0/25')
+ False
+ """
+
+ if isinstance(item, IP):
+ if item._ipversion != self._ipversion:
+ return False
+ else:
+ item = IP(item)
+ if item.ip >= self.ip and item.ip < self.ip + self.len() - item.len() + 1:
+ return True
+ else:
+ return False
+
+
+ def overlaps(self, item):
+ """Check if two IP address ranges overlap.
+
+ Returns 0 if the two ranges don't overlap, 1 if the given
+ range overlaps at the end and -1 if it does at the beginning.
+
+ >>> IP('192.168.0.0/23').overlaps('192.168.1.0/24')
+ 1
+ >>> IP('192.168.0.0/23').overlaps('192.168.1.255')
+ 1
+ >>> IP('192.168.0.0/23').overlaps('192.168.2.0')
+ 0
+ >>> IP('192.168.1.0/24').overlaps('192.168.0.0/23')
+ -1
+ """
+
+ if not isinstance(item, IP):
+ item = IP(item)
+ if item.ip >= self.ip and item.ip < self.ip + self.len():
+ return 1
+ elif self.ip >= item.ip and self.ip < item.ip + item.len():
+ return -1
+ else:
+ return 0
+
+
+ def __str__(self):
+ """Dispatch to the prefered String Representation.
+
+ Used to implement str(IP)."""
+
+ return self.strCompressed()
+
+
+ def __repr__(self):
+ """Print a representation of the Object.
+
+ Used to implement repr(IP). Returns a string which evaluates
+ to an identical Object (without the wantprefixlen stuff - see
+ module docstring.
+
+ >>> print(repr(IP('10.0.0.0/24')))
+ IP('10.0.0.0/24')
+ """
+
+ return("IPint('%s')" % (self.strCompressed(1)))
+
+
+ def __cmp__(self, other):
+ """Called by comparison operations.
+
+ Should return a negative integer if self < other, zero if self
+ == other, a positive integer if self > other.
+
+ Order is first determined by the address family. IPv4 addresses
+ are always smaller than IPv6 addresses:
+
+ >>> IP('10.0.0.0') < IP('2001:db8::')
+ 1
+
+ Then the first address is compared. Lower addresses are
+ always smaller:
+
+ >>> IP('10.0.0.0') > IP('10.0.0.1')
+ 0
+ >>> IP('10.0.0.0/24') > IP('10.0.0.1')
+ 0
+ >>> IP('10.0.1.0') > IP('10.0.0.0/24')
+ 1
+ >>> IP('10.0.1.0/24') > IP('10.0.0.0/24')
+ 1
+ >>> IP('10.0.1.0/24') > IP('10.0.0.0')
+ 1
+
+ Then the prefix length is compared. Shorter prefixes are
+ considered smaller than longer prefixes:
+
+ >>> IP('10.0.0.0/24') > IP('10.0.0.0')
+ 0
+ >>> IP('10.0.0.0/24') > IP('10.0.0.0/25')
+ 0
+ >>> IP('10.0.0.0/24') > IP('10.0.0.0/23')
+ 1
+
+ """
+ if not isinstance(other, IPint):
+ raise TypeError
+
+ # Lower version -> lower result
+ if self._ipversion != other._ipversion:
+ return self._ipversion < other._ipversion and -1 or 1
+
+ # Lower start address -> lower result
+ if self.ip != other.ip:
+ return self.ip < other.ip and -1 or 1
+
+ # Shorter prefix length -> lower result
+ if self._prefixlen != other._prefixlen:
+ return self._prefixlen < other._prefixlen and -1 or 1
+
+ # No differences found
+ return 0
+
+ def __eq__(self, other):
+ if not isinstance(other, IPint):
+ return False
+ return self.__cmp__(other) == 0
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __lt__(self, other):
+ return self.__cmp__(other) < 0
+
+ def __hash__(self):
+ """Called for the key object for dictionary operations, and by
+ the built-in function hash(). Should return a 32-bit integer
+ usable as a hash value for dictionary operations. The only
+ required property is that objects which compare equal have the
+ same hash value
+
+ >>> IP('10.0.0.0/24').__hash__()
+ -167772185
+ """
+
+ thehash = int(-1)
+ ip = self.ip
+ while ip > 0:
+ thehash = thehash ^ (ip & 0x7fffffff)
+ ip = ip >> 32
+ thehash = thehash ^ self._prefixlen
+ return int(thehash)
+
+
+class IP(IPint):
+ """Class for handling IP addresses and networks."""
+
+ def net(self):
+ """Return the base (first) address of a network as an IP object.
+
+ The same as IP[0].
+
+ >>> IP('10.0.0.0/8').net()
+ IP('10.0.0.0')
+ """
+ return IP(IPint.net(self), ipversion=self._ipversion)
+
+ def broadcast(self):
+ """Return the broadcast (last) address of a network as an IP object.
+
+ The same as IP[-1].
+
+ >>> IP('10.0.0.0/8').broadcast()
+ IP('10.255.255.255')
+ """
+ return IP(IPint.broadcast(self))
+
+ def netmask(self):
+ """Return netmask as an IP object.
+
+ >>> IP('10.0.0.0/8').netmask()
+ IP('255.0.0.0')
+ """
+ return IP(IPint.netmask(self), ipversion=self._ipversion)
+
+ def _getIPv4Map(self):
+ if self._ipversion != 6:
+ return None
+ if (self.ip >> 32) != 0xffff:
+ return None
+ ipv4 = self.ip & MAX_IPV4_ADDRESS
+ if self._prefixlen != 128:
+ ipv4 = '%s/%s' % (ipv4, 32-(128-self._prefixlen))
+ return IP(ipv4, ipversion=4)
+
+ def reverseNames(self):
+ """Return a list with values forming the reverse lookup.
+
+ >>> IP('213.221.113.87/32').reverseNames()
+ ['87.113.221.213.in-addr.arpa.']
+ >>> IP('213.221.112.224/30').reverseNames()
+ ['224.112.221.213.in-addr.arpa.', '225.112.221.213.in-addr.arpa.', '226.112.221.213.in-addr.arpa.', '227.112.221.213.in-addr.arpa.']
+ >>> IP('127.0.0.0/24').reverseNames()
+ ['0.0.127.in-addr.arpa.']
+ >>> IP('127.0.0.0/23').reverseNames()
+ ['0.0.127.in-addr.arpa.', '1.0.127.in-addr.arpa.']
+ >>> IP('127.0.0.0/16').reverseNames()
+ ['0.127.in-addr.arpa.']
+ >>> IP('127.0.0.0/15').reverseNames()
+ ['0.127.in-addr.arpa.', '1.127.in-addr.arpa.']
+ >>> IP('128.0.0.0/8').reverseNames()
+ ['128.in-addr.arpa.']
+ >>> IP('128.0.0.0/7').reverseNames()
+ ['128.in-addr.arpa.', '129.in-addr.arpa.']
+ >>> IP('::1:2').reverseNames()
+ ['2.0.0.0.1.ip6.arpa.']
+ """
+
+ if self._ipversion == 4:
+ ret = []
+ # TODO: Refactor. Add support for IPint objects
+ if self.len() < 2**8:
+ for x in self:
+ ret.append(x.reverseName())
+ elif self.len() < 2**16:
+ for i in xrange(0, self.len(), 2**8):
+ ret.append(self[i].reverseName()[2:])
+ elif self.len() < 2**24:
+ for i in xrange(0, self.len(), 2**16):
+ ret.append(self[i].reverseName()[4:])
+ else:
+ for i in xrange(0, self.len(), 2**24):
+ ret.append(self[i].reverseName()[6:])
+ return ret
+ elif self._ipversion == 6:
+ ipv4 = self._getIPv4Map()
+ if ipv4 is not None:
+ return ipv4.reverseNames()
+ s = "%x" % self.ip
+ if self._prefixlen % 4 != 0:
+ raise NotImplementedError("can't create IPv6 reverse names at sub nibble level")
+ s = list(s)
+ s.reverse()
+ s = '.'.join(s)
+ first_nibble_index = int(32 - (self._prefixlen // 4)) * 2
+ return ["%s.ip6.arpa." % s[first_nibble_index:]]
+ else:
+ raise ValueError("only IPv4 and IPv6 supported")
+
+ def reverseName(self):
+ """Return the value for reverse lookup/PTR records as RFC 2317 look alike.
+
+ RFC 2317 is an ugly hack which only works for sub-/24 e.g. not
+ for /23. Do not use it. Better set up a zone for every
+ address. See reverseName for a way to achieve that.
+
+ >>> print(IP('195.185.1.1').reverseName())
+ 1.1.185.195.in-addr.arpa.
+ >>> print(IP('195.185.1.0/28').reverseName())
+ 0-15.1.185.195.in-addr.arpa.
+ >>> IP('::1:2').reverseName()
+ '2.0.0.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.'
+ >>> IP('ff02::/64').reverseName()
+ '0.0.0.0.0.0.0.0.0.0.0.0.2.0.f.f.ip6.arpa.'
+ """
+
+ if self._ipversion == 4:
+ s = self.strFullsize(0)
+ s = s.split('.')
+ s.reverse()
+ first_byte_index = int(4 - (self._prefixlen // 8))
+ if self._prefixlen % 8 != 0:
+ nibblepart = "%s-%s" % (s[3-(self._prefixlen // 8)], intToIp(self.ip + self.len() - 1, 4).split('.')[-1])
+ nibblepart += '.'
+ else:
+ nibblepart = ""
+
+ s = '.'.join(s[first_byte_index:])
+ return "%s%s.in-addr.arpa." % (nibblepart, s)
+
+ elif self._ipversion == 6:
+ ipv4 = self._getIPv4Map()
+ if ipv4 is not None:
+ return ipv4.reverseName()
+ s = '%032x' % self.ip
+ if self._prefixlen % 4 != 0:
+ nibblepart = "%s-%x" % (s[self._prefixlen:], self.ip + self.len() - 1)
+ nibblepart += '.'
+ else:
+ nibblepart = ""
+ s = list(s)
+ s.reverse()
+ s = '.'.join(s)
+ first_nibble_index = int(32 - (self._prefixlen // 4)) * 2
+ return "%s%s.ip6.arpa." % (nibblepart, s[first_nibble_index:])
+ else:
+ raise ValueError("only IPv4 and IPv6 supported")
+
+ def make_net(self, netmask):
+ """Transform a single IP address into a network specification by
+ applying the given netmask.
+
+ Returns a new IP instance.
+
+ >>> print(IP('127.0.0.1').make_net('255.0.0.0'))
+ 127.0.0.0/8
+ """
+ if '/' in str(netmask):
+ raise ValueError("invalid netmask (%s)" % netmask)
+ return IP('%s/%s' % (self, netmask), make_net=True)
+
+ def __getitem__(self, key):
+ """Called to implement evaluation of self[key].
+
+ >>> ip=IP('127.0.0.0/30')
+ >>> for x in ip:
+ ... print(str(x))
+ ...
+ 127.0.0.0
+ 127.0.0.1
+ 127.0.0.2
+ 127.0.0.3
+ >>> print(str(ip[2]))
+ 127.0.0.2
+ >>> print(str(ip[-1]))
+ 127.0.0.3
+ """
+ if isinstance(key, slice):
+ return [IP(IPint.__getitem__(self, x), ipversion=self._ipversion) for x in xrange(*key.indices(len(self)))]
+ return IP(IPint.__getitem__(self, key), ipversion=self._ipversion)
+
+ def __repr__(self):
+ """Print a representation of the Object.
+
+ >>> IP('10.0.0.0/8')
+ IP('10.0.0.0/8')
+ """
+
+ return("IP('%s')" % (self.strCompressed(1)))
+
+ def get_mac(self):
+ """
+ Get the 802.3 MAC address from IPv6 RFC 2464 address, in lower case.
+ Return None if the address is an IPv4 or not a IPv6 RFC 2464 address.
+
+ >>> IP('fe80::f66d:04ff:fe47:2fae').get_mac()
+ 'f4:6d:04:47:2f:ae'
+ """
+ if self._ipversion != 6:
+ return None
+ if (self.ip & 0x20000ffff000000) != 0x20000fffe000000:
+ return None
+ return '%02x:%02x:%02x:%02x:%02x:%02x' % (
+ (((self.ip >> 56) & 0xff) & 0xfd),
+ (self.ip >> 48) & 0xff,
+ (self.ip >> 40) & 0xff,
+ (self.ip >> 16) & 0xff,
+ (self.ip >> 8) & 0xff,
+ self.ip & 0xff,
+ )
+
+ def v46map(self):
+ """
+ Returns the IPv6 mapped address of an IPv4 address, or the corresponding
+ IPv4 address if the IPv6 address is in the appropriate range.
+ Raises a ValueError if the IPv6 address is not translatable. See RFC 4291.
+
+ >>> IP('192.168.1.1').v46map()
+ IP('::ffff:192.168.1.1')
+ >>> IP('::ffff:192.168.1.1').v46map()
+ IP('192.168.1.1')
+ """
+ if self._ipversion == 4:
+ return IP(str(IPV6_MAP_MASK + self.ip) +
+ "/%s" % (self._prefixlen + 96))
+ else:
+ if self.ip & IPV6_TEST_MAP == IPV6_MAP_MASK:
+ return IP(str(self.ip - IPV6_MAP_MASK) +
+ "/%s" % (self._prefixlen - 96))
+ raise ValueError("%s cannot be converted to an IPv4 address."
+ % repr(self))
+
+class IPSet(collections.MutableSet):
+ def __init__(self, iterable=[]):
+ # Make sure it's iterable, otherwise wrap
+ if not isinstance(iterable, collections.Iterable):
+ raise TypeError("'%s' object is not iterable" % type(iterable).__name__)
+
+ # Make sure we only accept IP objects
+ for prefix in iterable:
+ if not isinstance(prefix, IP):
+ raise ValueError('Only IP objects can be added to an IPSet')
+
+ # Store and optimize
+ self.prefixes = iterable[:]
+ self.optimize()
+
+ def __contains__(self, ip):
+ valid_masks = self.prefixtable.keys()
+ if isinstance(ip, IP):
+ #Don't dig through more-specific ranges
+ ip_mask = ip._prefixlen
+ valid_masks = [x for x in valid_masks if x <= ip_mask]
+ for mask in sorted(valid_masks):
+ i = bisect.bisect(self.prefixtable[mask], ip)
+ # Because of sorting order, a match can only occur in the prefix
+ # that comes before the result of the search.
+ if i and ip in self.prefixtable[mask][i - 1]:
+ return True
+
+ def __iter__(self):
+ for prefix in self.prefixes:
+ yield prefix
+
+ def __len__(self):
+ return self.len()
+
+ def __add__(self, other):
+ return IPSet(self.prefixes + other.prefixes)
+
+ def __sub__(self, other):
+ new = IPSet(self.prefixes)
+ for prefix in other:
+ new.discard(prefix)
+ return new
+
+ def __and__(self, other):
+ left = iter(self.prefixes)
+ right = iter(other.prefixes)
+ result = []
+ try:
+ l = next(left)
+ r = next(right)
+ while True:
+ # iterate over prefixes in order, keeping the smaller of the
+ # two if they overlap
+ if l in r:
+ result.append(l)
+ l = next(left)
+ continue
+ elif r in l:
+ result.append(r)
+ r = next(right)
+ continue
+ if l < r:
+ l = next(left)
+ else:
+ r = next(right)
+ except StopIteration:
+ return IPSet(result)
+
+ def __repr__(self):
+ return '%s([' % self.__class__.__name__ + ', '.join(map(repr, self.prefixes)) + '])'
+
+ def len(self):
+ return sum(prefix.len() for prefix in self.prefixes)
+
+ def add(self, value):
+ # Make sure it's iterable, otherwise wrap
+ if not isinstance(value, collections.Iterable):
+ value = [value]
+
+ # Check type
+ for prefix in value:
+ if not isinstance(prefix, IP):
+ raise ValueError('Only IP objects can be added to an IPSet')
+
+ # Append and optimize
+ self.prefixes.extend(value)
+ self.optimize()
+
+ def discard(self, value):
+ # Make sure it's iterable, otherwise wrap
+ if not isinstance(value, collections.Iterable):
+ value = [value]
+
+ # This is much faster than iterating over the addresses
+ if isinstance(value, IPSet):
+ value = value.prefixes
+
+ # Remove
+ for del_prefix in value:
+ if not isinstance(del_prefix, IP):
+ raise ValueError('Only IP objects can be removed from an IPSet')
+
+ # First check if this prefix contains anything in our list
+ found = False
+ d = 0
+ for i in range(len(self.prefixes)):
+ if self.prefixes[i - d] in del_prefix:
+ self.prefixes.pop(i - d)
+ d = d + 1
+ found = True
+
+ if found:
+ # If the prefix was bigger than an existing prefix, then it's
+ # certainly not a subset of one, so skip the rest
+ continue
+
+ # Maybe one of our prefixes contains this prefix
+ found = False
+ for i in range(len(self.prefixes)):
+ if del_prefix in self.prefixes[i]:
+ self.prefixes[i:i+1] = self.prefixes[i] - del_prefix
+ break
+
+ self.optimize()
+
+ def isdisjoint(self, other):
+ left = iter(self.prefixes)
+ right = iter(other.prefixes)
+ try:
+ l = next(left)
+ r = next(right)
+ while True:
+ if l in r or r in l:
+ return False
+ if l < r:
+ l = next(left)
+ else:
+ r = next(right)
+ except StopIteration:
+ return True
+
+ def optimize(self):
+ # The algorithm below *depends* on the sort order
+ self.prefixes.sort()
+
+ # First eliminate all values that are a subset of other values
+ addrlen = len(self.prefixes)
+ i = 0
+ while i < addrlen:
+ # Everything that might be inside this prefix follows
+ # directly behind it
+ j = i+1
+ while j < addrlen and self.prefixes[j] in self.prefixes[i]:
+ # Mark for deletion by overwriting with None
+ self.prefixes[j] = None
+ j += 1
+
+ # Continue where we left off
+ i = j
+
+ # Try to merge as many prefixes as possible
+ run_again = True
+ while run_again:
+ # Filter None values. This happens when a subset is eliminated
+ # above, or when two prefixes are merged below
+ self.prefixes = [a for a in self.prefixes if a is not None]
+
+ # We'll set run_again to True when we make changes that require
+ # re-evaluation of the whole list
+ run_again = False
+
+ # We can merge two prefixes that have the same version, same
+ # prefix length and differ only on the last bit of the prefix
+ addrlen = len(self.prefixes)
+ i = 0
+ while i < addrlen-1:
+ j = i + 1
+
+ try:
+ # The next line will throw an exception when merging
+ # is not possible
+ self.prefixes[i] += self.prefixes[j]
+ self.prefixes[j] = None
+ i = j + 1
+ run_again = True
+ except ValueError:
+ # Can't be merged, see if position j can be merged
+ i = j
+
+ # O(n) insertion now by prefix means faster searching on __contains__
+ # when lots of ranges with the same length exist
+ self.prefixtable = {}
+ for address in self.prefixes:
+ try:
+ self.prefixtable[address._prefixlen].append(address)
+ except KeyError:
+ self.prefixtable[address._prefixlen] = [address]
+
+def _parseAddressIPv6(ipstr):
+ """
+ Internal function used by parseAddress() to parse IPv6 address with ':'.
+
+ >>> print(_parseAddressIPv6('::'))
+ 0
+ >>> print(_parseAddressIPv6('::1'))
+ 1
+ >>> print(_parseAddressIPv6('0:0:0:0:0:0:0:1'))
+ 1
+ >>> print(_parseAddressIPv6('0:0:0::0:0:1'))
+ 1
+ >>> print(_parseAddressIPv6('0:0:0:0:0:0:0:0'))
+ 0
+ >>> print(_parseAddressIPv6('0:0:0::0:0:0'))
+ 0
+
+ >>> print(_parseAddressIPv6('FEDC:BA98:7654:3210:FEDC:BA98:7654:3210'))
+ 338770000845734292534325025077361652240
+ >>> print(_parseAddressIPv6('1080:0000:0000:0000:0008:0800:200C:417A'))
+ 21932261930451111902915077091070067066
+ >>> print(_parseAddressIPv6('1080:0:0:0:8:800:200C:417A'))
+ 21932261930451111902915077091070067066
+ >>> print(_parseAddressIPv6('1080:0::8:800:200C:417A'))
+ 21932261930451111902915077091070067066
+ >>> print(_parseAddressIPv6('1080::8:800:200C:417A'))
+ 21932261930451111902915077091070067066
+ >>> print(_parseAddressIPv6('FF01:0:0:0:0:0:0:43'))
+ 338958331222012082418099330867817087043
+ >>> print(_parseAddressIPv6('FF01:0:0::0:0:43'))
+ 338958331222012082418099330867817087043
+ >>> print(_parseAddressIPv6('FF01::43'))
+ 338958331222012082418099330867817087043
+ >>> print(_parseAddressIPv6('0:0:0:0:0:0:13.1.68.3'))
+ 218186755
+ >>> print(_parseAddressIPv6('::13.1.68.3'))
+ 218186755
+ >>> print(_parseAddressIPv6('0:0:0:0:0:FFFF:129.144.52.38'))
+ 281472855454758
+ >>> print(_parseAddressIPv6('::FFFF:129.144.52.38'))
+ 281472855454758
+ >>> print(_parseAddressIPv6('1080:0:0:0:8:800:200C:417A'))
+ 21932261930451111902915077091070067066
+ >>> print(_parseAddressIPv6('1080::8:800:200C:417A'))
+ 21932261930451111902915077091070067066
+ >>> print(_parseAddressIPv6('::1:2:3:4:5:6'))
+ 1208962713947218704138246
+ >>> print(_parseAddressIPv6('1:2:3:4:5:6::'))
+ 5192455318486707404433266432802816
+ """
+
+ # Split string into a list, example:
+ # '1080:200C::417A' => ['1080', '200C', '417A'] and fill_pos=2
+ # and fill_pos is the position of '::' in the list
+ items = []
+ index = 0
+ fill_pos = None
+ while index < len(ipstr):
+ text = ipstr[index:]
+ if text.startswith("::"):
+ if fill_pos is not None:
+ # Invalid IPv6, eg. '1::2::'
+ raise ValueError("%r: Invalid IPv6 address: more than one '::'" % ipstr)
+ fill_pos = len(items)
+ index += 2
+ continue
+ pos = text.find(':')
+ if pos == 0:
+ # Invalid IPv6, eg. '1::2:'
+ raise ValueError("%r: Invalid IPv6 address" % ipstr)
+ if pos != -1:
+ items.append(text[:pos])
+ if text[pos:pos+2] == "::":
+ index += pos
+ else:
+ index += pos+1
+
+ if index == len(ipstr):
+ # Invalid IPv6, eg. '1::2:'
+ raise ValueError("%r: Invalid IPv6 address" % ipstr)
+ else:
+ items.append(text)
+ break
+
+ if items and '.' in items[-1]:
+ # IPv6 ending with IPv4 like '::ffff:192.168.0.1'
+ if (fill_pos is not None) and not (fill_pos <= len(items)-1):
+ # Invalid IPv6: 'ffff:192.168.0.1::'
+ raise ValueError("%r: Invalid IPv6 address: '::' after IPv4" % ipstr)
+ value = parseAddress(items[-1])[0]
+ items = items[:-1] + ["%04x" % (value >> 16), "%04x" % (value & 0xffff)]
+
+ # Expand fill_pos to fill with '0'
+ # ['1','2'] with fill_pos=1 => ['1', '0', '0', '0', '0', '0', '0', '2']
+ if fill_pos is not None:
+ diff = 8 - len(items)
+ if diff <= 0:
+ raise ValueError("%r: Invalid IPv6 address: '::' is not needed" % ipstr)
+ items = items[:fill_pos] + ['0']*diff + items[fill_pos:]
+
+ # Here we have a list of 8 strings
+ if len(items) != 8:
+ # Invalid IPv6, eg. '1:2:3'
+ raise ValueError("%r: Invalid IPv6 address: should have 8 hextets" % ipstr)
+
+ # Convert strings to long integer
+ value = 0
+ index = 0
+ for item in items:
+ try:
+ item = int(item, 16)
+ error = not(0 <= item <= 0xffff)
+ except ValueError:
+ error = True
+ if error:
+ raise ValueError("%r: Invalid IPv6 address: invalid hexlet %r" % (ipstr, item))
+ value = (value << 16) + item
+ index += 1
+ return value
+
+def parseAddress(ipstr):
+ """
+ Parse a string and return the corresponding IP address (as integer)
+ and a guess of the IP version.
+
+ Following address formats are recognized:
+
+ >>> def testParseAddress(address):
+ ... ip, version = parseAddress(address)
+ ... print(("%s (IPv%s)" % (ip, version)))
+ ...
+ >>> testParseAddress('0x0123456789abcdef') # IPv4 if <= 0xffffffff else IPv6
+ 81985529216486895 (IPv6)
+ >>> testParseAddress('123.123.123.123') # IPv4
+ 2071690107 (IPv4)
+ >>> testParseAddress('123.123') # 0-padded IPv4
+ 2071658496 (IPv4)
+ >>> testParseAddress('127')
+ 2130706432 (IPv4)
+ >>> testParseAddress('255')
+ 4278190080 (IPv4)
+ >>> testParseAddress('256')
+ 256 (IPv4)
+ >>> testParseAddress('108000000000000000080800200C417A')
+ 21932261930451111902915077091070067066 (IPv6)
+ >>> testParseAddress('0x108000000000000000080800200C417A')
+ 21932261930451111902915077091070067066 (IPv6)
+ >>> testParseAddress('1080:0000:0000:0000:0008:0800:200C:417A')
+ 21932261930451111902915077091070067066 (IPv6)
+ >>> testParseAddress('1080:0:0:0:8:800:200C:417A')
+ 21932261930451111902915077091070067066 (IPv6)
+ >>> testParseAddress('1080:0::8:800:200C:417A')
+ 21932261930451111902915077091070067066 (IPv6)
+ >>> testParseAddress('::1')
+ 1 (IPv6)
+ >>> testParseAddress('::')
+ 0 (IPv6)
+ >>> testParseAddress('0:0:0:0:0:FFFF:129.144.52.38')
+ 281472855454758 (IPv6)
+ >>> testParseAddress('::13.1.68.3')
+ 218186755 (IPv6)
+ >>> testParseAddress('::FFFF:129.144.52.38')
+ 281472855454758 (IPv6)
+ """
+
+ try:
+ hexval = int(ipstr, 16)
+ except ValueError:
+ hexval = None
+ try:
+ intval = int(ipstr, 10)
+ except ValueError:
+ intval = None
+
+ if ipstr.startswith('0x') and hexval is not None:
+ if hexval > MAX_IPV6_ADDRESS:
+ raise ValueError("IP Address can't be larger than %x: %x" % (MAX_IPV6_ADDRESS, hexval))
+ if hexval <= MAX_IPV4_ADDRESS:
+ return (hexval, 4)
+ else:
+ return (hexval, 6)
+
+ if ipstr.find(':') != -1:
+ return (_parseAddressIPv6(ipstr), 6)
+
+ elif len(ipstr) == 32 and hexval is not None:
+ # assume IPv6 in pure hexadecimal notation
+ return (hexval, 6)
+
+ elif ipstr.find('.') != -1 or (intval is not None and intval < 256):
+ # assume IPv4 ('127' gets interpreted as '127.0.0.0')
+ bytes = ipstr.split('.')
+ if len(bytes) > 4:
+ raise ValueError("IPv4 Address with more than 4 bytes")
+ bytes += ['0'] * (4 - len(bytes))
+ bytes = [int(x) for x in bytes]
+ for x in bytes:
+ if x > 255 or x < 0:
+ raise ValueError("%r: single byte must be 0 <= byte < 256" % (ipstr))
+ return ((bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3], 4)
+
+ elif intval is not None:
+ # we try to interprete it as a decimal digit -
+ # this ony works for numbers > 255 ... others
+ # will be interpreted as IPv4 first byte
+ if intval > MAX_IPV6_ADDRESS:
+ raise ValueError("IP Address can't be larger than %x: %x" % (MAX_IPV6_ADDRESS, intval))
+ if intval <= MAX_IPV4_ADDRESS:
+ return (intval, 4)
+ else:
+ return (intval, 6)
+
+ raise ValueError("IP Address format was invalid: %s" % ipstr)
+
+
+def intToIp(ip, version):
+ """Transform an integer string into an IP address."""
+
+ # just to be sure and hoping for Python 2.2
+ ip = int(ip)
+
+ if ip < 0:
+ raise ValueError("IPs can't be negative: %d" % (ip))
+
+ ret = ''
+ if version == 4:
+ if ip > MAX_IPV4_ADDRESS:
+ raise ValueError("IPv4 Address can't be larger than %x: %x" % (MAX_IPV4_ADDRESS, ip))
+ for l in xrange(4):
+ ret = str(ip & 0xff) + '.' + ret
+ ip = ip >> 8
+ ret = ret[:-1]
+ elif version == 6:
+ if ip > MAX_IPV6_ADDRESS:
+ raise ValueError("IPv6 Address can't be larger than %x: %x" % (MAX_IPV6_ADDRESS, ip))
+ l = "%032x" % ip
+ for x in xrange(1, 33):
+ ret = l[-x] + ret
+ if x % 4 == 0:
+ ret = ':' + ret
+ ret = ret[1:]
+ else:
+ raise ValueError("only IPv4 and IPv6 supported")
+
+ return ret
+
+def _ipVersionToLen(version):
+ """Return number of bits in address for a certain IP version.
+
+ >>> _ipVersionToLen(4)
+ 32
+ >>> _ipVersionToLen(6)
+ 128
+ >>> _ipVersionToLen(5)
+ Traceback (most recent call last):
+ File "", line 1, in ?
+ File "IPy.py", line 1076, in _ipVersionToLen
+ raise ValueError("only IPv4 and IPv6 supported")
+ ValueError: only IPv4 and IPv6 supported
+ """
+
+ if version == 4:
+ return 32
+ elif version == 6:
+ return 128
+ else:
+ raise ValueError("only IPv4 and IPv6 supported")
+
+
+def _countFollowingZeros(l):
+ """Return number of elements containing 0 at the beginning of the list."""
+ if len(l) == 0:
+ return 0
+ elif l[0] != 0:
+ return 0
+ else:
+ return 1 + _countFollowingZeros(l[1:])
+
+
+_BitTable = {'0': '0000', '1': '0001', '2': '0010', '3': '0011',
+ '4': '0100', '5': '0101', '6': '0110', '7': '0111',
+ '8': '1000', '9': '1001', 'a': '1010', 'b': '1011',
+ 'c': '1100', 'd': '1101', 'e': '1110', 'f': '1111'}
+
+def _intToBin(val):
+ """Return the binary representation of an integer as string."""
+
+ if val < 0:
+ raise ValueError("Only positive values allowed")
+ s = "%x" % val
+ ret = ''
+ for x in s:
+ ret += _BitTable[x]
+ # remove leading zeros
+ while ret[0] == '0' and len(ret) > 1:
+ ret = ret[1:]
+ return ret
+
+def _count1Bits(num):
+ """Find the highest bit set to 1 in an integer."""
+ ret = 0
+ while num > 0:
+ num = num >> 1
+ ret += 1
+ return ret
+
+def _count0Bits(num):
+ """Find the highest bit set to 0 in an integer."""
+
+ # this could be so easy if _count1Bits(~int(num)) would work as excepted
+ num = int(num)
+ if num < 0:
+ raise ValueError("Only positive Numbers please: %s" % (num))
+ ret = 0
+ while num > 0:
+ if num & 1 == 1:
+ break
+ num = num >> 1
+ ret += 1
+ return ret
+
+
+def _checkPrefix(ip, prefixlen, version):
+ """Check the validity of a prefix
+
+ Checks if the variant part of a prefix only has 0s, and the length is
+ correct.
+
+ >>> _checkPrefix(0x7f000000, 24, 4)
+ 1
+ >>> _checkPrefix(0x7f000001, 24, 4)
+ 0
+ >>> repr(_checkPrefix(0x7f000001, -1, 4))
+ 'None'
+ >>> repr(_checkPrefix(0x7f000001, 33, 4))
+ 'None'
+ """
+
+ # TODO: unify this v4/v6/invalid code in a function
+ bits = _ipVersionToLen(version)
+
+ if prefixlen < 0 or prefixlen > bits:
+ return None
+
+ if ip == 0:
+ zbits = bits + 1
+ else:
+ zbits = _count0Bits(ip)
+ if zbits < bits - prefixlen:
+ return 0
+ else:
+ return 1
+
+
+def _checkNetmask(netmask, masklen):
+ """Checks if a netmask is expressable as a prefixlen."""
+
+ num = int(netmask)
+ bits = masklen
+
+ # remove zero bits at the end
+ while (num & 1) == 0 and bits != 0:
+ num = num >> 1
+ bits -= 1
+ if bits == 0:
+ break
+ # now check if the rest consists only of ones
+ while bits > 0:
+ if (num & 1) == 0:
+ raise ValueError("Netmask 0x%x can't be expressed as an prefix." % netmask)
+ num = num >> 1
+ bits -= 1
+
+
+def _checkNetaddrWorksWithPrefixlen(net, prefixlen, version):
+ """Check if a base addess of a network is compatible with a prefixlen"""
+ try:
+ return (net & _prefixlenToNetmask(prefixlen, version) == net)
+ except ValueError:
+ return False
+
+
+def _netmaskToPrefixlen(netmask):
+ """Convert an Integer representing a netmask to a prefixlen.
+
+ E.g. 0xffffff00 (255.255.255.0) returns 24
+ """
+
+ netlen = _count0Bits(netmask)
+ masklen = _count1Bits(netmask)
+ _checkNetmask(netmask, masklen)
+ return masklen - netlen
+
+
+def _prefixlenToNetmask(prefixlen, version):
+ """Return a mask of n bits as a long integer.
+
+ From 'IP address conversion functions with the builtin socket module'
+ by Alex Martelli
+ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66517
+ """
+ if prefixlen == 0:
+ return 0
+ elif prefixlen < 0:
+ raise ValueError("Prefixlen must be > 0")
+ return ((2<text) lookups via __getitem__ and
+ reverse (text->value) lookups via __getattr__. Applications
+ using the old interface will need to be updated.
+
+ * Hostnames are now returned with a trailing dot by default (in
+ line with RFC)
+
+ * Most object attributes are now typed in line with the record
+ definitions to make it harder to generate invalid packets
+
+ * Support for encoding/decoding resource records in 'Zone' (BIND)
+ file format
+
+ * Support for encoding/decoding packets in 'DiG' format
+
+ * Server framework allowing (in most cases) custom resolvers to
+ be created by just subclassing the DNSResolver class and
+ overriding the 'resolve' method
+
+ * A lot of fixes to error detection/handling which should make
+ the library much more robust to invalid/unsupported data. The
+ library should now either return a valid DNSRecord instance
+ when parsing a packet or raise DNSError (tested via fuzzing)
+
+ * Improved utilities (dnslib.client, dnslib.proxy, dnslib.intercept)
+
+ * Improvements to encoding/decoding tests including the ability
+ to generate test data automatically in test_decode.py (comparing
+ outputs against DiG)
+
+ * Ability to compare and diff DNSRecords
+
+Classes
+-------
+
+The key DNS packet handling classes are in dnslib.dns and map to the
+standard DNS packet sections:
+
+ * DNSRecord - container for DNS packet. Contains:
+ - DNSHeader
+ - Question section containing zero or more DNSQuestion objects
+ - Answer section containing zero or more RR objects
+ - Authority section containing zero or more RR objects
+ - Additional section containing zero or more RR objects
+ * DNS RRs (resource records) contain an RR header and an RD object)
+ * Specific RD types are implemented as subclasses of RD
+ * DNS labels are represented by a DNSLabel class - in most cases
+ this handles conversion to/from textual representation however
+ does support arbitatry labels via a tuple of bytes objects
+
+Usage
+-----
+
+To decode a DNS packet:
+
+ >>> packet = binascii.unhexlify(b'd5ad818000010005000000000377777706676f6f676c6503636f6d0000010001c00c0005000100000005000803777777016cc010c02c0001000100000005000442f95b68c02c0001000100000005000442f95b63c02c0001000100000005000442f95b67c02c0001000100000005000442f95b93')
+ >>> d = DNSRecord.parse(packet)
+ >>> d
+
+
+
+
+
+
+
+
+The default text representation of the DNSRecord is in zone file format:
+
+ >>> print(d)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54701
+ ;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;www.google.com. IN A
+ ;; ANSWER SECTION:
+ www.google.com. 5 IN CNAME www.l.google.com.
+ www.l.google.com. 5 IN A 66.249.91.104
+ www.l.google.com. 5 IN A 66.249.91.99
+ www.l.google.com. 5 IN A 66.249.91.103
+ www.l.google.com. 5 IN A 66.249.91.147
+
+To create a DNS Request Packet:
+
+ >>> d = DNSRecord.question("google.com")
+
+(This is equivalent to: d = DNSRecord(q=DNSQuestion("google.com") )
+
+ >>> d
+
+
+
+ >>> str(DNSRecord.parse(d.pack())) == str(d)
+ True
+
+ >>> print(d)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;google.com. IN A
+
+ >>> d = DNSRecord.question("google.com","MX")
+
+(This is equivalent to: d = DNSRecord(q=DNSQuestion("google.com",QTYPE.MX) )
+
+ >>> str(DNSRecord.parse(d.pack())) == str(d)
+ True
+
+ >>> print(d)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;google.com. IN MX
+
+To create a DNS Response Packet:
+
+ >>> d = DNSRecord(DNSHeader(qr=1,aa=1,ra=1),
+ ... q=DNSQuestion("abc.com"),
+ ... a=RR("abc.com",rdata=A("1.2.3.4")))
+ >>> d
+
+
+
+ >>> str(DNSRecord.parse(d.pack())) == str(d)
+ True
+
+ >>> print(d)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;; ANSWER SECTION:
+ abc.com. 0 IN A 1.2.3.4
+
+It is also possible to create RRs from a string in zone file format
+
+ >>> RR.fromZone("abc.com IN A 1.2.3.4")
+ []
+
+(Note: this produces a list of RRs which should be unpacked if being
+ passed to add_answer/add_auth/add_ar etc)
+
+ >>> q = DNSRecord.question("abc.com")
+ >>> a = q.reply()
+ >>> a.add_answer(*RR.fromZone("abc.com 60 A 1.2.3.4"))
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;; ANSWER SECTION:
+ abc.com. 60 IN A 1.2.3.4
+
+The zone file can contain multiple entries and supports most of the normal
+format defined in RFC1035 (specifically not $INCLUDE)
+
+ >>> z = '''
+ ... $TTL 300
+ ... $ORIGIN abc.com
+ ...
+ ... @ IN MX 10 mail.abc.com.
+ ... www IN A 1.2.3.4
+ ... IN TXT "Some Text"
+ ... mail IN CNAME www.abc.com.
+ ... '''
+ >>> for rr in RR.fromZone(textwrap.dedent(z)):
+ ... print(rr)
+ abc.com. 300 IN MX 10 mail.abc.com.
+ www.abc.com. 300 IN A 1.2.3.4
+ www.abc.com. 300 IN TXT "Some Text"
+ mail.abc.com. 300 IN CNAME www.abc.com.
+
+To create a skeleton reply to a DNS query:
+
+ >>> q = DNSRecord(q=DNSQuestion("abc.com",QTYPE.ANY))
+ >>> a = q.reply()
+ >>> a.add_answer(RR("abc.com",QTYPE.A,rdata=A("1.2.3.4"),ttl=60))
+ >>> str(DNSRecord.parse(a.pack())) == str(a)
+ True
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN ANY
+ ;; ANSWER SECTION:
+ abc.com. 60 IN A 1.2.3.4
+
+Add additional RRs:
+
+ >>> a.add_answer(RR("xxx.abc.com",QTYPE.A,rdata=A("1.2.3.4")))
+ >>> a.add_answer(RR("xxx.abc.com",QTYPE.AAAA,rdata=AAAA("1234:5678::1")))
+ >>> str(DNSRecord.parse(a.pack())) == str(a)
+ True
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN ANY
+ ;; ANSWER SECTION:
+ abc.com. 60 IN A 1.2.3.4
+ xxx.abc.com. 0 IN A 1.2.3.4
+ xxx.abc.com. 0 IN AAAA 1234:5678::1
+
+
+It is also possible to create a reply from a string in zone file format:
+
+ >>> q = DNSRecord(q=DNSQuestion("abc.com",QTYPE.ANY))
+ >>> a = q.replyZone("abc.com 60 IN CNAME xxx.abc.com")
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN ANY
+ ;; ANSWER SECTION:
+ abc.com. 60 IN CNAME xxx.abc.com.
+
+ >>> str(DNSRecord.parse(a.pack())) == str(a)
+ True
+
+ >>> q = DNSRecord(q=DNSQuestion("abc.com",QTYPE.ANY))
+ >>> a = q.replyZone(textwrap.dedent(z))
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN ANY
+ ;; ANSWER SECTION:
+ abc.com. 300 IN MX 10 mail.abc.com.
+ www.abc.com. 300 IN A 1.2.3.4
+ www.abc.com. 300 IN TXT "Some Text"
+ mail.abc.com. 300 IN CNAME www.abc.com.
+
+
+The library also includes a simple framework for generating custom DNS
+resolvers in dnslib.server (see module docs). In post cases this just
+requires implementing a custom 'resolve' method which receives a question
+object and returns a response.
+
+A number of sample resolvers are provided as examples (see CLI --help):
+
+ * dnslib.fixedresolver - Respond to all requests with fixed response
+ * dnslib.zoneresolver - Respond from Zone file
+ * dnslib.shellresolver - Call shell script to generate response
+
+The library includes a number of client utilities:
+
+ * DiG like client library
+
+ # python -m dnslib.client --help
+
+ * DNS Proxy Server
+
+ # python -m dnslib.proxy --help
+
+ * Intercepting DNS Proxy Server (replace proxy responses for specified domains)
+
+ # python -m dnslib.intercept --help
+
+
+Changelog:
+----------
+
+ * 0.1 2010-09-19 Initial Release
+ * 0.2 2010-09-22 Minor fixes
+ * 0.3 2010-10-02 Add DNSLabel class to support arbitrary labels (embedded '.')
+ * 0.4 2012-02-26 Merge with dbslib-circuits
+ * 0.5 2012-09-13 Add support for RFC2136 DDNS updates
+ Patch provided by Wesley Shields - thanks
+ * 0.6 2012-10-20 Basic AAAA support
+ * 0.7 2012-10-20 Add initial EDNS0 support (untested)
+ * 0.8 2012-11-04 Add support for NAPTR, Authority RR and additional RR
+ Patch provided by Stefan Andersson (https://bitbucket.org/norox) - thanks
+ * 0.8.1 2012-11-05 Added NAPTR test case and fixed logic error
+ Patch provided by Stefan Andersson (https://bitbucket.org/norox) - thanks
+ * 0.8.2 2012-11-11 Patch to fix IPv6 formatting
+ Patch provided by Torbjörn Lönnemark (https://bitbucket.org/tobbezz) - thanks
+ * 0.8.3 2013-04-27 Don't parse rdata if rdlength is 0
+ Patch provided by Wesley Shields - thanks
+ * 0.9.0 2014-05-05 Major update including Py3 support (see docs)
+ * 0.9.1 2014-05-05 Minor fixes
+ * 0.9.2 2014-08-26 Fix Bimap handling of unknown mappings to avoid exception in printing
+ Add typed attributes to classes
+ Misc fixes from James Mills - thanks
+ * 0.9.3 2014-08-26 Workaround for argparse bug which raises AssertionError if [] is
+ present in option text (really?)
+ * 0.9.4 2015-04-10 Fix to support multiple strings in TXT record
+ Patch provided by James Cherry (https://bitbucket.org/james_cherry) - thanks
+ NOTE: For consistency this patch changes the 'repr' output for
+ TXT records to always be quoted
+ * 0.9.5 2015-10-27 Add threading & timeout handling to DNSServer
+ * 0.9.6 2015-10-28 Replace strftime in RRSIG formatting to avoid possible locale issues
+ Identified by Bryan Everly - thanks
+
+
+License:
+--------
+
+BSD
+
+Author:
+-------
+
+ * Paul Chakravarti (paul.chakravarti@gmail.com)
+
+Master Repository/Issues:
+-------------------------
+
+ * https://bitbucket.org/paulc/dnslib
+ (Cloned on GitHub: https://github.com/paulchakravarti/dnslib)
+"""
+
+from dnslib.dns import *
+
+version = "0.9.6"
+
+if __name__ == '__main__':
+ import doctest,textwrap
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
+
diff --git a/lib/dnslib/build/lib.linux-i686-2.7/dnslib/bimap.py b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/bimap.py
new file mode 100644
index 0000000..4cf9d37
--- /dev/null
+++ b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/bimap.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+
+"""
+ Bimap - bidirectional mapping between code/value
+"""
+
+class BimapError(Exception):
+ pass
+
+class Bimap(object):
+
+ """
+ Bi-directional mapping between code/text.
+
+ Initialised using:
+
+ name: Used for exceptions
+ dict: Dict mapping from code (numeric) to text
+ error: Error type to raise if key not found
+
+ The class provides:
+
+ * A 'forward' map (code->text) which is accessed through
+ __getitem__ (bimap[code])
+ * A 'reverse' map (code>value) which is accessed through
+ __getattr__ (bimap.text)
+ * A 'get' method which does a forward lookup (code->text)
+ and returns a textual version of code if there is no
+ explicit mapping (or default provided)
+
+ >>> class TestError(Exception):
+ ... pass
+
+ >>> TEST = Bimap('TEST',{1:'A', 2:'B', 3:'C'},TestError)
+ >>> TEST[1]
+ 'A'
+ >>> TEST.A
+ 1
+ >>> TEST.X
+ Traceback (most recent call last):
+ ...
+ TestError: TEST: Invalid reverse lookup: [X]
+ >>> TEST[99]
+ Traceback (most recent call last):
+ ...
+ TestError: TEST: Invalid forward lookup: [99]
+ >>> TEST.get(99)
+ '99'
+
+ """
+
+ def __init__(self,name,forward,error=KeyError):
+ self.name = name
+ self.error = error
+ self.forward = forward.copy()
+ self.reverse = dict([(v,k) for (k,v) in list(forward.items())])
+
+ def get(self,k,default=None):
+ try:
+ return self.forward[k]
+ except KeyError as e:
+ return default or str(k)
+
+ def __getitem__(self,k):
+ try:
+ return self.forward[k]
+ except KeyError as e:
+ raise self.error("%s: Invalid forward lookup: [%s]" % (self.name,k))
+
+ def __getattr__(self,k):
+ try:
+ return self.reverse[k]
+ except KeyError as e:
+ raise self.error("%s: Invalid reverse lookup: [%s]" % (self.name,k))
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
diff --git a/lib/dnslib/build/lib.linux-i686-2.7/dnslib/bit.py b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/bit.py
new file mode 100644
index 0000000..8a9f5fe
--- /dev/null
+++ b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/bit.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+"""
+ Some basic bit mainpulation utilities
+"""
+from __future__ import print_function
+
+FILTER = bytearray([ (i < 32 or i > 127) and 46 or i for i in range(256) ])
+
+def hexdump(src, length=16, prefix=''):
+ """
+ Print hexdump of string
+
+ >>> print(hexdump(b"abcd" * 4))
+ 0000 61 62 63 64 61 62 63 64 61 62 63 64 61 62 63 64 abcdabcd abcdabcd
+
+ >>> print(hexdump(bytearray(range(48))))
+ 0000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ........ ........
+ 0010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ........ ........
+ 0020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&' ()*+,-./
+
+ """
+ n = 0
+ left = length // 2
+ right = length - left
+ result= []
+ src = bytearray(src)
+ while src:
+ s,src = src[:length],src[length:]
+ l,r = s[:left],s[left:]
+ hexa = "%-*s" % (left*3,' '.join(["%02x"%x for x in l]))
+ hexb = "%-*s" % (right*3,' '.join(["%02x"%x for x in r]))
+ lf = l.translate(FILTER)
+ rf = r.translate(FILTER)
+ result.append("%s%04x %s %s %s %s" % (prefix, n, hexa, hexb,
+ lf.decode(), rf.decode()))
+ n += length
+ return "\n".join(result)
+
+def get_bits(data,offset,bits=1):
+ """
+ Get specified bits from integer
+
+ >>> bin(get_bits(0b0011100,2))
+ '0b1'
+ >>> bin(get_bits(0b0011100,0,4))
+ '0b1100'
+
+ """
+ mask = ((1 << bits) - 1) << offset
+ return (data & mask) >> offset
+
+def set_bits(data,value,offset,bits=1):
+ """
+ Set specified bits in integer
+
+ >>> bin(set_bits(0,0b1010,0,4))
+ '0b1010'
+ >>> bin(set_bits(0,0b1010,3,4))
+ '0b1010000'
+ """
+ mask = ((1 << bits) - 1) << offset
+ clear = 0xffff ^ mask
+ data = (data & clear) | ((value << offset) & mask)
+ return data
+
+def binary(n,count=16,reverse=False):
+ """
+ Display n in binary (only difference from built-in `bin` is
+ that this function returns a fixed width string and can
+ optionally be reversed
+
+ >>> binary(6789)
+ '0001101010000101'
+ >>> binary(6789,8)
+ '10000101'
+ >>> binary(6789,reverse=True)
+ '1010000101011000'
+
+ """
+ bits = [str((n >> y) & 1) for y in range(count-1, -1, -1)]
+ if reverse:
+ bits.reverse()
+ return "".join(bits)
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+
diff --git a/lib/dnslib/build/lib.linux-i686-2.7/dnslib/buffer.py b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/buffer.py
new file mode 100644
index 0000000..f8772c8
--- /dev/null
+++ b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/buffer.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+"""
+ Buffer - simple data buffer
+"""
+
+import binascii,struct
+
+class BufferError(Exception):
+ pass
+
+class Buffer(object):
+
+ """
+ A simple data buffer - supports packing/unpacking in struct format
+
+ # Needed for Python 2/3 doctest compatibility
+ >>> def p(s):
+ ... if not isinstance(s,str):
+ ... return s.decode()
+ ... return s
+
+ >>> b = Buffer()
+ >>> b.pack("!BHI",1,2,3)
+ >>> b.offset
+ 7
+ >>> b.append(b"0123456789")
+ >>> b.offset
+ 17
+ >>> p(b.hex())
+ '0100020000000330313233343536373839'
+ >>> b.offset = 0
+ >>> b.unpack("!BHI")
+ (1, 2, 3)
+ >>> bytearray(b.get(5))
+ bytearray(b'01234')
+ >>> bytearray(b.get(5))
+ bytearray(b'56789')
+ >>> b.update(7,"2s",b"xx")
+ >>> b.offset = 7
+ >>> bytearray(b.get(5))
+ bytearray(b'xx234')
+ """
+
+ def __init__(self,data=b''):
+ """
+ Initialise Buffer from data
+ """
+ self.data = bytearray(data)
+ self.offset = 0
+
+ def remaining(self):
+ """
+ Return bytes remaining
+ """
+ return len(self.data) - self.offset
+
+ def get(self,length):
+ """
+ Gen len bytes at current offset (& increment offset)
+ """
+ if length > self.remaining():
+ raise BufferError("Not enough bytes [offset=%d,remaining=%d,requested=%d]" %
+ (self.offset,self.remaining(),length))
+ start = self.offset
+ end = self.offset + length
+ self.offset += length
+ return bytes(self.data[start:end])
+
+ def hex(self):
+ """
+ Return data as hex string
+ """
+ return binascii.hexlify(self.data)
+
+ def pack(self,fmt,*args):
+ """
+ Pack data at end of data according to fmt (from struct) & increment
+ offset
+ """
+ self.offset += struct.calcsize(fmt)
+ self.data += struct.pack(fmt,*args)
+
+ def append(self,s):
+ """
+ Append s to end of data & increment offset
+ """
+ self.offset += len(s)
+ self.data += s
+
+ def update(self,ptr,fmt,*args):
+ """
+ Modify data at offset `ptr`
+ """
+ s = struct.pack(fmt,*args)
+ self.data[ptr:ptr+len(s)] = s
+
+ def unpack(self,fmt):
+ """
+ Unpack data at current offset according to fmt (from struct)
+ """
+ try:
+ data = self.get(struct.calcsize(fmt))
+ return struct.unpack(fmt,data)
+ except struct.error as e:
+ raise BufferError("Error unpacking struct '%s' <%s>" %
+ (fmt,binascii.hexlify(data).decode()))
+
+ def __len__(self):
+ return len(self.data)
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
diff --git a/lib/dnslib/build/lib.linux-i686-2.7/dnslib/client.py b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/client.py
new file mode 100644
index 0000000..9952243
--- /dev/null
+++ b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/client.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+
+"""
+ DNS Client - DiG-like CLI utility.
+
+ Mostly useful for testing. Can optionally compare results from two
+ nameservers (--diff) or compare results against DiG (--dig).
+
+ Usage: python -m dnslib.client [options|--help]
+
+ See --help for usage.
+"""
+
+from __future__ import print_function
+
+try:
+ from subprocess import getoutput
+except ImportError:
+ from commands import getoutput
+
+import binascii,code,pprint
+
+from dnslib.dns import DNSRecord,DNSHeader,DNSQuestion,QTYPE
+from dnslib.digparser import DigParser
+
+if __name__ == '__main__':
+
+ import argparse,sys,time
+
+ p = argparse.ArgumentParser(description="DNS Client")
+ p.add_argument("--server","-s",default="8.8.8.8",
+ metavar="",
+ help="Server address:port (default:8.8.8.8:53) (port is optional)")
+ p.add_argument("--query",action='store_true',default=False,
+ help="Show query (default: False)")
+ p.add_argument("--hex",action='store_true',default=False,
+ help="Dump packet in hex (default: False)")
+ p.add_argument("--tcp",action='store_true',default=False,
+ help="Use TCP (default: UDP)")
+ p.add_argument("--noretry",action='store_true',default=False,
+ help="Don't retry query using TCP if truncated (default: false)")
+ p.add_argument("--diff",default="",
+ help="Compare response from alternate nameserver (format: address:port / default: false)")
+ p.add_argument("--dig",action='store_true',default=False,
+ help="Compare result with DiG - if ---diff also specified use alternative nameserver for DiG request (default: false)")
+ p.add_argument("--short",action='store_true',default=False,
+ help="Short output - rdata only (default: false)")
+ p.add_argument("--debug",action='store_true',default=False,
+ help="Drop into CLI after request (default: false)")
+ p.add_argument("domain",metavar="",
+ help="Query domain")
+ p.add_argument("qtype",metavar="",default="A",nargs="?",
+ help="Query type (default: A)")
+ args = p.parse_args()
+
+ # Construct request
+ q = DNSRecord(q=DNSQuestion(args.domain,getattr(QTYPE,args.qtype)))
+
+ address,_,port = args.server.partition(':')
+ port = int(port or 53)
+
+ if args.query:
+ print(";; Sending%s:" % (" (TCP)" if args.tcp else ""))
+ if args.hex:
+ print(";; QUERY:",binascii.hexlify(q.pack()).decode())
+ print(q)
+ print()
+
+ a_pkt = q.send(address,port,tcp=args.tcp)
+ a = DNSRecord.parse(a_pkt)
+
+ if a.header.tc and args.noretry == False:
+ # Truncated - retry in TCP mode
+ a_pkt = q.send(address,port,tcp=True)
+ a = DNSRecord.parse(a_pkt)
+
+ if args.dig or args.diff:
+ if args.diff:
+ address,_,port = args.diff.partition(':')
+ port = int(port or 53)
+
+ if args.dig:
+ dig = getoutput("dig +qr -p %d %s %s @%s" % (
+ port, args.domain, args.qtype, address))
+ dig_reply = list(iter(DigParser(dig)))
+ # DiG might have retried in TCP mode so get last q/a
+ q_diff = dig_reply[-2]
+ a_diff = dig_reply[-1]
+ else:
+ q_diff = DNSRecord(header=DNSHeader(id=q.header.id),
+ q=DNSQuestion(args.domain,
+ getattr(QTYPE,args.qtype)))
+ q_diff = q
+ diff = q_diff.send(address,port,tcp=args.tcp)
+ a_diff = DNSRecord.parse(diff)
+ if a_diff.header.tc and args.noretry == False:
+ diff = q_diff.send(address,port,tcp=True)
+ a_diff = DNSRecord.parse(diff)
+
+ if args.short:
+ print(a.short())
+ else:
+ print(";; Got answer:")
+ if args.hex:
+ print(";; RESPONSE:",binascii.hexlify(a_pkt).decode())
+ if args.diff and not args.dig:
+ print(";; DIFF :",binascii.hexlify(diff).decode())
+ print(a)
+ print()
+
+ if args.dig or args.diff:
+ if q != q_diff:
+ print(";;; ERROR: Diff Question differs")
+ for (d1,d2) in q.diff(q_diff):
+ if d1:
+ print(";; - %s" % d1)
+ if d2:
+ print(";; + %s" % d2)
+ if a != a_diff:
+ print(";;; ERROR: Diff Response differs")
+ for (d1,d2) in a.diff(a_diff):
+ if d1:
+ print(";; - %s" % d1)
+ if d2:
+ print(";; + %s" % d2)
+
+ if args.debug:
+ code.interact(local=locals())
+
diff --git a/lib/dnslib/build/lib.linux-i686-2.7/dnslib/digparser.py b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/digparser.py
new file mode 100644
index 0000000..ee7c8ef
--- /dev/null
+++ b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/digparser.py
@@ -0,0 +1,231 @@
+# -*- coding: utf-8 -*-
+"""
+
+ digparser
+ ---------
+
+ Encode/decode DNS packets from DiG textual representation. Parses
+ question (if present: +qr flag) & answer sections and returns list
+ of DNSRecord objects.
+
+ Unsupported RR types are skipped (this is different from the packet
+ parser which will store and encode the RDATA as a binary blob)
+
+ >>> dig = os.path.join(os.path.dirname(__file__),"test","dig","google.com-A.dig")
+ >>> with open(dig) as f:
+ ... l = DigParser(f)
+ ... for record in l:
+ ... print('---')
+ ... print(repr(record))
+ ---
+
+
+ ---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >>> dig = os.path.join(os.path.dirname(__file__),"test","dig","google.com-ANY.dig")
+ >>> with open(dig) as f:
+ ... l = DigParser(f)
+ ... for record in l:
+ ... print('---')
+ ... print(repr(record))
+ ---
+
+
+ ---
+
+
+ ---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+from __future__ import print_function
+
+import glob,os.path,string
+
+from dnslib.lex import WordLexer
+from dnslib.dns import (DNSRecord,DNSHeader,DNSQuestion,DNSError,
+ RR,RD,RDMAP,QR,RCODE,CLASS,QTYPE)
+
+class DigParser:
+
+ """
+ Parse Dig output
+ """
+
+ def __init__(self,dig,debug=False):
+ self.debug = debug
+ self.l = WordLexer(dig)
+ self.l.commentchars = ';'
+ self.l.nltok = ('NL',None)
+ self.i = iter(self.l)
+
+ def parseHeader(self,l1,l2):
+ _,_,_,opcode,_,status,_,_id = l1.split()
+ _,flags,_ = l2.split(';')
+ header = DNSHeader(id=int(_id),bitmap=0)
+ header.opcode = getattr(QR,opcode.rstrip(','))
+ header.rcode = getattr(RCODE,status.rstrip(','))
+ for f in ('qr','aa','tc','rd','ra'):
+ if f in flags:
+ setattr(header,f,1)
+ return header
+
+ def expect(self,expect):
+ t,val = next(self.i)
+ if t != expect:
+ raise ValueError("Invalid Token: %s (expecting: %s)" % (t,expect))
+ return val
+
+ def parseQuestions(self,q,dns):
+ for qname,qclass,qtype in q:
+ dns.add_question(DNSQuestion(qname,
+ getattr(QTYPE,qtype),
+ getattr(CLASS,qclass)))
+
+ def parseAnswers(self,a,auth,ar,dns):
+ sect_map = {'a':'add_answer','auth':'add_auth','ar':'add_ar'}
+ for sect in 'a','auth','ar':
+ f = getattr(dns,sect_map[sect])
+ for rr in locals()[sect]:
+ rname,ttl,rclass,rtype = rr[:4]
+ rdata = rr[4:]
+ rd = RDMAP.get(rtype,RD)
+ try:
+ if rd == RD and \
+ any([ x not in string.hexdigits for x in rdata[-1]]):
+ # Only support hex encoded data for fallback RD
+ pass
+ else:
+ f(RR(rname=rname,
+ ttl=int(ttl),
+ rtype=getattr(QTYPE,rtype),
+ rclass=getattr(CLASS,rclass),
+ rdata=rd.fromZone(rdata)))
+ except DNSError as e:
+ if self.debug:
+ print("DNSError:",e,rr)
+ else:
+ # Skip records we dont understand
+ pass
+
+ def __iter__(self):
+ return self.parse()
+
+ def parse(self):
+ dns = None
+ section = None
+ paren = False
+ rr = []
+ try:
+ while True:
+ tok,val = next(self.i)
+ if tok == 'COMMENT':
+ if 'Sending:' in val or 'Got answer:' in val:
+ if dns:
+ self.parseQuestions(q,dns)
+ self.parseAnswers(a,auth,ar,dns)
+ yield(dns)
+ dns = DNSRecord()
+ q,a,auth,ar = [],[],[],[]
+ elif val.startswith('; ->>HEADER<<-'):
+ self.expect('NL')
+ val2 = self.expect('COMMENT')
+ dns.header = self.parseHeader(val,val2)
+ elif val.startswith('; QUESTION'):
+ section = q
+ elif val.startswith('; ANSWER'):
+ section = a
+ elif val.startswith('; AUTHORITY'):
+ section = auth
+ elif val.startswith('; ADDITIONAL'):
+ section = ar
+ elif val.startswith(';') or tok[1].startswith('<<>>'):
+ pass
+ elif dns and section == q:
+ q.append(val.split())
+ elif tok == 'ATOM':
+ if val == '(':
+ paren = True
+ elif val == ')':
+ paren = False
+ else:
+ rr.append(val)
+ elif tok == 'NL' and not paren and rr:
+ if self.debug:
+ print(">>",rr)
+ section.append(rr)
+ rr = []
+ except StopIteration:
+ if rr:
+ self.section.append(rr)
+ if dns:
+ self.parseQuestions(q,dns)
+ self.parseAnswers(a,auth,ar,dns)
+ yield(dns)
+
+if __name__ == '__main__':
+
+ import argparse,doctest,sys
+
+ p = argparse.ArgumentParser(description="DigParser Test")
+ p.add_argument("--dig",action='store_true',default=False,
+ help="Parse DiG output (stdin)")
+ p.add_argument("--debug",action='store_true',default=False,
+ help="Debug output")
+
+ args = p.parse_args()
+
+ if args.dig:
+ l = DigParser(sys.stdin,args.debug)
+ for record in l:
+ print(repr(record))
+ else:
+ doctest.testmod()
diff --git a/lib/dnslib/build/lib.linux-i686-2.7/dnslib/dns.py b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/dns.py
new file mode 100644
index 0000000..e4cdd3a
--- /dev/null
+++ b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/dns.py
@@ -0,0 +1,1669 @@
+
+"""
+ DNS - main dnslib module
+
+ Contains core DNS packet handling code
+"""
+
+from __future__ import print_function
+
+import base64,binascii,calendar,collections,copy,os.path,random,socket,\
+ string,struct,textwrap,time
+
+from itertools import chain
+
+try:
+ from itertools import zip_longest
+except ImportError:
+ from itertools import izip_longest as zip_longest
+
+from dnslib.bit import get_bits,set_bits
+from dnslib.bimap import Bimap,BimapError
+from dnslib.buffer import Buffer,BufferError
+from dnslib.label import DNSLabel,DNSLabelError,DNSBuffer
+from dnslib.lex import WordLexer
+from dnslib.ranges import BYTES,B,H,I,IP4,IP6,ntuple_range,check_range,\
+ check_bytes
+
+class DNSError(Exception):
+ pass
+
+# DNS codes
+
+QTYPE = Bimap('QTYPE',
+ {1:'A', 2:'NS', 5:'CNAME', 6:'SOA', 12:'PTR', 15:'MX',
+ 16:'TXT', 17:'RP', 18:'AFSDB', 24:'SIG', 25:'KEY', 28:'AAAA',
+ 29:'LOC', 33:'SRV', 35:'NAPTR', 36:'KX', 37:'CERT', 38:'A6',
+ 39:'DNAME', 41:'OPT', 42:'APL', 43:'DS', 44:'SSHFP',
+ 45:'IPSECKEY', 46:'RRSIG', 47:'NSEC', 48:'DNSKEY', 49:'DHCID',
+ 50:'NSEC3', 51:'NSEC3PARAM', 52:'TLSA', 55:'HIP', 99:'SPF',
+ 249:'TKEY', 250:'TSIG', 251:'IXFR', 252:'AXFR', 255:'ANY',
+ 257:'TYPE257', 32768:'TA', 32769:'DLV'},
+ DNSError)
+CLASS = Bimap('CLASS',
+ {1:'IN', 2:'CS', 3:'CH', 4:'Hesiod', 254:'None', 255:'*'},
+ DNSError)
+QR = Bimap('QR',
+ {0:'QUERY', 1:'RESPONSE'},
+ DNSError)
+RCODE = Bimap('RCODE',
+ {0:'NOERROR', 1:'FORMERR', 2:'SERVFAIL', 3:'NXDOMAIN',
+ 4:'NOTIMP', 5:'REFUSED', 6:'YXDOMAIN', 7:'YXRRSET',
+ 8:'NXRRSET', 9:'NOTAUTH', 10:'NOTZONE'},
+ DNSError)
+OPCODE = Bimap('OPCODE',{0:'QUERY', 1:'IQUERY', 2:'STATUS', 5:'UPDATE'},
+ DNSError)
+
+def label(label,origin=None):
+ if label.endswith("."):
+ return DNSLabel(label)
+ else:
+ return (origin if isinstance(origin,DNSLabel)
+ else DNSLabel(origin)).add(label)
+
+class DNSRecord(object):
+
+ """
+ Main DNS class - corresponds to DNS packet & comprises DNSHeader,
+ DNSQuestion and RR sections (answer,ns,ar)
+
+ >>> d = DNSRecord()
+ >>> d.add_question(DNSQuestion("abc.com")) # Or DNSRecord.question("abc.com")
+ >>> d.add_answer(RR("abc.com",QTYPE.CNAME,ttl=60,rdata=CNAME("ns.abc.com")))
+ >>> d.add_auth(RR("abc.com",QTYPE.SOA,ttl=60,rdata=SOA("ns.abc.com","admin.abc.com",(20140101,3600,3600,3600,3600))))
+ >>> d.add_ar(RR("ns.abc.com",ttl=60,rdata=A("1.2.3.4")))
+ >>> print(d)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;; ANSWER SECTION:
+ abc.com. 60 IN CNAME ns.abc.com.
+ ;; AUTHORITY SECTION:
+ abc.com. 60 IN SOA ns.abc.com. admin.abc.com. 20140101 3600 3600 3600 3600
+ ;; ADDITIONAL SECTION:
+ ns.abc.com. 60 IN A 1.2.3.4
+ >>> str(d) == str(DNSRecord.parse(d.pack()))
+ True
+ """
+
+ @classmethod
+ def parse(cls,packet):
+ """
+ Parse DNS packet data and return DNSRecord instance
+ Recursively parses sections (calling appropriate parse method)
+ """
+ buffer = DNSBuffer(packet)
+ try:
+ header = DNSHeader.parse(buffer)
+ questions = []
+ rr = []
+ auth = []
+ ar = []
+ for i in range(header.q):
+ questions.append(DNSQuestion.parse(buffer))
+ for i in range(header.a):
+ rr.append(RR.parse(buffer))
+ for i in range(header.auth):
+ auth.append(RR.parse(buffer))
+ for i in range(header.ar):
+ ar.append(RR.parse(buffer))
+ return cls(header,questions,rr,auth=auth,ar=ar)
+ except DNSError:
+ raise
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking DNSRecord [offset=%d]: %s" % (
+ buffer.offset,e))
+
+ @classmethod
+ def question(cls,qname,qtype="A",qclass="IN"):
+ """
+ Shortcut to create question
+
+ >>> q = DNSRecord.question("www.google.com")
+ >>> print(q)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;www.google.com. IN A
+
+ >>> q = DNSRecord.question("www.google.com","NS")
+ >>> print(q)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;www.google.com. IN NS
+ """
+ return DNSRecord(q=DNSQuestion(qname,getattr(QTYPE,qtype),
+ getattr(CLASS,qclass)))
+
+
+ def __init__(self,header=None,questions=None,
+ rr=None,q=None,a=None,auth=None,ar=None):
+ """
+ Create new DNSRecord
+ """
+ self.header = header or DNSHeader()
+ self.questions = questions or []
+ self.rr = rr or []
+ self.auth = auth or []
+ self.ar = ar or []
+ # Shortcuts to add a single Question/Answer
+ if q:
+ self.questions.append(q)
+ if a:
+ self.rr.append(a)
+ self.set_header_qa()
+
+ def reply(self,ra=1,aa=1):
+ """
+ Create skeleton reply packet
+
+ >>> q = DNSRecord.question("abc.com")
+ >>> a = q.reply()
+ >>> a.add_answer(RR("abc.com",QTYPE.A,rdata=A("1.2.3.4"),ttl=60))
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;; ANSWER SECTION:
+ abc.com. 60 IN A 1.2.3.4
+ """
+ return DNSRecord(DNSHeader(id=self.header.id,
+ bitmap=self.header.bitmap,
+ qr=1,ra=ra,aa=aa),
+ q=self.q)
+
+ def replyZone(self,zone,ra=1,aa=1):
+ """
+ Create reply with response data in zone-file format
+ >>> q = DNSRecord.question("abc.com")
+ >>> a = q.replyZone("abc.com 60 A 1.2.3.4")
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;; ANSWER SECTION:
+ abc.com. 60 IN A 1.2.3.4
+ """
+ return DNSRecord(DNSHeader(id=self.header.id,
+ bitmap=self.header.bitmap,
+ qr=1,ra=ra,aa=aa),
+ q=self.q,
+ rr=RR.fromZone(zone))
+
+ def add_question(self,*q):
+ """
+ Add question(s)
+
+ >>> q = DNSRecord()
+ >>> q.add_question(DNSQuestion("abc.com"),
+ ... DNSQuestion("abc.com",QTYPE.MX))
+ >>> print(q)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: rd; QUERY: 2, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;abc.com. IN MX
+ """
+ self.questions.extend(q)
+ self.set_header_qa()
+
+ def add_answer(self,*rr):
+ """
+ Add answer(s)
+
+ >>> q = DNSRecord.question("abc.com")
+ >>> a = q.reply()
+ >>> a.add_answer(*RR.fromZone("abc.com A 1.2.3.4"))
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;; ANSWER SECTION:
+ abc.com. 0 IN A 1.2.3.4
+ """
+ self.rr.extend(rr)
+ self.set_header_qa()
+
+ def add_auth(self,*auth):
+ """
+ Add authority records
+
+ >>> q = DNSRecord.question("abc.com")
+ >>> a = q.reply()
+ >>> a.add_answer(*RR.fromZone("abc.com 60 A 1.2.3.4"))
+ >>> a.add_auth(*RR.fromZone("abc.com 3600 NS nsa.abc.com"))
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;; ANSWER SECTION:
+ abc.com. 60 IN A 1.2.3.4
+ ;; AUTHORITY SECTION:
+ abc.com. 3600 IN NS nsa.abc.com.
+ """
+ self.auth.extend(auth)
+ self.set_header_qa()
+
+ def add_ar(self,*ar):
+ """
+ Add additional records
+
+ >>> q = DNSRecord.question("abc.com")
+ >>> a = q.reply()
+ >>> a.add_answer(*RR.fromZone("abc.com 60 CNAME x.abc.com"))
+ >>> a.add_ar(*RR.fromZone("x.abc.com 3600 A 1.2.3.4"))
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;; ANSWER SECTION:
+ abc.com. 60 IN CNAME x.abc.com.
+ ;; ADDITIONAL SECTION:
+ x.abc.com. 3600 IN A 1.2.3.4
+ """
+ self.ar.extend(ar)
+ self.set_header_qa()
+
+ def set_header_qa(self):
+ """
+ Reset header q/a/auth/ar counts to match numver of records
+ (normally done transparently)
+ """
+ self.header.q = len(self.questions)
+ self.header.a = len(self.rr)
+ self.header.auth = len(self.auth)
+ self.header.ar = len(self.ar)
+
+ # Shortcut to get first question
+ def get_q(self):
+ return self.questions[0] if self.questions else DNSQuestion()
+ q = property(get_q)
+
+ # Shortcut to get first answer
+ def get_a(self):
+ return self.rr[0] if self.rr else RR()
+ a = property(get_a)
+
+ def pack(self):
+ """
+ Pack record into binary packet
+ (recursively packs each section into buffer)
+
+ >>> q = DNSRecord.question("abc.com")
+ >>> q.header.id = 1234
+ >>> a = q.replyZone("abc.com A 1.2.3.4")
+ >>> a.header.aa = 0
+ >>> pkt = a.pack()
+ >>> print(DNSRecord.parse(pkt))
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1234
+ ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;; ANSWER SECTION:
+ abc.com. 0 IN A 1.2.3.4
+ """
+ self.set_header_qa()
+ buffer = DNSBuffer()
+ self.header.pack(buffer)
+ for q in self.questions:
+ q.pack(buffer)
+ for rr in self.rr:
+ rr.pack(buffer)
+ for auth in self.auth:
+ auth.pack(buffer)
+ for ar in self.ar:
+ ar.pack(buffer)
+ return buffer.data
+
+ def truncate(self):
+ """
+ Return truncated copy of DNSRecord (with TC flag set)
+ (removes all Questions & RRs and just returns header)
+
+ >>> q = DNSRecord.question("abc.com")
+ >>> a = q.reply()
+ >>> a.add_answer(*RR.fromZone('abc.com IN TXT %s' % ('x' * 255)))
+ >>> a.add_answer(*RR.fromZone('abc.com IN TXT %s' % ('x' * 255)))
+ >>> a.add_answer(*RR.fromZone('abc.com IN TXT %s' % ('x' * 255)))
+ >>> len(a.pack())
+ 829
+ >>> t = a.truncate()
+ >>> print(t)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa tc rd ra; QUERY: 0, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
+
+ """
+ return DNSRecord(DNSHeader(id=self.header.id,
+ bitmap=self.header.bitmap,
+ tc=1))
+
+ def send(self,dest,port=53,tcp=False,timeout=None,ipv6=False):
+ """
+ Send packet to nameserver and return response
+ """
+ data = self.pack()
+ if ipv6:
+ inet = socket.AF_INET6
+ else:
+ inet = socket.AF_INET
+ if tcp:
+ if len(data) > 65535:
+ raise ValueError("Packet length too long: %d" % len(data))
+ data = struct.pack("!H",len(data)) + data
+ sock = socket.socket(inet,socket.SOCK_STREAM)
+ if timeout is not None:
+ sock.settimeout(timeout)
+ sock.connect((dest,port))
+ sock.sendall(data)
+ response = sock.recv(8192)
+ length = struct.unpack("!H",bytes(response[:2]))[0]
+ while len(response) - 2 < length:
+ response += sock.recv(8192)
+ sock.close()
+ response = response[2:]
+ else:
+ sock = socket.socket(inet,socket.SOCK_DGRAM)
+ if timeout is not None:
+ sock.settimeout(timeout)
+ sock.sendto(self.pack(),(dest,port))
+ response,server = sock.recvfrom(8192)
+ sock.close()
+ return response
+
+ def format(self,prefix="",sort=False):
+ """
+ Formatted 'repr'-style representation of record
+ (optionally with prefix and/or sorted RRs)
+ """
+ s = sorted if sort else lambda x:x
+ sections = [ repr(self.header) ]
+ sections.extend(s([repr(q) for q in self.questions]))
+ sections.extend(s([repr(rr) for rr in self.rr]))
+ sections.extend(s([repr(rr) for rr in self.auth]))
+ sections.extend(s([repr(rr) for rr in self.ar]))
+ return prefix + ("\n" + prefix).join(sections)
+
+ def toZone(self,prefix=""):
+ """
+ Formatted 'DiG' (zone) style output
+ (with optional prefix)
+ """
+ z = self.header.toZone().split("\n")
+ if self.questions:
+ z.append(";; QUESTION SECTION:")
+ [ z.extend(q.toZone().split("\n")) for q in self.questions ]
+ if self.rr:
+ z.append(";; ANSWER SECTION:")
+ [ z.extend(rr.toZone().split("\n")) for rr in self.rr ]
+ if self.auth:
+ z.append(";; AUTHORITY SECTION:")
+ [ z.extend(rr.toZone().split("\n")) for rr in self.auth ]
+ if self.ar:
+ z.append(";; ADDITIONAL SECTION:")
+ [ z.extend(rr.toZone().split("\n")) for rr in self.ar ]
+ return prefix + ("\n" + prefix).join(z)
+
+ def short(self):
+ """
+ Just return RDATA
+ """
+ return "\n".join([rr.rdata.toZone() for rr in self.rr])
+
+ def __eq__(self,other):
+ """
+ Test for equality by diffing records
+ """
+ if type(other) != type(self):
+ return False
+ else:
+ return self.diff(other) == []
+
+ def __ne__(self,other):
+ return not(self.__eq__(other))
+
+ def diff(self,other):
+ """
+ Diff records - recursively diff sections (sorting RRs)
+ """
+ err = []
+ if self.header != other.header:
+ err.append((self.header,other.header))
+ for section in ('questions','rr','auth','ar'):
+ if section == 'questions':
+ k = lambda x:tuple(map(str,(x.qname,x.qtype)))
+ else:
+ k = lambda x:tuple(map(str,(x.rname,x.rtype,x.rdata)))
+ a = dict([(k(rr),rr) for rr in getattr(self,section)])
+ b = dict([(k(rr),rr) for rr in getattr(other,section)])
+ sa = set(a)
+ sb = set(b)
+ for e in sorted(sa.intersection(sb)):
+ if a[e] != b[e]:
+ err.append((a[e],b[e]))
+ for e in sorted(sa.difference(sb)):
+ err.append((a[e],None))
+ for e in sorted(sb.difference(sa)):
+ err.append((None,b[e]))
+ return err
+
+ def __repr__(self):
+ return self.format()
+
+ def __str__(self):
+ return self.toZone()
+
+class DNSHeader(object):
+
+ """
+ DNSHeader section
+ """
+
+ # Ensure attribute values match packet
+ id = H('id')
+ bitmap = H('bitmap')
+ q = H('q')
+ a = H('a')
+ auth = H('auth')
+ ar = H('ar')
+
+ @classmethod
+ def parse(cls,buffer):
+ """
+ Implements parse interface
+ """
+ try:
+ (id,bitmap,q,a,auth,ar) = buffer.unpack("!HHHHHH")
+ return cls(id,bitmap,q,a,auth,ar)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking DNSHeader [offset=%d]: %s" % (
+ buffer.offset,e))
+
+ def __init__(self,id=None,bitmap=None,q=0,a=0,auth=0,ar=0,**args):
+ if id is None:
+ self.id = random.randint(0,65535)
+ else:
+ self.id = id
+ if bitmap is None:
+ self.bitmap = 0
+ self.rd = 1
+ else:
+ self.bitmap = bitmap
+ self.q = q
+ self.a = a
+ self.auth = auth
+ self.ar = ar
+ for k,v in args.items():
+ if k.lower() == "qr":
+ self.qr = v
+ elif k.lower() == "opcode":
+ self.opcode = v
+ elif k.lower() == "aa":
+ self.aa = v
+ elif k.lower() == "tc":
+ self.tc = v
+ elif k.lower() == "rd":
+ self.rd = v
+ elif k.lower() == "ra":
+ self.ra = v
+ elif k.lower() == "rcode":
+ self.rcode = v
+
+ # Accessors for header properties (automatically pack/unpack
+ # into bitmap)
+ def get_qr(self):
+ return get_bits(self.bitmap,15)
+
+ def set_qr(self,val):
+ self.bitmap = set_bits(self.bitmap,val,15)
+
+ qr = property(get_qr,set_qr)
+
+ def get_opcode(self):
+ return get_bits(self.bitmap,11,4)
+
+ def set_opcode(self,val):
+ self.bitmap = set_bits(self.bitmap,val,11,4)
+
+ opcode = property(get_opcode,set_opcode)
+
+ def get_aa(self):
+ return get_bits(self.bitmap,10)
+
+ def set_aa(self,val):
+ self.bitmap = set_bits(self.bitmap,val,10)
+
+ aa = property(get_aa,set_aa)
+
+ def get_tc(self):
+ return get_bits(self.bitmap,9)
+
+ def set_tc(self,val):
+ self.bitmap = set_bits(self.bitmap,val,9)
+
+ tc = property(get_tc,set_tc)
+
+ def get_rd(self):
+ return get_bits(self.bitmap,8)
+
+ def set_rd(self,val):
+ self.bitmap = set_bits(self.bitmap,val,8)
+
+ rd = property(get_rd,set_rd)
+
+ def get_ra(self):
+ return get_bits(self.bitmap,7)
+
+ def set_ra(self,val):
+ self.bitmap = set_bits(self.bitmap,val,7)
+
+ ra = property(get_ra,set_ra)
+
+ def get_rcode(self):
+ return get_bits(self.bitmap,0,4)
+
+ def set_rcode(self,val):
+ self.bitmap = set_bits(self.bitmap,val,0,4)
+
+ rcode = property(get_rcode,set_rcode)
+
+ def pack(self,buffer):
+ buffer.pack("!HHHHHH",self.id,self.bitmap,
+ self.q,self.a,self.auth,self.ar)
+
+ def __repr__(self):
+ f = [ self.aa and 'AA',
+ self.tc and 'TC',
+ self.rd and 'RD',
+ self.ra and 'RA' ]
+ if OPCODE.get(self.opcode) == 'UPDATE':
+ f1='zo'
+ f2='pr'
+ f3='up'
+ f4='ad'
+ else:
+ f1='q'
+ f2='a'
+ f3='ns'
+ f4='ar'
+ return "" % (
+ self.id,
+ QR.get(self.qr),
+ OPCODE.get(self.opcode),
+ ",".join(filter(None,f)),
+ RCODE.get(self.rcode),
+ f1, self.q, f2, self.a, f3, self.auth, f4, self.ar )
+
+ def toZone(self):
+ f = [ self.qr and 'qr',
+ self.aa and 'aa',
+ self.tc and 'tc',
+ self.rd and 'rd',
+ self.ra and 'ra' ]
+ z1 = ';; ->>HEADER<<- opcode: %s, status: %s, id: %d' % (
+ OPCODE.get(self.opcode),RCODE.get(self.rcode),self.id)
+ z2 = ';; flags: %s; QUERY: %d, ANSWER: %d, AUTHORITY: %d, ADDITIONAL: %d' % (
+ " ".join(filter(None,f)),
+ self.q,self.a,self.auth,self.ar)
+ return z1 + "\n" + z2
+
+ def __str__(self):
+ return self.toZone()
+
+ def __ne__(self,other):
+ return not(self.__eq__(other))
+
+ def __eq__(self,other):
+ if type(other) != type(self):
+ return False
+ else:
+ # Ignore id
+ attrs = ('qr','aa','tc','rd','ra','opcode','rcode')
+ return all([getattr(self,x) == getattr(other,x) for x in attrs])
+
+class DNSQuestion(object):
+
+ """
+ DNSQuestion section
+ """
+
+ @classmethod
+ def parse(cls,buffer):
+ try:
+ qname = buffer.decode_name()
+ qtype,qclass = buffer.unpack("!HH")
+ return cls(qname,qtype,qclass)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking DNSQuestion [offset=%d]: %s" % (
+ buffer.offset,e))
+
+ def __init__(self,qname=None,qtype=1,qclass=1):
+ self.qname = qname
+ self.qtype = qtype
+ self.qclass = qclass
+
+ def set_qname(self,qname):
+ if isinstance(qname,DNSLabel):
+ self._qname = qname
+ else:
+ self._qname = DNSLabel(qname)
+
+ def get_qname(self):
+ return self._qname
+
+ qname = property(get_qname,set_qname)
+
+ def pack(self,buffer):
+ buffer.encode_name(self.qname)
+ buffer.pack("!HH",self.qtype,self.qclass)
+
+ def toZone(self):
+ return ';%-30s %-7s %s' % (self.qname,CLASS.get(self.qclass),
+ QTYPE.get(self.qtype))
+
+ def __repr__(self):
+ return "" % (
+ self.qname, QTYPE.get(self.qtype), CLASS.get(self.qclass))
+
+ def __str__(self):
+ return self.toZone()
+
+ def __ne__(self,other):
+ return not(self.__eq__(other))
+
+ def __eq__(self,other):
+ if type(other) != type(self):
+ return False
+ else:
+ # List of attributes to compare when diffing
+ attrs = ('qname','qtype','qclass')
+ return all([getattr(self,x) == getattr(other,x) for x in attrs])
+
+class EDNSOption(object):
+
+ """
+ EDNSOption pseudo-section
+
+ Very rudimentary support for EDNS0 options however this has not been
+ tested due to a lack of data (anyone wanting to improve support or
+ provide test data please raise an issue)
+
+ >>> EDNSOption(1,b"1234")
+
+ >>> EDNSOption(99999,b"1234")
+ Traceback (most recent call last):
+ ...
+ ValueError: Attribute 'code' must be between 0-65535 [99999]
+ >>> EDNSOption(1,None)
+ Traceback (most recent call last):
+ ...
+ ValueError: Attribute 'data' must be instance of ...
+
+ """
+
+ code = H('code')
+ data = BYTES('data')
+
+ def __init__(self,code,data):
+ self.code = code
+ self.data = data
+
+ def pack(self,buffer):
+ buffer.pack("!HH",self.code,len(self.data))
+ buffer.append(self.data)
+
+ def __repr__(self):
+ return "" % (
+ self.code,binascii.hexlify(self.data).decode())
+
+ def toZone(self):
+ return ";EDNS: code: %s; data: %s" % (
+ self.code,binascii.hexlify(self.data).decode())
+
+ def __str__(self):
+ return self.toZone()
+
+ def __ne__(self,other):
+ return not(self.__eq__(other))
+
+ def __eq__(self,other):
+ if type(other) != type(self):
+ return False
+ else:
+ # List of attributes to compare when diffing
+ attrs = ('code','data')
+ return all([getattr(self,x) == getattr(other,x) for x in attrs])
+
+class RR(object):
+
+ """
+ DNS Resource Record
+ Contains RR header and RD (resource data) instance
+ """
+
+ rtype = H('rtype')
+ rclass = H('rclass')
+ ttl = I('ttl')
+ rdlength = H('rdlength')
+
+ @classmethod
+ def parse(cls,buffer):
+ try:
+ rname = buffer.decode_name()
+ rtype,rclass,ttl,rdlength = buffer.unpack("!HHIH")
+ if rtype == QTYPE.OPT:
+ options = []
+ option_buffer = Buffer(buffer.get(rdlength))
+ while option_buffer.remaining() > 4:
+ code,length = option_buffer.unpack("!HH")
+ data = option_buffer.get(length)
+ options.append(EDNSOption(code,data))
+ rdata = options
+ else:
+ if rdlength:
+ rdata = RDMAP.get(QTYPE.get(rtype),RD).parse(
+ buffer,rdlength)
+ else:
+ rdata = ''
+ return cls(rname,rtype,rclass,ttl,rdata)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking RR [offset=%d]: %s" % (
+ buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,zone,origin="",ttl=0):
+ """
+ Parse RR data from zone file and return list of RRs
+ """
+ return list(ZoneParser(zone,origin=origin,ttl=ttl))
+
+ def __init__(self,rname=None,rtype=1,rclass=1,ttl=0,rdata=None):
+ self.rname = rname
+ self.rtype = rtype
+ self.rclass = rclass
+ self.ttl = ttl
+ self.rdata = rdata
+ # TODO Add property getters/setters
+ if self.rtype == QTYPE.OPT:
+ self.edns_len = self.rclass
+ self.edns_do = get_bits(self.ttl,15)
+ self.edns_ver = get_bits(self.ttl,16,8)
+ self.edns_rcode = get_bits(self.ttl,24,8)
+
+ def set_rname(self,rname):
+ if isinstance(rname,DNSLabel):
+ self._rname = rname
+ else:
+ self._rname = DNSLabel(rname)
+
+ def get_rname(self):
+ return self._rname
+
+ rname = property(get_rname,set_rname)
+
+ def pack(self,buffer):
+ buffer.encode_name(self.rname)
+ buffer.pack("!HHI",self.rtype,self.rclass,self.ttl)
+ rdlength_ptr = buffer.offset
+ buffer.pack("!H",0)
+ start = buffer.offset
+ if self.rtype == QTYPE.OPT:
+ for opt in self.rdata:
+ opt.pack(buffer)
+ else:
+ self.rdata.pack(buffer)
+ end = buffer.offset
+ buffer.update(rdlength_ptr,"!H",end-start)
+
+ def __repr__(self):
+ if self.rtype == QTYPE.OPT:
+ s = ["" % (
+ self.edns_ver,self.edns_do,self.edns_rcode,self.edns_len)]
+ s.extend([repr(opt) for opt in self.rdata])
+ return "\n".join(s)
+ else:
+ return "" % (
+ self.rname, QTYPE.get(self.rtype), CLASS.get(self.rclass),
+ self.ttl, self.rdata)
+
+ def toZone(self):
+ if self.rtype == QTYPE.OPT:
+ edns = [ ";OPT PSEUDOSECTION",
+ ";EDNS: version: %d, flags: %s; udp: %d" % (
+ self.edns_ver,
+ "do" if self.edns_do else "",
+ self.edns_len)
+ ]
+ edns.extend([str(opt) for opt in self.rdata])
+ return "\n".join(edns)
+ else:
+ return '%-23s %-7s %-7s %-7s %s' % (self.rname,self.ttl,
+ CLASS.get(self.rclass),
+ QTYPE.get(self.rtype),
+ self.rdata.toZone())
+
+ def __str__(self):
+ return self.toZone()
+
+ def __ne__(self,other):
+ return not(self.__eq__(other))
+
+ def __eq__(self,other):
+ # Handle OPT specially as may be different types (RR/EDNS0)
+ if self.rtype == QTYPE.OPT and getattr(other,"rtype",False) == QTYPE.OPT:
+ attrs = ('rname','rclass','rtype','ttl','rdata')
+ return all([getattr(self,x) == getattr(other,x) for x in attrs])
+ else:
+ if type(other) != type(self):
+ return False
+ else:
+ # List of attributes to compare when diffing (ignore ttl)
+ attrs = ('rname','rclass','rtype','rdata')
+ return all([getattr(self,x) == getattr(other,x) for x in attrs])
+
+class EDNS0(RR):
+
+ """
+
+ ENDS0 pseudo-record
+
+ Wrapper around the ENDS0 support in RR to make it more convenient to
+ create EDNS0 pseudo-record - this just makes it easier to specify the
+ EDNS0 parameters directly
+
+ EDNS flags should be passed as a space separated string of options
+ (currently only 'do' is supported)
+
+ >>> EDNS0("abc.com",flags="do",udp_len=2048,version=1)
+
+ >>> print(_)
+ ;OPT PSEUDOSECTION
+ ;EDNS: version: 1, flags: do; udp: 2048
+ >>> opt = EDNS0("abc.com",flags="do",ext_rcode=1,udp_len=2048,version=1,opts=[EDNSOption(1,b'abcd')])
+ >>> opt
+
+
+ >>> print(opt)
+ ;OPT PSEUDOSECTION
+ ;EDNS: version: 1, flags: do; udp: 2048
+ ;EDNS: code: 1; data: 61626364
+ >>> r = DNSRecord.question("abc.com").replyZone("abc.com A 1.2.3.4")
+ >>> r.add_ar(opt)
+ >>> print(r)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
+ ;; QUESTION SECTION:
+ ;abc.com. IN A
+ ;; ANSWER SECTION:
+ abc.com. 0 IN A 1.2.3.4
+ ;; ADDITIONAL SECTION:
+ ;OPT PSEUDOSECTION
+ ;EDNS: version: 1, flags: do; udp: 2048
+ ;EDNS: code: 1; data: 61626364
+ >>> DNSRecord.parse(r.pack()) == r
+ True
+ """
+
+ def __init__(self,rname=None,rtype=QTYPE.OPT,
+ ext_rcode=0,version=0,flags="",udp_len=0,opts=None):
+ check_range('ext_rcode',ext_rcode,0,255)
+ check_range('version',version,0,255)
+ edns_flags = { 'do' : 1 << 15 }
+ flag_bitmap = sum([edns_flags[x] for x in flags.split()])
+ ttl = (ext_rcode << 24) + (version << 16) + flag_bitmap
+ if opts and not all([isinstance(o,EDNSOption) for o in opts]):
+ raise ValueError("Option must be instance of EDNSOption")
+ super(EDNS0,self).__init__(rname,rtype,udp_len,ttl,opts or [])
+
+class RD(object):
+ """
+ Base RD object - also used as placeholder for unknown RD types
+
+ To create a new RD type subclass this and add to RDMAP (below)
+
+ Subclass should implement (as a mininum):
+
+ parse (parse from packet data)
+ __init__ (create class)
+ __repr__ (return in zone format)
+ fromZone (create from zone format)
+
+ (toZone uses __repr__ by default)
+
+ Unknown rdata types default to RD and store rdata as a binary
+ blob (this allows round-trip encoding/decoding)
+ """
+
+ @classmethod
+ def parse(cls,buffer,length):
+ """
+ Unpack from buffer
+ """
+ try:
+ data = buffer.get(length)
+ return cls(data)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking RD [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ """
+ Create new record from zone format data
+ RD is a list of strings parsed from DiG output
+ """
+ # Unknown rata - assume hexdump in zone format
+ # (DiG prepends "\\# " to the hexdump so get last item)
+ return cls(binascii.unhexlify(rd[-1].encode('ascii')))
+
+ def __init__(self,data=b""):
+ # Assume raw bytes
+ check_bytes('data',data)
+ self.data = bytes(data)
+
+ def pack(self,buffer):
+ """
+ Pack record into buffer
+ """
+ buffer.append(self.data)
+
+ def __repr__(self):
+ """
+ Default 'repr' format should be equivalent to RD zone format
+ """
+ # For unknown rdata just default to hex
+ return binascii.hexlify(self.data).decode()
+
+ def toZone(self):
+ return repr(self)
+
+ # Comparison operations - in most cases only need to override 'attrs'
+ # in subclass (__eq__ will automatically compare defined atttrs)
+
+ # Attributes for comparison
+ attrs = ('data',)
+
+ def __eq__(self,other):
+ if type(other) != type(self):
+ return False
+ else:
+ return all([getattr(self,x) == getattr(other,x) for x in self.attrs])
+
+ def __ne__(self,other):
+ return not(self.__eq__(other))
+
+def _force_bytes(x):
+ if isinstance(x,bytes):
+ return x
+ else:
+ return x.encode()
+
+class TXT(RD):
+ """
+ DNS TXT record. Pass in either a single string, or a tuple/list of strings.
+
+ >>> TXT('txtvers=1')
+ "txtvers=1"
+ >>> TXT(('txtvers=1',))
+ "txtvers=1"
+ >>> TXT(['txtvers=1',])
+ "txtvers=1"
+ >>> TXT(['txtvers=1','swver=2.5'])
+ "txtvers=1","swver=2.5"
+ >>> a = DNSRecord()
+ >>> a.add_answer(*RR.fromZone('example.com 60 IN TXT "txtvers=1"'))
+ >>> a.add_answer(*RR.fromZone('example.com 120 IN TXT "txtvers=1" "swver=2.3"'))
+ >>> print(a)
+ ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
+ ;; flags: rd; QUERY: 0, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
+ ;; ANSWER SECTION:
+ example.com. 60 IN TXT "txtvers=1"
+ example.com. 120 IN TXT "txtvers=1" "swver=2.3"
+ """
+
+ @classmethod
+ def parse(cls,buffer,length):
+ try:
+ data = list()
+ start_bo = buffer.offset
+ now_length = 0
+ while buffer.offset < start_bo + length:
+ (txtlength,) = buffer.unpack("!B")
+ # First byte is TXT length (not in RFC?)
+ if now_length + txtlength < length:
+ now_length += txtlength
+ data.append(buffer.get(txtlength))
+ else:
+ raise DNSError("Invalid TXT record: len(%d) > RD len(%d)" %
+ (txtlength,length))
+ return cls(data)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking TXT [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ return cls(list(map(lambda x: x.encode(), rd)))
+
+ def __init__(self,data):
+ if type(data) in (tuple,list):
+ self.data = [ _force_bytes(x) for x in data ]
+ else:
+ self.data = [ _force_bytes(data) ]
+ if any([len(x)>255 for x in self.data]):
+ raise DNSError("TXT record too long: %s" % self.data)
+
+ def pack(self,buffer):
+ for ditem in self.data:
+ if len(ditem) > 255:
+ raise DNSError("TXT record too long: %s" % ditem)
+ buffer.pack("!B",len(ditem))
+ buffer.append(ditem)
+
+ def toZone(self):
+ return " ".join([ '"%s"' % x.decode(errors='replace') for x in self.data ])
+
+ def __repr__(self):
+ return ",".join([ '"%s"' % x.decode(errors='replace') for x in self.data ])
+
+class A(RD):
+
+ data = IP4('data')
+
+ @classmethod
+ def parse(cls,buffer,length):
+ try:
+ data = buffer.unpack("!BBBB")
+ return cls(data)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking A [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ return cls(rd[0])
+
+ def __init__(self,data):
+ if type(data) in (tuple,list):
+ self.data = tuple(data)
+ else:
+ self.data = tuple(map(int,data.rstrip(".").split(".")))
+
+ def pack(self,buffer):
+ buffer.pack("!BBBB",*self.data)
+
+ def __repr__(self):
+ return "%d.%d.%d.%d" % self.data
+
+def _parse_ipv6(a):
+ """
+ Parse IPv6 address. Ideally we would use the ipaddress module in
+ Python3.3 but can't rely on having this.
+
+ Does not handle dotted-quad addresses or subnet prefix
+
+ >>> _parse_ipv6("::") == (0,) * 16
+ True
+ >>> _parse_ipv6("1234:5678::abcd:0:ff00")
+ (18, 52, 86, 120, 0, 0, 0, 0, 0, 0, 171, 205, 0, 0, 255, 0)
+
+ """
+ l,_,r = a.partition("::")
+ l_groups = list(chain(*[divmod(int(x,16),256) for x in l.split(":") if x]))
+ r_groups = list(chain(*[divmod(int(x,16),256) for x in r.split(":") if x]))
+ zeros = [0] * (16 - len(l_groups) - len(r_groups))
+ return tuple(l_groups + zeros + r_groups)
+
+def _format_ipv6(a):
+ """
+ Format IPv6 address (from tuple of 16 bytes) compressing sequence of
+ zero bytes to '::'. Ideally we would use the ipaddress module in
+ Python3.3 but can't rely on having this.
+
+ >>> _format_ipv6([0]*16)
+ '::'
+ >>> _format_ipv6(_parse_ipv6("::0012:5678"))
+ '::12:5678'
+ >>> _format_ipv6(_parse_ipv6("1234:0:5678::ff:0:1"))
+ '1234:0:5678::ff:0:1'
+ """
+ left = []
+ right = []
+ current = 'left'
+ for i in range(0,16,2):
+ group = (a[i] << 8) + a[i+1]
+ if current == 'left':
+ if group == 0 and i < 14:
+ if (a[i+2] << 8) + a[i+3] == 0:
+ current = 'right'
+ else:
+ left.append("0")
+ else:
+ left.append("%x" % group)
+ else:
+ if group == 0 and len(right) == 0:
+ pass
+ else:
+ right.append("%x" % group)
+ if len(left) < 8:
+ return ":".join(left) + "::" + ":".join(right)
+ else:
+ return ":".join(left)
+
+class AAAA(RD):
+
+ """
+ Basic support for AAAA record - accepts IPv6 address data as either
+ a tuple of 16 bytes or in text format
+ """
+
+ data = IP6('data')
+
+ @classmethod
+ def parse(cls,buffer,length):
+ try:
+ data = buffer.unpack("!16B")
+ return cls(data)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking AAAA [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ return cls(rd[0])
+
+ def __init__(self,data):
+ if type(data) in (tuple,list):
+ self.data = tuple(data)
+ else:
+ self.data = _parse_ipv6(data)
+
+ def pack(self,buffer):
+ buffer.pack("!16B",*self.data)
+
+ def __repr__(self):
+ return _format_ipv6(self.data)
+
+class MX(RD):
+
+ preference = H('preference')
+
+ @classmethod
+ def parse(cls,buffer,length):
+ try:
+ (preference,) = buffer.unpack("!H")
+ mx = buffer.decode_name()
+ return cls(mx,preference)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking MX [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ return cls(label(rd[1],origin),int(rd[0]))
+
+ def __init__(self,label=None,preference=10):
+ self.label = label
+ self.preference = preference
+
+ def set_label(self,label):
+ if isinstance(label,DNSLabel):
+ self._label = label
+ else:
+ self._label = DNSLabel(label)
+
+ def get_label(self):
+ return self._label
+
+ label = property(get_label,set_label)
+
+ def pack(self,buffer):
+ buffer.pack("!H",self.preference)
+ buffer.encode_name(self.label)
+
+ def __repr__(self):
+ return "%d %s" % (self.preference,self.label)
+
+ attrs = ('preference','label')
+
+class CNAME(RD):
+
+ @classmethod
+ def parse(cls,buffer,length):
+ try:
+ label = buffer.decode_name()
+ return cls(label)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking CNAME [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ return cls(label(rd[0],origin))
+
+ def __init__(self,label=None):
+ self.label = label
+
+ def set_label(self,label):
+ if isinstance(label,DNSLabel):
+ self._label = label
+ else:
+ self._label = DNSLabel(label)
+
+ def get_label(self):
+ return self._label
+
+ label = property(get_label,set_label)
+
+ def pack(self,buffer):
+ buffer.encode_name(self.label)
+
+ def __repr__(self):
+ return "%s" % (self.label)
+
+ attrs = ('label',)
+
+class PTR(CNAME):
+ pass
+
+class NS(CNAME):
+ pass
+
+class SOA(RD):
+
+ times = ntuple_range('times',5,0,4294967295)
+ @classmethod
+ def parse(cls,buffer,length):
+ try:
+ mname = buffer.decode_name()
+ rname = buffer.decode_name()
+ times = buffer.unpack("!IIIII")
+ return cls(mname,rname,times)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking SOA [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ return cls(label(rd[0],origin),label(rd[1],origin),[parse_time(t) for t in rd[2:]])
+
+ def __init__(self,mname=None,rname=None,times=None):
+ self.mname = mname
+ self.rname = rname
+ self.times = tuple(times) if times else (0,0,0,0,0)
+
+ def set_mname(self,mname):
+ if isinstance(mname,DNSLabel):
+ self._mname = mname
+ else:
+ self._mname = DNSLabel(mname)
+
+ def get_mname(self):
+ return self._mname
+
+ mname = property(get_mname,set_mname)
+
+ def set_rname(self,rname):
+ if isinstance(rname,DNSLabel):
+ self._rname = rname
+ else:
+ self._rname = DNSLabel(rname)
+
+ def get_rname(self):
+ return self._rname
+
+ rname = property(get_rname,set_rname)
+
+ def pack(self,buffer):
+ buffer.encode_name(self.mname)
+ buffer.encode_name(self.rname)
+ buffer.pack("!IIIII", *self.times)
+
+ def __repr__(self):
+ return "%s %s %s" % (self.mname,self.rname,
+ " ".join(map(str,self.times)))
+
+ attrs = ('mname','rname','times')
+
+class SRV(RD):
+
+ priority = H('priority')
+ weight = H('weight')
+ port = H('port')
+
+ @classmethod
+ def parse(cls,buffer,length):
+ try:
+ priority,weight,port = buffer.unpack("!HHH")
+ target = buffer.decode_name()
+ return cls(priority,weight,port,target)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking SRV [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ return cls(int(rd[0]),int(rd[1]),int(rd[2]),rd[3])
+
+ def __init__(self,priority=0,weight=0,port=0,target=None):
+ self.priority = priority
+ self.weight = weight
+ self.port = port
+ self.target = target
+
+ def set_target(self,target):
+ if isinstance(target,DNSLabel):
+ self._target = target
+ else:
+ self._target = DNSLabel(target)
+
+ def get_target(self):
+ return self._target
+
+ target = property(get_target,set_target)
+
+ def pack(self,buffer):
+ buffer.pack("!HHH",self.priority,self.weight,self.port)
+ buffer.encode_name(self.target)
+
+ def __repr__(self):
+ return "%d %d %d %s" % (self.priority,self.weight,self.port,self.target)
+
+ attrs = ('priority','weight','port','target')
+
+class NAPTR(RD):
+
+ order = H('order')
+ preference = H('preference')
+
+ @classmethod
+ def parse(cls, buffer, length):
+ try:
+ order, preference = buffer.unpack('!HH')
+ (length,) = buffer.unpack('!B')
+ flags = buffer.get(length)
+ (length,) = buffer.unpack('!B')
+ service = buffer.get(length)
+ (length,) = buffer.unpack('!B')
+ regexp = buffer.get(length)
+ replacement = buffer.decode_name()
+ return cls(order, preference, flags, service, regexp, replacement)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking NAPTR [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ encode = lambda s : s.encode()
+ _label = lambda s : label(s,origin)
+ m = (int,int,encode,encode,encode,_label)
+ return cls(*[ f(v) for f,v in zip(m,rd)])
+
+ def __init__(self,order,preference,flags,service,regexp,replacement=None):
+ self.order = order
+ self.preference = preference
+ self.flags = flags
+ self.service = service
+ self.regexp = regexp
+ self.replacement = replacement
+
+ def set_replacement(self,replacement):
+ if isinstance(replacement,DNSLabel):
+ self._replacement = replacement
+ else:
+ self._replacement = DNSLabel(replacement)
+
+ def get_replacement(self):
+ return self._replacement
+
+ replacement = property(get_replacement,set_replacement)
+
+ def pack(self, buffer):
+ buffer.pack('!HH', self.order, self.preference)
+ buffer.pack('!B', len(self.flags))
+ buffer.append(self.flags)
+ buffer.pack('!B', len(self.service))
+ buffer.append(self.service)
+ buffer.pack('!B', len(self.regexp))
+ buffer.append(self.regexp)
+ buffer.encode_name(self.replacement)
+
+ def __repr__(self):
+ return '%d %d "%s" "%s" "%s" %s' %(
+ self.order,self.preference,self.flags.decode(),
+ self.service.decode(),
+ self.regexp.decode().replace('\\','\\\\'),
+ self.replacement or '.'
+ )
+
+ attrs = ('order','preference','flags','service','regexp','replacement')
+
+class DNSKEY(RD):
+
+ flags = H('flags')
+ protocol = B('protocol')
+ algorithm = B('algorithm')
+
+ @classmethod
+ def parse(cls,buffer,length):
+ try:
+ (flags,protocol,algorithm) = buffer.unpack("!HBB")
+ key = buffer.get(length - 4)
+ return cls(flags,protocol,algorithm,key)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking DNSKEY [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ return cls(int(rd[0]),int(rd[1]),int(rd[2]),
+ base64.b64decode(("".join(rd[3:])).encode('ascii')))
+
+ def __init__(self,flags,protocol,algorithm,key):
+ self.flags = flags
+ self.protocol = protocol
+ self.algorithm = algorithm
+ self.key = key
+
+ def pack(self,buffer):
+ buffer.pack("!HBB",self.flags,self.protocol,self.algorithm)
+ buffer.append(self.key)
+
+ def __repr__(self):
+ return "%d %d %d %s" % (self.flags,self.protocol,self.algorithm,
+ base64.b64encode(self.key).decode())
+
+ attrs = ('flags','protocol','algorithm','key')
+
+class RRSIG(RD):
+
+ covered = H('covered')
+ algorithm = B('algorithm')
+ labels = B('labels')
+ orig_ttl = I('orig_ttl')
+ sig_exp = I('sig_exp')
+ sig_inc = I('sig_inc')
+ key_tag = H('key_tag')
+
+ @classmethod
+ def parse(cls,buffer,length):
+ try:
+ start = buffer.offset
+ (covered,algorithm,labels,
+ orig_ttl,sig_exp,sig_inc,key_tag) = buffer.unpack("!HBBIIIH")
+ name = buffer.decode_name()
+ sig = buffer.get(length - (buffer.offset - start))
+ return cls(covered,algorithm,labels,orig_ttl,sig_exp,sig_inc,key_tag,
+ name,sig)
+ except (BufferError,BimapError) as e:
+ raise DNSError("Error unpacking DNSKEY [offset=%d]: %s" %
+ (buffer.offset,e))
+
+ @classmethod
+ def fromZone(cls,rd,origin=None):
+ return cls(getattr(QTYPE,rd[0]),int(rd[1]),int(rd[2]),int(rd[3]),
+ int(calendar.timegm(time.strptime(rd[4]+'UTC',"%Y%m%d%H%M%S%Z"))),
+ int(calendar.timegm(time.strptime(rd[5]+'UTC',"%Y%m%d%H%M%S%Z"))),
+ int(rd[6]),rd[7],
+ base64.b64decode(("".join(rd[8:])).encode('ascii')))
+
+ def __init__(self,covered,algorithm,labels,orig_ttl,
+ sig_exp,sig_inc,key_tag,name,sig):
+ self.covered = covered
+ self.algorithm = algorithm
+ self.labels = labels
+ self.orig_ttl = orig_ttl
+ self.sig_exp = sig_exp
+ self.sig_inc = sig_inc
+ self.key_tag = key_tag
+ self.name = DNSLabel(name)
+ self.sig = sig
+
+ def pack(self,buffer):
+ buffer.pack("!HBBIIIH",self.covered,self.algorithm,self.labels,
+ self.orig_ttl,self.sig_exp,self.sig_inc,
+ self.key_tag)
+ buffer.encode_name_nocompress(self.name)
+ buffer.append(self.sig)
+
+ def __repr__(self):
+ timestamp_fmt = "{0.tm_year}{0.tm_mon:02}{0.tm_mday:02}{0.tm_hour:02}{0.tm_min:02}{0.tm_sec:02}"
+ return "%s %d %d %d %s %s %d %s %s" % (
+ QTYPE.get(self.covered),
+ self.algorithm,
+ self.labels,
+ self.orig_ttl,
+ timestamp_fmt.format(time.gmtime(self.sig_exp)),
+ timestamp_fmt.format(time.gmtime(self.sig_inc)),
+ self.key_tag,
+ self.name,
+ base64.b64encode(self.sig).decode())
+
+ attrs = ('covered','algorithm','labels','orig_ttl','sig_exp','sig_inc',
+ 'key_tag','name','sig')
+
+# Map from RD type to class (used to pack/unpack records)
+# If you add a new RD class you must add to RDMAP
+
+RDMAP = { 'CNAME':CNAME, 'A':A, 'AAAA':AAAA, 'TXT':TXT, 'MX':MX,
+ 'PTR':PTR, 'SOA':SOA, 'NS':NS, 'NAPTR': NAPTR, 'SRV':SRV,
+ 'DNSKEY':DNSKEY, 'RRSIG':RRSIG,
+ }
+
+##
+## Zone parser
+## TODO - ideally this would be in a separate file but have to deal
+## with circular dependencies
+##
+
+secs = {'s':1,'m':60,'h':3600,'d':86400,'w':604800}
+
+def parse_time(s):
+ """
+ Parse time spec with optional s/m/h/d/w suffix
+ """
+ if s[-1].lower() in secs:
+ return int(s[:-1]) * secs[s[-1].lower()]
+ else:
+ return int(s)
+
+class ZoneParser:
+
+ """
+ Zone file parser
+
+ >>> z = ZoneParser("www.example.com. 60 IN A 1.2.3.4")
+ >>> list(z.parse())
+ []
+ """
+
+ def __init__(self,zone,origin="",ttl=0):
+ self.l = WordLexer(zone)
+ self.l.commentchars = ';'
+ self.l.nltok = ('NL',None)
+ self.l.spacetok = ('SPACE',None)
+ self.i = iter(self.l)
+ if type(origin) is DNSLabel:
+ self.origin = origin
+ else:
+ self.origin= DNSLabel(origin)
+ self.ttl = ttl
+ self.label = DNSLabel("")
+ self.prev = None
+
+ def expect(self,expect):
+ t,val = next(self.i)
+ if t != expect:
+ raise ValueError("Invalid Token: %s (expecting: %s)" % (t,expect))
+ return val
+
+ def parse_label(self,label):
+ if label.endswith("."):
+ self.label = DNSLabel(label)
+ elif label == "@":
+ self.label = self.origin
+ elif label == '':
+ pass
+ else:
+ self.label = self.origin.add(label)
+ return self.label
+
+ def parse_rr(self,rr):
+ label = self.parse_label(rr.pop(0))
+ ttl = int(rr.pop(0)) if rr[0].isdigit() else self.ttl
+ rclass = rr.pop(0) if rr[0] in ('IN','CH','HS') else 'IN'
+ rtype = rr.pop(0)
+ rdata = rr
+ rd = RDMAP.get(rtype,RD)
+ return RR(rname=label,
+ ttl=ttl,
+ rclass=getattr(CLASS,rclass),
+ rtype=getattr(QTYPE,rtype),
+ rdata=rd.fromZone(rdata,self.origin))
+
+ def __iter__(self):
+ return self.parse()
+
+ def parse(self):
+ rr = []
+ paren = False
+ try:
+ while True:
+ tok,val = next(self.i)
+ if tok == 'NL':
+ if not paren and rr:
+ self.prev = tok
+ yield self.parse_rr(rr)
+ rr = []
+ elif tok == 'SPACE' and self.prev == 'NL' and not paren:
+ rr.append('')
+ elif tok == 'ATOM':
+ if val == '(':
+ paren = True
+ elif val == ')':
+ paren = False
+ elif val == '$ORIGIN':
+ self.expect('SPACE')
+ origin = self.expect('ATOM')
+ self.origin = self.label = DNSLabel(origin)
+ elif val == '$TTL':
+ self.expect('SPACE')
+ ttl = self.expect('ATOM')
+ self.ttl = parse_time(ttl)
+ else:
+ rr.append(val)
+ self.prev = tok
+ except StopIteration:
+ if rr:
+ yield self.parse_rr(rr)
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
diff --git a/lib/dnslib/build/lib.linux-i686-2.7/dnslib/fixedresolver.py b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/fixedresolver.py
new file mode 100644
index 0000000..a0c50e8
--- /dev/null
+++ b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/fixedresolver.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+
+"""
+ FixedResolver - example resolver which responds with fixed response
+ to all requests
+"""
+
+from __future__ import print_function
+
+import copy
+
+from dnslib import RR
+from dnslib.server import DNSServer,DNSHandler,BaseResolver,DNSLogger
+
+class FixedResolver(BaseResolver):
+ """
+ Respond with fixed response to all requests
+ """
+ def __init__(self,zone):
+ # Parse RRs
+ self.rrs = RR.fromZone(zone)
+
+ def resolve(self,request,handler):
+ reply = request.reply()
+ qname = request.q.qname
+ # Replace labels with request label
+ for rr in self.rrs:
+ a = copy.copy(rr)
+ a.rname = qname
+ reply.add_answer(a)
+ return reply
+
+if __name__ == '__main__':
+
+ import argparse,sys,time
+
+ p = argparse.ArgumentParser(description="Fixed DNS Resolver")
+ p.add_argument("--response","-r",default=". 60 IN A 127.0.0.1",
+ metavar="",
+ help="DNS response (zone format) (default: 127.0.0.1)")
+ p.add_argument("--zonefile","-f",
+ metavar="",
+ help="DNS response (zone file, '-' for stdin)")
+ p.add_argument("--port","-p",type=int,default=53,
+ metavar="",
+ help="Server port (default:53)")
+ p.add_argument("--address","-a",default="",
+ metavar="",
+ help="Listen address (default:all)")
+ p.add_argument("--udplen","-u",type=int,default=0,
+ metavar="",
+ help="Max UDP packet length (default:0)")
+ p.add_argument("--tcp",action='store_true',default=False,
+ help="TCP server (default: UDP only)")
+ p.add_argument("--log",default="request,reply,truncated,error",
+ help="Log hooks to enable (default: +request,+reply,+truncated,+error,-recv,-send,-data)")
+ p.add_argument("--log-prefix",action='store_true',default=False,
+ help="Log prefix (timestamp/handler/resolver) (default: False)")
+ args = p.parse_args()
+
+ if args.zonefile:
+ if args.zonefile == '-':
+ args.response = sys.stdin
+ else:
+ args.response = open(args.zonefile)
+
+ resolver = FixedResolver(args.response)
+ logger = DNSLogger(args.log,args.log_prefix)
+
+ print("Starting Fixed Resolver (%s:%d) [%s]" % (
+ args.address or "*",
+ args.port,
+ "UDP/TCP" if args.tcp else "UDP"))
+
+ for rr in resolver.rrs:
+ print(" | ",rr.toZone().strip(),sep="")
+ print()
+
+ if args.udplen:
+ DNSHandler.udplen = args.udplen
+
+ udp_server = DNSServer(resolver,
+ port=args.port,
+ address=args.address,
+ logger=logger)
+ udp_server.start_thread()
+
+ if args.tcp:
+ tcp_server = DNSServer(resolver,
+ port=args.port,
+ address=args.address,
+ tcp=True,
+ logger=logger)
+ tcp_server.start_thread()
+
+ while udp_server.isAlive():
+ time.sleep(1)
+
diff --git a/lib/dnslib/build/lib.linux-i686-2.7/dnslib/intercept.py b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/intercept.py
new file mode 100644
index 0000000..85ad03a
--- /dev/null
+++ b/lib/dnslib/build/lib.linux-i686-2.7/dnslib/intercept.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+
+"""
+ InterceptResolver - proxy requests to upstream server
+ (optionally intercepting)
+
+"""
+from __future__ import print_function
+
+import binascii,copy,socket,struct,sys
+
+from dnslib import DNSRecord,RR,QTYPE,RCODE,parse_time
+from dnslib.server import DNSServer,DNSHandler,BaseResolver,DNSLogger
+from dnslib.label import DNSLabel
+
+class InterceptResolver(BaseResolver):
+
+ """
+ Intercepting resolver
+
+ Proxy requests to upstream server optionally intercepting requests
+ matching local records
+ """
+
+ def __init__(self,address,port,ttl,intercept,skip,nxdomain,timeout=0):
+ """
+ address/port - upstream server
+ ttl - default ttl for intercept records
+ intercept - list of wildcard RRs to respond to (zone format)
+ skip - list of wildcard labels to skip
+ nxdomain - list of wildcard labels to retudn NXDOMAIN
+ timeout - timeout for upstream server
+ """
+ self.address = address
+ self.port = port
+ self.ttl = parse_time(ttl)
+ self.skip = skip
+ self.nxdomain = nxdomain
+ self.timeout = timeout
+ self.zone = []
+ for i in intercept:
+ if i == '-':
+ i = sys.stdin.read()
+ for rr in RR.fromZone(i,ttl=self.ttl):
+ self.zone.append((rr.rname,QTYPE[rr.rtype],rr))
+
+ def resolve(self,request,handler):
+ reply = request.reply()
+ qname = request.q.qname
+ qtype = QTYPE[request.q.qtype]
+ # Try to resolve locally unless on skip list
+ if not any([qname.matchGlob(s) for s in self.skip]):
+ for name,rtype,rr in self.zone:
+ if qname.matchGlob(name) and (qtype in (rtype,'ANY','CNAME')):
+ a = copy.copy(rr)
+ a.rname = qname
+ reply.add_answer(a)
+ # Check for NXDOMAIN
+ if any([qname.matchGlob(s) for s in self.nxdomain]):
+ reply.header.rcode = getattr(RCODE,'NXDOMAIN')
+ return reply
+ # Otherwise proxy
+ if not reply.rr:
+ try:
+ if handler.protocol == 'udp':
+ proxy_r = request.send(self.address,self.port,
+ timeout=self.timeout)
+ else:
+ proxy_r = request.send(self.address,self.port,
+ tcp=True,timeout=self.timeout)
+ reply = DNSRecord.parse(proxy_r)
+ except socket.timeout:
+ reply.header.rcode = getattr(RCODE,'NXDOMAIN')
+
+ return reply
+
+if __name__ == '__main__':
+
+ import argparse,sys,time
+
+ p = argparse.ArgumentParser(description="DNS Intercept Proxy")
+ p.add_argument("--port","-p",type=int,default=53,
+ metavar="",
+ help="Local proxy port (default:53)")
+ p.add_argument("--address","-a",default="",
+ metavar="",
+ help="Local proxy listen address (default:all)")
+ p.add_argument("--upstream","-u",default="8.8.8.8:53",
+ metavar="",
+ help="Upstream DNS server:port (default:8.8.8.8:53)")
+ p.add_argument("--tcp",action='store_true',default=False,
+ help="TCP proxy (default: UDP only)")
+ p.add_argument("--intercept","-i",action="append",
+ metavar="",
+ help="Intercept requests matching zone record (glob) ('-' for stdin)")
+ p.add_argument("--skip","-s",action="append",
+ metavar="