]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.recursor-dnssec/test_Protobuf.py
Merge pull request #10826 from omoerbeek/rec-prep-4.5.6
[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
49193d6f
OM
371class ProtobufProxyTest(TestRecursorProtobuf):
372 """
373 This test makes sure that we correctly export addresses over protobuf when the proxy protocol is used.
374 """
375
376 _confdir = 'ProtobufProxy'
377 _config_template = """
378auth-zones=example=configs/%s/example.zone
379proxy-protocol-from=127.0.0.1/32
380allow-from=127.0.0.1,6.6.6.6
381""" % _confdir
382
383 def testA(self):
384 name = 'a.example.'
385 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
386 query = dns.message.make_query(name, 'A', want_dnssec=True)
387 query.flags |= dns.flags.CD
388 res = self.sendUDPQueryWithProxyProtocol(query, False, '6.6.6.6', '7.7.7.7', 666, 777)
389
390 self.assertRRsetInAnswer(res, expected)
391
392 # check the protobuf messages corresponding to the UDP query and answer
393 msg = self.getFirstProtobufMessage()
394 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '6.6.6.6', '7.7.7.7')
395 # then the response
396 msg = self.getFirstProtobufMessage()
397 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '6.6.6.6')
398 self.assertEqual(len(msg.response.rrs), 1)
399 rr = msg.response.rrs[0]
400 # we have max-cache-ttl set to 15
401 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
402 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
403 self.checkNoRemainingMessage()
404
5dbc7fbe
CHB
405class OutgoingProtobufDefaultTest(TestRecursorProtobuf):
406 """
407 This test makes sure that we correctly export outgoing queries over protobuf.
408 It must be improved and setup env so we can check for incoming responses, but makes sure for now
409 that the recursor at least connects to the protobuf server.
410 """
411
412 _confdir = 'OutgoingProtobufDefault'
413 _config_template = """
bfd27bc9 414 # Switch off QName Minimization, it generates much more protobuf messages
8949a3e0
OM
415 # (or make the test much more smart!)
416 qname-minimization=no
0d3ea020 417"""
5dbc7fbe 418 _lua_config_file = """
b773359c
RG
419 outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
420 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
5dbc7fbe
CHB
421
422 def testA(self):
0d3ea020
PL
423 name = 'host1.secure.example.'
424 expected = list()
943b519e
RG
425
426 # the root DNSKEY has been learned with priming the root NS already
427 # ('.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 201),
d87ef9d3
RG
428 for qname, qtype, proto, responseSize in [
429 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 248),
d87ef9d3 430 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 221),
0d3ea020 431 ('example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 219),
0d3ea020 432 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 175),
d87ef9d3 433 ('secure.example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 233),
0d3ea020
PL
434 ]:
435 if not qname:
436 expected.append((None, None, None, None, None, None))
437 continue
438 query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True)
439 resp = dns.message.make_response(query)
440 expected.append((
d87ef9d3 441 qname, qtype, query, resp, proto, responseSize
0d3ea020
PL
442 ))
443
5dbc7fbe
CHB
444 query = dns.message.make_query(name, 'A', want_dnssec=True)
445 query.flags |= dns.flags.RD
446 res = self.sendUDPQuery(query)
447
d87ef9d3 448 for qname, qtype, qry, ans, proto, responseSize in expected:
0d3ea020
PL
449 if not qname:
450 self.getFirstProtobufMessage()
451 self.getFirstProtobufMessage()
452 continue
453
454 msg = self.getFirstProtobufMessage()
455 self.checkProtobufOutgoingQuery(msg, proto, qry, dns.rdataclass.IN, qtype, qname)
456
457 # Check the answer
458 msg = self.getFirstProtobufMessage()
d87ef9d3 459 self.checkProtobufIncomingResponse(msg, proto, ans, length=responseSize)
0d3ea020 460
5dbc7fbe
CHB
461 self.checkNoRemainingMessage()
462
87cab7c0
RG
463class OutgoingProtobufNoQueriesTest(TestRecursorProtobuf):
464 """
465 This test makes sure that we correctly export incoming responses but not outgoing queries over protobuf.
466 It must be improved and setup env so we can check for incoming responses, but makes sure for now
467 that the recursor at least connects to the protobuf server.
468 """
469
470 _confdir = 'OutgoingProtobufNoQueries'
471 _config_template = """
bfd27bc9 472 # Switch off QName Minimization, it generates much more protobuf messages
8949a3e0 473 # (or make the test much more smart!)
fc2ad22e 474 qname-minimization=no"""
87cab7c0
RG
475 _lua_config_file = """
476 outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true })
477 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
478
479 def testA(self):
fc2ad22e
PL
480 name = 'host1.secure.example.'
481 expected = list()
943b519e
RG
482 # the root DNSKEY has been learned with priming the root NS already
483 # ('.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 201),
fc2ad22e 484 for qname, qtype, proto, size in [
d87ef9d3 485 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 248),
d87ef9d3 486 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 221),
fc2ad22e 487 ('example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 219),
fc2ad22e 488 ('host1.secure.example.', dns.rdatatype.A, dnsmessage_pb2.PBDNSMessage.UDP, 175),
d87ef9d3 489 ('secure.example.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 233),
fc2ad22e
PL
490 ]:
491 if not qname:
492 expected.append((None, None, None, None, None, None))
493 continue
494 query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True)
495 resp = dns.message.make_response(query)
496 expected.append((
497 qname, qtype, query, resp, proto, size
498 ))
499
87cab7c0
RG
500 query = dns.message.make_query(name, 'A', want_dnssec=True)
501 query.flags |= dns.flags.RD
502 res = self.sendUDPQuery(query)
503
fc2ad22e
PL
504 for qname, qtype, qry, ans, proto, size in expected:
505 if not qname:
506 self.getFirstProtobufMessage()
507 continue
508
509 # check the response
510 msg = self.getFirstProtobufMessage()
511 self.checkProtobufIncomingResponse(msg, proto, ans, length=size)
512
87cab7c0
RG
513 self.checkNoRemainingMessage()
514
f1c7929a
RG
515class ProtobufMasksTest(TestRecursorProtobuf):
516 """
517 This test makes sure that we correctly export queries and response over protobuf, respecting the configured initiator masking.
518 """
519
520 _confdir = 'ProtobufMasks'
521 _config_template = """
522auth-zones=example=configs/%s/example.zone""" % _confdir
f1c7929a
RG
523 _protobufMaskV4 = 4
524 _protobufMaskV6 = 128
525 _lua_config_file = """
b773359c 526 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
f1c7929a 527 setProtobufMasks(%d, %d)
b773359c 528 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port, _protobufMaskV4, _protobufMaskV6)
f1c7929a
RG
529
530 def testA(self):
531 name = 'a.example.'
532 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
533 query = dns.message.make_query(name, 'A', want_dnssec=True)
534 query.flags |= dns.flags.CD
535 res = self.sendUDPQuery(query)
536 self.assertRRsetInAnswer(res, expected)
537
538 # check the protobuf messages corresponding to the UDP query and answer
539 # but first let the protobuf messages the time to get there
540 msg = self.getFirstProtobufMessage()
541 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '112.0.0.0')
542 # then the response
543 msg = self.getFirstProtobufMessage()
544 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '112.0.0.0')
4bfebc93 545 self.assertEqual(len(msg.response.rrs), 1)
f1c7929a
RG
546 rr = msg.response.rrs[0]
547 # we have max-cache-ttl set to 15
548 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
4bfebc93 549 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
f1c7929a
RG
550 self.checkNoRemainingMessage()
551
552class ProtobufQueriesOnlyTest(TestRecursorProtobuf):
553 """
554 This test makes sure that we correctly export queries but not responses over protobuf.
555 """
556
557 _confdir = 'ProtobufQueriesOnly'
558 _config_template = """
559auth-zones=example=configs/%s/example.zone""" % _confdir
f1c7929a 560 _lua_config_file = """
b773359c
RG
561 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=false } )
562 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
f1c7929a
RG
563
564 def testA(self):
565 name = 'a.example.'
566 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
567 query = dns.message.make_query(name, 'A', want_dnssec=True)
568 query.flags |= dns.flags.CD
569 res = self.sendUDPQuery(query)
570 self.assertRRsetInAnswer(res, expected)
571
572 # check the protobuf message corresponding to the UDP query
573 msg = self.getFirstProtobufMessage()
574 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
575 # no response
576 self.checkNoRemainingMessage()
577
578class ProtobufResponsesOnlyTest(TestRecursorProtobuf):
579 """
580 This test makes sure that we correctly export responses but not queries over protobuf.
581 """
582
583 _confdir = 'ProtobufResponsesOnly'
584 _config_template = """
585auth-zones=example=configs/%s/example.zone""" % _confdir
f1c7929a 586 _lua_config_file = """
b773359c
RG
587 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true } )
588 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
f1c7929a
RG
589
590 def testA(self):
591 name = 'a.example.'
592 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
593 query = dns.message.make_query(name, 'A', want_dnssec=True)
594 query.flags |= dns.flags.CD
595 res = self.sendUDPQuery(query)
596 self.assertRRsetInAnswer(res, expected)
597
598 # check the protobuf message corresponding to the UDP response
599 msg = self.getFirstProtobufMessage()
600 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
4bfebc93 601 self.assertEqual(len(msg.response.rrs), 1)
f1c7929a
RG
602 rr = msg.response.rrs[0]
603 # we have max-cache-ttl set to 15
604 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
4bfebc93 605 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
f1c7929a
RG
606 # nothing else in the queue
607 self.checkNoRemainingMessage()
608
609class ProtobufTaggedOnlyTest(TestRecursorProtobuf):
610 """
611 This test makes sure that we correctly export queries and responses but only if they have been tagged.
612 """
613
614 _confdir = 'ProtobufTaggedOnly'
615 _config_template = """
616auth-zones=example=configs/%s/example.zone""" % _confdir
f1c7929a 617 _lua_config_file = """
b773359c
RG
618 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true, taggedOnly=true } )
619 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
f1c7929a
RG
620 _tags = ['tag1', 'tag2']
621 _tag_from_gettag = 'tag-from-gettag'
622 _lua_dns_script_file = """
623 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
624 if qname:equal('tagged.example.') then
625 return 0, { '%s' }
626 end
627 return 0
628 end
629 function preresolve(dq)
630 if dq.qname:equal('tagged.example.') then
631 dq:addPolicyTag('%s')
632 dq:addPolicyTag('%s')
633 end
634 return false
635 end
636 """ % (_tag_from_gettag, _tags[0], _tags[1])
637
638 def testA(self):
639 name = 'a.example.'
640 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
641 query = dns.message.make_query(name, 'A', want_dnssec=True)
642 query.flags |= dns.flags.CD
643 res = self.sendUDPQuery(query)
644 self.assertRRsetInAnswer(res, expected)
645
646 # check the protobuf message corresponding to the UDP response
647 # the first query and answer are not tagged, so there is nothing in the queue
648 time.sleep(1)
649 self.checkNoRemainingMessage()
650
651 def testTagged(self):
652 name = 'tagged.example.'
653 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
654 query = dns.message.make_query(name, 'A', want_dnssec=True)
655 query.flags |= dns.flags.CD
656 res = self.sendUDPQuery(query)
657 self.assertRRsetInAnswer(res, expected)
658
659 # check the protobuf messages corresponding to the UDP query and answer
660 msg = self.getFirstProtobufMessage()
661 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
b502d522 662 self.checkProtobufTags(msg, [ self._tag_from_gettag ])
f1c7929a
RG
663 # then the response
664 msg = self.getFirstProtobufMessage()
665 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
4bfebc93 666 self.assertEqual(len(msg.response.rrs), 1)
f1c7929a
RG
667 rr = msg.response.rrs[0]
668 # we have max-cache-ttl set to 15
669 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
4bfebc93 670 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
b502d522 671 tags = [ self._tag_from_gettag ] + self._tags
f1c7929a
RG
672 self.checkProtobufTags(msg, tags)
673 self.checkNoRemainingMessage()
674
675class ProtobufSelectedFromLuaTest(TestRecursorProtobuf):
676 """
677 This test makes sure that we correctly export queries and responses but only if they have been selected from Lua.
678 """
679
680 _confdir = 'ProtobufSelectedFromLua'
681 _config_template = """
682auth-zones=example=configs/%s/example.zone""" % _confdir
f1c7929a 683 _lua_config_file = """
b773359c
RG
684 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=false } )
685 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
f1c7929a
RG
686 _lua_dns_script_file = """
687 local ffi = require("ffi")
688
689 ffi.cdef[[
690 typedef struct pdns_ffi_param pdns_ffi_param_t;
691
692 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
693 void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery);
694 ]]
695
696 function gettag_ffi(obj)
697 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
698 if qname == 'query-selected.example' then
699 ffi.C.pdns_ffi_param_set_log_query(obj, true)
700 end
701 return 0
702 end
703
704 function preresolve(dq)
705 if dq.qname:equal('answer-selected.example.') then
706 dq.logResponse = true
707 end
708 return false
709 end
710 """
711
712 def testA(self):
713 name = 'a.example.'
714 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
715 query = dns.message.make_query(name, 'A', want_dnssec=True)
716 query.flags |= dns.flags.CD
717 res = self.sendUDPQuery(query)
718 self.assertRRsetInAnswer(res, expected)
719
720 # check the protobuf message corresponding to the UDP response
721 # the first query and answer are not selected, so there is nothing in the queue
722 self.checkNoRemainingMessage()
723
724 def testQuerySelected(self):
725 name = 'query-selected.example.'
726 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
727 query = dns.message.make_query(name, 'A', want_dnssec=True)
728 query.flags |= dns.flags.CD
729 res = self.sendUDPQuery(query)
730 self.assertRRsetInAnswer(res, expected)
731
732 # check the protobuf messages corresponding to the UDP query
733 msg = self.getFirstProtobufMessage()
734 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
735 # there should be no response
736 self.checkNoRemainingMessage()
737
738 def testResponseSelected(self):
739 name = 'answer-selected.example.'
740 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
741 query = dns.message.make_query(name, 'A', want_dnssec=True)
742 query.flags |= dns.flags.CD
743 res = self.sendUDPQuery(query)
744 self.assertRRsetInAnswer(res, expected)
745
746 # check the protobuf messages corresponding to the UDP response
747 msg = self.getFirstProtobufMessage()
748 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
4bfebc93 749 self.assertEqual(len(msg.response.rrs), 1)
f1c7929a
RG
750 rr = msg.response.rrs[0]
751 # we have max-cache-ttl set to 15
752 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
4bfebc93 753 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
f1c7929a 754 self.checkNoRemainingMessage()
0bd2e252
RG
755
756class ProtobufExportTypesTest(TestRecursorProtobuf):
757 """
758 This test makes sure that we correctly export other types than A, AAAA and CNAME over protobuf.
759 """
760
761 _confdir = 'ProtobufExportTypes'
762 _config_template = """
763auth-zones=example=configs/%s/example.zone""" % _confdir
0bd2e252 764 _lua_config_file = """
b773359c
RG
765 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { exportTypes={"AAAA", "MX", "SPF", "SRV", "TXT"} } )
766 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
0bd2e252
RG
767
768 def testA(self):
769 name = 'types.example.'
770 expected = [dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84'),
771 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'AAAA', '2001:DB8::1'),
772 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'MX', '10 a.example.'),
773 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'SPF', '"v=spf1 -all"'),
774 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'SRV', '10 20 443 a.example.'),
775 dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', '"Lorem ipsum dolor sit amet"'),
776 ]
777 query = dns.message.make_query(name, 'ANY', want_dnssec=True)
778 query.flags |= dns.flags.CD
779 raw = self.sendUDPQuery(query, decode=False)
780 res = dns.message.from_wire(raw)
781
782 for rrset in expected:
783 self.assertRRsetInAnswer(res, rrset)
784
785 # check the protobuf messages corresponding to the UDP query and answer
786 msg = self.getFirstProtobufMessage()
787 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
788 # then the response
789 msg = self.getFirstProtobufMessage()
790 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1', receivedSize=len(raw))
4bfebc93 791 self.assertEqual(len(msg.response.rrs), 5)
0bd2e252
RG
792 for rr in msg.response.rrs:
793 self.assertTrue(rr.type in [dns.rdatatype.AAAA, dns.rdatatype.TXT, dns.rdatatype.MX, dns.rdatatype.SPF, dns.rdatatype.SRV])
794
795 if rr.type == dns.rdatatype.AAAA:
796 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.AAAA, name, 15)
4bfebc93 797 self.assertEqual(socket.inet_ntop(socket.AF_INET6, rr.rdata), '2001:db8::1')
0bd2e252
RG
798 elif rr.type == dns.rdatatype.TXT:
799 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.TXT, name, 15)
4bfebc93 800 self.assertEqual(rr.rdata, b'"Lorem ipsum dolor sit amet"')
0bd2e252
RG
801 elif rr.type == dns.rdatatype.MX:
802 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.MX, name, 15)
4bfebc93 803 self.assertEqual(rr.rdata, b'a.example.')
0bd2e252
RG
804 elif rr.type == dns.rdatatype.SPF:
805 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.SPF, name, 15)
4bfebc93 806 self.assertEqual(rr.rdata, b'"v=spf1 -all"')
0bd2e252
RG
807 elif rr.type == dns.rdatatype.SRV:
808 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.SRV, name, 15)
4bfebc93 809 self.assertEqual(rr.rdata, b'a.example.')
0bd2e252
RG
810
811 self.checkNoRemainingMessage()
0a6a45c8
CHB
812
813class ProtobufTaggedExtraFieldsTest(TestRecursorProtobuf):
814 """
815 This test makes sure that we correctly export extra fields that may have been set while being tagged.
816 """
817
818 _confdir = 'ProtobufTaggedExtraFields'
819 _config_template = """
820auth-zones=example=configs/%s/example.zone""" % _confdir
821 _lua_config_file = """
822 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
823 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
824 _requestorId = 'S-000001727'
825 _deviceId = 'd1:0a:91:dc:cc:82'
826 _deviceName = 'Joe'
827 _lua_dns_script_file = """
828 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
829 if qname:equal('tagged.example.') then
830 -- tag number, policy tags, data, requestorId, deviceId, deviceName
831 return 0, {}, {}, '%s', '%s', '%s'
832 end
833 return 0
834 end
835 """ % (_requestorId, _deviceId, _deviceName)
836
837 def testA(self):
838 name = 'a.example.'
839 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
840 query = dns.message.make_query(name, 'A', want_dnssec=True)
841 query.flags |= dns.flags.CD
842 res = self.sendUDPQuery(query)
843 self.assertRRsetInAnswer(res, expected)
844
845 # check the protobuf message corresponding to the UDP response
846 # the first query and answer are not tagged, so there is nothing in the queue
847 # check the protobuf messages corresponding to the UDP query and answer
848 msg = self.getFirstProtobufMessage()
849 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
3d144e24 850 self.checkProtobufIdentity(msg, '', b'', '')
0a6a45c8
CHB
851
852 # then the response
853 msg = self.getFirstProtobufMessage()
854 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1')
4bfebc93 855 self.assertEqual(len(msg.response.rrs), 1)
0a6a45c8
CHB
856 rr = msg.response.rrs[0]
857 # we have max-cache-ttl set to 15
858 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
4bfebc93 859 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
3d144e24 860 self.checkProtobufIdentity(msg, '', b'', '')
0a6a45c8
CHB
861 self.checkNoRemainingMessage()
862
863 def testTagged(self):
864 name = 'tagged.example.'
865 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
866 query = dns.message.make_query(name, 'A', want_dnssec=True)
867 query.flags |= dns.flags.CD
868 res = self.sendUDPQuery(query)
869 self.assertRRsetInAnswer(res, expected)
870
871 # check the protobuf messages corresponding to the UDP query and answer
872 msg = self.getFirstProtobufMessage()
873 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
3d144e24 874 self.checkProtobufIdentity(msg, self._requestorId, self._deviceId.encode('ascii'), self._deviceName)
0a6a45c8
CHB
875
876 # then the response
877 msg = self.getFirstProtobufMessage()
878 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
4bfebc93 879 self.assertEqual(len(msg.response.rrs), 1)
0a6a45c8
CHB
880 rr = msg.response.rrs[0]
881 # we have max-cache-ttl set to 15
882 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
4bfebc93 883 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
3d144e24 884 self.checkProtobufIdentity(msg, self._requestorId, self._deviceId.encode('ascii'), self._deviceName)
0a6a45c8
CHB
885 self.checkNoRemainingMessage()
886
887class ProtobufTaggedExtraFieldsFFITest(ProtobufTaggedExtraFieldsTest):
888 """
889 This test makes sure that we correctly export extra fields that may have been set while being tagged (FFI version).
890 """
891 _confdir = 'ProtobufTaggedExtraFieldsFFI'
892 _config_template = """
893auth-zones=example=configs/%s/example.zone""" % _confdir
894 _lua_config_file = """
895 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
896 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
897 _lua_dns_script_file = """
898 local ffi = require("ffi")
899
900 ffi.cdef[[
901 typedef struct pdns_ffi_param pdns_ffi_param_t;
902
903 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
904 void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag);
905 void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name);
906 void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name);
907 void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name);
908 ]]
909
910 function gettag_ffi(obj)
911 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
912 if qname == 'tagged.example' then
913 ffi.C.pdns_ffi_param_set_requestorid(obj, "%s")
914 deviceid = "%s"
915 ffi.C.pdns_ffi_param_set_deviceid(obj, string.len(deviceid), deviceid)
916 ffi.C.pdns_ffi_param_set_devicename(obj, "%s")
917 end
918 return 0
919 end
920 """ % (ProtobufTaggedExtraFieldsTest._requestorId, ProtobufTaggedExtraFieldsTest._deviceId, ProtobufTaggedExtraFieldsTest._deviceName)
f89ae456
RG
921
922class ProtobufRPZTest(TestRecursorProtobuf):
923 """
924 This test makes sure that we correctly export the RPZ applied policy in our protobuf messages
925 """
926
927 _confdir = 'ProtobufRPZ'
928 _config_template = """
929auth-zones=example=configs/%s/example.rpz.zone""" % _confdir
930 _lua_config_file = """
931 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
932 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
933 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port, _confdir)
934
935 @classmethod
936 def generateRecursorConfig(cls, confdir):
937 authzonepath = os.path.join(confdir, 'example.rpz.zone')
938 with open(authzonepath, 'w') as authzone:
939 authzone.write("""$ORIGIN example.
940@ 3600 IN SOA {soa}
941sub.test 3600 IN A 192.0.2.42
942""".format(soa=cls._SOA))
943
944 rpzFilePath = os.path.join(confdir, 'zone.rpz')
945 with open(rpzFilePath, 'w') as rpzZone:
946 rpzZone.write("""$ORIGIN zone.rpz.
947@ 3600 IN SOA {soa}
948*.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
949""".format(soa=cls._SOA))
950
951 super(ProtobufRPZTest, cls).generateRecursorConfig(confdir)
952
953 def testA(self):
954 name = 'sub.test.example.'
955 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
956 query = dns.message.make_query(name, 'A', want_dnssec=True)
957 query.flags |= dns.flags.CD
958 res = self.sendUDPQuery(query)
959 self.assertRRsetInAnswer(res, expected)
960
961 # check the protobuf messages corresponding to the UDP query and answer
962 msg = self.getFirstProtobufMessage()
963 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
964
965 # then the response
966 msg = self.getFirstProtobufMessage()
967 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
fe1a9389 968 self.checkProtobufPolicy(msg, dnsmessage_pb2.PBDNSMessage.PolicyType.QNAME, 'zone.rpz.', '*.test.example.', 'sub.test.example', dnsmessage_pb2.PBDNSMessage.PolicyKind.NoAction)
4bfebc93 969 self.assertEqual(len(msg.response.rrs), 1)
f89ae456
RG
970 rr = msg.response.rrs[0]
971 # we have max-cache-ttl set to 15
972 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
4bfebc93 973 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
f89ae456 974 self.checkNoRemainingMessage()
b502d522
RG
975
976class ProtobufRPZTagsTest(TestRecursorProtobuf):
977 """
978 This test makes sure that we correctly export the RPZ tags in our protobuf messages
979 """
980
981 _confdir = 'ProtobufRPZTags'
982 _config_template = """
983auth-zones=example=configs/%s/example.rpz.zone""" % _confdir
984 _tags = ['tag1', 'tag2']
985 _tags_from_gettag = ['tag1-from-gettag', 'tag2-from-gettag']
986 _tags_from_rpz = ['tag1-from-rpz', 'tag2-from-rpz' ]
987 _lua_config_file = """
988 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true, tags={'tag1', 'tag2'} } )
989 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", tags={ '%s', '%s'} })
990 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port, _confdir, _tags_from_rpz[0], _tags_from_rpz[1])
991 _lua_dns_script_file = """
992 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
993 return 0, { '%s', '%s' }
994 end
995 function preresolve(dq)
996 dq:addPolicyTag('%s')
997 dq:addPolicyTag('%s')
998 return false
999 end
1000 """ % (_tags_from_gettag[0], _tags_from_gettag[1], _tags[0], _tags[1])
1001
1002 @classmethod
1003 def generateRecursorConfig(cls, confdir):
1004 authzonepath = os.path.join(confdir, 'example.rpz.zone')
1005 with open(authzonepath, 'w') as authzone:
1006 authzone.write("""$ORIGIN example.
1007@ 3600 IN SOA {soa}
1008sub.test 3600 IN A 192.0.2.42
1009""".format(soa=cls._SOA))
1010
1011 rpzFilePath = os.path.join(confdir, 'zone.rpz')
1012 with open(rpzFilePath, 'w') as rpzZone:
1013 rpzZone.write("""$ORIGIN zone.rpz.
1014@ 3600 IN SOA {soa}
1015*.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
1016""".format(soa=cls._SOA))
1017
1018 super(ProtobufRPZTagsTest, cls).generateRecursorConfig(confdir)
1019
1020 def testA(self):
1021 name = 'sub.test.example.'
1022 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
1023 query = dns.message.make_query(name, 'A', want_dnssec=True)
1024 query.flags |= dns.flags.CD
1025 res = self.sendUDPQuery(query)
1026 self.assertRRsetInAnswer(res, expected)
1027
1028 # check the protobuf messages corresponding to the UDP query and answer
1029 msg = self.getFirstProtobufMessage()
1030 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
1031
1032 # then the response
1033 msg = self.getFirstProtobufMessage()
1034 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
fe1a9389 1035 self.checkProtobufPolicy(msg, dnsmessage_pb2.PBDNSMessage.PolicyType.QNAME, 'zone.rpz.', '*.test.example.', 'sub.test.example', dnsmessage_pb2.PBDNSMessage.PolicyKind.NoAction)
b502d522 1036 self.checkProtobufTags(msg, self._tags + self._tags_from_gettag + self._tags_from_rpz)
4bfebc93 1037 self.assertEqual(len(msg.response.rrs), 1)
b502d522
RG
1038 rr = msg.response.rrs[0]
1039 # we have max-cache-ttl set to 15
1040 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
4bfebc93 1041 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
b502d522 1042 self.checkNoRemainingMessage()
3211bbaf
CHB
1043
1044
1045class ProtobufMetaFFITest(TestRecursorProtobuf):
1046 """
1047 This test makes sure that we can correctly add extra meta fields (FFI version).
1048 """
1049 _confdir = 'ProtobufMetaFFITest'
1050 _config_template = """
1051auth-zones=example=configs/%s/example.zone""" % _confdir
1052 _lua_config_file = """
1053 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1054 """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
1055 _lua_dns_script_file = """
1056 local ffi = require("ffi")
1057
1058 ffi.cdef[[
1059 typedef struct pdns_ffi_param pdns_ffi_param_t;
1060
1061 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
1062 void pdns_ffi_param_add_meta_single_string_kv(pdns_ffi_param_t *ref, const char* key, const char* val);
1063 void pdns_ffi_param_add_meta_single_int64_kv(pdns_ffi_param_t *ref, const char* key, int64_t val);
1064 ]]
1065
1066 function gettag_ffi(obj)
1067 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
1068 if qname == 'meta.example' then
1069 ffi.C.pdns_ffi_param_add_meta_single_string_kv(obj, "meta-str", "keyword")
1070 ffi.C.pdns_ffi_param_add_meta_single_int64_kv(obj, "meta-int", 42)
1071 ffi.C.pdns_ffi_param_add_meta_single_string_kv(obj, "meta-str", "content")
1072 ffi.C.pdns_ffi_param_add_meta_single_int64_kv(obj, "meta-int", 21)
1073 end
1074 return 0
1075 end
1076 """
1077 def testMeta(self):
1078 name = 'meta.example.'
1079 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.85')
1080 query = dns.message.make_query(name, 'A', want_dnssec=True)
1081 query.flags |= dns.flags.CD
1082 res = self.sendUDPQuery(query)
1083 self.assertRRsetInAnswer(res, expected)
1084
1085 # check the protobuf messages corresponding to the UDP query and answer
1086 msg = self.getFirstProtobufMessage()
1087 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
1088 self.checkProtobufMetas(msg, {'meta-str': { "stringVal" : ["content", "keyword"]}, 'meta-int': {"intVal" : [21, 42]}})
1089
1090 # then the response
1091 msg = self.getFirstProtobufMessage()
1092 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
1093 self.assertEqual(len(msg.response.rrs), 1)
1094 rr = msg.response.rrs[0]
1095 # we have max-cache-ttl set to 15
1096 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
1097 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.85')
1098 self.checkProtobufMetas(msg, {'meta-str': { "stringVal" : ["content", "keyword"]}, 'meta-int': {"intVal" : [21, 42]}})
1099
1100 self.checkNoRemainingMessage()