def request(self, zc, timeout):
        """Returns true if the service could be discovered on the
        network, and updates this object with details discovered.
        """
        now = zeroconf.current_time_millis()
        delay = zeroconf._LISTENER_TIME
        next_ = now + delay
        last = now + timeout

        record_types_for_check_cache = [
            (zeroconf._TYPE_SRV, zeroconf._CLASS_IN),
            (zeroconf._TYPE_TXT, zeroconf._CLASS_IN),
        ]
        if self.server is not None:
            record_types_for_check_cache.append((zeroconf._TYPE_A, zeroconf._CLASS_IN))
        for record_type in record_types_for_check_cache:
            cached = zc.cache.get_by_details(self.name, *record_type)
            if cached:
                self.update_record(zc, now, cached)

        if None not in (self.server, self.address, self.text, self.port):
            return True

        try:
            zc.add_listener(self, zeroconf.DNSQuestion(self.name, zeroconf._TYPE_ANY, zeroconf._CLASS_IN))
            while None in (self.server, self.address, self.text, self.port):
                if last <= now:
                    return False
                if next_ <= now:
                    out = zeroconf.DNSOutgoing(zeroconf._FLAGS_QR_QUERY)
                    out.add_question(
                        zeroconf.DNSQuestion(self.name, zeroconf._TYPE_SRV, zeroconf._CLASS_IN))
                    
                    if self.port is not None:
                        out.add_answer_at_time(
                            zc.cache.get_by_details(
                                self.name, zeroconf._TYPE_SRV, zeroconf._CLASS_IN), now)
            
                    out.add_question(
                        zeroconf.DNSQuestion(self.name, zeroconf._TYPE_TXT, zeroconf._CLASS_IN))
                    out.add_answer_at_time(
                        zc.cache.get_by_details(
                            self.name, zeroconf._TYPE_TXT, zeroconf._CLASS_IN), now)
            
                    if self.server is not None:
                        out.add_question(
                            zeroconf.DNSQuestion(self.server, zeroconf._TYPE_A, zeroconf._CLASS_IN))
                        out.add_answer_at_time(
                            zc.cache.get_by_details(
                                self.server, zeroconf._TYPE_A, zeroconf._CLASS_IN), now)
                    zc.send(out)
                    next_ = now + delay
                    delay *= 2
        
                zc.wait(min(next_, last) - now)
                now = zeroconf.current_time_millis()
        finally:
            zc.remove_listener(self)

        return True
Example #2
0
    def request(self, zc, timeout):
        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
Example #3
0
def test_ptr_optimization():

    # instantiate a zeroconf instance
    zc = Zeroconf(interfaces=['127.0.0.1'])

    # service definition
    type_ = "_test-srvc-type._tcp.local."
    name = "xxxyyy"
    registration_name = "%s.%s" % (name, type_)

    desc = {'path': '/~paulsm/'}
    info = ServiceInfo(
        type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")]
    )

    # register
    zc.register_service(info)

    # Verify we won't respond for 1s with the same multicast
    query = r.DNSOutgoing(const._FLAGS_QR_QUERY)
    query.add_question(r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN))
    unicast_out, multicast_out = zc.query_handler.response(
        [r.DNSIncoming(packet) for packet in query.packets()], None, const._MDNS_PORT
    )
    assert unicast_out is None
    assert multicast_out is None

    # Clear the cache to allow responding again
    _clear_cache(zc)

    # Verify we will now respond
    query = r.DNSOutgoing(const._FLAGS_QR_QUERY)
    query.add_question(r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN))
    unicast_out, multicast_out = zc.query_handler.response(
        [r.DNSIncoming(packet) for packet in query.packets()], None, const._MDNS_PORT
    )
    assert multicast_out.id == query.id
    assert unicast_out is None
    assert multicast_out is not None
    has_srv = has_txt = has_a = False
    nbr_additionals = 0
    nbr_answers = len(multicast_out.answers)
    nbr_authorities = len(multicast_out.authorities)
    for answer in multicast_out.additionals:
        nbr_additionals += 1
        if answer.type == const._TYPE_SRV:
            has_srv = True
        elif answer.type == const._TYPE_TXT:
            has_txt = True
        elif answer.type == const._TYPE_A:
            has_a = True
    assert nbr_answers == 1 and nbr_additionals == 3 and nbr_authorities == 0
    assert has_srv and has_txt and has_a

    # unregister
    zc.unregister_service(info)
    zc.close()
Example #4
0
def test_any_query_for_ptr():
    """Test that queries for ANY will return PTR records."""
    zc = Zeroconf(interfaces=['127.0.0.1'])
    type_ = "_anyptr._tcp.local."
    name = "knownname"
    registration_name = "%s.%s" % (name, type_)
    desc = {'path': '/~paulsm/'}
    server_name = "ash-2.local."
    ipv6_address = socket.inet_pton(socket.AF_INET6, "2001:db8::1")
    info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, server_name, addresses=[ipv6_address])
    zc.registry.add(info)

    _clear_cache(zc)
    generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
    question = r.DNSQuestion(type_, const._TYPE_ANY, const._CLASS_IN)
    generated.add_question(question)
    packets = generated.packets()
    _, multicast_out = zc.query_handler.response(
        [r.DNSIncoming(packet) for packet in packets], "1.2.3.4", const._MDNS_PORT
    )
    assert multicast_out.answers[0][0].name == type_
    assert multicast_out.answers[0][0].alias == registration_name
    # unregister
    zc.registry.remove(info)
    zc.close()
Example #5
0
 def testLongName(self):
     generated = r.DNSOutgoing(r._FLAGS_QR_RESPONSE)
     question = r.DNSQuestion(
         "this.is.a.very.long.name.with.lots.of.parts.in.it.local.",
         r._TYPE_SRV, r._CLASS_IN)
     generated.addQuestion(question)
     parsed = r.DNSIncoming(generated.packet())
Example #6
0
 def test_same_name(self):
     name = "paired.local."
     generated = r.DNSOutgoing(r._FLAGS_QR_RESPONSE)
     question = r.DNSQuestion(name, r._TYPE_SRV, r._CLASS_IN)
     generated.add_question(question)
     generated.add_question(question)
     r.DNSIncoming(generated.packet())
Example #7
0
 def test_long_name(self):
     generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
     question = r.DNSQuestion(
         "this.is.a.very.long.name.with.lots.of.parts.in.it.local.",
         const._TYPE_SRV, const._CLASS_IN)
     generated.add_question(question)
     r.DNSIncoming(generated.packets()[0])
Example #8
0
def test_tc_bit_in_query_packet():
    """Verify the TC bit is set when known answers exceed the packet size."""
    out = r.DNSOutgoing(const._FLAGS_QR_QUERY | const._FLAGS_AA)
    type_ = "_hap._tcp.local."
    out.add_question(r.DNSQuestion(type_, const._TYPE_PTR, const._CLASS_IN))

    for i in range(30):
        out.add_answer_at_time(
            DNSText(
                ("HASS Bridge W9DN %s._hap._tcp.local." % i),
                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,
        )

    packets = out.packets()
    assert len(packets) == 3

    first_packet = r.DNSIncoming(packets[0])
    assert first_packet.flags & const._FLAGS_TC == const._FLAGS_TC
    assert first_packet.valid is True

    second_packet = r.DNSIncoming(packets[1])
    assert second_packet.flags & const._FLAGS_TC == const._FLAGS_TC
    assert second_packet.valid is True

    third_packet = r.DNSIncoming(packets[2])
    assert third_packet.flags & const._FLAGS_TC == 0
    assert third_packet.valid is True
Example #9
0
    def test_register_and_lookup_type_by_uppercase_name(self):
        # instantiate a zeroconf instance
        zc = Zeroconf(interfaces=['127.0.0.1'])
        type_ = "_mylowertype._tcp.local."
        name = "Home"
        registration_name = "%s.%s" % (name, type_)

        info = ServiceInfo(
            type_,
            name=registration_name,
            server="random123.local.",
            addresses=[socket.inet_pton(socket.AF_INET, "1.2.3.4")],
            port=80,
            properties={"version": "1.0"},
        )
        zc.register_service(info)
        _clear_cache(zc)
        info = ServiceInfo(type_, registration_name)
        info.load_from_cache(zc)
        assert info.addresses == []

        out = r.DNSOutgoing(const._FLAGS_QR_QUERY)
        out.add_question(
            r.DNSQuestion(type_.upper(), const._TYPE_PTR, const._CLASS_IN))
        zc.send(out)
        time.sleep(0.5)
        info = ServiceInfo(type_, registration_name)
        info.load_from_cache(zc)
        assert info.addresses == [socket.inet_pton(socket.AF_INET, "1.2.3.4")]
        assert info.properties == {b"version": b"1.0"}
        zc.close()
Example #10
0
def test_aaaa_query():
    """Test that queries for AAAA records work."""
    zc = Zeroconf(interfaces=['127.0.0.1'])
    type_ = "_knownservice._tcp.local."
    name = "knownname"
    registration_name = "%s.%s" % (name, type_)
    desc = {'path': '/~paulsm/'}
    server_name = "ash-2.local."
    ipv6_address = socket.inet_pton(socket.AF_INET6, "2001:db8::1")
    info = ServiceInfo(type_,
                       registration_name,
                       80,
                       0,
                       0,
                       desc,
                       server_name,
                       addresses=[ipv6_address])
    zc.register_service(info)

    _clear_cache(zc)
    generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
    question = r.DNSQuestion(server_name, const._TYPE_AAAA, const._CLASS_IN)
    generated.add_question(question)
    packets = generated.packets()
    _, multicast_out = zc.query_handler.response(r.DNSIncoming(packets[0]),
                                                 "1.2.3.4", const._MDNS_PORT)
    assert multicast_out.answers[0][0].address == ipv6_address
    # unregister
    zc.unregister_service(info)
    zc.close()
Example #11
0
 def test_match_question(self):
     generated = r.DNSOutgoing(r._FLAGS_QR_QUERY)
     question = r.DNSQuestion("testname.local.", r._TYPE_SRV, r._CLASS_IN)
     generated.add_question(question)
     parsed = r.DNSIncoming(generated.packet())
     self.assertEqual(len(generated.questions), 1)
     self.assertEqual(len(generated.questions), len(parsed.questions))
     self.assertEqual(question, parsed.questions[0])
Example #12
0
 def test_match_question(self):
     generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
     question = r.DNSQuestion("testname.local.", const._TYPE_SRV,
                              const._CLASS_IN)
     generated.add_question(question)
     parsed = r.DNSIncoming(generated.packets()[0])
     assert len(generated.questions) == 1
     assert len(generated.questions) == len(parsed.questions)
     assert question == parsed.questions[0]
Example #13
0
def test_ptr_optimization():

    # instantiate a zeroconf instance
    zc = Zeroconf(interfaces=['127.0.0.1'])

    # service definition
    type_ = "_test-srvc-type._tcp.local."
    name = "xxxyyy"
    registration_name = "%s.%s" % (name, type_)

    desc = {'path': '/~paulsm/'}
    info = ServiceInfo(type_, registration_name, socket.inet_aton("10.0.1.2"),
                       80, 0, 0, desc, "ash-2.local.")

    # we are going to monkey patch the zeroconf send to check packet sizes
    old_send = zc.send

    nbr_answers = nbr_additionals = nbr_authorities = 0
    has_srv = has_txt = has_a = False

    def send(out, addr=r._MDNS_ADDR, port=r._MDNS_PORT):
        """Sends an outgoing packet."""
        nonlocal nbr_answers, nbr_additionals, nbr_authorities
        nonlocal has_srv, has_txt, has_a

        nbr_answers += len(out.answers)
        nbr_authorities += len(out.authorities)
        for answer in out.additionals:
            nbr_additionals += 1
            if answer.type == r._TYPE_SRV:
                has_srv = True
            elif answer.type == r._TYPE_TXT:
                has_txt = True
            elif answer.type == r._TYPE_A:
                has_a = True

        old_send(out, addr=addr, port=port)

    # monkey patch the zeroconf send
    setattr(zc, "send", send)

    # register
    zc.register_service(info)
    nbr_answers = nbr_additionals = nbr_authorities = 0

    # query
    query = r.DNSOutgoing(r._FLAGS_QR_QUERY | r._FLAGS_AA)
    query.add_question(r.DNSQuestion(info.type, r._TYPE_PTR, r._CLASS_IN))
    zc.handle_query(r.DNSIncoming(query.packet()), r._MDNS_ADDR, r._MDNS_PORT)
    assert nbr_answers == 1 and nbr_additionals == 3 and nbr_authorities == 0
    assert has_srv and has_txt and has_a

    # unregister
    zc.unregister_service(info)
Example #14
0
 def test_numbers_questions(self):
     generated = r.DNSOutgoing(r._FLAGS_QR_RESPONSE)
     question = r.DNSQuestion("testname.local.", r._TYPE_SRV, r._CLASS_IN)
     for i in range(10):
         generated.add_question(question)
     bytes = generated.packet()
     (num_questions, num_answers, num_authorities, num_additionals) = struct.unpack('!4H', bytes[4:12])
     self.assertEqual(num_questions, 10)
     self.assertEqual(num_answers, 0)
     self.assertEqual(num_authorities, 0)
     self.assertEqual(num_additionals, 0)
Example #15
0
def test_multi_packet_known_answer_supression():
    zc = Zeroconf(interfaces=['127.0.0.1'])
    type_ = "_handlermultis._tcp.local."
    name = "knownname"
    name2 = "knownname2"
    name3 = "knownname3"

    registration_name = "%s.%s" % (name, type_)
    registration2_name = "%s.%s" % (name2, type_)
    registration3_name = "%s.%s" % (name3, type_)

    desc = {'path': '/~paulsm/'}
    server_name = "ash-2.local."
    server_name2 = "ash-3.local."
    server_name3 = "ash-4.local."

    info = ServiceInfo(
        type_, registration_name, 80, 0, 0, desc, server_name, addresses=[socket.inet_aton("10.0.1.2")]
    )
    info2 = ServiceInfo(
        type_, registration2_name, 80, 0, 0, desc, server_name2, addresses=[socket.inet_aton("10.0.1.2")]
    )
    info3 = ServiceInfo(
        type_, registration3_name, 80, 0, 0, desc, server_name3, addresses=[socket.inet_aton("10.0.1.2")]
    )
    zc.registry.add(info)
    zc.registry.add(info2)
    zc.registry.add(info3)

    now = current_time_millis()
    _clear_cache(zc)
    # Test PTR supression
    generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
    question = r.DNSQuestion(type_, const._TYPE_PTR, const._CLASS_IN)
    generated.add_question(question)
    for _ in range(1000):
        # Add so many answers we end up with another packet
        generated.add_answer_at_time(info.dns_pointer(), now)
    generated.add_answer_at_time(info2.dns_pointer(), now)
    generated.add_answer_at_time(info3.dns_pointer(), now)
    packets = generated.packets()
    assert len(packets) > 1
    unicast_out, multicast_out = zc.query_handler.response(
        [r.DNSIncoming(packet) for packet in packets], "1.2.3.4", const._MDNS_PORT
    )
    assert unicast_out is None
    assert multicast_out is None
    # unregister
    zc.registry.remove(info)
    zc.registry.remove(info2)
    zc.registry.remove(info3)
    zc.close()
Example #16
0
 def test_numbers_questions(self):
     generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
     question = r.DNSQuestion("testname.local.", const._TYPE_SRV,
                              const._CLASS_IN)
     for i in range(10):
         generated.add_question(question)
     bytes = generated.packets()[0]
     (num_questions, num_answers, num_authorities,
      num_additionals) = struct.unpack('!4H', bytes[4:12])
     assert num_questions == 10
     assert num_answers == 0
     assert num_authorities == 0
     assert num_additionals == 0
Example #17
0
def test_unicast_response():
    """Ensure we send a unicast response when the source port is not the MDNS port."""
    # instantiate a zeroconf instance
    zc = Zeroconf(interfaces=['127.0.0.1'])

    # service definition
    type_ = "_test-srvc-type._tcp.local."
    name = "xxxyyy"
    registration_name = "%s.%s" % (name, type_)
    desc = {'path': '/~paulsm/'}
    info = ServiceInfo(type_,
                       registration_name,
                       80,
                       0,
                       0,
                       desc,
                       "ash-2.local.",
                       addresses=[socket.inet_aton("10.0.1.2")])
    # register
    zc.register_service(info)
    _clear_cache(zc)

    # query
    query = r.DNSOutgoing(const._FLAGS_QR_QUERY | const._FLAGS_AA)
    query.add_question(
        r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN))
    unicast_out, multicast_out = zc.query_handler.response(
        r.DNSIncoming(query.packets()[0]), "1.2.3.4", 1234)
    for out in (unicast_out, multicast_out):
        assert out.id == query.id
        has_srv = has_txt = has_a = False
        nbr_additionals = 0
        nbr_answers = len(out.answers)
        nbr_authorities = len(out.authorities)
        for answer in out.additionals:
            nbr_additionals += 1
            if answer.type == const._TYPE_SRV:
                has_srv = True
            elif answer.type == const._TYPE_TXT:
                has_txt = True
            elif answer.type == const._TYPE_A:
                has_a = True
        assert nbr_answers == 1 and nbr_additionals == 3 and nbr_authorities == 0
        assert has_srv and has_txt and has_a

    # unregister
    zc.unregister_service(info)
    zc.close()
Example #18
0
    def test_suppress_answer(self):
        query_generated = r.DNSOutgoing(r._FLAGS_QR_QUERY)
        question = r.DNSQuestion("testname.local.", r._TYPE_SRV, r._CLASS_IN)
        query_generated.add_question(question)
        answer1 = r.DNSService(
            "testname1.local.", r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL, 0, 0, 80, "foo.local."
        )
        staleanswer2 = r.DNSService(
            "testname2.local.", r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL / 2, 0, 0, 80, "foo.local."
        )
        answer2 = r.DNSService(
            "testname2.local.", r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL, 0, 0, 80, "foo.local."
        )
        query_generated.add_answer_at_time(answer1, 0)
        query_generated.add_answer_at_time(staleanswer2, 0)
        query = r.DNSIncoming(query_generated.packet())

        # Should be suppressed
        response = r.DNSOutgoing(r._FLAGS_QR_RESPONSE)
        response.add_answer(query, answer1)
        assert len(response.answers) == 0

        # Should not be suppressed, TTL in query is too short
        response.add_answer(query, answer2)
        assert len(response.answers) == 1

        # Should not be suppressed, name is different
        tmp = copy.copy(answer1)
        tmp.name = "testname3.local."
        response.add_answer(query, tmp)
        assert len(response.answers) == 2

        # Should not be suppressed, type is different
        tmp = copy.copy(answer1)
        tmp.type = r._TYPE_A
        response.add_answer(query, tmp)
        assert len(response.answers) == 3

        # Should not be suppressed, class is different
        tmp = copy.copy(answer1)
        tmp.class_ = r._CLASS_NONE
        response.add_answer(query, tmp)
        assert len(response.answers) == 4
Example #19
0
    def test_questions_do_not_end_up_every_packet(self):
        """Test that questions are not sent again when multiple packets are needed.

        https://datatracker.ietf.org/doc/html/rfc6762#section-7.2
        Sometimes a Multicast DNS querier will already have too many answers
        to fit in the Known-Answer Section of its query packets....  It MUST
        immediately follow the packet with another query packet containing no
        questions and as many more Known-Answer records as will fit.
        """

        generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
        for i in range(35):
            question = r.DNSQuestion(f"testname{i}.local.", const._TYPE_SRV,
                                     const._CLASS_IN)
            generated.add_question(question)
            answer = r.DNSService(
                f"testname{i}.local.",
                const._TYPE_SRV,
                const._CLASS_IN | const._CLASS_UNIQUE,
                const._DNS_HOST_TTL,
                0,
                0,
                80,
                f"foo{i}.local.",
            )
            generated.add_answer_at_time(answer, 0)

        assert len(generated.questions) == 35
        assert len(generated.answers) == 35

        packets = generated.packets()
        assert len(packets) == 2
        assert len(packets[0]) <= const._MAX_MSG_TYPICAL
        assert len(packets[1]) <= const._MAX_MSG_TYPICAL

        parsed1 = r.DNSIncoming(packets[0])
        assert len(parsed1.questions) == 35
        assert len(parsed1.answers) == 33

        parsed2 = r.DNSIncoming(packets[1])
        assert len(parsed2.questions) == 0
        assert len(parsed2.answers) == 2
Example #20
0
    def test_many_questions(self):
        """Test many questions get seperated into multiple packets."""
        generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
        questions = []
        for i in range(100):
            question = r.DNSQuestion(f"testname{i}.local.", const._TYPE_SRV,
                                     const._CLASS_IN)
            generated.add_question(question)
            questions.append(question)
        assert len(generated.questions) == 100

        packets = generated.packets()
        assert len(packets) == 2
        assert len(packets[0]) < const._MAX_MSG_TYPICAL
        assert len(packets[1]) < const._MAX_MSG_TYPICAL

        parsed1 = r.DNSIncoming(packets[0])
        assert len(parsed1.questions) == 85
        parsed2 = r.DNSIncoming(packets[1])
        assert len(parsed2.questions) == 15
Example #21
0
    def test_massive_probe_packet_split(self):
        """Test probe with many authorative answers."""
        generated = r.DNSOutgoing(const._FLAGS_QR_QUERY | const._FLAGS_AA)
        questions = []
        for _ in range(30):
            question = r.DNSQuestion(f"_hap._tcp.local.", const._TYPE_PTR,
                                     const._CLASS_IN | const._CLASS_UNIQUE)
            generated.add_question(question)
            questions.append(question)
        assert len(generated.questions) == 30
        now = current_time_millis()
        for _ in range(200):
            authorative_answer = r.DNSPointer(
                "myservice{i}_tcp._tcp.local.",
                const._TYPE_PTR,
                const._CLASS_IN | const._CLASS_UNIQUE,
                const._DNS_OTHER_TTL,
                '123.local.',
            )
            generated.add_authorative_answer(authorative_answer)
        packets = generated.packets()
        assert len(packets) == 3
        assert len(packets[0]) <= const._MAX_MSG_TYPICAL
        assert len(packets[1]) <= const._MAX_MSG_TYPICAL
        assert len(packets[2]) <= const._MAX_MSG_TYPICAL

        parsed1 = r.DNSIncoming(packets[0])
        assert parsed1.questions[0].unicast is True
        assert len(parsed1.questions) == 30
        assert parsed1.num_authorities == 88
        assert parsed1.flags & const._FLAGS_TC == const._FLAGS_TC
        parsed2 = r.DNSIncoming(packets[1])
        assert len(parsed2.questions) == 0
        assert parsed2.num_authorities == 101
        assert parsed2.flags & const._FLAGS_TC == const._FLAGS_TC
        parsed3 = r.DNSIncoming(packets[2])
        assert len(parsed3.questions) == 0
        assert parsed3.num_authorities == 11
        assert parsed3.flags & const._FLAGS_TC == 0
Example #22
0
    def test_many_questions_with_many_known_answers(self):
        """Test many questions and known answers get seperated into multiple packets."""
        generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
        questions = []
        for _ in range(30):
            question = r.DNSQuestion(f"_hap._tcp.local.", const._TYPE_PTR,
                                     const._CLASS_IN)
            generated.add_question(question)
            questions.append(question)
        assert len(generated.questions) == 30
        now = current_time_millis()
        for _ in range(200):
            known_answer = r.DNSPointer(
                "myservice{i}_tcp._tcp.local.",
                const._TYPE_PTR,
                const._CLASS_IN | const._CLASS_UNIQUE,
                const._DNS_OTHER_TTL,
                '123.local.',
            )
            generated.add_answer_at_time(known_answer, now)
        packets = generated.packets()
        assert len(packets) == 3
        assert len(packets[0]) <= const._MAX_MSG_TYPICAL
        assert len(packets[1]) <= const._MAX_MSG_TYPICAL
        assert len(packets[2]) <= const._MAX_MSG_TYPICAL

        parsed1 = r.DNSIncoming(packets[0])
        assert len(parsed1.questions) == 30
        assert len(parsed1.answers) == 88
        assert parsed1.flags & const._FLAGS_TC == const._FLAGS_TC
        parsed2 = r.DNSIncoming(packets[1])
        assert len(parsed2.questions) == 0
        assert len(parsed2.answers) == 101
        assert parsed2.flags & const._FLAGS_TC == const._FLAGS_TC
        parsed3 = r.DNSIncoming(packets[2])
        assert len(parsed3.questions) == 0
        assert len(parsed3.answers) == 11
        assert parsed3.flags & const._FLAGS_TC == 0
Example #23
0
def test_records_same_packet_share_fate():
    """Test records in the same packet all have the same created time."""
    out = r.DNSOutgoing(const._FLAGS_QR_QUERY | const._FLAGS_AA)
    type_ = "_hap._tcp.local."
    out.add_question(r.DNSQuestion(type_, const._TYPE_PTR, const._CLASS_IN))

    for i in range(30):
        out.add_answer_at_time(
            DNSText(
                ("HASS Bridge W9DN %s._hap._tcp.local." % i),
                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 packet in out.packets():
        dnsin = DNSIncoming(packet)
        first_time = dnsin.answers[0].created
        for answer in dnsin.answers:
            assert answer.created == first_time
Example #24
0
    def test_ttl(self):

        # instantiate a zeroconf instance
        zc = Zeroconf(interfaces=["127.0.0.1"])

        # service definition
        type_ = "_test-srvc-type._tcp.local."
        name = "xxxyyy"
        registration_name = "%s.%s" % (name, type_)

        desc = {"path": "/~paulsm/"}
        info = ServiceInfo(
            type_,
            registration_name,
            socket.inet_aton("10.0.1.2"),
            80,
            0,
            0,
            desc,
            "ash-2.local.",
        )

        # we are going to monkey patch the zeroconf send to check packet sizes
        old_send = zc.send

        # needs to be a list so that we can modify it in our phony send
        nbr_answers = [0, None]
        nbr_additionals = [0, None]
        nbr_authorities = [0, None]

        def send(out, addr=r._MDNS_ADDR, port=r._MDNS_PORT):
            """Sends an outgoing packet."""
            for answer, time_ in out.answers:
                nbr_answers[0] += 1
                assert answer.ttl == expected_ttl
            for answer in out.additionals:
                nbr_additionals[0] += 1
                assert answer.ttl == expected_ttl
            for answer in out.authorities:
                nbr_authorities[0] += 1
                assert answer.ttl == expected_ttl
            old_send(out, addr=addr, port=port)

        # monkey patch the zeroconf send
        zc.send = send

        # register service with default TTL
        expected_ttl = r._DNS_TTL
        zc.register_service(info)
        assert (nbr_answers[0] == 12 and nbr_additionals[0] == 0
                and nbr_authorities[0] == 3)
        nbr_answers[0] = nbr_additionals[0] = nbr_authorities[0] = 0

        # query
        query = r.DNSOutgoing(r._FLAGS_QR_QUERY | r._FLAGS_AA)
        query.add_question(r.DNSQuestion(info.type, r._TYPE_PTR, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.name, r._TYPE_SRV, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.name, r._TYPE_TXT, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.server, r._TYPE_A, r._CLASS_IN))
        zc.handle_query(query, r._MDNS_ADDR, r._MDNS_PORT)
        assert (nbr_answers[0] == 4 and nbr_additionals[0] == 1
                and nbr_authorities[0] == 0)
        nbr_answers[0] = nbr_additionals[0] = nbr_authorities[0] = 0

        # unregister
        expected_ttl = 0
        zc.unregister_service(info)
        assert (nbr_answers[0] == 12 and nbr_additionals[0] == 0
                and nbr_authorities[0] == 0)
        nbr_answers[0] = nbr_additionals[0] = nbr_authorities[0] = 0

        # register service with custom TTL
        expected_ttl = r._DNS_TTL * 2
        assert expected_ttl != r._DNS_TTL
        zc.register_service(info, ttl=expected_ttl)
        assert (nbr_answers[0] == 12 and nbr_additionals[0] == 0
                and nbr_authorities[0] == 3)
        nbr_answers[0] = nbr_additionals[0] = nbr_authorities[0] = 0

        # query
        query = r.DNSOutgoing(r._FLAGS_QR_QUERY | r._FLAGS_AA)
        query.add_question(r.DNSQuestion(info.type, r._TYPE_PTR, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.name, r._TYPE_SRV, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.name, r._TYPE_TXT, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.server, r._TYPE_A, r._CLASS_IN))
        zc.handle_query(query, r._MDNS_ADDR, r._MDNS_PORT)
        assert (nbr_answers[0] == 4 and nbr_additionals[0] == 1
                and nbr_authorities[0] == 0)
        nbr_answers[0] = nbr_additionals[0] = nbr_authorities[0] = 0

        # unregister
        expected_ttl = 0
        zc.unregister_service(info)
        assert (nbr_answers[0] == 12 and nbr_additionals[0] == 0
                and nbr_authorities[0] == 0)
        nbr_answers[0] = nbr_additionals[0] = nbr_authorities[0] = 0
Example #25
0
 def test_dns_question_repr(self):
     question = r.DNSQuestion("irrelevant", r._TYPE_SRV,
                              r._CLASS_IN | r._CLASS_UNIQUE)
     repr(question)
     assert not question != question
Example #26
0
    def test_ttl(self):

        # instantiate a zeroconf instance
        zc = Zeroconf(interfaces=['127.0.0.1'])

        # service definition
        type_ = "_test-srvc-type._tcp.local."
        name = "xxxyyy"
        registration_name = "%s.%s" % (name, type_)

        desc = {'path': '/~paulsm/'}
        info = ServiceInfo(
            type_, registration_name, socket.inet_aton("10.0.1.2"), 80, 0, 0, desc, "ash-2.local."
        )

        # we are going to monkey patch the zeroconf send to check packet sizes
        old_send = zc.send

        nbr_answers = nbr_additionals = nbr_authorities = 0

        def get_ttl(record_type):
            if expected_ttl is not None:
                return expected_ttl
            elif record_type in [r._TYPE_A, r._TYPE_SRV]:
                return r._DNS_HOST_TTL
            else:
                return r._DNS_OTHER_TTL

        def send(out, addr=r._MDNS_ADDR, port=r._MDNS_PORT):
            """Sends an outgoing packet."""
            nonlocal nbr_answers, nbr_additionals, nbr_authorities

            for answer, time_ in out.answers:
                nbr_answers += 1
                assert answer.ttl == get_ttl(answer.type)
            for answer in out.additionals:
                nbr_additionals += 1
                assert answer.ttl == get_ttl(answer.type)
            for answer in out.authorities:
                nbr_authorities += 1
                assert answer.ttl == get_ttl(answer.type)
            old_send(out, addr=addr, port=port)

        # monkey patch the zeroconf send
        setattr(zc, "send", send)

        # register service with default TTL
        expected_ttl = None
        zc.register_service(info)
        assert nbr_answers == 12 and nbr_additionals == 0 and nbr_authorities == 3
        nbr_answers = nbr_additionals = nbr_authorities = 0

        # query
        query = r.DNSOutgoing(r._FLAGS_QR_QUERY | r._FLAGS_AA)
        query.add_question(r.DNSQuestion(info.type, r._TYPE_PTR, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.name, r._TYPE_SRV, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.name, r._TYPE_TXT, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.server, r._TYPE_A, r._CLASS_IN))
        zc.handle_query(r.DNSIncoming(query.packet()), r._MDNS_ADDR, r._MDNS_PORT)
        assert nbr_answers == 4 and nbr_additionals == 4 and nbr_authorities == 0
        nbr_answers = nbr_additionals = nbr_authorities = 0

        # unregister
        expected_ttl = 0
        zc.unregister_service(info)
        assert nbr_answers == 12 and nbr_additionals == 0 and nbr_authorities == 0
        nbr_answers = nbr_additionals = nbr_authorities = 0

        # register service with custom TTL
        expected_ttl = r._DNS_HOST_TTL * 2
        assert expected_ttl != r._DNS_HOST_TTL
        zc.register_service(info, ttl=expected_ttl)
        assert nbr_answers == 12 and nbr_additionals == 0 and nbr_authorities == 3
        nbr_answers = nbr_additionals = nbr_authorities = 0

        # query
        query = r.DNSOutgoing(r._FLAGS_QR_QUERY | r._FLAGS_AA)
        query.add_question(r.DNSQuestion(info.type, r._TYPE_PTR, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.name, r._TYPE_SRV, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.name, r._TYPE_TXT, r._CLASS_IN))
        query.add_question(r.DNSQuestion(info.server, r._TYPE_A, r._CLASS_IN))
        zc.handle_query(r.DNSIncoming(query.packet()), r._MDNS_ADDR, r._MDNS_PORT)
        assert nbr_answers == 4 and nbr_additionals == 4 and nbr_authorities == 0
        nbr_answers = nbr_additionals = nbr_authorities = 0

        # unregister
        expected_ttl = 0
        zc.unregister_service(info)
        assert nbr_answers == 12 and nbr_additionals == 0 and nbr_authorities == 0
        nbr_answers = nbr_additionals = nbr_authorities = 0
Example #27
0
 def test_exceedingly_long_name_part(self):
     name = "%s.local." % ("a" * 1000)
     generated = r.DNSOutgoing(r._FLAGS_QR_RESPONSE)
     question = r.DNSQuestion(name, r._TYPE_SRV, r._CLASS_IN)
     generated.add_question(question)
     self.assertRaises(r.NamePartTooLongException, generated.packet)
Example #28
0
 def test_exceedingly_long_name(self):
     generated = r.DNSOutgoing(r._FLAGS_QR_RESPONSE)
     name = "%slocal." % ("part." * 1000)
     question = r.DNSQuestion(name, r._TYPE_SRV, r._CLASS_IN)
     generated.add_question(question)
     r.DNSIncoming(generated.packet())
Example #29
0
 def testParseOwnPacketQuestion(self):
     generated = r.DNSOutgoing(r._FLAGS_QR_QUERY)
     generated.addQuestion(
         r.DNSQuestion("testname.local.", r._TYPE_SRV, r._CLASS_IN))
     parsed = r.DNSIncoming(generated.packet())
Example #30
0
 def test_parse_own_packet_question(self):
     generated = r.DNSOutgoing(r._FLAGS_QR_QUERY)
     generated.add_question(
         r.DNSQuestion("testname.local.", r._TYPE_SRV, r._CLASS_IN))
     r.DNSIncoming(generated.packet())