11 from recursortests
import RecursorTest
13 class RPZServer(object):
15 def __init__(self
, port
):
16 self
._currentSerial
= 0
17 self
._targetSerial
= 1
18 self
._serverPort
= port
19 listener
= threading
.Thread(name
='RPZ Listener', target
=self
._listener
, args
=[])
20 listener
.setDaemon(True)
23 def getCurrentSerial(self
):
24 return self
._currentSerial
26 def moveToSerial(self
, newSerial
):
27 if newSerial
== self
._currentSerial
:
30 if newSerial
!= self
._currentSerial
+ 1:
31 raise AssertionError("Asking the RPZ server to serve serial %d, already serving %d" % (newSerial
, self
._currentSerial
))
32 self
._targetSerial
= newSerial
35 def _getAnswer(self
, message
):
37 response
= dns
.message
.make_response(message
)
40 if message
.question
[0].rdtype
== dns
.rdatatype
.AXFR
:
41 if self
._currentSerial
!= 0:
42 print('Received an AXFR query but IXFR expected because the current serial is %d' % (self
._currentSerial
))
43 return (None, self
._currentSerial
)
45 newSerial
= self
._targetSerial
47 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
48 dns
.rrset
.from_text('a.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1'),
49 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
)
52 elif message
.question
[0].rdtype
== dns
.rdatatype
.IXFR
:
53 oldSerial
= message
.authority
[0][0].serial
55 # special case for the 9th update, which might get skipped
56 if oldSerial
!= self
._currentSerial
and self
._currentSerial
!= 9:
57 print('Received an IXFR query with an unexpected serial %d, expected %d' % (oldSerial
, self
._currentSerial
))
58 return (None, self
._currentSerial
)
60 newSerial
= self
._targetSerial
63 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
64 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial
),
66 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
67 dns
.rrset
.from_text('b.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1'),
68 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
)
72 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
73 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial
),
74 dns
.rrset
.from_text('a.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1'),
75 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
77 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
)
81 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
82 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial
),
83 dns
.rrset
.from_text('b.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1'),
84 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
85 dns
.rrset
.from_text('c.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1'),
86 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
)
89 # this one is a bit special, we are answering with a full AXFR
91 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
92 dns
.rrset
.from_text('d.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1'),
93 dns
.rrset
.from_text('tc.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, 'rpz-tcp-only.'),
94 dns
.rrset
.from_text('drop.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, 'rpz-drop.'),
95 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
)
100 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
101 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial
),
102 dns
.rrset
.from_text('d.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1'),
103 dns
.rrset
.from_text('tc.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, 'rpz-tcp-only.'),
104 dns
.rrset
.from_text('drop.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, 'rpz-drop.'),
105 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
106 dns
.rrset
.from_text('e.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1', '192.0.2.2'),
107 dns
.rrset
.from_text('e.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.MX
, '10 mx.example.'),
108 dns
.rrset
.from_text('f.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, 'e.example.'),
109 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
)
113 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
114 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial
),
115 dns
.rrset
.from_text('e.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1', '192.0.2.2'),
116 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
117 dns
.rrset
.from_text('e.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.2'),
118 dns
.rrset
.from_text('tc.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, 'rpz-tcp-only.'),
119 dns
.rrset
.from_text('drop.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, 'rpz-drop.'),
120 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
)
123 # this one is a bit special too, we are answering with a full AXFR and the new zone is empty
125 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
126 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
)
129 # IXFR inserting a duplicate, we should not crash and skip it
131 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
132 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial
),
133 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
134 dns
.rrset
.from_text('dup.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, 'rpz-passthru.'),
135 dns
.rrset
.from_text('dup.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.CNAME
, 'rpz-passthru.'),
136 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
)
138 elif newSerial
== 10:
139 # full AXFR to make sure we are removing the duplicate, adding a record, to check that the update was correctly applied
141 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
142 dns
.rrset
.from_text('f.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1'),
143 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
)
145 elif newSerial
== 11:
146 # IXFR with two deltas, the first one adding a 'g' and the second one removing 'f'
148 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % (newSerial
+ 1)),
149 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % oldSerial
),
150 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
151 dns
.rrset
.from_text('g.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1'),
152 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial
),
153 dns
.rrset
.from_text('f.example.zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.1'),
154 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % (newSerial
+ 1)),
155 dns
.rrset
.from_text('zone.rpz.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.SOA
, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % (newSerial
+ 1))
157 # this one has two updates in one
158 newSerial
= newSerial
+ 1
159 self
._targetSerial
= self
._targetSerial
+ 1
161 response
.answer
= records
162 return (newSerial
, response
)
164 def _connectionHandler(self
, conn
):
170 (datalen
,) = struct
.unpack("!H", data
)
171 data
= conn
.recv(datalen
)
175 message
= dns
.message
.from_wire(data
)
176 if len(message
.question
) != 1:
177 print('Invalid RPZ query, qdcount is %d' % (len(message
.question
)))
179 if not message
.question
[0].rdtype
in [dns
.rdatatype
.AXFR
, dns
.rdatatype
.IXFR
]:
180 print('Invalid RPZ query, qtype is %d' % (message
.question
.rdtype
))
182 (serial
, answer
) = self
._getAnswer
(message
)
184 print('Unable to get a response for %s %d' % (message
.question
[0].name
, message
.question
[0].rdtype
))
187 wire
= answer
.to_wire()
188 conn
.send(struct
.pack("!H", len(wire
)))
190 self
._currentSerial
= serial
196 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
197 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEPORT
, 1)
199 sock
.bind(("127.0.0.1", self
._serverPort
))
200 except socket
.error
as e
:
201 print("Error binding in the RPZ listener: %s" % str(e
))
207 (conn
, _
) = sock
.accept()
208 thread
= threading
.Thread(name
='RPZ Connection Handler',
209 target
=self
._connectionHandler
,
211 thread
.setDaemon(True)
214 except socket
.error
as e
:
215 print('Error in RPZ socket: %s' % str(e
))
218 class RPZRecursorTest(RecursorTest
):
221 _wsPassword
= 'secretpassword'
222 _apiKey
= 'secretapikey'
224 _lua_dns_script_file
= """
227 -- disable the RPZ policy named 'zone.rpz' for AD=1 queries
228 if dq:getDH():getAD() then
229 dq:discardPolicy('zone.rpz.')
235 _config_template
= """
236 auth-zones=example=configs/%s/example.zone
239 webserver-address=127.0.0.1
240 webserver-password=%s
243 """ % (_confdir
, _wsPort
, _wsPassword
, _apiKey
)
249 cls
.startResponders()
251 confdir
= os
.path
.join('configs', cls
._confdir
)
252 cls
.createConfigDir(confdir
)
254 cls
.generateRecursorConfig(confdir
)
255 cls
.startRecursor(confdir
, cls
._recursorPort
)
258 def tearDownClass(cls
):
259 cls
.tearDownRecursor()
261 def checkBlocked(self
, name
, shouldBeBlocked
=True, adQuery
=False, singleCheck
=False):
262 query
= dns
.message
.make_query(name
, 'A', want_dnssec
=True)
263 query
.flags |
= dns
.flags
.CD
265 query
.flags |
= dns
.flags
.AD
267 for method
in ("sendUDPQuery", "sendTCPQuery"):
268 sender
= getattr(self
, method
)
270 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
272 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.1')
274 expected
= dns
.rrset
.from_text(name
, 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42')
276 self
.assertRRsetInAnswer(res
, expected
)
280 def checkNotBlocked(self
, name
, adQuery
=False, singleCheck
=False):
281 self
.checkBlocked(name
, False, adQuery
, singleCheck
)
283 def checkCustom(self
, qname
, qtype
, expected
):
284 query
= dns
.message
.make_query(qname
, qtype
, want_dnssec
=True)
285 query
.flags |
= dns
.flags
.CD
286 for method
in ("sendUDPQuery", "sendTCPQuery"):
287 sender
= getattr(self
, method
)
289 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
290 self
.assertRRsetInAnswer(res
, expected
)
292 def checkNoData(self
, qname
, qtype
):
293 query
= dns
.message
.make_query(qname
, qtype
, want_dnssec
=True)
294 query
.flags |
= dns
.flags
.CD
295 for method
in ("sendUDPQuery", "sendTCPQuery"):
296 sender
= getattr(self
, method
)
298 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
299 self
.assertEqual(len(res
.answer
), 0)
301 def checkNXD(self
, qname
, qtype
='A'):
302 query
= dns
.message
.make_query(qname
, qtype
, want_dnssec
=True)
303 query
.flags |
= dns
.flags
.CD
304 for method
in ("sendUDPQuery", "sendTCPQuery"):
305 sender
= getattr(self
, method
)
307 self
.assertRcodeEqual(res
, dns
.rcode
.NXDOMAIN
)
308 self
.assertEqual(len(res
.answer
), 0)
309 self
.assertEqual(len(res
.authority
), 1)
311 def checkTruncated(self
, qname
, qtype
='A'):
312 query
= dns
.message
.make_query(qname
, qtype
, want_dnssec
=True)
313 query
.flags |
= dns
.flags
.CD
314 res
= self
.sendUDPQuery(query
)
315 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
316 self
.assertMessageHasFlags(res
, ['QR', 'RA', 'RD', 'CD', 'TC'])
317 self
.assertEqual(len(res
.answer
), 0)
318 self
.assertEqual(len(res
.authority
), 0)
319 self
.assertEqual(len(res
.additional
), 0)
321 res
= self
.sendTCPQuery(query
)
322 self
.assertRcodeEqual(res
, dns
.rcode
.NXDOMAIN
)
323 self
.assertMessageHasFlags(res
, ['QR', 'RA', 'RD', 'CD'])
324 self
.assertEqual(len(res
.answer
), 0)
325 self
.assertEqual(len(res
.authority
), 1)
326 self
.assertEqual(len(res
.additional
), 0)
328 def checkDropped(self
, qname
, qtype
='A'):
329 query
= dns
.message
.make_query(qname
, qtype
, want_dnssec
=True)
330 query
.flags |
= dns
.flags
.CD
331 for method
in ("sendUDPQuery", "sendTCPQuery"):
332 sender
= getattr(self
, method
)
334 self
.assertEqual(res
, None)
336 def checkRPZStats(self
, serial
, recordsCount
, fullXFRCount
, totalXFRCount
):
337 headers
= {'x-api-key': self
._apiKey
}
338 url
= 'http://127.0.0.1:' + str(self
._wsPort
) + '/api/v1/servers/localhost/rpzstatistics'
339 r
= requests
.get(url
, headers
=headers
, timeout
=self
._wsTimeout
)
341 self
.assertEquals(r
.status_code
, 200)
342 self
.assertTrue(r
.json())
344 self
.assertIn('zone.rpz.', content
)
345 zone
= content
['zone.rpz.']
346 for key
in ['last_update', 'records', 'serial', 'transfers_failed', 'transfers_full', 'transfers_success']:
347 self
.assertIn(key
, zone
)
349 self
.assertEquals(zone
['serial'], serial
)
350 self
.assertEquals(zone
['records'], recordsCount
)
351 self
.assertEquals(zone
['transfers_full'], fullXFRCount
)
352 self
.assertEquals(zone
['transfers_success'], totalXFRCount
)
355 rpzServer
= RPZServer(rpzServerPort
)
357 class RPZXFRRecursorTest(RPZRecursorTest
):
359 This test makes sure that we correctly update RPZ zones via AXFR then IXFR
363 _lua_config_file
= """
364 -- The first server is a bogus one, to test that we correctly fail over to the second one
365 rpzMaster({'127.0.0.1:9999', '127.0.0.1:%d'}, 'zone.rpz.', { refresh=1 })
366 """ % (rpzServerPort
)
370 _wsPassword
= 'secretpassword'
371 _apiKey
= 'secretapikey'
372 _config_template
= """
373 auth-zones=example=configs/%s/example.zone
376 webserver-address=127.0.0.1
377 webserver-password=%s
379 """ % (_confdir
, _wsPort
, _wsPassword
, _apiKey
)
383 def generateRecursorConfig(cls
, confdir
):
384 authzonepath
= os
.path
.join(confdir
, 'example.zone')
385 with
open(authzonepath
, 'w') as authzone
:
386 authzone
.write("""$ORIGIN example.
388 a 3600 IN A 192.0.2.42
389 b 3600 IN A 192.0.2.42
390 c 3600 IN A 192.0.2.42
391 d 3600 IN A 192.0.2.42
392 e 3600 IN A 192.0.2.42
393 """.format(soa
=cls
._SOA
))
394 super(RPZRecursorTest
, cls
).generateRecursorConfig(confdir
)
396 def waitUntilCorrectSerialIsLoaded(self
, serial
, timeout
=5):
399 rpzServer
.moveToSerial(serial
)
402 while attempts
< timeout
:
403 currentSerial
= rpzServer
.getCurrentSerial()
404 if currentSerial
> serial
:
405 raise AssertionError("Expected serial %d, got %d" % (serial
, currentSerial
))
406 if currentSerial
== serial
:
407 self
._xfrDone
= self
._xfrDone
+ 1
410 attempts
= attempts
+ 1
413 raise AssertionError("Waited %d seconds for the serial to be updated to %d but the serial is still %d" % (timeout
, serial
, currentSerial
))
416 # first zone, only a should be blocked
417 self
.waitUntilCorrectSerialIsLoaded(1)
418 self
.checkRPZStats(1, 1, 1, self
._xfrDone
)
419 self
.checkBlocked('a.example.')
420 self
.checkNotBlocked('b.example.')
421 self
.checkNotBlocked('c.example.')
423 # second zone, a and b should be blocked
424 self
.waitUntilCorrectSerialIsLoaded(2)
425 self
.checkRPZStats(2, 2, 1, self
._xfrDone
)
426 self
.checkBlocked('a.example.')
427 self
.checkBlocked('b.example.')
428 self
.checkNotBlocked('c.example.')
430 # third zone, only b should be blocked
431 self
.waitUntilCorrectSerialIsLoaded(3)
432 self
.checkRPZStats(3, 1, 1, self
._xfrDone
)
433 self
.checkNotBlocked('a.example.')
434 self
.checkBlocked('b.example.')
435 self
.checkNotBlocked('c.example.')
437 # fourth zone, only c should be blocked
438 self
.waitUntilCorrectSerialIsLoaded(4)
439 self
.checkRPZStats(4, 1, 1, self
._xfrDone
)
440 self
.checkNotBlocked('a.example.')
441 self
.checkNotBlocked('b.example.')
442 self
.checkBlocked('c.example.')
444 # fifth zone, we should get a full AXFR this time, and only d should be blocked
445 self
.waitUntilCorrectSerialIsLoaded(5)
446 self
.checkRPZStats(5, 3, 2, self
._xfrDone
)
447 self
.checkNotBlocked('a.example.')
448 self
.checkNotBlocked('b.example.')
449 self
.checkNotBlocked('c.example.')
450 self
.checkBlocked('d.example.')
452 # sixth zone, only e should be blocked, f is a local data record
453 self
.waitUntilCorrectSerialIsLoaded(6)
454 self
.checkRPZStats(6, 2, 2, self
._xfrDone
)
455 self
.checkNotBlocked('a.example.')
456 self
.checkNotBlocked('b.example.')
457 self
.checkNotBlocked('c.example.')
458 self
.checkNotBlocked('d.example.')
459 self
.checkCustom('e.example.', 'A', dns
.rrset
.from_text('e.example.', 0, dns
.rdataclass
.IN
, 'A', '192.0.2.1', '192.0.2.2'))
460 self
.checkCustom('e.example.', 'MX', dns
.rrset
.from_text('e.example.', 0, dns
.rdataclass
.IN
, 'MX', '10 mx.example.'))
461 self
.checkNoData('e.example.', 'AAAA')
462 self
.checkCustom('f.example.', 'A', dns
.rrset
.from_text('f.example.', 0, dns
.rdataclass
.IN
, 'CNAME', 'e.example.'))
464 # seventh zone, e should only have one A
465 self
.waitUntilCorrectSerialIsLoaded(7)
466 self
.checkRPZStats(7, 4, 2, self
._xfrDone
)
467 self
.checkNotBlocked('a.example.')
468 self
.checkNotBlocked('b.example.')
469 self
.checkNotBlocked('c.example.')
470 self
.checkNotBlocked('d.example.')
471 self
.checkCustom('e.example.', 'A', dns
.rrset
.from_text('e.example.', 0, dns
.rdataclass
.IN
, 'A', '192.0.2.2'))
472 self
.checkCustom('e.example.', 'MX', dns
.rrset
.from_text('e.example.', 0, dns
.rdataclass
.IN
, 'MX', '10 mx.example.'))
473 self
.checkNoData('e.example.', 'AAAA')
474 self
.checkCustom('f.example.', 'A', dns
.rrset
.from_text('f.example.', 0, dns
.rdataclass
.IN
, 'CNAME', 'e.example.'))
475 # check that the policy is disabled for AD=1 queries
476 self
.checkNotBlocked('e.example.', True)
477 # check non-custom policies
478 self
.checkTruncated('tc.example.')
479 self
.checkDropped('drop.example.')
481 # eighth zone, all entries should be gone
482 self
.waitUntilCorrectSerialIsLoaded(8)
483 self
.checkRPZStats(8, 0, 3, self
._xfrDone
)
484 self
.checkNotBlocked('a.example.')
485 self
.checkNotBlocked('b.example.')
486 self
.checkNotBlocked('c.example.')
487 self
.checkNotBlocked('d.example.')
488 self
.checkNotBlocked('e.example.')
489 self
.checkNXD('f.example.')
490 self
.checkNXD('tc.example.')
491 self
.checkNXD('drop.example.')
493 # 9th zone is a duplicate, it might get skipped
495 rpzServer
.moveToSerial(9)
497 self
.waitUntilCorrectSerialIsLoaded(10)
498 self
.checkRPZStats(10, 1, 4, self
._xfrDone
)
499 self
.checkNotBlocked('a.example.')
500 self
.checkNotBlocked('b.example.')
501 self
.checkNotBlocked('c.example.')
502 self
.checkNotBlocked('d.example.')
503 self
.checkNotBlocked('e.example.')
504 self
.checkBlocked('f.example.')
505 self
.checkNXD('tc.example.')
506 self
.checkNXD('drop.example.')
508 # the next update will update the zone twice
509 rpzServer
.moveToSerial(11)
511 self
.waitUntilCorrectSerialIsLoaded(12)
512 self
.checkRPZStats(12, 1, 4, self
._xfrDone
)
513 self
.checkNotBlocked('a.example.')
514 self
.checkNotBlocked('b.example.')
515 self
.checkNotBlocked('c.example.')
516 self
.checkNotBlocked('d.example.')
517 self
.checkNotBlocked('e.example.')
518 self
.checkNXD('f.example.')
519 self
.checkBlocked('g.example.')
520 self
.checkNXD('tc.example.')
521 self
.checkNXD('drop.example.')
523 class RPZFileRecursorTest(RPZRecursorTest
):
525 This test makes sure that we correctly load RPZ zones from a file
529 _lua_config_file
= """
530 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
532 _config_template
= """
533 auth-zones=example=configs/%s/example.zone
537 def generateRecursorConfig(cls
, confdir
):
538 authzonepath
= os
.path
.join(confdir
, 'example.zone')
539 with
open(authzonepath
, 'w') as authzone
:
540 authzone
.write("""$ORIGIN example.
542 a 3600 IN A 192.0.2.42
543 b 3600 IN A 192.0.2.42
544 c 3600 IN A 192.0.2.42
545 d 3600 IN A 192.0.2.42
546 e 3600 IN A 192.0.2.42
547 z 3600 IN A 192.0.2.42
548 """.format(soa
=cls
._SOA
))
550 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
551 with
open(rpzFilePath
, 'w') as rpzZone
:
552 rpzZone
.write("""$ORIGIN zone.rpz.
554 a.example.zone.rpz. 60 IN A 192.0.2.42
555 a.example.zone.rpz. 60 IN A 192.0.2.43
556 a.example.zone.rpz. 60 IN TXT "some text"
557 drop.example.zone.rpz. 60 IN CNAME rpz-drop.
558 z.example.zone.rpz. 60 IN A 192.0.2.1
559 tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
560 """.format(soa
=cls
._SOA
))
561 super(RPZFileRecursorTest
, cls
).generateRecursorConfig(confdir
)
564 self
.checkCustom('a.example.', 'A', dns
.rrset
.from_text('a.example.', 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42', '192.0.2.43'))
565 self
.checkCustom('a.example.', 'TXT', dns
.rrset
.from_text('a.example.', 0, dns
.rdataclass
.IN
, 'TXT', '"some text"'))
566 self
.checkBlocked('z.example.')
567 self
.checkNotBlocked('b.example.')
568 self
.checkNotBlocked('c.example.')
569 self
.checkNotBlocked('d.example.')
570 self
.checkNotBlocked('e.example.')
571 # check that the policy is disabled for AD=1 queries
572 self
.checkNotBlocked('z.example.', True)
573 # check non-custom policies
574 self
.checkTruncated('tc.example.')
575 self
.checkDropped('drop.example.')
577 class RPZFileDefaultPolRecursorTest(RPZRecursorTest
):
579 This test makes sure that we correctly load RPZ zones from a file with a default policy
582 _confdir
= 'RPZFileDefaultPolicy'
583 _lua_config_file
= """
584 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", defpol=Policy.NoAction })
586 _config_template
= """
587 auth-zones=example=configs/%s/example.zone
591 def generateRecursorConfig(cls
, confdir
):
592 authzonepath
= os
.path
.join(confdir
, 'example.zone')
593 with
open(authzonepath
, 'w') as authzone
:
594 authzone
.write("""$ORIGIN example.
596 a 3600 IN A 192.0.2.42
597 b 3600 IN A 192.0.2.42
598 c 3600 IN A 192.0.2.42
599 d 3600 IN A 192.0.2.42
600 drop 3600 IN A 192.0.2.42
601 e 3600 IN A 192.0.2.42
602 z 3600 IN A 192.0.2.42
603 """.format(soa
=cls
._SOA
))
605 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
606 with
open(rpzFilePath
, 'w') as rpzZone
:
607 rpzZone
.write("""$ORIGIN zone.rpz.
609 a.example.zone.rpz. 60 IN A 192.0.2.42
610 drop.example.zone.rpz. 60 IN CNAME rpz-drop.
611 z.example.zone.rpz. 60 IN A 192.0.2.1
612 tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
613 """.format(soa
=cls
._SOA
))
614 super(RPZFileDefaultPolRecursorTest
, cls
).generateRecursorConfig(confdir
)
617 # local data entries are overridden by default
618 self
.checkCustom('a.example.', 'A', dns
.rrset
.from_text('a.example.', 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42'))
619 self
.checkNoData('a.example.', 'TXT')
620 # will not be blocked because the default policy overrides local data entries by default
621 self
.checkNotBlocked('z.example.')
622 self
.checkNotBlocked('b.example.')
623 self
.checkNotBlocked('c.example.')
624 self
.checkNotBlocked('d.example.')
625 self
.checkNotBlocked('e.example.')
626 # check non-local policies, they should be overridden by the default policy
627 self
.checkNXD('tc.example.', 'A')
628 self
.checkNotBlocked('drop.example.')
630 class RPZFileDefaultPolNotOverrideLocalRecursorTest(RPZRecursorTest
):
632 This test makes sure that we correctly load RPZ zones from a file with a default policy, not overriding local data entries
635 _confdir
= 'RPZFileDefaultPolicyNotOverrideLocal'
636 _lua_config_file
= """
637 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", defpol=Policy.NoAction, defpolOverrideLocalData=false })
639 _config_template
= """
640 auth-zones=example=configs/%s/example.zone
644 def generateRecursorConfig(cls
, confdir
):
645 authzonepath
= os
.path
.join(confdir
, 'example.zone')
646 with
open(authzonepath
, 'w') as authzone
:
647 authzone
.write("""$ORIGIN example.
649 a 3600 IN A 192.0.2.42
650 b 3600 IN A 192.0.2.42
651 c 3600 IN A 192.0.2.42
652 d 3600 IN A 192.0.2.42
653 drop 3600 IN A 192.0.2.42
654 e 3600 IN A 192.0.2.42
655 z 3600 IN A 192.0.2.42
656 """.format(soa
=cls
._SOA
))
658 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
659 with
open(rpzFilePath
, 'w') as rpzZone
:
660 rpzZone
.write("""$ORIGIN zone.rpz.
662 a.example.zone.rpz. 60 IN A 192.0.2.42
663 a.example.zone.rpz. 60 IN A 192.0.2.43
664 a.example.zone.rpz. 60 IN TXT "some text"
665 drop.example.zone.rpz. 60 IN CNAME rpz-drop.
666 z.example.zone.rpz. 60 IN A 192.0.2.1
667 tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
668 """.format(soa
=cls
._SOA
))
669 super(RPZFileDefaultPolNotOverrideLocalRecursorTest
, cls
).generateRecursorConfig(confdir
)
672 # local data entries will not be overridden by the default policy
673 self
.checkCustom('a.example.', 'A', dns
.rrset
.from_text('a.example.', 0, dns
.rdataclass
.IN
, 'A', '192.0.2.42', '192.0.2.43'))
674 self
.checkCustom('a.example.', 'TXT', dns
.rrset
.from_text('a.example.', 0, dns
.rdataclass
.IN
, 'TXT', '"some text"'))
675 # will be blocked because the default policy does not override local data entries
676 self
.checkBlocked('z.example.')
677 self
.checkNotBlocked('b.example.')
678 self
.checkNotBlocked('c.example.')
679 self
.checkNotBlocked('d.example.')
680 self
.checkNotBlocked('e.example.')
681 # check non-local policies, they should be overridden by the default policy
682 self
.checkNXD('tc.example.', 'A')
683 self
.checkNotBlocked('drop.example.')
685 class RPZSimpleAuthServer(object):
687 def __init__(self
, port
):
688 self
._serverPort
= port
689 listener
= threading
.Thread(name
='RPZ Simple Auth Listener', target
=self
._listener
, args
=[])
690 listener
.setDaemon(True)
693 def _getAnswer(self
, message
):
695 response
= dns
.message
.make_response(message
)
696 response
.flags |
= dns
.flags
.AA
698 dns
.rrset
.from_text('nsip.delegated.example.', 60, dns
.rdataclass
.IN
, dns
.rdatatype
.A
, '192.0.2.42')
701 response
.answer
= records
705 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
)
707 sock
.bind(("127.0.0.1", self
._serverPort
))
708 except socket
.error
as e
:
709 print("Error binding in the RPZ simple auth listener: %s" % str(e
))
714 data
, addr
= sock
.recvfrom(4096)
715 message
= dns
.message
.from_wire(data
)
716 if len(message
.question
) != 1:
717 print('Invalid query, qdcount is %d' % (len(message
.question
)))
720 answer
= self
._getAnswer
(message
)
722 print('Unable to get a response for %s %d' % (message
.question
[0].name
, message
.question
[0].rdtype
))
725 wire
= answer
.to_wire()
726 sock
.sendto(wire
, addr
)
728 except socket
.error
as e
:
729 print('Error in RPZ simple auth socket: %s' % str(e
))
731 rpzAuthServerPort
= 4260
732 rpzAuthServer
= RPZSimpleAuthServer(rpzAuthServerPort
)
734 class RPZOrderingPrecedenceRecursorTest(RPZRecursorTest
):
736 This test makes sure that the recursor respects the RPZ ordering precedence rules
739 _confdir
= 'RPZOrderingPrecedence'
740 _lua_config_file
= """
741 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
742 rpzFile('configs/%s/zone2.rpz', { policyName="zone2.rpz."})
743 """ % (_confdir
, _confdir
)
744 _config_template
= """
745 auth-zones=example=configs/%s/example.zone
746 forward-zones=delegated.example=127.0.0.1:%d
747 """ % (_confdir
, rpzAuthServerPort
)
750 def generateRecursorConfig(cls
, confdir
):
751 authzonepath
= os
.path
.join(confdir
, 'example.zone')
752 with
open(authzonepath
, 'w') as authzone
:
753 authzone
.write("""$ORIGIN example.
755 sub.test 3600 IN A 192.0.2.42
756 passthru-then-blocked-by-higher 3600 IN A 192.0.2.66
757 passthru-then-blocked-by-same 3600 IN A 192.0.2.66
758 blocked-then-passhtru-by-higher 3600 IN A 192.0.2.100
759 """.format(soa
=cls
._SOA
))
761 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
762 with
open(rpzFilePath
, 'w') as rpzZone
:
763 rpzZone
.write("""$ORIGIN zone.rpz.
765 *.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
766 32.66.2.0.192.rpz-ip.zone.rpz. 60 IN A 192.0.2.1
767 32.100.2.0.192.rpz-ip.zone.rpz. 60 IN CNAME rpz-passthru.
768 passthru-then-blocked-by-same.example.zone.rpz. 60 IN CNAME rpz-passthru.
769 32.1.0.0.127.rpz-nsip.zone.rpz. 60 IN CNAME rpz-passthru.
770 """.format(soa
=cls
._SOA
))
772 rpzFilePath
= os
.path
.join(confdir
, 'zone2.rpz')
773 with
open(rpzFilePath
, 'w') as rpzZone
:
774 rpzZone
.write("""$ORIGIN zone2.rpz.
776 sub.test.example.com.zone2.rpz. 60 IN CNAME .
777 passthru-then-blocked-by-higher.example.zone2.rpz. 60 IN CNAME rpz-passthru.
778 blocked-then-passhtru-by-higher.example.zone2.rpz. 60 IN A 192.0.2.1
779 32.42.2.0.192.rpz-ip 60 IN CNAME .
780 """.format(soa
=cls
._SOA
))
782 super(RPZOrderingPrecedenceRecursorTest
, cls
).generateRecursorConfig(confdir
)
784 def testRPZOrderingForQNameAndWhitelisting(self
):
785 # we should first match on the qname (the wildcard, not on the exact name since
786 # we respect the order of the RPZ zones), see the pass-thru rule
787 # and only process RPZ rules of higher precedence.
788 # The subsequent rule on the content of the A should therefore not trigger a NXDOMAIN.
789 self
.checkNotBlocked('sub.test.example.')
791 def testRPZOrderingWhitelistedThenBlockedByHigher(self
):
792 # we should first match on the qname from the second RPZ zone,
793 # continue the resolution process, and get blocked by the content of the A record
794 # based on the first RPZ zone, whose priority is higher than the second one.
795 self
.checkBlocked('passthru-then-blocked-by-higher.example.')
797 def testRPZOrderingWhitelistedThenBlockedBySame(self
):
798 # we should first match on the qname from the first RPZ zone,
799 # continue the resolution process, and NOT get blocked by the content of the A record
800 # based on the same RPZ zone, since it's not higher.
801 self
.checkCustom('passthru-then-blocked-by-same.example.', 'A', dns
.rrset
.from_text('passthru-then-blocked-by-same.example.', 0, dns
.rdataclass
.IN
, 'A', '192.0.2.66'))
803 def testRPZOrderBlockedThenWhitelisted(self
):
804 # The qname is first blocked by the second RPZ zone
805 # Then, should the resolution process go on, the A record would be whitelisted
807 # This is what the RPZ specification requires, but we currently decided that we
808 # don't want to leak queries to malicious DNS servers and waste time if the qname is blacklisted.
809 # We might change our opinion at some point, though.
810 self
.checkBlocked('blocked-then-passhtru-by-higher.example.')
812 def testRPZOrderDelegate(self
):
813 # The IP of the NS we are going to contact is whitelisted (passthru) in zone 1,
814 # so even though the record (192.0.2.42) returned by the server is blacklisted
815 # by zone 2, it should not be blocked.
816 # We only test once because after that the answer is cached, so the NS is not contacted
817 # and the whitelist is not applied (yes, NSIP and NSDNAME are brittle).
818 self
.checkNotBlocked('nsip.delegated.example.', singleCheck
=True)
820 class RPZNSIPCustomTest(RPZRecursorTest
):
822 This test makes sure that the recursor handles custom RPZ rules in a NSIP
825 _confdir
= 'RPZNSIPCustom'
826 _lua_config_file
= """
827 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
828 rpzFile('configs/%s/zone2.rpz', { policyName="zone2.rpz."})
829 """ % (_confdir
, _confdir
)
830 _config_template
= """
831 auth-zones=example=configs/%s/example.zone
832 forward-zones=delegated.example=127.0.0.1:%d
833 """ % (_confdir
, rpzAuthServerPort
)
836 def generateRecursorConfig(cls
, confdir
):
837 authzonepath
= os
.path
.join(confdir
, 'example.zone')
838 with
open(authzonepath
, 'w') as authzone
:
839 authzone
.write("""$ORIGIN example.
841 """.format(soa
=cls
._SOA
))
843 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
844 with
open(rpzFilePath
, 'w') as rpzZone
:
845 rpzZone
.write("""$ORIGIN zone.rpz.
847 32.1.0.0.127.rpz-nsip.zone.rpz. 60 IN A 192.0.2.1
848 """.format(soa
=cls
._SOA
))
850 rpzFilePath
= os
.path
.join(confdir
, 'zone2.rpz')
851 with
open(rpzFilePath
, 'w') as rpzZone
:
852 rpzZone
.write("""$ORIGIN zone2.rpz.
854 32.1.2.0.192.rpz-ip 60 IN CNAME .
855 """.format(soa
=cls
._SOA
))
857 super(RPZNSIPCustomTest
, cls
).generateRecursorConfig(confdir
)
859 def testRPZDelegate(self
):
860 # The IP of the NS we are going to contact should result in a custom record (192.0.2.1) from zone 1,
861 # so even though the record (192.0.2.1) returned by the server is blacklisted
862 # by zone 2, it should not be blocked.
863 # We only test once because after that the answer is cached, so the NS is not contacted
864 # and the whitelist is not applied (yes, NSIP and NSDNAME are brittle).
865 self
.checkCustom('nsip.delegated.example.', 'A', dns
.rrset
.from_text('nsip.delegated.example.', 0, dns
.rdataclass
.IN
, 'A', '192.0.2.1'))
868 class RPZResponseIPCNameChainCustomTest(RPZRecursorTest
):
870 This test makes sure that the recursor applies response IP rules to records in a CNAME chain,
871 and resolves the target of a custom CNAME.
874 _confdir
= 'RPZResponseIPCNameChainCustom'
875 _lua_config_file
= """
876 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
878 _config_template
= """
879 auth-zones=example=configs/%s/example.zone
880 forward-zones=delegated.example=127.0.0.1:%d
881 """ % (_confdir
, rpzAuthServerPort
)
884 def generateRecursorConfig(cls
, confdir
):
885 authzonepath
= os
.path
.join(confdir
, 'example.zone')
886 with
open(authzonepath
, 'w') as authzone
:
887 authzone
.write("""$ORIGIN example.
890 cname IN A 192.0.2.255
891 custom-target IN A 192.0.2.254
892 """.format(soa
=cls
._SOA
))
894 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
895 with
open(rpzFilePath
, 'w') as rpzZone
:
896 rpzZone
.write("""$ORIGIN zone.rpz.
898 cname.example IN CNAME custom-target.example.
899 custom-target.example IN A 192.0.2.253
900 """.format(soa
=cls
._SOA
))
902 super(RPZResponseIPCNameChainCustomTest
, cls
).generateRecursorConfig(confdir
)
904 def testRPZChain(self
):
905 # we request the A record for 'name.example.', which is a CNAME to 'cname.example'
906 # this one does exist but we have a RPZ rule that should be triggered,
907 # replacing the 'real' CNAME by a CNAME to 'custom-target.example.'
908 # There is a RPZ rule for that name but it should not be triggered, since
909 # the RPZ specs state "Recall that only one policy rule, from among all those matched at all
910 # stages of resolving a CNAME or DNAME chain, can affect the final
911 # response; this is true even if the selected rule has a PASSTHRU
912 # action" in 5.1 "CNAME or DNAME Chain Position" Precedence Rule
914 # two times to check the cache
916 query
= dns
.message
.make_query('name.example.', 'A', want_dnssec
=True)
917 query
.flags |
= dns
.flags
.CD
918 for method
in ("sendUDPQuery", "sendTCPQuery"):
919 sender
= getattr(self
, method
)
921 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
922 self
.assertRRsetInAnswer(res
, dns
.rrset
.from_text('name.example.', 0, dns
.rdataclass
.IN
, 'CNAME', 'cname.example.'))
923 self
.assertRRsetInAnswer(res
, dns
.rrset
.from_text('cname.example.', 0, dns
.rdataclass
.IN
, 'CNAME', 'custom-target.example.'))
924 self
.assertRRsetInAnswer(res
, dns
.rrset
.from_text('custom-target.example.', 0, dns
.rdataclass
.IN
, 'A', '192.0.2.254'))
927 class RPZCNameChainCustomTest(RPZRecursorTest
):
929 This test makes sure that the recursor applies QName rules to names in a CNAME chain.
930 No forward or internal auth zones here, as we want to test the real resolution
931 (with QName Minimization).
934 _PREFIX
= os
.environ
['PREFIX']
935 _confdir
= 'RPZCNameChainCustom'
936 _lua_config_file
= """
937 rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
939 _config_template
= ""
945 cls
.startResponders()
947 confdir
= os
.path
.join('configs', cls
._confdir
)
948 cls
.createConfigDir(confdir
)
950 cls
.generateAllAuthConfig(confdir
)
951 cls
.startAuth(os
.path
.join(confdir
, "auth-8"), cls
._PREFIX
+ '.8')
952 cls
.startAuth(os
.path
.join(confdir
, "auth-10"), cls
._PREFIX
+ '.10')
954 cls
.generateRecursorConfig(confdir
)
955 cls
.startRecursor(confdir
, cls
._recursorPort
)
958 def tearDownClass(cls
):
960 cls
.tearDownRecursor()
963 def generateRecursorConfig(cls
, confdir
):
964 rpzFilePath
= os
.path
.join(confdir
, 'zone.rpz')
965 with
open(rpzFilePath
, 'w') as rpzZone
:
966 rpzZone
.write("""$ORIGIN zone.rpz.
968 32.100.2.0.192.rpz-ip IN CNAME .
969 32.101.2.0.192.rpz-ip IN CNAME *.
970 32.102.2.0.192.rpz-ip IN A 192.0.2.103
971 """.format(soa
=cls
._SOA
))
973 super(RPZCNameChainCustomTest
, cls
).generateRecursorConfig(confdir
)
975 def testRPZChainNXD(self
):
976 # we should match the A at the end of the CNAME chain and
979 # two times to check the cache
981 query
= dns
.message
.make_query('cname-nxd.example.', 'A', want_dnssec
=True)
982 query
.flags |
= dns
.flags
.CD
983 for method
in ("sendUDPQuery", "sendTCPQuery"):
984 sender
= getattr(self
, method
)
986 self
.assertRcodeEqual(res
, dns
.rcode
.NXDOMAIN
)
987 self
.assertEquals(len(res
.answer
), 0)
989 def testRPZChainNODATA(self
):
990 # we should match the A at the end of the CNAME chain and
993 # two times to check the cache
995 query
= dns
.message
.make_query('cname-nodata.example.', 'A', want_dnssec
=True)
996 query
.flags |
= dns
.flags
.CD
997 for method
in ("sendUDPQuery", "sendTCPQuery"):
998 sender
= getattr(self
, method
)
1000 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
1001 self
.assertEquals(len(res
.answer
), 0)
1003 def testRPZChainCustom(self
):
1004 # we should match the A at the end of the CNAME chain and
1005 # get a custom A, replacing the existing one
1007 # two times to check the cache
1009 query
= dns
.message
.make_query('cname-custom-a.example.', 'A', want_dnssec
=True)
1010 query
.flags |
= dns
.flags
.CD
1011 for method
in ("sendUDPQuery", "sendTCPQuery"):
1012 sender
= getattr(self
, method
)
1014 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
1015 # the original CNAME record is signed
1016 self
.assertEquals(len(res
.answer
), 3)
1017 self
.assertRRsetInAnswer(res
, dns
.rrset
.from_text('cname-custom-a.example.', 0, dns
.rdataclass
.IN
, 'CNAME', 'cname-custom-a-target.example.'))
1018 self
.assertRRsetInAnswer(res
, dns
.rrset
.from_text('cname-custom-a-target.example.', 0, dns
.rdataclass
.IN
, 'A', '192.0.2.103'))