summaryrefslogtreecommitdiff
path: root/project2/proj2_s4498062/dns/domainname.py
blob: 81b5f4c8d9adb3f88c59beb079c2bea3c734052d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
""" Parsing and composing domain names

This module contains two classes for converting domain names to and from bytes.
You won't have to use these classes. They're used internally in Message,
Question, ResourceRecord and RecordData. You can read section 4.1.4 of RFC 1035
if you want more info.
"""

import struct


class Composer(object):
    """ Converts a string representation of a domain name to bytes """

    def __init__(self):
        self.offsets = dict()

    def to_bytes(self, offset, dnames):
        """Convert each domain name in to bytes"""
        result = b""
        for dname in dnames:
            # Split domain name into labels
            labels = dname.split(".")

            # Determine keys of subdomains in offset dict
            keys = []
            for label in reversed(labels):
                name = label
                if keys:
                    name += "." + keys[-1]
                keys.append(name)
            keys.reverse()

            # Convert label to bytes
            add_null = True
            for j, label in enumerate(labels):
                if keys[j] in self.offsets:
                    offset = self.offsets[keys[j]]
                    pointer = (3 << 14) + offset
                    result += struct.pack("!H", pointer)
                    add_null = False
                    offset += 2
                    break
                else:
                    self.offsets[keys[j]] = offset
                    result += struct.pack("!B{}s".format(len(label)),
                                          len(label),
                                          label)
                    offset += 1 + len(label)

            # Add null character at end
            if add_null:
                result += b"\x00"
                offset += 1

        return result


class Parser(object):
    """ Convert byte representations of domain names to strings """

    def __init__(self):
        self.labels = dict()

    def from_bytes(self, packet, offset, num):
        """ Convert domain name from bytes to string

        Args:
            packet (bytes): packet containing the domain name
            offset (int): offset of domain name in packet
            num (int): number of domain names to decode

        Returns:
            str, int
        """

        dnames = []

        # Read the domain names
        for _ in range(num):
            # Read a new domain name
            dname = ""
            prev_offsets = []
            done = False
            while done is False:
                # Read length of next label
                llength = struct.unpack_from("!B", packet, offset)[0]

                # Done reading domain when length is zero
                if llength == 0:
                    offset += 1
                    break

                # Compression label
                elif (llength >> 6) == 3:
                    new_offset = offset + 2
                    target = struct.unpack_from("!H", packet, offset)[0]
                    target -= 3 << 14
                    label = self.labels[target]
                    done = True

                # Normal label
                else:
                    new_offset = offset + llength + 1
                    label = struct.unpack_from("{}s".format(llength),
                                               packet, offset+1)[0]

                # Add label to dictionary
                self.labels[offset] = label
                for prev_offset in prev_offsets:
                    self.labels[prev_offset] += "." + label
                prev_offsets.append(offset)

                # Update offset
                offset = new_offset

                # Append label to domain name
                if len(dname) > 0:
                    dname += "."
                dname += label

            # Append domain name to list
            dnames.append(dname)

        return dnames, offset