summaryrefslogtreecommitdiff
path: root/project2/proj2_s4498062/dns/resolver.py
blob: ffb7c16fef3ebac07167942cbe60fc8ade5fa2f1 (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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/env python2

""" DNS Resolver

This module contains a class for resolving hostnames. You will have to implement
things in this module. This resolver will be both used by the DNS client and the
DNS server, but with a different list of servers.
"""

from random import randint
import socket

from dns.classes import Class
from dns.types import Type

import dns.cache
from dns.message import Message, Question, Header
import dns.rcodes


class Resolver(object):
    """ DNS resolver """

    ROOT_SERVERS = [
        '198.41.0.4',
        '192.228.79.201',
        '192.33.4.12',
        '199.7.91.13',
        '192.203.230.10',
        '192.5.5.241',
        '192.112.36.4',
        '198.97.190.53',
        '192.36.148.17',
        '192.58.128.30',
        '193.0.14.129',
        '199.7.83.42',
        '202.12.27.33'
        ]

    def __init__(self, caching, ttl, timeout=3):
        """ Initialize the resolver

        Args:
            caching (bool): caching is enabled if True
            ttl (int): ttl of cache entries (if > 0)
        """
        self.caching = caching
        self.ttl = ttl
        self.timeout = timeout

    def do_query(self, hint, hostname, type_, class_=Class.IN):
        """Do a query to a hint"""
        ident = randint(0, 65535)
        header = Header(ident, 0, 1, 0, 0, 0)
        header.qr = 0
        header.opcode = 0
        header.rd = 1
        req = Message(header, [Question(hostname, type_, class_)])

        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(self.timeout)
        sock.sendto(req.to_bytes(), (hint, 53))

        try:
            data = sock.recv(512)
            resp = Message.from_bytes(data)
            if resp.header.ident == ident:
                return resp
        except socket.timeout:
            pass
        return None

    def do_query_to_multiple(self, hints, hostname, type_, class_=Class.IN):
        """Do a query to multiple hints, return the remaining hints"""
        while hints != []:
            hint = hints.pop()
            response = self.do_query(hint, hostname, type_, class_)
            if response is not None:
                return hints, response
        return [], None

    def gethostbyname(self, hostname):
        """ Translate a host name to IPv4 address.

        Currently this method contains an example. You will have to replace
        this example with example with the algorithm described in section
        5.3.3 in RFC 1034.

        Args:
            hostname (str): the hostname to resolve

        Returns:
            (str, [str], [str]): (hostname, aliaslist, ipaddrlist)
        """
        domains = hostname.split('.')
        hints = self.ROOT_SERVERS

        if domains == []:
            return hostname, [], []

        domain = domains.pop(-1)
        aliases = []
        while hints != []:
            hints, resp = self.do_query_to_multiple(hints, domain, Type.A)
            if resp == None:
                continue

            info = resp.answers + resp.authorities + resp.additionals

            aliases += [
                r.rdata.data for r in info
                if r.match(type_=Type.CNAME, class_=Class.IN, name=domain)]

            # Case 1: answer
            ips = [
                r.rdata.data for r in info
                if r.match(type_=Type.A, class_=Class.IN, name=domain)]
            if ips != []:
                return hostname, aliases, ips
            # Case 2: name servers
            auths = [
                r.rdata.data for r in info
                if r.match(type_=Type.NS, class_=Class.IN, name=domain)]
            ips = [
                add.rdata.data for ns in auths for add in info
                if add.match(name=ns, type_=Type.A)]
            if ips != []:
                hints += ips
                if domain != hostname:
                    domain = domains.pop(-1) + '.' + domain
                continue
            if auths != []:
                auths = [h for a in auths for h in self.gethostbyname(a)[2]]
                hints += auths
                if domain != hostname:
                    domain = domains.pop(-1) + '.' + domain
                continue
            # Case 3: delegation to other name servers
            parent = '.'.join(domain.split('.')[1:])
            refs = [
                r.rdata.data for r in info
                if r.match(type_=Type.NS, class_=Class.IN, name=parent)]
            ips = [
                add.rdata.data for ns in refs for add in info
                if add.match(name=ns, type_=Type.A)]
            if ips != []:
                hints += ips
                continue
            if refs != []:
                refs = [h for r in refs for h in self.gethostbyname(r)[2]]
                hints += refs
                continue
            # Case 4: aliases
            for alias in aliases:
                _, extra_aliases, alias_addresses = self.gethostbyname(alias)
                if alias_addresses != []:
                    return hostname, aliases + extra_aliases, alias_addresses

        return hostname, aliases, []