]> git.ipfire.org Git - thirdparty/pdns.git/blame - regression-tests.recursor-dnssec/test_ECS.py
rec: dnspython's API changed wrt NSID, apply (version dependent) fix in regression...
[thirdparty/pdns.git] / regression-tests.recursor-dnssec / test_ECS.py
CommitLineData
9a0b88e8
RG
1import dns
2import os
3import socket
4import struct
5import threading
a5849a16 6import time
9a0b88e8 7import clientsubnetoption
fb027663
PL
8import unittest
9from recursortests import RecursorTest, have_ipv6
9a0b88e8
RG
10from twisted.internet.protocol import DatagramProtocol
11from twisted.internet import reactor
12
13emptyECSText = 'No ECS received'
14nameECS = 'ecs-echo.example.'
635a6765 15nameECSInvalidScope = 'invalid-scope.ecs-echo.example.'
a5849a16 16ttlECS = 60
8a3a3822 17ecsReactorRunning = False
8064abbb 18ecsReactorv6Running = False
9a0b88e8
RG
19
20class ECSTest(RecursorTest):
a5849a16
RG
21 _config_template_default = """
22daemon=no
23trace=yes
24dont-query=
25local-address=127.0.0.1
d2c1660a
OM
26packetcache-ttl=15
27packetcache-servfail-ttl=15
a5849a16 28max-cache-ttl=600
d2c1660a 29threads=2
a5849a16
RG
30loglevel=9
31disable-syslog=yes
9425e4ca
O
32log-common-errors=yes
33statistics-interval=0
34ecs-add-for=0.0.0.0/0
a5849a16
RG
35"""
36
c86e1192 37 def sendECSQuery(self, query, expected, expectedFirstTTL=None, scopeZeroResponse=None):
a5849a16
RG
38 res = self.sendUDPQuery(query)
39
40 self.assertRcodeEqual(res, dns.rcode.NOERROR)
41 self.assertRRsetInAnswer(res, expected)
42 # this will break if you are not looking for the first RR, sorry!
43 if expectedFirstTTL is not None:
40795092 44 self.assertTrue(res.answer[0].ttl == expectedFirstTTL or res.answer[0].ttl == expectedFirstTTL - 1)
a5849a16
RG
45 else:
46 expectedFirstTTL = res.answer[0].ttl
4bfebc93 47 self.assertEqual(res.edns, query.edns)
c86e1192
RG
48
49 if scopeZeroResponse is not None:
50 self.assertEqual(res.edns, 0)
51 if scopeZeroResponse:
52 self.assertEqual(len(res.options), 1)
53 self.assertEqual(res.options[0].otype, 8)
54 self.assertEqual(res.options[0].scope, 0)
55 else:
56 self.assertEqual(len(res.options), 1)
57 self.assertEqual(res.options[0].otype, 8)
58 self.assertNotEqual(res.options[0].scope, 0)
a5849a16
RG
59
60 # wait one second, check that the TTL has been
61 # decreased indicating a cache hit
62 time.sleep(1)
63
64 res = self.sendUDPQuery(query)
65
66 self.assertRcodeEqual(res, dns.rcode.NOERROR)
67 self.assertRRsetInAnswer(res, expected)
68 self.assertLess(res.answer[0].ttl, expectedFirstTTL)
4bfebc93 69 self.assertEqual(res.edns, query.edns)
a5849a16
RG
70
71 def checkECSQueryHit(self, query, expected):
72 res = self.sendUDPQuery(query)
73
74 self.assertRcodeEqual(res, dns.rcode.NOERROR)
75 self.assertRRsetInAnswer(res, expected)
76 # this will break if you are not looking for the first RR, sorry!
77 self.assertLess(res.answer[0].ttl, ttlECS)
9a0b88e8
RG
78
79 @classmethod
80 def startResponders(cls):
8a3a3822 81 global ecsReactorRunning
8064abbb 82 global ecsReactorv6Running
9a0b88e8
RG
83 print("Launching responders..")
84
85 address = cls._PREFIX + '.21'
86 port = 53
87
8a3a3822 88 if not ecsReactorRunning:
9a0b88e8 89 reactor.listenUDP(port, UDPECSResponder(), interface=address)
8a3a3822 90 ecsReactorRunning = True
9a0b88e8 91
fb027663 92 if not ecsReactorv6Running and have_ipv6():
8064abbb
PL
93 reactor.listenUDP(53000, UDPECSResponder(), interface='::1')
94 ecsReactorv6Running = True
95
8a3a3822 96 if not reactor.running:
fbfaa4a7 97 cls._UDPResponder = threading.Thread(name='UDP Responder', target=reactor.run, args=(False,))
9a0b88e8
RG
98 cls._UDPResponder.setDaemon(True)
99 cls._UDPResponder.start()
100
9a0b88e8
RG
101 @classmethod
102 def setUpClass(cls):
103 cls.setUpSockets()
104
105 cls.startResponders()
106
107 confdir = os.path.join('configs', cls._confdir)
108 cls.createConfigDir(confdir)
109
110 cls.generateRecursorConfig(confdir)
111 cls.startRecursor(confdir, cls._recursorPort)
112
113 print("Launching tests..")
114
115 @classmethod
116 def tearDownClass(cls):
117 cls.tearDownRecursor()
118
119class testNoECS(ECSTest):
120 _confdir = 'NoECS'
121
3be6dde8 122 _config_template = """edns-subnet-allow-list=
9a0b88e8
RG
123forward-zones=ecs-echo.example=%s.21
124 """ % (os.environ['PREFIX'])
125
126 def testSendECS(self):
a5849a16 127 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
9a0b88e8
RG
128 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
129 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 130 self.sendECSQuery(query, expected)
9a0b88e8
RG
131
132 def testNoECS(self):
a5849a16 133 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
9a0b88e8 134 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 135 self.sendECSQuery(query, expected)
9a0b88e8 136
8a3a3822
RG
137 def testRequireNoECS(self):
138 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
139
140 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
141 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
142 self.sendECSQuery(query, expected)
143
9a0b88e8
RG
144class testIncomingNoECS(ECSTest):
145 _confdir = 'IncomingNoECS'
146
3be6dde8 147 _config_template = """edns-subnet-allow-list=
9a0b88e8
RG
148use-incoming-edns-subnet=yes
149forward-zones=ecs-echo.example=%s.21
150 """ % (os.environ['PREFIX'])
151
152 def testSendECS(self):
a5849a16 153 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
9a0b88e8
RG
154
155 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
156 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
c86e1192 157 self.sendECSQuery(query, expected, scopeZeroResponse=True)
9a0b88e8
RG
158
159 def testNoECS(self):
a5849a16 160 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
9a0b88e8
RG
161
162 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 163 self.sendECSQuery(query, expected)
9a0b88e8 164
8a3a3822
RG
165 def testRequireNoECS(self):
166 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
167
168 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
169 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
c86e1192 170 self.sendECSQuery(query, expected, scopeZeroResponse=True)
8a3a3822 171
9a0b88e8
RG
172class testECSByName(ECSTest):
173 _confdir = 'ECSByName'
174
3be6dde8 175 _config_template = """edns-subnet-allow-list=ecs-echo.example.
9a0b88e8
RG
176forward-zones=ecs-echo.example=%s.21
177 """ % (os.environ['PREFIX'])
178
179 def testSendECS(self):
a5849a16 180 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
9a0b88e8
RG
181 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
182 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 183 self.sendECSQuery(query, expected)
9a0b88e8 184
a5849a16
RG
185 # check that a query in a different ECS range is a hit, because we don't use the incoming ECS
186 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32)
187 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
188 self.checkECSQueryHit(query, expected)
9a0b88e8
RG
189
190 def testNoECS(self):
a5849a16 191 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
9a0b88e8 192 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 193 self.sendECSQuery(query, expected)
9a0b88e8 194
8a3a3822
RG
195 def testRequireNoECS(self):
196 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
197
198 # the request for no ECS is ignored because use-incoming-edns-subnet is not set
199 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
200 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
201 self.sendECSQuery(query, expected)
202
9a0b88e8
RG
203class testECSByNameLarger(ECSTest):
204 _confdir = 'ECSByNameLarger'
205
3be6dde8 206 _config_template = """edns-subnet-allow-list=ecs-echo.example.
9a0b88e8
RG
207ecs-ipv4-bits=32
208forward-zones=ecs-echo.example=%s.21
30974ecc
RG
209ecs-ipv4-cache-bits=32
210ecs-ipv6-cache-bits=128
9a0b88e8
RG
211 """ % (os.environ['PREFIX'])
212
213 def testSendECS(self):
a5849a16 214 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.1/32')
9a0b88e8
RG
215 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
216 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 217 self.sendECSQuery(query, expected)
9a0b88e8 218
a5849a16
RG
219 # check that a query in a different range is a miss
220 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32)
221 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
222 self.sendECSQuery(query, expected)
9a0b88e8
RG
223
224 def testNoECS(self):
a5849a16 225 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.1/32')
9a0b88e8 226 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 227 self.sendECSQuery(query, expected)
9a0b88e8 228
8a3a3822
RG
229 def testRequireNoECS(self):
230 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.1/32')
231
232 # the request for no ECS is ignored because use-incoming-edns-subnet is not set
233 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
234 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
235 self.sendECSQuery(query, expected)
236
9a0b88e8
RG
237class testECSByNameSmaller(ECSTest):
238 _confdir = 'ECSByNameLarger'
239
3be6dde8 240 _config_template = """edns-subnet-allow-list=ecs-echo.example.
9a0b88e8
RG
241ecs-ipv4-bits=16
242forward-zones=ecs-echo.example=%s.21
243 """ % (os.environ['PREFIX'])
244
245 def testSendECS(self):
a5849a16 246 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/16')
9a0b88e8
RG
247 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
248 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 249 self.sendECSQuery(query, expected)
9a0b88e8
RG
250
251 def testNoECS(self):
a5849a16 252 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/16')
9a0b88e8 253 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 254 self.sendECSQuery(query, expected)
9a0b88e8 255
8a3a3822
RG
256 def testRequireNoECS(self):
257 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/16')
258
259 # the request for no ECS is ignored because use-incoming-edns-subnet is not set
260 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
261 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
262 self.sendECSQuery(query, expected)
263
9a0b88e8
RG
264class testIncomingECSByName(ECSTest):
265 _confdir = 'ECSIncomingByName'
266
3be6dde8 267 _config_template = """edns-subnet-allow-list=ecs-echo.example.
9a0b88e8
RG
268use-incoming-edns-subnet=yes
269forward-zones=ecs-echo.example=%s.21
8a3a3822 270ecs-scope-zero-address=2001:db8::42
30974ecc
RG
271ecs-ipv4-cache-bits=32
272ecs-ipv6-cache-bits=128
9a0b88e8
RG
273 """ % (os.environ['PREFIX'])
274
275 def testSendECS(self):
a5849a16 276 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.0.2.0/24')
9a0b88e8
RG
277 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
278 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 279 self.sendECSQuery(query, expected, ttlECS)
9a0b88e8 280
a5849a16
RG
281 # check that a query in the same ECS range is a hit
282 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32)
283 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
284 self.checkECSQueryHit(query, expected)
9a0b88e8 285
a5849a16
RG
286 # check that a query in a different ECS range is a miss
287 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.1.2.0/24')
288 ecso = clientsubnetoption.ClientSubnetOption('192.1.2.2', 32)
289 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
290 self.sendECSQuery(query, expected)
9a0b88e8 291
a5849a16
RG
292 def testNoECS(self):
293 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
9a0b88e8 294 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 295 self.sendECSQuery(query, expected, ttlECS)
9a0b88e8 296
8a3a3822
RG
297 def testRequireNoECS(self):
298 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', "2001:db8::42/128")
299
300 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
301 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
302 self.sendECSQuery(query, expected, ttlECS)
303
9a0b88e8
RG
304class testIncomingECSByNameLarger(ECSTest):
305 _confdir = 'ECSIncomingByNameLarger'
306
3be6dde8 307 _config_template = """edns-subnet-allow-list=ecs-echo.example.
9a0b88e8
RG
308use-incoming-edns-subnet=yes
309ecs-ipv4-bits=32
310forward-zones=ecs-echo.example=%s.21
8a3a3822 311ecs-scope-zero-address=192.168.0.1
30974ecc
RG
312ecs-ipv4-cache-bits=32
313ecs-ipv6-cache-bits=128
9a0b88e8
RG
314 """ % (os.environ['PREFIX'])
315
316 def testSendECS(self):
a5849a16 317 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.0.2.1/32')
9a0b88e8
RG
318
319 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
320 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 321 self.sendECSQuery(query, expected, ttlECS)
9a0b88e8
RG
322
323 def testNoECS(self):
a5849a16 324 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.1/32')
9a0b88e8
RG
325
326 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 327 self.sendECSQuery(query, expected, ttlECS)
9a0b88e8 328
8a3a3822
RG
329 def testRequireNoECS(self):
330 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.168.0.1/32')
331
332 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
333 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
334 self.sendECSQuery(query, expected, ttlECS)
335
9a0b88e8
RG
336class testIncomingECSByNameSmaller(ECSTest):
337 _confdir = 'ECSIncomingByNameSmaller'
338
3be6dde8 339 _config_template = """edns-subnet-allow-list=ecs-echo.example.
9a0b88e8
RG
340use-incoming-edns-subnet=yes
341ecs-ipv4-bits=16
342forward-zones=ecs-echo.example=%s.21
8a3a3822 343ecs-scope-zero-address=192.168.0.1
30974ecc
RG
344ecs-ipv4-cache-bits=32
345ecs-ipv6-cache-bits=128
9a0b88e8
RG
346 """ % (os.environ['PREFIX'])
347
348 def testSendECS(self):
a5849a16 349 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.0.0.0/16')
9a0b88e8
RG
350 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
351 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 352 self.sendECSQuery(query, expected)
9a0b88e8
RG
353
354 def testNoECS(self):
a5849a16 355 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/16')
9a0b88e8 356 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 357 self.sendECSQuery(query, expected)
9a0b88e8 358
8a3a3822
RG
359 def testRequireNoECS(self):
360 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.168.0.1/32')
361
362 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
363 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
364 self.sendECSQuery(query, expected, ttlECS)
365
fb027663 366@unittest.skipIf(not have_ipv6(), "No IPv6")
9a0b88e8
RG
367class testIncomingECSByNameV6(ECSTest):
368 _confdir = 'ECSIncomingByNameV6'
369
3be6dde8 370 _config_template = """edns-subnet-allow-list=ecs-echo.example.
9a0b88e8
RG
371use-incoming-edns-subnet=yes
372ecs-ipv6-bits=128
30974ecc
RG
373ecs-ipv4-cache-bits=32
374ecs-ipv6-cache-bits=128
8064abbb
PL
375query-local-address=::1
376forward-zones=ecs-echo.example=[::1]:53000
377 """
9a0b88e8
RG
378
379 def testSendECS(self):
a5849a16 380 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '2001:db8::1/128')
9a0b88e8
RG
381 ecso = clientsubnetoption.ClientSubnetOption('2001:db8::1', 128)
382 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 383 self.sendECSQuery(query, expected, ttlECS)
9a0b88e8
RG
384
385 def testNoECS(self):
a5849a16 386 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
9a0b88e8
RG
387
388 query = dns.message.make_query(nameECS, 'TXT')
389 res = self.sendUDPQuery(query)
a5849a16 390 self.sendECSQuery(query, expected, ttlECS)
9a0b88e8 391
8a3a3822 392 def testRequireNoECS(self):
ea02eeba 393 # we should get ::1/128 because ecs-scope-zero-addr is unset and query-local-address is set to ::1
8a3a3822
RG
394 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', "::1/128")
395
396 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
397 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
398 self.sendECSQuery(query, expected, ttlECS)
399
9a0b88e8
RG
400class testECSNameMismatch(ECSTest):
401 _confdir = 'ECSNameMismatch'
402
3be6dde8 403 _config_template = """edns-subnet-allow-list=not-the-right-name.example.
9a0b88e8
RG
404forward-zones=ecs-echo.example=%s.21
405 """ % (os.environ['PREFIX'])
406
407 def testSendECS(self):
a5849a16 408 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
9a0b88e8
RG
409 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
410 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 411 self.sendECSQuery(query, expected)
9a0b88e8
RG
412
413 def testNoECS(self):
a5849a16 414 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
9a0b88e8 415 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 416 self.sendECSQuery(query, expected)
9a0b88e8 417
8a3a3822
RG
418 def testRequireNoECS(self):
419 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
420
421 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
422 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
423 self.sendECSQuery(query, expected)
424
9a0b88e8
RG
425class testECSByIP(ECSTest):
426 _confdir = 'ECSByIP'
427
3be6dde8 428 _config_template = """edns-subnet-allow-list=%s.21
9a0b88e8
RG
429forward-zones=ecs-echo.example=%s.21
430 """ % (os.environ['PREFIX'], os.environ['PREFIX'])
431
432 def testSendECS(self):
a5849a16 433 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
9a0b88e8
RG
434 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
435 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 436 self.sendECSQuery(query, expected)
9a0b88e8
RG
437
438 def testNoECS(self):
a5849a16 439 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
9a0b88e8 440 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 441 self.sendECSQuery(query, expected)
9a0b88e8 442
8a3a3822
RG
443 def testRequireNoECS(self):
444 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
445
446 # the request for no ECS is ignored because use-incoming-edns-subnet is not set
447 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
448 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
449 self.sendECSQuery(query, expected)
450
9a0b88e8
RG
451class testIncomingECSByIP(ECSTest):
452 _confdir = 'ECSIncomingByIP'
453
3be6dde8 454 _config_template = """edns-subnet-allow-list=%s.21
9a0b88e8
RG
455use-incoming-edns-subnet=yes
456forward-zones=ecs-echo.example=%s.21
8a3a3822 457ecs-scope-zero-address=::1
30974ecc
RG
458ecs-ipv4-cache-bits=32
459ecs-ipv6-cache-bits=128
9a0b88e8
RG
460 """ % (os.environ['PREFIX'], os.environ['PREFIX'])
461
462 def testSendECS(self):
a5849a16 463 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '192.0.2.0/24')
9a0b88e8
RG
464
465 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
466 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 467 self.sendECSQuery(query, expected)
9a0b88e8
RG
468
469 def testNoECS(self):
a5849a16 470 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '127.0.0.0/24')
9a0b88e8 471 query = dns.message.make_query(nameECS, 'TXT')
a5849a16 472 self.sendECSQuery(query, expected)
9a0b88e8 473
8a3a3822
RG
474 def testRequireNoECS(self):
475 # we will get ::1 because ecs-scope-zero-addr is set to ::1
476 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', '::1/128')
477
478 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
479 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
480 self.sendECSQuery(query, expected, ttlECS)
481
635a6765
RG
482 def testSendECSInvalidScope(self):
483 # test that the recursor does not cache with a more specific scope than the source it sent
484 expected = dns.rrset.from_text(nameECSInvalidScope, ttlECS, dns.rdataclass.IN, 'TXT', '192.0.2.0/24')
485
486 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
487 query = dns.message.make_query(nameECSInvalidScope, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
488
489 self.sendECSQuery(query, expected)
490
9a0b88e8
RG
491class testECSIPMismatch(ECSTest):
492 _confdir = 'ECSIPMismatch'
493
3be6dde8 494 _config_template = """edns-subnet-allow-list=192.0.2.1
9a0b88e8
RG
495forward-zones=ecs-echo.example=%s.21
496 """ % (os.environ['PREFIX'])
497
498 def testSendECS(self):
a5849a16 499 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
9a0b88e8
RG
500 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
501 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
a5849a16 502 self.sendECSQuery(query, expected)
9a0b88e8
RG
503
504 def testNoECS(self):
a5849a16 505 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
9a0b88e8
RG
506 query = dns.message.make_query(nameECS, 'TXT')
507 res = self.sendUDPQuery(query)
a5849a16 508 self.sendECSQuery(query, expected)
9a0b88e8 509
8a3a3822
RG
510 def testRequireNoECS(self):
511 expected = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'TXT', emptyECSText)
512
513 ecso = clientsubnetoption.ClientSubnetOption('0.0.0.0', 0)
514 query = dns.message.make_query(nameECS, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
515 self.sendECSQuery(query, expected)
516
9166ee1b
RG
517class testECSWithProxyProtocoldRecursorTest(ECSTest):
518 _confdir = 'ECSWithProxyProtocol'
519 _config_template = """
520 ecs-add-for=2001:db8::1/128
521 edns-subnet-allow-list=ecs-echo.example.
522 forward-zones=ecs-echo.example=%s.21
523 proxy-protocol-from=127.0.0.1/32
524 allow-from=2001:db8::1/128
525""" % (os.environ['PREFIX'])
526
527 def testProxyProtocolPlusECS(self):
528 qname = nameECS
529 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'TXT', '2001:db8::/56')
530
531 query = dns.message.make_query(qname, 'TXT', use_edns=True)
532 for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
533 sender = getattr(self, method)
534 res = sender(query, True, '2001:db8::1', '2001:db8::2', 0, 65535)
535 self.assertRcodeEqual(res, dns.rcode.NOERROR)
536 self.assertRRsetInAnswer(res, expected)
537
c86e1192
RG
538class testTooLargeToAddZeroScope(RecursorTest):
539
540 _confdir = 'TooLargeToAddZeroScope'
9425e4ca 541 _config_template = """
c86e1192
RG
542use-incoming-edns-subnet=yes
543dnssec=validate
c86e1192 544"""
c86e1192
RG
545 _lua_dns_script_file = """
546 function preresolve(dq)
547 if dq.qname == newDN('toolarge.ecs.') then
548 dq:addRecord(pdns.TXT, '%s', pdns.place.ANSWER)
549 return true
550 end
551 return false
552 end
553 """ % ('A'*447)
554
555 _roothints = None
556
557 @classmethod
558 def setUpClass(cls):
559
560 # we don't need all the auth stuff
561 cls.setUpSockets()
562 cls.startResponders()
563
564 confdir = os.path.join('configs', cls._confdir)
565 cls.createConfigDir(confdir)
566
567 cls.generateRecursorConfig(confdir)
568 cls.startRecursor(confdir, cls._recursorPort)
569
570 @classmethod
571 def tearDownClass(cls):
572 cls.tearDownRecursor()
573
574 @classmethod
575 def generateRecursorConfig(cls, confdir):
576 super(testTooLargeToAddZeroScope, cls).generateRecursorConfig(confdir)
577
578 def testTooLarge(self):
579 qname = 'toolarge.ecs.'
580 ecso = clientsubnetoption.ClientSubnetOption('192.0.2.1', 24)
581 query = dns.message.make_query(qname, 'TXT', 'IN', use_edns=True, options=[ecso], payload=512)
582
583 # should not have an ECS Option since the packet is too large already
584 res = self.sendUDPQuery(query, timeout=5.0)
585 self.assertRcodeEqual(res, dns.rcode.NOERROR)
4bfebc93 586 self.assertEqual(len(res.answer), 1)
c86e1192
RG
587 self.assertEqual(res.edns, 0)
588 self.assertEqual(len(res.options), 0)
589
590 res = self.sendTCPQuery(query, timeout=5.0)
591 self.assertRcodeEqual(res, dns.rcode.NOERROR)
4bfebc93 592 self.assertEqual(len(res.answer), 1)
c86e1192
RG
593 self.assertEqual(res.edns, 0)
594 self.assertEqual(len(res.options), 1)
595 self.assertEqual(res.options[0].otype, 8)
596 self.assertEqual(res.options[0].scope, 0)
597
9a0b88e8
RG
598class UDPECSResponder(DatagramProtocol):
599 @staticmethod
600 def ipToStr(option):
601 if option.family == clientsubnetoption.FAMILY_IPV4:
602 ip = socket.inet_ntop(socket.AF_INET, struct.pack('!L', option.ip))
603 elif option.family == clientsubnetoption.FAMILY_IPV6:
604 ip = socket.inet_ntop(socket.AF_INET6,
605 struct.pack('!QQ',
606 option.ip >> 64,
607 option.ip & (2 ** 64 - 1)))
608 return ip
609
610 def datagramReceived(self, datagram, address):
611 request = dns.message.from_wire(datagram)
612
613 response = dns.message.make_response(request)
a5849a16
RG
614 response.flags |= dns.flags.AA
615 ecso = None
9a0b88e8 616
635a6765
RG
617 if (request.question[0].name == dns.name.from_text(nameECS) or request.question[0].name == dns.name.from_text(nameECSInvalidScope)) and request.question[0].rdtype == dns.rdatatype.TXT:
618
9a0b88e8
RG
619 text = emptyECSText
620 for option in request.options:
621 if option.otype == clientsubnetoption.ASSIGNED_OPTION_CODE and isinstance(option, clientsubnetoption.ClientSubnetOption):
622 text = self.ipToStr(option) + '/' + str(option.mask)
623
635a6765
RG
624 # Send a scope more specific than the received source for nameECSInvalidScope
625 if request.question[0].name == dns.name.from_text(nameECSInvalidScope):
626 ecso = clientsubnetoption.ClientSubnetOption("192.0.42.42", 32, 32)
627 else:
628 ecso = clientsubnetoption.ClientSubnetOption(self.ipToStr(option), option.mask, option.mask)
629
630 answer = dns.rrset.from_text(request.question[0].name, ttlECS, dns.rdataclass.IN, 'TXT', text)
9a0b88e8 631 response.answer.append(answer)
635a6765 632
9a0b88e8 633 elif request.question[0].name == dns.name.from_text(nameECS) and request.question[0].rdtype == dns.rdatatype.NS:
a5849a16 634 answer = dns.rrset.from_text(nameECS, ttlECS, dns.rdataclass.IN, 'NS', 'ns1.ecs-echo.example.')
9a0b88e8 635 response.answer.append(answer)
efd26793 636 additional = dns.rrset.from_text('ns1.ecs-echo.example.', 15, dns.rdataclass.IN, 'A', os.environ['PREFIX'] + '.21')
9a0b88e8
RG
637 response.additional.append(additional)
638
a5849a16 639 if ecso:
3d144e24 640 response.use_edns(options = [ecso])
a5849a16 641
9a0b88e8 642 self.transport.write(response.to_wire(), address)