]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.recursor-dnssec/test_Protobuf.py
Avoid a race in test code between processing the first query and the priming code.
[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 import clientsubnetoption
10
11 # Python2/3 compatibility hacks
12 try:
13 from queue import Queue
14 except ImportError:
15 from Queue import Queue
16
17 try:
18 range = xrange
19 except NameError:
20 pass
21
22 from recursortests import RecursorTest
23
24 def ProtobufConnectionHandler(queue, conn):
25 data = None
26 while True:
27 data = conn.recv(2)
28 if not data:
29 break
30 (datalen,) = struct.unpack("!H", data)
31 data = conn.recv(datalen)
32 if not data:
33 break
34
35 queue.put(data, True, timeout=2.0)
36
37 conn.close()
38
39 def ProtobufListener(queue, port):
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=[queue, 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 class ProtobufServerParams:
65 def __init__(self, port):
66 self.queue = Queue()
67 self.port = port
68
69 protobufServersParameters = [ProtobufServerParams(4243), ProtobufServerParams(4244)]
70 protobufListeners = []
71 for param in protobufServersParameters:
72 listener = threading.Thread(name='Protobuf Listener', target=ProtobufListener, args=[param.queue, param.port])
73 listener.setDaemon(True)
74 listener.start()
75 protobufListeners.append(listener)
76
77 class TestRecursorProtobuf(RecursorTest):
78
79 _lua_config_file = """
80 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
81 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
82
83 _auth_zones = {
84 '8': {'threads': 1,
85 'zones': ['ROOT']},
86 '9': {'threads': 1,
87 'zones': ['secure.example', 'islandofsecurity.example']},
88 '10': {'threads': 1,
89 'zones': ['example']},
90 '18': {'threads': 1,
91 'zones': ['example']}
92 }
93
94 def getFirstProtobufMessage(self, retries=1, waitTime=1):
95 msg = None
96
97 print("in getFirstProtobufMessage")
98 for param in protobufServersParameters:
99 print(param.port)
100 failed = 0
101
102 while param.queue.empty:
103 print(failed)
104 print(retries)
105 if failed >= retries:
106 break
107
108 failed = failed + 1
109 print("waiting")
110 time.sleep(waitTime)
111
112 self.assertFalse(param.queue.empty())
113 data = param.queue.get(False)
114 self.assertTrue(data)
115 oldmsg = msg
116 msg = dnsmessage_pb2.PBDNSMessage()
117 msg.ParseFromString(data)
118 if oldmsg is not None:
119 self.assertEqual(msg, oldmsg)
120
121 print(msg)
122 return msg
123
124 def emptyProtoBufQueue(self):
125 for param in protobufServersParameters:
126 while not param.queue.empty():
127 param.queue.get(False)
128
129 def checkNoRemainingMessage(self):
130 for param in protobufServersParameters:
131 self.assertTrue(param.queue.empty())
132
133 def checkProtobufBase(self, msg, protocol, query, initiator, normalQueryResponse=True, expectedECS=None, receivedSize=None):
134 self.assertTrue(msg)
135 self.assertTrue(msg.HasField('timeSec'))
136 self.assertTrue(msg.HasField('socketFamily'))
137 self.assertEqual(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET)
138 self.assertTrue(msg.HasField('from'))
139 fromvalue = getattr(msg, 'from')
140 self.assertEqual(socket.inet_ntop(socket.AF_INET, fromvalue), initiator)
141 self.assertTrue(msg.HasField('socketProtocol'))
142 self.assertEqual(msg.socketProtocol, protocol)
143 self.assertTrue(msg.HasField('messageId'))
144 self.assertTrue(msg.HasField('serverIdentity'))
145 self.assertTrue(msg.HasField('id'))
146 self.assertEqual(msg.id, query.id)
147 self.assertTrue(msg.HasField('inBytes'))
148 if normalQueryResponse:
149 # compare inBytes with length of query/response
150 # Note that for responses, the size we received might differ
151 # because dnspython might compress labels differently from
152 # the recursor
153 if receivedSize:
154 self.assertEqual(msg.inBytes, receivedSize)
155 else:
156 self.assertEqual(msg.inBytes, len(query.to_wire()))
157 if expectedECS is not None:
158 self.assertTrue(msg.HasField('originalRequestorSubnet'))
159 # v4 only for now
160 self.assertEqual(len(msg.originalRequestorSubnet), 4)
161 self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.originalRequestorSubnet), '127.0.0.1')
162
163 def checkOutgoingProtobufBase(self, msg, protocol, query, initiator, length=None, expectedECS=None):
164 self.assertTrue(msg)
165 self.assertTrue(msg.HasField('timeSec'))
166 self.assertTrue(msg.HasField('socketFamily'))
167 self.assertEqual(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET)
168 self.assertTrue(msg.HasField('socketProtocol'))
169 self.assertEqual(msg.socketProtocol, protocol)
170 self.assertTrue(msg.HasField('messageId'))
171 self.assertTrue(msg.HasField('serverIdentity'))
172 self.assertTrue(msg.HasField('id'))
173 self.assertNotEqual(msg.id, query.id)
174 self.assertTrue(msg.HasField('inBytes'))
175 if length is not None:
176 self.assertEqual(msg.inBytes, length)
177 else:
178 # compare inBytes with length of query/response
179 self.assertEqual(msg.inBytes, len(query.to_wire()))
180 if expectedECS is not None:
181 self.assertTrue(msg.HasField('originalRequestorSubnet'))
182 # v4 only for now
183 self.assertEqual(len(msg.originalRequestorSubnet), 4)
184 self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.originalRequestorSubnet), expectedECS)
185
186 def checkProtobufQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1', to='127.0.0.1'):
187 self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSQueryType)
188 self.checkProtobufBase(msg, protocol, query, initiator)
189 # dnsdist doesn't fill the responder field for responses
190 # because it doesn't keep the information around.
191 self.assertTrue(msg.HasField('to'))
192 self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.to), to)
193 self.assertTrue(msg.HasField('question'))
194 self.assertTrue(msg.question.HasField('qClass'))
195 self.assertEqual(msg.question.qClass, qclass)
196 self.assertTrue(msg.question.HasField('qType'))
197 self.assertEqual(msg.question.qClass, qtype)
198 self.assertTrue(msg.question.HasField('qName'))
199 self.assertEqual(msg.question.qName, qname)
200
201 def checkProtobufResponse(self, msg, protocol, response, initiator='127.0.0.1', receivedSize=None, vstate=dnsmessage_pb2.PBDNSMessage.VState.Indeterminate):
202 self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSResponseType)
203 self.checkProtobufBase(msg, protocol, response, initiator, receivedSize=receivedSize)
204 self.assertTrue(msg.HasField('response'))
205 self.assertTrue(msg.response.HasField('queryTimeSec'))
206 self.assertTrue(msg.response.HasField('validationState'))
207 self.assertEqual(msg.response.validationState, vstate)
208
209 def checkProtobufResponseRecord(self, record, rclass, rtype, rname, rttl, checkTTL=True):
210 self.assertTrue(record.HasField('class'))
211 self.assertEqual(getattr(record, 'class'), rclass)
212 self.assertTrue(record.HasField('type'))
213 self.assertEqual(record.type, rtype)
214 self.assertTrue(record.HasField('name'))
215 self.assertEqual(record.name, rname)
216 self.assertTrue(record.HasField('ttl'))
217 if checkTTL:
218 self.assertEqual(record.ttl, rttl)
219 self.assertTrue(record.HasField('rdata'))
220
221 def checkProtobufPolicy(self, msg, policyType, reason, trigger, hit, kind):
222 self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSResponseType)
223 self.assertTrue(msg.response.HasField('appliedPolicyType'))
224 self.assertTrue(msg.response.HasField('appliedPolicy'))
225 self.assertTrue(msg.response.HasField('appliedPolicyTrigger'))
226 self.assertTrue(msg.response.HasField('appliedPolicyHit'))
227 self.assertTrue(msg.response.HasField('appliedPolicyKind'))
228 self.assertEqual(msg.response.appliedPolicy, reason)
229 self.assertEqual(msg.response.appliedPolicyType, policyType)
230 self.assertEqual(msg.response.appliedPolicyTrigger, trigger)
231 self.assertEqual(msg.response.appliedPolicyHit, hit)
232 self.assertEqual(msg.response.appliedPolicyKind, kind)
233
234 def checkProtobufTags(self, msg, tags):
235 print(tags)
236 print('---')
237 print(msg.response.tags)
238 self.assertEqual(len(msg.response.tags), len(tags))
239 for tag in msg.response.tags:
240 self.assertTrue(tag in tags)
241
242 def checkProtobufMetas(self, msg, metas):
243 print(metas)
244 print('---')
245 print(msg.meta)
246 self.assertEqual(len(msg.meta), len(metas))
247 for m in msg.meta:
248 self.assertTrue(m.HasField('key'))
249 self.assertTrue(m.HasField('value'))
250 self.assertTrue(m.key in metas)
251 for i in m.value.intVal :
252 self.assertTrue(i in metas[m.key]['intVal'])
253 for s in m.value.stringVal :
254 self.assertTrue(s in metas[m.key]['stringVal'])
255
256 def checkProtobufOutgoingQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1', length=None, expectedECS=None):
257 self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSOutgoingQueryType)
258 self.checkOutgoingProtobufBase(msg, protocol, query, initiator, length=length, expectedECS=expectedECS)
259 self.assertTrue(msg.HasField('to'))
260 self.assertTrue(msg.HasField('question'))
261 self.assertTrue(msg.question.HasField('qClass'))
262 self.assertEqual(msg.question.qClass, qclass)
263 self.assertTrue(msg.question.HasField('qType'))
264 self.assertEqual(msg.question.qType, qtype)
265 self.assertTrue(msg.question.HasField('qName'))
266 self.assertEqual(msg.question.qName, qname)
267
268 def checkProtobufIncomingResponse(self, msg, protocol, response, initiator='127.0.0.1', length=None):
269 self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSIncomingResponseType)
270 self.checkOutgoingProtobufBase(msg, protocol, response, initiator, length=length)
271 self.assertTrue(msg.HasField('response'))
272 self.assertTrue(msg.response.HasField('rcode'))
273 self.assertTrue(msg.response.HasField('queryTimeSec'))
274
275 def checkProtobufIncomingNetworkErrorResponse(self, msg, protocol, response, initiator='127.0.0.1'):
276 self.checkProtobufIncomingResponse(msg, protocol, response, initiator, length=0)
277 self.assertEqual(msg.response.rcode, 65536)
278
279 def checkProtobufIdentity(self, msg, requestorId, deviceId, deviceName):
280 print(msg)
281 self.assertTrue((requestorId == '') == (not msg.HasField('requestorId')))
282 self.assertTrue((deviceId == b'') == (not msg.HasField('deviceId')))
283 self.assertTrue((deviceName == '') == (not msg.HasField('deviceName')))
284 self.assertEqual(msg.requestorId, requestorId)
285 self.assertEqual(msg.deviceId, deviceId)
286 self.assertEqual(msg.deviceName, deviceName)
287
288 def setUp(self):
289 super(TestRecursorProtobuf, self).setUp()
290 # Make sure the queue is empty, in case
291 # a previous test failed
292 self.emptyProtoBufQueue()
293 # wait long enough to be sure that the housekeeping has
294 # prime the root NS
295 time.sleep(1)
296
297 @classmethod
298 def generateRecursorConfig(cls, confdir):
299 authzonepath = os.path.join(confdir, 'example.zone')
300 with open(authzonepath, 'w') as authzone:
301 authzone.write("""$ORIGIN example.
302 @ 3600 IN SOA {soa}
303 a 3600 IN A 192.0.2.42
304 tagged 3600 IN A 192.0.2.84
305 meta 3600 IN A 192.0.2.85
306 query-selected 3600 IN A 192.0.2.84
307 answer-selected 3600 IN A 192.0.2.84
308 types 3600 IN A 192.0.2.84
309 types 3600 IN AAAA 2001:DB8::1
310 types 3600 IN TXT "Lorem ipsum dolor sit amet"
311 types 3600 IN MX 10 a.example.
312 types 3600 IN SPF "v=spf1 -all"
313 types 3600 IN SRV 10 20 443 a.example.
314 cname 3600 IN CNAME a.example.
315
316 """.format(soa=cls._SOA))
317 super(TestRecursorProtobuf, cls).generateRecursorConfig(confdir)
318
319
320 class ProtobufDefaultTest(TestRecursorProtobuf):
321 """
322 This test makes sure that we correctly export queries and response over protobuf.
323 """
324
325 _confdir = 'ProtobufDefault'
326 _config_template = """
327 auth-zones=example=configs/%s/example.zone""" % _confdir
328
329 def testA(self):
330 name = 'a.example.'
331 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
332 query = dns.message.make_query(name, 'A', want_dnssec=True)
333 query.flags |= dns.flags.CD
334 res = self.sendUDPQuery(query)
335
336 self.assertRRsetInAnswer(res, expected)
337
338 # check the protobuf messages corresponding to the UDP query and answer
339 msg = self.getFirstProtobufMessage()
340 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
341 # then the response
342 msg = self.getFirstProtobufMessage()
343 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1')
344 self.assertEqual(len(msg.response.rrs), 1)
345 rr = msg.response.rrs[0]
346 # we have max-cache-ttl set to 15
347 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
348 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
349 self.checkNoRemainingMessage()
350
351 def testCNAME(self):
352 name = 'cname.example.'
353 expectedCNAME = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'CNAME', 'a.example.')
354 expectedA = dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42')
355 query = dns.message.make_query(name, 'A', want_dnssec=True)
356 query.flags |= dns.flags.CD
357 raw = self.sendUDPQuery(query, decode=False)
358 res = dns.message.from_wire(raw)
359 self.assertRRsetInAnswer(res, expectedCNAME)
360 self.assertRRsetInAnswer(res, expectedA)
361
362 # check the protobuf messages corresponding to the UDP query and answer
363 # but first let the protobuf messages the time to get there
364 msg = self.getFirstProtobufMessage()
365 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
366 # then the response
367 msg = self.getFirstProtobufMessage()
368 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1', receivedSize=len(raw))
369 self.assertEqual(len(msg.response.rrs), 2)
370 rr = msg.response.rrs[0]
371 # we don't want to check the TTL for the A record, it has been cached by the previous test
372 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.CNAME, name, 15)
373 self.assertEqual(rr.rdata, b'a.example.')
374 rr = msg.response.rrs[1]
375 # we have max-cache-ttl set to 15
376 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, 'a.example.', 15, checkTTL=False)
377 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
378 self.checkNoRemainingMessage()
379
380 class ProtobufProxyMappingTest(TestRecursorProtobuf):
381 """
382 This test makes sure that we correctly export queries and response over protobuf with a proxyMapping
383 """
384
385 _confdir = 'ProtobufProxyMappingTest'
386 _config_template = """
387 auth-zones=example=configs/%s/example.zone
388 allow-from=3.4.5.0/24
389 """ % _confdir
390
391 _lua_config_file = """
392 addProxyMapping("127.0.0.1/24", "3.4.5.6:99")
393 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
394 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
395
396 def testA(self):
397 name = 'a.example.'
398 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
399 query = dns.message.make_query(name, 'A', want_dnssec=True)
400 query.flags |= dns.flags.CD
401 res = self.sendUDPQuery(query)
402
403 self.assertRRsetInAnswer(res, expected)
404
405 # check the protobuf messages corresponding to the UDP query and answer
406 msg = self.getFirstProtobufMessage()
407 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
408 # then the response
409 msg = self.getFirstProtobufMessage()
410 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1')
411 self.assertEqual(len(msg.response.rrs), 1)
412 rr = msg.response.rrs[0]
413 # we have max-cache-ttl set to 15
414 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
415 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
416 self.checkNoRemainingMessage()
417
418 class ProtobufProxyMappingLogMappedTest(TestRecursorProtobuf):
419 """
420 This test makes sure that we correctly export queries and response over protobuf.
421 """
422
423 _confdir = 'ProtobufProxyMappingLogMappedTest'
424 _config_template = """
425 auth-zones=example=configs/%s/example.zone
426 allow-from=3.4.5.0/0"
427 """ % _confdir
428
429 _lua_config_file = """
430 addProxyMapping("127.0.0.1/24", "3.4.5.6:99")
431 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logMappedFrom = true })
432 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
433
434 def testA(self):
435 name = 'a.example.'
436 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
437 query = dns.message.make_query(name, 'A', want_dnssec=True)
438 query.flags |= dns.flags.CD
439 res = self.sendUDPQuery(query)
440
441 self.assertRRsetInAnswer(res, expected)
442
443 # check the protobuf messages corresponding to the UDP query and answer
444 msg = self.getFirstProtobufMessage()
445 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '3.4.5.6')
446 # then the response
447 msg = self.getFirstProtobufMessage()
448 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '3.4.5.6')
449 self.assertEqual(len(msg.response.rrs), 1)
450 rr = msg.response.rrs[0]
451 # we have max-cache-ttl set to 15
452 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
453 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
454 self.checkNoRemainingMessage()
455
456 class ProtobufProxyTest(TestRecursorProtobuf):
457 """
458 This test makes sure that we correctly export addresses over protobuf when the proxy protocol is used.
459 """
460
461 _confdir = 'ProtobufProxy'
462 _config_template = """
463 auth-zones=example=configs/%s/example.zone
464 proxy-protocol-from=127.0.0.1/32
465 allow-from=127.0.0.1,6.6.6.6
466 """ % _confdir
467
468 def testA(self):
469 name = 'a.example.'
470 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
471 query = dns.message.make_query(name, 'A', want_dnssec=True)
472 query.flags |= dns.flags.CD
473 res = self.sendUDPQueryWithProxyProtocol(query, False, '6.6.6.6', '7.7.7.7', 666, 777)
474
475 self.assertRRsetInAnswer(res, expected)
476
477 # check the protobuf messages corresponding to the UDP query and answer
478 msg = self.getFirstProtobufMessage()
479 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '6.6.6.6', '7.7.7.7')
480 # then the response
481 msg = self.getFirstProtobufMessage()
482 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '6.6.6.6')
483 self.assertEqual(len(msg.response.rrs), 1)
484 rr = msg.response.rrs[0]
485 # we have max-cache-ttl set to 15
486 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
487 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
488 self.checkNoRemainingMessage()
489
490 class ProtobufProxyWithProxyByTableTest(TestRecursorProtobuf):
491 """
492 This test makes sure that we correctly export addresses over protobuf when the proxy protocol and a proxy table mapping is used
493 """
494
495 _confdir = 'ProtobufProxyWithProxyByTable'
496 _config_template = """
497 auth-zones=example=configs/%s/example.zone
498 proxy-protocol-from=127.0.0.1/32
499 allow-from=3.4.5.6
500 """ % _confdir
501
502 _lua_config_file = """
503 addProxyMapping("6.6.6.6/24", "3.4.5.6:99")
504 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
505 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
506
507 def testA(self):
508 name = 'a.example.'
509 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
510 query = dns.message.make_query(name, 'A', want_dnssec=True)
511 query.flags |= dns.flags.CD
512 res = self.sendUDPQueryWithProxyProtocol(query, False, '6.6.6.6', '7.7.7.7', 666, 777)
513
514 self.assertRRsetInAnswer(res, expected)
515
516 # check the protobuf messages corresponding to the UDP query and answer
517 msg = self.getFirstProtobufMessage()
518 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '6.6.6.6', '7.7.7.7')
519 # then the response
520 msg = self.getFirstProtobufMessage()
521 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '6.6.6.6')
522 self.assertEqual(len(msg.response.rrs), 1)
523 rr = msg.response.rrs[0]
524 # we have max-cache-ttl set to 15
525 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
526 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
527 self.checkNoRemainingMessage()
528
529 class ProtobufProxyWithProxyByTableLogMappedTest(TestRecursorProtobuf):
530 """
531 This test makes sure that we correctly export addresses over protobuf when the proxy protocol and a proxy table mapping is used
532 """
533
534 _confdir = 'ProtobufProxyWithProxyByTableLogMapped'
535 _config_template = """
536 auth-zones=example=configs/%s/example.zone
537 proxy-protocol-from=127.0.0.1/32
538 allow-from=3.4.5.6
539 """ % _confdir
540
541 _lua_config_file = """
542 addProxyMapping("6.6.6.6/24", "3.4.5.6:99")
543 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logMappedFrom = true })
544 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
545
546 def testA(self):
547 name = 'a.example.'
548 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
549 query = dns.message.make_query(name, 'A', want_dnssec=True)
550 query.flags |= dns.flags.CD
551 res = self.sendUDPQueryWithProxyProtocol(query, False, '6.6.6.6', '7.7.7.7', 666, 777)
552
553 self.assertRRsetInAnswer(res, expected)
554
555 # check the protobuf messages corresponding to the UDP query and answer
556 msg = self.getFirstProtobufMessage()
557 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '3.4.5.6', '7.7.7.7')
558 # then the response
559 msg = self.getFirstProtobufMessage()
560 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '3.4.5.6')
561 self.assertEqual(len(msg.response.rrs), 1)
562 rr = msg.response.rrs[0]
563 # we have max-cache-ttl set to 15
564 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
565 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
566 self.checkNoRemainingMessage()
567
568
569 class OutgoingProtobufDefaultTest(TestRecursorProtobuf):
570 """
571 This test makes sure that we correctly export outgoing queries over protobuf.
572 It must be improved and setup env so we can check for incoming responses, but makes sure for now
573 that the recursor at least connects to the protobuf server.
574 """
575
576 _confdir = 'OutgoingProtobufDefault'
577 _config_template = """
578 # Switch off QName Minimization, it generates much more protobuf messages
579 # (or make the test much more smart!)
580 qname-minimization=no
581 max-cache-ttl=600
582 loglevel=9
583 """
584 _lua_config_file = """
585 outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
586 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
587
588 def testA(self):
589 # There is a race in priming (having the . DNSKEY in cache in particular) and this code.
590 # So make sure we have the . DNSKEY in cache
591 query = dns.message.make_query('.', 'A', want_dnssec=True)
592 query.flags |= dns.flags.RD
593 res = self.sendUDPQuery(query)
594 time.sleep(1)
595 self.emptyProtoBufQueue()
596
597 name = 'host1.secure.example.'
598 expected = list()
599
600 for qname, qtype, proto, responseSize in [
601 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 248),
602 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 221),
603 ('example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 219),
604 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 175),
605 ('secure.example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 233),
606 ]:
607 if not qname:
608 expected.append((None, None, None, None, None, None))
609 continue
610 query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True)
611 resp = dns.message.make_response(query)
612 expected.append((
613 qname, qtype, query, resp, proto, responseSize
614 ))
615
616 query = dns.message.make_query(name, 'A', want_dnssec=True)
617 query.flags |= dns.flags.RD
618 res = self.sendUDPQuery(query)
619
620 for qname, qtype, qry, ans, proto, responseSize in expected:
621 if not qname:
622 self.getFirstProtobufMessage()
623 self.getFirstProtobufMessage()
624 continue
625
626 msg = self.getFirstProtobufMessage()
627 self.checkProtobufOutgoingQuery(msg, proto, qry, dns.rdataclass.IN, qtype, qname)
628
629 # Check the answer
630 msg = self.getFirstProtobufMessage()
631 self.checkProtobufIncomingResponse(msg, proto, ans, length=responseSize)
632
633 self.checkNoRemainingMessage()
634
635 class OutgoingProtobufWithECSMappingTest(TestRecursorProtobuf):
636 """
637 This test makes sure that we correctly export outgoing queries over protobuf.
638 It must be improved and setup env so we can check for incoming responses, but makes sure for now
639 that the recursor at least connects to the protobuf server.
640 """
641
642 _confdir = 'OutgoingProtobuffWithECSMapping'
643 _config_template = """
644 # Switch off QName Minimization, it generates much more protobuf messages
645 # (or make the test much more smart!)
646 qname-minimization=no
647 edns-subnet-allow-list=example
648 allow-from=1.2.3.4/32
649 # this is to not let . queries interfere
650 max-cache-ttl=600
651 loglevel=9
652 """
653 _lua_config_file = """
654 outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
655 addProxyMapping("127.0.0.0/8", "1.2.3.4", { "host1.secure.example." })
656 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
657
658 def testA(self):
659 # There is a race in priming (having the . DNSKEY in cache in particular) and this code.
660 # So make sure we have the . DNSKEY in cache
661 query = dns.message.make_query('.', 'A', want_dnssec=True)
662 query.flags |= dns.flags.RD
663 res = self.sendUDPQuery(query)
664 time.sleep(1)
665 self.emptyProtoBufQueue()
666
667 name = 'host1.secure.example.'
668 expected = list()
669
670 for qname, qtype, proto, responseSize, ecs in [
671 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 248, "1.2.3.0"),
672 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 221, "1.2.3.0"),
673 ('example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 219, "1.2.3.0"),
674 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 175, "1.2.3.0"),
675 ('secure.example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 233, "1.2.3.0"),
676 ]:
677 if not qname:
678 expected.append((None, None, None, None, None, None, None))
679 continue
680 ecso = clientsubnetoption.ClientSubnetOption('9.10.11.12', 24)
681 query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True, options=[ecso], payload=512)
682 resp = dns.message.make_response(query)
683 expected.append((
684 qname, qtype, query, resp, proto, responseSize, ecs
685 ))
686
687 query = dns.message.make_query(name, 'A', want_dnssec=True)
688 query.flags |= dns.flags.RD
689 res = self.sendUDPQuery(query)
690
691 for qname, qtype, qry, ans, proto, responseSize, ecs in expected:
692 if not qname:
693 self.getFirstProtobufMessage()
694 self.getFirstProtobufMessage()
695 continue
696
697 msg = self.getFirstProtobufMessage()
698 self.checkProtobufOutgoingQuery(msg, proto, qry, dns.rdataclass.IN, qtype, qname, "127.0.0.1", None, ecs)
699 # Check the answer
700 msg = self.getFirstProtobufMessage()
701 self.checkProtobufIncomingResponse(msg, proto, ans, length=responseSize)
702
703 self.checkNoRemainingMessage()
704
705 # this query should use the unmapped ECS
706 name = 'mx1.secure.example.'
707 expected = list()
708
709 for qname, qtype, proto, responseSize, ecs in [
710 ('mx1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 173, "127.0.0.1"),
711 ]:
712 if not qname:
713 expected.append((None, None, None, None, None, None, None))
714 continue
715 ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 32)
716 query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True, options=[ecso], payload=512)
717 resp = dns.message.make_response(query)
718 expected.append((
719 qname, qtype, query, resp, proto, responseSize, ecs
720 ))
721
722 query = dns.message.make_query(name, 'A', want_dnssec=True)
723 query.flags |= dns.flags.RD
724 res = self.sendUDPQuery(query)
725
726 for qname, qtype, qry, ans, proto, responseSize, ecs in expected:
727 if not qname:
728 self.getFirstProtobufMessage()
729 self.getFirstProtobufMessage()
730 continue
731
732 msg = self.getFirstProtobufMessage()
733 self.checkProtobufOutgoingQuery(msg, proto, qry, dns.rdataclass.IN, qtype, qname, "127.0.0.1", None, ecs)
734 # Check the answer
735 msg = self.getFirstProtobufMessage()
736 self.checkProtobufIncomingResponse(msg, proto, ans, length=responseSize)
737
738 self.checkNoRemainingMessage()
739
740 class OutgoingProtobufNoQueriesTest(TestRecursorProtobuf):
741 """
742 This test makes sure that we correctly export incoming responses but not outgoing queries over protobuf.
743 It must be improved and setup env so we can check for incoming responses, but makes sure for now
744 that the recursor at least connects to the protobuf server.
745 """
746
747 _confdir = 'OutgoingProtobufNoQueries'
748 _config_template = """
749 # Switch off QName Minimization, it generates much more protobuf messages
750 # (or make the test much more smart!)
751 qname-minimization=no
752 max-cache-ttl=600
753 loglevel=9
754 """
755 _lua_config_file = """
756 outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true })
757 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
758
759 def testA(self):
760 # There is a race in priming (having the . DNSKEY in cache in particular) and this code.
761 # So make sure we have the . DNSKEY in cache
762 query = dns.message.make_query('.', 'A', want_dnssec=True)
763 query.flags |= dns.flags.RD
764 res = self.sendUDPQuery(query)
765 time.sleep(1)
766 self.emptyProtoBufQueue()
767
768 name = 'host1.secure.example.'
769 expected = list()
770 # the root DNSKEY has been learned with priming the root NS already
771 # ('.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 201),
772 for qname, qtype, proto, size in [
773 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 248),
774 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 221),
775 ('example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 219),
776 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 175),
777 ('secure.example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 233),
778 ]:
779 if not qname:
780 expected.append((None, None, None, None, None, None))
781 continue
782 query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True)
783 resp = dns.message.make_response(query)
784 expected.append((
785 qname, qtype, query, resp, proto, size
786 ))
787
788 query = dns.message.make_query(name, 'A', want_dnssec=True)
789 query.flags |= dns.flags.RD
790 res = self.sendUDPQuery(query)
791
792 for qname, qtype, qry, ans, proto, size in expected:
793 if not qname:
794 self.getFirstProtobufMessage()
795 continue
796
797 # check the response
798 msg = self.getFirstProtobufMessage()
799 self.checkProtobufIncomingResponse(msg, proto, ans, length=size)
800
801 self.checkNoRemainingMessage()
802
803 class ProtobufMasksTest(TestRecursorProtobuf):
804 """
805 This test makes sure that we correctly export queries and response over protobuf, respecting the configured initiator masking.
806 """
807
808 _confdir = 'ProtobufMasks'
809 _config_template = """
810 auth-zones=example=configs/%s/example.zone""" % _confdir
811 _protobufMaskV4 = 4
812 _protobufMaskV6 = 128
813 _lua_config_file = """
814 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
815 setProtobufMasks(%d, %d)
816 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port, _protobufMaskV4, _protobufMaskV6)
817
818 def testA(self):
819 name = 'a.example.'
820 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
821 query = dns.message.make_query(name, 'A', want_dnssec=True)
822 query.flags |= dns.flags.CD
823 res = self.sendUDPQuery(query)
824 self.assertRRsetInAnswer(res, expected)
825
826 # check the protobuf messages corresponding to the UDP query and answer
827 # but first let the protobuf messages the time to get there
828 msg = self.getFirstProtobufMessage()
829 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '112.0.0.0')
830 # then the response
831 msg = self.getFirstProtobufMessage()
832 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '112.0.0.0')
833 self.assertEqual(len(msg.response.rrs), 1)
834 rr = msg.response.rrs[0]
835 # we have max-cache-ttl set to 15
836 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
837 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
838 self.checkNoRemainingMessage()
839
840 class ProtobufQueriesOnlyTest(TestRecursorProtobuf):
841 """
842 This test makes sure that we correctly export queries but not responses over protobuf.
843 """
844
845 _confdir = 'ProtobufQueriesOnly'
846 _config_template = """
847 auth-zones=example=configs/%s/example.zone""" % _confdir
848 _lua_config_file = """
849 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=false } )
850 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
851
852 def testA(self):
853 name = 'a.example.'
854 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
855 query = dns.message.make_query(name, 'A', want_dnssec=True)
856 query.flags |= dns.flags.CD
857 res = self.sendUDPQuery(query)
858 self.assertRRsetInAnswer(res, expected)
859
860 # check the protobuf message corresponding to the UDP query
861 msg = self.getFirstProtobufMessage()
862 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
863 # no response
864 self.checkNoRemainingMessage()
865
866 class ProtobufResponsesOnlyTest(TestRecursorProtobuf):
867 """
868 This test makes sure that we correctly export responses but not queries over protobuf.
869 """
870
871 _confdir = 'ProtobufResponsesOnly'
872 _config_template = """
873 auth-zones=example=configs/%s/example.zone""" % _confdir
874 _lua_config_file = """
875 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true } )
876 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
877
878 def testA(self):
879 name = 'a.example.'
880 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
881 query = dns.message.make_query(name, 'A', want_dnssec=True)
882 query.flags |= dns.flags.CD
883 res = self.sendUDPQuery(query)
884 self.assertRRsetInAnswer(res, expected)
885
886 # check the protobuf message corresponding to the UDP response
887 msg = self.getFirstProtobufMessage()
888 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
889 self.assertEqual(len(msg.response.rrs), 1)
890 rr = msg.response.rrs[0]
891 # we have max-cache-ttl set to 15
892 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
893 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
894 # nothing else in the queue
895 self.checkNoRemainingMessage()
896
897 class ProtobufTaggedOnlyTest(TestRecursorProtobuf):
898 """
899 This test makes sure that we correctly export queries and responses but only if they have been tagged.
900 """
901
902 _confdir = 'ProtobufTaggedOnly'
903 _config_template = """
904 auth-zones=example=configs/%s/example.zone""" % _confdir
905 _lua_config_file = """
906 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true, taggedOnly=true } )
907 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
908 _tags = ['tag1', 'tag2']
909 _tag_from_gettag = 'tag-from-gettag'
910 _lua_dns_script_file = """
911 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
912 if qname:equal('tagged.example.') then
913 return 0, { '%s' }
914 end
915 return 0
916 end
917 function preresolve(dq)
918 if dq.qname:equal('tagged.example.') then
919 dq:addPolicyTag('%s')
920 dq:addPolicyTag('%s')
921 end
922 return false
923 end
924 """ % (_tag_from_gettag, _tags[0], _tags[1])
925
926 def testA(self):
927 name = 'a.example.'
928 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
929 query = dns.message.make_query(name, 'A', want_dnssec=True)
930 query.flags |= dns.flags.CD
931 res = self.sendUDPQuery(query)
932 self.assertRRsetInAnswer(res, expected)
933
934 # check the protobuf message corresponding to the UDP response
935 # the first query and answer are not tagged, so there is nothing in the queue
936 time.sleep(1)
937 self.checkNoRemainingMessage()
938
939 def testTagged(self):
940 name = 'tagged.example.'
941 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
942 query = dns.message.make_query(name, 'A', want_dnssec=True)
943 query.flags |= dns.flags.CD
944 res = self.sendUDPQuery(query)
945 self.assertRRsetInAnswer(res, expected)
946
947 # check the protobuf messages corresponding to the UDP query and answer
948 msg = self.getFirstProtobufMessage()
949 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
950 self.checkProtobufTags(msg, [ self._tag_from_gettag ])
951 # then the response
952 msg = self.getFirstProtobufMessage()
953 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
954 self.assertEqual(len(msg.response.rrs), 1)
955 rr = msg.response.rrs[0]
956 # we have max-cache-ttl set to 15
957 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
958 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
959 tags = [ self._tag_from_gettag ] + self._tags
960 self.checkProtobufTags(msg, tags)
961 self.checkNoRemainingMessage()
962
963 class ProtobufSelectedFromLuaTest(TestRecursorProtobuf):
964 """
965 This test makes sure that we correctly export queries and responses but only if they have been selected from Lua.
966 """
967
968 _confdir = 'ProtobufSelectedFromLua'
969 _config_template = """
970 auth-zones=example=configs/%s/example.zone""" % _confdir
971 _lua_config_file = """
972 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=false } )
973 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
974 _lua_dns_script_file = """
975 local ffi = require("ffi")
976
977 ffi.cdef[[
978 typedef struct pdns_ffi_param pdns_ffi_param_t;
979
980 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
981 void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery);
982 ]]
983
984 function gettag_ffi(obj)
985 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
986 if qname == 'query-selected.example' then
987 ffi.C.pdns_ffi_param_set_log_query(obj, true)
988 end
989 return 0
990 end
991
992 function preresolve(dq)
993 if dq.qname:equal('answer-selected.example.') then
994 dq.logResponse = true
995 end
996 return false
997 end
998 """
999
1000 def testA(self):
1001 name = 'a.example.'
1002 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
1003 query = dns.message.make_query(name, 'A', want_dnssec=True)
1004 query.flags |= dns.flags.CD
1005 res = self.sendUDPQuery(query)
1006 self.assertRRsetInAnswer(res, expected)
1007
1008 # check the protobuf message corresponding to the UDP response
1009 # the first query and answer are not selected, so there is nothing in the queue
1010 self.checkNoRemainingMessage()
1011
1012 def testQuerySelected(self):
1013 name = 'query-selected.example.'
1014 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
1015 query = dns.message.make_query(name, 'A', want_dnssec=True)
1016 query.flags |= dns.flags.CD
1017 res = self.sendUDPQuery(query)
1018 self.assertRRsetInAnswer(res, expected)
1019
1020 # check the protobuf messages corresponding to the UDP query
1021 msg = self.getFirstProtobufMessage()
1022 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
1023 # there should be no response
1024 self.checkNoRemainingMessage()
1025
1026 def testResponseSelected(self):
1027 name = 'answer-selected.example.'
1028 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
1029 query = dns.message.make_query(name, 'A', want_dnssec=True)
1030 query.flags |= dns.flags.CD
1031 res = self.sendUDPQuery(query)
1032 self.assertRRsetInAnswer(res, expected)
1033
1034 # check the protobuf messages corresponding to the UDP response
1035 msg = self.getFirstProtobufMessage()
1036 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
1037 self.assertEqual(len(msg.response.rrs), 1)
1038 rr = msg.response.rrs[0]
1039 # we have max-cache-ttl set to 15
1040 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
1041 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
1042 self.checkNoRemainingMessage()
1043
1044 class ProtobufExportTypesTest(TestRecursorProtobuf):
1045 """
1046 This test makes sure that we correctly export other types than A, AAAA and CNAME over protobuf.
1047 """
1048
1049 _confdir = 'ProtobufExportTypes'
1050 _config_template = """
1051 auth-zones=example=configs/%s/example.zone""" % _confdir
1052 _lua_config_file = """
1053 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { exportTypes={"AAAA", "MX", "SPF", "SRV", "TXT"} } )
1054 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
1055
1056 def testA(self):
1057 name = 'types.example.'
1058 expected = [dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84'),
1059 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'AAAA', '2001:DB8::1'),
1060 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'MX', '10 a.example.'),
1061 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'SPF', '"v=spf1 -all"'),
1062 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'SRV', '10 20 443 a.example.'),
1063 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', '"Lorem ipsum dolor sit amet"'),
1064 ]
1065 query = dns.message.make_query(name, 'ANY', want_dnssec=True)
1066 query.flags |= dns.flags.CD
1067 raw = self.sendUDPQuery(query, decode=False)
1068 res = dns.message.from_wire(raw)
1069
1070 for rrset in expected:
1071 self.assertRRsetInAnswer(res, rrset)
1072
1073 # check the protobuf messages corresponding to the UDP query and answer
1074 msg = self.getFirstProtobufMessage()
1075 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
1076 # then the response
1077 msg = self.getFirstProtobufMessage()
1078 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1', receivedSize=len(raw))
1079 self.assertEqual(len(msg.response.rrs), 5)
1080 for rr in msg.response.rrs:
1081 self.assertTrue(rr.type in [dns.rdatatype.AAAA, dns.rdatatype.TXT, dns.rdatatype.MX, dns.rdatatype.SPF, dns.rdatatype.SRV])
1082
1083 if rr.type == dns.rdatatype.AAAA:
1084 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.AAAA, name, 15)
1085 self.assertEqual(socket.inet_ntop(socket.AF_INET6, rr.rdata), '2001:db8::1')
1086 elif rr.type == dns.rdatatype.TXT:
1087 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.TXT, name, 15)
1088 self.assertEqual(rr.rdata, b'"Lorem ipsum dolor sit amet"')
1089 elif rr.type == dns.rdatatype.MX:
1090 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.MX, name, 15)
1091 self.assertEqual(rr.rdata, b'a.example.')
1092 elif rr.type == dns.rdatatype.SPF:
1093 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.SPF, name, 15)
1094 self.assertEqual(rr.rdata, b'"v=spf1 -all"')
1095 elif rr.type == dns.rdatatype.SRV:
1096 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.SRV, name, 15)
1097 self.assertEqual(rr.rdata, b'a.example.')
1098
1099 self.checkNoRemainingMessage()
1100
1101 class ProtobufTaggedExtraFieldsTest(TestRecursorProtobuf):
1102 """
1103 This test makes sure that we correctly export extra fields that may have been set while being tagged.
1104 """
1105
1106 _confdir = 'ProtobufTaggedExtraFields'
1107 _config_template = """
1108 auth-zones=example=configs/%s/example.zone""" % _confdir
1109 _lua_config_file = """
1110 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1111 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
1112 _requestorId = 'S-000001727'
1113 _deviceId = 'd1:0a:91:dc:cc:82'
1114 _deviceName = 'Joe'
1115 _lua_dns_script_file = """
1116 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
1117 if qname:equal('tagged.example.') then
1118 -- tag number, policy tags, data, requestorId, deviceId, deviceName
1119 return 0, {}, {}, '%s', '%s', '%s'
1120 end
1121 return 0
1122 end
1123 """ % (_requestorId, _deviceId, _deviceName)
1124
1125 def testA(self):
1126 name = 'a.example.'
1127 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
1128 query = dns.message.make_query(name, 'A', want_dnssec=True)
1129 query.flags |= dns.flags.CD
1130 res = self.sendUDPQuery(query)
1131 self.assertRRsetInAnswer(res, expected)
1132
1133 # check the protobuf message corresponding to the UDP response
1134 # the first query and answer are not tagged, so there is nothing in the queue
1135 # check the protobuf messages corresponding to the UDP query and answer
1136 msg = self.getFirstProtobufMessage()
1137 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
1138 self.checkProtobufIdentity(msg, '', b'', '')
1139
1140 # then the response
1141 msg = self.getFirstProtobufMessage()
1142 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1')
1143 self.assertEqual(len(msg.response.rrs), 1)
1144 rr = msg.response.rrs[0]
1145 # we have max-cache-ttl set to 15
1146 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
1147 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
1148 self.checkProtobufIdentity(msg, '', b'', '')
1149 self.checkNoRemainingMessage()
1150
1151 def testTagged(self):
1152 name = 'tagged.example.'
1153 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
1154 query = dns.message.make_query(name, 'A', want_dnssec=True)
1155 query.flags |= dns.flags.CD
1156 res = self.sendUDPQuery(query)
1157 self.assertRRsetInAnswer(res, expected)
1158
1159 # check the protobuf messages corresponding to the UDP query and answer
1160 msg = self.getFirstProtobufMessage()
1161 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
1162 self.checkProtobufIdentity(msg, self._requestorId, self._deviceId.encode('ascii'), self._deviceName)
1163
1164 # then the response
1165 msg = self.getFirstProtobufMessage()
1166 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
1167 self.assertEqual(len(msg.response.rrs), 1)
1168 rr = msg.response.rrs[0]
1169 # we have max-cache-ttl set to 15
1170 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
1171 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
1172 self.checkProtobufIdentity(msg, self._requestorId, self._deviceId.encode('ascii'), self._deviceName)
1173 self.checkNoRemainingMessage()
1174
1175 class ProtobufTaggedExtraFieldsFFITest(ProtobufTaggedExtraFieldsTest):
1176 """
1177 This test makes sure that we correctly export extra fields that may have been set while being tagged (FFI version).
1178 """
1179 _confdir = 'ProtobufTaggedExtraFieldsFFI'
1180 _config_template = """
1181 auth-zones=example=configs/%s/example.zone""" % _confdir
1182 _lua_config_file = """
1183 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1184 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
1185 _lua_dns_script_file = """
1186 local ffi = require("ffi")
1187
1188 ffi.cdef[[
1189 typedef struct pdns_ffi_param pdns_ffi_param_t;
1190
1191 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
1192 void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag);
1193 void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name);
1194 void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name);
1195 void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name);
1196 ]]
1197
1198 function gettag_ffi(obj)
1199 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
1200 if qname == 'tagged.example' then
1201 ffi.C.pdns_ffi_param_set_requestorid(obj, "%s")
1202 deviceid = "%s"
1203 ffi.C.pdns_ffi_param_set_deviceid(obj, string.len(deviceid), deviceid)
1204 ffi.C.pdns_ffi_param_set_devicename(obj, "%s")
1205 end
1206 return 0
1207 end
1208 """ % (ProtobufTaggedExtraFieldsTest._requestorId, ProtobufTaggedExtraFieldsTest._deviceId, ProtobufTaggedExtraFieldsTest._deviceName)
1209
1210 class ProtobufRPZTest(TestRecursorProtobuf):
1211 """
1212 This test makes sure that we correctly export the RPZ applied policy in our protobuf messages
1213 """
1214
1215 _confdir = 'ProtobufRPZ'
1216 _config_template = """
1217 auth-zones=example=configs/%s/example.rpz.zone""" % _confdir
1218 _lua_config_file = """
1219 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1220 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
1221 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port, _confdir)
1222
1223 @classmethod
1224 def generateRecursorConfig(cls, confdir):
1225 authzonepath = os.path.join(confdir, 'example.rpz.zone')
1226 with open(authzonepath, 'w') as authzone:
1227 authzone.write("""$ORIGIN example.
1228 @ 3600 IN SOA {soa}
1229 sub.test 3600 IN A 192.0.2.42
1230 ip 3600 IN A 33.22.11.99
1231 """.format(soa=cls._SOA))
1232
1233 rpzFilePath = os.path.join(confdir, 'zone.rpz')
1234 with open(rpzFilePath, 'w') as rpzZone:
1235 rpzZone.write("""$ORIGIN zone.rpz.
1236 @ 3600 IN SOA {soa}
1237 *.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
1238 24.0.11.22.33.rpz-ip 60 IN A 1.2.3.4
1239 """.format(soa=cls._SOA))
1240
1241 super(ProtobufRPZTest, cls).generateRecursorConfig(confdir)
1242
1243 def testA(self):
1244 name = 'sub.test.example.'
1245 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
1246 query = dns.message.make_query(name, 'A', want_dnssec=True)
1247 query.flags |= dns.flags.CD
1248 res = self.sendUDPQuery(query)
1249 self.assertRRsetInAnswer(res, expected)
1250
1251 # check the protobuf messages corresponding to the UDP query and answer
1252 msg = self.getFirstProtobufMessage()
1253 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
1254
1255 # then the response
1256 msg = self.getFirstProtobufMessage()
1257 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
1258 self.checkProtobufPolicy(msg, dnsmessage_pb2.PBDNSMessage.PolicyType.QNAME, 'zone.rpz.', '*.test.example.', 'sub.test.example', dnsmessage_pb2.PBDNSMessage.PolicyKind.NoAction)
1259 self.assertEqual(len(msg.response.rrs), 1)
1260 rr = msg.response.rrs[0]
1261 # we have max-cache-ttl set to 15
1262 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
1263 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
1264 self.checkNoRemainingMessage()
1265
1266 def testB(self):
1267 name = 'ip.example.'
1268 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '1.2.3.4')
1269 query = dns.message.make_query(name, 'A', want_dnssec=True)
1270 query.flags |= dns.flags.CD
1271 res = self.sendUDPQuery(query)
1272 self.assertRRsetInAnswer(res, expected)
1273
1274 # check the protobuf messages corresponding to the UDP query and answer
1275 msg = self.getFirstProtobufMessage()
1276 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
1277
1278 # then the response
1279 msg = self.getFirstProtobufMessage()
1280 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
1281 self.checkProtobufPolicy(msg, dnsmessage_pb2.PBDNSMessage.PolicyType.RESPONSEIP, 'zone.rpz.', '24.0.11.22.33.rpz-ip.', '33.22.11.99', dnsmessage_pb2.PBDNSMessage.PolicyKind.Custom)
1282 self.assertEqual(len(msg.response.rrs), 1)
1283 self.checkNoRemainingMessage()
1284
1285 class ProtobufRPZTagsTest(TestRecursorProtobuf):
1286 """
1287 This test makes sure that we correctly export the RPZ tags in our protobuf messages
1288 """
1289
1290 _confdir = 'ProtobufRPZTags'
1291 _config_template = """
1292 auth-zones=example=configs/%s/example.rpz.zone""" % _confdir
1293 _tags = ['tag1', 'tag2']
1294 _tags_from_gettag = ['tag1-from-gettag', 'tag2-from-gettag']
1295 _tags_from_rpz = ['tag1-from-rpz', 'tag2-from-rpz' ]
1296 _lua_config_file = """
1297 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true, tags={'tag1', 'tag2'} } )
1298 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", tags={ '%s', '%s'} })
1299 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port, _confdir, _tags_from_rpz[0], _tags_from_rpz[1])
1300 _lua_dns_script_file = """
1301 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
1302 return 0, { '%s', '%s' }
1303 end
1304 function preresolve(dq)
1305 dq:addPolicyTag('%s')
1306 dq:addPolicyTag('%s')
1307 return false
1308 end
1309 """ % (_tags_from_gettag[0], _tags_from_gettag[1], _tags[0], _tags[1])
1310
1311 @classmethod
1312 def generateRecursorConfig(cls, confdir):
1313 authzonepath = os.path.join(confdir, 'example.rpz.zone')
1314 with open(authzonepath, 'w') as authzone:
1315 authzone.write("""$ORIGIN example.
1316 @ 3600 IN SOA {soa}
1317 sub.test 3600 IN A 192.0.2.42
1318 """.format(soa=cls._SOA))
1319
1320 rpzFilePath = os.path.join(confdir, 'zone.rpz')
1321 with open(rpzFilePath, 'w') as rpzZone:
1322 rpzZone.write("""$ORIGIN zone.rpz.
1323 @ 3600 IN SOA {soa}
1324 *.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
1325 """.format(soa=cls._SOA))
1326
1327 super(ProtobufRPZTagsTest, cls).generateRecursorConfig(confdir)
1328
1329 def testA(self):
1330 name = 'sub.test.example.'
1331 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
1332 query = dns.message.make_query(name, 'A', want_dnssec=True)
1333 query.flags |= dns.flags.CD
1334 res = self.sendUDPQuery(query)
1335 self.assertRRsetInAnswer(res, expected)
1336
1337 # check the protobuf messages corresponding to the UDP query and answer
1338 msg = self.getFirstProtobufMessage()
1339 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
1340
1341 # then the response
1342 msg = self.getFirstProtobufMessage()
1343 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
1344 self.checkProtobufPolicy(msg, dnsmessage_pb2.PBDNSMessage.PolicyType.QNAME, 'zone.rpz.', '*.test.example.', 'sub.test.example', dnsmessage_pb2.PBDNSMessage.PolicyKind.NoAction)
1345 self.checkProtobufTags(msg, self._tags + self._tags_from_gettag + self._tags_from_rpz)
1346 self.assertEqual(len(msg.response.rrs), 1)
1347 rr = msg.response.rrs[0]
1348 # we have max-cache-ttl set to 15
1349 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
1350 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
1351 self.checkNoRemainingMessage()
1352
1353
1354 class ProtobufMetaFFITest(TestRecursorProtobuf):
1355 """
1356 This test makes sure that we can correctly add extra meta fields (FFI version).
1357 """
1358 _confdir = 'ProtobufMetaFFITest'
1359 _config_template = """
1360 auth-zones=example=configs/%s/example.zone""" % _confdir
1361 _lua_config_file = """
1362 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1363 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
1364 _lua_dns_script_file = """
1365 local ffi = require("ffi")
1366
1367 ffi.cdef[[
1368 typedef struct pdns_ffi_param pdns_ffi_param_t;
1369
1370 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
1371 void pdns_ffi_param_add_meta_single_string_kv(pdns_ffi_param_t *ref, const char* key, const char* val);
1372 void pdns_ffi_param_add_meta_single_int64_kv(pdns_ffi_param_t *ref, const char* key, int64_t val);
1373 ]]
1374
1375 function gettag_ffi(obj)
1376 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
1377 if qname == 'meta.example' then
1378 ffi.C.pdns_ffi_param_add_meta_single_string_kv(obj, "meta-str", "keyword")
1379 ffi.C.pdns_ffi_param_add_meta_single_int64_kv(obj, "meta-int", 42)
1380 ffi.C.pdns_ffi_param_add_meta_single_string_kv(obj, "meta-str", "content")
1381 ffi.C.pdns_ffi_param_add_meta_single_int64_kv(obj, "meta-int", 21)
1382 end
1383 return 0
1384 end
1385 """
1386 def testMeta(self):
1387 name = 'meta.example.'
1388 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.85')
1389 query = dns.message.make_query(name, 'A', want_dnssec=True)
1390 query.flags |= dns.flags.CD
1391 res = self.sendUDPQuery(query)
1392 self.assertRRsetInAnswer(res, expected)
1393
1394 # check the protobuf messages corresponding to the UDP query and answer
1395 msg = self.getFirstProtobufMessage()
1396 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
1397 self.checkProtobufMetas(msg, {'meta-str': { "stringVal" : ["content", "keyword"]}, 'meta-int': {"intVal" : [21, 42]}})
1398
1399 # then the response
1400 msg = self.getFirstProtobufMessage()
1401 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
1402 self.assertEqual(len(msg.response.rrs), 1)
1403 rr = msg.response.rrs[0]
1404 # we have max-cache-ttl set to 15
1405 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
1406 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.85')
1407 self.checkProtobufMetas(msg, {'meta-str': { "stringVal" : ["content", "keyword"]}, 'meta-int': {"intVal" : [21, 42]}})
1408
1409 self.checkNoRemainingMessage()