[ldns] * Mon Sep 19 2011 Paul Wouters <paul at xelerance.com> - 1.6.10-2 - Fix for losing nameserver when it d

Paul Wouters pwouters at fedoraproject.org
Mon Sep 19 15:57:42 UTC 2011


commit 1b7d3089d5088dfd65a91d712242005ed8237855
Author: Paul Wouters <paul at xelerance.com>
Date:   Mon Sep 19 11:57:02 2011 -0400

    * Mon Sep 19 2011 Paul Wouters <paul at xelerance.com> - 1.6.10-2
    - Fix for losing nameserver when it drops UDP fragments in
      ldns_resolver_send_pkt [Willem Toorop <willem at NLnetLabs.nl>]
    - Added ldnsx module (to be merged into ldns soon)
      http://git.xelerance.com/cgi-bin/gitweb.cgi?p=ldnsx.git;a=summary

 ldns-fragmentdrop.patch |   70 ++++
 ldns.spec               |   17 +-
 ldnsx.py                |  923 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1008 insertions(+), 2 deletions(-)
---
diff --git a/ldns-fragmentdrop.patch b/ldns-fragmentdrop.patch
new file mode 100644
index 0000000..0305016
--- /dev/null
+++ b/ldns-fragmentdrop.patch
@@ -0,0 +1,70 @@
+Index: resolver.c
+===================================================================
+--- resolver.c	(revision 3550)
++++ resolver.c	(revision 3551)
+@@ -1031,12 +1031,41 @@
+ 	return pkt;
+ }
+ 
++static size_t *
++ldns_resolver_backup_rtt(ldns_resolver *r)
++{
++	size_t *new_rtt;
++	size_t *old_rtt = ldns_resolver_rtt(r);
++
++	if (old_rtt && ldns_resolver_nameserver_count(r)) {
++		new_rtt = LDNS_XMALLOC(size_t
++				, ldns_resolver_nameserver_count(r));
++		memcpy(new_rtt, old_rtt, sizeof(size_t)
++				* ldns_resolver_nameserver_count(r));
++		ldns_resolver_set_rtt(r, new_rtt);
++		return old_rtt;
++	}
++	return NULL;
++}
++
++static void
++ldns_resolver_restore_rtt(ldns_resolver *r, size_t *old_rtt)
++{
++	size_t *cur_rtt = ldns_resolver_rtt(r);
++
++	if (cur_rtt) {
++		LDNS_FREE(cur_rtt);
++	}
++	ldns_resolver_set_rtt(r, old_rtt);
++}
++
+ ldns_status
+ ldns_resolver_send_pkt(ldns_pkt **answer, ldns_resolver *r,
+ 				   ldns_pkt *query_pkt)
+ {
+ 	ldns_pkt *answer_pkt = NULL;
+ 	ldns_status stat = LDNS_STATUS_OK;
++	size_t *rtt;
+ 
+ 	stat = ldns_send(&answer_pkt, (ldns_resolver *)r, query_pkt);
+ 	if (stat != LDNS_STATUS_OK) {
+@@ -1051,9 +1080,21 @@
+ 			if (ldns_pkt_tc(answer_pkt)) {
+ 				/* was EDNS0 set? */
+ 				if (ldns_pkt_edns_udp_size(query_pkt) == 0) {
+-					ldns_pkt_set_edns_udp_size(query_pkt, 4096);
++					ldns_pkt_set_edns_udp_size(query_pkt
++							, 4096);
+ 					ldns_pkt_free(answer_pkt);
+-					stat = ldns_send(&answer_pkt, r, query_pkt);
++					/* Nameservers should not become 
++					 * unreachable because fragments are
++					 * dropped (network error). We might
++					 * still have success with TCP.
++					 * Therefore maintain reachability
++					 * statuses of the nameservers by
++					 * backup and restore the rtt list.
++					 */
++					rtt = ldns_resolver_backup_rtt(r);
++					stat = ldns_send(&answer_pkt, r
++							, query_pkt);
++					ldns_resolver_restore_rtt(r, rtt);
+ 				}
+ 				/* either way, if it is still truncated, use TCP */
+ 				if (stat != LDNS_STATUS_OK ||
diff --git a/ldns.spec b/ldns.spec
index 6785a8a..4f1c397 100644
--- a/ldns.spec
+++ b/ldns.spec
@@ -8,16 +8,17 @@
 Summary: Lowlevel DNS(SEC) library with API
 Name: ldns
 Version: 1.6.10
-Release: 1%{?dist}
+Release: 2%{?dist}
 License: BSD
 Url: http://www.nlnetlabs.nl/%{name}/
 Source: http://www.nlnetlabs.nl/downloads/%{name}/%{name}-%{version}.tar.gz
+Source1: ldnsx.py
+Patch1: ldns-fragmentdrop.patch
 Group: System Environment/Libraries
 BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
 BuildRequires: perl, libpcap-devel, openssl-devel, gcc-c++, doxygen,
 # for snapshots only
 # BuildRequires: libtool, autoconf, automake
-
 %if %{with_python}
 BuildRequires:  python-devel, swig
 %endif
@@ -55,6 +56,11 @@ Python extensions for ldns
 #libtoolize -c --install
 #autoreconf --install
 
+# Fix for wrong install path for python
+sed -i "s/0,0/1,0/" acx_python.m4
+
+%patch1 -p0
+
 %build
 %configure --disable-rpath --disable-static --with-sha2 --disable-gost \
 %if %{with_python}
@@ -78,6 +84,7 @@ make DESTDIR=%{buildroot} INSTALL="%{__install} -p" install-doc
 %if %{with_python}
 # remove execute perms from python files
 chmod a-x %{buildroot}%{python_sitelib}/*py
+cp %{SOURCE1} %{buildroot}%{python_sitelib}
 %endif
 
 # don't package building script in doc
@@ -125,6 +132,12 @@ rm -rf %{buildroot}
 %postun -p /sbin/ldconfig
 
 %changelog
+* Mon Sep 19 2011 Paul Wouters <paul at xelerance.com> - 1.6.10-2
+- Fix for losing nameserver when it drops UDP fragments in
+  ldns_resolver_send_pkt [Willem Toorop <willem at NLnetLabs.nl>]
+- Added ldnsx module (to be merged into ldns soon)
+  http://git.xelerance.com/cgi-bin/gitweb.cgi?p=ldnsx.git;a=summary
+
 * Wed Jun 08 2011 Paul Wouters <paul at xelerance.com> - 1.6.10-1
 - Upodated to 1.6.10
 - Commented out dependancies that are only needed for snapshots
diff --git a/ldnsx.py b/ldnsx.py
new file mode 100644
index 0000000..d6ddfc9
--- /dev/null
+++ b/ldnsx.py
@@ -0,0 +1,923 @@
+# (c) Christopher Olah <colah at xelerance.com>, 2011. Xelerance <http://www.xelerance.com/>.
+# License: BSD
+
+
+""" Easy DNS (including DNSSEC) via ldns.
+
+ldns is a great library. It is a powerfull tool for
+working with DNS. python-ldns it is a straight up clone of the C
+interface, howver that is not a very good interface for python. Its
+documentation is incomplete and some functions don't work as
+described. And some objects don't have a full python API.
+
+ldnsx aims to fix this. It wraps around the ldns python bindings,
+working around its limitations and providing a well-documented, more
+pythonistic interface.
+
+**WARNING:** 
+
+**API subject to change.** No backwards compatibility guarantee. Write software using this version at your own risk!
+
+Examples
+--------
+
+Query the default resolver for google.com's A records. Print the response
+packet.
+
+>>> import ldnsx
+>>> resolver = ldnsx.resolver()
+>>> print resolver.query("google.com","A")
+
+
+Print the root NS records from f.root-servers.net; if we get a
+response, else an error message.
+
+>>> import ldnsx
+>>> pkt = ldnsx.resolver("f.root-servers.net").query(".", "NS")
+>>> if pkt:
+>>>    for rr in pkt.answer():
+>>>       print rr
+>>> else:
+>>>    print "response not received" 
+
+"""
+
+import time, sys, calendar, warnings
+try:
+	import ipcalc
+except ImportError:
+	print >> sys.stderr, "ldnsx requires the python-ipcalc"
+	print >> sys.stderr, "Fedora/CentOS: yum install python-ipcalc"
+	print >> sys.stderr, "Debian/Ubuntu: apt-get install python-ipcalc"
+	print >> sys.stderr, "openSUSE: zypper in python-ipcalc"
+	sys.exit(1)
+try:
+	import ldns
+except ImportError:
+	print >> sys.stderr, "ldnsx requires the ldns-python sub-package from http://www.nlnetlabs.nl/projects/ldns/"
+	print >> sys.stderr, "Fedora/CentOS: yum install ldns-python"
+	print >> sys.stderr, "Debian/Ubuntu: apt-get install python-ldns"
+	print >> sys.stderr, "openSUSE: zypper in python-ldns"
+	sys.exit(1)
+
+__version__ = "0.1"
+
+def isValidIP(ipaddr):
+	try:
+		bits_to_type = { 32 : 4, 128 : 6}
+		bits = len(ipcalc.IP(ipaddr).bin())
+		return bits_to_type[bits]
+	except:
+		return 0
+
+def query(name, rr_type, rr_class="IN", flags=["RD"], tries = 3, res=None):
+	"""Convenience function. Creates a resolver and then queries it. Refer to resolver.query() 
+	   * name -- domain to query for 
+	   * rr_type -- rr_type to query for
+	   * flags -- flags for query (list of strings)
+	   * tries -- number of times to retry the query on failure
+	   * res -- configurations for the resolver as a dict -- see resolver()
+	   """
+	if isinstance(res, list) or isinstance(res, tuple):
+		res = resolver(*res)
+	elif isinstance(res, dict):
+		res = resolver(**res)
+	else:
+		res = resolver(res)
+	return res.query(name, rr_type, rr_class, flags, tries)
+
+def get_rrs(name, rr_type, rr_class="IN", tries = 3, strict = False, res=None, **kwds):
+	"""Convenience function. Gets RRs for name of type rr_type trying tries times. 
+	   If strict, it raises and exception on failure, otherwise it returns []. 
+	   * name -- domain to query for 
+	   * rr_type -- rr_type to query for
+	   * flags -- flags for query (list of strings)
+	   * tries -- number of times to retry the query on failure
+	   * strict -- if the query fails, do we return [] or raise an exception?
+	   * res -- configurations for the resolver as a dict -- see resolver()
+	   * kwds -- query filters, refer to packet.answer()
+       """
+	if isinstance(res, list) or isinstance(res, tuple):
+		res = resolver(*res)
+	elif isinstance(res, dict):
+		res = resolver(**res)
+	else:
+		res = resolver(res)
+	if "|" in rr_type:
+		pkt = res.query(name, "ANY", rr_class=rr_class, tries=tries)
+	else:
+		pkt = res.query(name, rr_type, rr_class=rr_class, tries=tries)
+	if pkt:
+		if rr_type in ["", "ANY", "*"]:
+			return pkt.answer( **kwds)
+		else:
+			return pkt.answer(rr_type=rr_type, **kwds)
+	else:
+		if strict:
+			raise Exception("LDNS couldn't complete query")
+		else:
+			return []
+
+def secure_query(name, rr_type, rr_class="IN", flags=["RD"], tries = 1, flex=False, res=None):
+	"""Convenience function. Creates a resolver and then does a DNSSEC query. Refer to resolver.query() 
+	   * name -- domain to query for 
+	   * rr_type -- rr_type to query for
+	   * flags -- flags for query (list of strings)
+	   * tries -- number of times to retry the query on failure
+	   * flex -- if we can't verify data, exception or warning?
+	   * res -- configurations for the resolver as a dict -- see resolver()"""
+	if isinstance(res, list) or isinstance(res, tuple):
+		res = resolver(*res)
+	elif isinstance(res, dict):
+		res = resolver(**res)
+	else:
+		res = resolver(res)
+	pkt = res.query(name, rr_type, rr_class, flags, tries)
+	if pkt.rcode() == "SERVFAIL":
+		raise Exception("%s lookup failed (server error or dnssec validation failed)" % name)
+	if pkt.rcode() == "NXDOMAIN":
+		if "AD" in pkt.flags():
+			raise Exception("%s lookup failed (non-existence proven by DNSSEC)" % hostname )
+		else:
+			raise Exception("%s lookup failed" % hostname )
+	if pkt.rcode() == "NOERROR":
+		if "AD" not in pkt.flags():
+			if not flex:
+				raise Exception("DNS lookup was insecure")
+			else:
+				warnings.warn("DNS lookup was insecure")
+		return pkt
+	else:
+		raise Exception("unknown ldns error, %s" % pkt.rcode())
+
+
+
+class resolver:
+	""" A wrapper around ldns.ldns_resolver. 
+
+			**Examples**
+
+			Making resolvers is easy!
+
+			>>> from ldnsx import resolver
+			>>> resolver() # from /etc/resolv.conf
+			<resolver: 192.168.111.9>
+			>>> resolver("") # resolver with no nameservers
+			<resolver: >
+			>>> resolver("193.110.157.135") #resolver pointing to ip addr
+			<resolver: 193.110.157.135>
+			>>> resolver("f.root-servers.net") # resolver pointing ip address(es) resolved from name
+			<resolver: 2001:500:2f::f, 192.5.5.241>
+			>>> resolver("193.110.157.135, 193.110.157.136") 
+			>>> # resolver pointing to multiple ip addr, first takes precedence.
+			<resolver: 193.110.157.136, 193.110.157.135>
+
+			So is playing around with their nameservers!
+
+			>>> import ldnsx
+			>>> res = ldnsx.resolver("192.168.1.1")
+			>>> res.add_nameserver("192.168.1.2")
+			>>> res.add_nameserver("192.168.1.3")
+			>>> res.nameservers_ip()
+			["192.168.1.1","192.168.1.2","192.168.1.3"]
+
+			And querying!
+
+			>>> from ldnsx import resolver
+			>>> res= resolver()
+			>>> res.query("cow.com","A")
+			;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 7663
+			;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 
+			;; QUESTION SECTION:
+			;; cow.com.     IN      A
+			;; ANSWER SECTION:
+			cow.com.        300     IN      A       208.87.34.18
+			;; AUTHORITY SECTION:
+			;; ADDITIONAL SECTION:
+			;; Query time: 313 msec
+			;; SERVER: 192.168.111.9
+			;; WHEN: Fri Jun  3 11:01:02 2011
+			;; MSG SIZE  rcvd: 41
+
+	
+			"""
+	
+	def __init__(self, ns = None, dnssec = False, tcp = 'auto', port = 53):
+		"""resolver constructor
+			
+			* ns    --  the nameserver/comma delimited nameserver list
+			            defaults to settings from /etc/resolv.conf
+			* dnssec -- should the resolver try and use dnssec or not?
+		    * tcp -- should the resolve try to connect with TCP? 
+		             'auto' tries without tcp, and falls back to it
+		             to work around both ldns choking on truncated packets
+		             and nameservers that don't support tcp.
+		    * port -- the port to use, must be the same for all nameservers
+
+			"""
+		# We construct based on a file and dump the nameservers rather than using
+		# ldns_resolver_new() to avoid environment/configuration/magic specific 
+		# bugs.
+		self._ldns_resolver = ldns.ldns_resolver.new_frm_file("/etc/resolv.conf")
+		if ns != None:
+			self.drop_nameservers()
+			nm_list = ns.split(',')
+			nm_list = map(lambda s: s.strip(), nm_list)
+			nm_list = filter(lambda s: s != "", nm_list)
+			nm_list.reverse()
+			for nm in nm_list:
+				self.add_nameserver(nm)
+		# Configure DNSSEC, tcp and port
+		self.set_dnssec(dnssec)
+		if tcp == 'auto':
+			self.autotcp = True
+			self._ldns_resolver.set_usevc(False)
+		else:
+			self.autotcp = False
+			self._ldns_resolver.set_usevc(tcp)
+		self._ldns_resolver.set_port(port)
+
+	
+	def query(self, name, rr_type, rr_class="IN", flags=["RD"], tries = 3):
+		"""Run a query on the resolver.
+				
+			* name -- name to query for
+			* rr_type -- the record type to query for
+			* rr_class -- the class to query for, defaults to IN (Internet)
+			* flags -- the flags to send the query with 
+			* tries -- the number of times to attempt to acheive query in case of packet loss, etc
+			
+			**Examples**
+			
+			Let's get some A records!
+
+			>>> google_a_records = resolver.query("google.com","A").answer()
+			
+			Using DNSSEC is easy :)
+
+			>>> dnssec_pkt = ldnsx.resolver(dnssec=True).query("xelerance.com")
+			
+			We let you use strings to make things easy, but if you prefer stay close to DNS...
+
+			>>> AAAA = 28
+			>>> resolver.query("ipv6.google.com", AAAA)
+			
+			**More about rr_type**
+			
+			rr_type must be a supported resource record type. There are a large number of RR types:
+
+			===========  ===================================  ==================
+			TYPE         Value and meaning                    Reference
+			===========  ===================================  ==================
+			A            1 a host address                     [RFC1035]
+			NS           2 an authoritative name server       [RFC1035]
+			...
+			AAAA         28 IP6 Address                       [RFC3596]
+			...
+			DS           43 Delegation Signer                 [RFC4034][RFC3658]
+			...
+			DNSKEY       48 DNSKEY                            [RFC4034][RFC3755]
+			...
+			Unassigned   32770-65279  
+			Private use  65280-65534
+			Reserved     65535 
+			===========  ===================================  ==================
+			
+			(From http://www.iana.org/assignments/dns-parameters)
+
+			RR types are given as a string (eg. "A"). In the case of Unassigned/Private use/Reserved ones,
+			they are given as "TYPEXXXXX" where XXXXX is the number. ie. RR type 65280 is "TYPE65280". You 
+			may also pass the integer, but you always be given the string.
+
+			If the version of ldnsx you are using is old, it is possible that there could be new rr_types that
+			we don't recognise mnemonic for. You can still use the number XXX or the string "TYPEXXX". To
+			determine what rr_type menmonics we support, please refer to resolver.supported_rr_types()
+
+		"""
+		# Determine rr_type int
+		if rr_type in _rr_types.keys():
+			_rr_type = _rr_types[rr_type]
+		elif isinstance(rr_type,int):
+			_rr_type = rr_type
+		elif isinstance(rr_type,str) and rr_type[0:4] == "TYPE":
+			try:
+				_rr_type = int(rr_type[4:])
+			except:
+				raise Exception("%s is a bad RR type. TYPEXXXX: XXXX must be a number")
+		else:
+			raise Exception("ldnsx (version %s) does not support the RR type %s."  % (__version__, str(rr_type)) ) 
+		# Determine rr_class int
+		if   rr_class == "IN": _rr_class = ldns.LDNS_RR_CLASS_IN 
+		elif rr_class == "CH": _rr_class = ldns.LDNS_RR_CLASS_CH
+		elif rr_class == "HS": _rr_class = ldns.LDNS_RR_CLASS_HS
+		else:
+			raise Exception("ldnsx (version %s) does not support the RR class %s." % (__version__, str(rr_class)) ) 
+		# Determine flags int
+		_flags = 0
+		if "QR" in flags:  _flags |= ldns.LDNS_QR
+		if "AA" in flags:  _flags |= ldns.LDNS_AA
+		if "TC" in flags:  _flags |= ldns.LDNS_TC
+		if "RD" in flags:  _flags |= ldns.LDNS_RD
+		if "CD" in flags:  _flags |= ldns.LDNS_CD
+		if "RA" in flags:  _flags |= ldns.LDNS_RA
+		if "AD" in flags:  _flags |= ldns.LDNS_AD
+		# Query
+		if tries == 0: return None
+		try:
+			pkt = self._ldns_resolver.query(name, _rr_type, _rr_class, _flags)
+		except KeyboardInterrupt: #Since so much time is spent waiting on ldns, this is very common place for Ctr-C to fall
+			raise
+		except: #Since the ldns exceptiion is not very descriptive...
+			raise Exception("ldns backend ran into problems. Likely, the name you were querying for, %s, was invalid." % name)
+		#Deal with failed queries
+		if not pkt:
+			if tries <= 1:
+				return None
+			else:
+				# One of the major causes of none-packets is truncation of packets
+				# When autotcp is set, we are in a flexible enough position to try and use tcp
+				# to get around this.
+				# Either way, we want to replace the resolver, since resolvers will sometimes
+				# just freeze up.
+				if self.autotcp:
+					self = resolver( ",".join(self.nameservers_ip()),tcp=True)
+					self.autotcp = True
+					pkt = self.query(name, rr_type, rr_class=rr_class, flags=flags, tries = tries-1) 
+					self._ldns_resolver.set_usevc(False)
+					return pkt
+				else:
+					self = resolver( ",".join(self.nameservers_ip()) )
+					time.sleep(1) # It could be that things are failing because of a brief outage
+					return self.query(name, rr_type, rr_class=rr_class, flags=flags, tries = tries-1) 
+		elif self.autotcp:
+			pkt = packet(pkt)
+			if "TC" in pkt.flags():
+				self._ldns_resolver.set_usevc(True)
+				pkt2 = self.query(name, rr_type, rr_class=rr_class, flags=flags, tries = tries-1) 
+				self._ldns_resolver.set_usevc(False)
+				if pkt2: return packet(pkt2)
+			return pkt
+		return packet(pkt)
+		#ret = []
+		#for rr in pkt.answer().rrs():
+		#	ret.append([str(rr.owner()),rr.ttl(),rr.get_class_str(),rr.get_type_str()]+[str(rdf) for rdf in rr.rdfs()])
+		#return ret
+
+	def suported_rr_types(self):
+		""" Returns the supported DNS resource record types.
+
+			Refer to resolver.query() for thorough documentation of resource 
+			record types or refer to:
+
+			http://www.iana.org/assignments/dns-parameters
+
+		"""
+		return _rr_types.keys()
+	
+	def AXFR(self,name):
+		"""AXFR for name
+
+			* name -- name to AXFR for
+			
+			This function is a generator. As it AXFRs it will yield you the records.
+
+			**Example**
+
+			Let's get a list of the tlds (gotta catch em all!):
+
+			>>> tlds = []
+			>>> for rr in resolver("f.root-servers.net").AXFR("."):
+			>>>    if rr.rr_type() == "NS":
+			>>>       tlds.append(rr.owner())
+
+		"""
+		#Dname seems to be unecessary on some computers, but it is on others. Avoid bugs.
+		if self._ldns_resolver.axfr_start(ldns.ldns_dname(name), ldns.LDNS_RR_CLASS_IN) != ldns.LDNS_STATUS_OK:
+			raise Exception("Starting AXFR failed. Error: %s" % ldns.ldns_get_errorstr_by_id(status))
+		pres = self._ldns_resolver.axfr_next()
+		while pres:
+			yield resource_record(pres)
+			pres = self._ldns_resolver.axfr_next()
+
+	def nameservers_ip(self):
+		""" returns a list of the resolvers nameservers (as IP addr)
+		
+		"""
+		nm_stack2 =[]
+		nm_str_stack2=[]
+		nm = self._ldns_resolver.pop_nameserver()
+		while nm:
+			nm_stack2.append(nm)
+			nm_str_stack2.append(str(nm))
+			nm = self._ldns_resolver.pop_nameserver()
+		for nm in nm_stack2:
+			self._ldns_resolver.push_nameserver(nm)
+		nm_str_stack2.reverse()
+		return nm_str_stack2
+
+
+	def add_nameserver(self,ns):
+		""" Add a nameserver, IPv4/IPv6/name.
+
+		"""
+		if isValidIP(ns) == 4:
+			address = ldns.ldns_rdf_new_frm_str(ldns.LDNS_RDF_TYPE_A,ns)
+			self._ldns_resolver.push_nameserver(address)
+		elif isValidIP(ns) == 6:
+			address = ldns.ldns_rdf_new_frm_str(ldns.LDNS_RDF_TYPE_AAAA,ns)
+			self._ldns_resolver.push_nameserver(address)
+		else:
+			resolver = ldns.ldns_resolver.new_frm_file("/etc/resolv.conf")
+			#address = resolver.get_addr_by_name(ns)
+			address = resolver.get_addr_by_name(ldns.ldns_dname(ns))
+			if not address:
+				address = resolver.get_addr_by_name(ldns.ldns_dname(ns))
+				if not address:
+					raise Exception("Failed to resolve address for %s" % ns)
+			for rr in address.rrs():
+				self._ldns_resolver.push_nameserver_rr(rr)
+
+	def drop_nameservers(self):
+		"""Drops all nameservers.
+			This function causes the resolver to forget all nameservers.
+
+		"""
+		while self._ldns_resolver.pop_nameserver():
+			pass
+
+	def set_nameservers(self, nm_list):
+		"""Takes a list of nameservers and sets the resolver to use them
+		
+		"""
+		self.drop_nameservers()
+		for nm in nm_list:
+			self.add_nameserver(nm)
+
+	def __repr__(self):
+		return "<resolver: %s>" % ", ".join(self.nameservers_ip())
+	__str__ = __repr__
+
+	def set_dnssec(self,new_dnssec_status):
+		"""Set whether the resolver uses DNSSEC.
+		
+		"""
+		self._ldns_resolver.set_dnssec(new_dnssec_status)
+
+class packet:
+	
+	def _construct_rr_filter(self, **kwds):
+		def match(pattern, target):
+			if pattern[0] in ["<",">","!"]:
+				rel = pattern[0]
+				pattern=pattern[1:]
+			elif pattern[0:2] in ["<=","=>"]:
+				rel = pattern[0:2]
+				pattern=pattern[2:]
+			else:
+				rel = "="
+			for val in pattern.split("|"):
+				if {"<" : target <  val,
+				    ">" : target >  val,
+				    "!" : target != val,
+				    "=" : target == val,
+				    ">=": target >= val,
+				    "<=": target <= val}[rel]:
+					return True
+			return False
+		def f(rr):
+			for key in kwds.keys():
+				if ( ( isinstance(kwds[key], list) and str(rr[key]) not in map(str,kwds[key]) )
+				  or ( not isinstance(kwds[key], list) and not match(str(kwds[key]), str(rr[key])))):
+					return False
+			return True
+		return f
+	
+	def __init__(self, pkt):
+		self._ldns_pkt = pkt
+	
+	def __repr__(self):
+		return str(self._ldns_pkt)
+	__str__ = __repr__
+	
+	def rcode(self):
+		"""Returns the rcode.
+
+		Example returned value: "NOERROR"
+
+		possilbe rcodes (via ldns): "FORMERR", "MASK", "NOERROR",
+		"NOTAUTH", "NOTIMPL", "NOTZONE", "NXDOMAIN",
+		"NXRSET", "REFUSED", "SERVFAIL", "SHIFT", 
+		"YXDOMAIN", "YXRRSET"
+
+		Refer to http://www.iana.org/assignments/dns-parameters
+		section: DNS RCODEs
+		"""
+		return self._ldns_pkt.rcode2str()
+
+	def opcode(self):
+		"""Returns the rcode.
+
+		Example returned value: "QUERY"
+
+		"""
+		return self._ldns_pkt.opcode2str()
+	
+	def flags(self):
+		"""Return packet flags (as list of strings).
+		
+		Example returned value: ['QR', 'RA', 'RD']
+
+		**What are the flags?**
+		
+		========  ====  =====================  =========
+		Bit       Flag  Description            Reference
+		========  ====  =====================  =========
+		bit 5     AA    Authoritative Answer   [RFC1035]
+		bit 6     TC    Truncated Response     [RFC1035]
+		bit 7     RD    Recursion Desired      [RFC1035]
+		bit 8     RA    Recursion Allowed      [RFC1035]
+		bit 9           Reserved
+		bit 10    AD    Authentic Data         [RFC4035]
+		bit 11    CD    Checking Disabled      [RFC4035]
+		========  ====  =====================  =========
+
+		(from http://www.iana.org/assignments/dns-parameters)
+
+		There is also QR. It is mentioned in other sources,
+		though not the above page. It being false means that
+		the packet is a query, it being true means that it is
+		a response.
+
+		"""
+		ret = []
+		if self._ldns_pkt.aa(): ret += ["AA"]
+		if self._ldns_pkt.ad(): ret += ["AD"]
+		if self._ldns_pkt.cd(): ret += ["CD"]
+		if self._ldns_pkt.qr(): ret += ["QR"]
+		if self._ldns_pkt.ra(): ret += ["RA"]
+		if self._ldns_pkt.rd(): ret += ["RD"]
+		if self._ldns_pkt.tc(): ret += ["TC"]
+		return ret
+
+	def answer(self, **filters):
+		"""Returns the answer section.
+		
+		* filters -- a filtering mechanism
+		
+		Since a very common desire is to filter the resource records in a packet
+		section, we provide a special tool for doing this: filters. They are a
+		lot like regular python filters, but more convenient. If you set a 
+		field equal to some value, you will only receive resource records for which
+		it holds true.
+
+		**Examples**
+
+		>>> res = ldnsx.resolver()
+		>>> pkt = res.query("google.ca","A")
+		>>> pkt.answer()
+		[google.ca.     28      IN      A       74.125.91.99
+		, google.ca.    28      IN      A       74.125.91.105
+		, google.ca.    28      IN      A       74.125.91.147
+		, google.ca.    28      IN      A       74.125.91.103
+		, google.ca.    28      IN      A       74.125.91.104
+		, google.ca.    28      IN      A       74.125.91.106
+		]
+
+		To understand filters, consider the following:
+
+		>>> pkt = ldnsx.query("cow.com","ANY")
+		>>> pkt.answer()
+		[cow.com.       276     IN      A       208.87.32.75
+		, cow.com.      3576    IN      NS      sell.internettraffic.com.
+		, cow.com.      3576    IN      NS      buy.internettraffic.com.
+		, cow.com.      3576    IN      SOA     buy.internettraffic.com. hostmaster.hostingnet.com. 1308785320 10800 3600 604800 3600
+		]
+		>>> pkt.answer(rr_type="A")
+		[cow.com.       276     IN      A       208.87.32.75
+		]
+		>>> pkt.answer(rr_type="A|NS")
+		[cow.com.       276     IN      A       208.87.32.75
+		, cow.com.      3576    IN      NS      sell.internettraffic.com.
+		, cow.com.      3576    IN      NS      buy.internettraffic.com.
+		]
+		>>> pkt.answer(rr_type="!NS")
+		[cow.com.       276     IN      A       208.87.32.75
+		, cow.com.      3576    IN      SOA     buy.internettraffic.com. hostmaster.hostingnet.com. 1308785320 10800 3600 604800 3600
+		]
+		
+		fields are the same as when indexing a resource record.
+		note: ordering is alphabetical.
+		"""
+		ret =  [resource_record(rr) for rr in self._ldns_pkt.answer().rrs()]
+		return filter(self._construct_rr_filter(**filters), ret)
+
+	def authority(self, **filters):
+		"""Returns the authority section.
+
+		* filters -- a filtering mechanism
+		
+		Since a very common desire is to filter the resource records in a packet
+		section, we provide a special tool for doing this: filters. They are a
+		lot like regular python filters, but more convenient. If you set a 
+		field equal to some value, you will only receive resource records for which
+		it holds true. See answer() for details.
+
+		**Examples**
+
+		>>> res = ldnsx.resolver()
+		>>> pkt = res.query("google.ca","A")
+		>>> pkt.authority()
+		[google.ca.     251090  IN      NS      ns3.google.com.
+		, google.ca.    251090  IN      NS      ns1.google.com.
+		, google.ca.    251090  IN      NS      ns2.google.com.
+		, google.ca.    251090  IN      NS      ns4.google.com.
+		]
+
+		"""
+		ret = [resource_record(rr) for rr in self._ldns_pkt.authority().rrs()]
+		return filter(self._construct_rr_filter(**filters), ret)
+
+	def additional(self, **filters):
+		"""Returns the additional section.
+
+		* filters -- a filtering mechanism
+		
+		Since a very common desire is to filter the resource records in a packet
+		section, we provide a special tool for doing this: filters. They are a
+		lot like regular python filters, but more convenient. If you set a 
+		field equal to some value, you will only receive resource records for which
+		it holds true. See answer() for details.
+
+		**Examples**
+
+		>>> res = ldnsx.resolver()
+		>>> pkt = res.query("google.ca","A")
+		>>> pkt.additional()
+		[ns3.google.com.        268778  IN      A       216.239.36.10
+		, ns1.google.com.       262925  IN      A       216.239.32.10
+		, ns2.google.com.       255659  IN      A       216.239.34.10
+		, ns4.google.com.       264489  IN      A       216.239.38.10
+		]
+
+		"""
+		ret = [resource_record(rr) for rr in self._ldns_pkt.additional().rrs()]
+		return filter(self._construct_rr_filter(**filters), ret)
+
+	def question(self, **filters):
+		"""Returns the question section.
+
+		* filters -- a filtering mechanism
+		
+		Since a very common desire is to filter the resource records in a packet
+		section, we provide a special tool for doing this: filters. They are a
+		lot like regular python filters, but more convenient. If you set a 
+		field equal to some value, you will only receive resource records for which
+		it holds true. See answer() for details.
+
+		"""
+		ret = [resource_record(rr) for rr in self._ldns_pkt.question().rrs()]
+		return filter(self._construct_rr_filter(**filters), ret)
+
+class resource_record:
+	
+	_rdfs = None
+	_iter_pos = None
+	
+	def __init__(self, rr):
+		self._ldns_rr = rr
+		self._rdfs = [str(rr.owner()),rr.ttl(),rr.get_class_str(),rr.get_type_str()]+[str(rdf) for rdf in rr.rdfs()]
+	
+	def __repr__(self):
+		return str(self._ldns_rr)
+	
+	__str__ = __repr__
+
+	def __iter__(self):
+		self._iter_pos = 0
+		return self
+
+	def next(self):
+		if self._iter_pos < len(self._rdfs):
+			self._iter_pos += 1
+			return self._rdfs[self._iter_pos-1]
+		else:
+			raise StopIteration
+
+	def __len__(self):
+		try:
+			return len(_rdfs)
+		except:
+			return 0
+
+	def __getitem__(self, n):
+		if isinstance(n, int):
+			return self._rdfs[n]
+		elif isinstance(n, str):
+			n = n.lower()
+			if n in ["owner"]:
+				return self.owner()
+			elif n in ["rr_type", "rr type", "type"]:
+				return self.rr_type()
+			elif n in ["rr_class", "rr class", "class"]:
+				return self.rr_class()
+			elif n in ["covered_type", "covered type", "type2"]:
+				return self.covered_type()
+			elif n in ["ttl"]:
+				return self.ttl()
+			elif n in ["ip"]:
+				return self.ip()			
+			elif n in ["alg", "algorithm"]:
+				return self.alg()
+			elif n in ["protocol"]:
+				return self.protocol()
+			elif n in ["flags"]:
+				return self.flags()
+			else:
+				raise Exception("ldnsx (version %s) does not recognize the rr field %s" % (__version__,n) ) 
+		else:
+			raise TypeError("bad type %s for index resource record" % type(n) ) 
+			
+
+	#def rdfs(self):
+	#	return self._rdfs.clone()
+	
+	def owner(self):
+		"""Get the RR's owner"""
+		return str(self._ldns_rr.owner())
+	
+	def rr_type(self):
+		"""Get a RR's type """
+		return self._ldns_rr.get_type_str()
+
+	def covered_type(self):
+		"""Get an RRSIG RR's covered type"""
+		if self.rr_type() == "RRSIG":
+			return self[4]
+		else:
+			return ""
+	
+	def rr_class(self):
+		"""Get the RR's collapse"""
+		return self._ldns_rr.get_class_str()
+	
+	def ttl(self):
+		"""Get the RR's TTL"""
+		return self._ldns_rr.ttl()
+
+	def inception(self, out_format="UTC"):
+		"""returns the inception time in format out_format, defaulting to a UTC string. 
+		options for out_format are:
+
+		   UTC -- a UTC string eg. 20110712192610 (2011/07/12 19:26:10) 
+		   unix -- number of seconds since the epoch, Jan 1, 1970
+		   struct_time -- the format used by python's time library
+		"""
+		# Something very strange is going on with inception/expiration dates in DNS.
+		# According to RFC 4034 section 3.1.5 (http://tools.ietf.org/html/rfc4034#page-9)
+		# the inception/expiration fields should be in seconds since Jan 1, 1970, the Unix
+		# epoch (as is standard in unix). Yet all the packets I've seen provide UTC encoded
+		# as a string instead, eg. "20110712192610" which is 2011/07/12 19:26:10. 
+		#
+		# It turns out that this is a standard thing that ldns is doing before the data gets 
+		# to us.
+		if self.rr_type() == "RRSIG":
+			if out_format.lower() in ["utc", "utc str", "utc_str"]:
+				return self[9]
+			elif out_format.lower() in ["unix", "posix", "ctime"]:
+				return calendar.timegm(time.strptime(self[9], "%Y%m%d%H%M%S"))
+			elif out_format.lower() in ["relative"]:
+				return calendar.timegm(time.strptime(self[9], "%Y%m%d%H%M%S")) - time.time()
+			elif out_format.lower() in ["struct_time", "time.struct_time"]:
+				return time.strptime(self[9], "%Y%m%d%H%M%S")
+			else:
+				raise Exception("unrecognized time format")
+		else:
+			return ""
+
+	def expiration(self, out_format="UTC"):
+		"""get expiration time. see inception() for more information"""
+		if self.rr_type() == "RRSIG":
+			if out_format.lower() in ["utc", "utc str", "utc_str"]:
+				return self[8]
+			elif out_format.lower() in ["unix", "posix", "ctime"]:
+				return calendar.timegm(time.strptime(self[8], "%Y%m%d%H%M%S"))
+			elif out_format.lower() in ["relative"]:
+				return calendar.timegm(time.strptime(self[8], "%Y%m%d%H%M%S")) - time.time()
+			elif out_format.lower() in ["struct_time", "time.struct_time"]:
+				return time.strptime(self[8], "%Y%m%d%H%M%S")
+			else:
+				raise Exception("unrecognized time format")
+		else:
+			return ""
+
+	def ip(self):
+		""" IP address form A/AAAA record"""
+		if self.rr_type() in ["A", "AAAA"]:
+			return self[4]
+		else:
+			raise Exception("ldnsx does not support ip for records other than A/AAAA")
+
+	def alg(self):
+		"""Returns algorithm of RRSIG/DNSKEY/DS"""
+		t = self.rr_type() 
+		if t == "RRSIG":
+			return int(self[5])
+		elif t == "DNSKEY":
+			return int(self[6])
+		elif t == "DS":
+			return int(self[5])
+		else:
+			return -1
+
+	def protocol(self):
+		""" Returns proticol of the DNSKEY"""
+		t = self.rr_type() 
+		if t == "DNSKEY":
+			return int(self[5])
+		else:
+			return -1
+
+	def flags(self):
+		"""Return RR flags for DNSKEY """
+		t = self.rr_type() 
+		if t == "DNSKEY":
+			ret = []
+			n = int(self[4])
+			for m in range(1):
+				if 2**(15-m) & n:
+					if   m == 7: ret.append("ZONE")
+					elif m == 8: ret.append("REVOKE")
+					elif m ==15: ret.append("SEP")
+					else:        ret.append(m)
+			return ret
+		else:
+			return []
+
+_rr_types={
+	"A"    : ldns.LDNS_RR_TYPE_A,
+	"A6"   : ldns.LDNS_RR_TYPE_A6,
+	"AAAA" : ldns.LDNS_RR_TYPE_AAAA,
+	"AFSDB": ldns.LDNS_RR_TYPE_AFSDB,
+	"ANY"  : ldns.LDNS_RR_TYPE_ANY,
+	"APL"  : ldns.LDNS_RR_TYPE_APL,
+	"ATMA" : ldns.LDNS_RR_TYPE_ATMA,
+	"AXFR" : ldns.LDNS_RR_TYPE_AXFR,
+	"CERT" : ldns.LDNS_RR_TYPE_CERT,
+	"CNAME": ldns.LDNS_RR_TYPE_CNAME,
+	"COUNT": ldns.LDNS_RR_TYPE_COUNT,
+	"DHCID": ldns.LDNS_RR_TYPE_DHCID,
+	"DLV"  : ldns.LDNS_RR_TYPE_DLV,
+	"DNAME": ldns.LDNS_RR_TYPE_DNAME,
+	"DNSKEY": ldns.LDNS_RR_TYPE_DNSKEY,
+	"DS"   : ldns.LDNS_RR_TYPE_DS,
+	"EID"  : ldns.LDNS_RR_TYPE_EID,
+	"FIRST": ldns.LDNS_RR_TYPE_FIRST,
+	"GID"  : ldns.LDNS_RR_TYPE_GID,
+	"GPOS" : ldns.LDNS_RR_TYPE_GPOS,
+	"HINFO": ldns.LDNS_RR_TYPE_HINFO,
+	"IPSECKEY": ldns.LDNS_RR_TYPE_IPSECKEY,
+	"ISDN" : ldns.LDNS_RR_TYPE_ISDN,
+	"IXFR" : ldns.LDNS_RR_TYPE_IXFR,
+	"KEY"  : ldns.LDNS_RR_TYPE_KEY,
+	"KX"   : ldns.LDNS_RR_TYPE_KX,
+	"LAST" : ldns.LDNS_RR_TYPE_LAST,
+	"LOC"  : ldns.LDNS_RR_TYPE_LOC,
+	"MAILA": ldns.LDNS_RR_TYPE_MAILA,
+	"MAILB": ldns.LDNS_RR_TYPE_MAILB,
+	"MB"   : ldns.LDNS_RR_TYPE_MB,
+	"MD"   : ldns.LDNS_RR_TYPE_MD,
+	"MF"   : ldns.LDNS_RR_TYPE_MF,
+	"MG"   : ldns.LDNS_RR_TYPE_MG,
+	"MINFO": ldns.LDNS_RR_TYPE_MINFO,
+	"MR"   : ldns.LDNS_RR_TYPE_MR,
+	"MX"   : ldns.LDNS_RR_TYPE_MX,
+	"NAPTR": ldns.LDNS_RR_TYPE_NAPTR,
+	"NIMLOC": ldns.LDNS_RR_TYPE_NIMLOC,
+	"NS"   : ldns.LDNS_RR_TYPE_NS,
+	"NSAP" : ldns.LDNS_RR_TYPE_NSAP,
+	"NSAP_PTR" : ldns.LDNS_RR_TYPE_NSAP_PTR,
+	"NSEC" : ldns.LDNS_RR_TYPE_NSEC,
+	"NSEC3": ldns.LDNS_RR_TYPE_NSEC3,
+	"NSEC3PARAMS" : ldns.LDNS_RR_TYPE_NSEC3PARAMS,
+	"NULL" : ldns.LDNS_RR_TYPE_NULL,
+	"NXT"  : ldns.LDNS_RR_TYPE_NXT,
+	"OPT"  : ldns.LDNS_RR_TYPE_OPT,
+	"PTR"  : ldns.LDNS_RR_TYPE_PTR,
+	"PX"   : ldns.LDNS_RR_TYPE_PX,
+	"RP"   : ldns.LDNS_RR_TYPE_RP,
+	"RRSIG": ldns.LDNS_RR_TYPE_RRSIG,
+	"RT"   : ldns.LDNS_RR_TYPE_RT,
+	"SIG"  : ldns.LDNS_RR_TYPE_SIG,
+	"SINK" : ldns.LDNS_RR_TYPE_SINK,
+	"SOA"  : ldns.LDNS_RR_TYPE_SOA,
+	"SRV"  : ldns.LDNS_RR_TYPE_SRV,
+	"SSHFP": ldns.LDNS_RR_TYPE_SSHFP,
+	"TSIG" : ldns.LDNS_RR_TYPE_TSIG,
+	"TXT"  : ldns.LDNS_RR_TYPE_TXT,
+	"UID"  : ldns.LDNS_RR_TYPE_UID,
+	"UINFO": ldns.LDNS_RR_TYPE_UINFO,
+	"UNSPEC": ldns.LDNS_RR_TYPE_UNSPEC,
+	"WKS"  : ldns.LDNS_RR_TYPE_WKS,
+	"X25"  : ldns.LDNS_RR_TYPE_X25
+}
+


More information about the scm-commits mailing list