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