dns.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. """Domain Name System."""
  2. from pypacker import pypacker, triggerlist
  3. TriggerList = triggerlist.TriggerList
  4. import struct
  5. import logging
  6. unpack = struct.unpack
  7. logger = logging.getLogger("pypacker")
  8. DNS_Q = 0
  9. DNS_R = 1
  10. # Opcodes
  11. DNS_QUERY = 0
  12. DNS_IQUERY = 1
  13. DNS_STATUS = 2
  14. DNS_NOTIFY = 4
  15. DNS_UPDATE = 5
  16. # Flags
  17. DNS_AN = 0x8000 # this is a response
  18. DNS_CD = 0x0010 # checking disabled
  19. DNS_AD = 0x0020 # authenticated data
  20. DNS_Z = 0x0040 # unused
  21. DNS_RA = 0x0080 # recursion available
  22. DNS_RD = 0x0100 # recursion desired
  23. DNS_TC = 0x0200 # truncated
  24. DNS_AA = 0x0400 # authoritative answer
  25. # Response codes
  26. DNS_RCODE_NOERR = 0
  27. DNS_RCODE_FORMERR = 1
  28. DNS_RCODE_SERVFAIL = 2
  29. DNS_RCODE_NXDOMAIN = 3
  30. DNS_RCODE_NOTIMP = 4
  31. DNS_RCODE_REFUSED = 5
  32. DNS_RCODE_YXDOMAIN = 6
  33. DNS_RCODE_YXRRSET = 7
  34. DNS_RCODE_NXRRSET = 8
  35. DNS_RCODE_NOTAUTH = 9
  36. DNS_RCODE_NOTZONE = 10
  37. # RR types
  38. DNS_A = 1
  39. DNS_NS = 2
  40. DNS_CNAME = 5
  41. DNS_SOA = 6
  42. DNS_WKS = 11
  43. DNS_PTR = 12
  44. DNS_HINFO = 13
  45. DNS_MINFO = 14
  46. DNS_MX = 15
  47. DNS_TXT = 16
  48. DNS_RP = 17
  49. DNS_SIG = 24
  50. DNS_GPOS = 27
  51. DNS_AAAA = 28
  52. DNS_LOC = 29
  53. DNS_SRV = 33
  54. DNS_NAPTR = 35
  55. DNS_KX = 36
  56. DNS_CERT = 37
  57. DNS_DNAME = 39
  58. DNS_DS = 43
  59. DNS_SSHFP = 44
  60. DNS_IPSECKEY = 45
  61. DNS_RRSIG = 46
  62. DNS_NSEC = 47
  63. DNS_DNSKEY = 48
  64. DNS_DHCID = 49
  65. DNS_NSEC3 = 50
  66. DNS_NSEC3PARAM = 51
  67. DNS_TLSA = 52
  68. DNS_SPF = 99
  69. DNS_TKEY = 249
  70. DNS_TSIG = 250
  71. DNS_IXFR = 251
  72. DNS_AXFR = 252
  73. DNS_CAA = 257
  74. DNS_TA = 32768
  75. DNS_DLV = 32769
  76. # RR classes
  77. DNS_IN = 1
  78. DNS_CHAOS = 3
  79. DNS_HESIOD = 4
  80. DNS_ANY = 255
  81. class DNS(pypacker.Packet):
  82. __hdr__ = (
  83. ("id", "H", 0x1234),
  84. ("flags", "H", DNS_AD | DNS_RD),
  85. ("questions_amount", "H", 0),
  86. ("answers_amount", "H", 0),
  87. ("authrr_amount", "H", 0),
  88. ("addrr_amount", "H", 0),
  89. ("queries", None, TriggerList),
  90. ("answers", None, TriggerList),
  91. ("auths", None, TriggerList),
  92. ("addrecords", None, TriggerList)
  93. )
  94. class Query(pypacker.Packet):
  95. """DNS question."""
  96. __hdr__ = (
  97. ("name", None, b"\x03www\x04test\x03com\x00"),
  98. ("type", "H", DNS_A),
  99. ("cls", "H", DNS_IN)
  100. )
  101. name_s = pypacker.get_property_dnsname("name")
  102. def _dissect(self, buf):
  103. idx = buf.find(b"\x00")
  104. #logger.debug("name in Query: %s" % buf[:idx+1])
  105. self.name = buf[:idx + 1]
  106. #logger.debug("val / format: %s %s" % (self._name, self._name_format))
  107. return len(buf) # name (including 0) + type + cls
  108. class Answer(pypacker.Packet):
  109. """DNS resource record."""
  110. __hdr__ = (
  111. ("name", "H", 0xc00c),
  112. ("type", "H", DNS_A),
  113. ("cls", "H", DNS_IN),
  114. ("ttl", "I", 180),
  115. ("dlen", "H", 4), # length of the next field
  116. ("address", None, b"1234") # eg IPv4
  117. )
  118. def _dissect(self, buf):
  119. # needed set format
  120. addr_len = unpack(">H", buf[10:12])[0]
  121. self.address = buf[12:12 + addr_len]
  122. # logger.debug("address: %s" % self.address)
  123. return 12 + addr_len
  124. class Auth(pypacker.Packet):
  125. """Auth, generic type."""
  126. __hdr__ = (
  127. ("name", "H", 0),
  128. ("type", "H", 0),
  129. ("cls", "H", 0),
  130. ("ttl", "I", 0),
  131. ("dlen", "H", 0), # length of the rest of header: server + x, x becmoes body content
  132. ("server", None, b"\x03www\x04test\x03com\x00")
  133. # TODO: add fields for mailbox, serial, refresh etc.
  134. )
  135. server_s = pypacker.get_property_dnsname("server")
  136. def _dissect(self, buf):
  137. # needed set format
  138. # find server name by 0-termination
  139. idx = buf.find(b"\x00", 12)
  140. if idx == -1:
  141. idx = len(buf)
  142. self.server = buf[12: idx + 1]
  143. # logger.debug("server: %s" % self.server)
  144. return idx + 1
  145. class AuthSOA(pypacker.Packet):
  146. """
  147. Auth type SOA.
  148. Not used atm
  149. """
  150. __hdr__ = (
  151. ("name", "H", 0),
  152. ("type", "H", 0),
  153. ("cls", "H", 0),
  154. ("ttl", "I", 0),
  155. ("dlen", "H", 0),
  156. ("name2", None, b"\x03www\x04test\x03com\x00"),
  157. ("mailbox", None, b"\x03www\x04test\x03com\x00"),
  158. ("pserver", "H", 0),
  159. ("mbox", "H", 0),
  160. ("serial", "H", 0),
  161. ("refresh", "H", 0),
  162. ("retry", "H", 0),
  163. ("expire", "H", 0),
  164. ("minttl", "H", 0)
  165. )
  166. name_s = pypacker.get_property_dnsname("name")
  167. mailbox_s = pypacker.get_property_dnsname("mailbox")
  168. def _dissect(self, buf):
  169. # set format
  170. # find server name by 0-termination
  171. idx = buf.find(b"\x00", 12)
  172. # logger.debug(buf[12: idx+1])
  173. # don't add trailing \0
  174. self.name = buf[12: idx + 1]
  175. # logger.debug("name: %s" % buf[idx + 1: -14])
  176. self.mailbox = buf[idx + 1: -14]
  177. return len(buf)
  178. class AddRecord(pypacker.Packet):
  179. """DNS additional records."""
  180. __hdr__ = (
  181. ("name", "H", 0),
  182. ("type", "H", 0x0001),
  183. ("clz", "H", 0x0001),
  184. ("ts", "I", 0),
  185. ("dlen", "H", 0),
  186. ("addr", None, b"\x01\x02\x03\x04")
  187. )
  188. def _dissect(self, buf):
  189. # logger.debug(buf[0: idx+1])
  190. self.addr = buf[12:]
  191. # logger.debug("addr: %s" % self.addr)
  192. return len(buf)
  193. class AddRecordRoot(pypacker.Packet):
  194. """DNS additional records."""
  195. __hdr__ = (
  196. ("name", "B", 0),
  197. ("type", "H", 0x0001),
  198. ("udpsize", "H", 0x0001),
  199. ("rcode", "B", 0),
  200. ("v", "B", 0),
  201. ("z", "H", 0),
  202. ("dlen", "H", 0)
  203. )
  204. def _dissect(self, buf):
  205. # unpack basic data to get things done
  206. quests_amount, ans_amount, authserver_amount, addreq_amount = unpack(">HHHH", buf[4:12])
  207. off = 12
  208. # TODO: use lazy dissect, dns seems to be too shitty for this
  209. #
  210. # parse queries
  211. #
  212. #logger.debug(">>> parsing questions: %d" % quests_amount)
  213. while quests_amount > 0:
  214. # find name by 0-termination
  215. idx = buf.find(b"\x00", off)
  216. q_end = idx + 5
  217. #logger.debug("name is: %s" % buf[off: q_end-4])
  218. #logger.debug("Query is: %s" % buf[off: q_end])
  219. #logger.debug(len(buf[off: q_end]))
  220. q = DNS.Query(buf[off: q_end])
  221. # logger.debug("query is following..")
  222. #logger.debug("Query: %s" % q)
  223. # logger.debug("query name format: %s" % q._name_format)
  224. self.queries.append(q)
  225. off = q_end
  226. quests_amount -= 1
  227. #
  228. # parse answers
  229. #
  230. #logger.debug(">>> parsing answers: %d" % ans_amount)
  231. while ans_amount > 0:
  232. # find name by label/0-termination
  233. # TODO: handle non-label names
  234. dlen = unpack(">H", buf[off + 10: off + 12])[0]
  235. index_end = 12 + dlen
  236. # logger.debug("Answer is: %r" % buf[off: off + index_end])
  237. a = DNS.Answer(buf[off: off + index_end])
  238. # logger.debug("Answer: %s" % a)
  239. self.answers.append(a)
  240. off += index_end
  241. ans_amount -= 1
  242. #
  243. # parse authorative servers
  244. #
  245. #logger.debug(">>> parsing authorative servers: %d" % authserver_amount)
  246. while authserver_amount > 0:
  247. dlen = unpack(">H", buf[off + 10: off + 12])[0]
  248. authlen = 12 + dlen
  249. # logger.debug("Auth: %r" % buf[off: off + authlen])
  250. a = DNS.Auth(buf[off: off + authlen])
  251. # logger.debug("Auth server: %s" % a)
  252. self.auths.append(a)
  253. off += authlen
  254. authserver_amount -= 1
  255. #
  256. # parse additional requests
  257. #
  258. #logger.debug(">>> parsing additional records: %d" % addreq_amount)
  259. while addreq_amount > 0:
  260. if buf[off: off + 3] == b"\x00\x00\x29":
  261. a = DNS.AddRecordRoot(buf[off: off + 11])
  262. #logger.debug(a)
  263. #logger.debug(a.bin())
  264. #logger.debug(len(a.bin()))
  265. off += 11
  266. else:
  267. # logger.debug(buf[idx:])
  268. # logger.debug(buf[off:])
  269. # logger.debug("data length via: %r" % buf[idx + 9: idx + 11])
  270. dlen = unpack(">H", buf[off + 10: off + 10 + 2])[0]
  271. # logger.debug("AddRecord: %s" % buf[off: off + 12 + dlen])
  272. a = DNS.AddRecord(buf[off: off + 12 + dlen])
  273. # logger.debug("Additional Record: %s" % a)
  274. off += 12 + dlen
  275. self.addrecords.append(a)
  276. addreq_amount -= 1
  277. # logger.debug("dns: %s" % self)
  278. return off
  279. def bin(self, update_auto_fields=True):
  280. if update_auto_fields and self._header_changed:
  281. # logger.debug("updating lenghts")
  282. # avoid lazy dissect by checking for [b"bytes", dissect_callback]
  283. # first assigning to length will trigger _unpack(...)
  284. if self._queries.__class__ is not list:
  285. self.questions_amount = len(self.queries)
  286. if self._answers.__class__ is not list:
  287. self.answers_amount = len(self.answers)
  288. if self._auths.__class__ is not list:
  289. self.authrr_amount = len(self.auths)
  290. if self._addrecords.__class__ is not list:
  291. self.addrr_amount = len(self.addrecords)
  292. # logger.debug("finished updating lengths")
  293. return pypacker.Packet.bin(self, update_auto_fields=update_auto_fields)