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