class SRV(RD): priority = H('priority') weight = H('weight') port = H('port') @classmethod def parse(cls, buffer, length): try: priority, weight, port = buffer.unpack("!HHH") target = buffer.decode_name() return cls(priority, weight, port, target) except (BufferError, BimapError) as e: raise DNSError("Error unpacking SRV [offset=%d]: %s" % (buffer.offset, e)) @classmethod def fromZone(cls, rd, origin=None): return cls(int(rd[0]), int(rd[1]), int(rd[2]), rd[3]) def __init__(self, priority=0, weight=0, port=0, target=None): self.priority = priority self.weight = weight self.port = port self.target = target def set_target(self, target): if isinstance(target, DNSLabel): self._target = target else: self._target = DNSLabel(target) def get_target(self): return self._target target = property(get_target, set_target) def pack(self, buffer): buffer.pack("!HHH", self.priority, self.weight, self.port) buffer.encode_name(self.target) def __repr__(self): return "%d %d %d %s" % (self.priority, self.weight, self.port, self.target) attrs = ('priority', 'weight', 'port', 'target')
class MX(RD): preference = H('preference') @classmethod def parse(cls,buffer,length): try: (preference,) = buffer.unpack("!H") mx = buffer.decode_name() return cls(mx,preference) except (BufferError,BimapError) as e: raise DNSError("Error unpacking MX [offset=%d]: %s" % (buffer.offset,e)) @classmethod def fromZone(cls,rd,origin=None): return cls(label(rd[1],origin),int(rd[0])) def __init__(self,label=None,preference=10): self.label = label self.preference = preference def set_label(self,label): if isinstance(label,DNSLabel): self._label = label else: self._label = DNSLabel(label) def get_label(self): return self._label label = property(get_label,set_label) def pack(self,buffer): buffer.pack("!H",self.preference) buffer.encode_name(self.label) def __repr__(self): return "%d %s" % (self.preference,self.label) attrs = ('preference','label')
class DNSKEY(RD): flags = H('flags') protocol = B('protocol') algorithm = B('algorithm') @classmethod def parse(cls,buffer,length): try: (flags,protocol,algorithm) = buffer.unpack("!HBB") key = buffer.get(length - 4) return cls(flags,protocol,algorithm,key) except (BufferError,BimapError) as e: raise DNSError("Error unpacking DNSKEY [offset=%d]: %s" % (buffer.offset,e)) @classmethod def fromZone(cls,rd,origin=None): return cls(int(rd[0]),int(rd[1]),int(rd[2]), base64.b64decode(("".join(rd[3:])).encode('ascii'))) def __init__(self,flags,protocol,algorithm,key): self.flags = flags self.protocol = protocol self.algorithm = algorithm self.key = key def pack(self,buffer): buffer.pack("!HBB",self.flags,self.protocol,self.algorithm) buffer.append(self.key) def __repr__(self): return "%d %d %d %s" % (self.flags,self.protocol,self.algorithm, base64.b64encode(self.key).decode()) attrs = ('flags','protocol','algorithm','key')
class RR(object): """ DNS Resource Record Contains RR header and RD (resource data) instance """ rtype = H('rtype') rclass = H('rclass') ttl = I('ttl') rdlength = H('rdlength') @classmethod def parse(cls,buffer): try: rname = buffer.decode_name() rtype,rclass,ttl,rdlength = buffer.unpack("!HHIH") if rtype == QTYPE.OPT: options = [] option_buffer = Buffer(buffer.get(rdlength)) while option_buffer.remaining() > 4: code,length = option_buffer.unpack("!HH") data = option_buffer.get(length) options.append(EDNSOption(code,data)) rdata = options else: if rdlength: rdata = RDMAP.get(QTYPE.get(rtype),RD).parse( buffer,rdlength) else: rdata = '' return cls(rname,rtype,rclass,ttl,rdata) except (BufferError,BimapError) as e: raise DNSError("Error unpacking RR [offset=%d]: %s" % ( buffer.offset,e)) @classmethod def fromZone(cls,zone,origin="",ttl=0): """ Parse RR data from zone file and return list of RRs """ return list(ZoneParser(zone,origin=origin,ttl=ttl)) def __init__(self,rname=None,rtype=1,rclass=1,ttl=0,rdata=None): self.rname = rname self.rtype = rtype self.rclass = rclass self.ttl = ttl self.rdata = rdata # TODO Add property getters/setters if self.rtype == QTYPE.OPT: self.edns_len = self.rclass self.edns_do = get_bits(self.ttl,15) self.edns_ver = get_bits(self.ttl,16,8) self.edns_rcode = get_bits(self.ttl,24,8) def set_rname(self,rname): if isinstance(rname,DNSLabel): self._rname = rname else: self._rname = DNSLabel(rname) def get_rname(self): return self._rname rname = property(get_rname,set_rname) def pack(self,buffer): buffer.encode_name(self.rname) buffer.pack("!HHI",self.rtype,self.rclass,self.ttl) rdlength_ptr = buffer.offset buffer.pack("!H",0) start = buffer.offset if self.rtype == QTYPE.OPT: for opt in self.rdata: opt.pack(buffer) else: self.rdata.pack(buffer) end = buffer.offset buffer.update(rdlength_ptr,"!H",end-start) def __repr__(self): if self.rtype == QTYPE.OPT: s = ["<DNS OPT: edns_ver=%d do=%d ext_rcode=%d udp_len=%d>" % ( self.edns_ver,self.edns_do,self.edns_rcode,self.edns_len)] s.extend([repr(opt) for opt in self.rdata]) return "\n".join(s) else: return "<DNS RR: '%s' rtype=%s rclass=%s ttl=%d rdata='%s'>" % ( self.rname, QTYPE.get(self.rtype), CLASS.get(self.rclass), self.ttl, self.rdata) def toZone(self): if self.rtype == QTYPE.OPT: edns = [ ";OPT PSEUDOSECTION", ";EDNS: version: %d, flags: %s; udp: %d" % ( self.edns_ver, "do" if self.edns_do else "", self.edns_len) ] edns.extend([str(opt) for opt in self.rdata]) return "\n".join(edns) else: return '%-23s %-7s %-7s %-7s %s' % (self.rname,self.ttl, CLASS.get(self.rclass), QTYPE.get(self.rtype), self.rdata.toZone()) def __str__(self): return self.toZone() def __ne__(self,other): return not(self.__eq__(other)) def __eq__(self,other): if type(other) != type(self): return False else: # List of attributes to compare when diffing (ignore ttl) attrs = ('rname','rclass','rtype','rdata') return all([getattr(self,x) == getattr(other,x) for x in attrs])
class DNSHeader(object): """ DNSHeader section """ # Ensure attribute values match packet id = H('id') bitmap = H('bitmap') q = H('q') a = H('a') auth = H('auth') ar = H('ar') @classmethod def parse(cls,buffer): """ Implements parse interface """ try: (id,bitmap,q,a,auth,ar) = buffer.unpack("!HHHHHH") return cls(id,bitmap,q,a,auth,ar) except (BufferError,BimapError) as e: raise DNSError("Error unpacking DNSHeader [offset=%d]: %s" % ( buffer.offset,e)) def __init__(self,id=None,bitmap=None,q=0,a=0,auth=0,ar=0,**args): if id is None: self.id = random.randint(0,65535) else: self.id = id if bitmap is None: self.bitmap = 0 self.rd = 1 else: self.bitmap = bitmap self.q = q self.a = a self.auth = auth self.ar = ar for k,v in list(args.items()): if k.lower() == "qr": self.qr = v elif k.lower() == "opcode": self.opcode = v elif k.lower() == "aa": self.aa = v elif k.lower() == "tc": self.tc = v elif k.lower() == "rd": self.rd = v elif k.lower() == "ra": self.ra = v elif k.lower() == "rcode": self.rcode = v # Accessors for header properties (automatically pack/unpack # into bitmap) def get_qr(self): return get_bits(self.bitmap,15) def set_qr(self,val): self.bitmap = set_bits(self.bitmap,val,15) qr = property(get_qr,set_qr) def get_opcode(self): return get_bits(self.bitmap,11,4) def set_opcode(self,val): self.bitmap = set_bits(self.bitmap,val,11,4) opcode = property(get_opcode,set_opcode) def get_aa(self): return get_bits(self.bitmap,10) def set_aa(self,val): self.bitmap = set_bits(self.bitmap,val,10) aa = property(get_aa,set_aa) def get_tc(self): return get_bits(self.bitmap,9) def set_tc(self,val): self.bitmap = set_bits(self.bitmap,val,9) tc = property(get_tc,set_tc) def get_rd(self): return get_bits(self.bitmap,8) def set_rd(self,val): self.bitmap = set_bits(self.bitmap,val,8) rd = property(get_rd,set_rd) def get_ra(self): return get_bits(self.bitmap,7) def set_ra(self,val): self.bitmap = set_bits(self.bitmap,val,7) ra = property(get_ra,set_ra) def get_rcode(self): return get_bits(self.bitmap,0,4) def set_rcode(self,val): self.bitmap = set_bits(self.bitmap,val,0,4) rcode = property(get_rcode,set_rcode) def pack(self,buffer): buffer.pack("!HHHHHH",self.id,self.bitmap, self.q,self.a,self.auth,self.ar) def __repr__(self): f = [ self.aa and 'AA', self.tc and 'TC', self.rd and 'RD', self.ra and 'RA' ] if OPCODE.get(self.opcode) == 'UPDATE': f1='zo' f2='pr' f3='up' f4='ad' else: f1='q' f2='a' f3='ns' f4='ar' return "<DNS Header: id=0x%x type=%s opcode=%s flags=%s " \ "rcode='%s' %s=%d %s=%d %s=%d %s=%d>" % ( self.id, QR.get(self.qr), OPCODE.get(self.opcode), ",".join([_f for _f in f if _f]), RCODE.get(self.rcode), f1, self.q, f2, self.a, f3, self.auth, f4, self.ar ) def toZone(self): f = [ self.qr and 'qr', self.aa and 'aa', self.tc and 'tc', self.rd and 'rd', self.ra and 'ra' ] z1 = ';; ->>HEADER<<- opcode: %s, status: %s, id: %d' % ( OPCODE.get(self.opcode),RCODE.get(self.rcode),self.id) z2 = ';; flags: %s; QUERY: %d, ANSWER: %d, AUTHORITY: %d, ADDITIONAL: %d' % ( " ".join([_f for _f in f if _f]), self.q,self.a,self.auth,self.ar) return z1 + "\n" + z2 def __str__(self): return self.toZone() def __ne__(self,other): return not(self.__eq__(other)) def __eq__(self,other): if type(other) != type(self): return False else: # Ignore id attrs = ('qr','aa','tc','rd','ra','opcode','rcode') return all([getattr(self,x) == getattr(other,x) for x in attrs])
class RRSIG(RD): covered = H('covered') algorithm = B('algorithm') labels = B('labels') orig_ttl = I('orig_ttl') sig_exp = I('sig_exp') sig_inc = I('sig_inc') key_tag = H('key_tag') @classmethod def parse(cls,buffer,length): try: start = buffer.offset (covered,algorithm,labels, orig_ttl,sig_exp,sig_inc,key_tag) = buffer.unpack("!HBBIIIH") name = buffer.decode_name() sig = buffer.get(length - (buffer.offset - start)) return cls(covered,algorithm,labels,orig_ttl,sig_exp,sig_inc,key_tag, name,sig) except (BufferError,BimapError) as e: raise DNSError("Error unpacking DNSKEY [offset=%d]: %s" % (buffer.offset,e)) @classmethod def fromZone(cls,rd,origin=None): return cls(getattr(QTYPE,rd[0]),int(rd[1]),int(rd[2]),int(rd[3]), int(time.mktime(time.strptime(rd[4]+'GMT',"%Y%m%d%H%M%S%Z"))), int(time.mktime(time.strptime(rd[5]+'GMT',"%Y%m%d%H%M%S%Z"))), int(rd[6]),rd[7], base64.b64decode(("".join(rd[8:])).encode('ascii'))) def __init__(self,covered,algorithm,labels,orig_ttl, sig_exp,sig_inc,key_tag,name,sig): self.covered = covered self.algorithm = algorithm self.labels = labels self.orig_ttl = orig_ttl self.sig_exp = sig_exp self.sig_inc = sig_inc self.key_tag = key_tag self.name = DNSLabel(name) self.sig = sig def pack(self,buffer): buffer.pack("!HBBIIIH",self.covered,self.algorithm,self.labels, self.orig_ttl,self.sig_exp,self.sig_inc, self.key_tag) buffer.encode_name_nocompress(self.name) buffer.append(self.sig) def __repr__(self): return "%s %d %d %d %s %s %d %s %s" % ( QTYPE.get(self.covered), self.algorithm, self.labels, self.orig_ttl, time.strftime("%Y%m%d%H%M%S",time.gmtime(self.sig_exp)), time.strftime("%Y%m%d%H%M%S",time.gmtime(self.sig_inc)), self.key_tag, self.name, base64.b64encode(self.sig).decode()) attrs = ('covered','algorithm','labels','orig_ttl','sig_exp','sig_inc', 'key_tag','name','sig')
class NAPTR(RD): order = H('order') preference = H('preference') @classmethod def parse(cls, buffer, length): try: order, preference = buffer.unpack('!HH') (length,) = buffer.unpack('!B') flags = buffer.get(length) (length,) = buffer.unpack('!B') service = buffer.get(length) (length,) = buffer.unpack('!B') regexp = buffer.get(length) replacement = buffer.decode_name() return cls(order, preference, flags, service, regexp, replacement) except (BufferError,BimapError) as e: raise DNSError("Error unpacking NAPTR [offset=%d]: %s" % (buffer.offset,e)) @classmethod def fromZone(cls,rd,origin=None): encode = lambda s : s.encode() _label = lambda s : label(s,origin) m = (int,int,encode,encode,encode,_label) return cls(*[ f(v) for f,v in zip(m,rd)]) def __init__(self,order,preference,flags,service,regexp,replacement=None): self.order = order self.preference = preference self.flags = flags self.service = service self.regexp = regexp self.replacement = replacement def set_replacement(self,replacement): if isinstance(replacement,DNSLabel): self._replacement = replacement else: self._replacement = DNSLabel(replacement) def get_replacement(self): return self._replacement replacement = property(get_replacement,set_replacement) def pack(self, buffer): buffer.pack('!HH', self.order, self.preference) buffer.pack('!B', len(self.flags)) buffer.append(self.flags) buffer.pack('!B', len(self.service)) buffer.append(self.service) buffer.pack('!B', len(self.regexp)) buffer.append(self.regexp) buffer.encode_name(self.replacement) def __repr__(self): return '%d %d "%s" "%s" "%s" %s' %( self.order,self.preference,self.flags.decode(), self.service.decode(), self.regexp.decode().replace('\\','\\\\'), self.replacement or '.' ) attrs = ('order','preference','flags','service','regexp','replacement')