]>
git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.recursor-dnssec/test_RoutingTag.py
7 import clientsubnetoption
9 from recursortests
import RecursorTest
10 from twisted
.internet
.protocol
import DatagramProtocol
11 from twisted
.internet
import reactor
13 emptyECSText
= 'No ECS received'
14 nameECS
= 'ecs-echo.example.'
15 nameECSInvalidScope
= 'invalid-scope.ecs-echo.example.'
17 routingReactorRunning
= False
19 class RoutingTagTest(RecursorTest
):
20 _config_template_default
= """
24 local-address=127.0.0.1
26 packetcache-servfail-ttl=15
36 def sendECSQuery(self
, query
, expected
, expectedFirstTTL
=None):
37 res
= self
.sendUDPQuery(query
)
39 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
40 self
.assertRRsetInAnswer(res
, expected
)
41 # this will break if you are not looking for the first RR, sorry!
42 if expectedFirstTTL
is not None:
43 self
.assertEqual(res
.answer
[0].ttl
, expectedFirstTTL
)
45 expectedFirstTTL
= res
.answer
[0].ttl
47 # wait one second, check that the TTL has been
48 # decreased indicating a cache hit
51 res
= self
.sendUDPQuery(query
)
53 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
54 self
.assertRRsetInAnswer(res
, expected
)
55 self
.assertLess(res
.answer
[0].ttl
, expectedFirstTTL
)
57 def checkECSQueryHit(self
, query
, expected
):
58 res
= self
.sendUDPQuery(query
)
60 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
61 self
.assertRRsetInAnswer(res
, expected
)
62 # this will break if you are not looking for the first RR, sorry!
63 self
.assertLess(res
.answer
[0].ttl
, ttlECS
)
65 def setRoutingTag(self
, tag
):
66 # This value is picked up by the gettag()
67 file = open('tagfile', 'w')
73 def startResponders(cls
):
74 global routingReactorRunning
75 print("Launching responders..")
77 address
= cls
._PREFIX
+ '.24'
80 if not routingReactorRunning
:
81 reactor
.listenUDP(port
, UDPRoutingResponder(), interface
=address
)
82 routingReactorRunning
= True
84 if not reactor
.running
:
85 cls
._UDPResponder
= threading
.Thread(name
='UDP Routing Responder', target
=reactor
.run
, args
=(False,))
86 cls
._UDPResponder
.setDaemon(True)
87 cls
._UDPResponder
.start()
95 confdir
= os
.path
.join('configs', cls
._confdir
)
96 cls
.createConfigDir(confdir
)
98 cls
.generateRecursorConfig(confdir
)
99 cls
.startRecursor(confdir
, cls
._recursorPort
)
101 print("Launching tests..")
104 def tearDownClass(cls
):
105 cls
.tearDownRecursor()
108 class testRoutingTag(RoutingTagTest
):
109 _confdir
= 'RoutingTag'
111 _config_template
= """
112 use-incoming-edns-subnet=yes
113 edns-subnet-allow-list=ecs-echo.example.
114 forward-zones=ecs-echo.example=%s.24
115 """ % (os
.environ
['PREFIX'])
116 _lua_dns_script_file
= """
118 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp, proxyProtocolValues)
120 for line in io.lines('tagfile') do
124 return 0, nil, nil, nil, nil, nil, rtag
128 def testSendECS(self
):
129 # First send an ECS query with routingTag
130 self
.setRoutingTag('foo')
131 expected1
= dns
.rrset
.from_text(nameECS
, ttlECS
, dns
.rdataclass
.IN
, 'TXT', '192.0.2.0/24')
132 ecso
= clientsubnetoption
.ClientSubnetOption('192.0.2.1', 32)
133 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN', use_edns
=True, options
=[ecso
], payload
=512)
134 self
.sendECSQuery(query
, expected1
)
136 # Now check a cache hit with the same routingTag (but no ECS)
137 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
138 self
.checkECSQueryHit(query
, expected1
)
140 expected2
= dns
.rrset
.from_text(nameECS
, ttlECS
, dns
.rdataclass
.IN
, 'TXT', '127.0.0.0/24')
141 # And see if a different tag does *not* hit the first one
142 self
.setRoutingTag('bar')
143 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
144 self
.sendECSQuery(query
, expected2
)
146 # And see if a *no* tag does *not* hit the first one
147 expected3
= dns
.rrset
.from_text(nameECS
, ttlECS
, dns
.rdataclass
.IN
, 'TXT', '192.0.3.0/24')
148 self
.setRoutingTag(None)
149 ecso
= clientsubnetoption
.ClientSubnetOption('192.0.3.1', 32)
150 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN', use_edns
=True, options
=[ecso
], payload
=512)
151 self
.sendECSQuery(query
, expected3
)
153 # And see if an unknown tag from the same subnet does hit the last
154 self
.setRoutingTag('baz')
155 ecso
= clientsubnetoption
.ClientSubnetOption('192.0.3.2', 32)
156 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN', use_edns
=True, options
=[ecso
], payload
=512)
157 self
.checkECSQueryHit(query
, expected3
)
159 # And a no tag and no subnet query does hit the general case
160 self
.setRoutingTag(None)
161 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
162 self
.sendECSQuery(query
, expected2
)
164 # And a unknown tag and no subnet query does hit the general case
165 self
.setRoutingTag('bag')
166 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
167 self
.sendECSQuery(query
, expected2
)
169 return # remove this line to peek at cache
170 rec_controlCmd
= [os
.environ
['RECCONTROL'],
171 '--config-dir=%s' % 'configs/' + self
._confdir
,
174 expected
= b
'dumped 7 records\n'
175 ret
= subprocess
.check_output(rec_controlCmd
, stderr
=subprocess
.STDOUT
)
176 self
.assertEqual(ret
, expected
)
178 except subprocess
.CalledProcessError
as e
:
182 class testRoutingTagFFI(RoutingTagTest
):
183 _confdir
= 'RoutingTagFFI'
185 _config_template
= """
186 use-incoming-edns-subnet=yes
187 edns-subnet-allow-list=ecs-echo.example.
188 forward-zones=ecs-echo.example=%s.24
189 """ % (os
.environ
['PREFIX'])
190 _lua_dns_script_file
= """
192 local ffi = require("ffi")
194 typedef struct pdns_ffi_param pdns_ffi_param_t;
196 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref);
197 void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* rtag);
200 function gettag_ffi(obj)
201 for line in io.lines('tagfile') do
202 local rtag = ffi.string(line)
203 ffi.C.pdns_ffi_param_set_routingtag(obj, rtag)
209 def testSendECS(self
):
210 # First send an ECS query with routingTag
211 self
.setRoutingTag('foo')
212 expected1
= dns
.rrset
.from_text(nameECS
, ttlECS
, dns
.rdataclass
.IN
, 'TXT', '192.0.2.0/24')
213 ecso
= clientsubnetoption
.ClientSubnetOption('192.0.2.1', 32)
214 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN', use_edns
=True, options
=[ecso
], payload
=512)
215 self
.sendECSQuery(query
, expected1
)
217 # Now check a cache hit with the same routingTag (but no ECS)
218 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
219 self
.checkECSQueryHit(query
, expected1
)
221 expected2
= dns
.rrset
.from_text(nameECS
, ttlECS
, dns
.rdataclass
.IN
, 'TXT', '127.0.0.0/24')
222 # And see if a different tag does *not* hit the first one
223 self
.setRoutingTag('bar')
224 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
225 self
.sendECSQuery(query
, expected2
)
227 # And see if a *no* tag does *not* hit the first one
228 expected3
= dns
.rrset
.from_text(nameECS
, ttlECS
, dns
.rdataclass
.IN
, 'TXT', '192.0.3.0/24')
229 self
.setRoutingTag(None)
230 ecso
= clientsubnetoption
.ClientSubnetOption('192.0.3.1', 32)
231 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN', use_edns
=True, options
=[ecso
], payload
=512)
232 self
.sendECSQuery(query
, expected3
)
234 # And see if an unknown tag from the same subnet does hit the last
235 self
.setRoutingTag('baz')
236 ecso
= clientsubnetoption
.ClientSubnetOption('192.0.3.2', 32)
237 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN', use_edns
=True, options
=[ecso
], payload
=512)
238 self
.checkECSQueryHit(query
, expected3
)
240 # And a no tag and no subnet query does hit the general case
241 self
.setRoutingTag(None)
242 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
243 self
.sendECSQuery(query
, expected2
)
245 # And a unknown tag and no subnet query does hit the general case
246 self
.setRoutingTag('bag')
247 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
248 self
.sendECSQuery(query
, expected2
)
250 return #remove this line to peek at cache
251 rec_controlCmd
= [os
.environ
['RECCONTROL'],
252 '--config-dir=%s' % 'configs/' + self
._confdir
,
255 expected
= 'dumped 6 records\n'
256 ret
= subprocess
.check_output(rec_controlCmd
, stderr
=subprocess
.STDOUT
)
257 self
.assertEqual(ret
, expected
)
259 except subprocess
.CalledProcessError
as e
:
263 class UDPRoutingResponder(DatagramProtocol
):
266 if option
.family
== clientsubnetoption
.FAMILY_IPV4
:
267 ip
= socket
.inet_ntop(socket
.AF_INET
, struct
.pack('!L', option
.ip
))
268 elif option
.family
== clientsubnetoption
.FAMILY_IPV6
:
269 ip
= socket
.inet_ntop(socket
.AF_INET6
,
272 option
.ip
& (2 ** 64 - 1)))
275 def datagramReceived(self
, datagram
, address
):
276 request
= dns
.message
.from_wire(datagram
)
278 response
= dns
.message
.make_response(request
)
279 response
.flags |
= dns
.flags
.AA
282 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
:
285 for option
in request
.options
:
286 if option
.otype
== clientsubnetoption
.ASSIGNED_OPTION_CODE
and isinstance(option
, clientsubnetoption
.ClientSubnetOption
):
287 text
= self
.ipToStr(option
) + '/' + str(option
.mask
)
289 # Send a scope more specific than the received source for nameECSInvalidScope
290 if request
.question
[0].name
== dns
.name
.from_text(nameECSInvalidScope
):
291 ecso
= clientsubnetoption
.ClientSubnetOption("192.0.42.42", 32, 32)
293 ecso
= clientsubnetoption
.ClientSubnetOption(self
.ipToStr(option
), option
.mask
, option
.mask
)
295 answer
= dns
.rrset
.from_text(request
.question
[0].name
, ttlECS
, dns
.rdataclass
.IN
, 'TXT', text
)
296 response
.answer
.append(answer
)
298 elif request
.question
[0].name
== dns
.name
.from_text(nameECS
) and request
.question
[0].rdtype
== dns
.rdatatype
.NS
:
299 answer
= dns
.rrset
.from_text(nameECS
, ttlECS
, dns
.rdataclass
.IN
, 'NS', 'ns1.ecs-echo.example.')
300 response
.answer
.append(answer
)
301 additional
= dns
.rrset
.from_text('ns1.ecs-echo.example.', 15, dns
.rdataclass
.IN
, 'A', os
.environ
['PREFIX'] + '.24')
302 response
.additional
.append(additional
)
305 response
.use_edns(options
= [ecso
])
307 self
.transport
.write(response
.to_wire(), address
)