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