1*9c5db199SXin Li# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Liimport collections 6*9c5db199SXin Liimport dpkt 7*9c5db199SXin Liimport logging 8*9c5db199SXin Liimport six 9*9c5db199SXin Liimport socket 10*9c5db199SXin Liimport time 11*9c5db199SXin Li 12*9c5db199SXin Li 13*9c5db199SXin LiDnsRecord = collections.namedtuple('DnsResult', ['rrname', 'rrtype', 'data', 'ts']) 14*9c5db199SXin Li 15*9c5db199SXin LiMDNS_IP_ADDR = '224.0.0.251' 16*9c5db199SXin LiMDNS_PORT = 5353 17*9c5db199SXin Li 18*9c5db199SXin Li# Value to | to a class value to signal cache flush. 19*9c5db199SXin LiDNS_CACHE_FLUSH = 0x8000 20*9c5db199SXin Li 21*9c5db199SXin Li# When considering SRV records, clients are supposed to unilaterally prefer 22*9c5db199SXin Li# numerically lower priorities, then pick probabilistically by weight. 23*9c5db199SXin Li# See RFC2782. 24*9c5db199SXin Li# An arbitrary number that will fit in 16 bits. 25*9c5db199SXin LiDEFAULT_PRIORITY = 500 26*9c5db199SXin Li# An arbitrary number that will fit in 16 bits. 27*9c5db199SXin LiDEFAULT_WEIGHT = 500 28*9c5db199SXin Li 29*9c5db199SXin Lidef _RR_equals(rra, rrb): 30*9c5db199SXin Li """Returns whether the two dpkt.dns.DNS.RR objects are equal.""" 31*9c5db199SXin Li # Compare all the members present in either object and on any RR object. 32*9c5db199SXin Li keys = set(rra.__dict__.keys() + rrb.__dict__.keys() + 33*9c5db199SXin Li dpkt.dns.DNS.RR.__slots__) 34*9c5db199SXin Li # On RR objects, rdata is packed based on the other members and the final 35*9c5db199SXin Li # packed string depends on other RR and Q elements on the same DNS/mDNS 36*9c5db199SXin Li # packet. 37*9c5db199SXin Li keys.discard('rdata') 38*9c5db199SXin Li for key in keys: 39*9c5db199SXin Li if hasattr(rra, key) != hasattr(rrb, key): 40*9c5db199SXin Li return False 41*9c5db199SXin Li if not hasattr(rra, key): 42*9c5db199SXin Li continue 43*9c5db199SXin Li if key == 'cls': 44*9c5db199SXin Li # cls attribute should be masked for the cache flush bit. 45*9c5db199SXin Li if (getattr(rra, key) & ~DNS_CACHE_FLUSH != 46*9c5db199SXin Li getattr(rrb, key) & ~DNS_CACHE_FLUSH): 47*9c5db199SXin Li return False 48*9c5db199SXin Li else: 49*9c5db199SXin Li if getattr(rra, key) != getattr(rrb, key): 50*9c5db199SXin Li return False 51*9c5db199SXin Li return True 52*9c5db199SXin Li 53*9c5db199SXin Li 54*9c5db199SXin Liclass ZeroconfDaemon(object): 55*9c5db199SXin Li """Implements a simulated Zeroconf daemon running on the given host. 56*9c5db199SXin Li 57*9c5db199SXin Li This class implements part of the Multicast DNS RFC 6762 able to simulate 58*9c5db199SXin Li a host exposing services or consuming services over mDNS. 59*9c5db199SXin Li """ 60*9c5db199SXin Li def __init__(self, host, hostname, domain='local'): 61*9c5db199SXin Li """Initializes the ZeroconfDameon running on the given host. 62*9c5db199SXin Li 63*9c5db199SXin Li For the purposes of the Zeroconf implementation, a host must have a 64*9c5db199SXin Li hostname and a domain that defaults to 'local'. The ZeroconfDaemon will 65*9c5db199SXin Li by default advertise the host address it is running on, which is 66*9c5db199SXin Li required by some services. 67*9c5db199SXin Li 68*9c5db199SXin Li @param host: The Host instance where this daemon runs on. 69*9c5db199SXin Li @param hostname: A string representing the hostname 70*9c5db199SXin Li """ 71*9c5db199SXin Li self._host = host 72*9c5db199SXin Li self._hostname = hostname 73*9c5db199SXin Li self._domain = domain 74*9c5db199SXin Li self._response_ttl = 60 # Default TTL in seconds. 75*9c5db199SXin Li 76*9c5db199SXin Li self._a_records = {} # Local A records. 77*9c5db199SXin Li self._srv_records = {} # Local SRV records. 78*9c5db199SXin Li self._ptr_records = {} # Local PTR records. 79*9c5db199SXin Li self._txt_records = {} # Local TXT records. 80*9c5db199SXin Li 81*9c5db199SXin Li # dict() of name --> (dict() of type --> (dict() of data --> timeout)) 82*9c5db199SXin Li # For example: _peer_records['somehost.local'][dpkt.dns.DNS_A] \ 83*9c5db199SXin Li # ['192.168.0.1'] = time.time() + 3600 84*9c5db199SXin Li self._peer_records = {} 85*9c5db199SXin Li 86*9c5db199SXin Li # Register the host address locally. 87*9c5db199SXin Li self.register_A(self.full_hostname, host.ip_addr) 88*9c5db199SXin Li 89*9c5db199SXin Li # Attend all the traffic to the mDNS port (unicast, multicast or 90*9c5db199SXin Li # broadcast). 91*9c5db199SXin Li self._sock = host.socket(socket.AF_INET, socket.SOCK_DGRAM) 92*9c5db199SXin Li self._sock.listen(MDNS_IP_ADDR, MDNS_PORT, self._mdns_request) 93*9c5db199SXin Li 94*9c5db199SXin Li # Observer list for new responses. 95*9c5db199SXin Li self._answer_callbacks = [] 96*9c5db199SXin Li 97*9c5db199SXin Li 98*9c5db199SXin Li def __del__(self): 99*9c5db199SXin Li self._sock.close() 100*9c5db199SXin Li 101*9c5db199SXin Li 102*9c5db199SXin Li @property 103*9c5db199SXin Li def host(self): 104*9c5db199SXin Li """The Host object where this daemon is running.""" 105*9c5db199SXin Li return self._host 106*9c5db199SXin Li 107*9c5db199SXin Li 108*9c5db199SXin Li @property 109*9c5db199SXin Li def hostname(self): 110*9c5db199SXin Li """The hostname part within a domain.""" 111*9c5db199SXin Li return self._hostname 112*9c5db199SXin Li 113*9c5db199SXin Li 114*9c5db199SXin Li @property 115*9c5db199SXin Li def domain(self): 116*9c5db199SXin Li """The domain where the given hostname is running.""" 117*9c5db199SXin Li return self._domain 118*9c5db199SXin Li 119*9c5db199SXin Li 120*9c5db199SXin Li @property 121*9c5db199SXin Li def full_hostname(self): 122*9c5db199SXin Li """The full hostname designation including host and domain name.""" 123*9c5db199SXin Li return self._hostname + '.' + self._domain 124*9c5db199SXin Li 125*9c5db199SXin Li 126*9c5db199SXin Li def _mdns_request(self, data, addr, port): 127*9c5db199SXin Li """Handles a mDNS multicast packet. 128*9c5db199SXin Li 129*9c5db199SXin Li This method will generate and send a mDNS response to any query 130*9c5db199SXin Li for which it has new authoritative information. Called by the Simulator 131*9c5db199SXin Li as a callback for every mDNS received packet. 132*9c5db199SXin Li 133*9c5db199SXin Li @param data: The string contained on the UDP message. 134*9c5db199SXin Li @param addr: The address where the message comes from. 135*9c5db199SXin Li @param port: The port number where the message comes from. 136*9c5db199SXin Li """ 137*9c5db199SXin Li # Parse the mDNS request using dpkt's DNS module. 138*9c5db199SXin Li mdns = dpkt.dns.DNS(data) 139*9c5db199SXin Li if mdns.op == 0x0000: # Query 140*9c5db199SXin Li QUERY_HANDLERS = { 141*9c5db199SXin Li dpkt.dns.DNS_A: self._process_A, 142*9c5db199SXin Li dpkt.dns.DNS_PTR: self._process_PTR, 143*9c5db199SXin Li dpkt.dns.DNS_TXT: self._process_TXT, 144*9c5db199SXin Li dpkt.dns.DNS_SRV: self._process_SRV, 145*9c5db199SXin Li } 146*9c5db199SXin Li 147*9c5db199SXin Li answers = [] 148*9c5db199SXin Li for q in mdns.qd: # Query entries 149*9c5db199SXin Li if q.type in QUERY_HANDLERS: 150*9c5db199SXin Li answers += QUERY_HANDLERS[q.type](q) 151*9c5db199SXin Li elif q.type == dpkt.dns.DNS_ANY: 152*9c5db199SXin Li # Special type matching any known type. 153*9c5db199SXin Li for _, handler in QUERY_HANDLERS.iteritems(): 154*9c5db199SXin Li answers += handler(q) 155*9c5db199SXin Li # Remove all the already known answers from the list. 156*9c5db199SXin Li answers = [ans for ans in answers if not any(True 157*9c5db199SXin Li for known_ans in mdns.an if _RR_equals(known_ans, ans))] 158*9c5db199SXin Li 159*9c5db199SXin Li self._send_answers(answers) 160*9c5db199SXin Li 161*9c5db199SXin Li # Always process the received authoritative answers. 162*9c5db199SXin Li answers = mdns.ns 163*9c5db199SXin Li 164*9c5db199SXin Li # Process the answers for response packets. 165*9c5db199SXin Li if mdns.op == 0x8400: # Standard response 166*9c5db199SXin Li answers.extend(mdns.an) 167*9c5db199SXin Li 168*9c5db199SXin Li if answers: 169*9c5db199SXin Li cur_time = time.time() 170*9c5db199SXin Li new_answers = [] 171*9c5db199SXin Li for rr in answers: # Answers RRs 172*9c5db199SXin Li # dpkt decodes the information on different fields depending on 173*9c5db199SXin Li # the response type. 174*9c5db199SXin Li if rr.type == dpkt.dns.DNS_A: 175*9c5db199SXin Li data = socket.inet_ntoa(rr.ip) 176*9c5db199SXin Li elif rr.type == dpkt.dns.DNS_PTR: 177*9c5db199SXin Li data = rr.ptrname 178*9c5db199SXin Li elif rr.type == dpkt.dns.DNS_TXT: 179*9c5db199SXin Li data = tuple(rr.text) # Convert the list to a hashable tuple 180*9c5db199SXin Li elif rr.type == dpkt.dns.DNS_SRV: 181*9c5db199SXin Li data = rr.srvname, rr.priority, rr.weight, rr.port 182*9c5db199SXin Li else: 183*9c5db199SXin Li continue # Ignore unsupported records. 184*9c5db199SXin Li if not rr.name in self._peer_records: 185*9c5db199SXin Li self._peer_records[rr.name] = {} 186*9c5db199SXin Li # Start a new cache or clear the existing if required. 187*9c5db199SXin Li if not rr.type in self._peer_records[rr.name] or ( 188*9c5db199SXin Li rr.cls & DNS_CACHE_FLUSH): 189*9c5db199SXin Li self._peer_records[rr.name][rr.type] = {} 190*9c5db199SXin Li 191*9c5db199SXin Li new_answers.append((rr.type, rr.name, data)) 192*9c5db199SXin Li cached_ans = self._peer_records[rr.name][rr.type] 193*9c5db199SXin Li rr_timeout = cur_time + rr.ttl 194*9c5db199SXin Li # Update the answer timeout if already cached. 195*9c5db199SXin Li if data in cached_ans: 196*9c5db199SXin Li cached_ans[data] = max(cached_ans[data], rr_timeout) 197*9c5db199SXin Li else: 198*9c5db199SXin Li cached_ans[data] = rr_timeout 199*9c5db199SXin Li if new_answers: 200*9c5db199SXin Li for cbk in self._answer_callbacks: 201*9c5db199SXin Li cbk(new_answers) 202*9c5db199SXin Li 203*9c5db199SXin Li 204*9c5db199SXin Li def clear_cache(self): 205*9c5db199SXin Li """Discards all the cached records.""" 206*9c5db199SXin Li self._peer_records = {} 207*9c5db199SXin Li 208*9c5db199SXin Li 209*9c5db199SXin Li def _send_answers(self, answers): 210*9c5db199SXin Li """Send a mDNS reply with the provided answers. 211*9c5db199SXin Li 212*9c5db199SXin Li This method uses the undelying Host to send an IP packet with a mDNS 213*9c5db199SXin Li response containing the list of answers of the type dpkt.dns.DNS.RR. 214*9c5db199SXin Li If the list is empty, no packet is sent. 215*9c5db199SXin Li 216*9c5db199SXin Li @param answers: The list of answers to send. 217*9c5db199SXin Li """ 218*9c5db199SXin Li if not answers: 219*9c5db199SXin Li return 220*9c5db199SXin Li logging.debug('Sending response with answers: %r.', answers) 221*9c5db199SXin Li resp_dns = dpkt.dns.DNS( 222*9c5db199SXin Li op = dpkt.dns.DNS_AA, # Authoritative Answer. 223*9c5db199SXin Li rcode = dpkt.dns.DNS_RCODE_NOERR, 224*9c5db199SXin Li an = answers) 225*9c5db199SXin Li # This property modifies the "op" field: 226*9c5db199SXin Li resp_dns.qr = dpkt.dns.DNS_R, # Response. 227*9c5db199SXin Li self._sock.send(str(resp_dns), MDNS_IP_ADDR, MDNS_PORT) 228*9c5db199SXin Li 229*9c5db199SXin Li 230*9c5db199SXin Li ### RFC 2782 - RR for specifying the location of services (DNS SRV). 231*9c5db199SXin Li def register_SRV(self, service, proto, priority, weight, port): 232*9c5db199SXin Li """Publishes the SRV specified record. 233*9c5db199SXin Li 234*9c5db199SXin Li A SRV record defines a service on a port of a host with given properties 235*9c5db199SXin Li like priority and weight. The service has a name of the form 236*9c5db199SXin Li "service.proto.domain". The target host, this is, the host where the 237*9c5db199SXin Li announced service is running on is set to the host where this zeroconf 238*9c5db199SXin Li daemon is running, "hostname.domain". 239*9c5db199SXin Li 240*9c5db199SXin Li @param service: A string with the service name. 241*9c5db199SXin Li @param proto: A string with the protocol name, for example "_tcp". 242*9c5db199SXin Li @param priority: The service priority number as defined by RFC2782. 243*9c5db199SXin Li @param weight: The service weight number as defined by RFC2782. 244*9c5db199SXin Li @param port: The port number where the service is running on. 245*9c5db199SXin Li """ 246*9c5db199SXin Li srvname = service + '.' + proto + '.' + self._domain 247*9c5db199SXin Li self._srv_records[srvname] = priority, weight, port 248*9c5db199SXin Li 249*9c5db199SXin Li 250*9c5db199SXin Li def _process_SRV(self, q): 251*9c5db199SXin Li """Process a SRV query provided in |q|. 252*9c5db199SXin Li 253*9c5db199SXin Li @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_SRV. 254*9c5db199SXin Li @return: A list of dns.DNS.RR responses to the provided query that can 255*9c5db199SXin Li be empty. 256*9c5db199SXin Li """ 257*9c5db199SXin Li if not q.name in self._srv_records: 258*9c5db199SXin Li return [] 259*9c5db199SXin Li priority, weight, port = self._srv_records[q.name] 260*9c5db199SXin Li full_hostname = self._hostname + '.' + self._domain 261*9c5db199SXin Li ans = dpkt.dns.DNS.RR( 262*9c5db199SXin Li type = dpkt.dns.DNS_SRV, 263*9c5db199SXin Li cls = dpkt.dns.DNS_IN | DNS_CACHE_FLUSH, 264*9c5db199SXin Li ttl = self._response_ttl, 265*9c5db199SXin Li name = q.name, 266*9c5db199SXin Li srvname = full_hostname, 267*9c5db199SXin Li priority = priority, 268*9c5db199SXin Li weight = weight, 269*9c5db199SXin Li port = port) 270*9c5db199SXin Li # The target host (srvname) requires to send an A record with its IP 271*9c5db199SXin Li # address. We do this as if a query for it was sent. 272*9c5db199SXin Li a_qry = dpkt.dns.DNS.Q(name=full_hostname, type=dpkt.dns.DNS_A) 273*9c5db199SXin Li return [ans] + self._process_A(a_qry) 274*9c5db199SXin Li 275*9c5db199SXin Li 276*9c5db199SXin Li ### RFC 1035 - 3.4.1, Domains Names - A (IPv4 address). 277*9c5db199SXin Li def register_A(self, hostname, ip_addr): 278*9c5db199SXin Li """Registers an Address record (A) pointing to the given IP addres. 279*9c5db199SXin Li 280*9c5db199SXin Li Records registered with method are assumed authoritative. 281*9c5db199SXin Li 282*9c5db199SXin Li @param hostname: The full host name, for example, "somehost.local". 283*9c5db199SXin Li @param ip_addr: The IPv4 address of the host, for example, "192.0.1.1". 284*9c5db199SXin Li """ 285*9c5db199SXin Li if not hostname in self._a_records: 286*9c5db199SXin Li self._a_records[hostname] = [] 287*9c5db199SXin Li self._a_records[hostname].append(socket.inet_aton(ip_addr)) 288*9c5db199SXin Li 289*9c5db199SXin Li 290*9c5db199SXin Li def _process_A(self, q): 291*9c5db199SXin Li """Process an A query provided in |q|. 292*9c5db199SXin Li 293*9c5db199SXin Li @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_A. 294*9c5db199SXin Li @return: A list of dns.DNS.RR responses to the provided query that can 295*9c5db199SXin Li be empty. 296*9c5db199SXin Li """ 297*9c5db199SXin Li if not q.name in self._a_records: 298*9c5db199SXin Li return [] 299*9c5db199SXin Li answers = [] 300*9c5db199SXin Li for ip_addr in self._a_records[q.name]: 301*9c5db199SXin Li answers.append(dpkt.dns.DNS.RR( 302*9c5db199SXin Li type = dpkt.dns.DNS_A, 303*9c5db199SXin Li cls = dpkt.dns.DNS_IN | DNS_CACHE_FLUSH, 304*9c5db199SXin Li ttl = self._response_ttl, 305*9c5db199SXin Li name = q.name, 306*9c5db199SXin Li ip = ip_addr)) 307*9c5db199SXin Li return answers 308*9c5db199SXin Li 309*9c5db199SXin Li 310*9c5db199SXin Li ### RFC 1035 - 3.3.12, Domain names - PTR (domain name pointer). 311*9c5db199SXin Li def register_PTR(self, domain, destination): 312*9c5db199SXin Li """Register a domain pointer record. 313*9c5db199SXin Li 314*9c5db199SXin Li A domain pointer record is simply a pointer to a hostname on the domain. 315*9c5db199SXin Li 316*9c5db199SXin Li @param domain: A domain name that can include a proto name, for 317*9c5db199SXin Li example, "_workstation._tcp.local". 318*9c5db199SXin Li @param destination: The hostname inside the given domain, for example, 319*9c5db199SXin Li "my-desktop". 320*9c5db199SXin Li """ 321*9c5db199SXin Li if not domain in self._ptr_records: 322*9c5db199SXin Li self._ptr_records[domain] = [] 323*9c5db199SXin Li self._ptr_records[domain].append(destination) 324*9c5db199SXin Li 325*9c5db199SXin Li 326*9c5db199SXin Li def _process_PTR(self, q): 327*9c5db199SXin Li """Process a PTR query provided in |q|. 328*9c5db199SXin Li 329*9c5db199SXin Li @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_PTR. 330*9c5db199SXin Li @return: A list of dns.DNS.RR responses to the provided query that can 331*9c5db199SXin Li be empty. 332*9c5db199SXin Li """ 333*9c5db199SXin Li if not q.name in self._ptr_records: 334*9c5db199SXin Li return [] 335*9c5db199SXin Li answers = [] 336*9c5db199SXin Li for dest in self._ptr_records[q.name]: 337*9c5db199SXin Li answers.append(dpkt.dns.DNS.RR( 338*9c5db199SXin Li type = dpkt.dns.DNS_PTR, 339*9c5db199SXin Li cls = dpkt.dns.DNS_IN, # Don't cache flush for PTR records. 340*9c5db199SXin Li ttl = self._response_ttl, 341*9c5db199SXin Li name = q.name, 342*9c5db199SXin Li ptrname = dest + '.' + q.name)) 343*9c5db199SXin Li return answers 344*9c5db199SXin Li 345*9c5db199SXin Li 346*9c5db199SXin Li ### RFC 1035 - 3.3.14, Domain names - TXT (descriptive text). 347*9c5db199SXin Li def register_TXT(self, domain, txt_list, announce=False): 348*9c5db199SXin Li """Register a TXT record on a domain with given list of strings. 349*9c5db199SXin Li 350*9c5db199SXin Li A TXT record can hold any list of text entries whos format depends on 351*9c5db199SXin Li the domain. This method replaces any previous TXT record previously 352*9c5db199SXin Li registered for the given domain. 353*9c5db199SXin Li 354*9c5db199SXin Li @param domain: A domain name that normally can include a proto name and 355*9c5db199SXin Li a service or host name. 356*9c5db199SXin Li @param txt_list: A list of strings. 357*9c5db199SXin Li @param announce: If True, the method will also announce the changes 358*9c5db199SXin Li on the network. 359*9c5db199SXin Li """ 360*9c5db199SXin Li self._txt_records[domain] = txt_list 361*9c5db199SXin Li if announce: 362*9c5db199SXin Li self._send_answers(self._process_TXT(dpkt.dns.DNS.Q(name=domain))) 363*9c5db199SXin Li 364*9c5db199SXin Li 365*9c5db199SXin Li def _process_TXT(self, q): 366*9c5db199SXin Li """Process a TXT query provided in |q|. 367*9c5db199SXin Li 368*9c5db199SXin Li @param q: The dns.DNS.Q query object with type dpkt.dns.DNS_TXT. 369*9c5db199SXin Li @return: A list of dns.DNS.RR responses to the provided query that can 370*9c5db199SXin Li be empty. 371*9c5db199SXin Li """ 372*9c5db199SXin Li if not q.name in self._txt_records: 373*9c5db199SXin Li return [] 374*9c5db199SXin Li text_list = self._txt_records[q.name] 375*9c5db199SXin Li answer = dpkt.dns.DNS.RR( 376*9c5db199SXin Li type = dpkt.dns.DNS_TXT, 377*9c5db199SXin Li cls = dpkt.dns.DNS_IN | DNS_CACHE_FLUSH, 378*9c5db199SXin Li ttl = self._response_ttl, 379*9c5db199SXin Li name = q.name, 380*9c5db199SXin Li text = text_list) 381*9c5db199SXin Li return [answer] 382*9c5db199SXin Li 383*9c5db199SXin Li 384*9c5db199SXin Li def register_service(self, unique_prefix, service_type, 385*9c5db199SXin Li protocol, port, txt_list): 386*9c5db199SXin Li """Register a service in the Avahi style. 387*9c5db199SXin Li 388*9c5db199SXin Li Avahi exposes a convenient set of methods for manipulating "services" 389*9c5db199SXin Li which are a trio of PTR, SRV, and TXT records. This is a similar 390*9c5db199SXin Li helper method for our daemon. 391*9c5db199SXin Li 392*9c5db199SXin Li @param unique_prefix: string unique prefix of service (part of the 393*9c5db199SXin Li canonical name). 394*9c5db199SXin Li @param service_type: string type of service (e.g. '_privet'). 395*9c5db199SXin Li @param protocol: string protocol to use for service (e.g. '_tcp'). 396*9c5db199SXin Li @param port: IP port of service (e.g. 53). 397*9c5db199SXin Li @param txt_list: list of txt records (e.g. ['vers=1.0', 'foo']). 398*9c5db199SXin Li """ 399*9c5db199SXin Li service_name = '.'.join([unique_prefix, service_type]) 400*9c5db199SXin Li fq_service_name = '.'.join([service_name, protocol, self._domain]) 401*9c5db199SXin Li logging.debug('Registering service=%s on port=%d with txt records=%r', 402*9c5db199SXin Li fq_service_name, port, txt_list) 403*9c5db199SXin Li self.register_SRV( 404*9c5db199SXin Li service_name, protocol, DEFAULT_PRIORITY, DEFAULT_WEIGHT, port) 405*9c5db199SXin Li self.register_PTR('.'.join([service_type, protocol, self._domain]), 406*9c5db199SXin Li unique_prefix) 407*9c5db199SXin Li self.register_TXT(fq_service_name, txt_list) 408*9c5db199SXin Li 409*9c5db199SXin Li 410*9c5db199SXin Li def cached_results(self, rrname, rrtype, timestamp=None): 411*9c5db199SXin Li """Return all the cached results for the requested rrname and rrtype. 412*9c5db199SXin Li 413*9c5db199SXin Li This method is used to request all the received mDNS answers present 414*9c5db199SXin Li on the cache that were valid at the provided timestamp or later. 415*9c5db199SXin Li Answers received before this timestamp whose TTL isn't long enough to 416*9c5db199SXin Li make them valid at the timestamp aren't returned. On the other hand, 417*9c5db199SXin Li answers received *after* the provided timestamp will always be 418*9c5db199SXin Li considered, even if they weren't known at the provided timestamp point. 419*9c5db199SXin Li A timestamp of None will return them all. 420*9c5db199SXin Li 421*9c5db199SXin Li This method allows to retrieve "volatile" answers with a TTL of zero. 422*9c5db199SXin Li According to the RFC, these answers should be only considered for the 423*9c5db199SXin Li "ongoing" request. To do this, call this method after a few seconds (the 424*9c5db199SXin Li request timeout) after calling the send_request() method, passing to 425*9c5db199SXin Li this method the returned timestamp. 426*9c5db199SXin Li 427*9c5db199SXin Li @param rrname: The requested domain name. 428*9c5db199SXin Li @param rrtype: The DNS record type. For example, dpkt.dns.DNS_TXT. 429*9c5db199SXin Li @param timestamp: The request timestamp. See description. 430*9c5db199SXin Li @return: The list of matching records of the form (rrname, rrtype, data, 431*9c5db199SXin Li timeout). 432*9c5db199SXin Li """ 433*9c5db199SXin Li if timestamp is None: 434*9c5db199SXin Li timestamp = 0 435*9c5db199SXin Li if not rrname in self._peer_records: 436*9c5db199SXin Li return [] 437*9c5db199SXin Li if not rrtype in self._peer_records[rrname]: 438*9c5db199SXin Li return [] 439*9c5db199SXin Li res = [] 440*9c5db199SXin Li for data, data_ts in six.iteritems(self._peer_records[rrname][rrtype]): 441*9c5db199SXin Li if data_ts >= timestamp: 442*9c5db199SXin Li res.append(DnsRecord(rrname, rrtype, data, data_ts)) 443*9c5db199SXin Li return res 444*9c5db199SXin Li 445*9c5db199SXin Li 446*9c5db199SXin Li def send_request(self, queries): 447*9c5db199SXin Li """Sends a request for the provided rrname and rrtype. 448*9c5db199SXin Li 449*9c5db199SXin Li All the known and valid answers for this request will be included in the 450*9c5db199SXin Li non authoritative list of known answers together with the request. This 451*9c5db199SXin Li is recommended by the RFC and avoid unnecessary responses. 452*9c5db199SXin Li 453*9c5db199SXin Li @param queries: A list of pairs (rrname, rrtype) where rrname is the 454*9c5db199SXin Li domain name you are requesting for and the rrtype is the DNS record 455*9c5db199SXin Li type. For example, ('somehost.local', dpkt.dns.DNS_ANY). 456*9c5db199SXin Li @return: The timestamp where this request is sent. See cached_results(). 457*9c5db199SXin Li """ 458*9c5db199SXin Li queries = [dpkt.dns.DNS.Q(name=rrname, type=rrtype) 459*9c5db199SXin Li for rrname, rrtype in queries] 460*9c5db199SXin Li # TODO(deymo): Inlcude the already known answers on the request. 461*9c5db199SXin Li answers = [] 462*9c5db199SXin Li mdns = dpkt.dns.DNS( 463*9c5db199SXin Li op = dpkt.dns.DNS_QUERY, 464*9c5db199SXin Li qd = queries, 465*9c5db199SXin Li an = answers) 466*9c5db199SXin Li self._sock.send(str(mdns), MDNS_IP_ADDR, MDNS_PORT) 467*9c5db199SXin Li return time.time() 468*9c5db199SXin Li 469*9c5db199SXin Li 470*9c5db199SXin Li def add_answer_observer(self, callback): 471*9c5db199SXin Li """Adds the callback to the list of observers for new answers. 472*9c5db199SXin Li 473*9c5db199SXin Li @param callback: A callable object accepting a list of tuples (rrname, 474*9c5db199SXin Li rrtype, data) where rrname is the domain name, rrtype the DNS record 475*9c5db199SXin Li type and data is the information associated with the answers, similar to 476*9c5db199SXin Li what cached_results() returns. 477*9c5db199SXin Li """ 478*9c5db199SXin Li self._answer_callbacks.append(callback) 479