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
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
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()
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()
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())
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())
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])
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
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()
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()
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])
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]
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)
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)
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()
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
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()
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
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
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
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
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
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
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
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
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
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)
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())
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())
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())