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