9 import clientsubnetoption
11 # Python2/3 compatibility hacks
13 from queue
import Queue
15 from Queue
import Queue
22 from recursortests
import RecursorTest
24 def ProtobufConnectionHandler(queue
, conn
):
30 (datalen
,) = struct
.unpack("!H", data
)
31 data
= conn
.recv(datalen
)
35 queue
.put(data
, True, timeout
=2.0)
39 def ProtobufListener(queue
, port
):
40 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
41 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
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
))
51 (conn
, _
) = sock
.accept()
52 thread
= threading
.Thread(name
='Connection Handler',
53 target
=ProtobufConnectionHandler
,
55 thread
.setDaemon(True)
58 except socket
.error
as e
:
59 print('Error in protobuf socket: %s' % str(e
))
64 class ProtobufServerParams
:
65 def __init__(self
, port
):
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)
75 protobufListeners
.append(listener
)
77 class TestRecursorProtobuf(RecursorTest
):
79 _lua_config_file
= """
80 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
81 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
87 'zones': ['secure.example', 'islandofsecurity.example']},
89 'zones': ['example']},
94 def getFirstProtobufMessage(self
, retries
=1, waitTime
=1):
97 print("in getFirstProtobufMessage")
98 for param
in protobufServersParameters
:
102 while param
.queue
.empty
:
105 if failed
>= retries
:
112 self
.assertFalse(param
.queue
.empty())
113 data
= param
.queue
.get(False)
114 self
.assertTrue(data
)
116 msg
= dnsmessage_pb2
.PBDNSMessage()
117 msg
.ParseFromString(data
)
118 if oldmsg
is not None:
119 self
.assertEqual(msg
, oldmsg
)
124 def checkNoRemainingMessage(self
):
125 for param
in protobufServersParameters
:
126 self
.assertTrue(param
.queue
.empty())
128 def checkProtobufBase(self
, msg
, protocol
, query
, initiator
, normalQueryResponse
=True, expectedECS
=None, receivedSize
=None):
130 self
.assertTrue(msg
.HasField('timeSec'))
131 self
.assertTrue(msg
.HasField('socketFamily'))
132 self
.assertEqual(msg
.socketFamily
, dnsmessage_pb2
.PBDNSMessage
.INET
)
133 self
.assertTrue(msg
.HasField('from'))
134 fromvalue
= getattr(msg
, 'from')
135 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, fromvalue
), initiator
)
136 self
.assertTrue(msg
.HasField('socketProtocol'))
137 self
.assertEqual(msg
.socketProtocol
, protocol
)
138 self
.assertTrue(msg
.HasField('messageId'))
139 self
.assertTrue(msg
.HasField('serverIdentity'))
140 self
.assertTrue(msg
.HasField('id'))
141 self
.assertEqual(msg
.id, query
.id)
142 self
.assertTrue(msg
.HasField('inBytes'))
143 if normalQueryResponse
:
144 # compare inBytes with length of query/response
145 # Note that for responses, the size we received might differ
146 # because dnspython might compress labels differently from
149 self
.assertEqual(msg
.inBytes
, receivedSize
)
151 self
.assertEqual(msg
.inBytes
, len(query
.to_wire()))
152 if expectedECS
is not None:
153 self
.assertTrue(msg
.HasField('originalRequestorSubnet'))
155 self
.assertEqual(len(msg
.originalRequestorSubnet
), 4)
156 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, msg
.originalRequestorSubnet
), '127.0.0.1')
158 def checkOutgoingProtobufBase(self
, msg
, protocol
, query
, initiator
, length
=None, expectedECS
=None):
160 self
.assertTrue(msg
.HasField('timeSec'))
161 self
.assertTrue(msg
.HasField('socketFamily'))
162 self
.assertEqual(msg
.socketFamily
, dnsmessage_pb2
.PBDNSMessage
.INET
)
163 self
.assertTrue(msg
.HasField('socketProtocol'))
164 self
.assertEqual(msg
.socketProtocol
, protocol
)
165 self
.assertTrue(msg
.HasField('messageId'))
166 self
.assertTrue(msg
.HasField('serverIdentity'))
167 self
.assertTrue(msg
.HasField('id'))
168 self
.assertNotEqual(msg
.id, query
.id)
169 self
.assertTrue(msg
.HasField('inBytes'))
170 if length
is not None:
171 self
.assertEqual(msg
.inBytes
, length
)
173 # compare inBytes with length of query/response
174 self
.assertEqual(msg
.inBytes
, len(query
.to_wire()))
175 if expectedECS
is not None:
176 self
.assertTrue(msg
.HasField('originalRequestorSubnet'))
178 self
.assertEqual(len(msg
.originalRequestorSubnet
), 4)
179 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, msg
.originalRequestorSubnet
), expectedECS
)
181 def checkProtobufQuery(self
, msg
, protocol
, query
, qclass
, qtype
, qname
, initiator
='127.0.0.1', to
='127.0.0.1'):
182 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSQueryType
)
183 self
.checkProtobufBase(msg
, protocol
, query
, initiator
)
184 # dnsdist doesn't fill the responder field for responses
185 # because it doesn't keep the information around.
186 self
.assertTrue(msg
.HasField('to'))
187 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, msg
.to
), to
)
188 self
.assertTrue(msg
.HasField('question'))
189 self
.assertTrue(msg
.question
.HasField('qClass'))
190 self
.assertEqual(msg
.question
.qClass
, qclass
)
191 self
.assertTrue(msg
.question
.HasField('qType'))
192 self
.assertEqual(msg
.question
.qClass
, qtype
)
193 self
.assertTrue(msg
.question
.HasField('qName'))
194 self
.assertEqual(msg
.question
.qName
, qname
)
196 def checkProtobufResponse(self
, msg
, protocol
, response
, initiator
='127.0.0.1', receivedSize
=None, vstate
=dnsmessage_pb2
.PBDNSMessage
.VState
.Indeterminate
):
197 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSResponseType
)
198 self
.checkProtobufBase(msg
, protocol
, response
, initiator
, receivedSize
=receivedSize
)
199 self
.assertTrue(msg
.HasField('response'))
200 self
.assertTrue(msg
.response
.HasField('queryTimeSec'))
201 self
.assertTrue(msg
.response
.HasField('validationState'))
202 self
.assertEqual(msg
.response
.validationState
, vstate
)
204 def checkProtobufResponseRecord(self
, record
, rclass
, rtype
, rname
, rttl
, checkTTL
=True):
205 self
.assertTrue(record
.HasField('class'))
206 self
.assertEqual(getattr(record
, 'class'), rclass
)
207 self
.assertTrue(record
.HasField('type'))
208 self
.assertEqual(record
.type, rtype
)
209 self
.assertTrue(record
.HasField('name'))
210 self
.assertEqual(record
.name
, rname
)
211 self
.assertTrue(record
.HasField('ttl'))
213 self
.assertEqual(record
.ttl
, rttl
)
214 self
.assertTrue(record
.HasField('rdata'))
216 def checkProtobufPolicy(self
, msg
, policyType
, reason
, trigger
, hit
, kind
):
217 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSResponseType
)
218 self
.assertTrue(msg
.response
.HasField('appliedPolicyType'))
219 self
.assertTrue(msg
.response
.HasField('appliedPolicy'))
220 self
.assertTrue(msg
.response
.HasField('appliedPolicyTrigger'))
221 self
.assertTrue(msg
.response
.HasField('appliedPolicyHit'))
222 self
.assertTrue(msg
.response
.HasField('appliedPolicyKind'))
223 self
.assertEqual(msg
.response
.appliedPolicy
, reason
)
224 self
.assertEqual(msg
.response
.appliedPolicyType
, policyType
)
225 self
.assertEqual(msg
.response
.appliedPolicyTrigger
, trigger
)
226 self
.assertEqual(msg
.response
.appliedPolicyHit
, hit
)
227 self
.assertEqual(msg
.response
.appliedPolicyKind
, kind
)
229 def checkProtobufTags(self
, msg
, tags
):
232 print(msg
.response
.tags
)
233 self
.assertEqual(len(msg
.response
.tags
), len(tags
))
234 for tag
in msg
.response
.tags
:
235 self
.assertTrue(tag
in tags
)
237 def checkProtobufMetas(self
, msg
, metas
):
241 self
.assertEqual(len(msg
.meta
), len(metas
))
243 self
.assertTrue(m
.HasField('key'))
244 self
.assertTrue(m
.HasField('value'))
245 self
.assertTrue(m
.key
in metas
)
246 for i
in m
.value
.intVal
:
247 self
.assertTrue(i
in metas
[m
.key
]['intVal'])
248 for s
in m
.value
.stringVal
:
249 self
.assertTrue(s
in metas
[m
.key
]['stringVal'])
251 def checkProtobufOutgoingQuery(self
, msg
, protocol
, query
, qclass
, qtype
, qname
, initiator
='127.0.0.1', length
=None, expectedECS
=None):
252 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSOutgoingQueryType
)
253 self
.checkOutgoingProtobufBase(msg
, protocol
, query
, initiator
, length
=length
, expectedECS
=expectedECS
)
254 self
.assertTrue(msg
.HasField('to'))
255 self
.assertTrue(msg
.HasField('question'))
256 self
.assertTrue(msg
.question
.HasField('qClass'))
257 self
.assertEqual(msg
.question
.qClass
, qclass
)
258 self
.assertTrue(msg
.question
.HasField('qType'))
259 self
.assertEqual(msg
.question
.qType
, qtype
)
260 self
.assertTrue(msg
.question
.HasField('qName'))
261 self
.assertEqual(msg
.question
.qName
, qname
)
263 def checkProtobufIncomingResponse(self
, msg
, protocol
, response
, initiator
='127.0.0.1', length
=None):
264 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSIncomingResponseType
)
265 self
.checkOutgoingProtobufBase(msg
, protocol
, response
, initiator
, length
=length
)
266 self
.assertTrue(msg
.HasField('response'))
267 self
.assertTrue(msg
.response
.HasField('rcode'))
268 self
.assertTrue(msg
.response
.HasField('queryTimeSec'))
270 def checkProtobufIncomingNetworkErrorResponse(self
, msg
, protocol
, response
, initiator
='127.0.0.1'):
271 self
.checkProtobufIncomingResponse(msg
, protocol
, response
, initiator
, length
=0)
272 self
.assertEqual(msg
.response
.rcode
, 65536)
274 def checkProtobufIdentity(self
, msg
, requestorId
, deviceId
, deviceName
):
276 self
.assertTrue((requestorId
== '') == (not msg
.HasField('requestorId')))
277 self
.assertTrue((deviceId
== b
'') == (not msg
.HasField('deviceId')))
278 self
.assertTrue((deviceName
== '') == (not msg
.HasField('deviceName')))
279 self
.assertEqual(msg
.requestorId
, requestorId
)
280 self
.assertEqual(msg
.deviceId
, deviceId
)
281 self
.assertEqual(msg
.deviceName
, deviceName
)
284 super(TestRecursorProtobuf
, self
).setUp()
285 # Make sure the queue is empty, in case
286 # a previous test failed
287 for param
in protobufServersParameters
:
288 while not param
.queue
.empty():
289 param
.queue
.get(False)
290 # wait long enough to be sure that the housekeeping has
295 def generateRecursorConfig(cls
, confdir
):
296 authzonepath
= os
.path
.join(confdir
, 'example.zone')
297 with
open(authzonepath
, 'w') as authzone
:
298 authzone
.write("""$ORIGIN example.
300 a 3600 IN A 192.0.2.42
301 tagged 3600 IN A 192.0.2.84
302 meta 3600 IN A 192.0.2.85
303 query-selected 3600 IN A 192.0.2.84
304 answer-selected 3600 IN A 192.0.2.84
305 types 3600 IN A 192.0.2.84
306 types 3600 IN AAAA 2001:DB8::1
307 types 3600 IN TXT "Lorem ipsum dolor sit amet"
308 types 3600 IN MX 10 a.example.
309 types 3600 IN SPF "v=spf1 -all"
310 types 3600 IN SRV 10 20 443 a.example.
311 cname 3600 IN CNAME a.example.
313 """.format(soa
=cls
._SOA
))
314 super(TestRecursorProtobuf
, cls
).generateRecursorConfig(confdir
)
317 class ProtobufDefaultTest(TestRecursorProtobuf
):
319 This test makes sure that we correctly export queries and response over protobuf.
322 _confdir
= 'ProtobufDefault'
323 _config_template
= """
324 auth-zones=example=configs/%s/example.zone""" % _confdir
328 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
329 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
330 query
.flags |
= dns
.flags
.CD
331 res
= self
.sendUDPQuery(query
)
333 self
.assertRRsetInAnswer(res
, expected
)
335 # check the protobuf messages corresponding to the UDP query and answer
336 msg
= self
.getFirstProtobufMessage()
337 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
339 msg
= self
.getFirstProtobufMessage()
340 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '127.0.0.1')
341 self
.assertEqual(len(msg
.response
.rrs
), 1)
342 rr
= msg
.response
.rrs
[0]
343 # we have max-cache-ttl set to 15
344 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
345 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
346 self
.checkNoRemainingMessage()
349 name
= 'cname.example.'
350 expectedCNAME
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'CNAME', 'a.example.')
351 expectedA
= dns
.rrset
.from_text('a.example.', 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
352 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
353 query
.flags |
= dns
.flags
.CD
354 raw
= self
.sendUDPQuery(query
, decode
=False)
355 res
= dns
.message
.from_wire(raw
)
356 self
.assertRRsetInAnswer(res
, expectedCNAME
)
357 self
.assertRRsetInAnswer(res
, expectedA
)
359 # check the protobuf messages corresponding to the UDP query and answer
360 # but first let the protobuf messages the time to get there
361 msg
= self
.getFirstProtobufMessage()
362 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
364 msg
= self
.getFirstProtobufMessage()
365 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '127.0.0.1', receivedSize
=len(raw
))
366 self
.assertEqual(len(msg
.response
.rrs
), 2)
367 rr
= msg
.response
.rrs
[0]
368 # we don't want to check the TTL for the A record, it has been cached by the previous test
369 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, name
, 15)
370 self
.assertEqual(rr
.rdata
, b
'a.example.')
371 rr
= msg
.response
.rrs
[1]
372 # we have max-cache-ttl set to 15
373 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, 'a.example.', 15, checkTTL
=False)
374 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
375 self
.checkNoRemainingMessage()
377 class ProtobufProxyMappingTest(TestRecursorProtobuf
):
379 This test makes sure that we correctly export queries and response over protobuf with a proxyMapping
382 _confdir
= 'ProtobufProxyMappingTest'
383 _config_template
= """
384 auth-zones=example=configs/%s/example.zone
385 allow-from=3.4.5.0/24
388 _lua_config_file
= """
389 addProxyMapping("127.0.0.1/24", "3.4.5.6:99")
390 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
391 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
395 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
396 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
397 query
.flags |
= dns
.flags
.CD
398 res
= self
.sendUDPQuery(query
)
400 self
.assertRRsetInAnswer(res
, expected
)
402 # check the protobuf messages corresponding to the UDP query and answer
403 msg
= self
.getFirstProtobufMessage()
404 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
406 msg
= self
.getFirstProtobufMessage()
407 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '127.0.0.1')
408 self
.assertEqual(len(msg
.response
.rrs
), 1)
409 rr
= msg
.response
.rrs
[0]
410 # we have max-cache-ttl set to 15
411 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
412 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
413 self
.checkNoRemainingMessage()
415 class ProtobufProxyMappingLogMappedTest(TestRecursorProtobuf
):
417 This test makes sure that we correctly export queries and response over protobuf.
420 _confdir
= 'ProtobufProxyMappingLogMappedTest'
421 _config_template
= """
422 auth-zones=example=configs/%s/example.zone
423 allow-from=3.4.5.0/0"
426 _lua_config_file
= """
427 addProxyMapping("127.0.0.1/24", "3.4.5.6:99")
428 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logMappedFrom = true })
429 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
433 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
434 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
435 query
.flags |
= dns
.flags
.CD
436 res
= self
.sendUDPQuery(query
)
438 self
.assertRRsetInAnswer(res
, expected
)
440 # check the protobuf messages corresponding to the UDP query and answer
441 msg
= self
.getFirstProtobufMessage()
442 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, '3.4.5.6')
444 msg
= self
.getFirstProtobufMessage()
445 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '3.4.5.6')
446 self
.assertEqual(len(msg
.response
.rrs
), 1)
447 rr
= msg
.response
.rrs
[0]
448 # we have max-cache-ttl set to 15
449 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
450 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
451 self
.checkNoRemainingMessage()
453 class ProtobufProxyTest(TestRecursorProtobuf
):
455 This test makes sure that we correctly export addresses over protobuf when the proxy protocol is used.
458 _confdir
= 'ProtobufProxy'
459 _config_template
= """
460 auth-zones=example=configs/%s/example.zone
461 proxy-protocol-from=127.0.0.1/32
462 allow-from=127.0.0.1,6.6.6.6
467 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
468 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
469 query
.flags |
= dns
.flags
.CD
470 res
= self
.sendUDPQueryWithProxyProtocol(query
, False, '6.6.6.6', '7.7.7.7', 666, 777)
472 self
.assertRRsetInAnswer(res
, expected
)
474 # check the protobuf messages corresponding to the UDP query and answer
475 msg
= self
.getFirstProtobufMessage()
476 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, '6.6.6.6', '7.7.7.7')
478 msg
= self
.getFirstProtobufMessage()
479 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '6.6.6.6')
480 self
.assertEqual(len(msg
.response
.rrs
), 1)
481 rr
= msg
.response
.rrs
[0]
482 # we have max-cache-ttl set to 15
483 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
484 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
485 self
.checkNoRemainingMessage()
487 class ProtobufProxyWithProxyByTableTest(TestRecursorProtobuf
):
489 This test makes sure that we correctly export addresses over protobuf when the proxy protocol and a proxy table mapping is used
492 _confdir
= 'ProtobufProxyWithProxyByTable'
493 _config_template
= """
494 auth-zones=example=configs/%s/example.zone
495 proxy-protocol-from=127.0.0.1/32
499 _lua_config_file
= """
500 addProxyMapping("6.6.6.6/24", "3.4.5.6:99")
501 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
502 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
506 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
507 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
508 query
.flags |
= dns
.flags
.CD
509 res
= self
.sendUDPQueryWithProxyProtocol(query
, False, '6.6.6.6', '7.7.7.7', 666, 777)
511 self
.assertRRsetInAnswer(res
, expected
)
513 # check the protobuf messages corresponding to the UDP query and answer
514 msg
= self
.getFirstProtobufMessage()
515 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, '6.6.6.6', '7.7.7.7')
517 msg
= self
.getFirstProtobufMessage()
518 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '6.6.6.6')
519 self
.assertEqual(len(msg
.response
.rrs
), 1)
520 rr
= msg
.response
.rrs
[0]
521 # we have max-cache-ttl set to 15
522 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
523 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
524 self
.checkNoRemainingMessage()
526 class ProtobufProxyWithProxyByTableLogMappedTest(TestRecursorProtobuf
):
528 This test makes sure that we correctly export addresses over protobuf when the proxy protocol and a proxy table mapping is used
531 _confdir
= 'ProtobufProxyWithProxyByTableLogMapped'
532 _config_template
= """
533 auth-zones=example=configs/%s/example.zone
534 proxy-protocol-from=127.0.0.1/32
538 _lua_config_file
= """
539 addProxyMapping("6.6.6.6/24", "3.4.5.6:99")
540 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logMappedFrom = true })
541 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
545 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
546 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
547 query
.flags |
= dns
.flags
.CD
548 res
= self
.sendUDPQueryWithProxyProtocol(query
, False, '6.6.6.6', '7.7.7.7', 666, 777)
550 self
.assertRRsetInAnswer(res
, expected
)
552 # check the protobuf messages corresponding to the UDP query and answer
553 msg
= self
.getFirstProtobufMessage()
554 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, '3.4.5.6', '7.7.7.7')
556 msg
= self
.getFirstProtobufMessage()
557 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '3.4.5.6')
558 self
.assertEqual(len(msg
.response
.rrs
), 1)
559 rr
= msg
.response
.rrs
[0]
560 # we have max-cache-ttl set to 15
561 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
562 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
563 self
.checkNoRemainingMessage()
566 class OutgoingProtobufDefaultTest(TestRecursorProtobuf
):
568 This test makes sure that we correctly export outgoing queries over protobuf.
569 It must be improved and setup env so we can check for incoming responses, but makes sure for now
570 that the recursor at least connects to the protobuf server.
573 _confdir
= 'OutgoingProtobufDefault'
574 _config_template
= """
575 # Switch off QName Minimization, it generates much more protobuf messages
576 # (or make the test much more smart!)
577 qname-minimization=no
579 _lua_config_file
= """
580 outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
581 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
584 name
= 'host1.secure.example.'
587 # the root DNSKEY has been learned with priming the root NS already
588 # ('.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 201),
589 for qname
, qtype
, proto
, responseSize
in [
590 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 248),
591 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 221),
592 ('example.', dns
.rdatatype
.DNSKEY
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 219),
593 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 175),
594 ('secure.example.', dns
.rdatatype
.DNSKEY
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 233),
597 expected
.append((None, None, None, None, None, None))
599 query
= dns
.message
.make_query(qname
, qtype
, use_edns
=True, want_dnssec
=True)
600 resp
= dns
.message
.make_response(query
)
602 qname
, qtype
, query
, resp
, proto
, responseSize
605 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
606 query
.flags |
= dns
.flags
.RD
607 res
= self
.sendUDPQuery(query
)
609 for qname
, qtype
, qry
, ans
, proto
, responseSize
in expected
:
611 self
.getFirstProtobufMessage()
612 self
.getFirstProtobufMessage()
615 msg
= self
.getFirstProtobufMessage()
616 self
.checkProtobufOutgoingQuery(msg
, proto
, qry
, dns
.rdataclass
.IN
, qtype
, qname
)
619 msg
= self
.getFirstProtobufMessage()
620 self
.checkProtobufIncomingResponse(msg
, proto
, ans
, length
=responseSize
)
622 self
.checkNoRemainingMessage()
624 class OutgoingProtobufWithECSMappingTest(TestRecursorProtobuf
):
626 This test makes sure that we correctly export outgoing queries over protobuf.
627 It must be improved and setup env so we can check for incoming responses, but makes sure for now
628 that the recursor at least connects to the protobuf server.
631 _confdir
= 'OutgoingProtobuffWithECSMapping'
632 _config_template
= """
633 # Switch off QName Minimization, it generates much more protobuf messages
634 # (or make the test much more smart!)
635 qname-minimization=no
636 edns-subnet-allow-list=example
637 allow-from=1.2.3.4/32
638 # this is to not let . queries interfere
641 _lua_config_file
= """
642 outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
643 addProxyMapping("127.0.0.0/8", "1.2.3.4", { "host1.secure.example." })
644 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
647 name
= 'host1.secure.example.'
650 # the root DNSKEY has been learned with priming the root NS already
651 # ('.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 201),
652 for qname
, qtype
, proto
, responseSize
, ecs
in [
653 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 248, "1.2.3.0"),
654 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 221, "1.2.3.0"),
655 ('example.', dns
.rdatatype
.DNSKEY
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 219, "1.2.3.0"),
656 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 175, "1.2.3.0"),
657 ('secure.example.', dns
.rdatatype
.DNSKEY
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 233, "1.2.3.0"),
660 expected
.append((None, None, None, None, None, None, None))
662 ecso
= clientsubnetoption
.ClientSubnetOption('9.10.11.12', 24)
663 query
= dns
.message
.make_query(qname
, qtype
, use_edns
=True, want_dnssec
=True, options
=[ecso
], payload
=512)
664 resp
= dns
.message
.make_response(query
)
666 qname
, qtype
, query
, resp
, proto
, responseSize
, ecs
669 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
670 query
.flags |
= dns
.flags
.RD
671 res
= self
.sendUDPQuery(query
)
673 for qname
, qtype
, qry
, ans
, proto
, responseSize
, ecs
in expected
:
675 self
.getFirstProtobufMessage()
676 self
.getFirstProtobufMessage()
679 msg
= self
.getFirstProtobufMessage()
680 print(qname
, qtype
, proto
, responseSize
, ecs
, file=sys
.stderr
)
681 self
.checkProtobufOutgoingQuery(msg
, proto
, qry
, dns
.rdataclass
.IN
, qtype
, qname
, "127.0.0.1", None, ecs
)
682 print("OK", file=sys
.stderr
);
684 msg
= self
.getFirstProtobufMessage()
685 self
.checkProtobufIncomingResponse(msg
, proto
, ans
, length
=responseSize
)
687 self
.checkNoRemainingMessage()
689 # this query should use the unmapped ECS
690 name
= 'mx1.secure.example.'
693 # the root DNSKEY has been learned with priming the root NS already
694 # ('.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 201),
695 for qname
, qtype
, proto
, responseSize
, ecs
in [
696 ('mx1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 173, "127.0.0.1"),
699 expected
.append((None, None, None, None, None, None, None))
701 ecso
= clientsubnetoption
.ClientSubnetOption('127.0.0.1', 32)
702 query
= dns
.message
.make_query(qname
, qtype
, use_edns
=True, want_dnssec
=True, options
=[ecso
], payload
=512)
703 resp
= dns
.message
.make_response(query
)
705 qname
, qtype
, query
, resp
, proto
, responseSize
, ecs
708 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
709 query
.flags |
= dns
.flags
.RD
710 res
= self
.sendUDPQuery(query
)
712 for qname
, qtype
, qry
, ans
, proto
, responseSize
, ecs
in expected
:
714 self
.getFirstProtobufMessage()
715 self
.getFirstProtobufMessage()
718 msg
= self
.getFirstProtobufMessage()
719 print(qname
, qtype
, proto
, responseSize
, ecs
, file=sys
.stderr
)
720 self
.checkProtobufOutgoingQuery(msg
, proto
, qry
, dns
.rdataclass
.IN
, qtype
, qname
, "127.0.0.1", None, ecs
)
721 print("OK", file=sys
.stderr
);
723 msg
= self
.getFirstProtobufMessage()
724 self
.checkProtobufIncomingResponse(msg
, proto
, ans
, length
=responseSize
)
726 self
.checkNoRemainingMessage()
728 class OutgoingProtobufNoQueriesTest(TestRecursorProtobuf
):
730 This test makes sure that we correctly export incoming responses but not outgoing queries over protobuf.
731 It must be improved and setup env so we can check for incoming responses, but makes sure for now
732 that the recursor at least connects to the protobuf server.
735 _confdir
= 'OutgoingProtobufNoQueries'
736 _config_template
= """
737 # Switch off QName Minimization, it generates much more protobuf messages
738 # (or make the test much more smart!)
739 qname-minimization=no"""
740 _lua_config_file
= """
741 outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true })
742 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
745 name
= 'host1.secure.example.'
747 # the root DNSKEY has been learned with priming the root NS already
748 # ('.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 201),
749 for qname
, qtype
, proto
, size
in [
750 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 248),
751 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 221),
752 ('example.', dns
.rdatatype
.DNSKEY
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 219),
753 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 175),
754 ('secure.example.', dns
.rdatatype
.DNSKEY
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 233),
757 expected
.append((None, None, None, None, None, None))
759 query
= dns
.message
.make_query(qname
, qtype
, use_edns
=True, want_dnssec
=True)
760 resp
= dns
.message
.make_response(query
)
762 qname
, qtype
, query
, resp
, proto
, size
765 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
766 query
.flags |
= dns
.flags
.RD
767 res
= self
.sendUDPQuery(query
)
769 for qname
, qtype
, qry
, ans
, proto
, size
in expected
:
771 self
.getFirstProtobufMessage()
775 msg
= self
.getFirstProtobufMessage()
776 self
.checkProtobufIncomingResponse(msg
, proto
, ans
, length
=size
)
778 self
.checkNoRemainingMessage()
780 class ProtobufMasksTest(TestRecursorProtobuf
):
782 This test makes sure that we correctly export queries and response over protobuf, respecting the configured initiator masking.
785 _confdir
= 'ProtobufMasks'
786 _config_template
= """
787 auth-zones=example=configs/%s/example.zone""" % _confdir
789 _protobufMaskV6
= 128
790 _lua_config_file
= """
791 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
792 setProtobufMasks(%d, %d)
793 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
, _protobufMaskV4
, _protobufMaskV6
)
797 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
798 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
799 query
.flags |
= dns
.flags
.CD
800 res
= self
.sendUDPQuery(query
)
801 self
.assertRRsetInAnswer(res
, expected
)
803 # check the protobuf messages corresponding to the UDP query and answer
804 # but first let the protobuf messages the time to get there
805 msg
= self
.getFirstProtobufMessage()
806 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, '112.0.0.0')
808 msg
= self
.getFirstProtobufMessage()
809 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '112.0.0.0')
810 self
.assertEqual(len(msg
.response
.rrs
), 1)
811 rr
= msg
.response
.rrs
[0]
812 # we have max-cache-ttl set to 15
813 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
814 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
815 self
.checkNoRemainingMessage()
817 class ProtobufQueriesOnlyTest(TestRecursorProtobuf
):
819 This test makes sure that we correctly export queries but not responses over protobuf.
822 _confdir
= 'ProtobufQueriesOnly'
823 _config_template
= """
824 auth-zones=example=configs/%s/example.zone""" % _confdir
825 _lua_config_file
= """
826 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=false } )
827 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
831 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
832 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
833 query
.flags |
= dns
.flags
.CD
834 res
= self
.sendUDPQuery(query
)
835 self
.assertRRsetInAnswer(res
, expected
)
837 # check the protobuf message corresponding to the UDP query
838 msg
= self
.getFirstProtobufMessage()
839 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
841 self
.checkNoRemainingMessage()
843 class ProtobufResponsesOnlyTest(TestRecursorProtobuf
):
845 This test makes sure that we correctly export responses but not queries over protobuf.
848 _confdir
= 'ProtobufResponsesOnly'
849 _config_template
= """
850 auth-zones=example=configs/%s/example.zone""" % _confdir
851 _lua_config_file
= """
852 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true } )
853 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
857 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
858 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
859 query
.flags |
= dns
.flags
.CD
860 res
= self
.sendUDPQuery(query
)
861 self
.assertRRsetInAnswer(res
, expected
)
863 # check the protobuf message corresponding to the UDP response
864 msg
= self
.getFirstProtobufMessage()
865 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
866 self
.assertEqual(len(msg
.response
.rrs
), 1)
867 rr
= msg
.response
.rrs
[0]
868 # we have max-cache-ttl set to 15
869 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
870 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
871 # nothing else in the queue
872 self
.checkNoRemainingMessage()
874 class ProtobufTaggedOnlyTest(TestRecursorProtobuf
):
876 This test makes sure that we correctly export queries and responses but only if they have been tagged.
879 _confdir
= 'ProtobufTaggedOnly'
880 _config_template
= """
881 auth-zones=example=configs/%s/example.zone""" % _confdir
882 _lua_config_file
= """
883 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true, taggedOnly=true } )
884 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
885 _tags
= ['tag1', 'tag2']
886 _tag_from_gettag
= 'tag-from-gettag'
887 _lua_dns_script_file
= """
888 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
889 if qname:equal('tagged.example.') then
894 function preresolve(dq)
895 if dq.qname:equal('tagged.example.') then
896 dq:addPolicyTag('%s')
897 dq:addPolicyTag('%s')
901 """ % (_tag_from_gettag
, _tags
[0], _tags
[1])
905 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
906 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
907 query
.flags |
= dns
.flags
.CD
908 res
= self
.sendUDPQuery(query
)
909 self
.assertRRsetInAnswer(res
, expected
)
911 # check the protobuf message corresponding to the UDP response
912 # the first query and answer are not tagged, so there is nothing in the queue
914 self
.checkNoRemainingMessage()
916 def testTagged(self
):
917 name
= 'tagged.example.'
918 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84')
919 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
920 query
.flags |
= dns
.flags
.CD
921 res
= self
.sendUDPQuery(query
)
922 self
.assertRRsetInAnswer(res
, expected
)
924 # check the protobuf messages corresponding to the UDP query and answer
925 msg
= self
.getFirstProtobufMessage()
926 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
927 self
.checkProtobufTags(msg
, [ self
._tag
_from
_gettag
])
929 msg
= self
.getFirstProtobufMessage()
930 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
931 self
.assertEqual(len(msg
.response
.rrs
), 1)
932 rr
= msg
.response
.rrs
[0]
933 # we have max-cache-ttl set to 15
934 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
935 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.84')
936 tags
= [ self
._tag
_from
_gettag
] + self
._tags
937 self
.checkProtobufTags(msg
, tags
)
938 self
.checkNoRemainingMessage()
940 class ProtobufSelectedFromLuaTest(TestRecursorProtobuf
):
942 This test makes sure that we correctly export queries and responses but only if they have been selected from Lua.
945 _confdir
= 'ProtobufSelectedFromLua'
946 _config_template
= """
947 auth-zones=example=configs/%s/example.zone""" % _confdir
948 _lua_config_file
= """
949 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=false } )
950 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
951 _lua_dns_script_file
= """
952 local ffi = require("ffi")
955 typedef struct pdns_ffi_param pdns_ffi_param_t;
957 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
958 void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery);
961 function gettag_ffi(obj)
962 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
963 if qname == 'query-selected.example' then
964 ffi.C.pdns_ffi_param_set_log_query(obj, true)
969 function preresolve(dq)
970 if dq.qname:equal('answer-selected.example.') then
971 dq.logResponse = true
979 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
980 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
981 query
.flags |
= dns
.flags
.CD
982 res
= self
.sendUDPQuery(query
)
983 self
.assertRRsetInAnswer(res
, expected
)
985 # check the protobuf message corresponding to the UDP response
986 # the first query and answer are not selected, so there is nothing in the queue
987 self
.checkNoRemainingMessage()
989 def testQuerySelected(self
):
990 name
= 'query-selected.example.'
991 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84')
992 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
993 query
.flags |
= dns
.flags
.CD
994 res
= self
.sendUDPQuery(query
)
995 self
.assertRRsetInAnswer(res
, expected
)
997 # check the protobuf messages corresponding to the UDP query
998 msg
= self
.getFirstProtobufMessage()
999 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1000 # there should be no response
1001 self
.checkNoRemainingMessage()
1003 def testResponseSelected(self
):
1004 name
= 'answer-selected.example.'
1005 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84')
1006 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1007 query
.flags |
= dns
.flags
.CD
1008 res
= self
.sendUDPQuery(query
)
1009 self
.assertRRsetInAnswer(res
, expected
)
1011 # check the protobuf messages corresponding to the UDP response
1012 msg
= self
.getFirstProtobufMessage()
1013 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1014 self
.assertEqual(len(msg
.response
.rrs
), 1)
1015 rr
= msg
.response
.rrs
[0]
1016 # we have max-cache-ttl set to 15
1017 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1018 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.84')
1019 self
.checkNoRemainingMessage()
1021 class ProtobufExportTypesTest(TestRecursorProtobuf
):
1023 This test makes sure that we correctly export other types than A, AAAA and CNAME over protobuf.
1026 _confdir
= 'ProtobufExportTypes'
1027 _config_template
= """
1028 auth-zones=example=configs/%s/example.zone""" % _confdir
1029 _lua_config_file
= """
1030 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { exportTypes={"AAAA", "MX", "SPF", "SRV", "TXT"} } )
1031 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
1034 name
= 'types.example.'
1035 expected
= [dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84'),
1036 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'AAAA', '2001:DB8::1'),
1037 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'MX', '10 a.example.'),
1038 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'SPF', '"v=spf1 -all"'),
1039 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'SRV', '10 20 443 a.example.'),
1040 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'TXT', '"Lorem ipsum dolor sit amet"'),
1042 query
= dns
.message
.make_query(name
, 'ANY', want_dnssec
=True)
1043 query
.flags |
= dns
.flags
.CD
1044 raw
= self
.sendUDPQuery(query
, decode
=False)
1045 res
= dns
.message
.from_wire(raw
)
1047 for rrset
in expected
:
1048 self
.assertRRsetInAnswer(res
, rrset
)
1050 # check the protobuf messages corresponding to the UDP query and answer
1051 msg
= self
.getFirstProtobufMessage()
1052 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1054 msg
= self
.getFirstProtobufMessage()
1055 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '127.0.0.1', receivedSize
=len(raw
))
1056 self
.assertEqual(len(msg
.response
.rrs
), 5)
1057 for rr
in msg
.response
.rrs
:
1058 self
.assertTrue(rr
.type in [dns
.rdatatype
.AAAA
, dns
.rdatatype
.TXT
, dns
.rdatatype
.MX
, dns
.rdatatype
.SPF
, dns
.rdatatype
.SRV
])
1060 if rr
.type == dns
.rdatatype
.AAAA
:
1061 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.AAAA
, name
, 15)
1062 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET6
, rr
.rdata
), '2001:db8::1')
1063 elif rr
.type == dns
.rdatatype
.TXT
:
1064 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.TXT
, name
, 15)
1065 self
.assertEqual(rr
.rdata
, b
'"Lorem ipsum dolor sit amet"')
1066 elif rr
.type == dns
.rdatatype
.MX
:
1067 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.MX
, name
, 15)
1068 self
.assertEqual(rr
.rdata
, b
'a.example.')
1069 elif rr
.type == dns
.rdatatype
.SPF
:
1070 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.SPF
, name
, 15)
1071 self
.assertEqual(rr
.rdata
, b
'"v=spf1 -all"')
1072 elif rr
.type == dns
.rdatatype
.SRV
:
1073 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.SRV
, name
, 15)
1074 self
.assertEqual(rr
.rdata
, b
'a.example.')
1076 self
.checkNoRemainingMessage()
1078 class ProtobufTaggedExtraFieldsTest(TestRecursorProtobuf
):
1080 This test makes sure that we correctly export extra fields that may have been set while being tagged.
1083 _confdir
= 'ProtobufTaggedExtraFields'
1084 _config_template
= """
1085 auth-zones=example=configs/%s/example.zone""" % _confdir
1086 _lua_config_file
= """
1087 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1088 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
1089 _requestorId
= 'S-000001727'
1090 _deviceId
= 'd1:0a:91:dc:cc:82'
1092 _lua_dns_script_file
= """
1093 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
1094 if qname:equal('tagged.example.') then
1095 -- tag number, policy tags, data, requestorId, deviceId, deviceName
1096 return 0, {}, {}, '%s', '%s', '%s'
1100 """ % (_requestorId
, _deviceId
, _deviceName
)
1104 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
1105 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1106 query
.flags |
= dns
.flags
.CD
1107 res
= self
.sendUDPQuery(query
)
1108 self
.assertRRsetInAnswer(res
, expected
)
1110 # check the protobuf message corresponding to the UDP response
1111 # the first query and answer are not tagged, so there is nothing in the queue
1112 # check the protobuf messages corresponding to the UDP query and answer
1113 msg
= self
.getFirstProtobufMessage()
1114 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1115 self
.checkProtobufIdentity(msg
, '', b
'', '')
1118 msg
= self
.getFirstProtobufMessage()
1119 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '127.0.0.1')
1120 self
.assertEqual(len(msg
.response
.rrs
), 1)
1121 rr
= msg
.response
.rrs
[0]
1122 # we have max-cache-ttl set to 15
1123 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1124 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
1125 self
.checkProtobufIdentity(msg
, '', b
'', '')
1126 self
.checkNoRemainingMessage()
1128 def testTagged(self
):
1129 name
= 'tagged.example.'
1130 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84')
1131 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1132 query
.flags |
= dns
.flags
.CD
1133 res
= self
.sendUDPQuery(query
)
1134 self
.assertRRsetInAnswer(res
, expected
)
1136 # check the protobuf messages corresponding to the UDP query and answer
1137 msg
= self
.getFirstProtobufMessage()
1138 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1139 self
.checkProtobufIdentity(msg
, self
._requestorId
, self
._deviceId
.encode('ascii'), self
._deviceName
)
1142 msg
= self
.getFirstProtobufMessage()
1143 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1144 self
.assertEqual(len(msg
.response
.rrs
), 1)
1145 rr
= msg
.response
.rrs
[0]
1146 # we have max-cache-ttl set to 15
1147 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1148 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.84')
1149 self
.checkProtobufIdentity(msg
, self
._requestorId
, self
._deviceId
.encode('ascii'), self
._deviceName
)
1150 self
.checkNoRemainingMessage()
1152 class ProtobufTaggedExtraFieldsFFITest(ProtobufTaggedExtraFieldsTest
):
1154 This test makes sure that we correctly export extra fields that may have been set while being tagged (FFI version).
1156 _confdir
= 'ProtobufTaggedExtraFieldsFFI'
1157 _config_template
= """
1158 auth-zones=example=configs/%s/example.zone""" % _confdir
1159 _lua_config_file
= """
1160 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1161 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
1162 _lua_dns_script_file
= """
1163 local ffi = require("ffi")
1166 typedef struct pdns_ffi_param pdns_ffi_param_t;
1168 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
1169 void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag);
1170 void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name);
1171 void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name);
1172 void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name);
1175 function gettag_ffi(obj)
1176 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
1177 if qname == 'tagged.example' then
1178 ffi.C.pdns_ffi_param_set_requestorid(obj, "%s")
1180 ffi.C.pdns_ffi_param_set_deviceid(obj, string.len(deviceid), deviceid)
1181 ffi.C.pdns_ffi_param_set_devicename(obj, "%s")
1185 """ % (ProtobufTaggedExtraFieldsTest
._requestorId
, ProtobufTaggedExtraFieldsTest
._deviceId
, ProtobufTaggedExtraFieldsTest
._deviceName
)
1187 class ProtobufRPZTest(TestRecursorProtobuf
):
1189 This test makes sure that we correctly export the RPZ applied policy in our protobuf messages
1192 _confdir
= 'ProtobufRPZ'
1193 _config_template
= """
1194 auth-zones=example=configs/%s/example.rpz.zone""" % _confdir
1195 _lua_config_file
= """
1196 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1197 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
1198 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
, _confdir
)
1201 def generateRecursorConfig(cls
, confdir
):
1202 authzonepath
= os
.path
.join(confdir
, 'example.rpz.zone')
1203 with
open(authzonepath
, 'w') as authzone
:
1204 authzone
.write("""$ORIGIN example.
1206 sub.test 3600 IN A 192.0.2.42
1207 ip 3600 IN A 33.22.11.99
1208 """.format(soa
=cls
._SOA
))
1210 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
1211 with
open(rpzFilePath
, 'w') as rpzZone
:
1212 rpzZone
.write("""$ORIGIN zone.rpz.
1214 *.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
1215 24.0.11.22.33.rpz-ip 60 IN A 1.2.3.4
1216 """.format(soa
=cls
._SOA
))
1218 super(ProtobufRPZTest
, cls
).generateRecursorConfig(confdir
)
1221 name
= 'sub.test.example.'
1222 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
1223 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1224 query
.flags |
= dns
.flags
.CD
1225 res
= self
.sendUDPQuery(query
)
1226 self
.assertRRsetInAnswer(res
, expected
)
1228 # check the protobuf messages corresponding to the UDP query and answer
1229 msg
= self
.getFirstProtobufMessage()
1230 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1233 msg
= self
.getFirstProtobufMessage()
1234 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1235 self
.checkProtobufPolicy(msg
, dnsmessage_pb2
.PBDNSMessage
.PolicyType
.QNAME
, 'zone.rpz.', '*.test.example.', 'sub.test.example', dnsmessage_pb2
.PBDNSMessage
.PolicyKind
.NoAction
)
1236 self
.assertEqual(len(msg
.response
.rrs
), 1)
1237 rr
= msg
.response
.rrs
[0]
1238 # we have max-cache-ttl set to 15
1239 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1240 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
1241 self
.checkNoRemainingMessage()
1244 name
= 'ip.example.'
1245 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '1.2.3.4')
1246 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1247 query
.flags |
= dns
.flags
.CD
1248 res
= self
.sendUDPQuery(query
)
1249 self
.assertRRsetInAnswer(res
, expected
)
1251 # check the protobuf messages corresponding to the UDP query and answer
1252 msg
= self
.getFirstProtobufMessage()
1253 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1256 msg
= self
.getFirstProtobufMessage()
1257 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1258 self
.checkProtobufPolicy(msg
, dnsmessage_pb2
.PBDNSMessage
.PolicyType
.RESPONSEIP
, 'zone.rpz.', '24.0.11.22.33.rpz-ip.', '33.22.11.99', dnsmessage_pb2
.PBDNSMessage
.PolicyKind
.Custom
)
1259 self
.assertEqual(len(msg
.response
.rrs
), 1)
1260 self
.checkNoRemainingMessage()
1262 class ProtobufRPZTagsTest(TestRecursorProtobuf
):
1264 This test makes sure that we correctly export the RPZ tags in our protobuf messages
1267 _confdir
= 'ProtobufRPZTags'
1268 _config_template
= """
1269 auth-zones=example=configs/%s/example.rpz.zone""" % _confdir
1270 _tags
= ['tag1', 'tag2']
1271 _tags_from_gettag
= ['tag1-from-gettag', 'tag2-from-gettag']
1272 _tags_from_rpz
= ['tag1-from-rpz', 'tag2-from-rpz' ]
1273 _lua_config_file
= """
1274 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true, tags={'tag1', 'tag2'} } )
1275 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", tags={ '%s', '%s'} })
1276 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
, _confdir
, _tags_from_rpz
[0], _tags_from_rpz
[1])
1277 _lua_dns_script_file
= """
1278 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
1279 return 0, { '%s', '%s' }
1281 function preresolve(dq)
1282 dq:addPolicyTag('%s')
1283 dq:addPolicyTag('%s')
1286 """ % (_tags_from_gettag
[0], _tags_from_gettag
[1], _tags
[0], _tags
[1])
1289 def generateRecursorConfig(cls
, confdir
):
1290 authzonepath
= os
.path
.join(confdir
, 'example.rpz.zone')
1291 with
open(authzonepath
, 'w') as authzone
:
1292 authzone
.write("""$ORIGIN example.
1294 sub.test 3600 IN A 192.0.2.42
1295 """.format(soa
=cls
._SOA
))
1297 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
1298 with
open(rpzFilePath
, 'w') as rpzZone
:
1299 rpzZone
.write("""$ORIGIN zone.rpz.
1301 *.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
1302 """.format(soa
=cls
._SOA
))
1304 super(ProtobufRPZTagsTest
, cls
).generateRecursorConfig(confdir
)
1307 name
= 'sub.test.example.'
1308 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
1309 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1310 query
.flags |
= dns
.flags
.CD
1311 res
= self
.sendUDPQuery(query
)
1312 self
.assertRRsetInAnswer(res
, expected
)
1314 # check the protobuf messages corresponding to the UDP query and answer
1315 msg
= self
.getFirstProtobufMessage()
1316 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1319 msg
= self
.getFirstProtobufMessage()
1320 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1321 self
.checkProtobufPolicy(msg
, dnsmessage_pb2
.PBDNSMessage
.PolicyType
.QNAME
, 'zone.rpz.', '*.test.example.', 'sub.test.example', dnsmessage_pb2
.PBDNSMessage
.PolicyKind
.NoAction
)
1322 self
.checkProtobufTags(msg
, self
._tags
+ self
._tags
_from
_gettag
+ self
._tags
_from
_rpz
)
1323 self
.assertEqual(len(msg
.response
.rrs
), 1)
1324 rr
= msg
.response
.rrs
[0]
1325 # we have max-cache-ttl set to 15
1326 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1327 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
1328 self
.checkNoRemainingMessage()
1331 class ProtobufMetaFFITest(TestRecursorProtobuf
):
1333 This test makes sure that we can correctly add extra meta fields (FFI version).
1335 _confdir
= 'ProtobufMetaFFITest'
1336 _config_template
= """
1337 auth-zones=example=configs/%s/example.zone""" % _confdir
1338 _lua_config_file
= """
1339 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1340 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
1341 _lua_dns_script_file
= """
1342 local ffi = require("ffi")
1345 typedef struct pdns_ffi_param pdns_ffi_param_t;
1347 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
1348 void pdns_ffi_param_add_meta_single_string_kv(pdns_ffi_param_t *ref, const char* key, const char* val);
1349 void pdns_ffi_param_add_meta_single_int64_kv(pdns_ffi_param_t *ref, const char* key, int64_t val);
1352 function gettag_ffi(obj)
1353 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
1354 if qname == 'meta.example' then
1355 ffi.C.pdns_ffi_param_add_meta_single_string_kv(obj, "meta-str", "keyword")
1356 ffi.C.pdns_ffi_param_add_meta_single_int64_kv(obj, "meta-int", 42)
1357 ffi.C.pdns_ffi_param_add_meta_single_string_kv(obj, "meta-str", "content")
1358 ffi.C.pdns_ffi_param_add_meta_single_int64_kv(obj, "meta-int", 21)
1364 name
= 'meta.example.'
1365 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.85')
1366 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1367 query
.flags |
= dns
.flags
.CD
1368 res
= self
.sendUDPQuery(query
)
1369 self
.assertRRsetInAnswer(res
, expected
)
1371 # check the protobuf messages corresponding to the UDP query and answer
1372 msg
= self
.getFirstProtobufMessage()
1373 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1374 self
.checkProtobufMetas(msg
, {'meta-str': { "stringVal" : ["content", "keyword"]}, 'meta-int': {"intVal" : [21, 42]}})
1377 msg
= self
.getFirstProtobufMessage()
1378 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1379 self
.assertEqual(len(msg
.response
.rrs
), 1)
1380 rr
= msg
.response
.rrs
[0]
1381 # we have max-cache-ttl set to 15
1382 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1383 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.85')
1384 self
.checkProtobufMetas(msg
, {'meta-str': { "stringVal" : ["content", "keyword"]}, 'meta-int': {"intVal" : [21, 42]}})
1386 self
.checkNoRemainingMessage()