move zones into zones; move scripts into bin
[root_zone_test.git] / bin / response_sizes.py
diff --git a/bin/response_sizes.py b/bin/response_sizes.py
new file mode 100755 (executable)
index 0000000..b3d1189
--- /dev/null
@@ -0,0 +1,188 @@
+#! /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()