xfrout_test.py.in 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. # Copyright (C) 2010 Internet Systems Consortium.
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. '''Tests for the XfroutSession and UnixSockServer classes '''
  16. import unittest
  17. import os
  18. from isc.testutils.tsigctx_mock import MockTSIGContext
  19. from isc.cc.session import *
  20. import isc.config
  21. from pydnspp import *
  22. from xfrout import *
  23. import xfrout
  24. import isc.log
  25. import isc.acl.dns
  26. TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
  27. # our fake socket, where we can read and insert messages
  28. class MySocket():
  29. def __init__(self, family, type):
  30. self.family = family
  31. self.type = type
  32. self.sendqueue = bytearray()
  33. def connect(self, to):
  34. pass
  35. def close(self):
  36. pass
  37. def send(self, data):
  38. self.sendqueue.extend(data);
  39. return len(data)
  40. def readsent(self):
  41. if len(self.sendqueue) >= 2:
  42. size = 2 + struct.unpack("!H", self.sendqueue[:2])[0]
  43. else:
  44. size = 0
  45. result = self.sendqueue[:size]
  46. self.sendqueue = self.sendqueue[size:]
  47. return result
  48. def read_msg(self):
  49. sent_data = self.readsent()
  50. get_msg = Message(Message.PARSE)
  51. get_msg.from_wire(bytes(sent_data[2:]))
  52. return get_msg
  53. def clear_send(self):
  54. del self.sendqueue[:]
  55. # We subclass the Session class we're testing here, only
  56. # to override the handle() and _send_data() method
  57. class MyXfroutSession(XfroutSession):
  58. def handle(self):
  59. pass
  60. def _send_data(self, sock, data):
  61. size = len(data)
  62. total_count = 0
  63. while total_count < size:
  64. count = sock.send(data[total_count:])
  65. total_count += count
  66. class Dbserver:
  67. def __init__(self):
  68. self._shutdown_event = threading.Event()
  69. def get_db_file(self):
  70. return None
  71. def decrease_transfers_counter(self):
  72. pass
  73. class TestXfroutSession(unittest.TestCase):
  74. def getmsg(self):
  75. msg = Message(Message.PARSE)
  76. msg.from_wire(self.mdata)
  77. return msg
  78. def create_mock_tsig_ctx(self, error):
  79. # This helper function creates a MockTSIGContext for a given key
  80. # and TSIG error to be used as a result of verify (normally faked
  81. # one)
  82. mock_ctx = MockTSIGContext(TSIG_KEY)
  83. mock_ctx.error = error
  84. return mock_ctx
  85. def message_has_tsig(self, msg):
  86. return msg.get_tsig_record() is not None
  87. def create_request_data(self, with_tsig=False):
  88. msg = Message(Message.RENDER)
  89. query_id = 0x1035
  90. msg.set_qid(query_id)
  91. msg.set_opcode(Opcode.QUERY())
  92. msg.set_rcode(Rcode.NOERROR())
  93. query_question = Question(Name("example.com"), RRClass.IN(),
  94. RRType.AXFR())
  95. msg.add_question(query_question)
  96. renderer = MessageRenderer()
  97. if with_tsig:
  98. tsig_ctx = MockTSIGContext(TSIG_KEY)
  99. msg.to_wire(renderer, tsig_ctx)
  100. else:
  101. msg.to_wire(renderer)
  102. request_data = renderer.get_data()
  103. return request_data
  104. def setUp(self):
  105. self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
  106. self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(),
  107. TSIGKeyRing(), ('127.0.0.1', 12345),
  108. # When not testing ACLs, simply accept
  109. isc.acl.dns.REQUEST_LOADER.load(
  110. [{"action": "ACCEPT"}]),
  111. {})
  112. self.mdata = self.create_request_data(False)
  113. self.soa_record = (4, 3, 'example.com.', 'com.example.', 3600, 'SOA', None, 'master.example.com. admin.example.com. 1234 3600 1800 2419200 7200')
  114. def test_parse_query_message(self):
  115. [get_rcode, get_msg] = self.xfrsess._parse_query_message(self.mdata)
  116. self.assertEqual(get_rcode.to_text(), "NOERROR")
  117. # tsig signed query message
  118. request_data = self.create_request_data(True)
  119. # BADKEY
  120. [rcode, msg] = self.xfrsess._parse_query_message(request_data)
  121. self.assertEqual(rcode.to_text(), "NOTAUTH")
  122. self.assertTrue(self.xfrsess._tsig_ctx is not None)
  123. # NOERROR
  124. self.assertEqual(TSIGKeyRing.SUCCESS,
  125. self.xfrsess._tsig_key_ring.add(TSIG_KEY))
  126. [rcode, msg] = self.xfrsess._parse_query_message(request_data)
  127. self.assertEqual(rcode.to_text(), "NOERROR")
  128. self.assertTrue(self.xfrsess._tsig_ctx is not None)
  129. def check_transfer_acl(self, acl_setter):
  130. # ACL checks, put some ACL inside
  131. acl_setter(isc.acl.dns.REQUEST_LOADER.load([
  132. {
  133. "from": "127.0.0.1",
  134. "action": "ACCEPT"
  135. },
  136. {
  137. "from": "192.0.2.1",
  138. "action": "DROP"
  139. }
  140. ]))
  141. # Localhost (the default in this test) is accepted
  142. rcode, msg = self.xfrsess._parse_query_message(self.mdata)
  143. self.assertEqual(rcode.to_text(), "NOERROR")
  144. # This should be dropped completely, therefore returning None
  145. self.xfrsess._remote = ('192.0.2.1', 12345)
  146. rcode, msg = self.xfrsess._parse_query_message(self.mdata)
  147. self.assertEqual(None, rcode)
  148. # This should be refused, therefore REFUSED
  149. self.xfrsess._remote = ('192.0.2.2', 12345)
  150. rcode, msg = self.xfrsess._parse_query_message(self.mdata)
  151. self.assertEqual(rcode.to_text(), "REFUSED")
  152. # TSIG signed request
  153. request_data = self.create_request_data(True)
  154. # If the TSIG check fails, it should not check ACL
  155. # (If it checked ACL as well, it would just drop the request)
  156. self.xfrsess._remote = ('192.0.2.1', 12345)
  157. self.xfrsess._tsig_key_ring = TSIGKeyRing()
  158. rcode, msg = self.xfrsess._parse_query_message(request_data)
  159. self.assertEqual(rcode.to_text(), "NOTAUTH")
  160. self.assertTrue(self.xfrsess._tsig_ctx is not None)
  161. # ACL using TSIG: successful case
  162. acl_setter(isc.acl.dns.REQUEST_LOADER.load([
  163. {"key": "example.com", "action": "ACCEPT"}, {"action": "REJECT"}
  164. ]))
  165. self.assertEqual(TSIGKeyRing.SUCCESS,
  166. self.xfrsess._tsig_key_ring.add(TSIG_KEY))
  167. [rcode, msg] = self.xfrsess._parse_query_message(request_data)
  168. self.assertEqual(rcode.to_text(), "NOERROR")
  169. # ACL using TSIG: key name doesn't match; should be rejected
  170. acl_setter(isc.acl.dns.REQUEST_LOADER.load([
  171. {"key": "example.org", "action": "ACCEPT"}, {"action": "REJECT"}
  172. ]))
  173. [rcode, msg] = self.xfrsess._parse_query_message(request_data)
  174. self.assertEqual(rcode.to_text(), "REFUSED")
  175. # ACL using TSIG: no TSIG; should be rejected
  176. acl_setter(isc.acl.dns.REQUEST_LOADER.load([
  177. {"key": "example.org", "action": "ACCEPT"}, {"action": "REJECT"}
  178. ]))
  179. [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
  180. self.assertEqual(rcode.to_text(), "REFUSED")
  181. #
  182. # ACL using IP + TSIG: both should match
  183. #
  184. acl_setter(isc.acl.dns.REQUEST_LOADER.load([
  185. {"ALL": [{"key": "example.com"}, {"from": "192.0.2.1"}],
  186. "action": "ACCEPT"},
  187. {"action": "REJECT"}
  188. ]))
  189. # both matches
  190. self.xfrsess._remote = ('192.0.2.1', 12345)
  191. [rcode, msg] = self.xfrsess._parse_query_message(request_data)
  192. self.assertEqual(rcode.to_text(), "NOERROR")
  193. # TSIG matches, but address doesn't
  194. self.xfrsess._remote = ('192.0.2.2', 12345)
  195. [rcode, msg] = self.xfrsess._parse_query_message(request_data)
  196. self.assertEqual(rcode.to_text(), "REFUSED")
  197. # Address matches, but TSIG doesn't (not included)
  198. self.xfrsess._remote = ('192.0.2.1', 12345)
  199. [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
  200. self.assertEqual(rcode.to_text(), "REFUSED")
  201. # Neither address nor TSIG matches
  202. self.xfrsess._remote = ('192.0.2.2', 12345)
  203. [rcode, msg] = self.xfrsess._parse_query_message(self.mdata)
  204. self.assertEqual(rcode.to_text(), "REFUSED")
  205. def test_transfer_acl(self):
  206. # ACL checks only with the default ACL
  207. def acl_setter(acl):
  208. self.xfrsess._acl = acl
  209. self.check_transfer_acl(acl_setter)
  210. def test_transfer_zoneacl(self):
  211. # ACL check with a per zone ACL + default ACL. The per zone ACL
  212. # should match the queryied zone, so it should be used.
  213. def acl_setter(acl):
  214. zone_key = ('IN', 'example.com.')
  215. self.xfrsess._zone_config[zone_key] = {}
  216. self.xfrsess._zone_config[zone_key]['transfer_acl'] = acl
  217. self.xfrsess._acl = isc.acl.dns.REQUEST_LOADER.load([
  218. {"from": "127.0.0.1", "action": "DROP"}])
  219. self.check_transfer_acl(acl_setter)
  220. def test_transfer_zoneacl_nomatch(self):
  221. # similar to the previous one, but the per zone doesn't match the
  222. # query. The default should be used.
  223. def acl_setter(acl):
  224. zone_key = ('IN', 'example.org.')
  225. self.xfrsess._zone_config[zone_key] = {}
  226. self.xfrsess._zone_config[zone_key]['transfer_acl'] = \
  227. isc.acl.dns.REQUEST_LOADER.load([
  228. {"from": "127.0.0.1", "action": "DROP"}])
  229. self.xfrsess._acl = acl
  230. self.check_transfer_acl(acl_setter)
  231. def test_get_transfer_acl(self):
  232. # set the default ACL. If there's no specific zone ACL, this one
  233. # should be used.
  234. self.xfrsess._acl = isc.acl.dns.REQUEST_LOADER.load([
  235. {"from": "127.0.0.1", "action": "ACCEPT"}])
  236. acl = self.xfrsess._get_transfer_acl(Name('example.com'), RRClass.IN())
  237. self.assertEqual(acl, self.xfrsess._acl)
  238. # install a per zone config with transfer ACL for example.com. Then
  239. # that ACL will be used for example.com; for others the default ACL
  240. # will still be used.
  241. com_acl = isc.acl.dns.REQUEST_LOADER.load([
  242. {"from": "127.0.0.1", "action": "REJECT"}])
  243. self.xfrsess._zone_config[('IN', 'example.com.')] = {}
  244. self.xfrsess._zone_config[('IN', 'example.com.')]['transfer_acl'] = \
  245. com_acl
  246. self.assertEqual(com_acl,
  247. self.xfrsess._get_transfer_acl(Name('example.com'),
  248. RRClass.IN()))
  249. self.assertEqual(self.xfrsess._acl,
  250. self.xfrsess._get_transfer_acl(Name('example.org'),
  251. RRClass.IN()))
  252. # Name matching should be case insensitive.
  253. self.assertEqual(com_acl,
  254. self.xfrsess._get_transfer_acl(Name('EXAMPLE.COM'),
  255. RRClass.IN()))
  256. def test_get_query_zone_name(self):
  257. msg = self.getmsg()
  258. self.assertEqual(self.xfrsess._get_query_zone_name(msg), "example.com.")
  259. def test_send_data(self):
  260. self.xfrsess._send_data(self.sock, self.mdata)
  261. senddata = self.sock.readsent()
  262. self.assertEqual(senddata, self.mdata)
  263. def test_reply_xfrout_query_with_error_rcode(self):
  264. msg = self.getmsg()
  265. self.xfrsess._reply_query_with_error_rcode(msg, self.sock, Rcode(3))
  266. get_msg = self.sock.read_msg()
  267. self.assertEqual(get_msg.get_rcode().to_text(), "NXDOMAIN")
  268. # tsig signed message
  269. msg = self.getmsg()
  270. self.xfrsess._tsig_ctx = self.create_mock_tsig_ctx(TSIGError.NOERROR)
  271. self.xfrsess._reply_query_with_error_rcode(msg, self.sock, Rcode(3))
  272. get_msg = self.sock.read_msg()
  273. self.assertEqual(get_msg.get_rcode().to_text(), "NXDOMAIN")
  274. self.assertTrue(self.message_has_tsig(get_msg))
  275. def test_send_message(self):
  276. msg = self.getmsg()
  277. msg.make_response()
  278. # soa record data with different cases
  279. soa_record = (4, 3, 'Example.com.', 'com.Example.', 3600, 'SOA', None, 'master.Example.com. admin.exAmple.com. 1234 3600 1800 2419200 7200')
  280. rrset_soa = self.xfrsess._create_rrset_from_db_record(soa_record)
  281. msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
  282. self.xfrsess._send_message(self.sock, msg)
  283. send_out_data = self.sock.readsent()[2:]
  284. # CASE_INSENSITIVE compression mode
  285. render = MessageRenderer();
  286. render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
  287. msg.to_wire(render)
  288. self.assertNotEqual(render.get_data(), send_out_data)
  289. # CASE_SENSITIVE compression mode
  290. render.clear()
  291. render.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
  292. render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
  293. msg.to_wire(render)
  294. self.assertEqual(render.get_data(), send_out_data)
  295. def test_clear_message(self):
  296. msg = self.getmsg()
  297. qid = msg.get_qid()
  298. opcode = msg.get_opcode()
  299. rcode = msg.get_rcode()
  300. self.xfrsess._clear_message(msg)
  301. self.assertEqual(msg.get_qid(), qid)
  302. self.assertEqual(msg.get_opcode(), opcode)
  303. self.assertEqual(msg.get_rcode(), rcode)
  304. self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_AA))
  305. def test_create_rrset_from_db_record(self):
  306. rrset = self.xfrsess._create_rrset_from_db_record(self.soa_record)
  307. self.assertEqual(rrset.get_name().to_text(), "example.com.")
  308. self.assertEqual(rrset.get_class(), RRClass("IN"))
  309. self.assertEqual(rrset.get_type().to_text(), "SOA")
  310. rdata = rrset.get_rdata()
  311. self.assertEqual(rdata[0].to_text(), self.soa_record[7])
  312. def test_send_message_with_last_soa(self):
  313. rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
  314. msg = self.getmsg()
  315. msg.make_response()
  316. # packet number less than TSIG_SIGN_EVERY_NTH
  317. packet_neet_not_sign = xfrout.TSIG_SIGN_EVERY_NTH - 1
  318. self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa,
  319. 0, packet_neet_not_sign)
  320. get_msg = self.sock.read_msg()
  321. # tsig context is not exist
  322. self.assertFalse(self.message_has_tsig(get_msg))
  323. self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 1)
  324. self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
  325. self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
  326. #answer_rrset_iter = section_iter(get_msg, section.ANSWER())
  327. answer = get_msg.get_section(Message.SECTION_ANSWER)[0]#answer_rrset_iter.get_rrset()
  328. self.assertEqual(answer.get_name().to_text(), "example.com.")
  329. self.assertEqual(answer.get_class(), RRClass("IN"))
  330. self.assertEqual(answer.get_type().to_text(), "SOA")
  331. rdata = answer.get_rdata()
  332. self.assertEqual(rdata[0].to_text(), self.soa_record[7])
  333. # msg is the TSIG_SIGN_EVERY_NTH one
  334. # sending the message with last soa together
  335. self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa,
  336. 0, TSIG_SIGN_EVERY_NTH)
  337. get_msg = self.sock.read_msg()
  338. # tsig context is not exist
  339. self.assertFalse(self.message_has_tsig(get_msg))
  340. def test_send_message_with_last_soa_with_tsig(self):
  341. # create tsig context
  342. self.xfrsess._tsig_ctx = self.create_mock_tsig_ctx(TSIGError.NOERROR)
  343. rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
  344. msg = self.getmsg()
  345. msg.make_response()
  346. # packet number less than TSIG_SIGN_EVERY_NTH
  347. packet_neet_not_sign = xfrout.TSIG_SIGN_EVERY_NTH - 1
  348. # msg is not the TSIG_SIGN_EVERY_NTH one
  349. # sending the message with last soa together
  350. self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa,
  351. 0, packet_neet_not_sign)
  352. get_msg = self.sock.read_msg()
  353. self.assertTrue(self.message_has_tsig(get_msg))
  354. self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 1)
  355. self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
  356. self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
  357. # msg is the TSIG_SIGN_EVERY_NTH one
  358. # sending the message with last soa together
  359. self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa,
  360. 0, TSIG_SIGN_EVERY_NTH)
  361. get_msg = self.sock.read_msg()
  362. self.assertTrue(self.message_has_tsig(get_msg))
  363. def test_trigger_send_message_with_last_soa(self):
  364. rrset_a = RRset(Name("example.com"), RRClass.IN(), RRType.A(), RRTTL(3600))
  365. rrset_a.add_rdata(Rdata(RRType.A(), RRClass.IN(), "192.0.2.1"))
  366. rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
  367. msg = self.getmsg()
  368. msg.make_response()
  369. msg.add_rrset(Message.SECTION_ANSWER, rrset_a)
  370. # length larger than MAX-len(rrset)
  371. length_need_split = xfrout.XFROUT_MAX_MESSAGE_SIZE - get_rrset_len(rrset_soa) + 1
  372. # packet number less than TSIG_SIGN_EVERY_NTH
  373. packet_neet_not_sign = xfrout.TSIG_SIGN_EVERY_NTH - 1
  374. # give the function a value that is larger than MAX-len(rrset)
  375. # this should have triggered the sending of two messages
  376. # (1 with the rrset we added manually, and 1 that triggered
  377. # the sending in _with_last_soa)
  378. self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa, length_need_split,
  379. packet_neet_not_sign)
  380. get_msg = self.sock.read_msg()
  381. self.assertFalse(self.message_has_tsig(get_msg))
  382. self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 1)
  383. self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
  384. self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
  385. answer = get_msg.get_section(Message.SECTION_ANSWER)[0]
  386. self.assertEqual(answer.get_name().to_text(), "example.com.")
  387. self.assertEqual(answer.get_class(), RRClass("IN"))
  388. self.assertEqual(answer.get_type().to_text(), "A")
  389. rdata = answer.get_rdata()
  390. self.assertEqual(rdata[0].to_text(), "192.0.2.1")
  391. get_msg = self.sock.read_msg()
  392. self.assertFalse(self.message_has_tsig(get_msg))
  393. self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 0)
  394. self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
  395. self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
  396. #answer_rrset_iter = section_iter(get_msg, Message.SECTION_ANSWER)
  397. answer = get_msg.get_section(Message.SECTION_ANSWER)[0]
  398. self.assertEqual(answer.get_name().to_text(), "example.com.")
  399. self.assertEqual(answer.get_class(), RRClass("IN"))
  400. self.assertEqual(answer.get_type().to_text(), "SOA")
  401. rdata = answer.get_rdata()
  402. self.assertEqual(rdata[0].to_text(), self.soa_record[7])
  403. # and it should not have sent anything else
  404. self.assertEqual(0, len(self.sock.sendqueue))
  405. def test_trigger_send_message_with_last_soa_with_tsig(self):
  406. self.xfrsess._tsig_ctx = self.create_mock_tsig_ctx(TSIGError.NOERROR)
  407. rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
  408. msg = self.getmsg()
  409. msg.make_response()
  410. msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
  411. # length larger than MAX-len(rrset)
  412. length_need_split = xfrout.XFROUT_MAX_MESSAGE_SIZE - get_rrset_len(rrset_soa) + 1
  413. # packet number less than TSIG_SIGN_EVERY_NTH
  414. packet_neet_not_sign = xfrout.TSIG_SIGN_EVERY_NTH - 1
  415. # give the function a value that is larger than MAX-len(rrset)
  416. # this should have triggered the sending of two messages
  417. # (1 with the rrset we added manually, and 1 that triggered
  418. # the sending in _with_last_soa)
  419. self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa, length_need_split,
  420. packet_neet_not_sign)
  421. get_msg = self.sock.read_msg()
  422. # msg is not the TSIG_SIGN_EVERY_NTH one, it shouldn't be tsig signed
  423. self.assertFalse(self.message_has_tsig(get_msg))
  424. # the last packet should be tsig signed
  425. get_msg = self.sock.read_msg()
  426. self.assertTrue(self.message_has_tsig(get_msg))
  427. # and it should not have sent anything else
  428. self.assertEqual(0, len(self.sock.sendqueue))
  429. # msg is the TSIG_SIGN_EVERY_NTH one, it should be tsig signed
  430. self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa, length_need_split,
  431. xfrout.TSIG_SIGN_EVERY_NTH)
  432. get_msg = self.sock.read_msg()
  433. self.assertTrue(self.message_has_tsig(get_msg))
  434. # the last packet should be tsig signed
  435. get_msg = self.sock.read_msg()
  436. self.assertTrue(self.message_has_tsig(get_msg))
  437. # and it should not have sent anything else
  438. self.assertEqual(0, len(self.sock.sendqueue))
  439. def test_get_rrset_len(self):
  440. rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
  441. self.assertEqual(82, get_rrset_len(rrset_soa))
  442. def test_zone_has_soa(self):
  443. global sqlite3_ds
  444. def mydb1(zone, file):
  445. return True
  446. sqlite3_ds.get_zone_soa = mydb1
  447. self.assertTrue(self.xfrsess._zone_has_soa(""))
  448. def mydb2(zone, file):
  449. return False
  450. sqlite3_ds.get_zone_soa = mydb2
  451. self.assertFalse(self.xfrsess._zone_has_soa(""))
  452. def test_zone_exist(self):
  453. global sqlite3_ds
  454. def zone_exist(zone, file):
  455. return zone
  456. sqlite3_ds.zone_exist = zone_exist
  457. self.assertTrue(self.xfrsess._zone_exist(True))
  458. self.assertFalse(self.xfrsess._zone_exist(False))
  459. def test_check_xfrout_available(self):
  460. def zone_exist(zone):
  461. return zone
  462. def zone_has_soa(zone):
  463. return (not zone)
  464. self.xfrsess._zone_exist = zone_exist
  465. self.xfrsess._zone_has_soa = zone_has_soa
  466. self.assertEqual(self.xfrsess._check_xfrout_available(False).to_text(), "NOTAUTH")
  467. self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "SERVFAIL")
  468. def zone_empty(zone):
  469. return zone
  470. self.xfrsess._zone_has_soa = zone_empty
  471. def false_func():
  472. return False
  473. self.xfrsess._server.increase_transfers_counter = false_func
  474. self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "REFUSED")
  475. def true_func():
  476. return True
  477. self.xfrsess._server.increase_transfers_counter = true_func
  478. self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "NOERROR")
  479. def test_dns_xfrout_start_formerror(self):
  480. # formerror
  481. self.xfrsess.dns_xfrout_start(self.sock, b"\xd6=\x00\x00\x00\x01\x00")
  482. sent_data = self.sock.readsent()
  483. self.assertEqual(len(sent_data), 0)
  484. def default(self, param):
  485. return "example.com"
  486. def test_dns_xfrout_start_notauth(self):
  487. self.xfrsess._get_query_zone_name = self.default
  488. def notauth(formpara):
  489. return Rcode.NOTAUTH()
  490. self.xfrsess._check_xfrout_available = notauth
  491. self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
  492. get_msg = self.sock.read_msg()
  493. self.assertEqual(get_msg.get_rcode().to_text(), "NOTAUTH")
  494. def test_dns_xfrout_start_noerror(self):
  495. self.xfrsess._get_query_zone_name = self.default
  496. def noerror(form):
  497. return Rcode.NOERROR()
  498. self.xfrsess._check_xfrout_available = noerror
  499. def myreply(msg, sock, zonename):
  500. self.sock.send(b"success")
  501. self.xfrsess._reply_xfrout_query = myreply
  502. self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
  503. self.assertEqual(self.sock.readsent(), b"success")
  504. def test_reply_xfrout_query_noerror(self):
  505. global sqlite3_ds
  506. def get_zone_soa(zonename, file):
  507. return self.soa_record
  508. def get_zone_datas(zone, file):
  509. return [self.soa_record]
  510. sqlite3_ds.get_zone_soa = get_zone_soa
  511. sqlite3_ds.get_zone_datas = get_zone_datas
  512. self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock, "example.com.")
  513. reply_msg = self.sock.read_msg()
  514. self.assertEqual(reply_msg.get_rr_count(Message.SECTION_ANSWER), 2)
  515. def test_reply_xfrout_query_noerror_with_tsig(self):
  516. rrset_data = (4, 3, 'a.example.com.', 'com.example.', 3600, 'A', None, '192.168.1.1')
  517. global sqlite3_ds
  518. global xfrout
  519. def get_zone_soa(zonename, file):
  520. return self.soa_record
  521. def get_zone_datas(zone, file):
  522. zone_rrsets = []
  523. for i in range(0, 100):
  524. zone_rrsets.insert(i, rrset_data)
  525. return zone_rrsets
  526. def get_rrset_len(rrset):
  527. return 65520
  528. sqlite3_ds.get_zone_soa = get_zone_soa
  529. sqlite3_ds.get_zone_datas = get_zone_datas
  530. xfrout.get_rrset_len = get_rrset_len
  531. self.xfrsess._tsig_ctx = self.create_mock_tsig_ctx(TSIGError.NOERROR)
  532. self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock, "example.com.")
  533. # tsig signed first package
  534. reply_msg = self.sock.read_msg()
  535. self.assertEqual(reply_msg.get_rr_count(Message.SECTION_ANSWER), 1)
  536. self.assertTrue(self.message_has_tsig(reply_msg))
  537. # (TSIG_SIGN_EVERY_NTH - 1) packets have no tsig
  538. for i in range(0, xfrout.TSIG_SIGN_EVERY_NTH - 1):
  539. reply_msg = self.sock.read_msg()
  540. self.assertFalse(self.message_has_tsig(reply_msg))
  541. # TSIG_SIGN_EVERY_NTH packet has tsig
  542. reply_msg = self.sock.read_msg()
  543. self.assertTrue(self.message_has_tsig(reply_msg))
  544. for i in range(0, 100 - TSIG_SIGN_EVERY_NTH):
  545. reply_msg = self.sock.read_msg()
  546. self.assertFalse(self.message_has_tsig(reply_msg))
  547. # tsig signed last package
  548. reply_msg = self.sock.read_msg()
  549. self.assertTrue(self.message_has_tsig(reply_msg))
  550. # and it should not have sent anything else
  551. self.assertEqual(0, len(self.sock.sendqueue))
  552. class MyCCSession(isc.config.ConfigData):
  553. def __init__(self):
  554. module_spec = isc.config.module_spec_from_file(
  555. xfrout.SPECFILE_LOCATION)
  556. ConfigData.__init__(self, module_spec)
  557. def get_remote_config_value(self, module_name, identifier):
  558. if module_name == "Auth" and identifier == "database_file":
  559. return "initdb.file", False
  560. else:
  561. return "unknown", False
  562. class MyUnixSockServer(UnixSockServer):
  563. def __init__(self):
  564. self._shutdown_event = threading.Event()
  565. self._common_init()
  566. self._cc = MyCCSession()
  567. self.update_config_data(self._cc.get_full_config())
  568. class TestUnixSockServer(unittest.TestCase):
  569. def setUp(self):
  570. self.write_sock, self.read_sock = socket.socketpair()
  571. self.unix = MyUnixSockServer()
  572. def test_guess_remote(self):
  573. """Test we can guess the remote endpoint when we have only the
  574. file descriptor. This is needed, because we get only that one
  575. from auth."""
  576. # We test with UDP, as it can be "connected" without other
  577. # endpoint
  578. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  579. sock.connect(('127.0.0.1', 12345))
  580. self.assertEqual(('127.0.0.1', 12345),
  581. self.unix._guess_remote(sock.fileno()))
  582. if socket.has_ipv6:
  583. # Don't check IPv6 address on hosts not supporting them
  584. sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
  585. sock.connect(('::1', 12345))
  586. self.assertEqual(('::1', 12345, 0, 0),
  587. self.unix._guess_remote(sock.fileno()))
  588. # Try when pretending there's no IPv6 support
  589. # (No need to pretend when there's really no IPv6)
  590. xfrout.socket.has_ipv6 = False
  591. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  592. sock.connect(('127.0.0.1', 12345))
  593. self.assertEqual(('127.0.0.1', 12345),
  594. self.unix._guess_remote(sock.fileno()))
  595. # Return it back
  596. xfrout.socket.has_ipv6 = True
  597. def test_receive_query_message(self):
  598. send_msg = b"\xd6=\x00\x00\x00\x01\x00"
  599. msg_len = struct.pack('H', socket.htons(len(send_msg)))
  600. self.write_sock.send(msg_len)
  601. self.write_sock.send(send_msg)
  602. recv_msg = self.unix._receive_query_message(self.read_sock)
  603. self.assertEqual(recv_msg, send_msg)
  604. def check_default_ACL(self):
  605. context = isc.acl.dns.RequestContext(socket.getaddrinfo("127.0.0.1",
  606. 1234, 0, socket.SOCK_DGRAM,
  607. socket.IPPROTO_UDP,
  608. socket.AI_NUMERICHOST)[0][4])
  609. self.assertEqual(isc.acl.acl.ACCEPT, self.unix._acl.execute(context))
  610. def check_loaded_ACL(self, acl):
  611. context = isc.acl.dns.RequestContext(socket.getaddrinfo("127.0.0.1",
  612. 1234, 0, socket.SOCK_DGRAM,
  613. socket.IPPROTO_UDP,
  614. socket.AI_NUMERICHOST)[0][4])
  615. self.assertEqual(isc.acl.acl.ACCEPT, acl.execute(context))
  616. context = isc.acl.dns.RequestContext(socket.getaddrinfo("192.0.2.1",
  617. 1234, 0, socket.SOCK_DGRAM,
  618. socket.IPPROTO_UDP,
  619. socket.AI_NUMERICHOST)[0][4])
  620. self.assertEqual(isc.acl.acl.REJECT, acl.execute(context))
  621. def test_update_config_data(self):
  622. self.check_default_ACL()
  623. tsig_key_str = 'example.com:SFuWd/q99SzF8Yzd1QbB9g=='
  624. tsig_key_list = [tsig_key_str]
  625. bad_key_list = ['bad..example.com:SFuWd/q99SzF8Yzd1QbB9g==']
  626. self.unix.update_config_data({'transfers_out':10 })
  627. self.assertEqual(self.unix._max_transfers_out, 10)
  628. self.assertTrue(self.unix.tsig_key_ring is not None)
  629. self.check_default_ACL()
  630. self.unix.update_config_data({'transfers_out':9,
  631. 'tsig_key_ring':tsig_key_list})
  632. self.assertEqual(self.unix._max_transfers_out, 9)
  633. self.assertEqual(self.unix.tsig_key_ring.size(), 1)
  634. self.unix.tsig_key_ring.remove(Name("example.com."))
  635. self.assertEqual(self.unix.tsig_key_ring.size(), 0)
  636. # bad tsig key
  637. config_data = {'transfers_out':9, 'tsig_key_ring': bad_key_list}
  638. self.assertRaises(None, self.unix.update_config_data(config_data))
  639. self.assertEqual(self.unix.tsig_key_ring.size(), 0)
  640. # Load the ACL
  641. self.unix.update_config_data({'transfer_acl': [{'from': '127.0.0.1',
  642. 'action': 'ACCEPT'}]})
  643. self.check_loaded_ACL(self.unix._acl)
  644. # Pass a wrong data there and check it does not replace the old one
  645. self.assertRaises(XfroutConfigError,
  646. self.unix.update_config_data,
  647. {'transfer_acl': ['Something bad']})
  648. self.check_loaded_ACL(self.unix._acl)
  649. def test_zone_config_data(self):
  650. # By default, there's no specific zone config
  651. self.assertEqual({}, self.unix._zone_config)
  652. # Adding config for a specific zone. The config is empty unless
  653. # explicitly specified.
  654. self.unix.update_config_data({'zone_config':
  655. [{'origin': 'example.com',
  656. 'class': 'IN'}]})
  657. self.assertEqual({}, self.unix._zone_config[('IN', 'example.com.')])
  658. # zone class can be omitted
  659. self.unix.update_config_data({'zone_config':
  660. [{'origin': 'example.com'}]})
  661. self.assertEqual({}, self.unix._zone_config[('IN', 'example.com.')])
  662. # zone class, name are stored in the "normalized" form. class
  663. # strings are upper cased, names are down cased.
  664. self.unix.update_config_data({'zone_config':
  665. [{'origin': 'EXAMPLE.com'}]})
  666. self.assertEqual({}, self.unix._zone_config[('IN', 'example.com.')])
  667. # invalid zone class, name will result in exceptions
  668. self.assertRaises(EmptyLabel,
  669. self.unix.update_config_data,
  670. {'zone_config': [{'origin': 'bad..example'}]})
  671. self.assertRaises(InvalidRRClass,
  672. self.unix.update_config_data,
  673. {'zone_config': [{'origin': 'example.com',
  674. 'class': 'badclass'}]})
  675. # Configuring a couple of more zones
  676. self.unix.update_config_data({'zone_config':
  677. [{'origin': 'example.com'},
  678. {'origin': 'example.com',
  679. 'class': 'CH'},
  680. {'origin': 'example.org'}]})
  681. self.assertEqual({}, self.unix._zone_config[('IN', 'example.com.')])
  682. self.assertEqual({}, self.unix._zone_config[('CH', 'example.com.')])
  683. self.assertEqual({}, self.unix._zone_config[('IN', 'example.org.')])
  684. # Duplicate data: should be rejected with an exception
  685. self.assertRaises(XfroutConfigError,
  686. self.unix.update_config_data,
  687. {'zone_config': [{'origin': 'example.com'},
  688. {'origin': 'example.org'},
  689. {'origin': 'example.com'}]})
  690. def test_zone_config_data_with_acl(self):
  691. # Similar to the previous test, but with transfer_acl config
  692. self.unix.update_config_data({'zone_config':
  693. [{'origin': 'example.com',
  694. 'transfer_acl':
  695. [{'from': '127.0.0.1',
  696. 'action': 'ACCEPT'}]}]})
  697. acl = self.unix._zone_config[('IN', 'example.com.')]['transfer_acl']
  698. self.check_loaded_ACL(acl)
  699. # invalid ACL syntax will be rejected with exception
  700. self.assertRaises(XfroutConfigError,
  701. self.unix.update_config_data,
  702. {'zone_config': [{'origin': 'example.com',
  703. 'transfer_acl':
  704. [{'action': 'BADACTION'}]}]})
  705. def test_get_db_file(self):
  706. self.assertEqual(self.unix.get_db_file(), "initdb.file")
  707. def test_increase_transfers_counter(self):
  708. self.unix._max_transfers_out = 10
  709. count = self.unix._transfers_counter
  710. self.assertEqual(self.unix.increase_transfers_counter(), True)
  711. self.assertEqual(count + 1, self.unix._transfers_counter)
  712. self.unix._max_transfers_out = 0
  713. count = self.unix._transfers_counter
  714. self.assertEqual(self.unix.increase_transfers_counter(), False)
  715. self.assertEqual(count, self.unix._transfers_counter)
  716. def test_decrease_transfers_counter(self):
  717. count = self.unix._transfers_counter
  718. self.unix.decrease_transfers_counter()
  719. self.assertEqual(count - 1, self.unix._transfers_counter)
  720. def _remove_file(self, sock_file):
  721. try:
  722. os.remove(sock_file)
  723. except OSError:
  724. pass
  725. def test_sock_file_in_use_file_exist(self):
  726. sock_file = 'temp.sock.file'
  727. self._remove_file(sock_file)
  728. self.assertFalse(self.unix._sock_file_in_use(sock_file))
  729. self.assertFalse(os.path.exists(sock_file))
  730. def test_sock_file_in_use_file_not_exist(self):
  731. self.assertFalse(self.unix._sock_file_in_use('temp.sock.file'))
  732. def _start_unix_sock_server(self, sock_file):
  733. serv = ThreadingUnixStreamServer(sock_file, BaseRequestHandler)
  734. serv_thread = threading.Thread(target=serv.serve_forever)
  735. serv_thread.setDaemon(True)
  736. serv_thread.start()
  737. def test_sock_file_in_use(self):
  738. sock_file = 'temp.sock.file'
  739. self._remove_file(sock_file)
  740. self.assertFalse(self.unix._sock_file_in_use(sock_file))
  741. self._start_unix_sock_server(sock_file)
  742. old_stdout = sys.stdout
  743. sys.stdout = open(os.devnull, 'w')
  744. self.assertTrue(self.unix._sock_file_in_use(sock_file))
  745. sys.stdout = old_stdout
  746. def test_remove_unused_sock_file_in_use(self):
  747. sock_file = 'temp.sock.file'
  748. self._remove_file(sock_file)
  749. self.assertFalse(self.unix._sock_file_in_use(sock_file))
  750. self._start_unix_sock_server(sock_file)
  751. old_stdout = sys.stdout
  752. sys.stdout = open(os.devnull, 'w')
  753. try:
  754. self.unix._remove_unused_sock_file(sock_file)
  755. except SystemExit:
  756. pass
  757. else:
  758. # This should never happen
  759. self.assertTrue(False)
  760. sys.stdout = old_stdout
  761. def test_remove_unused_sock_file_dir(self):
  762. import tempfile
  763. dir_name = tempfile.mkdtemp()
  764. old_stdout = sys.stdout
  765. sys.stdout = open(os.devnull, 'w')
  766. try:
  767. self.unix._remove_unused_sock_file(dir_name)
  768. except SystemExit:
  769. pass
  770. else:
  771. # This should never happen
  772. self.assertTrue(False)
  773. sys.stdout = old_stdout
  774. os.rmdir(dir_name)
  775. class TestInitialization(unittest.TestCase):
  776. def setEnv(self, name, value):
  777. if value is None:
  778. if name in os.environ:
  779. del os.environ[name]
  780. else:
  781. os.environ[name] = value
  782. def setUp(self):
  783. self._oldSocket = os.getenv("BIND10_XFROUT_SOCKET_FILE")
  784. self._oldFromBuild = os.getenv("B10_FROM_BUILD")
  785. def tearDown(self):
  786. self.setEnv("B10_FROM_BUILD", self._oldFromBuild)
  787. self.setEnv("BIND10_XFROUT_SOCKET_FILE", self._oldSocket)
  788. # Make sure even the computed values are back
  789. xfrout.init_paths()
  790. def testNoEnv(self):
  791. self.setEnv("B10_FROM_BUILD", None)
  792. self.setEnv("BIND10_XFROUT_SOCKET_FILE", None)
  793. xfrout.init_paths()
  794. self.assertEqual(xfrout.UNIX_SOCKET_FILE,
  795. "@@LOCALSTATEDIR@@/@PACKAGE_NAME@/auth_xfrout_conn")
  796. def testProvidedSocket(self):
  797. self.setEnv("B10_FROM_BUILD", None)
  798. self.setEnv("BIND10_XFROUT_SOCKET_FILE", "The/Socket/File")
  799. xfrout.init_paths()
  800. self.assertEqual(xfrout.UNIX_SOCKET_FILE, "The/Socket/File")
  801. if __name__== "__main__":
  802. isc.log.resetUnitTestRootLogger()
  803. unittest.main()