]>
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 | |
9 | ||
10 | # Python2/3 compatibility hacks | |
11 | if sys.version_info[0] == 2: | |
12 | from Queue import Queue | |
13 | range = xrange | |
14 | else: | |
15 | from queue import Queue | |
16 | range = range # allow re-export of the builtin name | |
17 | ||
18 | from recursortests import RecursorTest | |
19 | ||
f1c7929a RG |
20 | def ProtobufConnectionHandler(queue, conn): |
21 | data = None | |
22 | while True: | |
23 | data = conn.recv(2) | |
24 | if not data: | |
25 | break | |
26 | (datalen,) = struct.unpack("!H", data) | |
27 | data = conn.recv(datalen) | |
28 | if not data: | |
29 | break | |
30 | ||
31 | queue.put(data, True, timeout=2.0) | |
32 | ||
33 | conn.close() | |
34 | ||
b773359c | 35 | def ProtobufListener(queue, port): |
f1c7929a RG |
36 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
37 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) | |
38 | try: | |
39 | sock.bind(("127.0.0.1", port)) | |
40 | except socket.error as e: | |
41 | print("Error binding in the protobuf listener: %s" % str(e)) | |
42 | sys.exit(1) | |
43 | ||
44 | sock.listen(100) | |
45 | while True: | |
46 | try: | |
47 | (conn, _) = sock.accept() | |
48 | thread = threading.Thread(name='Connection Handler', | |
49 | target=ProtobufConnectionHandler, | |
b773359c | 50 | args=[queue, conn]) |
f1c7929a RG |
51 | thread.setDaemon(True) |
52 | thread.start() | |
53 | ||
54 | except socket.error as e: | |
55 | print('Error in protobuf socket: %s' % str(e)) | |
56 | ||
57 | sock.close() | |
58 | ||
59 | ||
b773359c RG |
60 | class ProtobufServerParams: |
61 | def __init__(self, port): | |
62 | self.queue = Queue() | |
63 | self.port = port | |
64 | ||
65 | protobufServersParameters = [ProtobufServerParams(4243), ProtobufServerParams(4244)] | |
66 | protobufListeners = [] | |
67 | for param in protobufServersParameters: | |
68 | listener = threading.Thread(name='Protobuf Listener', target=ProtobufListener, args=[param.queue, param.port]) | |
69 | listener.setDaemon(True) | |
70 | listener.start() | |
71 | protobufListeners.append(listener) | |
f1c7929a RG |
72 | |
73 | class TestRecursorProtobuf(RecursorTest): | |
74 | ||
f1c7929a | 75 | _lua_config_file = """ |
b773359c RG |
76 | protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}) |
77 | """ % (protobufServersParameters[0].port, protobufServersParameters[1].port) | |
f1c7929a RG |
78 | |
79 | ||
80 | def getFirstProtobufMessage(self, retries=1, waitTime=1): | |
b773359c RG |
81 | msg = None |
82 | ||
83 | print("in getFirstProtobufMessage") | |
84 | for param in protobufServersParameters: | |
85 | print(param.port) | |
86 | failed = 0 | |
87 | ||
88 | while param.queue.empty: | |
89 | print(failed) | |
90 | print(retries) | |
91 | if failed >= retries: | |
92 | break | |
93 | ||
94 | failed = failed + 1 | |
95 | print("waiting") | |
96 | time.sleep(waitTime) | |
97 | ||
98 | self.assertFalse(param.queue.empty()) | |
99 | data = param.queue.get(False) | |
100 | self.assertTrue(data) | |
101 | oldmsg = msg | |
102 | msg = dnsmessage_pb2.PBDNSMessage() | |
103 | msg.ParseFromString(data) | |
104 | if oldmsg is not None: | |
105 | self.assertEquals(msg, oldmsg) | |
f1c7929a | 106 | |
f1c7929a RG |
107 | return msg |
108 | ||
109 | def checkNoRemainingMessage(self): | |
b773359c RG |
110 | for param in protobufServersParameters: |
111 | self.assertTrue(param.queue.empty()) | |
f1c7929a | 112 | |
0bd2e252 | 113 | def checkProtobufBase(self, msg, protocol, query, initiator, normalQueryResponse=True, expectedECS=None, receivedSize=None): |
f1c7929a RG |
114 | self.assertTrue(msg) |
115 | self.assertTrue(msg.HasField('timeSec')) | |
116 | self.assertTrue(msg.HasField('socketFamily')) | |
117 | self.assertEquals(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET) | |
118 | self.assertTrue(msg.HasField('from')) | |
119 | fromvalue = getattr(msg, 'from') | |
120 | self.assertEquals(socket.inet_ntop(socket.AF_INET, fromvalue), initiator) | |
121 | self.assertTrue(msg.HasField('socketProtocol')) | |
122 | self.assertEquals(msg.socketProtocol, protocol) | |
123 | self.assertTrue(msg.HasField('messageId')) | |
c165308b | 124 | self.assertTrue(msg.HasField('serverIdentity')) |
f1c7929a RG |
125 | self.assertTrue(msg.HasField('id')) |
126 | self.assertEquals(msg.id, query.id) | |
127 | self.assertTrue(msg.HasField('inBytes')) | |
128 | if normalQueryResponse: | |
129 | # compare inBytes with length of query/response | |
0bd2e252 RG |
130 | # Note that for responses, the size we received might differ |
131 | # because dnspython might compress labels differently from | |
132 | # the recursor | |
133 | if receivedSize: | |
134 | self.assertEquals(msg.inBytes, receivedSize) | |
135 | else: | |
136 | self.assertEquals(msg.inBytes, len(query.to_wire())) | |
f1c7929a RG |
137 | if expectedECS is not None: |
138 | self.assertTrue(msg.HasField('originalRequestorSubnet')) | |
139 | # v4 only for now | |
140 | self.assertEquals(len(msg.originalRequestorSubnet), 4) | |
141 | self.assertEquals(socket.inet_ntop(socket.AF_INET, msg.originalRequestorSubnet), '127.0.0.1') | |
142 | ||
5dbc7fbe CHB |
143 | def checkOutgoingProtobufBase(self, msg, protocol, query, initiator): |
144 | self.assertTrue(msg) | |
145 | self.assertTrue(msg.HasField('timeSec')) | |
146 | self.assertTrue(msg.HasField('socketFamily')) | |
147 | self.assertEquals(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET) | |
148 | self.assertTrue(msg.HasField('socketProtocol')) | |
149 | self.assertEquals(msg.socketProtocol, protocol) | |
150 | self.assertTrue(msg.HasField('messageId')) | |
c165308b | 151 | self.assertTrue(msg.HasField('serverIdentity')) |
5dbc7fbe CHB |
152 | self.assertTrue(msg.HasField('id')) |
153 | self.assertNotEquals(msg.id, query.id) | |
154 | self.assertTrue(msg.HasField('inBytes')) | |
155 | # compare inBytes with length of query/response | |
156 | self.assertEquals(msg.inBytes, len(query.to_wire())) | |
157 | ||
f1c7929a RG |
158 | def checkProtobufQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1'): |
159 | self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSQueryType) | |
160 | self.checkProtobufBase(msg, protocol, query, initiator) | |
161 | # dnsdist doesn't fill the responder field for responses | |
162 | # because it doesn't keep the information around. | |
163 | self.assertTrue(msg.HasField('to')) | |
164 | self.assertEquals(socket.inet_ntop(socket.AF_INET, msg.to), '127.0.0.1') | |
165 | self.assertTrue(msg.HasField('question')) | |
166 | self.assertTrue(msg.question.HasField('qClass')) | |
167 | self.assertEquals(msg.question.qClass, qclass) | |
168 | self.assertTrue(msg.question.HasField('qType')) | |
169 | self.assertEquals(msg.question.qClass, qtype) | |
170 | self.assertTrue(msg.question.HasField('qName')) | |
171 | self.assertEquals(msg.question.qName, qname) | |
172 | ||
0bd2e252 | 173 | def checkProtobufResponse(self, msg, protocol, response, initiator='127.0.0.1', receivedSize=None): |
f1c7929a | 174 | self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSResponseType) |
0bd2e252 | 175 | self.checkProtobufBase(msg, protocol, response, initiator, receivedSize=receivedSize) |
f1c7929a RG |
176 | self.assertTrue(msg.HasField('response')) |
177 | self.assertTrue(msg.response.HasField('queryTimeSec')) | |
178 | ||
0bd2e252 | 179 | def checkProtobufResponseRecord(self, record, rclass, rtype, rname, rttl, checkTTL=True): |
f1c7929a RG |
180 | self.assertTrue(record.HasField('class')) |
181 | self.assertEquals(getattr(record, 'class'), rclass) | |
182 | self.assertTrue(record.HasField('type')) | |
183 | self.assertEquals(record.type, rtype) | |
184 | self.assertTrue(record.HasField('name')) | |
185 | self.assertEquals(record.name, rname) | |
186 | self.assertTrue(record.HasField('ttl')) | |
0bd2e252 RG |
187 | if checkTTL: |
188 | self.assertEquals(record.ttl, rttl) | |
f1c7929a RG |
189 | self.assertTrue(record.HasField('rdata')) |
190 | ||
191 | def checkProtobufPolicy(self, msg, policyType, reason): | |
192 | self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSResponseType) | |
193 | self.assertTrue(msg.response.HasField('appliedPolicyType')) | |
194 | self.assertTrue(msg.response.HasField('appliedPolicy')) | |
195 | self.assertEquals(msg.response.appliedPolicy, reason) | |
196 | self.assertEquals(msg.response.appliedPolicyType, policyType) | |
197 | ||
198 | def checkProtobufTags(self, msg, tags): | |
199 | self.assertEquals(len(msg.response.tags), len(tags)) | |
200 | for tag in msg.response.tags: | |
201 | self.assertTrue(tag in tags) | |
202 | ||
5dbc7fbe CHB |
203 | def checkProtobufOutgoingQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1'): |
204 | self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSOutgoingQueryType) | |
205 | self.checkOutgoingProtobufBase(msg, protocol, query, initiator) | |
206 | self.assertTrue(msg.HasField('to')) | |
207 | self.assertTrue(msg.HasField('question')) | |
208 | self.assertTrue(msg.question.HasField('qClass')) | |
209 | self.assertEquals(msg.question.qClass, qclass) | |
210 | self.assertTrue(msg.question.HasField('qType')) | |
211 | self.assertEquals(msg.question.qClass, qtype) | |
212 | self.assertTrue(msg.question.HasField('qName')) | |
213 | self.assertEquals(msg.question.qName, qname) | |
214 | ||
215 | def checkProtobufIncomingResponse(self, msg, protocol, response, initiator='127.0.0.1'): | |
216 | self.assertEquals(msg.type, dnsmessage_pb2.PBDNSMessage.DNSIncomingResponseType) | |
217 | self.checkOutgoingProtobufBase(msg, protocol, response, initiator) | |
218 | self.assertTrue(msg.HasField('response')) | |
219 | self.assertTrue(msg.response.HasField('queryTimeSec')) | |
220 | ||
f1c7929a RG |
221 | @classmethod |
222 | def setUpClass(cls): | |
223 | ||
f1c7929a RG |
224 | cls.setUpSockets() |
225 | ||
226 | cls.startResponders() | |
227 | ||
228 | confdir = os.path.join('configs', cls._confdir) | |
229 | cls.createConfigDir(confdir) | |
230 | ||
231 | cls.generateRecursorConfig(confdir) | |
232 | cls.startRecursor(confdir, cls._recursorPort) | |
233 | ||
234 | def setUp(self): | |
235 | # Make sure the queue is empty, in case | |
236 | # a previous test failed | |
b773359c RG |
237 | for param in protobufServersParameters: |
238 | while not param.queue.empty(): | |
239 | param.queue.get(False) | |
f1c7929a RG |
240 | |
241 | @classmethod | |
242 | def generateRecursorConfig(cls, confdir): | |
243 | authzonepath = os.path.join(confdir, 'example.zone') | |
244 | with open(authzonepath, 'w') as authzone: | |
245 | authzone.write("""$ORIGIN example. | |
246 | @ 3600 IN SOA {soa} | |
247 | a 3600 IN A 192.0.2.42 | |
248 | tagged 3600 IN A 192.0.2.84 | |
249 | query-selected 3600 IN A 192.0.2.84 | |
250 | answer-selected 3600 IN A 192.0.2.84 | |
0bd2e252 RG |
251 | types 3600 IN A 192.0.2.84 |
252 | types 3600 IN AAAA 2001:DB8::1 | |
253 | types 3600 IN TXT "Lorem ipsum dolor sit amet" | |
254 | types 3600 IN MX 10 a.example. | |
255 | types 3600 IN SPF "v=spf1 -all" | |
256 | types 3600 IN SRV 10 20 443 a.example. | |
257 | cname 3600 IN CNAME a.example. | |
258 | ||
f1c7929a RG |
259 | """.format(soa=cls._SOA)) |
260 | super(TestRecursorProtobuf, cls).generateRecursorConfig(confdir) | |
261 | ||
262 | @classmethod | |
263 | def tearDownClass(cls): | |
264 | cls.tearDownRecursor() | |
265 | ||
266 | class ProtobufDefaultTest(TestRecursorProtobuf): | |
267 | """ | |
268 | This test makes sure that we correctly export queries and response over protobuf. | |
269 | """ | |
270 | ||
271 | _confdir = 'ProtobufDefault' | |
272 | _config_template = """ | |
273 | auth-zones=example=configs/%s/example.zone""" % _confdir | |
274 | ||
275 | def testA(self): | |
276 | name = 'a.example.' | |
277 | expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42') | |
278 | query = dns.message.make_query(name, 'A', want_dnssec=True) | |
279 | query.flags |= dns.flags.CD | |
280 | res = self.sendUDPQuery(query) | |
0bd2e252 | 281 | |
f1c7929a RG |
282 | self.assertRRsetInAnswer(res, expected) |
283 | ||
284 | # check the protobuf messages corresponding to the UDP query and answer | |
285 | msg = self.getFirstProtobufMessage() | |
286 | self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name) | |
287 | # then the response | |
288 | msg = self.getFirstProtobufMessage() | |
0bd2e252 | 289 | self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1') |
f1c7929a RG |
290 | self.assertEquals(len(msg.response.rrs), 1) |
291 | rr = msg.response.rrs[0] | |
292 | # we have max-cache-ttl set to 15 | |
293 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15) | |
294 | self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42') | |
295 | self.checkNoRemainingMessage() | |
296 | ||
0bd2e252 RG |
297 | def testCNAME(self): |
298 | name = 'cname.example.' | |
299 | expectedCNAME = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'CNAME', 'a.example.') | |
300 | expectedA = dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42') | |
301 | query = dns.message.make_query(name, 'A', want_dnssec=True) | |
302 | query.flags |= dns.flags.CD | |
303 | raw = self.sendUDPQuery(query, decode=False) | |
304 | res = dns.message.from_wire(raw) | |
305 | self.assertRRsetInAnswer(res, expectedCNAME) | |
306 | self.assertRRsetInAnswer(res, expectedA) | |
307 | ||
308 | # check the protobuf messages corresponding to the UDP query and answer | |
309 | # but first let the protobuf messages the time to get there | |
310 | msg = self.getFirstProtobufMessage() | |
311 | self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name) | |
312 | # then the response | |
313 | msg = self.getFirstProtobufMessage() | |
314 | self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1', receivedSize=len(raw)) | |
315 | self.assertEquals(len(msg.response.rrs), 2) | |
316 | rr = msg.response.rrs[0] | |
317 | # we don't want to check the TTL for the A record, it has been cached by the previous test | |
318 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.CNAME, name, 15) | |
319 | self.assertEquals(rr.rdata, 'a.example.') | |
320 | rr = msg.response.rrs[1] | |
321 | # we have max-cache-ttl set to 15 | |
322 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, 'a.example.', 15, checkTTL=False) | |
323 | self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42') | |
324 | self.checkNoRemainingMessage() | |
325 | ||
5dbc7fbe CHB |
326 | class OutgoingProtobufDefaultTest(TestRecursorProtobuf): |
327 | """ | |
328 | This test makes sure that we correctly export outgoing queries over protobuf. | |
329 | It must be improved and setup env so we can check for incoming responses, but makes sure for now | |
330 | that the recursor at least connects to the protobuf server. | |
331 | """ | |
332 | ||
333 | _confdir = 'OutgoingProtobufDefault' | |
334 | _config_template = """ | |
335 | auth-zones=example=configs/%s/example.zone""" % _confdir | |
336 | _lua_config_file = """ | |
b773359c RG |
337 | outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}) |
338 | """ % (protobufServersParameters[0].port, protobufServersParameters[1].port) | |
5dbc7fbe CHB |
339 | |
340 | def testA(self): | |
341 | name = 'www.example.org.' | |
342 | expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42') | |
343 | query = dns.message.make_query(name, 'A', want_dnssec=True) | |
344 | query.flags |= dns.flags.RD | |
345 | res = self.sendUDPQuery(query) | |
346 | ||
347 | # check the protobuf messages corresponding to the UDP query and answer | |
348 | msg = self.getFirstProtobufMessage() | |
349 | self.checkProtobufOutgoingQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name) | |
350 | # # then the response | |
351 | # msg = self.getFirstProtobufMessage() | |
352 | # self.checkProtobufIncomingResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res) | |
353 | self.checkNoRemainingMessage() | |
354 | ||
f1c7929a RG |
355 | class ProtobufMasksTest(TestRecursorProtobuf): |
356 | """ | |
357 | This test makes sure that we correctly export queries and response over protobuf, respecting the configured initiator masking. | |
358 | """ | |
359 | ||
360 | _confdir = 'ProtobufMasks' | |
361 | _config_template = """ | |
362 | auth-zones=example=configs/%s/example.zone""" % _confdir | |
f1c7929a RG |
363 | _protobufMaskV4 = 4 |
364 | _protobufMaskV6 = 128 | |
365 | _lua_config_file = """ | |
b773359c | 366 | protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}) |
f1c7929a | 367 | setProtobufMasks(%d, %d) |
b773359c | 368 | """ % (protobufServersParameters[0].port, protobufServersParameters[1].port, _protobufMaskV4, _protobufMaskV6) |
f1c7929a RG |
369 | |
370 | def testA(self): | |
371 | name = 'a.example.' | |
372 | expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42') | |
373 | query = dns.message.make_query(name, 'A', want_dnssec=True) | |
374 | query.flags |= dns.flags.CD | |
375 | res = self.sendUDPQuery(query) | |
376 | self.assertRRsetInAnswer(res, expected) | |
377 | ||
378 | # check the protobuf messages corresponding to the UDP query and answer | |
379 | # but first let the protobuf messages the time to get there | |
380 | msg = self.getFirstProtobufMessage() | |
381 | self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '112.0.0.0') | |
382 | # then the response | |
383 | msg = self.getFirstProtobufMessage() | |
384 | self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '112.0.0.0') | |
385 | self.assertEquals(len(msg.response.rrs), 1) | |
386 | rr = msg.response.rrs[0] | |
387 | # we have max-cache-ttl set to 15 | |
388 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15) | |
389 | self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42') | |
390 | self.checkNoRemainingMessage() | |
391 | ||
392 | class ProtobufQueriesOnlyTest(TestRecursorProtobuf): | |
393 | """ | |
394 | This test makes sure that we correctly export queries but not responses over protobuf. | |
395 | """ | |
396 | ||
397 | _confdir = 'ProtobufQueriesOnly' | |
398 | _config_template = """ | |
399 | auth-zones=example=configs/%s/example.zone""" % _confdir | |
f1c7929a | 400 | _lua_config_file = """ |
b773359c RG |
401 | protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=false } ) |
402 | """ % (protobufServersParameters[0].port, protobufServersParameters[1].port) | |
f1c7929a RG |
403 | |
404 | def testA(self): | |
405 | name = 'a.example.' | |
406 | expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42') | |
407 | query = dns.message.make_query(name, 'A', want_dnssec=True) | |
408 | query.flags |= dns.flags.CD | |
409 | res = self.sendUDPQuery(query) | |
410 | self.assertRRsetInAnswer(res, expected) | |
411 | ||
412 | # check the protobuf message corresponding to the UDP query | |
413 | msg = self.getFirstProtobufMessage() | |
414 | self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name) | |
415 | # no response | |
416 | self.checkNoRemainingMessage() | |
417 | ||
418 | class ProtobufResponsesOnlyTest(TestRecursorProtobuf): | |
419 | """ | |
420 | This test makes sure that we correctly export responses but not queries over protobuf. | |
421 | """ | |
422 | ||
423 | _confdir = 'ProtobufResponsesOnly' | |
424 | _config_template = """ | |
425 | auth-zones=example=configs/%s/example.zone""" % _confdir | |
f1c7929a | 426 | _lua_config_file = """ |
b773359c RG |
427 | protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true } ) |
428 | """ % (protobufServersParameters[0].port, protobufServersParameters[1].port) | |
f1c7929a RG |
429 | |
430 | def testA(self): | |
431 | name = 'a.example.' | |
432 | expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42') | |
433 | query = dns.message.make_query(name, 'A', want_dnssec=True) | |
434 | query.flags |= dns.flags.CD | |
435 | res = self.sendUDPQuery(query) | |
436 | self.assertRRsetInAnswer(res, expected) | |
437 | ||
438 | # check the protobuf message corresponding to the UDP response | |
439 | msg = self.getFirstProtobufMessage() | |
440 | self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res) | |
441 | self.assertEquals(len(msg.response.rrs), 1) | |
442 | rr = msg.response.rrs[0] | |
443 | # we have max-cache-ttl set to 15 | |
444 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15) | |
445 | self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42') | |
446 | # nothing else in the queue | |
447 | self.checkNoRemainingMessage() | |
448 | ||
449 | class ProtobufTaggedOnlyTest(TestRecursorProtobuf): | |
450 | """ | |
451 | This test makes sure that we correctly export queries and responses but only if they have been tagged. | |
452 | """ | |
453 | ||
454 | _confdir = 'ProtobufTaggedOnly' | |
455 | _config_template = """ | |
456 | auth-zones=example=configs/%s/example.zone""" % _confdir | |
f1c7929a | 457 | _lua_config_file = """ |
b773359c RG |
458 | protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true, taggedOnly=true } ) |
459 | """ % (protobufServersParameters[0].port, protobufServersParameters[1].port) | |
f1c7929a RG |
460 | _tags = ['tag1', 'tag2'] |
461 | _tag_from_gettag = 'tag-from-gettag' | |
462 | _lua_dns_script_file = """ | |
463 | function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp) | |
464 | if qname:equal('tagged.example.') then | |
465 | return 0, { '%s' } | |
466 | end | |
467 | return 0 | |
468 | end | |
469 | function preresolve(dq) | |
470 | if dq.qname:equal('tagged.example.') then | |
471 | dq:addPolicyTag('%s') | |
472 | dq:addPolicyTag('%s') | |
473 | end | |
474 | return false | |
475 | end | |
476 | """ % (_tag_from_gettag, _tags[0], _tags[1]) | |
477 | ||
478 | def testA(self): | |
479 | name = 'a.example.' | |
480 | expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42') | |
481 | query = dns.message.make_query(name, 'A', want_dnssec=True) | |
482 | query.flags |= dns.flags.CD | |
483 | res = self.sendUDPQuery(query) | |
484 | self.assertRRsetInAnswer(res, expected) | |
485 | ||
486 | # check the protobuf message corresponding to the UDP response | |
487 | # the first query and answer are not tagged, so there is nothing in the queue | |
488 | time.sleep(1) | |
489 | self.checkNoRemainingMessage() | |
490 | ||
491 | def testTagged(self): | |
492 | name = 'tagged.example.' | |
493 | expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84') | |
494 | query = dns.message.make_query(name, 'A', want_dnssec=True) | |
495 | query.flags |= dns.flags.CD | |
496 | res = self.sendUDPQuery(query) | |
497 | self.assertRRsetInAnswer(res, expected) | |
498 | ||
499 | # check the protobuf messages corresponding to the UDP query and answer | |
500 | msg = self.getFirstProtobufMessage() | |
501 | self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name) | |
502 | self.checkProtobufTags(msg, [self._tag_from_gettag]) | |
503 | # then the response | |
504 | msg = self.getFirstProtobufMessage() | |
505 | self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res) | |
506 | self.assertEquals(len(msg.response.rrs), 1) | |
507 | rr = msg.response.rrs[0] | |
508 | # we have max-cache-ttl set to 15 | |
509 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15) | |
510 | self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84') | |
511 | tags = [self._tag_from_gettag] + self._tags | |
512 | self.checkProtobufTags(msg, tags) | |
513 | self.checkNoRemainingMessage() | |
514 | ||
515 | class ProtobufSelectedFromLuaTest(TestRecursorProtobuf): | |
516 | """ | |
517 | This test makes sure that we correctly export queries and responses but only if they have been selected from Lua. | |
518 | """ | |
519 | ||
520 | _confdir = 'ProtobufSelectedFromLua' | |
521 | _config_template = """ | |
522 | auth-zones=example=configs/%s/example.zone""" % _confdir | |
f1c7929a | 523 | _lua_config_file = """ |
b773359c RG |
524 | protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=false } ) |
525 | """ % (protobufServersParameters[0].port, protobufServersParameters[1].port) | |
f1c7929a RG |
526 | _lua_dns_script_file = """ |
527 | local ffi = require("ffi") | |
528 | ||
529 | ffi.cdef[[ | |
530 | typedef struct pdns_ffi_param pdns_ffi_param_t; | |
531 | ||
532 | const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref); | |
533 | void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery); | |
534 | ]] | |
535 | ||
536 | function gettag_ffi(obj) | |
537 | qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj)) | |
538 | if qname == 'query-selected.example' then | |
539 | ffi.C.pdns_ffi_param_set_log_query(obj, true) | |
540 | end | |
541 | return 0 | |
542 | end | |
543 | ||
544 | function preresolve(dq) | |
545 | if dq.qname:equal('answer-selected.example.') then | |
546 | dq.logResponse = true | |
547 | end | |
548 | return false | |
549 | end | |
550 | """ | |
551 | ||
552 | def testA(self): | |
553 | name = 'a.example.' | |
554 | expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42') | |
555 | query = dns.message.make_query(name, 'A', want_dnssec=True) | |
556 | query.flags |= dns.flags.CD | |
557 | res = self.sendUDPQuery(query) | |
558 | self.assertRRsetInAnswer(res, expected) | |
559 | ||
560 | # check the protobuf message corresponding to the UDP response | |
561 | # the first query and answer are not selected, so there is nothing in the queue | |
562 | self.checkNoRemainingMessage() | |
563 | ||
564 | def testQuerySelected(self): | |
565 | name = 'query-selected.example.' | |
566 | expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84') | |
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 messages 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 | # there should be no response | |
576 | self.checkNoRemainingMessage() | |
577 | ||
578 | def testResponseSelected(self): | |
579 | name = 'answer-selected.example.' | |
580 | expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84') | |
581 | query = dns.message.make_query(name, 'A', want_dnssec=True) | |
582 | query.flags |= dns.flags.CD | |
583 | res = self.sendUDPQuery(query) | |
584 | self.assertRRsetInAnswer(res, expected) | |
585 | ||
586 | # check the protobuf messages corresponding to the UDP response | |
587 | msg = self.getFirstProtobufMessage() | |
588 | self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res) | |
589 | self.assertEquals(len(msg.response.rrs), 1) | |
590 | rr = msg.response.rrs[0] | |
591 | # we have max-cache-ttl set to 15 | |
592 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15) | |
593 | self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84') | |
594 | self.checkNoRemainingMessage() | |
0bd2e252 RG |
595 | |
596 | class ProtobufExportTypesTest(TestRecursorProtobuf): | |
597 | """ | |
598 | This test makes sure that we correctly export other types than A, AAAA and CNAME over protobuf. | |
599 | """ | |
600 | ||
601 | _confdir = 'ProtobufExportTypes' | |
602 | _config_template = """ | |
603 | auth-zones=example=configs/%s/example.zone""" % _confdir | |
0bd2e252 | 604 | _lua_config_file = """ |
b773359c RG |
605 | protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { exportTypes={"AAAA", "MX", "SPF", "SRV", "TXT"} } ) |
606 | """ % (protobufServersParameters[0].port, protobufServersParameters[1].port) | |
0bd2e252 RG |
607 | |
608 | def testA(self): | |
609 | name = 'types.example.' | |
610 | expected = [dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84'), | |
611 | dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'AAAA', '2001:DB8::1'), | |
612 | dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'MX', '10 a.example.'), | |
613 | dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'SPF', '"v=spf1 -all"'), | |
614 | dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'SRV', '10 20 443 a.example.'), | |
615 | dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'TXT', '"Lorem ipsum dolor sit amet"'), | |
616 | ] | |
617 | query = dns.message.make_query(name, 'ANY', want_dnssec=True) | |
618 | query.flags |= dns.flags.CD | |
619 | raw = self.sendUDPQuery(query, decode=False) | |
620 | res = dns.message.from_wire(raw) | |
621 | ||
622 | for rrset in expected: | |
623 | self.assertRRsetInAnswer(res, rrset) | |
624 | ||
625 | # check the protobuf messages corresponding to the UDP query and answer | |
626 | msg = self.getFirstProtobufMessage() | |
627 | self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name) | |
628 | # then the response | |
629 | msg = self.getFirstProtobufMessage() | |
630 | self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res, '127.0.0.1', receivedSize=len(raw)) | |
631 | self.assertEquals(len(msg.response.rrs), 5) | |
632 | for rr in msg.response.rrs: | |
633 | self.assertTrue(rr.type in [dns.rdatatype.AAAA, dns.rdatatype.TXT, dns.rdatatype.MX, dns.rdatatype.SPF, dns.rdatatype.SRV]) | |
634 | ||
635 | if rr.type == dns.rdatatype.AAAA: | |
636 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.AAAA, name, 15) | |
637 | self.assertEquals(socket.inet_ntop(socket.AF_INET6, rr.rdata), '2001:db8::1') | |
638 | elif rr.type == dns.rdatatype.TXT: | |
639 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.TXT, name, 15) | |
640 | self.assertEquals(rr.rdata, '"Lorem ipsum dolor sit amet"') | |
641 | elif rr.type == dns.rdatatype.MX: | |
642 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.MX, name, 15) | |
643 | self.assertEquals(rr.rdata, 'a.example.') | |
644 | elif rr.type == dns.rdatatype.SPF: | |
645 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.SPF, name, 15) | |
646 | self.assertEquals(rr.rdata, '"v=spf1 -all"') | |
647 | elif rr.type == dns.rdatatype.SRV: | |
648 | self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.SRV, name, 15) | |
649 | self.assertEquals(rr.rdata, 'a.example.') | |
650 | ||
651 | self.checkNoRemainingMessage() |