1732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo# Use of this source code is governed by a BSD-style license that can be
3732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo# found in the LICENSE file.
4732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
5cb9864e93dd06fd51555f96cf95df4dbf0ce02c4Christopher Wileyimport collections
6732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymoimport dpkt
7074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wileyimport logging
8732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymoimport socket
9e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymoimport time
10732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
11732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
12cb9864e93dd06fd51555f96cf95df4dbf0ce02c4Christopher WileyDnsRecord = collections.namedtuple('DnsResult', ['rrname', 'rrtype', 'data', 'ts'])
13cb9864e93dd06fd51555f96cf95df4dbf0ce02c4Christopher Wiley
14732dfe9c1124b51b906cb6510f219ad84be58836Alex DeymoMDNS_IP_ADDR = '224.0.0.251'
15732dfe9c1124b51b906cb6510f219ad84be58836Alex DeymoMDNS_PORT = 5353
16732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
17732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo# Value to | to a class value to signal cache flush.
18732dfe9c1124b51b906cb6510f219ad84be58836Alex DeymoDNS_CACHE_FLUSH = 0x8000
19732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
20074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley# When considering SRV records, clients are supposed to unilaterally prefer
21074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley# numerically lower priorities, then pick probabilistically by weight.
22074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley# See RFC2782.
23074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley# An arbitrary number that will fit in 16 bits.
24074f65c0504cfba2062b934269b96e82e08f8cd9Christopher WileyDEFAULT_PRIORITY = 500
25074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley# An arbitrary number that will fit in 16 bits.
26074f65c0504cfba2062b934269b96e82e08f8cd9Christopher WileyDEFAULT_WEIGHT = 500
27732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
28732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymodef _RR_equals(rra, rrb):
29732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    """Returns whether the two dpkt.dns.DNS.RR objects are equal."""
30732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    # Compare all the members present in either object and on any RR object.
31732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    keys = set(rra.__dict__.keys() + rrb.__dict__.keys() +
32732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo               dpkt.dns.DNS.RR.__slots__)
33732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    # On RR objects, rdata is packed based on the other members and the final
34732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    # packed string depends on other RR and Q elements on the same DNS/mDNS
35732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    # packet.
36732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    keys.discard('rdata')
37732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    for key in keys:
38732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if hasattr(rra, key) != hasattr(rrb, key):
39732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            return False
40732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if not hasattr(rra, key):
41732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            continue
42732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if key == 'cls':
43732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo          # cls attribute should be masked for the cache flush bit.
44732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo          if (getattr(rra, key) & ~DNS_CACHE_FLUSH !=
45732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                getattr(rrb, key) & ~DNS_CACHE_FLUSH):
46732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo              return False
47732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        else:
48732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo          if getattr(rra, key) != getattr(rrb, key):
49732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo              return False
50732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    return True
51732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
52732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
53732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymoclass ZeroconfDaemon(object):
54732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    """Implements a simulated Zeroconf daemon running on the given host.
55732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
56732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    This class implements part of the Multicast DNS RFC 6762 able to simulate
57732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    a host exposing services or consuming services over mDNS.
58732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    """
59732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def __init__(self, host, hostname, domain='local'):
60732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """Initializes the ZeroconfDameon running on the given host.
61732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
62732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        For the purposes of the Zeroconf implementation, a host must have a
63732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        hostname and a domain that defaults to 'local'. The ZeroconfDaemon will
64732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        by default advertise the host address it is running on, which is
65732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        required by some services.
66732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
67732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param host: The Host instance where this daemon runs on.
68732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param hostname: A string representing the hostname
69732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
70732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._host = host
71732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._hostname = hostname
72732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._domain = domain
73732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._response_ttl = 60 # Default TTL in seconds.
74732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
75732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._a_records = {} # Local A records.
76732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._srv_records = {} # Local SRV records.
77732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._ptr_records = {} # Local PTR records.
78732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._txt_records = {} # Local TXT records.
79732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
80e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        # dict() of name --> (dict() of type --> (dict() of data --> timeout))
81e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        # For example: _peer_records['somehost.local'][dpkt.dns.DNS_A] \
82e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        #     ['192.168.0.1'] = time.time() + 3600
83e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        self._peer_records = {}
84e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo
85732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        # Register the host address locally.
86732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self.register_A(self.full_hostname, host.ip_addr)
87732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
88732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        # Attend all the traffic to the mDNS port (unicast, multicast or
89732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        # broadcast).
90732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._sock = host.socket(socket.AF_INET, socket.SOCK_DGRAM)
91732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._sock.listen(MDNS_IP_ADDR, MDNS_PORT, self._mdns_request)
92732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
93c39594fae840955bf530305e9349694be5953c8fAlex Deymo        # Observer list for new responses.
94c39594fae840955bf530305e9349694be5953c8fAlex Deymo        self._answer_callbacks = []
95c39594fae840955bf530305e9349694be5953c8fAlex Deymo
96732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
97732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def __del__(self):
98732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._sock.close()
99732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
100732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
101732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    @property
102732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def host(self):
103732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """The Host object where this daemon is running."""
104732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        return self._host
105732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
106732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
107732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    @property
108732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def hostname(self):
109732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """The hostname part within a domain."""
110732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        return self._hostname
111732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
112732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
113732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    @property
114732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def domain(self):
115732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """The domain where the given hostname is running."""
116732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        return self._domain
117732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
118732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
119732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    @property
120732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def full_hostname(self):
121732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """The full hostname designation including host and domain name."""
122732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        return self._hostname + '.' + self._domain
123732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
124732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
125732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def _mdns_request(self, data, addr, port):
126dd874b90d66b46deb889ee21438f5c27a9ca7647Alex Deymo        """Handles a mDNS multicast packet.
127732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
128732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        This method will generate and send a mDNS response to any query
129732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        for which it has new authoritative information. Called by the Simulator
130732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        as a callback for every mDNS received packet.
131732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
132732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param data: The string contained on the UDP message.
133732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param addr: The address where the message comes from.
134732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param port: The port number where the message comes from.
135732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
136732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        # Parse the mDNS request using dpkt's DNS module.
137732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        mdns = dpkt.dns.DNS(data)
138732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if mdns.op == 0x0000: # Query
139732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            QUERY_HANDLERS = {
140732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                dpkt.dns.DNS_A: self._process_A,
141732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                dpkt.dns.DNS_PTR: self._process_PTR,
142732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                dpkt.dns.DNS_TXT: self._process_TXT,
143732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                dpkt.dns.DNS_SRV: self._process_SRV,
144732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            }
145732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
146732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            answers = []
147732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            for q in mdns.qd: # Query entries
148732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                if q.type in QUERY_HANDLERS:
149732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                    answers += QUERY_HANDLERS[q.type](q)
150732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                elif q.type == dpkt.dns.DNS_ANY:
151732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                    # Special type matching any known type.
152732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                    for _, handler in QUERY_HANDLERS.iteritems():
153732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                        answers += handler(q)
154732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            # Remove all the already known answers from the list.
155732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            answers = [ans for ans in answers if not any(True
156732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                for known_ans in mdns.an if _RR_equals(known_ans, ans))]
157732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
158732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            self._send_answers(answers)
15990000536174d9c752a91e48a0514a8eb2dd64441Alex Deymo
16090000536174d9c752a91e48a0514a8eb2dd64441Alex Deymo        # Always process the received authoritative answers.
16190000536174d9c752a91e48a0514a8eb2dd64441Alex Deymo        answers = mdns.ns
16290000536174d9c752a91e48a0514a8eb2dd64441Alex Deymo
16390000536174d9c752a91e48a0514a8eb2dd64441Alex Deymo        # Process the answers for response packets.
16490000536174d9c752a91e48a0514a8eb2dd64441Alex Deymo        if mdns.op == 0x8400: # Standard response
16590000536174d9c752a91e48a0514a8eb2dd64441Alex Deymo            answers.extend(mdns.an)
16690000536174d9c752a91e48a0514a8eb2dd64441Alex Deymo
16790000536174d9c752a91e48a0514a8eb2dd64441Alex Deymo        if answers:
168e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo            cur_time = time.time()
169c39594fae840955bf530305e9349694be5953c8fAlex Deymo            new_answers = []
17090000536174d9c752a91e48a0514a8eb2dd64441Alex Deymo            for rr in answers: # Answers RRs
171e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                # dpkt decodes the information on different fields depending on
172e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                # the response type.
173e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                if rr.type == dpkt.dns.DNS_A:
174e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                    data = socket.inet_ntoa(rr.ip)
175e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                elif rr.type == dpkt.dns.DNS_PTR:
176e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                    data = rr.ptrname
177e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                elif rr.type == dpkt.dns.DNS_TXT:
178e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                    data = tuple(rr.text) # Convert the list to a hashable tuple
179e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                elif rr.type == dpkt.dns.DNS_SRV:
180e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                    data = rr.srvname, rr.priority, rr.weight, rr.port
181e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                else:
182e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                    continue # Ignore unsupported records.
183e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                if not rr.name in self._peer_records:
184e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                    self._peer_records[rr.name] = {}
185e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                # Start a new cache or clear the existing if required.
186e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                if not rr.type in self._peer_records[rr.name] or (
187e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                        rr.cls & DNS_CACHE_FLUSH):
188e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                    self._peer_records[rr.name][rr.type] = {}
189e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo
190c39594fae840955bf530305e9349694be5953c8fAlex Deymo                new_answers.append((rr.type, rr.name, data))
191e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                cached_ans = self._peer_records[rr.name][rr.type]
192e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                rr_timeout = cur_time + rr.ttl
193e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                # Update the answer timeout if already cached.
194e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                if data in cached_ans:
195e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                    cached_ans[data] = max(cached_ans[data], rr_timeout)
196e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                else:
197e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                    cached_ans[data] = rr_timeout
198c39594fae840955bf530305e9349694be5953c8fAlex Deymo            if new_answers:
199c39594fae840955bf530305e9349694be5953c8fAlex Deymo                for cbk in self._answer_callbacks:
200c39594fae840955bf530305e9349694be5953c8fAlex Deymo                    cbk(new_answers)
201e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo
202e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo
203e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo    def clear_cache(self):
204e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        """Discards all the cached records."""
205e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        self._peer_records = {}
206732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
207732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
208732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def _send_answers(self, answers):
209732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """Send a mDNS reply with the provided answers.
210732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
211732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        This method uses the undelying Host to send an IP packet with a mDNS
212732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        response containing the list of answers of the type dpkt.dns.DNS.RR.
213732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        If the list is empty, no packet is sent.
214732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
215732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param answers: The list of answers to send.
216732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
217732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if not answers:
218732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            return
219074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        logging.debug('Sending response with answers: %r.', answers)
220732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        resp_dns = dpkt.dns.DNS(
221732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            op = dpkt.dns.DNS_AA, # Authoritative Answer.
222732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            rcode = dpkt.dns.DNS_RCODE_NOERR,
223732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            an = answers)
224732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        # This property modifies the "op" field:
225732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        resp_dns.qr = dpkt.dns.DNS_R, # Response.
226732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._sock.send(str(resp_dns), MDNS_IP_ADDR, MDNS_PORT)
227732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
228732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
229732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    ### RFC 2782 - RR for specifying the location of services (DNS SRV).
230732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def register_SRV(self, service, proto, priority, weight, port):
231732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """Publishes the SRV specified record.
232732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
233732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        A SRV record defines a service on a port of a host with given properties
234732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        like priority and weight. The service has a name of the form
235732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        "service.proto.domain". The target host, this is, the host where the
236732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        announced service is running on is set to the host where this zeroconf
237732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        daemon is running, "hostname.domain".
238732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
239732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param service: A string with the service name.
240732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param proto: A string with the protocol name, for example "_tcp".
241732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param priority: The service priority number as defined by RFC2782.
242732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param weight: The service weight number as defined by RFC2782.
243732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param port: The port number where the service is running on.
244732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
245732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        srvname = service + '.' + proto + '.' + self._domain
246732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._srv_records[srvname] = priority, weight, port
247732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
248732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
249732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def _process_SRV(self, q):
250732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """Process a SRV query provided in |q|.
251732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
252732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_SRV.
253732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @return: A list of dns.DNS.RR responses to the provided query that can
254732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        be empty.
255732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
256732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if not q.name in self._srv_records:
257732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            return []
258732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        priority, weight, port = self._srv_records[q.name]
259732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        full_hostname = self._hostname + '.' + self._domain
260732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        ans = dpkt.dns.DNS.RR(
261732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            type = dpkt.dns.DNS_SRV,
262732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            cls = dpkt.dns.DNS_IN | DNS_CACHE_FLUSH,
263732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            ttl = self._response_ttl,
264732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            name = q.name,
265732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            srvname = full_hostname,
266732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            priority = priority,
267732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            weight = weight,
268732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            port = port)
269732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        # The target host (srvname) requires to send an A record with its IP
270732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        # address. We do this as if a query for it was sent.
271732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        a_qry = dpkt.dns.DNS.Q(name=full_hostname, type=dpkt.dns.DNS_A)
272732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        return [ans] + self._process_A(a_qry)
273732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
274732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
275732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    ### RFC 1035 - 3.4.1, Domains Names - A (IPv4 address).
276732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def register_A(self, hostname, ip_addr):
277732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """Registers an Address record (A) pointing to the given IP addres.
278732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
279732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        Records registered with method are assumed authoritative.
280732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
281732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param hostname: The full host name, for example, "somehost.local".
282732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param ip_addr: The IPv4 address of the host, for example, "192.0.1.1".
283732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
284732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if not hostname in self._a_records:
285732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            self._a_records[hostname] = []
286732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._a_records[hostname].append(socket.inet_aton(ip_addr))
287732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
288732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
289732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def _process_A(self, q):
290732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """Process an A query provided in |q|.
291732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
292732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_A.
293732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @return: A list of dns.DNS.RR responses to the provided query that can
294732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        be empty.
295732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
296732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if not q.name in self._a_records:
297732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            return []
298732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        answers = []
299732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        for ip_addr in self._a_records[q.name]:
300732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            answers.append(dpkt.dns.DNS.RR(
301732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                type = dpkt.dns.DNS_A,
302732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                cls = dpkt.dns.DNS_IN | DNS_CACHE_FLUSH,
303732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                ttl = self._response_ttl,
304732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                name = q.name,
305732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                ip = ip_addr))
306732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        return answers
307732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
308732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
309732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    ### RFC 1035 - 3.3.12, Domain names - PTR (domain name pointer).
310732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def register_PTR(self, domain, destination):
311732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """Register a domain pointer record.
312732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
313732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        A domain pointer record is simply a pointer to a hostname on the domain.
314732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
315732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param domain: A domain name that can include a proto name, for
316732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        example, "_workstation._tcp.local".
317732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param destination: The hostname inside the given domain, for example,
318732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        "my-desktop".
319732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
320732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if not domain in self._ptr_records:
321732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            self._ptr_records[domain] = []
322732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._ptr_records[domain].append(destination)
323732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
324732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
325732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def _process_PTR(self, q):
326732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """Process a PTR query provided in |q|.
327732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
328732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_PTR.
329732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @return: A list of dns.DNS.RR responses to the provided query that can
330732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        be empty.
331732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
332732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if not q.name in self._ptr_records:
333732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            return []
334732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        answers = []
335732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        for dest in self._ptr_records[q.name]:
336732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            answers.append(dpkt.dns.DNS.RR(
337732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                type = dpkt.dns.DNS_PTR,
3388cbb993ebc2483d0576ecefbd05d425abd91b5d9Alex Deymo                cls = dpkt.dns.DNS_IN, # Don't cache flush for PTR records.
339732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                ttl = self._response_ttl,
340732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                name = q.name,
341732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo                ptrname = dest + '.' + q.name))
342732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        return answers
343732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
344732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
345732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    ### RFC 1035 - 3.3.14, Domain names - TXT (descriptive text).
3462c5c348c740930e6effa3e25441e6ba0080b3221Alex Deymo    def register_TXT(self, domain, txt_list, announce=False):
347732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """Register a TXT record on a domain with given list of strings.
348732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
349732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        A TXT record can hold any list of text entries whos format depends on
350732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        the domain. This method replaces any previous TXT record previously
351732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        registered for the given domain.
352732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
353732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param domain: A domain name that normally can include a proto name and
354732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        a service or host name.
355732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param txt_list: A list of strings.
3562c5c348c740930e6effa3e25441e6ba0080b3221Alex Deymo        @param announce: If True, the method will also announce the changes
3572c5c348c740930e6effa3e25441e6ba0080b3221Alex Deymo        on the network.
358732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
359732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        self._txt_records[domain] = txt_list
3602c5c348c740930e6effa3e25441e6ba0080b3221Alex Deymo        if announce:
3612c5c348c740930e6effa3e25441e6ba0080b3221Alex Deymo            self._send_answers(self._process_TXT(dpkt.dns.DNS.Q(name=domain)))
362732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
363732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
364732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo    def _process_TXT(self, q):
365732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """Process a TXT query provided in |q|.
366732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo
367732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_TXT.
368732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        @return: A list of dns.DNS.RR responses to the provided query that can
369732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        be empty.
370732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        """
371732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        if not q.name in self._txt_records:
372732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            return []
373732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        text_list = self._txt_records[q.name]
374732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        answer = dpkt.dns.DNS.RR(
375732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            type = dpkt.dns.DNS_TXT,
376732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            cls = dpkt.dns.DNS_IN | DNS_CACHE_FLUSH,
377732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            ttl = self._response_ttl,
378732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            name = q.name,
379732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo            text = text_list)
380732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo        return [answer]
381e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo
382e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo
383074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley    def register_service(self, unique_prefix, service_type,
384074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley                         protocol, port, txt_list):
385074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        """Register a service in the Avahi style.
386074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley
387074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        Avahi exposes a convenient set of methods for manipulating "services"
388074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        which are a trio of PTR, SRV, and TXT records.  This is a similar
389074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        helper method for our daemon.
390074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley
391074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        @param unique_prefix: string unique prefix of service (part of the
392074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley                              canonical name).
393074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        @param service_type: string type of service (e.g. '_privet').
394074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        @param protocol: string protocol to use for service (e.g. '_tcp').
395074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        @param port: IP port of service (e.g. 53).
396074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        @param txt_list: list of txt records (e.g. ['vers=1.0', 'foo']).
397074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        """
398074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        service_name = '.'.join([unique_prefix, service_type])
399074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        fq_service_name = '.'.join([service_name, protocol, self._domain])
400074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        logging.debug('Registering service=%s on port=%d with txt records=%r',
401074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley                      fq_service_name, port, txt_list)
402074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        self.register_SRV(
403074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley                service_name, protocol, DEFAULT_PRIORITY, DEFAULT_WEIGHT, port)
404074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        self.register_PTR('.'.join([service_type, protocol, self._domain]),
405074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley                          unique_prefix)
406074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley        self.register_TXT(fq_service_name, txt_list)
407074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley
408074f65c0504cfba2062b934269b96e82e08f8cd9Christopher Wiley
409e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo    def cached_results(self, rrname, rrtype, timestamp=None):
410e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        """Return all the cached results for the requested rrname and rrtype.
411e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo
412e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        This method is used to request all the received mDNS answers present
413e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        on the cache that were valid at the provided timestamp or later.
414e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        Answers received before this timestamp whose TTL isn't long enough to
415e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        make them valid at the timestamp aren't returned. On the other hand,
416e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        answers received *after* the provided timestamp will always be
417e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        considered, even if they weren't known at the provided timestamp point.
418e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        A timestamp of None will return them all.
419e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo
420e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        This method allows to retrieve "volatile" answers with a TTL of zero.
421e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        According to the RFC, these answers should be only considered for the
422e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        "ongoing" request. To do this, call this method after a few seconds (the
423e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        request timeout) after calling the send_request() method, passing to
424e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        this method the returned timestamp.
425e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo
426e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        @param rrname: The requested domain name.
427e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        @param rrtype: The DNS record type. For example, dpkt.dns.DNS_TXT.
428e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        @param timestamp: The request timestamp. See description.
429e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        @return: The list of matching records of the form (rrname, rrtype, data,
430e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo                 timeout).
431e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        """
432e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        if timestamp is None:
433e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo            timestamp = 0
434e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        if not rrname in self._peer_records:
435e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo            return []
436e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        if not rrtype in self._peer_records[rrname]:
437e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo            return []
438e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        res = []
439e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        for data, data_ts in self._peer_records[rrname][rrtype].iteritems():
440e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo            if data_ts >= timestamp:
441cb9864e93dd06fd51555f96cf95df4dbf0ce02c4Christopher Wiley                res.append(DnsRecord(rrname, rrtype, data, data_ts))
442e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo        return res
443c39594fae840955bf530305e9349694be5953c8fAlex Deymo
444c39594fae840955bf530305e9349694be5953c8fAlex Deymo
445c39594fae840955bf530305e9349694be5953c8fAlex Deymo    def send_request(self, queries):
446c39594fae840955bf530305e9349694be5953c8fAlex Deymo        """Sends a request for the provided rrname and rrtype.
447c39594fae840955bf530305e9349694be5953c8fAlex Deymo
448c39594fae840955bf530305e9349694be5953c8fAlex Deymo        All the known and valid answers for this request will be included in the
449c39594fae840955bf530305e9349694be5953c8fAlex Deymo        non authoritative list of known answers together with the request. This
450c39594fae840955bf530305e9349694be5953c8fAlex Deymo        is recommended by the RFC and avoid unnecessary responses.
451c39594fae840955bf530305e9349694be5953c8fAlex Deymo
452c39594fae840955bf530305e9349694be5953c8fAlex Deymo        @param queries: A list of pairs (rrname, rrtype) where rrname is the
453c39594fae840955bf530305e9349694be5953c8fAlex Deymo        domain name you are requesting for and the rrtype is the DNS record
454c39594fae840955bf530305e9349694be5953c8fAlex Deymo        type. For example, ('somehost.local', dpkt.dns.DNS_ANY).
455c39594fae840955bf530305e9349694be5953c8fAlex Deymo        @return: The timestamp where this request is sent. See cached_results().
456c39594fae840955bf530305e9349694be5953c8fAlex Deymo        """
457c39594fae840955bf530305e9349694be5953c8fAlex Deymo        queries = [dpkt.dns.DNS.Q(name=rrname, type=rrtype)
458c39594fae840955bf530305e9349694be5953c8fAlex Deymo                for rrname, rrtype in queries]
459c39594fae840955bf530305e9349694be5953c8fAlex Deymo        # TODO(deymo): Inlcude the already known answers on the request.
460c39594fae840955bf530305e9349694be5953c8fAlex Deymo        answers = []
461c39594fae840955bf530305e9349694be5953c8fAlex Deymo        mdns = dpkt.dns.DNS(
462c39594fae840955bf530305e9349694be5953c8fAlex Deymo            op = dpkt.dns.DNS_QUERY,
463c39594fae840955bf530305e9349694be5953c8fAlex Deymo            qd = queries,
464c39594fae840955bf530305e9349694be5953c8fAlex Deymo            an = answers)
465c39594fae840955bf530305e9349694be5953c8fAlex Deymo        self._sock.send(str(mdns), MDNS_IP_ADDR, MDNS_PORT)
466c39594fae840955bf530305e9349694be5953c8fAlex Deymo        return time.time()
467c39594fae840955bf530305e9349694be5953c8fAlex Deymo
468c39594fae840955bf530305e9349694be5953c8fAlex Deymo
469c39594fae840955bf530305e9349694be5953c8fAlex Deymo    def add_answer_observer(self, callback):
470c39594fae840955bf530305e9349694be5953c8fAlex Deymo        """Adds the callback to the list of observers for new answers.
471c39594fae840955bf530305e9349694be5953c8fAlex Deymo
472c39594fae840955bf530305e9349694be5953c8fAlex Deymo        @param callback: A callable object accepting a list of tuples (rrname,
473c39594fae840955bf530305e9349694be5953c8fAlex Deymo        rrtype, data) where rrname is the domain name, rrtype the DNS record
474c39594fae840955bf530305e9349694be5953c8fAlex Deymo        type and data is the information associated with the answers, similar to
475c39594fae840955bf530305e9349694be5953c8fAlex Deymo        what cached_results() returns.
476c39594fae840955bf530305e9349694be5953c8fAlex Deymo        """
477c39594fae840955bf530305e9349694be5953c8fAlex Deymo        self._answer_callbacks.append(callback)
478