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 emptyProtoBufQueue(self
):
125 for param
in protobufServersParameters
:
126 while not param
.queue
.empty():
127 param
.queue
.get(False)
129 def checkNoRemainingMessage(self
):
130 for param
in protobufServersParameters
:
131 self
.assertTrue(param
.queue
.empty())
133 def checkProtobufBase(self
, msg
, protocol
, query
, initiator
, normalQueryResponse
=True, expectedECS
=None, receivedSize
=None):
135 self
.assertTrue(msg
.HasField('timeSec'))
136 self
.assertTrue(msg
.HasField('socketFamily'))
137 self
.assertEqual(msg
.socketFamily
, dnsmessage_pb2
.PBDNSMessage
.INET
)
138 self
.assertTrue(msg
.HasField('from'))
139 fromvalue
= getattr(msg
, 'from')
140 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, fromvalue
), initiator
)
141 self
.assertTrue(msg
.HasField('socketProtocol'))
142 self
.assertEqual(msg
.socketProtocol
, protocol
)
143 self
.assertTrue(msg
.HasField('messageId'))
144 self
.assertTrue(msg
.HasField('serverIdentity'))
145 self
.assertTrue(msg
.HasField('id'))
146 self
.assertEqual(msg
.id, query
.id)
147 self
.assertTrue(msg
.HasField('inBytes'))
148 if normalQueryResponse
:
149 # compare inBytes with length of query/response
150 # Note that for responses, the size we received might differ
151 # because dnspython might compress labels differently from
154 self
.assertEqual(msg
.inBytes
, receivedSize
)
156 self
.assertEqual(msg
.inBytes
, len(query
.to_wire()))
157 if expectedECS
is not None:
158 self
.assertTrue(msg
.HasField('originalRequestorSubnet'))
160 self
.assertEqual(len(msg
.originalRequestorSubnet
), 4)
161 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, msg
.originalRequestorSubnet
), '127.0.0.1')
163 def checkOutgoingProtobufBase(self
, msg
, protocol
, query
, initiator
, length
=None, expectedECS
=None):
165 self
.assertTrue(msg
.HasField('timeSec'))
166 self
.assertTrue(msg
.HasField('socketFamily'))
167 self
.assertEqual(msg
.socketFamily
, dnsmessage_pb2
.PBDNSMessage
.INET
)
168 self
.assertTrue(msg
.HasField('socketProtocol'))
169 self
.assertEqual(msg
.socketProtocol
, protocol
)
170 self
.assertTrue(msg
.HasField('messageId'))
171 self
.assertTrue(msg
.HasField('serverIdentity'))
172 self
.assertTrue(msg
.HasField('id'))
173 self
.assertNotEqual(msg
.id, query
.id)
174 self
.assertTrue(msg
.HasField('inBytes'))
175 if length
is not None:
176 self
.assertEqual(msg
.inBytes
, length
)
178 # compare inBytes with length of query/response
179 self
.assertEqual(msg
.inBytes
, len(query
.to_wire()))
180 if expectedECS
is not None:
181 self
.assertTrue(msg
.HasField('originalRequestorSubnet'))
183 self
.assertEqual(len(msg
.originalRequestorSubnet
), 4)
184 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, msg
.originalRequestorSubnet
), expectedECS
)
186 def checkProtobufQuery(self
, msg
, protocol
, query
, qclass
, qtype
, qname
, initiator
='127.0.0.1', to
='127.0.0.1'):
187 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSQueryType
)
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'))
192 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, msg
.to
), to
)
193 self
.assertTrue(msg
.HasField('question'))
194 self
.assertTrue(msg
.question
.HasField('qClass'))
195 self
.assertEqual(msg
.question
.qClass
, qclass
)
196 self
.assertTrue(msg
.question
.HasField('qType'))
197 self
.assertEqual(msg
.question
.qClass
, qtype
)
198 self
.assertTrue(msg
.question
.HasField('qName'))
199 self
.assertEqual(msg
.question
.qName
, qname
)
201 def checkProtobufResponse(self
, msg
, protocol
, response
, initiator
='127.0.0.1', receivedSize
=None, vstate
=dnsmessage_pb2
.PBDNSMessage
.VState
.Indeterminate
):
202 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSResponseType
)
203 self
.checkProtobufBase(msg
, protocol
, response
, initiator
, receivedSize
=receivedSize
)
204 self
.assertTrue(msg
.HasField('response'))
205 self
.assertTrue(msg
.response
.HasField('queryTimeSec'))
206 self
.assertTrue(msg
.response
.HasField('validationState'))
207 self
.assertEqual(msg
.response
.validationState
, vstate
)
209 def checkProtobufResponseRecord(self
, record
, rclass
, rtype
, rname
, rttl
, checkTTL
=True):
210 self
.assertTrue(record
.HasField('class'))
211 self
.assertEqual(getattr(record
, 'class'), rclass
)
212 self
.assertTrue(record
.HasField('type'))
213 self
.assertEqual(record
.type, rtype
)
214 self
.assertTrue(record
.HasField('name'))
215 self
.assertEqual(record
.name
, rname
)
216 self
.assertTrue(record
.HasField('ttl'))
218 self
.assertEqual(record
.ttl
, rttl
)
219 self
.assertTrue(record
.HasField('rdata'))
221 def checkProtobufPolicy(self
, msg
, policyType
, reason
, trigger
, hit
, kind
):
222 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSResponseType
)
223 self
.assertTrue(msg
.response
.HasField('appliedPolicyType'))
224 self
.assertTrue(msg
.response
.HasField('appliedPolicy'))
225 self
.assertTrue(msg
.response
.HasField('appliedPolicyTrigger'))
226 self
.assertTrue(msg
.response
.HasField('appliedPolicyHit'))
227 self
.assertTrue(msg
.response
.HasField('appliedPolicyKind'))
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
)
234 def checkProtobufTags(self
, msg
, tags
):
237 print(msg
.response
.tags
)
238 self
.assertEqual(len(msg
.response
.tags
), len(tags
))
239 for tag
in msg
.response
.tags
:
240 self
.assertTrue(tag
in tags
)
242 def checkProtobufMetas(self
, msg
, metas
):
246 self
.assertEqual(len(msg
.meta
), len(metas
))
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'])
256 def checkProtobufOutgoingQuery(self
, msg
, protocol
, query
, qclass
, qtype
, qname
, initiator
='127.0.0.1', length
=None, expectedECS
=None):
257 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSOutgoingQueryType
)
258 self
.checkOutgoingProtobufBase(msg
, protocol
, query
, initiator
, length
=length
, expectedECS
=expectedECS
)
259 self
.assertTrue(msg
.HasField('to'))
260 self
.assertTrue(msg
.HasField('question'))
261 self
.assertTrue(msg
.question
.HasField('qClass'))
262 self
.assertEqual(msg
.question
.qClass
, qclass
)
263 self
.assertTrue(msg
.question
.HasField('qType'))
264 self
.assertEqual(msg
.question
.qType
, qtype
)
265 self
.assertTrue(msg
.question
.HasField('qName'))
266 self
.assertEqual(msg
.question
.qName
, qname
)
268 def checkProtobufIncomingResponse(self
, msg
, protocol
, response
, initiator
='127.0.0.1', length
=None):
269 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSIncomingResponseType
)
270 self
.checkOutgoingProtobufBase(msg
, protocol
, response
, initiator
, length
=length
)
271 self
.assertTrue(msg
.HasField('response'))
272 self
.assertTrue(msg
.response
.HasField('rcode'))
273 self
.assertTrue(msg
.response
.HasField('queryTimeSec'))
275 def checkProtobufIncomingNetworkErrorResponse(self
, msg
, protocol
, response
, initiator
='127.0.0.1'):
276 self
.checkProtobufIncomingResponse(msg
, protocol
, response
, initiator
, length
=0)
277 self
.assertEqual(msg
.response
.rcode
, 65536)
279 def checkProtobufIdentity(self
, msg
, requestorId
, deviceId
, deviceName
):
281 self
.assertTrue((requestorId
== '') == (not msg
.HasField('requestorId')))
282 self
.assertTrue((deviceId
== b
'') == (not msg
.HasField('deviceId')))
283 self
.assertTrue((deviceName
== '') == (not msg
.HasField('deviceName')))
284 self
.assertEqual(msg
.requestorId
, requestorId
)
285 self
.assertEqual(msg
.deviceId
, deviceId
)
286 self
.assertEqual(msg
.deviceName
, deviceName
)
289 super(TestRecursorProtobuf
, self
).setUp()
290 # Make sure the queue is empty, in case
291 # a previous test failed
292 self
.emptyProtoBufQueue()
293 # wait long enough to be sure that the housekeeping has
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.
303 a 3600 IN A 192.0.2.42
304 tagged 3600 IN A 192.0.2.84
305 meta 3600 IN A 192.0.2.85
306 query-selected 3600 IN A 192.0.2.84
307 answer-selected 3600 IN A 192.0.2.84
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.
316 """.format(soa
=cls
._SOA
))
317 super(TestRecursorProtobuf
, cls
).generateRecursorConfig(confdir
)
320 class ProtobufDefaultTest(TestRecursorProtobuf
):
322 This test makes sure that we correctly export queries and response over protobuf.
325 _confdir
= 'ProtobufDefault'
326 _config_template
= """
327 auth-zones=example=configs/%s/example.zone""" % _confdir
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
)
336 self
.assertRRsetInAnswer(res
, expected
)
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
)
342 msg
= self
.getFirstProtobufMessage()
343 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '127.0.0.1')
344 self
.assertEqual(len(msg
.response
.rrs
), 1)
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)
348 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
349 self
.checkNoRemainingMessage()
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
)
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
)
367 msg
= self
.getFirstProtobufMessage()
368 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '127.0.0.1', receivedSize
=len(raw
))
369 self
.assertEqual(len(msg
.response
.rrs
), 2)
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)
373 self
.assertEqual(rr
.rdata
, b
'a.example.')
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)
377 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
378 self
.checkNoRemainingMessage()
380 class ProtobufProxyMappingTest(TestRecursorProtobuf
):
382 This test makes sure that we correctly export queries and response over protobuf with a proxyMapping
385 _confdir
= 'ProtobufProxyMappingTest'
386 _config_template
= """
387 auth-zones=example=configs/%s/example.zone
388 allow-from=3.4.5.0/24
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
)
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
)
403 self
.assertRRsetInAnswer(res
, expected
)
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
)
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()
418 class ProtobufProxyMappingLogMappedTest(TestRecursorProtobuf
):
420 This test makes sure that we correctly export queries and response over protobuf.
423 _confdir
= 'ProtobufProxyMappingLogMappedTest'
424 _config_template
= """
425 auth-zones=example=configs/%s/example.zone
426 allow-from=3.4.5.0/0"
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
)
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
)
441 self
.assertRRsetInAnswer(res
, expected
)
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')
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()
456 class ProtobufProxyTest(TestRecursorProtobuf
):
458 This test makes sure that we correctly export addresses over protobuf when the proxy protocol is used.
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
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)
475 self
.assertRRsetInAnswer(res
, expected
)
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')
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()
490 class ProtobufProxyWithProxyByTableTest(TestRecursorProtobuf
):
492 This test makes sure that we correctly export addresses over protobuf when the proxy protocol and a proxy table mapping is used
495 _confdir
= 'ProtobufProxyWithProxyByTable'
496 _config_template
= """
497 auth-zones=example=configs/%s/example.zone
498 proxy-protocol-from=127.0.0.1/32
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
)
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)
514 self
.assertRRsetInAnswer(res
, expected
)
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')
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()
529 class ProtobufProxyWithProxyByTableLogMappedTest(TestRecursorProtobuf
):
531 This test makes sure that we correctly export addresses over protobuf when the proxy protocol and a proxy table mapping is used
534 _confdir
= 'ProtobufProxyWithProxyByTableLogMapped'
535 _config_template
= """
536 auth-zones=example=configs/%s/example.zone
537 proxy-protocol-from=127.0.0.1/32
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
)
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)
553 self
.assertRRsetInAnswer(res
, expected
)
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')
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()
569 class OutgoingProtobufDefaultTest(TestRecursorProtobuf
):
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.
576 _confdir
= 'OutgoingProtobufDefault'
577 _config_template
= """
578 # Switch off QName Minimization, it generates much more protobuf messages
579 # (or make the test much more smart!)
580 qname-minimization=no
584 _lua_config_file
= """
585 outgoingProtobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
586 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
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
)
595 self
.emptyProtoBufQueue()
597 name
= 'host1.secure.example.'
600 for qname
, qtype
, proto
, responseSize
in [
601 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 248),
602 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 221),
603 ('example.', dns
.rdatatype
.DNSKEY
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 219),
604 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 175),
605 ('secure.example.', dns
.rdatatype
.DNSKEY
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 233),
608 expected
.append((None, None, None, None, None, None))
610 query
= dns
.message
.make_query(qname
, qtype
, use_edns
=True, want_dnssec
=True)
611 resp
= dns
.message
.make_response(query
)
613 qname
, qtype
, query
, resp
, proto
, responseSize
616 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
617 query
.flags |
= dns
.flags
.RD
618 res
= self
.sendUDPQuery(query
)
620 for qname
, qtype
, qry
, ans
, proto
, responseSize
in expected
:
622 self
.getFirstProtobufMessage()
623 self
.getFirstProtobufMessage()
626 msg
= self
.getFirstProtobufMessage()
627 self
.checkProtobufOutgoingQuery(msg
, proto
, qry
, dns
.rdataclass
.IN
, qtype
, qname
)
630 msg
= self
.getFirstProtobufMessage()
631 self
.checkProtobufIncomingResponse(msg
, proto
, ans
, length
=responseSize
)
633 self
.checkNoRemainingMessage()
635 class OutgoingProtobufWithECSMappingTest(TestRecursorProtobuf
):
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.
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
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
)
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
)
665 self
.emptyProtoBufQueue()
667 name
= 'host1.secure.example.'
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"),
678 expected
.append((None, None, None, None, None, None, None))
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
)
684 qname
, qtype
, query
, resp
, proto
, responseSize
, ecs
687 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
688 query
.flags |
= dns
.flags
.RD
689 res
= self
.sendUDPQuery(query
)
691 for qname
, qtype
, qry
, ans
, proto
, responseSize
, ecs
in expected
:
693 self
.getFirstProtobufMessage()
694 self
.getFirstProtobufMessage()
697 msg
= self
.getFirstProtobufMessage()
698 self
.checkProtobufOutgoingQuery(msg
, proto
, qry
, dns
.rdataclass
.IN
, qtype
, qname
, "127.0.0.1", None, ecs
)
700 msg
= self
.getFirstProtobufMessage()
701 self
.checkProtobufIncomingResponse(msg
, proto
, ans
, length
=responseSize
)
703 self
.checkNoRemainingMessage()
705 # this query should use the unmapped ECS
706 name
= 'mx1.secure.example.'
709 for qname
, qtype
, proto
, responseSize
, ecs
in [
710 ('mx1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 173, "127.0.0.1"),
713 expected
.append((None, None, None, None, None, None, None))
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
)
719 qname
, qtype
, query
, resp
, proto
, responseSize
, ecs
722 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
723 query
.flags |
= dns
.flags
.RD
724 res
= self
.sendUDPQuery(query
)
726 for qname
, qtype
, qry
, ans
, proto
, responseSize
, ecs
in expected
:
728 self
.getFirstProtobufMessage()
729 self
.getFirstProtobufMessage()
732 msg
= self
.getFirstProtobufMessage()
733 self
.checkProtobufOutgoingQuery(msg
, proto
, qry
, dns
.rdataclass
.IN
, qtype
, qname
, "127.0.0.1", None, ecs
)
735 msg
= self
.getFirstProtobufMessage()
736 self
.checkProtobufIncomingResponse(msg
, proto
, ans
, length
=responseSize
)
738 self
.checkNoRemainingMessage()
740 class OutgoingProtobufNoQueriesTest(TestRecursorProtobuf
):
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.
747 _confdir
= 'OutgoingProtobufNoQueries'
748 _config_template
= """
749 # Switch off QName Minimization, it generates much more protobuf messages
750 # (or make the test much more smart!)
751 qname-minimization=no
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
)
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
)
766 self
.emptyProtoBufQueue()
768 name
= 'host1.secure.example.'
770 # the root DNSKEY has been learned with priming the root NS already
771 # ('.', dns.rdatatype.DNSKEY, dnsmessage_pb2.PBDNSMessage.UDP, 201),
772 for qname
, qtype
, proto
, size
in [
773 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 248),
774 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 221),
775 ('example.', dns
.rdatatype
.DNSKEY
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 219),
776 ('host1.secure.example.', dns
.rdatatype
.A
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 175),
777 ('secure.example.', dns
.rdatatype
.DNSKEY
, dnsmessage_pb2
.PBDNSMessage
.UDP
, 233),
780 expected
.append((None, None, None, None, None, None))
782 query
= dns
.message
.make_query(qname
, qtype
, use_edns
=True, want_dnssec
=True)
783 resp
= dns
.message
.make_response(query
)
785 qname
, qtype
, query
, resp
, proto
, size
788 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
789 query
.flags |
= dns
.flags
.RD
790 res
= self
.sendUDPQuery(query
)
792 for qname
, qtype
, qry
, ans
, proto
, size
in expected
:
794 self
.getFirstProtobufMessage()
798 msg
= self
.getFirstProtobufMessage()
799 self
.checkProtobufIncomingResponse(msg
, proto
, ans
, length
=size
)
801 self
.checkNoRemainingMessage()
803 class ProtobufMasksTest(TestRecursorProtobuf
):
805 This test makes sure that we correctly export queries and response over protobuf, respecting the configured initiator masking.
808 _confdir
= 'ProtobufMasks'
809 _config_template
= """
810 auth-zones=example=configs/%s/example.zone""" % _confdir
812 _protobufMaskV6
= 128
813 _lua_config_file
= """
814 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"})
815 setProtobufMasks(%d, %d)
816 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
, _protobufMaskV4
, _protobufMaskV6
)
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
)
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')
831 msg
= self
.getFirstProtobufMessage()
832 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '112.0.0.0')
833 self
.assertEqual(len(msg
.response
.rrs
), 1)
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)
837 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
838 self
.checkNoRemainingMessage()
840 class ProtobufQueriesOnlyTest(TestRecursorProtobuf
):
842 This test makes sure that we correctly export queries but not responses over protobuf.
845 _confdir
= 'ProtobufQueriesOnly'
846 _config_template
= """
847 auth-zones=example=configs/%s/example.zone""" % _confdir
848 _lua_config_file
= """
849 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=false } )
850 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
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
)
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
)
864 self
.checkNoRemainingMessage()
866 class ProtobufResponsesOnlyTest(TestRecursorProtobuf
):
868 This test makes sure that we correctly export responses but not queries over protobuf.
871 _confdir
= 'ProtobufResponsesOnly'
872 _config_template
= """
873 auth-zones=example=configs/%s/example.zone""" % _confdir
874 _lua_config_file
= """
875 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true } )
876 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
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
)
886 # check the protobuf message corresponding to the UDP response
887 msg
= self
.getFirstProtobufMessage()
888 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
889 self
.assertEqual(len(msg
.response
.rrs
), 1)
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)
893 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
894 # nothing else in the queue
895 self
.checkNoRemainingMessage()
897 class ProtobufTaggedOnlyTest(TestRecursorProtobuf
):
899 This test makes sure that we correctly export queries and responses but only if they have been tagged.
902 _confdir
= 'ProtobufTaggedOnly'
903 _config_template
= """
904 auth-zones=example=configs/%s/example.zone""" % _confdir
905 _lua_config_file
= """
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
)
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
917 function preresolve(dq)
918 if dq.qname:equal('tagged.example.') then
919 dq:addPolicyTag('%s')
920 dq:addPolicyTag('%s')
924 """ % (_tag_from_gettag
, _tags
[0], _tags
[1])
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
)
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
937 self
.checkNoRemainingMessage()
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
)
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
])
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 self
.checkProtobufTags(msg
, tags
)
961 self
.checkNoRemainingMessage()
963 class ProtobufSelectedFromLuaTest(TestRecursorProtobuf
):
965 This test makes sure that we correctly export queries and responses but only if they have been selected from Lua.
968 _confdir
= 'ProtobufSelectedFromLua'
969 _config_template
= """
970 auth-zones=example=configs/%s/example.zone""" % _confdir
971 _lua_config_file
= """
972 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=false } )
973 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
974 _lua_dns_script_file
= """
975 local ffi = require("ffi")
978 typedef struct pdns_ffi_param pdns_ffi_param_t;
980 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
981 void pdns_ffi_param_set_log_query(pdns_ffi_param_t* ref, bool logQuery);
984 function gettag_ffi(obj)
985 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
986 if qname == 'query-selected.example' then
987 ffi.C.pdns_ffi_param_set_log_query(obj, true)
992 function preresolve(dq)
993 if dq.qname:equal('answer-selected.example.') then
994 dq.logResponse = true
1002 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
1003 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1004 query
.flags |
= dns
.flags
.CD
1005 res
= self
.sendUDPQuery(query
)
1006 self
.assertRRsetInAnswer(res
, expected
)
1008 # check the protobuf message corresponding to the UDP response
1009 # the first query and answer are not selected, so there is nothing in the queue
1010 self
.checkNoRemainingMessage()
1012 def testQuerySelected(self
):
1013 name
= 'query-selected.example.'
1014 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84')
1015 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1016 query
.flags |
= dns
.flags
.CD
1017 res
= self
.sendUDPQuery(query
)
1018 self
.assertRRsetInAnswer(res
, expected
)
1020 # check the protobuf messages corresponding to the UDP query
1021 msg
= self
.getFirstProtobufMessage()
1022 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1023 # there should be no response
1024 self
.checkNoRemainingMessage()
1026 def testResponseSelected(self
):
1027 name
= 'answer-selected.example.'
1028 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84')
1029 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1030 query
.flags |
= dns
.flags
.CD
1031 res
= self
.sendUDPQuery(query
)
1032 self
.assertRRsetInAnswer(res
, expected
)
1034 # check the protobuf messages corresponding to the UDP response
1035 msg
= self
.getFirstProtobufMessage()
1036 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1037 self
.assertEqual(len(msg
.response
.rrs
), 1)
1038 rr
= msg
.response
.rrs
[0]
1039 # we have max-cache-ttl set to 15
1040 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1041 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.84')
1042 self
.checkNoRemainingMessage()
1044 class ProtobufExportTypesTest(TestRecursorProtobuf
):
1046 This test makes sure that we correctly export other types than A, AAAA and CNAME over protobuf.
1049 _confdir
= 'ProtobufExportTypes'
1050 _config_template
= """
1051 auth-zones=example=configs/%s/example.zone""" % _confdir
1052 _lua_config_file
= """
1053 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { exportTypes={"AAAA", "MX", "SPF", "SRV", "TXT"} } )
1054 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
1057 name
= 'types.example.'
1058 expected
= [dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84'),
1059 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'AAAA', '2001:DB8::1'),
1060 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'MX', '10 a.example.'),
1061 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'SPF', '"v=spf1 -all"'),
1062 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'SRV', '10 20 443 a.example.'),
1063 dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'TXT', '"Lorem ipsum dolor sit amet"'),
1065 query
= dns
.message
.make_query(name
, 'ANY', want_dnssec
=True)
1066 query
.flags |
= dns
.flags
.CD
1067 raw
= self
.sendUDPQuery(query
, decode
=False)
1068 res
= dns
.message
.from_wire(raw
)
1070 for rrset
in expected
:
1071 self
.assertRRsetInAnswer(res
, rrset
)
1073 # check the protobuf messages corresponding to the UDP query and answer
1074 msg
= self
.getFirstProtobufMessage()
1075 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1077 msg
= self
.getFirstProtobufMessage()
1078 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '127.0.0.1', receivedSize
=len(raw
))
1079 self
.assertEqual(len(msg
.response
.rrs
), 5)
1080 for rr
in msg
.response
.rrs
:
1081 self
.assertTrue(rr
.type in [dns
.rdatatype
.AAAA
, dns
.rdatatype
.TXT
, dns
.rdatatype
.MX
, dns
.rdatatype
.SPF
, dns
.rdatatype
.SRV
])
1083 if rr
.type == dns
.rdatatype
.AAAA
:
1084 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.AAAA
, name
, 15)
1085 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET6
, rr
.rdata
), '2001:db8::1')
1086 elif rr
.type == dns
.rdatatype
.TXT
:
1087 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.TXT
, name
, 15)
1088 self
.assertEqual(rr
.rdata
, b
'"Lorem ipsum dolor sit amet"')
1089 elif rr
.type == dns
.rdatatype
.MX
:
1090 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.MX
, name
, 15)
1091 self
.assertEqual(rr
.rdata
, b
'a.example.')
1092 elif rr
.type == dns
.rdatatype
.SPF
:
1093 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.SPF
, name
, 15)
1094 self
.assertEqual(rr
.rdata
, b
'"v=spf1 -all"')
1095 elif rr
.type == dns
.rdatatype
.SRV
:
1096 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.SRV
, name
, 15)
1097 self
.assertEqual(rr
.rdata
, b
'a.example.')
1099 self
.checkNoRemainingMessage()
1101 class ProtobufTaggedExtraFieldsTest(TestRecursorProtobuf
):
1103 This test makes sure that we correctly export extra fields that may have been set while being tagged.
1106 _confdir
= 'ProtobufTaggedExtraFields'
1107 _config_template
= """
1108 auth-zones=example=configs/%s/example.zone""" % _confdir
1109 _lua_config_file
= """
1110 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1111 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
1112 _requestorId
= 'S-000001727'
1113 _deviceId
= 'd1:0a:91:dc:cc:82'
1115 _lua_dns_script_file
= """
1116 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
1117 if qname:equal('tagged.example.') then
1118 -- tag number, policy tags, data, requestorId, deviceId, deviceName
1119 return 0, {}, {}, '%s', '%s', '%s'
1123 """ % (_requestorId
, _deviceId
, _deviceName
)
1127 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
1128 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1129 query
.flags |
= dns
.flags
.CD
1130 res
= self
.sendUDPQuery(query
)
1131 self
.assertRRsetInAnswer(res
, expected
)
1133 # check the protobuf message corresponding to the UDP response
1134 # the first query and answer are not tagged, so there is nothing in the queue
1135 # check the protobuf messages corresponding to the UDP query and answer
1136 msg
= self
.getFirstProtobufMessage()
1137 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1138 self
.checkProtobufIdentity(msg
, '', b
'', '')
1141 msg
= self
.getFirstProtobufMessage()
1142 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
, '127.0.0.1')
1143 self
.assertEqual(len(msg
.response
.rrs
), 1)
1144 rr
= msg
.response
.rrs
[0]
1145 # we have max-cache-ttl set to 15
1146 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1147 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
1148 self
.checkProtobufIdentity(msg
, '', b
'', '')
1149 self
.checkNoRemainingMessage()
1151 def testTagged(self
):
1152 name
= 'tagged.example.'
1153 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.84')
1154 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1155 query
.flags |
= dns
.flags
.CD
1156 res
= self
.sendUDPQuery(query
)
1157 self
.assertRRsetInAnswer(res
, expected
)
1159 # check the protobuf messages corresponding to the UDP query and answer
1160 msg
= self
.getFirstProtobufMessage()
1161 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1162 self
.checkProtobufIdentity(msg
, self
._requestorId
, self
._deviceId
.encode('ascii'), self
._deviceName
)
1165 msg
= self
.getFirstProtobufMessage()
1166 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1167 self
.assertEqual(len(msg
.response
.rrs
), 1)
1168 rr
= msg
.response
.rrs
[0]
1169 # we have max-cache-ttl set to 15
1170 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1171 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.84')
1172 self
.checkProtobufIdentity(msg
, self
._requestorId
, self
._deviceId
.encode('ascii'), self
._deviceName
)
1173 self
.checkNoRemainingMessage()
1175 class ProtobufTaggedExtraFieldsFFITest(ProtobufTaggedExtraFieldsTest
):
1177 This test makes sure that we correctly export extra fields that may have been set while being tagged (FFI version).
1179 _confdir
= 'ProtobufTaggedExtraFieldsFFI'
1180 _config_template
= """
1181 auth-zones=example=configs/%s/example.zone""" % _confdir
1182 _lua_config_file
= """
1183 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1184 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
1185 _lua_dns_script_file
= """
1186 local ffi = require("ffi")
1189 typedef struct pdns_ffi_param pdns_ffi_param_t;
1191 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
1192 void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag);
1193 void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name);
1194 void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name);
1195 void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name);
1198 function gettag_ffi(obj)
1199 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
1200 if qname == 'tagged.example' then
1201 ffi.C.pdns_ffi_param_set_requestorid(obj, "%s")
1203 ffi.C.pdns_ffi_param_set_deviceid(obj, string.len(deviceid), deviceid)
1204 ffi.C.pdns_ffi_param_set_devicename(obj, "%s")
1208 """ % (ProtobufTaggedExtraFieldsTest
._requestorId
, ProtobufTaggedExtraFieldsTest
._deviceId
, ProtobufTaggedExtraFieldsTest
._deviceName
)
1210 class ProtobufRPZTest(TestRecursorProtobuf
):
1212 This test makes sure that we correctly export the RPZ applied policy in our protobuf messages
1215 _confdir
= 'ProtobufRPZ'
1216 _config_template
= """
1217 auth-zones=example=configs/%s/example.rpz.zone""" % _confdir
1218 _lua_config_file
= """
1219 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1220 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
1221 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
, _confdir
)
1224 def generateRecursorConfig(cls
, confdir
):
1225 authzonepath
= os
.path
.join(confdir
, 'example.rpz.zone')
1226 with
open(authzonepath
, 'w') as authzone
:
1227 authzone
.write("""$ORIGIN example.
1229 sub.test 3600 IN A 192.0.2.42
1230 ip 3600 IN A 33.22.11.99
1231 """.format(soa
=cls
._SOA
))
1233 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
1234 with
open(rpzFilePath
, 'w') as rpzZone
:
1235 rpzZone
.write("""$ORIGIN zone.rpz.
1237 *.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
1238 24.0.11.22.33.rpz-ip 60 IN A 1.2.3.4
1239 """.format(soa
=cls
._SOA
))
1241 super(ProtobufRPZTest
, cls
).generateRecursorConfig(confdir
)
1244 name
= 'sub.test.example.'
1245 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
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
.QNAME
, 'zone.rpz.', '*.test.example.', 'sub.test.example', dnsmessage_pb2
.PBDNSMessage
.PolicyKind
.NoAction
)
1259 self
.assertEqual(len(msg
.response
.rrs
), 1)
1260 rr
= msg
.response
.rrs
[0]
1261 # we have max-cache-ttl set to 15
1262 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1263 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
1264 self
.checkNoRemainingMessage()
1267 name
= 'ip.example.'
1268 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '1.2.3.4')
1269 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1270 query
.flags |
= dns
.flags
.CD
1271 res
= self
.sendUDPQuery(query
)
1272 self
.assertRRsetInAnswer(res
, expected
)
1274 # check the protobuf messages corresponding to the UDP query and answer
1275 msg
= self
.getFirstProtobufMessage()
1276 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1279 msg
= self
.getFirstProtobufMessage()
1280 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1281 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
)
1282 self
.assertEqual(len(msg
.response
.rrs
), 1)
1283 self
.checkNoRemainingMessage()
1285 class ProtobufRPZTagsTest(TestRecursorProtobuf
):
1287 This test makes sure that we correctly export the RPZ tags in our protobuf messages
1290 _confdir
= 'ProtobufRPZTags'
1291 _config_template
= """
1292 auth-zones=example=configs/%s/example.rpz.zone""" % _confdir
1293 _tags
= ['tag1', 'tag2']
1294 _tags_from_gettag
= ['tag1-from-gettag', 'tag2-from-gettag']
1295 _tags_from_rpz
= ['tag1-from-rpz', 'tag2-from-rpz' ]
1296 _lua_config_file
= """
1297 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true, tags={'tag1', 'tag2'} } )
1298 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", tags={ '%s', '%s'} })
1299 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
, _confdir
, _tags_from_rpz
[0], _tags_from_rpz
[1])
1300 _lua_dns_script_file
= """
1301 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
1302 return 0, { '%s', '%s' }
1304 function preresolve(dq)
1305 dq:addPolicyTag('%s')
1306 dq:addPolicyTag('%s')
1309 """ % (_tags_from_gettag
[0], _tags_from_gettag
[1], _tags
[0], _tags
[1])
1312 def generateRecursorConfig(cls
, confdir
):
1313 authzonepath
= os
.path
.join(confdir
, 'example.rpz.zone')
1314 with
open(authzonepath
, 'w') as authzone
:
1315 authzone
.write("""$ORIGIN example.
1317 sub.test 3600 IN A 192.0.2.42
1318 """.format(soa
=cls
._SOA
))
1320 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
1321 with
open(rpzFilePath
, 'w') as rpzZone
:
1322 rpzZone
.write("""$ORIGIN zone.rpz.
1324 *.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
1325 """.format(soa
=cls
._SOA
))
1327 super(ProtobufRPZTagsTest
, cls
).generateRecursorConfig(confdir
)
1330 name
= 'sub.test.example.'
1331 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
1332 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1333 query
.flags |
= dns
.flags
.CD
1334 res
= self
.sendUDPQuery(query
)
1335 self
.assertRRsetInAnswer(res
, expected
)
1337 # check the protobuf messages corresponding to the UDP query and answer
1338 msg
= self
.getFirstProtobufMessage()
1339 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1342 msg
= self
.getFirstProtobufMessage()
1343 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1344 self
.checkProtobufPolicy(msg
, dnsmessage_pb2
.PBDNSMessage
.PolicyType
.QNAME
, 'zone.rpz.', '*.test.example.', 'sub.test.example', dnsmessage_pb2
.PBDNSMessage
.PolicyKind
.NoAction
)
1345 self
.checkProtobufTags(msg
, self
._tags
+ self
._tags
_from
_gettag
+ self
._tags
_from
_rpz
)
1346 self
.assertEqual(len(msg
.response
.rrs
), 1)
1347 rr
= msg
.response
.rrs
[0]
1348 # we have max-cache-ttl set to 15
1349 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1350 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.42')
1351 self
.checkNoRemainingMessage()
1354 class ProtobufMetaFFITest(TestRecursorProtobuf
):
1356 This test makes sure that we can correctly add extra meta fields (FFI version).
1358 _confdir
= 'ProtobufMetaFFITest'
1359 _config_template
= """
1360 auth-zones=example=configs/%s/example.zone""" % _confdir
1361 _lua_config_file
= """
1362 protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
1363 """ % (protobufServersParameters
[0].port
, protobufServersParameters
[1].port
)
1364 _lua_dns_script_file
= """
1365 local ffi = require("ffi")
1368 typedef struct pdns_ffi_param pdns_ffi_param_t;
1370 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
1371 void pdns_ffi_param_add_meta_single_string_kv(pdns_ffi_param_t *ref, const char* key, const char* val);
1372 void pdns_ffi_param_add_meta_single_int64_kv(pdns_ffi_param_t *ref, const char* key, int64_t val);
1375 function gettag_ffi(obj)
1376 qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
1377 if qname == 'meta.example' then
1378 ffi.C.pdns_ffi_param_add_meta_single_string_kv(obj, "meta-str", "keyword")
1379 ffi.C.pdns_ffi_param_add_meta_single_int64_kv(obj, "meta-int", 42)
1380 ffi.C.pdns_ffi_param_add_meta_single_string_kv(obj, "meta-str", "content")
1381 ffi.C.pdns_ffi_param_add_meta_single_int64_kv(obj, "meta-int", 21)
1387 name
= 'meta.example.'
1388 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.85')
1389 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
1390 query
.flags |
= dns
.flags
.CD
1391 res
= self
.sendUDPQuery(query
)
1392 self
.assertRRsetInAnswer(res
, expected
)
1394 # check the protobuf messages corresponding to the UDP query and answer
1395 msg
= self
.getFirstProtobufMessage()
1396 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
1397 self
.checkProtobufMetas(msg
, {'meta-str': { "stringVal" : ["content", "keyword"]}, 'meta-int': {"intVal" : [21, 42]}})
1400 msg
= self
.getFirstProtobufMessage()
1401 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, res
)
1402 self
.assertEqual(len(msg
.response
.rrs
), 1)
1403 rr
= msg
.response
.rrs
[0]
1404 # we have max-cache-ttl set to 15
1405 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 15)
1406 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '192.0.2.85')
1407 self
.checkProtobufMetas(msg
, {'meta-str': { "stringVal" : ["content", "keyword"]}, 'meta-int': {"intVal" : [21, 42]}})
1409 self
.checkNoRemainingMessage()