コード例 #1
0
 def test_reaper(self):
     zeroconf = _core.Zeroconf(interfaces=['127.0.0.1'])
     cache = zeroconf.cache
     original_entries = list(
         itertools.chain(
             *[cache.entries_with_name(name) for name in cache.names()]))
     record_with_10s_ttl = r.DNSAddress('a', const._TYPE_SOA,
                                        const._CLASS_IN, 10, b'a')
     record_with_1s_ttl = r.DNSAddress('a', const._TYPE_SOA,
                                       const._CLASS_IN, 1, b'b')
     zeroconf.cache.add(record_with_10s_ttl)
     zeroconf.cache.add(record_with_1s_ttl)
     entries_with_cache = list(
         itertools.chain(
             *[cache.entries_with_name(name) for name in cache.names()]))
     time.sleep(1)
     zeroconf.notify_all()
     time.sleep(0.1)
     entries = list(
         itertools.chain(
             *[cache.entries_with_name(name) for name in cache.names()]))
     zeroconf.close()
     assert entries != original_entries
     assert entries_with_cache != original_entries
     assert record_with_10s_ttl in entries
     assert record_with_1s_ttl not in entries
コード例 #2
0
ファイル: test_dns.py プロジェクト: ibygrave/python-zeroconf
def test_dns_address_record_hashablity():
    """Test DNSAddress are hashable."""
    address1 = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 1,
                            b'a')
    address2 = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 1,
                            b'b')
    address3 = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 1,
                            b'c')
    address4 = r.DNSAddress('irrelevant', const._TYPE_AAAA, const._CLASS_IN, 1,
                            b'c')

    record_set = set([address1, address2, address3, address4])
    assert len(record_set) == 4

    record_set.add(address1)
    assert len(record_set) == 4

    address3_dupe = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN,
                                 1, b'c')

    record_set.add(address3_dupe)
    assert len(record_set) == 4

    # Verify we can remove records
    additional_set = set([address1, address2])
    record_set -= additional_set
    assert record_set == set([address3, address4])
コード例 #3
0
 def test_order(self):
     record1 = r.DNSAddress('a', const._TYPE_SOA, const._CLASS_IN, 1, b'a')
     record2 = r.DNSAddress('a', const._TYPE_SOA, const._CLASS_IN, 1, b'b')
     cache = r.DNSCache()
     cache.add(record1)
     cache.add(record2)
     entry = r.DNSEntry('a', const._TYPE_SOA, const._CLASS_IN)
     cached_record = cache.get(entry)
     assert cached_record == record2
コード例 #4
0
 def test_order(self):
     record1 = r.DNSAddress("a", r._TYPE_SOA, r._CLASS_IN, 1, b"a")
     record2 = r.DNSAddress("a", r._TYPE_SOA, r._CLASS_IN, 1, b"b")
     cache = r.DNSCache()
     cache.add(record1)
     cache.add(record2)
     entry = r.DNSEntry("a", r._TYPE_SOA, r._CLASS_IN)
     cached_record = cache.get(entry)
     self.assertEqual(cached_record, record2)
コード例 #5
0
 def test_cache_empty_does_not_leak_memory_by_leaving_empty_list(self):
     record1 = r.DNSAddress('a', const._TYPE_SOA, const._CLASS_IN, 1, b'a')
     record2 = r.DNSAddress('a', const._TYPE_SOA, const._CLASS_IN, 1, b'b')
     cache = r.DNSCache()
     cache.add(record1)
     cache.add(record2)
     assert 'a' in cache.cache
     cache.remove(record1)
     cache.remove(record2)
     assert 'a' not in cache.cache
コード例 #6
0
    def test_dns_address_repr(self):
        address = r.DNSAddress('irrelevant', const._TYPE_SOA, const._CLASS_IN, 1, b'a')
        assert repr(address).endswith("b'a'")

        address_ipv4 = r.DNSAddress(
            'irrelevant', const._TYPE_SOA, const._CLASS_IN, 1, socket.inet_pton(socket.AF_INET, '127.0.0.1')
        )
        assert repr(address_ipv4).endswith('127.0.0.1')

        address_ipv6 = r.DNSAddress(
            'irrelevant', const._TYPE_SOA, const._CLASS_IN, 1, socket.inet_pton(socket.AF_INET6, '::1')
        )
        assert repr(address_ipv6).endswith('::1')
コード例 #7
0
 def test_cache_empty_multiple_calls_does_not_throw(self):
     record1 = r.DNSAddress('a', const._TYPE_SOA, const._CLASS_IN, 1, b'a')
     record2 = r.DNSAddress('a', const._TYPE_SOA, const._CLASS_IN, 1, b'b')
     cache = r.DNSCache()
     cache.add(record1)
     cache.add(record2)
     assert 'a' in cache.cache
     cache.remove(record1)
     cache.remove(record2)
     # Ensure multiple removes does not throw
     cache.remove(record1)
     cache.remove(record2)
     assert 'a' not in cache.cache
コード例 #8
0
def test_rrset_does_not_consider_ttl():
    """Test DNSRRSet does not consider the ttl in the hash."""

    longarec = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 100, b'same')
    shortarec = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 10, b'same')
    longaaaarec = r.DNSAddress('irrelevant', const._TYPE_AAAA, const._CLASS_IN, 100, b'same')
    shortaaaarec = r.DNSAddress('irrelevant', const._TYPE_AAAA, const._CLASS_IN, 10, b'same')

    rrset = DNSRRSet([longarec, shortaaaarec])

    assert rrset.suppresses(longarec)
    assert rrset.suppresses(shortarec)
    assert not rrset.suppresses(longaaaarec)
    assert rrset.suppresses(shortaaaarec)

    verylongarec = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 1000, b'same')
    longarec = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 100, b'same')
    mediumarec = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 60, b'same')
    shortarec = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, 10, b'same')

    rrset2 = DNSRRSet([mediumarec])
    assert not rrset2.suppresses(verylongarec)
    assert rrset2.suppresses(longarec)
    assert rrset2.suppresses(mediumarec)
    assert rrset2.suppresses(shortarec)
コード例 #9
0
 def mock_split_incoming_msg(
         service_state_change: r.ServiceStateChange) -> r.DNSIncoming:
     """Mock an incoming message for the case where the packet is split."""
     ttl = 120
     generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
     generated.add_answer_at_time(
         r.DNSAddress(
             service_server,
             const._TYPE_A,
             const._CLASS_IN | const._CLASS_UNIQUE,
             ttl,
             socket.inet_aton(service_address),
         ),
         0,
     )
     generated.add_answer_at_time(
         r.DNSService(
             service_name,
             const._TYPE_SRV,
             const._CLASS_IN | const._CLASS_UNIQUE,
             ttl,
             0,
             0,
             80,
             service_server,
         ),
         0,
     )
     return r.DNSIncoming(generated.packets()[0])
コード例 #10
0
 def test_incoming_unknown_type(self):
     generated = r.DNSOutgoing(0)
     answer = r.DNSAddress("a", r._TYPE_SOA, r._CLASS_IN, 1, b"a")
     generated.add_additional_answer(answer)
     packet = generated.packet()
     parsed = r.DNSIncoming(packet)
     assert len(parsed.answers) == 0
     assert parsed.is_query() != parsed.is_response()
コード例 #11
0
def test_dns_record_hashablity_does_not_consider_ttl():
    """Test DNSRecord are hashable."""

    # Verify the TTL is not considered in the hash
    record1 = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, const._DNS_OTHER_TTL, b'same')
    record2 = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, const._DNS_HOST_TTL, b'same')

    record_set = set([record1, record2])
    assert len(record_set) == 1

    record_set.add(record1)
    assert len(record_set) == 1

    record3_dupe = r.DNSAddress('irrelevant', const._TYPE_A, const._CLASS_IN, const._DNS_HOST_TTL, b'same')
    assert record2 == record3_dupe
    assert record2.__hash__() == record3_dupe.__hash__()

    record_set.add(record3_dupe)
    assert len(record_set) == 1
コード例 #12
0
        def mock_incoming_msg(
                service_state_change: r.ServiceStateChange) -> r.DNSIncoming:
            ttl = 120
            generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)

            if service_state_change == r.ServiceStateChange.Updated:
                generated.add_answer_at_time(
                    r.DNSText(
                        service_name,
                        const._TYPE_TXT,
                        const._CLASS_IN | const._CLASS_UNIQUE,
                        ttl,
                        service_text,
                    ),
                    0,
                )
                return r.DNSIncoming(generated.packets()[0])

            if service_state_change == r.ServiceStateChange.Removed:
                ttl = 0

            generated.add_answer_at_time(
                r.DNSPointer(service_type, const._TYPE_PTR, const._CLASS_IN,
                             ttl, service_name), 0)
            generated.add_answer_at_time(
                r.DNSService(
                    service_name,
                    const._TYPE_SRV,
                    const._CLASS_IN | const._CLASS_UNIQUE,
                    ttl,
                    0,
                    0,
                    80,
                    service_server,
                ),
                0,
            )
            generated.add_answer_at_time(
                r.DNSText(service_name, const._TYPE_TXT,
                          const._CLASS_IN | const._CLASS_UNIQUE, ttl,
                          service_text),
                0,
            )
            generated.add_answer_at_time(
                r.DNSAddress(
                    service_server,
                    const._TYPE_A,
                    const._CLASS_IN | const._CLASS_UNIQUE,
                    ttl,
                    socket.inet_aton(service_address),
                ),
                0,
            )

            return r.DNSIncoming(generated.packets()[0])
コード例 #13
0
 def test_incoming_ipv6(self):
     addr = "2606:2800:220:1:248:1893:25c8:1946"  # example.com
     packed = socket.inet_pton(socket.AF_INET6, addr)
     generated = r.DNSOutgoing(0)
     answer = r.DNSAddress('domain', r._TYPE_AAAA, r._CLASS_IN, 1, packed)
     generated.add_additional_answer(answer)
     packet = generated.packet()
     parsed = r.DNSIncoming(packet)
     record = parsed.answers[0]
     assert isinstance(record, r.DNSAddress)
     assert record.address == packed
コード例 #14
0
ファイル: test_dns.py プロジェクト: ibygrave/python-zeroconf
def test_dns_compression_rollback_for_corruption():
    """Verify rolling back does not lead to dns compression corruption."""
    out = r.DNSOutgoing(const._FLAGS_QR_RESPONSE | const._FLAGS_AA)
    address = socket.inet_pton(socket.AF_INET, "192.168.208.5")

    additionals = [
        {
            "name":
            "HASS Bridge ZJWH FF5137._hap._tcp.local.",
            "address":
            address,
            "port":
            51832,
            "text":
            b"\x13md=HASS Bridge"
            b" ZJWH\x06pv=1.0\x14id=01:6B:30:FF:51:37\x05c#=12\x04s#=1\x04ff=0\x04"
            b"ci=2\x04sf=0\x0bsh=L0m/aQ==",
        },
        {
            "name":
            "HASS Bridge 3K9A C2582A._hap._tcp.local.",
            "address":
            address,
            "port":
            51834,
            "text":
            b"\x13md=HASS Bridge"
            b" 3K9A\x06pv=1.0\x14id=E2:AA:5B:C2:58:2A\x05c#=12\x04s#=1\x04ff=0\x04"
            b"ci=2\x04sf=0\x0bsh=b2CnzQ==",
        },
        {
            "name":
            "Master Bed TV CEDB27._hap._tcp.local.",
            "address":
            address,
            "port":
            51830,
            "text":
            b"\x10md=Master Bed"
            b" TV\x06pv=1.0\x14id=9E:B7:44:CE:DB:27\x05c#=18\x04s#=1\x04ff=0\x05"
            b"ci=31\x04sf=0\x0bsh=CVj1kw==",
        },
        {
            "name":
            "Living Room TV 921B77._hap._tcp.local.",
            "address":
            address,
            "port":
            51833,
            "text":
            b"\x11md=Living Room"
            b" TV\x06pv=1.0\x14id=11:61:E7:92:1B:77\x05c#=17\x04s#=1\x04ff=0\x05"
            b"ci=31\x04sf=0\x0bsh=qU77SQ==",
        },
        {
            "name":
            "HASS Bridge ZC8X FF413D._hap._tcp.local.",
            "address":
            address,
            "port":
            51829,
            "text":
            b"\x13md=HASS Bridge"
            b" ZC8X\x06pv=1.0\x14id=96:14:45:FF:41:3D\x05c#=12\x04s#=1\x04ff=0\x04"
            b"ci=2\x04sf=0\x0bsh=b0QZlg==",
        },
        {
            "name":
            "HASS Bridge WLTF 4BE61F._hap._tcp.local.",
            "address":
            address,
            "port":
            51837,
            "text":
            b"\x13md=HASS Bridge"
            b" WLTF\x06pv=1.0\x14id=E0:E7:98:4B:E6:1F\x04c#=2\x04s#=1\x04ff=0\x04"
            b"ci=2\x04sf=0\x0bsh=ahAISA==",
        },
        {
            "name":
            "FrontdoorCamera 8941D1._hap._tcp.local.",
            "address":
            address,
            "port":
            54898,
            "text":
            b"\x12md=FrontdoorCamera\x06pv=1.0\x14id=9F:B7:DC:89:41:D1\x04c#=2\x04"
            b"s#=1\x04ff=0\x04ci=2\x04sf=0\x0bsh=0+MXmA==",
        },
        {
            "name":
            "HASS Bridge W9DN 5B5CC5._hap._tcp.local.",
            "address":
            address,
            "port":
            51836,
            "text":
            b"\x13md=HASS Bridge"
            b" W9DN\x06pv=1.0\x14id=11:8E:DB:5B:5C:C5\x05c#=12\x04s#=1\x04ff=0\x04"
            b"ci=2\x04sf=0\x0bsh=6fLM5A==",
        },
        {
            "name":
            "HASS Bridge Y9OO EFF0A7._hap._tcp.local.",
            "address":
            address,
            "port":
            51838,
            "text":
            b"\x13md=HASS Bridge"
            b" Y9OO\x06pv=1.0\x14id=D3:FE:98:EF:F0:A7\x04c#=2\x04s#=1\x04ff=0\x04"
            b"ci=2\x04sf=0\x0bsh=u3bdfw==",
        },
        {
            "name":
            "Snooze Room TV 6B89B0._hap._tcp.local.",
            "address":
            address,
            "port":
            51835,
            "text":
            b"\x11md=Snooze Room"
            b" TV\x06pv=1.0\x14id=5F:D5:70:6B:89:B0\x05c#=17\x04s#=1\x04ff=0\x05"
            b"ci=31\x04sf=0\x0bsh=xNTqsg==",
        },
        {
            "name":
            "AlexanderHomeAssistant 74651D._hap._tcp.local.",
            "address":
            address,
            "port":
            54811,
            "text":
            b"\x19md=AlexanderHomeAssistant\x06pv=1.0\x14id=59:8A:0B:74:65:1D\x05"
            b"c#=14\x04s#=1\x04ff=0\x04ci=2\x04sf=0\x0bsh=ccZLPA==",
        },
        {
            "name":
            "HASS Bridge OS95 39C053._hap._tcp.local.",
            "address":
            address,
            "port":
            51831,
            "text":
            b"\x13md=HASS Bridge"
            b" OS95\x06pv=1.0\x14id=7E:8C:E6:39:C0:53\x05c#=12\x04s#=1\x04ff=0\x04ci=2"
            b"\x04sf=0\x0bsh=Xfe5LQ==",
        },
    ]

    out.add_answer_at_time(
        DNSText(
            "HASS Bridge W9DN 5B5CC5._hap._tcp.local.",
            const._TYPE_TXT,
            const._CLASS_IN | const._CLASS_UNIQUE,
            const._DNS_OTHER_TTL,
            b'\x13md=HASS Bridge W9DN\x06pv=1.0\x14id=11:8E:DB:5B:5C:C5\x05c#=12\x04s#=1'
            b'\x04ff=0\x04ci=2\x04sf=0\x0bsh=6fLM5A==',
        ),
        0,
    )

    for record in additionals:
        out.add_additional_answer(
            r.DNSService(
                record["name"],  # type: ignore
                const._TYPE_SRV,
                const._CLASS_IN | const._CLASS_UNIQUE,
                const._DNS_HOST_TTL,
                0,
                0,
                record["port"],  # type: ignore
                record["name"],  # type: ignore
            ))
        out.add_additional_answer(
            r.DNSText(
                record["name"],  # type: ignore
                const._TYPE_TXT,
                const._CLASS_IN | const._CLASS_UNIQUE,
                const._DNS_OTHER_TTL,
                record["text"],  # type: ignore
            ))
        out.add_additional_answer(
            r.DNSAddress(
                record["name"],  # type: ignore
                const._TYPE_A,
                const._CLASS_IN | const._CLASS_UNIQUE,
                const._DNS_HOST_TTL,
                record["address"],  # type: ignore
            ))

    for packet in out.packets():
        # Verify we can process the packets we created to
        # ensure there is no corruption with the dns compression
        incoming = r.DNSIncoming(packet)
        assert incoming.valid is True
コード例 #15
0
 def test_dns_address_repr(self):
     address = r.DNSAddress("irrelevant", r._TYPE_SOA, r._CLASS_IN, 1, b"a")
     repr(address)
コード例 #16
0
 def test_dns_address_repr(self):
     address = r.DNSAddress('irrelevant', r._TYPE_SOA, r._CLASS_IN, 1, b'a')
     repr(address)
コード例 #17
0
        def mock_incoming_msg(service_state_change: r.ServiceStateChange) -> r.DNSIncoming:

            generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
            assert generated.is_response() is True

            if service_state_change == r.ServiceStateChange.Removed:
                ttl = 0
            else:
                ttl = 120

            generated.add_answer_at_time(
                r.DNSText(
                    service_name, const._TYPE_TXT, const._CLASS_IN | const._CLASS_UNIQUE, ttl, service_text
                ),
                0,
            )

            generated.add_answer_at_time(
                r.DNSService(
                    service_name,
                    const._TYPE_SRV,
                    const._CLASS_IN | const._CLASS_UNIQUE,
                    ttl,
                    0,
                    0,
                    80,
                    service_server,
                ),
                0,
            )

            # Send the IPv6 address first since we previously
            # had a bug where the IPv4 would be missing if the
            # IPv6 was seen first
            if enable_ipv6:
                generated.add_answer_at_time(
                    r.DNSAddress(
                        service_server,
                        const._TYPE_AAAA,
                        const._CLASS_IN | const._CLASS_UNIQUE,
                        ttl,
                        socket.inet_pton(socket.AF_INET6, service_v6_address),
                    ),
                    0,
                )
                generated.add_answer_at_time(
                    r.DNSAddress(
                        service_server,
                        const._TYPE_AAAA,
                        const._CLASS_IN | const._CLASS_UNIQUE,
                        ttl,
                        socket.inet_pton(socket.AF_INET6, service_v6_second_address),
                    ),
                    0,
                )
            generated.add_answer_at_time(
                r.DNSAddress(
                    service_server,
                    const._TYPE_A,
                    const._CLASS_IN | const._CLASS_UNIQUE,
                    ttl,
                    socket.inet_aton(service_address),
                ),
                0,
            )

            generated.add_answer_at_time(
                r.DNSPointer(service_type, const._TYPE_PTR, const._CLASS_IN, ttl, service_name), 0
            )

            return r.DNSIncoming(generated.packets()[0])
コード例 #18
0
    def test_service_info_rejects_non_matching_updates(self):
        """Verify records with the wrong name are rejected."""

        zc = r.Zeroconf(interfaces=['127.0.0.1'])
        desc = {'path': '/~paulsm/'}
        service_name = 'name._type._tcp.local.'
        service_type = '_type._tcp.local.'
        service_server = 'ash-1.local.'
        service_address = socket.inet_aton("10.0.1.2")
        ttl = 120
        now = r.current_time_millis()
        info = ServiceInfo(
            service_type, service_name, 22, 0, 0, desc, service_server, addresses=[service_address]
        )
        # Verify backwards compatiblity with calling with None
        info.update_record(zc, now, None)
        # Matching updates
        info.update_record(
            zc,
            now,
            r.DNSText(
                service_name,
                const._TYPE_TXT,
                const._CLASS_IN | const._CLASS_UNIQUE,
                ttl,
                b'\x04ff=0\x04ci=2\x04sf=0\x0bsh=6fLM5A==',
            ),
        )
        assert info.properties[b"ci"] == b"2"
        info.update_record(
            zc,
            now,
            r.DNSService(
                service_name,
                const._TYPE_SRV,
                const._CLASS_IN | const._CLASS_UNIQUE,
                ttl,
                0,
                0,
                80,
                'ASH-2.local.',
            ),
        )
        assert info.server_key == 'ash-2.local.'
        assert info.server == 'ASH-2.local.'
        new_address = socket.inet_aton("10.0.1.3")
        info.update_record(
            zc,
            now,
            r.DNSAddress(
                'ASH-2.local.',
                const._TYPE_A,
                const._CLASS_IN | const._CLASS_UNIQUE,
                ttl,
                new_address,
            ),
        )
        assert new_address in info.addresses
        # Non-matching updates
        info.update_record(
            zc,
            now,
            r.DNSText(
                "incorrect.name.",
                const._TYPE_TXT,
                const._CLASS_IN | const._CLASS_UNIQUE,
                ttl,
                b'\x04ff=0\x04ci=3\x04sf=0\x0bsh=6fLM5A==',
            ),
        )
        assert info.properties[b"ci"] == b"2"
        info.update_record(
            zc,
            now,
            r.DNSService(
                "incorrect.name.",
                const._TYPE_SRV,
                const._CLASS_IN | const._CLASS_UNIQUE,
                ttl,
                0,
                0,
                80,
                'ASH-2.local.',
            ),
        )
        assert info.server_key == 'ash-2.local.'
        assert info.server == 'ASH-2.local.'
        new_address = socket.inet_aton("10.0.1.4")
        info.update_record(
            zc,
            now,
            r.DNSAddress(
                "incorrect.name.",
                const._TYPE_A,
                const._CLASS_IN | const._CLASS_UNIQUE,
                ttl,
                new_address,
            ),
        )
        assert new_address not in info.addresses
        zc.close()
コード例 #19
0
    def test_get_info_single(self):

        zc = r.Zeroconf(interfaces=['127.0.0.1'])

        service_name = 'name._type._tcp.local.'
        service_type = '_type._tcp.local.'
        service_server = 'ash-1.local.'
        service_text = b'path=/~matt1/'
        service_address = '10.0.1.2'

        service_info = None
        send_event = Event()
        service_info_event = Event()

        last_sent = None  # type: Optional[r.DNSOutgoing]

        def send(out, addr=const._MDNS_ADDR, port=const._MDNS_PORT):
            """Sends an outgoing packet."""
            nonlocal last_sent

            last_sent = out
            send_event.set()

        # patch the zeroconf send
        with unittest.mock.patch.object(zc, "send", send):

            def mock_incoming_msg(records) -> r.DNSIncoming:

                generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)

                for record in records:
                    generated.add_answer_at_time(record, 0)

                return r.DNSIncoming(generated.packets()[0])

            def get_service_info_helper(zc, type, name):
                nonlocal service_info
                service_info = zc.get_service_info(type, name)
                service_info_event.set()

            try:
                ttl = 120
                helper_thread = threading.Thread(
                    target=get_service_info_helper, args=(zc, service_type, service_name)
                )
                helper_thread.start()
                wait_time = 1

                # Expext query for SRV, TXT, A, AAAA
                send_event.wait(wait_time)
                assert last_sent is not None
                assert len(last_sent.questions) == 4
                assert r.DNSQuestion(service_name, const._TYPE_SRV, const._CLASS_IN) in last_sent.questions
                assert r.DNSQuestion(service_name, const._TYPE_TXT, const._CLASS_IN) in last_sent.questions
                assert r.DNSQuestion(service_name, const._TYPE_A, const._CLASS_IN) in last_sent.questions
                assert r.DNSQuestion(service_name, const._TYPE_AAAA, const._CLASS_IN) in last_sent.questions
                assert service_info is None

                # Expext no further queries
                last_sent = None
                send_event.clear()
                _inject_response(
                    zc,
                    mock_incoming_msg(
                        [
                            r.DNSText(
                                service_name,
                                const._TYPE_TXT,
                                const._CLASS_IN | const._CLASS_UNIQUE,
                                ttl,
                                service_text,
                            ),
                            r.DNSService(
                                service_name,
                                const._TYPE_SRV,
                                const._CLASS_IN | const._CLASS_UNIQUE,
                                ttl,
                                0,
                                0,
                                80,
                                service_server,
                            ),
                            r.DNSAddress(
                                service_server,
                                const._TYPE_A,
                                const._CLASS_IN | const._CLASS_UNIQUE,
                                ttl,
                                socket.inet_pton(socket.AF_INET, service_address),
                            ),
                        ]
                    ),
                )
                send_event.wait(wait_time)
                assert last_sent is None
                assert service_info is not None

            finally:
                helper_thread.join()
                zc.remove_all_service_listeners()
                zc.close()