]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.recursor-dnssec/test_Protobuf.py
rec: Export the server ID in protobuf messages
[thirdparty/pdns.git] / regression-tests.recursor-dnssec / test_Protobuf.py
1 import dns
2 import dnsmessage_pb2
3 import os
4 import socket
5 import struct
6 import sys
7 import threading
8 import time
9
10 # Python2/3 compatibility hacks
11 if sys.version_info[0] == 2:
12 from Queue import Queue
13 range = xrange
14 else:
15 from queue import Queue
16 range = range # allow re-export of the builtin name
17
18 from recursortests import RecursorTest
19
20 protobufQueue = Queue()
21 protobufServerPort = 4243
22
23 def ProtobufConnectionHandler(queue, conn):
24 data = None
25 while True:
26 data = conn.recv(2)
27 if not data:
28 break
29 (datalen,) = struct.unpack("!H", data)
30 data = conn.recv(datalen)
31 if not data:
32 break
33
34 queue.put(data, True, timeout=2.0)
35
36 conn.close()
37
38 def ProtobufListener(port):
39 global protobufQueue
40 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
41 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
42 try:
43 sock.bind(("127.0.0.1", port))
44 except socket.error as e:
45 print("Error binding in the protobuf listener: %s" % str(e))
46 sys.exit(1)
47
48 sock.listen(100)
49 while True:
50 try:
51 (conn, _) = sock.accept()
52 thread = threading.Thread(name='Connection Handler',
53 target=ProtobufConnectionHandler,
54 args=[protobufQueue, conn])
55 thread.setDaemon(True)
56 thread.start()
57
58 except socket.error as e:
59 print('Error in protobuf socket: %s' % str(e))
60
61 sock.close()
62
63
64 protobufListener = threading.Thread(name='Protobuf Listener', target=ProtobufListener, args=[protobufServerPort])
65 protobufListener.setDaemon(True)
66 protobufListener.start()
67
68 class TestRecursorProtobuf(RecursorTest):
69
70 global protobufServerPort
71 _lua_config_file = """
72 protobufServer("127.0.0.1:%d")
73 """ % (protobufServerPort)
74
75
76 def getFirstProtobufMessage(self, retries=1, waitTime=1):
77 global protobufQueue
78 failed = 0
79
80 while protobufQueue.empty:
81 if failed >= retries:
82 break
83
84 failed = failed + 1
85 time.sleep(waitTime)
86
87 self.assertFalse(protobufQueue.empty())
88 data = protobufQueue.get(False)
89 self.assertTrue(data)
90 msg = dnsmessage_pb2.PBDNSMessage()
91 msg.ParseFromString(data)
92 return msg
93
94 def checkNoRemainingMessage(self):
95 global protobufQueue
96 self.assertTrue(protobufQueue.empty())
97
98 def checkProtobufBase(self, msg, protocol, query, initiator, normalQueryResponse=True, expectedECS=None):
99 self.assertTrue(msg)
100 self.assertTrue(msg.HasField('timeSec'))
101 self.assertTrue(msg.HasField('socketFamily'))
102 self.assertEquals(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET)
103 self.assertTrue(msg.HasField('from'))
104 fromvalue = getattr(msg, 'from')
105 self.assertEquals(socket.inet_ntop(socket.AF_INET, fromvalue), initiator)
106 self.assertTrue(msg.HasField('socketProtocol'))
107 self.assertEquals(msg.socketProtocol, protocol)
108 self.assertTrue(msg.HasField('messageId'))
109 self.assertTrue(msg.HasField('serverIdentity'))
110 self.assertTrue(msg.HasField('id'))
111 self.assertEquals(msg.id, query.id)
112 self.assertTrue(msg.HasField('inBytes'))
113 if normalQueryResponse:
114 # compare inBytes with length of query/response
115 self.assertEquals(msg.inBytes, len(query.to_wire()))
116 if expectedECS is not None:
117 self.assertTrue(msg.HasField('originalRequestorSubnet'))
118 # v4 only for now
119 self.assertEquals(len(msg.originalRequestorSubnet), 4)
120 self.assertEquals(socket.inet_ntop(socket.AF_INET, msg.originalRequestorSubnet), '127.0.0.1')
121
122 def checkOutgoingProtobufBase(self, msg, protocol, query, initiator):
123 self.assertTrue(msg)
124 self.assertTrue(msg.HasField('timeSec'))
125 self.assertTrue(msg.HasField('socketFamily'))
126 self.assertEquals(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET)
127 self.assertTrue(msg.HasField('socketProtocol'))
128 self.assertEquals(msg.socketProtocol, protocol)
129 self.assertTrue(msg.HasField('messageId'))
130 self.assertTrue(msg.HasField('serverIdentity'))
131 self.assertTrue(msg.HasField('id'))
132 self.assertNotEquals(msg.id, query.id)
133 self.assertTrue(msg.HasField('inBytes'))
134 # compare inBytes with length of query/response
135 self.assertEquals(msg.inBytes, len(query.to_wire()))
136
137 def checkProtobufQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1'):
138 self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSQueryType)
139 self.checkProtobufBase(msg, protocol, query, initiator)
140 # dnsdist doesn't fill the responder field for responses
141 # because it doesn't keep the information around.
142 self.assertTrue(msg.HasField('to'))
143 self.assertEquals(socket.inet_ntop(socket.AF_INET, msg.to), '127.0.0.1')
144 self.assertTrue(msg.HasField('question'))
145 self.assertTrue(msg.question.HasField('qClass'))
146 self.assertEquals(msg.question.qClass, qclass)
147 self.assertTrue(msg.question.HasField('qType'))
148 self.assertEquals(msg.question.qClass, qtype)
149 self.assertTrue(msg.question.HasField('qName'))
150 self.assertEquals(msg.question.qName, qname)
151
152 def checkProtobufResponse(self, msg, protocol, response, initiator='127.0.0.1'):
153 self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSResponseType)
154 self.checkProtobufBase(msg, protocol, response, initiator)
155 self.assertTrue(msg.HasField('response'))
156 self.assertTrue(msg.response.HasField('queryTimeSec'))
157
158 def checkProtobufResponseRecord(self, record, rclass, rtype, rname, rttl):
159 self.assertTrue(record.HasField('class'))
160 self.assertEquals(getattr(record, 'class'), rclass)
161 self.assertTrue(record.HasField('type'))
162 self.assertEquals(record.type, rtype)
163 self.assertTrue(record.HasField('name'))
164 self.assertEquals(record.name, rname)
165 self.assertTrue(record.HasField('ttl'))
166 self.assertEquals(record.ttl, rttl)
167 self.assertTrue(record.HasField('rdata'))
168
169 def checkProtobufPolicy(self, msg, policyType, reason):
170 self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSResponseType)
171 self.assertTrue(msg.response.HasField('appliedPolicyType'))
172 self.assertTrue(msg.response.HasField('appliedPolicy'))
173 self.assertEquals(msg.response.appliedPolicy, reason)
174 self.assertEquals(msg.response.appliedPolicyType, policyType)
175
176 def checkProtobufTags(self, msg, tags):
177 self.assertEquals(len(msg.response.tags), len(tags))
178 for tag in msg.response.tags:
179 self.assertTrue(tag in tags)
180
181 def checkProtobufOutgoingQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1'):
182 self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSOutgoingQueryType)
183 self.checkOutgoingProtobufBase(msg, protocol, query, initiator)
184 self.assertTrue(msg.HasField('to'))
185 self.assertTrue(msg.HasField('question'))
186 self.assertTrue(msg.question.HasField('qClass'))
187 self.assertEquals(msg.question.qClass, qclass)
188 self.assertTrue(msg.question.HasField('qType'))
189 self.assertEquals(msg.question.qClass, qtype)
190 self.assertTrue(msg.question.HasField('qName'))
191 self.assertEquals(msg.question.qName, qname)
192
193 def checkProtobufIncomingResponse(self, msg, protocol, response, initiator='127.0.0.1'):
194 self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSIncomingResponseType)
195 self.checkOutgoingProtobufBase(msg, protocol, response, initiator)
196 self.assertTrue(msg.HasField('response'))
197 self.assertTrue(msg.response.HasField('queryTimeSec'))
198
199 @classmethod
200 def setUpClass(cls):
201
202 global protobufListener
203 global protobufServerPort
204 global ProtobufListener
205 if protobufListener is None or not protobufListener.isAlive():
206 protobufListener = threading.Thread(name='Protobuf Listener', target=ProtobufListener, args=[protobufServerPort])
207 protobufListener.setDaemon(True)
208 protobufListener.start()
209
210 cls.setUpSockets()
211
212 cls.startResponders()
213
214 confdir = os.path.join('configs', cls._confdir)
215 cls.createConfigDir(confdir)
216
217 cls.generateRecursorConfig(confdir)
218 cls.startRecursor(confdir, cls._recursorPort)
219
220 def setUp(self):
221 # Make sure the queue is empty, in case
222 # a previous test failed
223 global protobufQueue
224 while not protobufQueue.empty():
225 protobufQueue.get(False)
226
227 @classmethod
228 def generateRecursorConfig(cls, confdir):
229 authzonepath = os.path.join(confdir, 'example.zone')
230 with open(authzonepath, 'w') as authzone:
231 authzone.write("""$ORIGIN example.
232 @ 3600 IN SOA {soa}
233 a 3600 IN A 192.0.2.42
234 tagged 3600 IN A 192.0.2.84
235 query-selected 3600 IN A 192.0.2.84
236 answer-selected 3600 IN A 192.0.2.84
237 """.format(soa=cls._SOA))
238 super(TestRecursorProtobuf, cls).generateRecursorConfig(confdir)
239
240 @classmethod
241 def tearDownClass(cls):
242 cls.tearDownRecursor()
243
244 class ProtobufDefaultTest(TestRecursorProtobuf):
245 """
246 This test makes sure that we correctly export queries and response over protobuf.
247 """
248
249 _confdir = 'ProtobufDefault'
250 _config_template = """
251 auth-zones=example=configs/%s/example.zone""" % _confdir
252
253 def testA(self):
254 name = 'a.example.'
255 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
256 query = dns.message.make_query(name, 'A', want_dnssec=True)
257 query.flags |= dns.flags.CD
258 res = self.sendUDPQuery(query)
259 self.assertRRsetInAnswer(res, expected)
260
261 # check the protobuf messages corresponding to the UDP query and answer
262 msg = self.getFirstProtobufMessage()
263 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
264 # then the response
265 msg = self.getFirstProtobufMessage()
266 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
267 self.assertEquals(len(msg.response.rrs), 1)
268 rr = msg.response.rrs[0]
269 # we have max-cache-ttl set to 15
270 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
271 self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
272 self.checkNoRemainingMessage()
273
274 class OutgoingProtobufDefaultTest(TestRecursorProtobuf):
275 """
276 This test makes sure that we correctly export outgoing queries over protobuf.
277 It must be improved and setup env so we can check for incoming responses, but makes sure for now
278 that the recursor at least connects to the protobuf server.
279 """
280
281 _confdir = 'OutgoingProtobufDefault'
282 _config_template = """
283 auth-zones=example=configs/%s/example.zone""" % _confdir
284 _lua_config_file = """
285 outgoingProtobufServer("127.0.0.1:%d")
286 """ % (protobufServerPort)
287
288 def testA(self):
289 name = 'www.example.org.'
290 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
291 query = dns.message.make_query(name, 'A', want_dnssec=True)
292 query.flags |= dns.flags.RD
293 res = self.sendUDPQuery(query)
294
295 # check the protobuf messages corresponding to the UDP query and answer
296 msg = self.getFirstProtobufMessage()
297 self.checkProtobufOutgoingQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
298 # # then the response
299 # msg = self.getFirstProtobufMessage()
300 # self.checkProtobufIncomingResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
301 self.checkNoRemainingMessage()
302
303 class ProtobufMasksTest(TestRecursorProtobuf):
304 """
305 This test makes sure that we correctly export queries and response over protobuf, respecting the configured initiator masking.
306 """
307
308 _confdir = 'ProtobufMasks'
309 _config_template = """
310 auth-zones=example=configs/%s/example.zone""" % _confdir
311 global protobufServerPort
312 _protobufMaskV4 = 4
313 _protobufMaskV6 = 128
314 _lua_config_file = """
315 protobufServer("127.0.0.1:%d")
316 setProtobufMasks(%d, %d)
317 """ % (protobufServerPort, _protobufMaskV4, _protobufMaskV6)
318
319 def testA(self):
320 name = 'a.example.'
321 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
322 query = dns.message.make_query(name, 'A', want_dnssec=True)
323 query.flags |= dns.flags.CD
324 res = self.sendUDPQuery(query)
325 self.assertRRsetInAnswer(res, expected)
326
327 # check the protobuf messages corresponding to the UDP query and answer
328 # but first let the protobuf messages the time to get there
329 msg = self.getFirstProtobufMessage()
330 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '112.0.0.0')
331 # then the response
332 msg = self.getFirstProtobufMessage()
333 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '112.0.0.0')
334 self.assertEquals(len(msg.response.rrs), 1)
335 rr = msg.response.rrs[0]
336 # we have max-cache-ttl set to 15
337 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
338 self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
339 self.checkNoRemainingMessage()
340
341 class ProtobufQueriesOnlyTest(TestRecursorProtobuf):
342 """
343 This test makes sure that we correctly export queries but not responses over protobuf.
344 """
345
346 _confdir = 'ProtobufQueriesOnly'
347 _config_template = """
348 auth-zones=example=configs/%s/example.zone""" % _confdir
349 global protobufServerPort
350 _lua_config_file = """
351 protobufServer("127.0.0.1:%d", { logQueries=true, logResponses=false } )
352 """ % (protobufServerPort)
353
354 def testA(self):
355 name = 'a.example.'
356 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
357 query = dns.message.make_query(name, 'A', want_dnssec=True)
358 query.flags |= dns.flags.CD
359 res = self.sendUDPQuery(query)
360 self.assertRRsetInAnswer(res, expected)
361
362 # check the protobuf message corresponding to the UDP query
363 msg = self.getFirstProtobufMessage()
364 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
365 # no response
366 self.checkNoRemainingMessage()
367
368 class ProtobufResponsesOnlyTest(TestRecursorProtobuf):
369 """
370 This test makes sure that we correctly export responses but not queries over protobuf.
371 """
372
373 _confdir = 'ProtobufResponsesOnly'
374 _config_template = """
375 auth-zones=example=configs/%s/example.zone""" % _confdir
376 global protobufServerPort
377 _lua_config_file = """
378 protobufServer("127.0.0.1:%d", { logQueries=false, logResponses=true } )
379 """ % (protobufServerPort)
380
381 def testA(self):
382 name = 'a.example.'
383 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
384 query = dns.message.make_query(name, 'A', want_dnssec=True)
385 query.flags |= dns.flags.CD
386 res = self.sendUDPQuery(query)
387 self.assertRRsetInAnswer(res, expected)
388
389 # check the protobuf message corresponding to the UDP response
390 msg = self.getFirstProtobufMessage()
391 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
392 self.assertEquals(len(msg.response.rrs), 1)
393 rr = msg.response.rrs[0]
394 # we have max-cache-ttl set to 15
395 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
396 self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
397 # nothing else in the queue
398 self.checkNoRemainingMessage()
399
400 class ProtobufTaggedOnlyTest(TestRecursorProtobuf):
401 """
402 This test makes sure that we correctly export queries and responses but only if they have been tagged.
403 """
404
405 _confdir = 'ProtobufTaggedOnly'
406 _config_template = """
407 auth-zones=example=configs/%s/example.zone""" % _confdir
408 global protobufServerPort
409 _lua_config_file = """
410 protobufServer("127.0.0.1:%d", { logQueries=true, logResponses=true, taggedOnly=true } )
411 """ % (protobufServerPort)
412 _tags = ['tag1', 'tag2']
413 _tag_from_gettag = 'tag-from-gettag'
414 _lua_dns_script_file = """
415 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
416 if qname:equal('tagged.example.') then
417 return 0, { '%s' }
418 end
419 return 0
420 end
421 function preresolve(dq)
422 if dq.qname:equal('tagged.example.') then
423 dq:addPolicyTag('%s')
424 dq:addPolicyTag('%s')
425 end
426 return false
427 end
428 """ % (_tag_from_gettag, _tags[0], _tags[1])
429
430 def testA(self):
431 name = 'a.example.'
432 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
433 query = dns.message.make_query(name, 'A', want_dnssec=True)
434 query.flags |= dns.flags.CD
435 res = self.sendUDPQuery(query)
436 self.assertRRsetInAnswer(res, expected)
437
438 # check the protobuf message corresponding to the UDP response
439 # the first query and answer are not tagged, so there is nothing in the queue
440 time.sleep(1)
441 self.checkNoRemainingMessage()
442
443 def testTagged(self):
444 name = 'tagged.example.'
445 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
446 query = dns.message.make_query(name, 'A', want_dnssec=True)
447 query.flags |= dns.flags.CD
448 res = self.sendUDPQuery(query)
449 self.assertRRsetInAnswer(res, expected)
450
451 # check the protobuf messages corresponding to the UDP query and answer
452 msg = self.getFirstProtobufMessage()
453 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
454 self.checkProtobufTags(msg, [self._tag_from_gettag])
455 # then the response
456 msg = self.getFirstProtobufMessage()
457 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
458 self.assertEquals(len(msg.response.rrs), 1)
459 rr = msg.response.rrs[0]
460 # we have max-cache-ttl set to 15
461 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
462 self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
463 tags = [self._tag_from_gettag] + self._tags
464 self.checkProtobufTags(msg, tags)
465 self.checkNoRemainingMessage()
466
467 class ProtobufSelectedFromLuaTest(TestRecursorProtobuf):
468 """
469 This test makes sure that we correctly export queries and responses but only if they have been selected from Lua.
470 """
471
472 _confdir = 'ProtobufSelectedFromLua'
473 _config_template = """
474 auth-zones=example=configs/%s/example.zone""" % _confdir
475 global protobufServerPort
476 _lua_config_file = """
477 protobufServer("127.0.0.1:%d", { logQueries=false, logResponses=false } )
478 """ % (protobufServerPort)
479 _lua_dns_script_file = """
480 local ffi = require("ffi")
481
482 ffi.cdef[[
483 typedef struct pdns_ffi_param pdns_ffi_param_t;
484
485 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
486 void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery);
487 ]]
488
489 function gettag_ffi(obj)
490 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
491 if qname == 'query-selected.example' then
492 ffi.C.pdns_ffi_param_set_log_query(obj, true)
493 end
494 return 0
495 end
496
497 function preresolve(dq)
498 if dq.qname:equal('answer-selected.example.') then
499 dq.logResponse = true
500 end
501 return false
502 end
503 """
504
505 def testA(self):
506 name = 'a.example.'
507 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
508 query = dns.message.make_query(name, 'A', want_dnssec=True)
509 query.flags |= dns.flags.CD
510 res = self.sendUDPQuery(query)
511 self.assertRRsetInAnswer(res, expected)
512
513 # check the protobuf message corresponding to the UDP response
514 # the first query and answer are not selected, so there is nothing in the queue
515 self.checkNoRemainingMessage()
516
517 def testQuerySelected(self):
518 name = 'query-selected.example.'
519 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
520 query = dns.message.make_query(name, 'A', want_dnssec=True)
521 query.flags |= dns.flags.CD
522 res = self.sendUDPQuery(query)
523 self.assertRRsetInAnswer(res, expected)
524
525 # check the protobuf messages corresponding to the UDP query
526 msg = self.getFirstProtobufMessage()
527 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
528 # there should be no response
529 self.checkNoRemainingMessage()
530
531 def testResponseSelected(self):
532 name = 'answer-selected.example.'
533 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
534 query = dns.message.make_query(name, 'A', want_dnssec=True)
535 query.flags |= dns.flags.CD
536 res = self.sendUDPQuery(query)
537 self.assertRRsetInAnswer(res, expected)
538
539 # check the protobuf messages corresponding to the UDP response
540 msg = self.getFirstProtobufMessage()
541 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
542 self.assertEquals(len(msg.response.rrs), 1)
543 rr = msg.response.rrs[0]
544 # we have max-cache-ttl set to 15
545 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
546 self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
547 self.checkNoRemainingMessage()