Пример #1
0
    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
Пример #2
0
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()
Пример #3
0
    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
Пример #4
0
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())