""" A DNS resource record This class contains classes for DNS resource records and record data. This module is fully implemented. You will have this module in the implementation of your resolver and server. """ import socket import struct from dns.types import Type class ResourceRecord(object): """ DNS resource record """ def __init__(self, name, type_, class_, ttl, rdata): """ Create a new resource record Args: name (str): domain name type_ (Type): the type class_ (Class): the class rdata (RecordData): the record data """ self.name = name self.type_ = type_ self.class_ = class_ self.ttl = ttl self.rdata = rdata def to_bytes(self, offset, composer): """ Convert ResourceRecord to bytes """ record = composer.to_bytes(offset, [self.name]) record += struct.pack("!HHI", self.type_, self.class_, self.ttl) offset += len(record) + 2 rdata = self.rdata.to_bytes(offset, composer) record += struct.pack("!H", len(rdata)) + rdata return record @classmethod def from_bytes(cls, packet, offset, parser): """ Convert ResourceRecord from bytes """ names, offset = parser.from_bytes(packet, offset, 1) name = names[0] type_, class_, ttl, rdlen = struct.unpack_from("!HHIH", packet, offset) offset += 10 rdata = RecordData.from_bytes(type_, packet, offset, rdlen, parser) offset += rdlen return cls(name, type_, class_, ttl, rdata), offset class RecordData(object): """ Record Data """ def __init__(self, data): """ Initialize the record data Args: data (str): data """ self.data = data @staticmethod def create(type_, data): """ Create a RecordData object from bytes Args: type_ (Type): type packet (bytes): packet offset (int): offset in message rdlength (int): length of rdata parser (int): domain name parser """ classdict = { Type.A: ARecordData, Type.CNAME: CNAMERecordData, Type.NS: NSRecordData, Type.AAAA: AAAARecordData } if type_ in classdict: return classdict[type_](data) else: return GenericRecordData(data) @staticmethod def from_bytes(type_, packet, offset, rdlength, parser): """ Create a RecordData object from bytes Args: type_ (Type): type packet (bytes): packet offset (int): offset in message rdlength (int): length of rdata parser (int): domain name parser """ classdict = { Type.A: ARecordData, Type.CNAME: CNAMERecordData, Type.NS: NSRecordData, Type.AAAA: AAAARecordData } if type_ in classdict: return classdict[type_].from_bytes( packet, offset, rdlength, parser) else: return GenericRecordData.from_bytes( packet, offset, rdlength, parser) class ARecordData(RecordData): """Data of an A record""" def to_bytes(self, offset, composer): """ Convert to bytes Args: offset (int): offset in message composer (Composer): domain name composer """ return socket.inet_aton(self.data) @classmethod def from_bytes(cls, packet, offset, rdlength, parser): """ Create a RecordData object from bytes Args: packet (bytes): packet offset (int): offset in message rdlength (int): length of rdata parser (int): domain name parser """ data = socket.inet_ntoa(packet[offset:offset+4]) return cls(data) class CNAMERecordData(RecordData): """Data of a CNAME record""" def to_bytes(self, offset, composer): """ Convert to bytes Args: offset (int): offset in message composer (Composer): domain name composer """ return composer.to_bytes(offset, [self.data]) @classmethod def from_bytes(cls, packet, offset, rdlength, parser): """ Create a RecordData object from bytes Args: packet (bytes): packet offset (int): offset in message rdlength (int): length of rdata parser (int): domain name parser """ names, offset = parser.from_bytes(packet, offset, 1) data = names[0] return cls(data) class NSRecordData(RecordData): """Data of an NS record""" def to_bytes(self, offset, composer): """ Convert to bytes Args: offset (int): offset in message composer (Composer): domain name composer """ return composer.to_bytes(offset, [self.data]) @classmethod def from_bytes(cls, packet, offset, rdlength, parser): """ Create a RecordData object from bytes Args: packet (bytes): packet offset (int): offset in message rdlength (int): length of rdata parser (int): domain name parser """ names, offset = parser.from_bytes(packet, offset, 1) data = names[0] return cls(data) class AAAARecordData(RecordData): """Data of an AAAA record""" def to_bytes(self, offset, composer): """ Convert to bytes Args: offset (int): offset in message composer (Composer): domain name composer """ return socket.inet_pton(socket.AF_INET6, self.data) @classmethod def from_bytes(cls, packet, offset, rdlength, parser): """ Create a RecordData object from bytes Args: packet (bytes): packet offset (int): offset in message rdlength (int): length of rdata parser (int): domain name parser """ data = socket.inet_ntop(socket.AF_INET6, packet[offset:offset+16]) return cls(data) class GenericRecordData(RecordData): """Data of a generic record""" def to_bytes(self, offset, composer): """ Convert to bytes Args: offset (int): offset in message composer (Composer): domain name composer """ return self.data @classmethod def from_bytes(cls, packet, offset, rdlength, parser): """ Create a RecordData object from bytes Args: packet (bytes): packet offset (int): offset in message rdlength (int): length of rdata parser (int): domain name parser """ data = packet[offset:offset+rdlength] return cls(data)