8 from dnsdisttests
import DNSDistTest
, pickAvailablePort
, Queue
9 from proxyprotocol
import ProxyProtocol
15 class DNSDistProtobufTest(DNSDistTest
):
16 _protobufServerPort
= pickAvailablePort()
17 _protobufQueue
= Queue()
18 _protobufServerID
= 'dnsdist-server-1'
22 def ProtobufListener(cls
, port
):
23 cls
._backgroundThreads
[threading
.get_native_id()] = True
24 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
25 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
27 sock
.bind(("127.0.0.1", port
))
28 except socket
.error
as e
:
29 print("Error binding in the protbuf listener: %s" % str(e
))
36 (conn
, _
) = sock
.accept()
37 except socket
.timeout
:
38 if cls
._backgroundThreads
.get(threading
.get_native_id(), False) == False:
39 del cls
._backgroundThreads
[threading
.get_native_id()]
49 (datalen
,) = struct
.unpack("!H", data
)
50 data
= conn
.recv(datalen
)
54 cls
._protobufQueue
.put(data
, True, timeout
=2.0)
60 def startResponders(cls
):
61 cls
._UDPResponder
= threading
.Thread(name
='UDP Responder', target
=cls
.UDPResponder
, args
=[cls
._testServerPort
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
62 cls
._UDPResponder
.daemon
= True
63 cls
._UDPResponder
.start()
65 cls
._TCPResponder
= threading
.Thread(name
='TCP Responder', target
=cls
.TCPResponder
, args
=[cls
._testServerPort
, cls
._toResponderQueue
, cls
._fromResponderQueue
])
66 cls
._TCPResponder
.daemon
= True
67 cls
._TCPResponder
.start()
69 cls
._protobufListener
= threading
.Thread(name
='Protobuf Listener', target
=cls
.ProtobufListener
, args
=[cls
._protobufServerPort
])
70 cls
._protobufListener
.daemon
= True
71 cls
._protobufListener
.start()
73 def getFirstProtobufMessage(self
):
74 self
.assertFalse(self
._protobufQueue
.empty())
75 data
= self
._protobufQueue
.get(False)
77 msg
= dnsmessage_pb2
.PBDNSMessage()
78 msg
.ParseFromString(data
)
81 def checkProtobufBase(self
, msg
, protocol
, query
, initiator
, normalQueryResponse
=True, v6
=False):
83 self
.assertTrue(msg
.HasField('timeSec'))
84 self
.assertTrue(msg
.HasField('socketFamily'))
86 self
.assertEqual(msg
.socketFamily
, dnsmessage_pb2
.PBDNSMessage
.INET6
)
88 self
.assertEqual(msg
.socketFamily
, dnsmessage_pb2
.PBDNSMessage
.INET
)
89 self
.assertTrue(msg
.HasField('from'))
90 fromvalue
= getattr(msg
, 'from')
92 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET6
, fromvalue
), initiator
)
94 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, fromvalue
), initiator
)
95 self
.assertTrue(msg
.HasField('socketProtocol'))
96 self
.assertEqual(msg
.socketProtocol
, protocol
)
97 self
.assertTrue(msg
.HasField('messageId'))
98 self
.assertTrue(msg
.HasField('id'))
99 self
.assertEqual(msg
.id, query
.id)
100 self
.assertTrue(msg
.HasField('inBytes'))
101 self
.assertTrue(msg
.HasField('serverIdentity'))
102 self
.assertEqual(msg
.serverIdentity
, self
._protobufServerID
.encode('utf-8'))
104 if normalQueryResponse
and (protocol
== dnsmessage_pb2
.PBDNSMessage
.UDP
or protocol
== dnsmessage_pb2
.PBDNSMessage
.TCP
):
105 # compare inBytes with length of query/response
106 self
.assertEqual(msg
.inBytes
, len(query
.to_wire()))
107 # dnsdist doesn't set the existing EDNS Subnet for now,
108 # although it might be set from Lua
109 # self.assertTrue(msg.HasField('originalRequestorSubnet'))
110 # self.assertEqual(len(msg.originalRequestorSubnet), 4)
111 # self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.originalRequestorSubnet), '127.0.0.1')
113 def checkProtobufQuery(self
, msg
, protocol
, query
, qclass
, qtype
, qname
, initiator
='127.0.0.1', v6
=False):
114 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSQueryType
)
115 self
.checkProtobufBase(msg
, protocol
, query
, initiator
, v6
=v6
)
116 # dnsdist doesn't fill the responder field for responses
117 # because it doesn't keep the information around.
118 self
.assertTrue(msg
.HasField('to'))
120 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, msg
.to
), '127.0.0.1')
121 self
.assertTrue(msg
.HasField('question'))
122 self
.assertTrue(msg
.question
.HasField('qClass'))
123 self
.assertEqual(msg
.question
.qClass
, qclass
)
124 self
.assertTrue(msg
.question
.HasField('qType'))
125 self
.assertEqual(msg
.question
.qClass
, qtype
)
126 self
.assertTrue(msg
.question
.HasField('qName'))
127 self
.assertEqual(msg
.question
.qName
, qname
)
129 def checkProtobufTags(self
, tags
, expectedTags
):
130 # only differences will be in new list
131 listx
= set(tags
) ^
set(expectedTags
)
132 # exclusive or of lists should be empty
133 self
.assertEqual(len(listx
), 0, "Protobuf tags don't match")
135 def checkProtobufQueryConvertedToResponse(self
, msg
, protocol
, response
, initiator
='127.0.0.0'):
136 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSResponseType
)
137 # skip comparing inBytes (size of the query) with the length of the generated response
138 self
.checkProtobufBase(msg
, protocol
, response
, initiator
, False)
139 self
.assertTrue(msg
.HasField('response'))
140 self
.assertTrue(msg
.response
.HasField('queryTimeSec'))
142 def checkProtobufResponse(self
, msg
, protocol
, response
, initiator
='127.0.0.1', v6
=False):
143 self
.assertEqual(msg
.type, dnsmessage_pb2
.PBDNSMessage
.DNSResponseType
)
144 self
.checkProtobufBase(msg
, protocol
, response
, initiator
, v6
=v6
)
145 self
.assertTrue(msg
.HasField('response'))
146 self
.assertTrue(msg
.response
.HasField('queryTimeSec'))
148 def checkProtobufResponseRecord(self
, record
, rclass
, rtype
, rname
, rttl
):
149 self
.assertTrue(record
.HasField('class'))
150 self
.assertEqual(getattr(record
, 'class'), rclass
)
151 self
.assertTrue(record
.HasField('type'))
152 self
.assertEqual(record
.type, rtype
)
153 self
.assertTrue(record
.HasField('name'))
154 self
.assertEqual(record
.name
, rname
)
155 self
.assertTrue(record
.HasField('ttl'))
156 self
.assertEqual(record
.ttl
, rttl
)
157 self
.assertTrue(record
.HasField('rdata'))
159 class TestProtobuf(DNSDistProtobufTest
):
160 _config_params
= ['_testServerPort', '_protobufServerPort', '_protobufServerID', '_protobufServerID']
161 _config_template
= """
162 luasmn = newSuffixMatchNode()
163 luasmn:add(newDNSName('lua.protobuf.tests.powerdns.com.'))
165 function alterProtobufResponse(dq, protobuf)
166 if luasmn:check(dq.qname) then
167 requestor = newCA(tostring(dq.remoteaddr)) -- called by testLuaProtobuf()
168 if requestor:isIPv4() then
169 requestor:truncate(24)
171 requestor:truncate(56)
173 protobuf:setRequestor(requestor)
176 table.insert(tableTags, "TestLabel1,TestData1")
177 table.insert(tableTags, "TestLabel2,TestData2")
179 protobuf:setTagArray(tableTags)
181 protobuf:setTag('TestLabel3,TestData3')
183 protobuf:setTag("Response,456")
187 local tableTags = {} -- called by testProtobuf()
188 table.insert(tableTags, "TestLabel1,TestData1")
189 table.insert(tableTags, "TestLabel2,TestData2")
190 protobuf:setTagArray(tableTags)
192 protobuf:setTag('TestLabel3,TestData3')
194 protobuf:setTag("Response,456")
199 function alterProtobufQuery(dq, protobuf)
201 if luasmn:check(dq.qname) then
202 requestor = newCA(tostring(dq.remoteaddr)) -- called by testLuaProtobuf()
203 if requestor:isIPv4() then
204 requestor:truncate(24)
206 requestor:truncate(56)
208 protobuf:setRequestor(requestor)
211 tableTags = dq:getTagArray() -- get table from DNSQuery
214 for k, v in pairs( tableTags) do
215 table.insert(tablePB, k .. "," .. v)
218 protobuf:setTagArray(tablePB) -- store table in protobuf
219 protobuf:setTag("Query,123") -- add another tag entry in protobuf
221 protobuf:setResponseCode(DNSRCode.NXDOMAIN) -- set protobuf response code to be NXDOMAIN
223 local strReqName = tostring(dq.qname) -- get request dns name
225 protobuf:setProtobufResponseType() -- set protobuf to look like a response and not a query, with 0 default time
227 blobData = '\127' .. '\000' .. '\000' .. '\002' -- 127.0.0.2, note: lua 5.1 can only embed decimal not hex
229 protobuf:addResponseRR(strReqName, 1, 1, 123, blobData) -- add a RR to the protobuf
231 protobuf:setBytes(65) -- set the size of the query to confirm in checkProtobufBase
235 local tableTags = {} -- called by testProtobuf()
236 table.insert(tableTags, "TestLabel1,TestData1")
237 table.insert(tableTags, "TestLabel2,TestData2")
239 protobuf:setTagArray(tableTags)
240 protobuf:setTag('TestLabel3,TestData3')
241 protobuf:setTag("Query,123")
246 function alterLuaFirst(dq) -- called when dnsdist receives new request
248 tt["TestLabel1"] = "TestData1"
249 tt["TestLabel2"] = "TestData2"
253 dq:setTag("TestLabel3","TestData3")
254 return DNSAction.None, "" -- continue to the next rule
257 newServer{address="127.0.0.1:%s", useClientSubnet=true}
258 rl = newRemoteLogger('127.0.0.1:%s')
260 addAction(AllRule(), LuaAction(alterLuaFirst)) -- Add tags to DNSQuery first
262 addAction(AllRule(), RemoteLogAction(rl, alterProtobufQuery, {serverID='%s'})) -- Send protobuf message before lookup
264 addResponseAction(AllRule(), RemoteLogResponseAction(rl, alterProtobufResponse, true, {serverID='%s'})) -- Send protobuf message after lookup
268 def testProtobuf(self
):
270 Protobuf: Send data to a protobuf server
272 name
= 'query.protobuf.tests.powerdns.com.'
274 target
= 'target.protobuf.tests.powerdns.com.'
275 query
= dns
.message
.make_query(name
, 'A', 'IN')
276 response
= dns
.message
.make_response(query
)
278 rrset
= dns
.rrset
.from_text(name
,
283 response
.answer
.append(rrset
)
285 rrset
= dns
.rrset
.from_text(target
,
290 response
.answer
.append(rrset
)
292 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
293 self
.assertTrue(receivedQuery
)
294 self
.assertTrue(receivedResponse
)
295 receivedQuery
.id = query
.id
296 self
.assertEqual(query
, receivedQuery
)
297 self
.assertEqual(response
, receivedResponse
)
299 if self
._protobufQueue
.empty():
300 # let the protobuf messages the time to get there
303 # check the protobuf message corresponding to the UDP query
304 msg
= self
.getFirstProtobufMessage()
306 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
307 self
.checkProtobufTags(msg
.response
.tags
, [u
"TestLabel1,TestData1", u
"TestLabel2,TestData2", u
"TestLabel3,TestData3", u
"Query,123"])
309 # check the protobuf message corresponding to the UDP response
310 msg
= self
.getFirstProtobufMessage()
311 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, response
)
312 self
.checkProtobufTags(msg
.response
.tags
, [ u
"TestLabel1,TestData1", u
"TestLabel2,TestData2", u
"TestLabel3,TestData3", u
"Response,456"])
313 self
.assertEqual(len(msg
.response
.rrs
), 2)
314 rr
= msg
.response
.rrs
[0]
315 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, name
, 3600)
316 self
.assertEqual(rr
.rdata
.decode('utf-8'), target
)
317 rr
= msg
.response
.rrs
[1]
318 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, target
, 3600)
319 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '127.0.0.1')
321 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
322 self
.assertTrue(receivedQuery
)
323 self
.assertTrue(receivedResponse
)
324 receivedQuery
.id = query
.id
325 self
.assertEqual(query
, receivedQuery
)
326 self
.assertEqual(response
, receivedResponse
)
328 if self
._protobufQueue
.empty():
329 # let the protobuf messages the time to get there
332 # check the protobuf message corresponding to the TCP query
333 msg
= self
.getFirstProtobufMessage()
335 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.TCP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
336 self
.checkProtobufTags(msg
.response
.tags
, [u
"TestLabel1,TestData1", u
"TestLabel2,TestData2", u
"TestLabel3,TestData3", u
"Query,123"])
338 # check the protobuf message corresponding to the TCP response
339 msg
= self
.getFirstProtobufMessage()
340 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.TCP
, response
)
341 self
.checkProtobufTags(msg
.response
.tags
, [ u
"TestLabel1,TestData1", u
"TestLabel2,TestData2", u
"TestLabel3,TestData3", u
"Response,456"])
342 self
.assertEqual(len(msg
.response
.rrs
), 2)
343 rr
= msg
.response
.rrs
[0]
344 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, name
, 3600)
345 self
.assertEqual(rr
.rdata
.decode('utf-8'), target
)
346 rr
= msg
.response
.rrs
[1]
347 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, target
, 3600)
348 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '127.0.0.1')
350 def testLuaProtobuf(self
):
353 Protobuf: Check that the Lua callback rewrote the initiator
355 name
= 'lua.protobuf.tests.powerdns.com.'
356 query
= dns
.message
.make_query(name
, 'A', 'IN')
357 response
= dns
.message
.make_response(query
)
358 rrset
= dns
.rrset
.from_text(name
,
363 response
.answer
.append(rrset
)
366 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
368 self
.assertTrue(receivedQuery
)
369 self
.assertTrue(receivedResponse
)
370 receivedQuery
.id = query
.id
371 self
.assertEqual(query
, receivedQuery
)
372 self
.assertEqual(response
, receivedResponse
)
374 if self
._protobufQueue
.empty():
375 # let the protobuf messages the time to get there
378 # check the protobuf message corresponding to the UDP query
379 msg
= self
.getFirstProtobufMessage()
381 self
.checkProtobufQueryConvertedToResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, response
, '127.0.0.0')
382 self
.checkProtobufTags(msg
.response
.tags
, [ u
"TestLabel1,TestData1", u
"TestLabel2,TestData2", u
"TestLabel3,TestData3", u
"Query,123"])
384 # check the protobuf message corresponding to the UDP response
385 msg
= self
.getFirstProtobufMessage()
386 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, response
, '127.0.0.0')
387 self
.checkProtobufTags(msg
.response
.tags
, [ u
"TestLabel1,TestData1", u
"TestLabel2,TestData2", u
"TestLabel3,TestData3", u
"Response,456"])
388 self
.assertEqual(len(msg
.response
.rrs
), 1)
389 for rr
in msg
.response
.rrs
:
390 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 3600)
391 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '127.0.0.1')
393 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
394 self
.assertTrue(receivedQuery
)
395 self
.assertTrue(receivedResponse
)
396 receivedQuery
.id = query
.id
397 self
.assertEqual(query
, receivedQuery
)
398 self
.assertEqual(response
, receivedResponse
)
400 if self
._protobufQueue
.empty():
401 # let the protobuf messages the time to get there
404 # check the protobuf message corresponding to the TCP query
405 msg
= self
.getFirstProtobufMessage()
406 self
.checkProtobufQueryConvertedToResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.TCP
, response
, '127.0.0.0')
407 self
.checkProtobufTags(msg
.response
.tags
, [ u
"TestLabel1,TestData1", u
"TestLabel2,TestData2", u
"TestLabel3,TestData3", u
"Query,123"])
409 # check the protobuf message corresponding to the TCP response
410 msg
= self
.getFirstProtobufMessage()
411 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.TCP
, response
, '127.0.0.0')
412 self
.checkProtobufTags(msg
.response
.tags
, [ u
"TestLabel1,TestData1", u
"TestLabel2,TestData2", u
"TestLabel3,TestData3", u
"Response,456"])
413 self
.assertEqual(len(msg
.response
.rrs
), 1)
414 for rr
in msg
.response
.rrs
:
415 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, 3600)
416 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '127.0.0.1')
418 class TestProtobufMetaTags(DNSDistProtobufTest
):
419 _config_params
= ['_testServerPort', '_protobufServerPort']
420 _config_template
= """
421 newServer{address="127.0.0.1:%s"}
422 rl = newRemoteLogger('127.0.0.1:%d')
424 addAction(AllRule(), SetTagAction('my-tag-key', 'my-tag-value'))
425 addAction(AllRule(), SetTagAction('my-empty-key', ''))
426 addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1', exportTags='*'}, {b64='b64-content', ['my-tag-export-name']='tag:my-tag-key'}))
427 addResponseAction(AllRule(), SetTagResponseAction('my-tag-key2', 'my-tag-value2'))
428 addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1', exportTags='my-empty-key,my-tag-key2'}, {['my-tag-export-name']='tags'}))
431 def testProtobufMeta(self
):
433 Protobuf: Meta values
435 name
= 'meta.protobuf.tests.powerdns.com.'
436 query
= dns
.message
.make_query(name
, 'A', 'IN')
437 response
= dns
.message
.make_response(query
)
438 rrset
= dns
.rrset
.from_text(name
,
443 response
.answer
.append(rrset
)
445 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
446 self
.assertTrue(receivedQuery
)
447 self
.assertTrue(receivedResponse
)
448 receivedQuery
.id = query
.id
449 self
.assertEqual(query
, receivedQuery
)
450 self
.assertEqual(response
, receivedResponse
)
452 if self
._protobufQueue
.empty():
453 # let the protobuf messages the time to get there
456 # check the protobuf message corresponding to the UDP query
457 msg
= self
.getFirstProtobufMessage()
459 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
461 self
.assertEqual(len(msg
.response
.tags
), 2)
462 self
.assertIn('my-tag-key:my-tag-value', msg
.response
.tags
)
463 self
.assertIn('my-empty-key', msg
.response
.tags
)
465 self
.assertEqual(len(msg
.meta
), 2)
467 for entry
in msg
.meta
:
468 tags
[entry
.key
] = entry
.value
.stringVal
470 self
.assertIn('b64', tags
)
471 self
.assertIn('my-tag-export-name', tags
)
473 b64EncodedQuery
= base64
.b64encode(query
.to_wire()).decode('ascii')
474 self
.assertEqual(tags
['b64'], [b64EncodedQuery
])
475 self
.assertEqual(tags
['my-tag-export-name'], ['my-tag-value'])
477 # check the protobuf message corresponding to the UDP response
478 msg
= self
.getFirstProtobufMessage()
479 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, response
)
481 self
.assertEqual(len(msg
.response
.tags
), 2)
482 self
.assertIn('my-tag-key2:my-tag-value2', msg
.response
.tags
)
483 self
.assertIn('my-empty-key', msg
.response
.tags
)
485 self
.assertEqual(len(msg
.meta
), 1)
486 self
.assertEqual(msg
.meta
[0].key
, 'my-tag-export-name')
487 self
.assertEqual(len(msg
.meta
[0].value
.stringVal
), 3)
488 self
.assertIn('my-tag-key:my-tag-value', msg
.meta
[0].value
.stringVal
)
489 self
.assertIn('my-tag-key2:my-tag-value2', msg
.meta
[0].value
.stringVal
)
490 # no ':' when the value is empty
491 self
.assertIn('my-empty-key', msg
.meta
[0].value
.stringVal
)
493 class TestProtobufExtendedDNSErrorTags(DNSDistProtobufTest
):
494 _config_params
= ['_testServerPort', '_protobufServerPort']
495 _config_template
= """
496 newServer{address="127.0.0.1:%s"}
497 rl = newRemoteLogger('127.0.0.1:%d')
499 addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}))
500 addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1', exportExtendedErrorsToMeta='extended-error'}))
503 def testProtobufExtendedError(self
):
505 Protobuf: Extended Error
507 name
= 'extended-error.protobuf.tests.powerdns.com.'
508 query
= dns
.message
.make_query(name
, 'A', 'IN')
509 response
= dns
.message
.make_response(query
)
510 rrset
= dns
.rrset
.from_text(name
,
515 response
.answer
.append(rrset
)
516 ede
= extendederrors
.ExtendedErrorOption(15, b
'Blocked by RPZ!')
517 response
.use_edns(edns
=True, payload
=4096, options
=[ede
])
519 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
520 self
.assertTrue(receivedQuery
)
521 self
.assertTrue(receivedResponse
)
522 receivedQuery
.id = query
.id
523 self
.assertEqual(query
, receivedQuery
)
524 self
.assertEqual(response
, receivedResponse
)
526 if self
._protobufQueue
.empty():
527 # let the protobuf messages the time to get there
530 # check the protobuf message corresponding to the UDP query
531 msg
= self
.getFirstProtobufMessage()
533 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
536 self
.assertEqual(len(msg
.meta
), 0)
538 # check the protobuf message corresponding to the UDP response
539 msg
= self
.getFirstProtobufMessage()
540 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, response
)
543 self
.assertEqual(len(msg
.meta
), 1)
545 self
.assertEqual(msg
.meta
[0].key
, 'extended-error')
546 self
.assertEqual(len(msg
.meta
[0].value
.intVal
), 1)
547 self
.assertEqual(len(msg
.meta
[0].value
.stringVal
), 1)
548 self
.assertIn(15, msg
.meta
[0].value
.intVal
)
549 self
.assertIn('Blocked by RPZ!', msg
.meta
[0].value
.stringVal
)
551 class TestProtobufMetaDOH(DNSDistProtobufTest
):
553 _serverKey
= 'server.key'
554 _serverCert
= 'server.chain'
555 _serverName
= 'tls.tests.dnsdist.org'
557 _tlsServerPort
= pickAvailablePort()
558 _dohWithNGHTTP2ServerPort
= pickAvailablePort()
559 _dohWithNGHTTP2BaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohWithNGHTTP2ServerPort
))
560 _dohWithH2OServerPort
= pickAvailablePort()
561 _dohWithH2OBaseURL
= ("https://%s:%d/dns-query" % (_serverName
, _dohWithH2OServerPort
))
562 _config_template
= """
563 newServer{address="127.0.0.1:%d"}
564 rl = newRemoteLogger('127.0.0.1:%d')
566 addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
567 addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { keepIncomingHeaders=true, library='nghttp2' })
568 addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { keepIncomingHeaders=true, library='h2o' })
570 local mytags = {path='doh-path', host='doh-host', ['query-string']='doh-query-string', scheme='doh-scheme', agent='doh-header:user-agent'}
571 addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}, mytags))
572 addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1'}, mytags))
574 _config_params
= ['_testServerPort', '_protobufServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey']
576 def testProtobufMetaDoH(self
):
578 Protobuf: Meta values - DoH
580 name
= 'meta-doh.protobuf.tests.powerdns.com.'
581 query
= dns
.message
.make_query(name
, 'A', 'IN')
582 response
= dns
.message
.make_response(query
)
583 rrset
= dns
.rrset
.from_text(name
,
588 response
.answer
.append(rrset
)
590 for method
in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper"):
591 sender
= getattr(self
, method
)
592 (receivedQuery
, receivedResponse
) = sender(query
, response
)
594 self
.assertTrue(receivedQuery
)
595 self
.assertTrue(receivedResponse
)
596 receivedQuery
.id = query
.id
597 self
.assertEqual(query
, receivedQuery
)
598 self
.assertEqual(response
, receivedResponse
)
600 if self
._protobufQueue
.empty():
601 # let the protobuf messages the time to get there
604 # check the protobuf message corresponding to the query
605 msg
= self
.getFirstProtobufMessage()
607 if method
== "sendUDPQuery":
608 pbMessageType
= dnsmessage_pb2
.PBDNSMessage
.UDP
609 elif method
== "sendTCPQuery":
610 pbMessageType
= dnsmessage_pb2
.PBDNSMessage
.TCP
611 elif method
== "sendDOTQueryWrapper":
612 pbMessageType
= dnsmessage_pb2
.PBDNSMessage
.DOT
613 elif method
== "sendDOHWithNGHTTP2QueryWrapper" or method
== "sendDOHWithH2OQueryWrapper":
614 pbMessageType
= dnsmessage_pb2
.PBDNSMessage
.DOH
616 self
.checkProtobufQuery(msg
, pbMessageType
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
)
617 self
.assertEqual(len(msg
.meta
), 5)
619 for entry
in msg
.meta
:
620 self
.assertEqual(len(entry
.value
.stringVal
), 1)
621 tags
[entry
.key
] = entry
.value
.stringVal
[0]
623 self
.assertIn('agent', tags
)
624 if method
== "sendDOHWithNGHTTP2QueryWrapper" or method
== "sendDOHWithH2OQueryWrapper":
625 self
.assertIn('PycURL', tags
['agent'])
626 self
.assertIn('host', tags
)
627 if method
== "sendDOHWithNGHTTP2QueryWrapper":
628 self
.assertEqual(tags
['host'], self
._serverName
+ ':' + str(self
._dohWithNGHTTP
2ServerPort
))
629 elif method
== "sendDOHWithH2OQueryWrapper":
630 self
.assertEqual(tags
['host'], self
._serverName
+ ':' + str(self
._dohWithH
2OServerPort
))
631 self
.assertIn('path', tags
)
632 self
.assertEqual(tags
['path'], '/dns-query')
633 self
.assertIn('query-string', tags
)
634 self
.assertIn('?dns=', tags
['query-string'])
635 self
.assertIn('scheme', tags
)
636 self
.assertEqual(tags
['scheme'], 'https')
638 # check the protobuf message corresponding to the response
639 msg
= self
.getFirstProtobufMessage()
640 self
.checkProtobufResponse(msg
, pbMessageType
, response
)
641 self
.assertEqual(len(msg
.meta
), 5)
643 for entry
in msg
.meta
:
644 self
.assertEqual(len(entry
.value
.stringVal
), 1)
645 tags
[entry
.key
] = entry
.value
.stringVal
[0]
647 self
.assertIn('agent', tags
)
648 if method
== "sendDOHWithNGHTTP2QueryWrapper" or method
== "sendDOHWithH2OQueryWrapper":
649 self
.assertIn('PycURL', tags
['agent'])
650 self
.assertIn('host', tags
)
651 if method
== "sendDOHWithNGHTTP2QueryWrapper":
652 self
.assertEqual(tags
['host'], self
._serverName
+ ':' + str(self
._dohWithNGHTTP
2ServerPort
))
653 elif method
== "sendDOHWithH2OQueryWrapper":
654 self
.assertEqual(tags
['host'], self
._serverName
+ ':' + str(self
._dohWithH
2OServerPort
))
655 self
.assertIn('path', tags
)
656 self
.assertEqual(tags
['path'], '/dns-query')
657 self
.assertIn('query-string', tags
)
658 self
.assertIn('?dns=', tags
['query-string'])
659 self
.assertIn('scheme', tags
)
660 self
.assertEqual(tags
['scheme'], 'https')
662 class TestProtobufMetaProxy(DNSDistProtobufTest
):
664 _config_params
= ['_testServerPort', '_protobufServerPort']
665 _config_template
= """
666 setProxyProtocolACL( { "127.0.0.1/32" } )
668 newServer{address="127.0.0.1:%d"}
669 rl = newRemoteLogger('127.0.0.1:%d')
671 local mytags = {pp='proxy-protocol-values', pp42='proxy-protocol-value:42'}
672 addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}, mytags))
674 -- proxy protocol values are NOT passed to the response
677 def testProtobufMetaProxy(self
):
679 Protobuf: Meta values - Proxy
681 name
= 'meta-proxy.protobuf.tests.powerdns.com.'
682 query
= dns
.message
.make_query(name
, 'A', 'IN')
683 response
= dns
.message
.make_response(query
)
684 rrset
= dns
.rrset
.from_text(name
,
689 response
.answer
.append(rrset
)
691 destAddr
= "2001:db8::9"
693 srcAddr
= "2001:db8::8"
695 udpPayload
= ProxyProtocol
.getPayload(False, False, True, srcAddr
, destAddr
, srcPort
, destPort
, [ [ 2, b
'foo'], [ 42, b
'proxy'] ])
696 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(udpPayload
+ query
.to_wire(), response
, rawQuery
=True)
698 self
.assertTrue(receivedQuery
)
699 self
.assertTrue(receivedResponse
)
700 receivedQuery
.id = query
.id
701 self
.assertEqual(query
, receivedQuery
)
702 self
.assertEqual(response
, receivedResponse
)
704 if self
._protobufQueue
.empty():
705 # let the protobuf messages the time to get there
708 # check the protobuf message corresponding to the UDP query
709 msg
= self
.getFirstProtobufMessage()
711 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, initiator
='2001:db8::8', v6
=True)
712 self
.assertEqual(len(msg
.meta
), 2)
714 for entry
in msg
.meta
:
715 tags
[entry
.key
] = entry
.value
.stringVal
717 self
.assertIn('pp42', tags
)
718 self
.assertEqual(tags
['pp42'], ['proxy'])
719 self
.assertIn('pp', tags
)
720 self
.assertEqual(len(tags
['pp']), 2)
721 self
.assertIn('2:foo', tags
['pp'])
722 self
.assertIn('42:proxy', tags
['pp'])
724 class TestProtobufIPCipher(DNSDistProtobufTest
):
725 _config_params
= ['_testServerPort', '_protobufServerPort', '_protobufServerID', '_protobufServerID']
726 _config_template
= """
727 newServer{address="127.0.0.1:%s", useClientSubnet=true}
728 key = makeIPCipherKey("some 16-byte key")
729 rl = newRemoteLogger('127.0.0.1:%s')
730 addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='%s', ipEncryptKey=key})) -- Send protobuf message before lookup
731 addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, true, {serverID='%s', ipEncryptKey=key})) -- Send protobuf message after lookup
735 def testProtobuf(self
):
737 Protobuf: Send data to a protobuf server, with pseudonymization
739 name
= 'query.protobuf-ipcipher.tests.powerdns.com.'
741 target
= 'target.protobuf-ipcipher.tests.powerdns.com.'
742 query
= dns
.message
.make_query(name
, 'A', 'IN')
743 response
= dns
.message
.make_response(query
)
745 rrset
= dns
.rrset
.from_text(name
,
750 response
.answer
.append(rrset
)
752 rrset
= dns
.rrset
.from_text(target
,
757 response
.answer
.append(rrset
)
759 (receivedQuery
, receivedResponse
) = self
.sendUDPQuery(query
, response
)
760 self
.assertTrue(receivedQuery
)
761 self
.assertTrue(receivedResponse
)
762 receivedQuery
.id = query
.id
763 self
.assertEqual(query
, receivedQuery
)
764 self
.assertEqual(response
, receivedResponse
)
766 if self
._protobufQueue
.empty():
767 # let the protobuf messages the time to get there
770 # check the protobuf message corresponding to the UDP query
771 msg
= self
.getFirstProtobufMessage()
773 # 108.41.239.98 is 127.0.0.1 pseudonymized with ipcipher and the current key
774 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, '108.41.239.98')
776 # check the protobuf message corresponding to the UDP response
777 msg
= self
.getFirstProtobufMessage()
778 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.UDP
, response
, '108.41.239.98')
780 self
.assertEqual(len(msg
.response
.rrs
), 2)
781 rr
= msg
.response
.rrs
[0]
782 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, name
, 3600)
783 self
.assertEqual(rr
.rdata
.decode('ascii'), target
)
784 rr
= msg
.response
.rrs
[1]
785 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, target
, 3600)
786 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '127.0.0.1')
788 (receivedQuery
, receivedResponse
) = self
.sendTCPQuery(query
, response
)
789 self
.assertTrue(receivedQuery
)
790 self
.assertTrue(receivedResponse
)
791 receivedQuery
.id = query
.id
792 self
.assertEqual(query
, receivedQuery
)
793 self
.assertEqual(response
, receivedResponse
)
795 if self
._protobufQueue
.empty():
796 # let the protobuf messages the time to get there
799 # check the protobuf message corresponding to the TCP query
800 msg
= self
.getFirstProtobufMessage()
801 # 108.41.239.98 is 127.0.0.1 pseudonymized with ipcipher and the current key
802 self
.checkProtobufQuery(msg
, dnsmessage_pb2
.PBDNSMessage
.TCP
, query
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, name
, '108.41.239.98')
804 # check the protobuf message corresponding to the TCP response
805 msg
= self
.getFirstProtobufMessage()
806 self
.checkProtobufResponse(msg
, dnsmessage_pb2
.PBDNSMessage
.TCP
, response
, '108.41.239.98')
807 self
.assertEqual(len(msg
.response
.rrs
), 2)
808 rr
= msg
.response
.rrs
[0]
809 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, name
, 3600)
810 self
.assertEqual(rr
.rdata
.decode('ascii'), target
)
811 rr
= msg
.response
.rrs
[1]
812 self
.checkProtobufResponseRecord(rr
, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, target
, 3600)
813 self
.assertEqual(socket
.inet_ntop(socket
.AF_INET
, rr
.rdata
), '127.0.0.1')