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