--- /dev/null
+#! /usr/bin/python
+
+import sys, socket, getopt
+
+import dns.zone
+import dns.query
+import dns.message
+import dns.rdatatype
+import dns.rdataclass
+
+def min_size(message):
+ """Calculate the minimum size this response can be without setting
+ the TC (truncation bit). This is calculated by removing all of
+ the additional section save the OPT pseudo-RR."""
+
+ additional = message.additional
+ # note that in dns.message.Message, the OPT RR is not stored in
+ # the additional section (where it was). Instead it is parsed
+ # into various member variables, so we don't need to do anything
+ # special to preserve it.
+ message.additional = []
+ wire = message.to_wire()
+ message.additional = additional
+ return len(wire)
+
+def additional_qname_length(qname):
+ """Calculate the additional size a message might take if it had
+ the maximum sized qname (e.g., a 255-octet qname). Since the
+ message already contains some qname, calculate the difference."""
+ return 255 - len(qname.to_wire())
+
+def do_query(qname, qtype):
+ """Given a qname and a qtype, use the dedicated socket to query a
+ namesrver with DO=1 and BUFSIZE=4096, then calculate the full
+ response size, the minimum size, and the additional qname
+ delta."""
+
+ q = dns.message.make_query(qname, qtype)
+ q.want_dnssec(True)
+ q.payload = 4096
+ wire = q.to_wire()
+
+ # send the query
+ s.send(wire)
+ # wait for the response (no timeouts yet)
+ resp_wire = s.recv(65535)
+ # calculate the base length.
+ resp_len = len(resp_wire)
+ # parse the response
+ resp = dns.message.from_wire(resp_wire)
+ # calculate our minimum size
+ minimum = min_size(resp)
+ # calculate our maximum size
+ plus_qname = additional_qname_length(q.question[0].name)
+ return (resp, resp_len, minimum, plus_qname)
+
+class Histogram:
+ """A class representing a basic histogram for DNS response
+ sizes."""
+
+ def __init__(self, factor):
+ self.hist_ = {}
+ self.labels_ = {}
+ self.factor_ = factor
+ self.minimum_ = 0
+ self.maximum_ = 0
+ self.minlabel_ = ""
+ self.maxlabel_ = ""
+
+ def add(self, length, label):
+ if label in self.labels_: return
+ self.labels_[label] = 1
+
+ index = int(length / self.factor_)
+ if index not in self.hist_:
+ self.hist_[index] = 0
+
+ self.hist_[index] += 1
+
+ if self.minimum_ == 0 or length < self.minimum_:
+ self.minimum_ = length
+ self.minlabel_ = label
+ if length > self.maximum_:
+ self.maximum_ = length
+ self.maxlabel_ = label
+
+ def to_text(self):
+ res = "range [%d - %d] min: %s, max: %s\n" % \
+ (self.minimum_, self.maximum_, self.minlabel_, self.maxlabel_)
+ keys = self.hist_.keys()
+ keys.sort()
+ for index in keys:
+ rangestr = "[%d - %d)" % (index * self.factor_,
+ (index + 1) * self.factor_)
+ res += "%20s : %d\n" % (rangestr, self.hist_[index])
+ return res
+
+def process_zone(zone, skip_addr=True):
+ last_name = ""; last_type = 0
+
+ for name, rd in zone.iterate_rdatasets():
+ n = name.to_text()
+ t = rd.rdtype
+
+ # skip queries to the glue records; they are redundant with
+ # the NS queries.
+ if skip_addr and (t == dns.rdatatype.A or t == dns.rdatatype.AAAA):
+ continue
+ label = n + "/" + dns.rdatatype.to_text(t)
+
+ (response, resp_len, minimum, plus_qname) = do_query(n, t)
+ print "%d\t%d\t%d\t%s" % (resp_len, minimum, plus_qname, label)
+
+ # Populate the referral histograms
+ if t == dns.rdatatype.NS:
+ ref_hist.add(minimum + plus_qname, label)
+ max_ref_hist.add(resp_len + plus_qname, label)
+ else:
+ other_hist.add(resp_len, label)
+
+ if n != last_name:
+ label = n + "/ANY"
+ (response, resp_len, minimum, plus_qname) = \
+ do_query(n , dns.rdatatype.ANY)
+ print "%d\t%d\t%d\t%s" % (resp_len, minimum, plus_qname, label)
+ other_hist.add(resp_len, label)
+
+ label = n + "_/A"
+ (response, resp_len, minimum, plus_qname) = \
+ do_query(n + "_", dns.rdatatype.A)
+ print "%d\t%d\t%d\t%s" % (resp_len, minimum, plus_qname, label)
+ # populate the nxdomain histogram
+ nx_hist.add(resp_len + plus_qname, label)
+
+ last_name = n
+
+if __name__ == "__main__":
+
+ port = 53
+ host = "127.0.0.1"
+
+ options, args = getopt.getopt(sys.argv[1:], "p:s:")
+ for k,v in options:
+ if k == '-p':
+ port = int(v)
+ elif k == '-s':
+ host = v
+
+ # create a query socket
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ # connect it to our server
+ s.connect((host, port))
+
+ ref_hist = Histogram(64)
+ max_ref_hist = Histogram(64)
+ nx_hist = Histogram(64)
+ other_hist = Histogram(64)
+
+ for zonefile in args:
+ origin=None
+ skip = True
+ if ':' in zonefile:
+ zonefile, origin = zonefile.split(':', 1)
+ if origin and ':' in origin:
+ origin, skip = origin.split(':', 1)
+ skip = len(skip) == 0
+ if origin and origin == '.':
+ rel = True
+ else:
+ rel = False
+
+ zone = dns.zone.from_file(zonefile, origin=origin, relativize=rel)
+
+ process_zone(zone, skip)
+
+ s.close()
+
+ print >> sys.stderr, "Referral sizes (Maximum truncation sizes):"
+ print >> sys.stderr, ref_hist.to_text()
+
+ print >> sys.stderr, "Referral sizes (Maximum overall sizes):"
+ print >> sys.stderr, max_ref_hist.to_text()
+
+ print >> sys.stderr, "NXDOMAIN sizes (Maximum truncation sizes):"
+ print >> sys.stderr, nx_hist.to_text()
+
+ print >> sys.stderr, "Other response sizes (Full response sizes):"
+ print >> sys.stderr, other_hist.to_text()