Exemple #1
0
    def _handle_authoritative_response(
            self, dns_response: DNSMessage, requested_domain_name: str,
            requested_type: int) -> List[ResourceRecord]:
        """
        If an authoritative response is received from the domain name server, then first look for any matching
        answer records. If there is one or more answer records, return them.

        If there are no such records, then find a cname record and resolve the domain using its cname value.

        :param dns_response: the most recently received dns response in the name resolution process.
        :param requested_domain_name: the domain name we wish to resolve.
        :param requested_type: the type of address we wish to resolve the domain name to.
        :raises: DNSNoMatchingResourceRecordError
        :return: a list of the answer records that match the desired domain name and type, if present.
        """

        answer_resource_records = dns_response.get_matching_answer_records(
            dns_response.answer_records, requested_domain_name, requested_type)
        cname_resource_records = dns_response.get_matching_answer_records(
            dns_response.answer_records, requested_domain_name, 5)

        if answer_resource_records:
            return answer_resource_records

        elif cname_resource_records:
            cname_domain_name: str = cname_resource_records[0].rdata
            return self.resolve_domain_name(cname_domain_name,
                                            self.starting_dns_server,
                                            requested_type)

        else:
            raise DNSNoMatchingResourceRecordError(
                "No matching resource record error: an authoritative response was returned for a desired domain name. However, the authoritative response did not contain any resource records that matched the desired type."
            )
Exemple #2
0
    def _handle_tracing_for_dns_response(self,
                                         dns_response: DNSMessage) -> None:
        """
        If the resolver is set to verbose, then print tracing information for the dns response.

        :param dns_response: the dns response we wish to provide tracing information of.
        :return: None.
        """
        if self.verbose:
            dns_response.print_dns_response()
    def test_create_domain_name_query(self):
        """
        Test case to create a DNS query to ask for a particular domain name from a dns server.
        """
        encoded_dns_message: BytesIO = DNSMessageUtilities.create_domain_name_query(
            'www.cs.ubc.ca', 12345, 15)

        encoded_dns_message.seek(
            0
        )  # need to start reading the dns message from the start of the BytesIO object
        dns_message = DNSMessage.decode_dns_message(encoded_dns_message)

        # Check that the query id is correct
        self.assertEqual(dns_message.query_id, 12345)

        # Check that flags are correct
        self.assertEqual(dns_message.is_response, False)
        self.assertEqual(dns_message.opcode, 0)
        self.assertEqual(dns_message.authoritative, False)
        self.assertEqual(dns_message.is_truncated, False)
        self.assertEqual(dns_message.recursion_desired, False)
        self.assertEqual(dns_message.recursion_available, False)
        self.assertEqual(dns_message.rcode, 0)

        # Check that record/question counts are correct
        self.assertEqual(dns_message.question_count, 1)
        self.assertEqual(dns_message.answer_count, 0)
        self.assertEqual(dns_message.nameserver_count, 0)
        self.assertEqual(dns_message.additional_count, 0)

        # Check that the question was properly decoded
        self.assertEqual(dns_message.dns_questions[0].name, 'www.cs.ubc.ca')
        self.assertEqual(dns_message.dns_questions[0].type, 15)
        self.assertEqual(dns_message.dns_questions[0].response_class, 1)
Exemple #4
0
    def _handle_non_authoritative_response(
            self, dns_response: DNSMessage, requested_domain_name: str,
            requested_type: int) -> List[ResourceRecord]:
        """
        Attempts to find a name server address to send the next name resolution request to.

        If the name server ip is included in the additional resource records, then use it for next dns query.

        If name server ip is not present, first resolve the name server domain name to an ipv4 address and send the
        next dns query there.

        :param dns_response: the most recently received dns response in the name resolution process.
        :param requested_domain_name: the domain name we wish to resolve.
        :param requested_type: the type of address we wish to resolve the domain name to.
        :return: a list of the answer records that match the desired domain name and type, if present.
        """
        name_server_ip: Optional[
            str] = dns_response.get_name_server_ip_address(
                dns_response.name_server_records,
                dns_response.additional_records)

        if name_server_ip:  # Response contains an address for one of the name servers, send packet to that server.
            return self.resolve_domain_name(requested_domain_name,
                                            name_server_ip, requested_type)

        else:
            # Name server ip address could not be found. Thus, resolve the name server domain name. When found,
            # use the resolved ip address to continue search for originally desired domain name.
            name_server_records: List[
                ResourceRecord] = self.resolve_domain_name(
                    dns_response.name_server_records[0].rdata,
                    self.starting_dns_server, 1)
            name_server_ip = name_server_records[0].rdata
            return self.resolve_domain_name(requested_domain_name,
                                            name_server_ip, requested_type)
    def test_read_resource_records(self):
        """
        Test case to decode three consecutive resource records
        """
        records: List[ResourceRecord] = DNSMessage._read_resource_records(
            BytesIO(self.three_consecutive_resource_records_encoded),
            BytesIO(self.three_consecutive_resource_records_encoded), 3)

        # Check that the first name server record was correctly decoded
        self.assertEqual(records[0].name, 'ca')
        self.assertEqual(records[0].type, 2)
        self.assertEqual(records[0].response_class, 1)
        self.assertEqual(records[0].ttl, 150873)
        self.assertEqual(records[0].rdlength, 14)
        self.assertEqual(records[0].rdata, 'x.ca-servers')

        # Check that the first name server record was correctly decoded
        self.assertEqual(records[1].name, 'ubc')
        self.assertEqual(records[1].type, 1)
        self.assertEqual(records[1].response_class, 2)
        self.assertEqual(records[1].ttl, 150873)
        self.assertEqual(records[1].rdlength, 4)
        self.assertEqual(records[1].rdata, '1.2.3.4')

        # Check that the first name server record was correctly decoded
        self.assertEqual(records[2].name, 'tsn')
        self.assertEqual(records[2].type, 5)
        self.assertEqual(records[2].response_class, 3)
        self.assertEqual(records[2].ttl, 150873)
        self.assertEqual(records[2].rdlength, 13)
        self.assertEqual(records[2].rdata, 'x.z-servers')
    def test_print_dns_response(self):
        """
        Test case for printing dns responses to stdout.
        """

        message: DNSMessage = DNSMessage(
            1, True, 1, True, True, True, True, 1, 1, 1, 2, 3,
            [DNSQuestion("question", 1, 1)],
            [ResourceRecord("record1", 10, 11, 12, 4, "1.2.3.4")], [
                ResourceRecord("record1", 10, 11, 12, 4, "1.2.3.4"),
                ResourceRecord("record2", 15, 16, 17, 4, "5.6.7.8")
            ], [
                ResourceRecord("record1", 10, 11, 12, 4, "1.2.3.4"),
                ResourceRecord("record2", 15, 16, 17, 4, "5.6.7.8"),
                ResourceRecord("record3", 18, 19, 20, 4, "9.10.11.12")
            ])

        buffer: StringIO = StringIO()

        with redirect_stdout(buffer):
            message.print_dns_response()

        self.assertEqual(
            buffer.getvalue(), "Response ID: 1 Authoritative = True\n"
            "  Answers (1)\n"
            "      record1                        12         10   1.2.3.4\n"
            "  Name Servers (2)\n"
            "      record1                        12         10   1.2.3.4\n"
            "      record2                        17         15   5.6.7.8\n"
            "  Additional Information (3)\n"
            "      record1                        12         10   1.2.3.4\n"
            "      record2                        17         15   5.6.7.8\n"
            "      record3                        20         18   9.10.11.12\n"
        )
    def test_get_rcode_value_from_flags_zero(self):
        """
        Test case to retrieve the rcode field when set to zero in the flags.
        """
        # flags = 0 has rcode = 0
        get_rcode_value_small: int = DNSMessage._get_rcode_value_from_flags(0)

        self.assertEqual(get_rcode_value_small, 0)
    def test_get_rcode_value_from_flags_large(self):
        """
        Test case to retrieve the rcode field when set to fifteen in the flags.
        """
        # flags = 15 has rcode = 15
        get_rcode_value_large: int = DNSMessage._get_rcode_value_from_flags(15)

        self.assertEqual(get_rcode_value_large, 15)
Exemple #9
0
    def _handle_tracing_for_dns_query(self, domain_name_query: BytesIO,
                                      next_dns_server_ip: str) -> None:
        """
        If the resolver is set to verbose, then print tracing information for the dns query.

        :param domain_name_query: the domain name query that is sent to the next_dns_server_ip.
        :param next_dns_server_ip: the dns server we send the domain name query to.
        :return: None
        """
        if self.verbose:
            print('')
            print('')
            domain_name_query.seek(
                0)  # need to ensure we are at the start of the writer
            # to be able to correctly decode into a DNS Question
            DNSMessage.decode_dns_message(domain_name_query).print_dns_query(
                next_dns_server_ip)
    def test_attempt_to_retrieve_all_flags(self):
        """
        Test case to retrieve all the flag values from a hypothetical flags value for a dns message.
        """

        get_response_value: bool = DNSMessage._get_response_value_from_flags(
            46218)
        get_opcode_value: int = DNSMessage._get_opcode_value_from_flags(46218)
        get_authoritative_value: bool = DNSMessage._get_authoritative_value_from_flags(
            46218)
        get_is_truncated_value: bool = DNSMessage._get_is_truncated_value_from_flags(
            46218)
        get_recursion_desired_value: bool = DNSMessage._get_recursion_desired_value_from_flags(
            46218)
        get_recursion_available_value: bool = DNSMessage._get_recursion_available_value_from_flags(
            46218)
        get_rcode_value: int = DNSMessage._get_rcode_value_from_flags(46218)

        self.assertEqual(get_response_value, True)
        self.assertEqual(get_opcode_value, 6)
        self.assertEqual(get_authoritative_value, True)
        self.assertEqual(get_is_truncated_value, False)
        self.assertEqual(get_recursion_desired_value, False)
        self.assertEqual(get_recursion_available_value, True)
        self.assertEqual(get_rcode_value, 10)
    def test_get_name_server_ip_address_no_name_match(self):
        """
        Test case where there is no match for any name server record.
        """

        name_server_ip: Optional[str] = DNSMessage.get_name_server_ip_address(
            self.name_server_records_list_2, self.additional_records)

        self.assertEqual(name_server_ip, None)
    def test_get_authoritative_value_from_flags_true(self):
        """
        Test case to retrieve the authoritative field when set to True in the flags.
        """
        # flags = 1024 has authoritative = True
        get_authoritative_value_true: bool = DNSMessage._get_authoritative_value_from_flags(
            1024)

        self.assertEqual(get_authoritative_value_true, True)
    def test_get_name_server_ip_address_no_additional_records(self):
        """
        Test case where no additional records are given.
        """

        name_server_ip: Optional[str] = DNSMessage.get_name_server_ip_address(
            self.name_server_records_list_1, [])

        self.assertEqual(name_server_ip, None)
    def test_get_matched_answer_records_type_mismatch(self):
        """
        Test case to match with a type that doesn't match any record with a given name.
        """

        records: List[ResourceRecord] = DNSMessage.get_matching_answer_records(
            self.resource_records, "name1", 5)

        self.assertEqual(len(records), 0)
    def test_get_matched_answer_records_single_match(self):
        """
        Test case to match with a single answer record that matches the domain name and type.
        """

        records: List[ResourceRecord] = DNSMessage.get_matching_answer_records(
            self.resource_records, "name1", 1)

        self.assertEqual(records, [self.resource_record_1])
    def test_get_matched_answer_records_no_records(self):
        """
        Test case to match on an empty list of records.
        """

        records: List[ResourceRecord] = DNSMessage.get_matching_answer_records(
            [], "", 1)

        self.assertEqual(len(records), 0)
    def test_get_matched_answer_records_domain_name_mismatch(self):
        """
        Test case to match with a domain name that isn't the name of any record.
        """

        records: List[ResourceRecord] = DNSMessage.get_matching_answer_records(
            self.resource_records, "no-match", 1)

        self.assertEqual(len(records), 0)
    def test_get_opcode_value_from_flags_large(self):
        """
        Test case to retrieve the opcode field when set to a large value in the flags.
        """
        # flags = 30720 has opcode = 15
        get_opcode_value_large: int = DNSMessage._get_opcode_value_from_flags(
            30720)

        self.assertEqual(get_opcode_value_large, 15)
    def test_get_authoritative_value_from_flags_false(self):
        """
        Test case to retrieve the authoritative field when set to False in the flags.
        """
        # flags = 0 has authoritative = False
        get_authoritative_value_false: bool = DNSMessage._get_authoritative_value_from_flags(
            0)

        self.assertEqual(get_authoritative_value_false, False)
    def test_get_name_server_ip_address_only_one_match(self):
        """
        Test case for where there is only one possible match for any of the name server records.
        """

        name_server_ip: Optional[str] = DNSMessage.get_name_server_ip_address(
            self.name_server_records_list_1, self.additional_records)

        self.assertEqual(name_server_ip, "1.2.3.4")
    def test_read_dns_questions_no_questions(self):
        """
        Test case to attempt to decode 0 questions. In other words, decode nothing and return an empty list.
        """
        dns_questions: List[DNSQuestion] = DNSMessage._read_dns_questions(
            BytesIO(self.three_consecutive_dns_questions_encoded),
            BytesIO(self.three_consecutive_dns_questions_encoded), 0)

        self.assertEqual(len(dns_questions), 0)
    def test_get_is_truncated_value_from_flags_true(self):
        """
        Test case to retrieve the is_truncated field when set to True in the flags.
        """
        # flags = 512 has is_truncated = True
        get_is_truncated_value_true: bool = DNSMessage._get_is_truncated_value_from_flags(
            512)

        self.assertEqual(get_is_truncated_value_true, True)
    def test_get_is_truncated_value_from_flags_false(self):
        """
        Test case to retrieve the is_truncated field when set to False in the flags.
        """
        # flags = 0 has is_truncated = False
        get_is_truncated_value_false: bool = DNSMessage._get_is_truncated_value_from_flags(
            0)

        self.assertEqual(get_is_truncated_value_false, False)
    def test_get_recursion_available_value_from_flags_false(self):
        """
        Test case to retrieve the recursion_available field when set to false in the flags.
        """
        # flags = 0 has recursion_available = False
        get_recursion_available_value_false: bool = DNSMessage._get_recursion_available_value_from_flags(
            0)

        self.assertEqual(get_recursion_available_value_false, False)
    def test_get_recursion_available_value_from_flags_true(self):
        """
        Test case to retrieve the recursion_available field when set to True in the flags.
        """
        # flags = 128 has recursion_available = True
        get_recursion_available_value_true: bool = DNSMessage._get_recursion_available_value_from_flags(
            128)

        self.assertEqual(get_recursion_available_value_true, True)
    def test_get_recursion_desired_value_from_flags_true(self):
        """
        Test case to retrieve the recursion_desired field when set to True in the flags.
        """
        # flags = 256 has recursion_desired = True
        get_recursion_desired_value_true: bool = DNSMessage._get_recursion_desired_value_from_flags(
            256)

        self.assertEqual(get_recursion_desired_value_true, True)
    def test_read_resource_questions_no_records(self):
        """
        Test case to attempt to decode 0 resource records. In other words, don't decode anything and return an empty list.
        """
        records: List[ResourceRecord] = DNSMessage._read_resource_records(
            BytesIO(self.three_consecutive_resource_records_encoded),
            BytesIO(self.three_consecutive_resource_records_encoded), 0)

        self.assertEqual(len(records), 0)
    def test_get_response_value_from_flags_true(self):
        """
        Test case to retrieve the get_response field when set to True in the flags.
        """
        # flags = 32768 has get_response flag set
        get_response_value_true: bool = DNSMessage._get_response_value_from_flags(
            32768)

        self.assertEqual(get_response_value_true, True)
    def test_get_matched_answer_records_multiple_matches(self):
        """
        Test case to match with two answer records that matches the domain name and type.
        """

        records: List[ResourceRecord] = DNSMessage.get_matching_answer_records(
            self.resource_records, "name2", 5)

        self.assertEqual(records,
                         [self.resource_record_2, self.resource_record_4])
    def test_get_name_server_ip_address_multiple_matches(self):
        """
        Test case to retrieve the ip address of a name server for which there is a match of the correct type
        and there is only one match in the additional records.
        """

        name_server_ip: Optional[str] = DNSMessage.get_name_server_ip_address(
            self.name_server_records_list_3, self.additional_records)

        self.assertEqual(name_server_ip, "1.2.3.4")