]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.dnsdist/test_Protobuf.py
Merge pull request #13756 from rgacogne/ddist-xsk-doc-typos
[thirdparty/pdns.git] / regression-tests.dnsdist / test_Protobuf.py
CommitLineData
1d0bd88a 1#!/usr/bin/env python
c02b6723 2import base64
1d0bd88a
RG
3import threading
4import socket
5import struct
6import sys
7import time
630eb526 8from dnsdisttests import DNSDistTest, pickAvailablePort, Queue
c02b6723 9from proxyprotocol import ProxyProtocol
1d0bd88a
RG
10
11import dns
12import dnsmessage_pb2
e3762c2e 13import extendederrors
1d0bd88a 14
f29fabd9 15class DNSDistProtobufTest(DNSDistTest):
630eb526 16 _protobufServerPort = pickAvailablePort()
b4f23783 17 _protobufQueue = Queue()
312a09a6 18 _protobufServerID = 'dnsdist-server-1'
1d0bd88a 19 _protobufCounter = 0
1d0bd88a
RG
20
21 @classmethod
22 def ProtobufListener(cls, port):
7373e3a6 23 cls._backgroundThreads[threading.get_native_id()] = True
1d0bd88a
RG
24 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
25 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
26 try:
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))
30 sys.exit(1)
31
32 sock.listen(100)
7373e3a6 33 sock.settimeout(1.0)
1d0bd88a 34 while True:
7373e3a6
RG
35 try:
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()]
40 break
41 else:
42 continue
43
1d0bd88a
RG
44 data = None
45 while True:
46 data = conn.recv(2)
47 if not data:
48 break
49 (datalen,) = struct.unpack("!H", data)
50 data = conn.recv(datalen)
51 if not data:
52 break
53
54 cls._protobufQueue.put(data, True, timeout=2.0)
55
56 conn.close()
57 sock.close()
58
59 @classmethod
60 def startResponders(cls):
5df86a8a 61 cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
630eb526 62 cls._UDPResponder.daemon = True
1d0bd88a 63 cls._UDPResponder.start()
741ebe08 64
5df86a8a 65 cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
630eb526 66 cls._TCPResponder.daemon = True
1d0bd88a
RG
67 cls._TCPResponder.start()
68
69 cls._protobufListener = threading.Thread(name='Protobuf Listener', target=cls.ProtobufListener, args=[cls._protobufServerPort])
630eb526 70 cls._protobufListener.daemon = True
1d0bd88a
RG
71 cls._protobufListener.start()
72
73 def getFirstProtobufMessage(self):
74 self.assertFalse(self._protobufQueue.empty())
75 data = self._protobufQueue.get(False)
76 self.assertTrue(data)
77 msg = dnsmessage_pb2.PBDNSMessage()
78 msg.ParseFromString(data)
79 return msg
80
c02b6723 81 def checkProtobufBase(self, msg, protocol, query, initiator, normalQueryResponse=True, v6=False):
1d0bd88a
RG
82 self.assertTrue(msg)
83 self.assertTrue(msg.HasField('timeSec'))
84 self.assertTrue(msg.HasField('socketFamily'))
c02b6723
RG
85 if v6:
86 self.assertEqual(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET6)
87 else:
88 self.assertEqual(msg.socketFamily, dnsmessage_pb2.PBDNSMessage.INET)
1d0bd88a
RG
89 self.assertTrue(msg.HasField('from'))
90 fromvalue = getattr(msg, 'from')
c02b6723
RG
91 if v6:
92 self.assertEqual(socket.inet_ntop(socket.AF_INET6, fromvalue), initiator)
93 else:
94 self.assertEqual(socket.inet_ntop(socket.AF_INET, fromvalue), initiator)
1d0bd88a 95 self.assertTrue(msg.HasField('socketProtocol'))
4bfebc93 96 self.assertEqual(msg.socketProtocol, protocol)
1d0bd88a
RG
97 self.assertTrue(msg.HasField('messageId'))
98 self.assertTrue(msg.HasField('id'))
4bfebc93 99 self.assertEqual(msg.id, query.id)
1d0bd88a 100 self.assertTrue(msg.HasField('inBytes'))
312a09a6 101 self.assertTrue(msg.HasField('serverIdentity'))
4bfebc93 102 self.assertEqual(msg.serverIdentity, self._protobufServerID.encode('utf-8'))
312a09a6 103
c02b6723 104 if normalQueryResponse and (protocol == dnsmessage_pb2.PBDNSMessage.UDP or protocol == dnsmessage_pb2.PBDNSMessage.TCP):
0096ada6 105 # compare inBytes with length of query/response
4bfebc93 106 self.assertEqual(msg.inBytes, len(query.to_wire()))
1d0bd88a
RG
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'))
4bfebc93
CH
110 # self.assertEqual(len(msg.originalRequestorSubnet), 4)
111 # self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.originalRequestorSubnet), '127.0.0.1')
1d0bd88a 112
c02b6723 113 def checkProtobufQuery(self, msg, protocol, query, qclass, qtype, qname, initiator='127.0.0.1', v6=False):
4bfebc93 114 self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSQueryType)
c02b6723 115 self.checkProtobufBase(msg, protocol, query, initiator, v6=v6)
1d0bd88a
RG
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'))
c02b6723
RG
119 if not v6:
120 self.assertEqual(socket.inet_ntop(socket.AF_INET, msg.to), '127.0.0.1')
1d0bd88a
RG
121 self.assertTrue(msg.HasField('question'))
122 self.assertTrue(msg.question.HasField('qClass'))
4bfebc93 123 self.assertEqual(msg.question.qClass, qclass)
1d0bd88a 124 self.assertTrue(msg.question.HasField('qType'))
4bfebc93 125 self.assertEqual(msg.question.qClass, qtype)
1d0bd88a 126 self.assertTrue(msg.question.HasField('qName'))
4bfebc93 127 self.assertEqual(msg.question.qName, qname)
1d0bd88a 128
0096ada6
RG
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")
741ebe08 134
29cd61cc 135 def checkProtobufQueryConvertedToResponse(self, msg, protocol, response, initiator='127.0.0.0'):
4bfebc93 136 self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSResponseType)
0096ada6
RG
137 # skip comparing inBytes (size of the query) with the length of the generated response
138 self.checkProtobufBase(msg, protocol, response, initiator, False)
29cd61cc
SO
139 self.assertTrue(msg.HasField('response'))
140 self.assertTrue(msg.response.HasField('queryTimeSec'))
141
c02b6723 142 def checkProtobufResponse(self, msg, protocol, response, initiator='127.0.0.1', v6=False):
4bfebc93 143 self.assertEqual(msg.type, dnsmessage_pb2.PBDNSMessage.DNSResponseType)
c02b6723 144 self.checkProtobufBase(msg, protocol, response, initiator, v6=v6)
29cd61cc
SO
145 self.assertTrue(msg.HasField('response'))
146 self.assertTrue(msg.response.HasField('queryTimeSec'))
741ebe08 147
8667904b
RG
148 def checkProtobufResponseRecord(self, record, rclass, rtype, rname, rttl):
149 self.assertTrue(record.HasField('class'))
4bfebc93 150 self.assertEqual(getattr(record, 'class'), rclass)
8667904b 151 self.assertTrue(record.HasField('type'))
4bfebc93 152 self.assertEqual(record.type, rtype)
8667904b 153 self.assertTrue(record.HasField('name'))
4bfebc93 154 self.assertEqual(record.name, rname)
8667904b 155 self.assertTrue(record.HasField('ttl'))
4bfebc93 156 self.assertEqual(record.ttl, rttl)
8667904b
RG
157 self.assertTrue(record.HasField('rdata'))
158
f29fabd9
RG
159class TestProtobuf(DNSDistProtobufTest):
160 _config_params = ['_testServerPort', '_protobufServerPort', '_protobufServerID', '_protobufServerID']
161 _config_template = """
162 luasmn = newSuffixMatchNode()
163 luasmn:add(newDNSName('lua.protobuf.tests.powerdns.com.'))
164
165 function alterProtobufResponse(dq, protobuf)
166 if luasmn:check(dq.qname) then
207e77dd 167 requestor = newCA(tostring(dq.remoteaddr)) -- called by testLuaProtobuf()
f29fabd9
RG
168 if requestor:isIPv4() then
169 requestor:truncate(24)
170 else
171 requestor:truncate(56)
172 end
173 protobuf:setRequestor(requestor)
174
175 local tableTags = {}
176 table.insert(tableTags, "TestLabel1,TestData1")
177 table.insert(tableTags, "TestLabel2,TestData2")
178
179 protobuf:setTagArray(tableTags)
180
181 protobuf:setTag('TestLabel3,TestData3')
182
183 protobuf:setTag("Response,456")
184
185 else
186
187 local tableTags = {} -- called by testProtobuf()
188 table.insert(tableTags, "TestLabel1,TestData1")
189 table.insert(tableTags, "TestLabel2,TestData2")
190 protobuf:setTagArray(tableTags)
191
192 protobuf:setTag('TestLabel3,TestData3')
193
194 protobuf:setTag("Response,456")
195
196 end
197 end
198
199 function alterProtobufQuery(dq, protobuf)
200
201 if luasmn:check(dq.qname) then
207e77dd 202 requestor = newCA(tostring(dq.remoteaddr)) -- called by testLuaProtobuf()
f29fabd9
RG
203 if requestor:isIPv4() then
204 requestor:truncate(24)
205 else
206 requestor:truncate(56)
207 end
208 protobuf:setRequestor(requestor)
209
210 local tableTags = {}
211 tableTags = dq:getTagArray() -- get table from DNSQuery
212
213 local tablePB = {}
214 for k, v in pairs( tableTags) do
215 table.insert(tablePB, k .. "," .. v)
216 end
217
218 protobuf:setTagArray(tablePB) -- store table in protobuf
219 protobuf:setTag("Query,123") -- add another tag entry in protobuf
220
d3ec24f9 221 protobuf:setResponseCode(DNSRCode.NXDOMAIN) -- set protobuf response code to be NXDOMAIN
f29fabd9 222
207e77dd 223 local strReqName = tostring(dq.qname) -- get request dns name
f29fabd9
RG
224
225 protobuf:setProtobufResponseType() -- set protobuf to look like a response and not a query, with 0 default time
226
87f46425 227 blobData = '\127' .. '\000' .. '\000' .. '\002' -- 127.0.0.2, note: lua 5.1 can only embed decimal not hex
f29fabd9
RG
228
229 protobuf:addResponseRR(strReqName, 1, 1, 123, blobData) -- add a RR to the protobuf
230
231 protobuf:setBytes(65) -- set the size of the query to confirm in checkProtobufBase
232
233 else
234
235 local tableTags = {} -- called by testProtobuf()
236 table.insert(tableTags, "TestLabel1,TestData1")
237 table.insert(tableTags, "TestLabel2,TestData2")
238
239 protobuf:setTagArray(tableTags)
240 protobuf:setTag('TestLabel3,TestData3')
241 protobuf:setTag("Query,123")
242
243 end
244 end
245
246 function alterLuaFirst(dq) -- called when dnsdist receives new request
247 local tt = {}
248 tt["TestLabel1"] = "TestData1"
249 tt["TestLabel2"] = "TestData2"
250
251 dq:setTagArray(tt)
252
253 dq:setTag("TestLabel3","TestData3")
254 return DNSAction.None, "" -- continue to the next rule
255 end
256
257 newServer{address="127.0.0.1:%s", useClientSubnet=true}
258 rl = newRemoteLogger('127.0.0.1:%s')
259
260 addAction(AllRule(), LuaAction(alterLuaFirst)) -- Add tags to DNSQuery first
261
262 addAction(AllRule(), RemoteLogAction(rl, alterProtobufQuery, {serverID='%s'})) -- Send protobuf message before lookup
263
264 addResponseAction(AllRule(), RemoteLogResponseAction(rl, alterProtobufResponse, true, {serverID='%s'})) -- Send protobuf message after lookup
265
266 """
267
1d0bd88a
RG
268 def testProtobuf(self):
269 """
270 Protobuf: Send data to a protobuf server
271 """
29cd61cc 272 name = 'query.protobuf.tests.powerdns.com.'
741ebe08 273
165c9030 274 target = 'target.protobuf.tests.powerdns.com.'
1d0bd88a
RG
275 query = dns.message.make_query(name, 'A', 'IN')
276 response = dns.message.make_response(query)
741ebe08 277
1d0bd88a 278 rrset = dns.rrset.from_text(name,
165c9030
RG
279 3600,
280 dns.rdataclass.IN,
281 dns.rdatatype.CNAME,
282 target)
283 response.answer.append(rrset)
741ebe08 284
165c9030 285 rrset = dns.rrset.from_text(target,
1d0bd88a
RG
286 3600,
287 dns.rdataclass.IN,
288 dns.rdatatype.A,
289 '127.0.0.1')
290 response.answer.append(rrset)
291
292 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
293 self.assertTrue(receivedQuery)
294 self.assertTrue(receivedResponse)
295 receivedQuery.id = query.id
4bfebc93
CH
296 self.assertEqual(query, receivedQuery)
297 self.assertEqual(response, receivedResponse)
1d0bd88a 298
e3762c2e
RG
299 if self._protobufQueue.empty():
300 # let the protobuf messages the time to get there
301 time.sleep(1)
1d0bd88a
RG
302
303 # check the protobuf message corresponding to the UDP query
304 msg = self.getFirstProtobufMessage()
741ebe08 305
29cd61cc 306 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
0096ada6 307 self.checkProtobufTags(msg.response.tags, [u"TestLabel1,TestData1", u"TestLabel2,TestData2", u"TestLabel3,TestData3", u"Query,123"])
1d0bd88a
RG
308
309 # check the protobuf message corresponding to the UDP response
310 msg = self.getFirstProtobufMessage()
0096ada6
RG
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"])
4bfebc93 313 self.assertEqual(len(msg.response.rrs), 2)
165c9030
RG
314 rr = msg.response.rrs[0]
315 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.CNAME, name, 3600)
4bfebc93 316 self.assertEqual(rr.rdata.decode('utf-8'), target)
165c9030
RG
317 rr = msg.response.rrs[1]
318 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, target, 3600)
4bfebc93 319 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '127.0.0.1')
1d0bd88a
RG
320
321 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
322 self.assertTrue(receivedQuery)
323 self.assertTrue(receivedResponse)
324 receivedQuery.id = query.id
4bfebc93
CH
325 self.assertEqual(query, receivedQuery)
326 self.assertEqual(response, receivedResponse)
1d0bd88a 327
e3762c2e
RG
328 if self._protobufQueue.empty():
329 # let the protobuf messages the time to get there
330 time.sleep(1)
1d0bd88a
RG
331
332 # check the protobuf message corresponding to the TCP query
333 msg = self.getFirstProtobufMessage()
29cd61cc 334
1d0bd88a 335 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.TCP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
0096ada6 336 self.checkProtobufTags(msg.response.tags, [u"TestLabel1,TestData1", u"TestLabel2,TestData2", u"TestLabel3,TestData3", u"Query,123"])
1d0bd88a
RG
337
338 # check the protobuf message corresponding to the TCP response
339 msg = self.getFirstProtobufMessage()
0096ada6
RG
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"])
4bfebc93 342 self.assertEqual(len(msg.response.rrs), 2)
165c9030
RG
343 rr = msg.response.rrs[0]
344 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.CNAME, name, 3600)
4bfebc93 345 self.assertEqual(rr.rdata.decode('utf-8'), target)
165c9030
RG
346 rr = msg.response.rrs[1]
347 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, target, 3600)
4bfebc93 348 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '127.0.0.1')
8667904b
RG
349
350 def testLuaProtobuf(self):
741ebe08 351
8667904b
RG
352 """
353 Protobuf: Check that the Lua callback rewrote the initiator
354 """
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,
359 3600,
360 dns.rdataclass.IN,
361 dns.rdatatype.A,
362 '127.0.0.1')
363 response.answer.append(rrset)
364
741ebe08 365
8667904b 366 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
741ebe08 367
29cd61cc
SO
368 self.assertTrue(receivedQuery)
369 self.assertTrue(receivedResponse)
8667904b 370 receivedQuery.id = query.id
4bfebc93
CH
371 self.assertEqual(query, receivedQuery)
372 self.assertEqual(response, receivedResponse)
8667904b 373
e3762c2e
RG
374 if self._protobufQueue.empty():
375 # let the protobuf messages the time to get there
376 time.sleep(1)
8667904b
RG
377
378 # check the protobuf message corresponding to the UDP query
379 msg = self.getFirstProtobufMessage()
29cd61cc 380
0096ada6
RG
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"])
8667904b
RG
383
384 # check the protobuf message corresponding to the UDP response
385 msg = self.getFirstProtobufMessage()
0096ada6
RG
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"])
4bfebc93 388 self.assertEqual(len(msg.response.rrs), 1)
8667904b
RG
389 for rr in msg.response.rrs:
390 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 3600)
4bfebc93 391 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '127.0.0.1')
8667904b
RG
392
393 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
394 self.assertTrue(receivedQuery)
395 self.assertTrue(receivedResponse)
396 receivedQuery.id = query.id
4bfebc93
CH
397 self.assertEqual(query, receivedQuery)
398 self.assertEqual(response, receivedResponse)
8667904b 399
e3762c2e
RG
400 if self._protobufQueue.empty():
401 # let the protobuf messages the time to get there
402 time.sleep(1)
8667904b
RG
403
404 # check the protobuf message corresponding to the TCP query
405 msg = self.getFirstProtobufMessage()
0096ada6
RG
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"])
8667904b
RG
408
409 # check the protobuf message corresponding to the TCP response
410 msg = self.getFirstProtobufMessage()
0096ada6
RG
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"])
4bfebc93 413 self.assertEqual(len(msg.response.rrs), 1)
8667904b
RG
414 for rr in msg.response.rrs:
415 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 3600)
4bfebc93 416 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '127.0.0.1')
f29fabd9 417
c02b6723
RG
418class 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')
423
424 addAction(AllRule(), SetTagAction('my-tag-key', 'my-tag-value'))
6b9302bd 425 addAction(AllRule(), SetTagAction('my-empty-key', ''))
fb63c25e 426 addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1', exportTags='*'}, {b64='b64-content', ['my-tag-export-name']='tag:my-tag-key'}))
c02b6723 427 addResponseAction(AllRule(), SetTagResponseAction('my-tag-key2', 'my-tag-value2'))
fb63c25e 428 addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1', exportTags='my-empty-key,my-tag-key2'}, {['my-tag-export-name']='tags'}))
c02b6723
RG
429 """
430
431 def testProtobufMeta(self):
432 """
433 Protobuf: Meta values
434 """
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,
439 3600,
440 dns.rdataclass.IN,
441 dns.rdatatype.A,
442 '127.0.0.1')
443 response.answer.append(rrset)
444
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)
451
e3762c2e
RG
452 if self._protobufQueue.empty():
453 # let the protobuf messages the time to get there
454 time.sleep(1)
c02b6723
RG
455
456 # check the protobuf message corresponding to the UDP query
457 msg = self.getFirstProtobufMessage()
458
459 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
fb63c25e
RG
460 # regular tags
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)
464 # meta tags
c02b6723 465 self.assertEqual(len(msg.meta), 2)
30b91a66
RG
466 tags = {}
467 for entry in msg.meta:
468 tags[entry.key] = entry.value.stringVal
469
470 self.assertIn('b64', tags)
471 self.assertIn('my-tag-export-name', tags)
472
c02b6723 473 b64EncodedQuery = base64.b64encode(query.to_wire()).decode('ascii')
30b91a66
RG
474 self.assertEqual(tags['b64'], [b64EncodedQuery])
475 self.assertEqual(tags['my-tag-export-name'], ['my-tag-value'])
c02b6723
RG
476
477 # check the protobuf message corresponding to the UDP response
478 msg = self.getFirstProtobufMessage()
479 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, response)
fb63c25e
RG
480 # regular tags
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)
484 # meta tags
c02b6723
RG
485 self.assertEqual(len(msg.meta), 1)
486 self.assertEqual(msg.meta[0].key, 'my-tag-export-name')
6b9302bd 487 self.assertEqual(len(msg.meta[0].value.stringVal), 3)
30b91a66
RG
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)
6b9302bd
RG
490 # no ':' when the value is empty
491 self.assertIn('my-empty-key', msg.meta[0].value.stringVal)
c02b6723 492
e3762c2e
RG
493class 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')
498
499 addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}))
500 addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1', exportExtendedErrorsToMeta='extended-error'}))
501 """
502
503 def testProtobufExtendedError(self):
504 """
505 Protobuf: Extended Error
506 """
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,
511 3600,
512 dns.rdataclass.IN,
513 dns.rdatatype.A,
514 '127.0.0.1')
515 response.answer.append(rrset)
516 ede = extendederrors.ExtendedErrorOption(15, b'Blocked by RPZ!')
517 response.use_edns(edns=True, payload=4096, options=[ede])
518
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)
525
526 if self._protobufQueue.empty():
527 # let the protobuf messages the time to get there
528 time.sleep(1)
529
530 # check the protobuf message corresponding to the UDP query
531 msg = self.getFirstProtobufMessage()
532
533 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
534
535 # meta tags
536 self.assertEqual(len(msg.meta), 0)
537
538 # check the protobuf message corresponding to the UDP response
539 msg = self.getFirstProtobufMessage()
540 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, response)
541
542 # meta tags
543 self.assertEqual(len(msg.meta), 1)
544
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)
550
c02b6723
RG
551class TestProtobufMetaDOH(DNSDistProtobufTest):
552
553 _serverKey = 'server.key'
554 _serverCert = 'server.chain'
555 _serverName = 'tls.tests.dnsdist.org'
556 _caCert = 'ca.pem'
630eb526 557 _tlsServerPort = pickAvailablePort()
b030f5bb
RG
558 _dohWithNGHTTP2ServerPort = pickAvailablePort()
559 _dohWithNGHTTP2BaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithNGHTTP2ServerPort))
560 _dohWithH2OServerPort = pickAvailablePort()
561 _dohWithH2OBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithH2OServerPort))
c02b6723
RG
562 _config_template = """
563 newServer{address="127.0.0.1:%d"}
564 rl = newRemoteLogger('127.0.0.1:%d')
334e3549
RG
565
566 addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
b030f5bb
RG
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' })
c02b6723
RG
569
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))
573 """
b030f5bb
RG
574 _config_params = ['_testServerPort', '_protobufServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey']
575
c02b6723
RG
576 def testProtobufMetaDoH(self):
577 """
578 Protobuf: Meta values - DoH
579 """
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,
584 3600,
585 dns.rdataclass.IN,
586 dns.rdatatype.A,
587 '127.0.0.1')
588 response.answer.append(rrset)
589
b030f5bb 590 for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper"):
334e3549
RG
591 sender = getattr(self, method)
592 (receivedQuery, receivedResponse) = sender(query, response)
593
594 self.assertTrue(receivedQuery)
595 self.assertTrue(receivedResponse)
596 receivedQuery.id = query.id
597 self.assertEqual(query, receivedQuery)
598 self.assertEqual(response, receivedResponse)
599
e3762c2e
RG
600 if self._protobufQueue.empty():
601 # let the protobuf messages the time to get there
602 time.sleep(1)
334e3549
RG
603
604 # check the protobuf message corresponding to the query
605 msg = self.getFirstProtobufMessage()
606
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
b030f5bb 613 elif method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
334e3549
RG
614 pbMessageType = dnsmessage_pb2.PBDNSMessage.DOH
615
334e3549
RG
616 self.checkProtobufQuery(msg, pbMessageType, query, dns.rdataclass.IN, dns.rdatatype.A, name)
617 self.assertEqual(len(msg.meta), 5)
618 tags = {}
619 for entry in msg.meta:
04ba56ed
RG
620 self.assertEqual(len(entry.value.stringVal), 1)
621 tags[entry.key] = entry.value.stringVal[0]
334e3549
RG
622
623 self.assertIn('agent', tags)
b030f5bb 624 if method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
334e3549
RG
625 self.assertIn('PycURL', tags['agent'])
626 self.assertIn('host', tags)
b030f5bb
RG
627 if method == "sendDOHWithNGHTTP2QueryWrapper":
628 self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithNGHTTP2ServerPort))
629 elif method == "sendDOHWithH2OQueryWrapper":
630 self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithH2OServerPort))
334e3549
RG
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')
94fe1b32 637 self.assertEqual(msg.httpVersion, dnsmessage_pb2.PBDNSMessage.HTTPVersion.HTTP2)
334e3549
RG
638
639 # check the protobuf message corresponding to the response
640 msg = self.getFirstProtobufMessage()
641 self.checkProtobufResponse(msg, pbMessageType, response)
642 self.assertEqual(len(msg.meta), 5)
643 tags = {}
644 for entry in msg.meta:
04ba56ed
RG
645 self.assertEqual(len(entry.value.stringVal), 1)
646 tags[entry.key] = entry.value.stringVal[0]
334e3549
RG
647
648 self.assertIn('agent', tags)
b030f5bb 649 if method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
334e3549
RG
650 self.assertIn('PycURL', tags['agent'])
651 self.assertIn('host', tags)
b030f5bb
RG
652 if method == "sendDOHWithNGHTTP2QueryWrapper":
653 self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithNGHTTP2ServerPort))
654 elif method == "sendDOHWithH2OQueryWrapper":
655 self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithH2OServerPort))
334e3549
RG
656 self.assertIn('path', tags)
657 self.assertEqual(tags['path'], '/dns-query')
658 self.assertIn('query-string', tags)
659 self.assertIn('?dns=', tags['query-string'])
660 self.assertIn('scheme', tags)
661 self.assertEqual(tags['scheme'], 'https')
c02b6723
RG
662
663class TestProtobufMetaProxy(DNSDistProtobufTest):
664
665 _config_params = ['_testServerPort', '_protobufServerPort']
666 _config_template = """
667 setProxyProtocolACL( { "127.0.0.1/32" } )
668
669 newServer{address="127.0.0.1:%d"}
670 rl = newRemoteLogger('127.0.0.1:%d')
671
672 local mytags = {pp='proxy-protocol-values', pp42='proxy-protocol-value:42'}
673 addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}, mytags))
674
675 -- proxy protocol values are NOT passed to the response
676 """
677
678 def testProtobufMetaProxy(self):
679 """
680 Protobuf: Meta values - Proxy
681 """
682 name = 'meta-proxy.protobuf.tests.powerdns.com.'
683 query = dns.message.make_query(name, 'A', 'IN')
684 response = dns.message.make_response(query)
685 rrset = dns.rrset.from_text(name,
686 3600,
687 dns.rdataclass.IN,
688 dns.rdatatype.A,
689 '127.0.0.1')
690 response.answer.append(rrset)
691
692 destAddr = "2001:db8::9"
693 destPort = 9999
694 srcAddr = "2001:db8::8"
695 srcPort = 8888
696 udpPayload = ProxyProtocol.getPayload(False, False, True, srcAddr, destAddr, srcPort, destPort, [ [ 2, b'foo'], [ 42, b'proxy'] ])
697 (receivedQuery, receivedResponse) = self.sendUDPQuery(udpPayload + query.to_wire(), response, rawQuery=True)
698
699 self.assertTrue(receivedQuery)
700 self.assertTrue(receivedResponse)
701 receivedQuery.id = query.id
702 self.assertEqual(query, receivedQuery)
703 self.assertEqual(response, receivedResponse)
704
e3762c2e
RG
705 if self._protobufQueue.empty():
706 # let the protobuf messages the time to get there
707 time.sleep(1)
c02b6723
RG
708
709 # check the protobuf message corresponding to the UDP query
710 msg = self.getFirstProtobufMessage()
711
712 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, initiator='2001:db8::8', v6=True)
713 self.assertEqual(len(msg.meta), 2)
714 tags = {}
715 for entry in msg.meta:
30b91a66 716 tags[entry.key] = entry.value.stringVal
c02b6723
RG
717
718 self.assertIn('pp42', tags)
30b91a66 719 self.assertEqual(tags['pp42'], ['proxy'])
c02b6723 720 self.assertIn('pp', tags)
30b91a66
RG
721 self.assertEqual(len(tags['pp']), 2)
722 self.assertIn('2:foo', tags['pp'])
723 self.assertIn('42:proxy', tags['pp'])
c02b6723 724
f29fabd9
RG
725class TestProtobufIPCipher(DNSDistProtobufTest):
726 _config_params = ['_testServerPort', '_protobufServerPort', '_protobufServerID', '_protobufServerID']
727 _config_template = """
728 newServer{address="127.0.0.1:%s", useClientSubnet=true}
729 key = makeIPCipherKey("some 16-byte key")
730 rl = newRemoteLogger('127.0.0.1:%s')
731 addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='%s', ipEncryptKey=key})) -- Send protobuf message before lookup
732 addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, true, {serverID='%s', ipEncryptKey=key})) -- Send protobuf message after lookup
733
734 """
735
736 def testProtobuf(self):
737 """
87f46425 738 Protobuf: Send data to a protobuf server, with pseudonymization
f29fabd9
RG
739 """
740 name = 'query.protobuf-ipcipher.tests.powerdns.com.'
741
78603024 742 target = 'target.protobuf-ipcipher.tests.powerdns.com.'
f29fabd9
RG
743 query = dns.message.make_query(name, 'A', 'IN')
744 response = dns.message.make_response(query)
745
746 rrset = dns.rrset.from_text(name,
747 3600,
748 dns.rdataclass.IN,
749 dns.rdatatype.CNAME,
750 target)
751 response.answer.append(rrset)
752
753 rrset = dns.rrset.from_text(target,
754 3600,
755 dns.rdataclass.IN,
756 dns.rdatatype.A,
757 '127.0.0.1')
758 response.answer.append(rrset)
759
760 (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
761 self.assertTrue(receivedQuery)
762 self.assertTrue(receivedResponse)
763 receivedQuery.id = query.id
4bfebc93
CH
764 self.assertEqual(query, receivedQuery)
765 self.assertEqual(response, receivedResponse)
f29fabd9 766
e3762c2e
RG
767 if self._protobufQueue.empty():
768 # let the protobuf messages the time to get there
769 time.sleep(1)
f29fabd9
RG
770
771 # check the protobuf message corresponding to the UDP query
772 msg = self.getFirstProtobufMessage()
773
774 # 108.41.239.98 is 127.0.0.1 pseudonymized with ipcipher and the current key
775 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '108.41.239.98')
776
777 # check the protobuf message corresponding to the UDP response
778 msg = self.getFirstProtobufMessage()
779 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, response, '108.41.239.98')
780
4bfebc93 781 self.assertEqual(len(msg.response.rrs), 2)
f29fabd9
RG
782 rr = msg.response.rrs[0]
783 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.CNAME, name, 3600)
4bfebc93 784 self.assertEqual(rr.rdata.decode('ascii'), target)
f29fabd9
RG
785 rr = msg.response.rrs[1]
786 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, target, 3600)
4bfebc93 787 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '127.0.0.1')
f29fabd9
RG
788
789 (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
790 self.assertTrue(receivedQuery)
791 self.assertTrue(receivedResponse)
792 receivedQuery.id = query.id
4bfebc93
CH
793 self.assertEqual(query, receivedQuery)
794 self.assertEqual(response, receivedResponse)
f29fabd9 795
e3762c2e
RG
796 if self._protobufQueue.empty():
797 # let the protobuf messages the time to get there
798 time.sleep(1)
f29fabd9
RG
799
800 # check the protobuf message corresponding to the TCP query
801 msg = self.getFirstProtobufMessage()
802 # 108.41.239.98 is 127.0.0.1 pseudonymized with ipcipher and the current key
803 self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.TCP, query, dns.rdataclass.IN, dns.rdatatype.A, name, '108.41.239.98')
804
805 # check the protobuf message corresponding to the TCP response
806 msg = self.getFirstProtobufMessage()
807 self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.TCP, response, '108.41.239.98')
4bfebc93 808 self.assertEqual(len(msg.response.rrs), 2)
f29fabd9
RG
809 rr = msg.response.rrs[0]
810 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.CNAME, name, 3600)
4bfebc93 811 self.assertEqual(rr.rdata.decode('ascii'), target)
f29fabd9
RG
812 rr = msg.response.rrs[1]
813 self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, target, 3600)
4bfebc93 814 self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '127.0.0.1')
655fe34d
CHB
815
816class TestProtobufQUIC(DNSDistProtobufTest):
817
818 _serverKey = 'server.key'
819 _serverCert = 'server.chain'
820 _serverName = 'tls.tests.dnsdist.org'
821 _caCert = 'ca.pem'
822 _doqServerPort = pickAvailablePort()
823 _doh3ServerPort = pickAvailablePort()
824 _dohBaseURL = ("https://%s:%d/" % (_serverName, _doh3ServerPort))
825 _config_template = """
826 newServer{address="127.0.0.1:%d"}
827 rl = newRemoteLogger('127.0.0.1:%d')
828
829 addDOQLocal("127.0.0.1:%d", "%s", "%s")
830 addDOH3Local("127.0.0.1:%d", "%s", "%s")
831
832 addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}))
833 """
834 _config_params = ['_testServerPort', '_protobufServerPort', '_doqServerPort', '_serverCert', '_serverKey', '_doh3ServerPort', '_serverCert', '_serverKey']
835
836 def testProtobufMetaDoH(self):
837 """
838 Protobuf: Test logged protocol for QUIC and DOH3
839 """
840 name = 'quic.protobuf.tests.powerdns.com.'
841 query = dns.message.make_query(name, 'A', 'IN')
842 response = dns.message.make_response(query)
843 rrset = dns.rrset.from_text(name,
844 3600,
845 dns.rdataclass.IN,
846 dns.rdatatype.A,
847 '127.0.0.1')
848 response.answer.append(rrset)
849
850 for method in ("sendDOQQueryWrapper", "sendDOH3QueryWrapper"):
851 sender = getattr(self, method)
852 (receivedQuery, receivedResponse) = sender(query, response)
853
854 self.assertTrue(receivedQuery)
855 self.assertTrue(receivedResponse)
856 receivedQuery.id = query.id
857 self.assertEqual(query, receivedQuery)
858 self.assertEqual(response, receivedResponse)
859
860 if self._protobufQueue.empty():
861 # let the protobuf messages the time to get there
862 time.sleep(1)
863
864 # check the protobuf message corresponding to the query
865 msg = self.getFirstProtobufMessage()
866
867 if method == "sendDOQQueryWrapper":
868 pbMessageType = dnsmessage_pb2.PBDNSMessage.DOQ
869 elif method == "sendDOH3QueryWrapper":
870 pbMessageType = dnsmessage_pb2.PBDNSMessage.DOH
871 self.assertEqual(msg.httpVersion, dnsmessage_pb2.PBDNSMessage.HTTPVersion.HTTP3)
872
873 self.checkProtobufQuery(msg, pbMessageType, query, dns.rdataclass.IN, dns.rdatatype.A, name)