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