zeroconf.py revision 2c5c348c740930e6effa3e25441e6ba0080b3221
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 5732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymoimport dpkt 6732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymoimport socket 7e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymoimport time 8732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 9732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 10732dfe9c1124b51b906cb6510f219ad84be58836Alex DeymoMDNS_IP_ADDR = '224.0.0.251' 11732dfe9c1124b51b906cb6510f219ad84be58836Alex DeymoMDNS_PORT = 5353 12732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 13732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo# Value to | to a class value to signal cache flush. 14732dfe9c1124b51b906cb6510f219ad84be58836Alex DeymoDNS_CACHE_FLUSH = 0x8000 15732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 16732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 17732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymodef _RR_equals(rra, rrb): 18732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Returns whether the two dpkt.dns.DNS.RR objects are equal.""" 19732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # Compare all the members present in either object and on any RR object. 20732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo keys = set(rra.__dict__.keys() + rrb.__dict__.keys() + 21732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo dpkt.dns.DNS.RR.__slots__) 22732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # On RR objects, rdata is packed based on the other members and the final 23732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # packed string depends on other RR and Q elements on the same DNS/mDNS 24732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # packet. 25732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo keys.discard('rdata') 26732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo for key in keys: 27732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if hasattr(rra, key) != hasattr(rrb, key): 28732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return False 29732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if not hasattr(rra, key): 30732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo continue 31732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if key == 'cls': 32732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # cls attribute should be masked for the cache flush bit. 33732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if (getattr(rra, key) & ~DNS_CACHE_FLUSH != 34732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo getattr(rrb, key) & ~DNS_CACHE_FLUSH): 35732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return False 36732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo else: 37732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if getattr(rra, key) != getattr(rrb, key): 38732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return False 39732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return True 40732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 41732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 42732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymoclass ZeroconfDaemon(object): 43732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Implements a simulated Zeroconf daemon running on the given host. 44732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 45732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo This class implements part of the Multicast DNS RFC 6762 able to simulate 46732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo a host exposing services or consuming services over mDNS. 47732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 48732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def __init__(self, host, hostname, domain='local'): 49732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Initializes the ZeroconfDameon running on the given host. 50732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 51732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo For the purposes of the Zeroconf implementation, a host must have a 52732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo hostname and a domain that defaults to 'local'. The ZeroconfDaemon will 53732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo by default advertise the host address it is running on, which is 54732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo required by some services. 55732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 56732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param host: The Host instance where this daemon runs on. 57732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param hostname: A string representing the hostname 58732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 59732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._host = host 60732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._hostname = hostname 61732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._domain = domain 62732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._response_ttl = 60 # Default TTL in seconds. 63732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 64732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._a_records = {} # Local A records. 65732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._srv_records = {} # Local SRV records. 66732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._ptr_records = {} # Local PTR records. 67732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._txt_records = {} # Local TXT records. 68732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 69e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo # dict() of name --> (dict() of type --> (dict() of data --> timeout)) 70e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo # For example: _peer_records['somehost.local'][dpkt.dns.DNS_A] \ 71e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo # ['192.168.0.1'] = time.time() + 3600 72e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo self._peer_records = {} 73e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo 74732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # Register the host address locally. 75732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self.register_A(self.full_hostname, host.ip_addr) 76732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 77732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # Attend all the traffic to the mDNS port (unicast, multicast or 78732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # broadcast). 79732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._sock = host.socket(socket.AF_INET, socket.SOCK_DGRAM) 80732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._sock.listen(MDNS_IP_ADDR, MDNS_PORT, self._mdns_request) 81732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 82c39594fae840955bf530305e9349694be5953c8fAlex Deymo # Observer list for new responses. 83c39594fae840955bf530305e9349694be5953c8fAlex Deymo self._answer_callbacks = [] 84c39594fae840955bf530305e9349694be5953c8fAlex Deymo 85732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 86732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def __del__(self): 87732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._sock.close() 88732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 89732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 90732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @property 91732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def host(self): 92732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """The Host object where this daemon is running.""" 93732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return self._host 94732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 95732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 96732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @property 97732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def hostname(self): 98732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """The hostname part within a domain.""" 99732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return self._hostname 100732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 101732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 102732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @property 103732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def domain(self): 104732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """The domain where the given hostname is running.""" 105732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return self._domain 106732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 107732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 108732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @property 109732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def full_hostname(self): 110732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """The full hostname designation including host and domain name.""" 111732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return self._hostname + '.' + self._domain 112732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 113732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 114732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def _mdns_request(self, data, addr, port): 115732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Handles an Ethernet packet containing a mDNS multicast packet. 116732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 117732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo This method will generate and send a mDNS response to any query 118732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo for which it has new authoritative information. Called by the Simulator 119732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo as a callback for every mDNS received packet. 120732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 121732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param data: The string contained on the UDP message. 122732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param addr: The address where the message comes from. 123732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param port: The port number where the message comes from. 124732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 125732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # Parse the mDNS request using dpkt's DNS module. 126732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo mdns = dpkt.dns.DNS(data) 127732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if mdns.op == 0x0000: # Query 128732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo QUERY_HANDLERS = { 129732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo dpkt.dns.DNS_A: self._process_A, 130732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo dpkt.dns.DNS_PTR: self._process_PTR, 131732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo dpkt.dns.DNS_TXT: self._process_TXT, 132732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo dpkt.dns.DNS_SRV: self._process_SRV, 133732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo } 134732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 135732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo answers = [] 136732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo for q in mdns.qd: # Query entries 137732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if q.type in QUERY_HANDLERS: 138732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo answers += QUERY_HANDLERS[q.type](q) 139732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo elif q.type == dpkt.dns.DNS_ANY: 140732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # Special type matching any known type. 141732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo for _, handler in QUERY_HANDLERS.iteritems(): 142732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo answers += handler(q) 143732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # Remove all the already known answers from the list. 144732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo answers = [ans for ans in answers if not any(True 145732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo for known_ans in mdns.an if _RR_equals(known_ans, ans))] 146732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 147732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._send_answers(answers) 148e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo elif mdns.op == 0x8400: # Standard response 149e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo cur_time = time.time() 150c39594fae840955bf530305e9349694be5953c8fAlex Deymo new_answers = [] 151e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo for rr in mdns.an: # Answers RRs 152e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo # dpkt decodes the information on different fields depending on 153e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo # the response type. 154e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo if rr.type == dpkt.dns.DNS_A: 155e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo data = socket.inet_ntoa(rr.ip) 156e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo elif rr.type == dpkt.dns.DNS_PTR: 157e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo data = rr.ptrname 158e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo elif rr.type == dpkt.dns.DNS_TXT: 159e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo data = tuple(rr.text) # Convert the list to a hashable tuple 160e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo elif rr.type == dpkt.dns.DNS_SRV: 161e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo data = rr.srvname, rr.priority, rr.weight, rr.port 162e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo else: 163e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo continue # Ignore unsupported records. 164e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo if not rr.name in self._peer_records: 165e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo self._peer_records[rr.name] = {} 166e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo # Start a new cache or clear the existing if required. 167e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo if not rr.type in self._peer_records[rr.name] or ( 168e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo rr.cls & DNS_CACHE_FLUSH): 169e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo self._peer_records[rr.name][rr.type] = {} 170e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo 171c39594fae840955bf530305e9349694be5953c8fAlex Deymo new_answers.append((rr.type, rr.name, data)) 172e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo cached_ans = self._peer_records[rr.name][rr.type] 173e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo rr_timeout = cur_time + rr.ttl 174e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo # Update the answer timeout if already cached. 175e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo if data in cached_ans: 176e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo cached_ans[data] = max(cached_ans[data], rr_timeout) 177e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo else: 178e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo cached_ans[data] = rr_timeout 179c39594fae840955bf530305e9349694be5953c8fAlex Deymo if new_answers: 180c39594fae840955bf530305e9349694be5953c8fAlex Deymo for cbk in self._answer_callbacks: 181c39594fae840955bf530305e9349694be5953c8fAlex Deymo cbk(new_answers) 182e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo 183e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo 184e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo def clear_cache(self): 185e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo """Discards all the cached records.""" 186e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo self._peer_records = {} 187732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 188732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 189732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def _send_answers(self, answers): 190732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Send a mDNS reply with the provided answers. 191732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 192732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo This method uses the undelying Host to send an IP packet with a mDNS 193732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo response containing the list of answers of the type dpkt.dns.DNS.RR. 194732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo If the list is empty, no packet is sent. 195732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 196732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param answers: The list of answers to send. 197732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 198732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if not answers: 199732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return 200732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo resp_dns = dpkt.dns.DNS( 201732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo op = dpkt.dns.DNS_AA, # Authoritative Answer. 202732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo rcode = dpkt.dns.DNS_RCODE_NOERR, 203732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo an = answers) 204732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # This property modifies the "op" field: 205732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo resp_dns.qr = dpkt.dns.DNS_R, # Response. 206732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._sock.send(str(resp_dns), MDNS_IP_ADDR, MDNS_PORT) 207732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 208732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 209732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ### RFC 2782 - RR for specifying the location of services (DNS SRV). 210732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def register_SRV(self, service, proto, priority, weight, port): 211732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Publishes the SRV specified record. 212732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 213732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo A SRV record defines a service on a port of a host with given properties 214732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo like priority and weight. The service has a name of the form 215732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo "service.proto.domain". The target host, this is, the host where the 216732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo announced service is running on is set to the host where this zeroconf 217732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo daemon is running, "hostname.domain". 218732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 219732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param service: A string with the service name. 220732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param proto: A string with the protocol name, for example "_tcp". 221732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param priority: The service priority number as defined by RFC2782. 222732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param weight: The service weight number as defined by RFC2782. 223732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param port: The port number where the service is running on. 224732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 225732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo srvname = service + '.' + proto + '.' + self._domain 226732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._srv_records[srvname] = priority, weight, port 227732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 228732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 229732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def _process_SRV(self, q): 230732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Process a SRV query provided in |q|. 231732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 232732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_SRV. 233732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @return: A list of dns.DNS.RR responses to the provided query that can 234732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo be empty. 235732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 236732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if not q.name in self._srv_records: 237732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return [] 238732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo priority, weight, port = self._srv_records[q.name] 239732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo full_hostname = self._hostname + '.' + self._domain 240732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ans = dpkt.dns.DNS.RR( 241732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo type = dpkt.dns.DNS_SRV, 242732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo cls = dpkt.dns.DNS_IN | DNS_CACHE_FLUSH, 243732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ttl = self._response_ttl, 244732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo name = q.name, 245732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo srvname = full_hostname, 246732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo priority = priority, 247732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo weight = weight, 248732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo port = port) 249732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # The target host (srvname) requires to send an A record with its IP 250732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo # address. We do this as if a query for it was sent. 251732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo a_qry = dpkt.dns.DNS.Q(name=full_hostname, type=dpkt.dns.DNS_A) 252732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return [ans] + self._process_A(a_qry) 253732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 254732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 255732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ### RFC 1035 - 3.4.1, Domains Names - A (IPv4 address). 256732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def register_A(self, hostname, ip_addr): 257732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Registers an Address record (A) pointing to the given IP addres. 258732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 259732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo Records registered with method are assumed authoritative. 260732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 261732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param hostname: The full host name, for example, "somehost.local". 262732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param ip_addr: The IPv4 address of the host, for example, "192.0.1.1". 263732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 264732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if not hostname in self._a_records: 265732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._a_records[hostname] = [] 266732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._a_records[hostname].append(socket.inet_aton(ip_addr)) 267732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 268732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 269732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def _process_A(self, q): 270732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Process an A query provided in |q|. 271732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 272732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_A. 273732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @return: A list of dns.DNS.RR responses to the provided query that can 274732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo be empty. 275732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 276732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if not q.name in self._a_records: 277732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return [] 278732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo answers = [] 279732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo for ip_addr in self._a_records[q.name]: 280732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo answers.append(dpkt.dns.DNS.RR( 281732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo type = dpkt.dns.DNS_A, 282732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo cls = dpkt.dns.DNS_IN | DNS_CACHE_FLUSH, 283732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ttl = self._response_ttl, 284732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo name = q.name, 285732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ip = ip_addr)) 286732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return answers 287732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 288732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 289732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ### RFC 1035 - 3.3.12, Domain names - PTR (domain name pointer). 290732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def register_PTR(self, domain, destination): 291732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Register a domain pointer record. 292732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 293732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo A domain pointer record is simply a pointer to a hostname on the domain. 294732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 295732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param domain: A domain name that can include a proto name, for 296732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo example, "_workstation._tcp.local". 297732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param destination: The hostname inside the given domain, for example, 298732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo "my-desktop". 299732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 300732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if not domain in self._ptr_records: 301732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._ptr_records[domain] = [] 302732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._ptr_records[domain].append(destination) 303732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 304732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 305732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def _process_PTR(self, q): 306732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Process a PTR query provided in |q|. 307732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 308732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_PTR. 309732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @return: A list of dns.DNS.RR responses to the provided query that can 310732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo be empty. 311732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 312732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if not q.name in self._ptr_records: 313732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return [] 314732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo answers = [] 315732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo for dest in self._ptr_records[q.name]: 316732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo answers.append(dpkt.dns.DNS.RR( 317732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo type = dpkt.dns.DNS_PTR, 3188cbb993ebc2483d0576ecefbd05d425abd91b5d9Alex Deymo cls = dpkt.dns.DNS_IN, # Don't cache flush for PTR records. 319732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ttl = self._response_ttl, 320732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo name = q.name, 321732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ptrname = dest + '.' + q.name)) 322732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return answers 323732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 324732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 325732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ### RFC 1035 - 3.3.14, Domain names - TXT (descriptive text). 3262c5c348c740930e6effa3e25441e6ba0080b3221Alex Deymo def register_TXT(self, domain, txt_list, announce=False): 327732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Register a TXT record on a domain with given list of strings. 328732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 329732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo A TXT record can hold any list of text entries whos format depends on 330732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo the domain. This method replaces any previous TXT record previously 331732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo registered for the given domain. 332732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 333732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param domain: A domain name that normally can include a proto name and 334732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo a service or host name. 335732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param txt_list: A list of strings. 3362c5c348c740930e6effa3e25441e6ba0080b3221Alex Deymo @param announce: If True, the method will also announce the changes 3372c5c348c740930e6effa3e25441e6ba0080b3221Alex Deymo on the network. 338732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 339732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo self._txt_records[domain] = txt_list 3402c5c348c740930e6effa3e25441e6ba0080b3221Alex Deymo if announce: 3412c5c348c740930e6effa3e25441e6ba0080b3221Alex Deymo self._send_answers(self._process_TXT(dpkt.dns.DNS.Q(name=domain))) 342732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 343732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 344732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo def _process_TXT(self, q): 345732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """Process a TXT query provided in |q|. 346732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo 347732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_TXT. 348732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo @return: A list of dns.DNS.RR responses to the provided query that can 349732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo be empty. 350732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo """ 351732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo if not q.name in self._txt_records: 352732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return [] 353732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo text_list = self._txt_records[q.name] 354732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo answer = dpkt.dns.DNS.RR( 355732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo type = dpkt.dns.DNS_TXT, 356732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo cls = dpkt.dns.DNS_IN | DNS_CACHE_FLUSH, 357732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo ttl = self._response_ttl, 358732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo name = q.name, 359732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo text = text_list) 360732dfe9c1124b51b906cb6510f219ad84be58836Alex Deymo return [answer] 361e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo 362e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo 363e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo def cached_results(self, rrname, rrtype, timestamp=None): 364e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo """Return all the cached results for the requested rrname and rrtype. 365e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo 366e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo This method is used to request all the received mDNS answers present 367e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo on the cache that were valid at the provided timestamp or later. 368e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo Answers received before this timestamp whose TTL isn't long enough to 369e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo make them valid at the timestamp aren't returned. On the other hand, 370e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo answers received *after* the provided timestamp will always be 371e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo considered, even if they weren't known at the provided timestamp point. 372e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo A timestamp of None will return them all. 373e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo 374e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo This method allows to retrieve "volatile" answers with a TTL of zero. 375e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo According to the RFC, these answers should be only considered for the 376e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo "ongoing" request. To do this, call this method after a few seconds (the 377e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo request timeout) after calling the send_request() method, passing to 378e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo this method the returned timestamp. 379e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo 380e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo @param rrname: The requested domain name. 381e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo @param rrtype: The DNS record type. For example, dpkt.dns.DNS_TXT. 382e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo @param timestamp: The request timestamp. See description. 383e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo @return: The list of matching records of the form (rrname, rrtype, data, 384e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo timeout). 385e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo """ 386e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo if timestamp is None: 387e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo timestamp = 0 388e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo if not rrname in self._peer_records: 389e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo return [] 390e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo if not rrtype in self._peer_records[rrname]: 391e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo return [] 392e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo res = [] 393e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo for data, data_ts in self._peer_records[rrname][rrtype].iteritems(): 394e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo if data_ts >= timestamp: 395e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo res.append((rrname, rrtype, data, data_ts)) 396e24633e8df180e0479e78d641a6643e3ec36845dAlex Deymo return res 397c39594fae840955bf530305e9349694be5953c8fAlex Deymo 398c39594fae840955bf530305e9349694be5953c8fAlex Deymo 399c39594fae840955bf530305e9349694be5953c8fAlex Deymo def send_request(self, queries): 400c39594fae840955bf530305e9349694be5953c8fAlex Deymo """Sends a request for the provided rrname and rrtype. 401c39594fae840955bf530305e9349694be5953c8fAlex Deymo 402c39594fae840955bf530305e9349694be5953c8fAlex Deymo All the known and valid answers for this request will be included in the 403c39594fae840955bf530305e9349694be5953c8fAlex Deymo non authoritative list of known answers together with the request. This 404c39594fae840955bf530305e9349694be5953c8fAlex Deymo is recommended by the RFC and avoid unnecessary responses. 405c39594fae840955bf530305e9349694be5953c8fAlex Deymo 406c39594fae840955bf530305e9349694be5953c8fAlex Deymo @param queries: A list of pairs (rrname, rrtype) where rrname is the 407c39594fae840955bf530305e9349694be5953c8fAlex Deymo domain name you are requesting for and the rrtype is the DNS record 408c39594fae840955bf530305e9349694be5953c8fAlex Deymo type. For example, ('somehost.local', dpkt.dns.DNS_ANY). 409c39594fae840955bf530305e9349694be5953c8fAlex Deymo @return: The timestamp where this request is sent. See cached_results(). 410c39594fae840955bf530305e9349694be5953c8fAlex Deymo """ 411c39594fae840955bf530305e9349694be5953c8fAlex Deymo queries = [dpkt.dns.DNS.Q(name=rrname, type=rrtype) 412c39594fae840955bf530305e9349694be5953c8fAlex Deymo for rrname, rrtype in queries] 413c39594fae840955bf530305e9349694be5953c8fAlex Deymo # TODO(deymo): Inlcude the already known answers on the request. 414c39594fae840955bf530305e9349694be5953c8fAlex Deymo answers = [] 415c39594fae840955bf530305e9349694be5953c8fAlex Deymo mdns = dpkt.dns.DNS( 416c39594fae840955bf530305e9349694be5953c8fAlex Deymo op = dpkt.dns.DNS_QUERY, 417c39594fae840955bf530305e9349694be5953c8fAlex Deymo qd = queries, 418c39594fae840955bf530305e9349694be5953c8fAlex Deymo an = answers) 419c39594fae840955bf530305e9349694be5953c8fAlex Deymo self._sock.send(str(mdns), MDNS_IP_ADDR, MDNS_PORT) 420c39594fae840955bf530305e9349694be5953c8fAlex Deymo return time.time() 421c39594fae840955bf530305e9349694be5953c8fAlex Deymo 422c39594fae840955bf530305e9349694be5953c8fAlex Deymo 423c39594fae840955bf530305e9349694be5953c8fAlex Deymo def add_answer_observer(self, callback): 424c39594fae840955bf530305e9349694be5953c8fAlex Deymo """Adds the callback to the list of observers for new answers. 425c39594fae840955bf530305e9349694be5953c8fAlex Deymo 426c39594fae840955bf530305e9349694be5953c8fAlex Deymo @param callback: A callable object accepting a list of tuples (rrname, 427c39594fae840955bf530305e9349694be5953c8fAlex Deymo rrtype, data) where rrname is the domain name, rrtype the DNS record 428c39594fae840955bf530305e9349694be5953c8fAlex Deymo type and data is the information associated with the answers, similar to 429c39594fae840955bf530305e9349694be5953c8fAlex Deymo what cached_results() returns. 430c39594fae840955bf530305e9349694be5953c8fAlex Deymo """ 431c39594fae840955bf530305e9349694be5953c8fAlex Deymo self._answer_callbacks.append(callback) 432