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_known_answer_supression(): 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." info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, server_name, addresses=[socket.inet_aton("10.0.1.2")]) zc.register_service(info) 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) packets = generated.packets() unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(packets[0]), "1.2.3.4", const._MDNS_PORT) assert unicast_out is None assert multicast_out is not None and multicast_out.answers generated = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(type_, const._TYPE_PTR, const._CLASS_IN) generated.add_question(question) generated.add_answer_at_time(info.dns_pointer(), now) packets = generated.packets() unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(packets[0]), "1.2.3.4", const._MDNS_PORT) assert unicast_out is None # If the answer is suppressed, the additional should be suppresed as well assert not multicast_out or not multicast_out.answers # Test A supression generated = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(server_name, const._TYPE_A, const._CLASS_IN) generated.add_question(question) packets = generated.packets() unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(packets[0]), "1.2.3.4", const._MDNS_PORT) assert unicast_out is None assert multicast_out is not None and multicast_out.answers generated = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(server_name, const._TYPE_A, const._CLASS_IN) generated.add_question(question) for dns_address in info.dns_addresses(): generated.add_answer_at_time(dns_address, now) packets = generated.packets() unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(packets[0]), "1.2.3.4", const._MDNS_PORT) assert unicast_out is None assert not multicast_out or not multicast_out.answers # Test SRV supression generated = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(registration_name, const._TYPE_SRV, const._CLASS_IN) generated.add_question(question) packets = generated.packets() unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(packets[0]), "1.2.3.4", const._MDNS_PORT) assert unicast_out is None assert multicast_out is not None and multicast_out.answers generated = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(registration_name, const._TYPE_SRV, const._CLASS_IN) generated.add_question(question) generated.add_answer_at_time(info.dns_service(), now) packets = generated.packets() unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(packets[0]), "1.2.3.4", const._MDNS_PORT) assert unicast_out is None # If the answer is suppressed, the additional should be suppresed as well assert not multicast_out or not multicast_out.answers # Test TXT supression generated = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(registration_name, const._TYPE_TXT, const._CLASS_IN) generated.add_question(question) packets = generated.packets() unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(packets[0]), "1.2.3.4", const._MDNS_PORT) assert unicast_out is None assert multicast_out is not None and multicast_out.answers generated = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(registration_name, const._TYPE_TXT, const._CLASS_IN) generated.add_question(question) generated.add_answer_at_time(info.dns_text(), now) packets = generated.packets() unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(packets[0]), "1.2.3.4", const._MDNS_PORT) assert unicast_out is None assert not multicast_out or not multicast_out.answers # unregister zc.unregister_service(info) zc.close()
def test_qu_response(): """Handle multicast incoming with the QU bit set.""" # instantiate a zeroconf instance zc = Zeroconf(interfaces=['127.0.0.1']) # service definition type_ = "_test-srvc-type._tcp.local." other_type_ = "_notthesame._tcp.local." name = "xxxyyy" registration_name = "%s.%s" % (name, type_) registration_name2 = "%s.%s" % (name, other_type_) desc = {'path': '/~paulsm/'} info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")]) info2 = ServiceInfo( other_type_, registration_name2, 80, 0, 0, desc, "ash-other.local.", addresses=[socket.inet_aton("10.0.4.2")], ) # register zc.register_service(info) def _validate_complete_response(query, 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 # With QU should respond to only unicast when the answer has been recently multicast query = r.DNSOutgoing(const._FLAGS_QR_QUERY | const._FLAGS_AA) question = r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN) question.unique = True # Set the QU bit assert question.unicast is True query.add_question(question) unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(query.packets()[0]), "1.2.3.4", const._MDNS_PORT) assert multicast_out is None _validate_complete_response(query, unicast_out) _clear_cache(zc) # With QU should respond to only multicast since the response hasn't been seen since 75% of the ttl query = r.DNSOutgoing(const._FLAGS_QR_QUERY | const._FLAGS_AA) question = r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN) question.unique = True # Set the QU bit assert question.unicast is True query.add_question(question) unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(query.packets()[0]), "1.2.3.4", const._MDNS_PORT) assert unicast_out is None _validate_complete_response(query, multicast_out) # With QU set and an authorative answer (probe) should respond to both unitcast and multicast since the response hasn't been seen since 75% of the ttl query = r.DNSOutgoing(const._FLAGS_QR_QUERY | const._FLAGS_AA) question = r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN) question.unique = True # Set the QU bit assert question.unicast is True query.add_question(question) query.add_authorative_answer(info2.dns_pointer()) unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(query.packets()[0]), "1.2.3.4", const._MDNS_PORT) _validate_complete_response(query, unicast_out) _validate_complete_response(query, multicast_out) _inject_response(zc, r.DNSIncoming(multicast_out.packets()[0])) # With the cache repopulated; should respond to only unicast when the answer has been recently multicast query = r.DNSOutgoing(const._FLAGS_QR_QUERY | const._FLAGS_AA) question = r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN) question.unique = True # Set the QU bit assert question.unicast is True query.add_question(question) unicast_out, multicast_out = zc.query_handler.response( r.DNSIncoming(query.packets()[0]), "1.2.3.4", const._MDNS_PORT) assert multicast_out is None _validate_complete_response(query, unicast_out) # unregister zc.unregister_service(info) zc.close()
def test_qu_response_only_sends_additionals_if_sends_answer(): """Test that a QU response does not send additionals unless it sends the answer as well.""" # instantiate a zeroconf instance zc = Zeroconf(interfaces=['127.0.0.1']) type_ = "_addtest1._tcp.local." name = "knownname" registration_name = "%s.%s" % (name, type_) desc = {'path': '/~paulsm/'} server_name = "ash-2.local." info = ServiceInfo( type_, registration_name, 80, 0, 0, desc, server_name, addresses=[socket.inet_aton("10.0.1.2")] ) zc.registry.add(info) type_2 = "_addtest2._tcp.local." name = "knownname" registration_name2 = "%s.%s" % (name, type_2) desc = {'path': '/~paulsm/'} server_name2 = "ash-3.local." info2 = ServiceInfo( type_2, registration_name2, 80, 0, 0, desc, server_name2, addresses=[socket.inet_aton("10.0.1.2")] ) zc.registry.add(info2) ptr_record = info.dns_pointer() # Add the PTR record to the cache zc.cache.add(ptr_record) # Add the A record to the cache with 50% ttl remaining a_record = info.dns_addresses()[0] a_record.set_created_ttl(current_time_millis() - (a_record.ttl * 1000 / 2), a_record.ttl) assert not a_record.is_recent(current_time_millis()) zc.cache.add(a_record) # With QU should respond to only unicast when the answer has been recently multicast # even if the additional has not been recently multicast query = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN) question.unicast = True # Set the QU bit assert question.unicast is True query.add_question(question) unicast_out, multicast_out = zc.query_handler.response( [r.DNSIncoming(packet) for packet in query.packets()], "1.2.3.4", const._MDNS_PORT ) assert multicast_out is None assert a_record in unicast_out.additionals assert unicast_out.answers[0][0] == ptr_record # Remove the 50% A record and add a 100% A record zc.cache.remove(a_record) a_record = info.dns_addresses()[0] assert a_record.is_recent(current_time_millis()) zc.cache.add(a_record) # With QU should respond to only unicast when the answer has been recently multicast # even if the additional has not been recently multicast query = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN) question.unicast = True # Set the QU bit assert question.unicast is True query.add_question(question) unicast_out, multicast_out = zc.query_handler.response( [r.DNSIncoming(packet) for packet in query.packets()], "1.2.3.4", const._MDNS_PORT ) assert multicast_out is None assert a_record in unicast_out.additionals assert unicast_out.answers[0][0] == ptr_record # Remove the 100% PTR record and add a 50% PTR record zc.cache.remove(ptr_record) ptr_record.set_created_ttl(current_time_millis() - (ptr_record.ttl * 1000 / 2), ptr_record.ttl) assert not ptr_record.is_recent(current_time_millis()) zc.cache.add(ptr_record) # With QU should respond to only multicast since the has less # than 75% of its ttl remaining query = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN) question.unicast = True # Set the QU bit assert question.unicast is True query.add_question(question) unicast_out, multicast_out = zc.query_handler.response( [r.DNSIncoming(packet) for packet in query.packets()], "1.2.3.4", const._MDNS_PORT ) assert multicast_out.answers[0][0] == ptr_record assert a_record in multicast_out.additionals assert info.dns_text() in multicast_out.additionals assert info.dns_service() in multicast_out.additionals assert unicast_out is None # Ask 2 QU questions, with info the PTR is at 50%, with info2 the PTR is at 100% # We should get back a unicast reply for info2, but info should be multicasted since its within 75% of its TTL # With QU should respond to only multicast since the has less # than 75% of its ttl remaining query = r.DNSOutgoing(const._FLAGS_QR_QUERY) question = r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN) question.unicast = True # Set the QU bit assert question.unicast is True query.add_question(question) question = r.DNSQuestion(info2.type, const._TYPE_PTR, const._CLASS_IN) question.unicast = True # Set the QU bit assert question.unicast is True query.add_question(question) zc.cache.add(info2.dns_pointer()) # Add 100% TTL for info2 to the cache unicast_out, multicast_out = zc.query_handler.response( [r.DNSIncoming(packet) for packet in query.packets()], "1.2.3.4", const._MDNS_PORT ) assert multicast_out.answers[0][0] == info.dns_pointer() assert info.dns_addresses()[0] in multicast_out.additionals assert info.dns_text() in multicast_out.additionals assert info.dns_service() in multicast_out.additionals assert unicast_out.answers[0][0] == info2.dns_pointer() assert info2.dns_addresses()[0] in unicast_out.additionals assert info2.dns_text() in unicast_out.additionals assert info2.dns_service() in unicast_out.additionals # unregister zc.registry.remove(info) zc.close()