@@ -701,3 +701,131 @@ def test_known_answer_supression_service_type_enumeration_query():
701701 zc .registry .remove (info )
702702 zc .registry .remove (info2 )
703703 zc .close ()
704+
705+
706+ def test_qu_response_only_sends_additionals_if_sends_answer ():
707+ """Test that a QU response does not send additionals unless it sends the answer as well."""
708+ # instantiate a zeroconf instance
709+ zc = Zeroconf (interfaces = ['127.0.0.1' ])
710+
711+ type_ = "_addtest1._tcp.local."
712+ name = "knownname"
713+ registration_name = "%s.%s" % (name , type_ )
714+ desc = {'path' : '/~paulsm/' }
715+ server_name = "ash-2.local."
716+ info = ServiceInfo (
717+ type_ , registration_name , 80 , 0 , 0 , desc , server_name , addresses = [socket .inet_aton ("10.0.1.2" )]
718+ )
719+ zc .registry .add (info )
720+
721+ type_2 = "_addtest2._tcp.local."
722+ name = "knownname"
723+ registration_name2 = "%s.%s" % (name , type_2 )
724+ desc = {'path' : '/~paulsm/' }
725+ server_name2 = "ash-3.local."
726+ info2 = ServiceInfo (
727+ type_2 , registration_name2 , 80 , 0 , 0 , desc , server_name2 , addresses = [socket .inet_aton ("10.0.1.2" )]
728+ )
729+ zc .registry .add (info2 )
730+
731+ ptr_record = info .dns_pointer ()
732+
733+ # Add the PTR record to the cache
734+ zc .cache .add (ptr_record )
735+
736+ # Add the A record to the cache with 50% ttl remaining
737+ a_record = info .dns_addresses ()[0 ]
738+ a_record ._set_created_ttl (current_time_millis () - (a_record .ttl * 1000 / 2 ), a_record .ttl )
739+ assert not a_record .is_recent (current_time_millis ())
740+ zc .cache .add (a_record )
741+
742+ # With QU should respond to only unicast when the answer has been recently multicast
743+ # even if the additional has not been recently multicast
744+ query = r .DNSOutgoing (const ._FLAGS_QR_QUERY )
745+ question = r .DNSQuestion (info .type , const ._TYPE_PTR , const ._CLASS_IN )
746+ question .unique = True # Set the QU bit
747+ assert question .unicast is True
748+ query .add_question (question )
749+
750+ unicast_out , multicast_out = zc .query_handler .response (
751+ [r .DNSIncoming (packet ) for packet in query .packets ()], "1.2.3.4" , const ._MDNS_PORT
752+ )
753+ assert multicast_out is None
754+ assert a_record in unicast_out .additionals
755+ assert unicast_out .answers [0 ][0 ] == ptr_record
756+
757+ # Remove the 50% A record and add a 100% A record
758+ zc .cache .remove (a_record )
759+ a_record = info .dns_addresses ()[0 ]
760+ assert a_record .is_recent (current_time_millis ())
761+ zc .cache .add (a_record )
762+ # With QU should respond to only unicast when the answer has been recently multicast
763+ # even if the additional has not been recently multicast
764+ query = r .DNSOutgoing (const ._FLAGS_QR_QUERY )
765+ question = r .DNSQuestion (info .type , const ._TYPE_PTR , const ._CLASS_IN )
766+ question .unique = True # Set the QU bit
767+ assert question .unicast is True
768+ query .add_question (question )
769+
770+ unicast_out , multicast_out = zc .query_handler .response (
771+ [r .DNSIncoming (packet ) for packet in query .packets ()], "1.2.3.4" , const ._MDNS_PORT
772+ )
773+ assert multicast_out is None
774+ assert a_record in unicast_out .additionals
775+ assert unicast_out .answers [0 ][0 ] == ptr_record
776+
777+ # Remove the 100% PTR record and add a 50% PTR record
778+ zc .cache .remove (ptr_record )
779+ ptr_record ._set_created_ttl (current_time_millis () - (ptr_record .ttl * 1000 / 2 ), ptr_record .ttl )
780+ assert not ptr_record .is_recent (current_time_millis ())
781+ zc .cache .add (ptr_record )
782+ # With QU should respond to only multicast since the has less
783+ # than 75% of its ttl remaining
784+ query = r .DNSOutgoing (const ._FLAGS_QR_QUERY )
785+ question = r .DNSQuestion (info .type , const ._TYPE_PTR , const ._CLASS_IN )
786+ question .unique = True # Set the QU bit
787+ assert question .unicast is True
788+ query .add_question (question )
789+
790+ unicast_out , multicast_out = zc .query_handler .response (
791+ [r .DNSIncoming (packet ) for packet in query .packets ()], "1.2.3.4" , const ._MDNS_PORT
792+ )
793+ assert multicast_out .answers [0 ][0 ] == ptr_record
794+ assert a_record in multicast_out .additionals
795+ assert info .dns_text () in multicast_out .additionals
796+ assert info .dns_service () in multicast_out .additionals
797+
798+ assert unicast_out is None
799+
800+ # Ask 2 QU questions, with info the PTR is at 50%, with info2 the PTR is at 100%
801+ # We should get back a unicast reply for info2, but info should be multicasted since its within 75% of its TTL
802+ # With QU should respond to only multicast since the has less
803+ # than 75% of its ttl remaining
804+ query = r .DNSOutgoing (const ._FLAGS_QR_QUERY )
805+ question = r .DNSQuestion (info .type , const ._TYPE_PTR , const ._CLASS_IN )
806+ question .unique = True # Set the QU bit
807+ assert question .unicast is True
808+ query .add_question (question )
809+
810+ question = r .DNSQuestion (info2 .type , const ._TYPE_PTR , const ._CLASS_IN )
811+ question .unique = True # Set the QU bit
812+ assert question .unicast is True
813+ query .add_question (question )
814+ zc .cache .add (info2 .dns_pointer ()) # Add 100% TTL for info2 to the cache
815+
816+ unicast_out , multicast_out = zc .query_handler .response (
817+ [r .DNSIncoming (packet ) for packet in query .packets ()], "1.2.3.4" , const ._MDNS_PORT
818+ )
819+ assert multicast_out .answers [0 ][0 ] == info .dns_pointer ()
820+ assert info .dns_addresses ()[0 ] in multicast_out .additionals
821+ assert info .dns_text () in multicast_out .additionals
822+ assert info .dns_service () in multicast_out .additionals
823+
824+ assert unicast_out .answers [0 ][0 ] == info2 .dns_pointer ()
825+ assert info2 .dns_addresses ()[0 ] in unicast_out .additionals
826+ assert info2 .dns_text () in unicast_out .additionals
827+ assert info2 .dns_service () in unicast_out .additionals
828+
829+ # unregister
830+ zc .registry .remove (info )
831+ zc .close ()
0 commit comments