minor edits to results text; current state of named.conf
[root_zone_test.git] / bin / response_sizes.py
1 #! /usr/bin/python
2
3 import sys, socket, getopt
4
5 import dns.zone
6 import dns.query
7 import dns.message
8 import dns.rdatatype
9 import dns.rdataclass
10
11 def min_size(message):
12     """Calculate the minimum size this response can be without setting
13     the TC (truncation bit).  This is calculated by removing all of
14     the additional section save the OPT pseudo-RR."""
15     
16     additional = message.additional
17     # note that in dns.message.Message, the OPT RR is not stored in
18     # the additional section (where it was).  Instead it is parsed
19     # into various member variables, so we don't need to do anything
20     # special to preserve it.
21     message.additional = []
22     wire = message.to_wire()
23     message.additional = additional
24     return len(wire)
25
26 def additional_qname_length(qname):
27     """Calculate the additional size a message might take if it had
28     the maximum sized qname (e.g., a 255-octet qname).  Since the
29     message already contains some qname, calculate the difference."""
30     return 255 - len(qname.to_wire())
31
32 def do_query(qname, qtype):
33     """Given a qname and a qtype, use the dedicated socket to query a
34     namesrver with DO=1 and BUFSIZE=4096, then calculate the full
35     response size, the minimum size, and the additional qname
36     delta."""
37
38     q = dns.message.make_query(qname, qtype)
39     q.want_dnssec(True)
40     q.payload = 4096
41     wire = q.to_wire()
42
43     # send the query
44     s.send(wire)
45     # wait for the response (no timeouts yet)
46     resp_wire = s.recv(65535)
47     # calculate the base length.
48     resp_len = len(resp_wire)
49     # parse the response
50     resp = dns.message.from_wire(resp_wire)
51     # calculate our minimum size
52     minimum = min_size(resp)
53     # calculate our maximum size
54     plus_qname = additional_qname_length(q.question[0].name)
55     return (resp, resp_len, minimum, plus_qname)
56
57 class Histogram:
58     """A class representing a basic histogram for DNS response
59     sizes."""
60
61     def __init__(self, factor):
62         self.hist_ = {}
63         self.labels_ = {}
64         self.factor_ = factor
65         self.minimum_ = 0
66         self.maximum_ = 0
67         self.minlabel_ = ""
68         self.maxlabel_ = ""
69
70     def add(self, length, label):
71         if label in self.labels_: return
72         self.labels_[label] = 1
73
74         index = int(length / self.factor_)
75         if index not in self.hist_:
76             self.hist_[index] = 0
77
78         self.hist_[index] += 1
79
80         if self.minimum_ == 0 or length < self.minimum_:
81             self.minimum_ = length
82             self.minlabel_ = label
83         if length > self.maximum_:
84             self.maximum_ = length
85             self.maxlabel_ = label
86
87     def to_text(self):
88         res = "range [%d - %d] min: %s, max: %s\n" % \
89             (self.minimum_, self.maximum_, self.minlabel_, self.maxlabel_)
90         keys = self.hist_.keys()
91         keys.sort()
92         for index in keys:
93             rangestr = "[%d - %d)" % (index * self.factor_, 
94                                       (index + 1) * self.factor_)
95             res += "%20s : %d\n" % (rangestr, self.hist_[index])
96         return res
97
98 def process_zone(zone, skip_addr=True):
99     last_name = ""; last_type = 0
100
101     for name, rd in zone.iterate_rdatasets():
102         n = name.to_text()
103         t = rd.rdtype
104         
105         # skip queries to the glue records; they are redundant with
106         # the NS queries.
107         if skip_addr and (t == dns.rdatatype.A or t == dns.rdatatype.AAAA):
108             continue
109         label = n + "/" + dns.rdatatype.to_text(t)
110  
111         (response, resp_len, minimum, plus_qname) = do_query(n, t)
112         print "%d\t%d\t%d\t%s" % (resp_len, minimum, plus_qname, label)
113
114         # Populate the referral histograms
115         if t == dns.rdatatype.NS:
116             ref_hist.add(minimum + plus_qname, label)
117             max_ref_hist.add(resp_len + plus_qname, label)
118         else:
119             other_hist.add(resp_len, label)
120
121         if n != last_name:
122             label = n + "/ANY"
123             (response, resp_len, minimum, plus_qname) = \
124                 do_query(n , dns.rdatatype.ANY)
125             print "%d\t%d\t%d\t%s" % (resp_len, minimum, plus_qname, label)
126             other_hist.add(resp_len, label)
127
128             label = n + "_/A"
129             (response, resp_len, minimum, plus_qname) = \
130                 do_query(n + "_", dns.rdatatype.A)
131             print "%d\t%d\t%d\t%s" % (resp_len, minimum, plus_qname, label)
132             # populate the nxdomain histogram
133             nx_hist.add(resp_len + plus_qname, label)
134
135             last_name = n
136
137 if __name__ == "__main__":
138
139     port = 53
140     host = "127.0.0.1"
141
142     options, args = getopt.getopt(sys.argv[1:], "p:s:")
143     for k,v in options:
144         if k == '-p':
145             port = int(v)
146         elif k == '-s':
147             host = v
148             
149     # create a query socket
150     s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
151     # connect it to our server
152     s.connect((host, port))
153
154     ref_hist = Histogram(64)
155     max_ref_hist = Histogram(64)
156     nx_hist = Histogram(64)
157     other_hist = Histogram(64)
158
159     for zonefile in args:
160         origin=None
161         skip = True
162         if ':' in zonefile:
163             zonefile, origin = zonefile.split(':', 1)
164         if origin and ':' in origin:
165             origin, skip = origin.split(':', 1)
166             skip = len(skip) == 0
167         if origin and origin == '.': 
168             rel = True
169         else: 
170             rel = False
171
172         zone = dns.zone.from_file(zonefile, origin=origin, relativize=rel)
173         
174         process_zone(zone, skip)
175
176     s.close()
177
178     print >> sys.stderr, "Referral sizes (Maximum truncation sizes):"
179     print >> sys.stderr, ref_hist.to_text()
180
181     print >> sys.stderr, "Referral sizes (Maximum overall sizes):"
182     print >> sys.stderr, max_ref_hist.to_text()
183
184     print >> sys.stderr, "NXDOMAIN sizes (Maximum overall sizes):"
185     print >> sys.stderr, nx_hist.to_text()
186
187     print >> sys.stderr, "Other response sizes (Full response sizes):"
188     print >> sys.stderr, other_hist.to_text()