def request(self, zc: Zeroconf, timeout: float) -> bool: now = time.time() delay = 0.2 next_ = now + delay last = now + timeout try: zc.add_listener(self, None) while self.address is None: if last <= now: # Timeout return False if next_ <= now: out = DNSOutgoing(_FLAGS_QR_QUERY) out.add_question(DNSQuestion(self.name, _TYPE_A, _CLASS_IN)) zc.send(out) next_ = now + delay delay *= 2 time.sleep(min(next_, last) - now) now = time.time() finally: zc.remove_listener(self) return True
def test_legacy_record_update_listener(): """Test a RecordUpdateListener that does not implement update_records.""" # instantiate a zeroconf instance zc = Zeroconf(interfaces=['127.0.0.1']) with pytest.raises(RuntimeError): r.RecordUpdateListener().update_record( zc, 0, r.DNSRecord('irrelevant', const._TYPE_SRV, const._CLASS_IN, const._DNS_HOST_TTL) ) updates = [] class LegacyRecordUpdateListener(r.RecordUpdateListener): """A RecordUpdateListener that does not implement update_records.""" def update_record(self, zc: 'Zeroconf', now: float, record: r.DNSRecord) -> None: nonlocal updates updates.append(record) listener = LegacyRecordUpdateListener() zc.add_listener(listener, None) # dummy service callback def on_service_state_change(zeroconf, service_type, state_change, name): pass # start a browser type_ = "_homeassistant._tcp.local." name = "MyTestHome" browser = ServiceBrowser(zc, type_, [on_service_state_change]) info_service = ServiceInfo( type_, '%s.%s' % (name, type_), 80, 0, 0, {'path': '/~paulsm/'}, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")], ) zc.register_service(info_service) zc.wait(1) browser.cancel() assert len(updates) assert len([isinstance(update, r.DNSPointer) and update.name == type_ for update in updates]) >= 1 zc.remove_listener(listener) # Removing a second time should not throw zc.remove_listener(listener) zc.close()
def request(self, zc: zeroconf.Zeroconf, timeout: float) -> bool: now = time.time() delay = 0.2 next_ = now + delay last = now + timeout try: zc.add_listener( self, zeroconf.DNSQuestion(self.name, zeroconf._TYPE_ANY, zeroconf._CLASS_IN), ) while self.address is None: if last <= now: # Timeout return False if next_ <= now: out = zeroconf.DNSOutgoing(zeroconf._FLAGS_QR_QUERY) out.add_question( zeroconf.DNSQuestion( self.name, zeroconf._TYPE_A, zeroconf._CLASS_IN ) ) out.add_answer_at_time( zc.cache.get_by_details( self.name, zeroconf._TYPE_A, zeroconf._CLASS_IN ), now, ) zc.send(out) next_ = now + delay delay *= 2 zc.wait(min(next_, last) - now) now = time.time() finally: zc.remove_listener(self) return True
class DynamicResolver(object): def __init__(self): self.zeroconf = Zeroconf(ip_version=4) def _dynamicResponseRequired(self, query): if str(query.name).endswith(domain): return True return False def _doDynamicResponse(self, query): if query.type == dns.SOA: return defer.succeed(([], [], [])) localname = str(query.name)[:-len(domain)] + "local." def browse(localname): services = [] def handler(zeroconf, service_type, name, state_change): if state_change is ServiceStateChange.Added: services.append(name) sb = ServiceBrowser(self.zeroconf, localname, [handler]) time.sleep(timeout) sb.cancel() answers, additional = [], [] for service in services: answers.append( dns.RRHeader(name=localname[:-6] + domain, ttl=ttl, type=dns.PTR, payload=dns.Record_PTR(name=service[:-6] + domain))) #txt_ans, _, _ = txt(service) #srv_ans, _, a_ans = srv(service) #additional += a_ans + txt_ans + srv_ans return answers, [], additional def txt(localname): if localname.endswith('._device-info._tcp.local.'): info = ServiceInfo(localname, localname) info.request(self.zeroconf, timeout * 1000) if not info.text: return [], [], [] else: info = self.zeroconf.get_service_info(localname, localname, timeout * 1000) if info is None: return [], [], [] order = [] i = 0 while i < len(info.text): length = info.text[i] i += 1 kv = info.text[i:i + length].split(b'=') order.append(kv[0]) i += length data = [ b"%s=%s" % (p, info.properties[p]) for p in sorted(info.properties, key=lambda k: order.index(k) if k in order else 1000) ] answers = [ dns.RRHeader(name=localname[:-6] + domain, ttl=ttl, type=dns.TXT, payload=dns.Record_TXT(*data)) ] return answers, [], [] def srv(localname): info = self.zeroconf.get_service_info(localname, localname, timeout * 1000) if info is None: return [], [], [] answers = [ dns.RRHeader(name=localname[:-6] + domain, ttl=ttl, type=dns.SRV, payload=dns.Record_SRV(info.priority, info.weight, info.port, info.server[:-6] + domain)) ] additional = [ dns.RRHeader(name=info.server[:-6] + domain, ttl=ttl, type=dns.A, payload=dns.Record_A( socket.inet_ntop(socket.AF_INET, addr))) for addr in info.addresses ] return answers, [], additional def host(localname): class listener(RecordUpdateListener): def __init__(self): self.addrs = [] self.time = time.time() def update_record(self, zc, now, record): if record.type == _TYPE_A and len(record.address) == 4: self.addrs.append( socket.inet_ntop(socket.AF_INET, record.address)) l = listener() q = DNSQuestion(localname, _TYPE_A, _CLASS_IN) self.zeroconf.add_listener(l, q) out = DNSOutgoing(_FLAGS_QR_QUERY) out.add_question(q) self.zeroconf.send(out) while len(l.addrs) == 0 and time.time() - l.time < timeout: time.sleep(0.1) self.zeroconf.remove_listener(l) answers = [ dns.RRHeader(name=query.name.name, ttl=ttl, type=dns.A, payload=dns.Record_A(addr)) for addr in l.addrs ] return answers, [], [] d = defer.Deferred() if query.type == dns.PTR: d = threads.deferToThread(browse, localname) return d elif query.type == dns.TXT: d = threads.deferToThread(txt, localname) return d elif query.type == dns.SRV: d = threads.deferToThread(srv, localname) return d elif query.type == dns.A: d = threads.deferToThread(host, localname) return d elif query.type != dns.AAAA: print("Unsupported request", query) d.callback(([], [], [])) return d def query(self, query, timeout=None): if self._dynamicResponseRequired(query): return self._doDynamicResponse(query) else: return defer.fail(error.DomainError())