"""Domain Name System.""" from pypacker import pypacker, triggerlist TriggerList = triggerlist.TriggerList import struct import logging unpack = struct.unpack logger = logging.getLogger("pypacker") DNS_Q = 0 DNS_R = 1 # Opcodes DNS_QUERY = 0 DNS_IQUERY = 1 DNS_STATUS = 2 DNS_NOTIFY = 4 DNS_UPDATE = 5 # Flags DNS_AN = 0x8000 # this is a response DNS_CD = 0x0010 # checking disabled DNS_AD = 0x0020 # authenticated data DNS_Z = 0x0040 # unused DNS_RA = 0x0080 # recursion available DNS_RD = 0x0100 # recursion desired DNS_TC = 0x0200 # truncated DNS_AA = 0x0400 # authoritative answer # Response codes DNS_RCODE_NOERR = 0 DNS_RCODE_FORMERR = 1 DNS_RCODE_SERVFAIL = 2 DNS_RCODE_NXDOMAIN = 3 DNS_RCODE_NOTIMP = 4 DNS_RCODE_REFUSED = 5 DNS_RCODE_YXDOMAIN = 6 DNS_RCODE_YXRRSET = 7 DNS_RCODE_NXRRSET = 8 DNS_RCODE_NOTAUTH = 9 DNS_RCODE_NOTZONE = 10 # RR types DNS_A = 1 DNS_NS = 2 DNS_CNAME = 5 DNS_SOA = 6 DNS_WKS = 11 DNS_PTR = 12 DNS_HINFO = 13 DNS_MINFO = 14 DNS_MX = 15 DNS_TXT = 16 DNS_RP = 17 DNS_SIG = 24 DNS_GPOS = 27 DNS_AAAA = 28 DNS_LOC = 29 DNS_SRV = 33 DNS_NAPTR = 35 DNS_KX = 36 DNS_CERT = 37 DNS_DNAME = 39 DNS_DS = 43 DNS_SSHFP = 44 DNS_IPSECKEY = 45 DNS_RRSIG = 46 DNS_NSEC = 47 DNS_DNSKEY = 48 DNS_DHCID = 49 DNS_NSEC3 = 50 DNS_NSEC3PARAM = 51 DNS_TLSA = 52 DNS_SPF = 99 DNS_TKEY = 249 DNS_TSIG = 250 DNS_IXFR = 251 DNS_AXFR = 252 DNS_CAA = 257 DNS_TA = 32768 DNS_DLV = 32769 # RR classes DNS_IN = 1 DNS_CHAOS = 3 DNS_HESIOD = 4 DNS_ANY = 255 class DNS(pypacker.Packet): __hdr__ = ( ("id", "H", 0x1234), ("flags", "H", DNS_AD | DNS_RD), ("questions_amount", "H", 0), ("answers_amount", "H", 0), ("authrr_amount", "H", 0), ("addrr_amount", "H", 0), ("queries", None, TriggerList), ("answers", None, TriggerList), ("auths", None, TriggerList), ("addrecords", None, TriggerList) ) class Query(pypacker.Packet): """DNS question.""" __hdr__ = ( ("name", None, b"\x03www\x04test\x03com\x00"), ("type", "H", DNS_A), ("cls", "H", DNS_IN) ) name_s = pypacker.get_property_dnsname("name") def _dissect(self, buf): idx = buf.find(b"\x00") #logger.debug("name in Query: %s" % buf[:idx+1]) self.name = buf[:idx + 1] #logger.debug("val / format: %s %s" % (self._name, self._name_format)) return len(buf) # name (including 0) + type + cls class Answer(pypacker.Packet): """DNS resource record.""" __hdr__ = ( ("name", "H", 0xc00c), ("type", "H", DNS_A), ("cls", "H", DNS_IN), ("ttl", "I", 180), ("dlen", "H", 4), # length of the next field ("address", None, b"1234") # eg IPv4 ) def _dissect(self, buf): # needed set format addr_len = unpack(">H", buf[10:12])[0] self.address = buf[12:12 + addr_len] # logger.debug("address: %s" % self.address) return 12 + addr_len class Auth(pypacker.Packet): """Auth, generic type.""" __hdr__ = ( ("name", "H", 0), ("type", "H", 0), ("cls", "H", 0), ("ttl", "I", 0), ("dlen", "H", 0), # length of the rest of header: server + x, x becmoes body content ("server", None, b"\x03www\x04test\x03com\x00") # TODO: add fields for mailbox, serial, refresh etc. ) server_s = pypacker.get_property_dnsname("server") def _dissect(self, buf): # needed set format # find server name by 0-termination idx = buf.find(b"\x00", 12) if idx == -1: idx = len(buf) self.server = buf[12: idx + 1] # logger.debug("server: %s" % self.server) return idx + 1 class AuthSOA(pypacker.Packet): """ Auth type SOA. Not used atm """ __hdr__ = ( ("name", "H", 0), ("type", "H", 0), ("cls", "H", 0), ("ttl", "I", 0), ("dlen", "H", 0), ("name2", None, b"\x03www\x04test\x03com\x00"), ("mailbox", None, b"\x03www\x04test\x03com\x00"), ("pserver", "H", 0), ("mbox", "H", 0), ("serial", "H", 0), ("refresh", "H", 0), ("retry", "H", 0), ("expire", "H", 0), ("minttl", "H", 0) ) name_s = pypacker.get_property_dnsname("name") mailbox_s = pypacker.get_property_dnsname("mailbox") def _dissect(self, buf): # set format # find server name by 0-termination idx = buf.find(b"\x00", 12) # logger.debug(buf[12: idx+1]) # don't add trailing \0 self.name = buf[12: idx + 1] # logger.debug("name: %s" % buf[idx + 1: -14]) self.mailbox = buf[idx + 1: -14] return len(buf) class AddRecord(pypacker.Packet): """DNS additional records.""" __hdr__ = ( ("name", "H", 0), ("type", "H", 0x0001), ("clz", "H", 0x0001), ("ts", "I", 0), ("dlen", "H", 0), ("addr", None, b"\x01\x02\x03\x04") ) def _dissect(self, buf): # logger.debug(buf[0: idx+1]) self.addr = buf[12:] # logger.debug("addr: %s" % self.addr) return len(buf) class AddRecordRoot(pypacker.Packet): """DNS additional records.""" __hdr__ = ( ("name", "B", 0), ("type", "H", 0x0001), ("udpsize", "H", 0x0001), ("rcode", "B", 0), ("v", "B", 0), ("z", "H", 0), ("dlen", "H", 0) ) def _dissect(self, buf): # unpack basic data to get things done quests_amount, ans_amount, authserver_amount, addreq_amount = unpack(">HHHH", buf[4:12]) off = 12 # TODO: use lazy dissect, dns seems to be too shitty for this # # parse queries # #logger.debug(">>> parsing questions: %d" % quests_amount) while quests_amount > 0: # find name by 0-termination idx = buf.find(b"\x00", off) q_end = idx + 5 #logger.debug("name is: %s" % buf[off: q_end-4]) #logger.debug("Query is: %s" % buf[off: q_end]) #logger.debug(len(buf[off: q_end])) q = DNS.Query(buf[off: q_end]) # logger.debug("query is following..") #logger.debug("Query: %s" % q) # logger.debug("query name format: %s" % q._name_format) self.queries.append(q) off = q_end quests_amount -= 1 # # parse answers # #logger.debug(">>> parsing answers: %d" % ans_amount) while ans_amount > 0: # find name by label/0-termination # TODO: handle non-label names dlen = unpack(">H", buf[off + 10: off + 12])[0] index_end = 12 + dlen # logger.debug("Answer is: %r" % buf[off: off + index_end]) a = DNS.Answer(buf[off: off + index_end]) # logger.debug("Answer: %s" % a) self.answers.append(a) off += index_end ans_amount -= 1 # # parse authorative servers # #logger.debug(">>> parsing authorative servers: %d" % authserver_amount) while authserver_amount > 0: dlen = unpack(">H", buf[off + 10: off + 12])[0] authlen = 12 + dlen # logger.debug("Auth: %r" % buf[off: off + authlen]) a = DNS.Auth(buf[off: off + authlen]) # logger.debug("Auth server: %s" % a) self.auths.append(a) off += authlen authserver_amount -= 1 # # parse additional requests # #logger.debug(">>> parsing additional records: %d" % addreq_amount) while addreq_amount > 0: if buf[off: off + 3] == b"\x00\x00\x29": a = DNS.AddRecordRoot(buf[off: off + 11]) #logger.debug(a) #logger.debug(a.bin()) #logger.debug(len(a.bin())) off += 11 else: # logger.debug(buf[idx:]) # logger.debug(buf[off:]) # logger.debug("data length via: %r" % buf[idx + 9: idx + 11]) dlen = unpack(">H", buf[off + 10: off + 10 + 2])[0] # logger.debug("AddRecord: %s" % buf[off: off + 12 + dlen]) a = DNS.AddRecord(buf[off: off + 12 + dlen]) # logger.debug("Additional Record: %s" % a) off += 12 + dlen self.addrecords.append(a) addreq_amount -= 1 # logger.debug("dns: %s" % self) return off def bin(self, update_auto_fields=True): if update_auto_fields and self._header_changed: # logger.debug("updating lenghts") # avoid lazy dissect by checking for [b"bytes", dissect_callback] # first assigning to length will trigger _unpack(...) if self._queries.__class__ is not list: self.questions_amount = len(self.queries) if self._answers.__class__ is not list: self.answers_amount = len(self.answers) if self._auths.__class__ is not list: self.authrr_amount = len(self.auths) if self._addrecords.__class__ is not list: self.addrr_amount = len(self.addrecords) # logger.debug("finished updating lengths") return pypacker.Packet.bin(self, update_auto_fields=update_auto_fields)