]>
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
= """
25 local-address=127.0.0.1
27 packetcache-servfail-ttl=0
34 def sendECSQuery(self
, query
, expected
, expectedFirstTTL
=None):
35 res
= self
.sendUDPQuery(query
)
37 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
38 self
.assertRRsetInAnswer(res
, expected
)
39 # this will break if you are not looking for the first RR, sorry!
40 if expectedFirstTTL
is not None:
41 self
.assertEqual(res
.answer
[0].ttl
, expectedFirstTTL
)
43 expectedFirstTTL
= res
.answer
[0].ttl
45 # wait one second, check that the TTL has been
46 # decreased indicating a cache hit
49 res
= self
.sendUDPQuery(query
)
51 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
52 self
.assertRRsetInAnswer(res
, expected
)
53 self
.assertLess(res
.answer
[0].ttl
, expectedFirstTTL
)
55 def checkECSQueryHit(self
, query
, expected
):
56 res
= self
.sendUDPQuery(query
)
58 self
.assertRcodeEqual(res
, dns
.rcode
.NOERROR
)
59 self
.assertRRsetInAnswer(res
, expected
)
60 # this will break if you are not looking for the first RR, sorry!
61 self
.assertLess(res
.answer
[0].ttl
, ttlECS
)
63 def setRoutingTag(self
, tag
):
64 # This value is picked up by the gettag()
65 file = open('tagfile', 'w')
71 def startResponders(cls
):
72 global routingReactorRunning
73 print("Launching responders..")
75 address
= cls
._PREFIX
+ '.24'
78 if not routingReactorRunning
:
79 reactor
.listenUDP(port
, UDPRoutingResponder(), interface
=address
)
80 routingReactorRunning
= True
82 if not reactor
.running
:
83 cls
._UDPResponder
= threading
.Thread(name
='UDP Routing Responder', target
=reactor
.run
, args
=(False,))
84 cls
._UDPResponder
.setDaemon(True)
85 cls
._UDPResponder
.start()
93 confdir
= os
.path
.join('configs', cls
._confdir
)
94 cls
.createConfigDir(confdir
)
96 cls
.generateRecursorConfig(confdir
)
97 cls
.startRecursor(confdir
, cls
._recursorPort
)
99 print("Launching tests..")
102 def tearDownClass(cls
):
103 cls
.tearDownRecursor()
106 class testRoutingTag(RoutingTagTest
):
107 _confdir
= 'RoutingTag'
109 _config_template
= """
110 log-common-errors=yes
111 use-incoming-edns-subnet=yes
112 edns-subnet-whitelist=ecs-echo.example.
113 forward-zones=ecs-echo.example=%s.24
114 """ % (os
.environ
['PREFIX'])
115 _lua_dns_script_file
= """
117 function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp, proxyProtocolValues)
119 for line in io.lines('tagfile') do
123 return 0, nil, nil, nil, nil, nil, rtag
127 def testSendECS(self
):
128 # First send an ECS query with routingTag
129 self
.setRoutingTag('foo')
130 expected1
= dns
.rrset
.from_text(nameECS
, ttlECS
, dns
.rdataclass
.IN
, 'TXT', '192.0.2.0/24')
131 ecso
= clientsubnetoption
.ClientSubnetOption('192.0.2.1', 32)
132 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN', use_edns
=True, options
=[ecso
], payload
=512)
133 self
.sendECSQuery(query
, expected1
)
135 # Now check a cache hit with the same routingTag (but no ECS)
136 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
137 self
.checkECSQueryHit(query
, expected1
)
139 expected2
= dns
.rrset
.from_text(nameECS
, ttlECS
, dns
.rdataclass
.IN
, 'TXT', '127.0.0.0/24')
140 # And see if a different tag does *not* hit the first one
141 self
.setRoutingTag('bar')
142 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
143 self
.sendECSQuery(query
, expected2
)
145 # And see if a *no* tag does *not* hit the first one
146 expected3
= dns
.rrset
.from_text(nameECS
, ttlECS
, dns
.rdataclass
.IN
, 'TXT', '192.0.3.0/24')
147 self
.setRoutingTag(None)
148 ecso
= clientsubnetoption
.ClientSubnetOption('192.0.3.1', 32)
149 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN', use_edns
=True, options
=[ecso
], payload
=512)
150 self
.sendECSQuery(query
, expected3
)
152 # And see if an unknown tag from the same subnet does hit the last
153 self
.setRoutingTag('baz')
154 ecso
= clientsubnetoption
.ClientSubnetOption('192.0.3.2', 32)
155 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN', use_edns
=True, options
=[ecso
], payload
=512)
156 self
.checkECSQueryHit(query
, expected3
)
158 # And a no tag and no subnet query does hit the general case
159 self
.setRoutingTag(None)
160 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
161 self
.sendECSQuery(query
, expected2
)
163 # And a unknown tag and no subnet query does hit the general case
164 self
.setRoutingTag('bag')
165 query
= dns
.message
.make_query(nameECS
, 'TXT', 'IN')
166 self
.sendECSQuery(query
, expected2
)
168 #return # remove this line to peek at cache
169 rec_controlCmd
= [os
.environ
['RECCONTROL'],
170 '--config-dir=%s' % 'configs/' + self
._confdir
,
173 expected
= 'dumped 6 records\n'
174 ret
= subprocess
.check_output(rec_controlCmd
, stderr
=subprocess
.STDOUT
)
175 self
.assertEqual(ret
, expected
)
177 except subprocess
.CalledProcessError
as e
:
181 class testRoutingTagFFI(RoutingTagTest
):
182 _confdir
= 'RoutingTagFFI'
184 _config_template
= """
185 log-common-errors=yes
186 use-incoming-edns-subnet=yes
187 edns-subnet-whitelist=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
.options
= [ecso
]
307 self
.transport
.write(response
.to_wire(), address
)