Skip to content

Commit bef8f59

Browse files
authored
Ensure all TXT, SRV, A records are unique
Fixes issues with shared records being used where they shouldn't be. PTR records should be shared, but SRV, TXT and A/AAAA records should be unique. Whilst mDNS and DNS-SD in theory support shared records for these types of record, they are not implemented in python-zeroconf at the moment. See zeroconf.check_service() method which verifies the service is unique on the network before registering.
1 parent 29432bf commit bef8f59

2 files changed

Lines changed: 91 additions & 20 deletions

File tree

zeroconf/__init__.py

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -887,13 +887,33 @@ def add_question(self, record: DNSQuestion) -> None:
887887
self.questions.append(record)
888888

889889
def add_answer(self, inp: DNSIncoming, record: DNSRecord) -> None:
890+
891+
"""Only support for unique answers"""
892+
if (
893+
record.type == _TYPE_TXT
894+
or record.type == _TYPE_SRV
895+
or record.type == _TYPE_A
896+
or record.type == _TYPE_AAAA
897+
):
898+
assert record.unique
899+
890900
"""Adds an answer"""
891901
if not record.suppressed_by(inp):
892902
self.add_answer_at_time(record, 0)
893903

894904
def add_answer_at_time(self, record: Optional[DNSRecord], now: Union[float, int]) -> None:
895905
"""Adds an answer if it does not expire by a certain time"""
896906
if record is not None:
907+
908+
"""Only support for unique answers"""
909+
if (
910+
record.type == _TYPE_TXT
911+
or record.type == _TYPE_SRV
912+
or record.type == _TYPE_A
913+
or record.type == _TYPE_AAAA
914+
):
915+
assert record.unique
916+
897917
if now == 0 or not record.is_expired(now):
898918
self.answers.append((record, now))
899919

@@ -937,6 +957,15 @@ def add_additional_answer(self, record: DNSRecord) -> None:
937957
o All address records (type "A" and "AAAA") named in the SRV rdata.
938958
939959
"""
960+
"""Only support for unique answers"""
961+
if (
962+
record.type == _TYPE_TXT
963+
or record.type == _TYPE_SRV
964+
or record.type == _TYPE_A
965+
or record.type == _TYPE_AAAA
966+
):
967+
assert record.unique
968+
940969
self.additionals.append(record)
941970

942971
def pack(self, format_: Union[bytes, str], value: Any) -> None:
@@ -2244,7 +2273,7 @@ def _broadcast_service(self, info: ServiceInfo) -> None:
22442273
DNSService(
22452274
info.name,
22462275
_TYPE_SRV,
2247-
_CLASS_IN,
2276+
_CLASS_IN | _CLASS_UNIQUE,
22482277
info.host_ttl,
22492278
info.priority,
22502279
info.weight,
@@ -2254,10 +2283,14 @@ def _broadcast_service(self, info: ServiceInfo) -> None:
22542283
0,
22552284
)
22562285

2257-
out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN, info.other_ttl, info.text), 0)
2286+
out.add_answer_at_time(
2287+
DNSText(info.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, info.other_ttl, info.text), 0
2288+
)
22582289
for address in info.addresses_by_version(IPVersion.All):
22592290
type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A
2260-
out.add_answer_at_time(DNSAddress(info.server, type_, _CLASS_IN, info.host_ttl, address), 0)
2291+
out.add_answer_at_time(
2292+
DNSAddress(info.server, type_, _CLASS_IN | _CLASS_UNIQUE, info.host_ttl, address), 0
2293+
)
22612294
self.send(out)
22622295
i += 1
22632296
next_time += _REGISTER_TIME
@@ -2286,7 +2319,7 @@ def unregister_service(self, info: ServiceInfo) -> None:
22862319
DNSService(
22872320
info.name,
22882321
_TYPE_SRV,
2289-
_CLASS_IN,
2322+
_CLASS_IN | _CLASS_UNIQUE,
22902323
0,
22912324
info.priority,
22922325
info.weight,
@@ -2295,11 +2328,13 @@ def unregister_service(self, info: ServiceInfo) -> None:
22952328
),
22962329
0,
22972330
)
2298-
out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
2331+
out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, 0, info.text), 0)
22992332

23002333
for address in info.addresses_by_version(IPVersion.All):
23012334
type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A
2302-
out.add_answer_at_time(DNSAddress(info.server, type_, _CLASS_IN, 0, address), 0)
2335+
out.add_answer_at_time(
2336+
DNSAddress(info.server, type_, _CLASS_IN | _CLASS_UNIQUE, 0, address), 0
2337+
)
23032338
self.send(out)
23042339
i += 1
23052340
next_time += _UNREGISTER_TIME
@@ -2322,7 +2357,7 @@ def unregister_all_services(self) -> None:
23222357
DNSService(
23232358
info.name,
23242359
_TYPE_SRV,
2325-
_CLASS_IN,
2360+
_CLASS_IN | _CLASS_UNIQUE,
23262361
0,
23272362
info.priority,
23282363
info.weight,
@@ -2331,10 +2366,14 @@ def unregister_all_services(self) -> None:
23312366
),
23322367
0,
23332368
)
2334-
out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0)
2369+
out.add_answer_at_time(
2370+
DNSText(info.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, 0, info.text), 0
2371+
)
23352372
for address in info.addresses_by_version(IPVersion.All):
23362373
type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A
2337-
out.add_answer_at_time(DNSAddress(info.server, type_, _CLASS_IN, 0, address), 0)
2374+
out.add_answer_at_time(
2375+
DNSAddress(info.server, type_, _CLASS_IN | _CLASS_UNIQUE, 0, address), 0
2376+
)
23382377
self.send(out)
23392378
i += 1
23402379
next_time += _UNREGISTER_TIME

zeroconf/test.py

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,17 @@ def test_parse_own_packet_question(self):
146146
def test_parse_own_packet_response(self):
147147
generated = r.DNSOutgoing(r._FLAGS_QR_RESPONSE)
148148
generated.add_answer_at_time(
149-
r.DNSService("æøå.local.", r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL, 0, 0, 80, "foo.local."), 0
149+
r.DNSService(
150+
"æøå.local.",
151+
r._TYPE_SRV,
152+
r._CLASS_IN | r._CLASS_UNIQUE,
153+
r._DNS_HOST_TTL,
154+
0,
155+
0,
156+
80,
157+
"foo.local.",
158+
),
159+
0,
150160
)
151161
parsed = r.DNSIncoming(generated.packet())
152162
self.assertEqual(len(generated.answers), 1)
@@ -166,13 +176,34 @@ def test_suppress_answer(self):
166176
question = r.DNSQuestion("testname.local.", r._TYPE_SRV, r._CLASS_IN)
167177
query_generated.add_question(question)
168178
answer1 = r.DNSService(
169-
"testname1.local.", r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL, 0, 0, 80, "foo.local."
179+
"testname1.local.",
180+
r._TYPE_SRV,
181+
r._CLASS_IN | r._CLASS_UNIQUE,
182+
r._DNS_HOST_TTL,
183+
0,
184+
0,
185+
80,
186+
"foo.local.",
170187
)
171188
staleanswer2 = r.DNSService(
172-
"testname2.local.", r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL / 2, 0, 0, 80, "foo.local."
189+
"testname2.local.",
190+
r._TYPE_SRV,
191+
r._CLASS_IN | r._CLASS_UNIQUE,
192+
r._DNS_HOST_TTL / 2,
193+
0,
194+
0,
195+
80,
196+
"foo.local.",
173197
)
174198
answer2 = r.DNSService(
175-
"testname2.local.", r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL, 0, 0, 80, "foo.local."
199+
"testname2.local.",
200+
r._TYPE_SRV,
201+
r._CLASS_IN | r._CLASS_UNIQUE,
202+
r._DNS_HOST_TTL,
203+
0,
204+
0,
205+
80,
206+
"foo.local.",
176207
)
177208
query_generated.add_answer_at_time(answer1, 0)
178209
query_generated.add_answer_at_time(staleanswer2, 0)
@@ -444,7 +475,8 @@ def generate_host(zc, host_name, type_):
444475
out = r.DNSOutgoing(r._FLAGS_QR_RESPONSE | r._FLAGS_AA)
445476
out.add_answer_at_time(r.DNSPointer(type_, r._TYPE_PTR, r._CLASS_IN, r._DNS_OTHER_TTL, name), 0)
446477
out.add_answer_at_time(
447-
r.DNSService(type_, r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL, 0, 0, 80, name), 0
478+
r.DNSService(type_, r._TYPE_SRV, r._CLASS_IN | r._CLASS_UNIQUE, r._DNS_HOST_TTL, 0, 0, 80, name),
479+
0,
448480
)
449481
zc.send(out)
450482

@@ -487,7 +519,7 @@ def mock_incoming_msg(service_state_change: r.ServiceStateChange) -> r.DNSIncomi
487519
ttl = 0
488520

489521
generated.add_answer_at_time(
490-
r.DNSPointer(service_type, r._TYPE_PTR, r._CLASS_IN | r._CLASS_UNIQUE, ttl, service_name), 0
522+
r.DNSPointer(service_type, r._TYPE_PTR, r._CLASS_IN, ttl, service_name), 0
491523
)
492524
generated.add_answer_at_time(
493525
r.DNSService(
@@ -670,7 +702,7 @@ def test_incoming_ipv6(self):
670702
addr = "2606:2800:220:1:248:1893:25c8:1946" # example.com
671703
packed = socket.inet_pton(socket.AF_INET6, addr)
672704
generated = r.DNSOutgoing(0)
673-
answer = r.DNSAddress('domain', r._TYPE_AAAA, r._CLASS_IN, 1, packed)
705+
answer = r.DNSAddress('domain', r._TYPE_AAAA, r._CLASS_IN | r._CLASS_UNIQUE, 1, packed)
674706
generated.add_additional_answer(answer)
675707
packet = generated.packet()
676708
parsed = r.DNSIncoming(packet)
@@ -886,6 +918,7 @@ def test_integration_with_listener_class(self):
886918
service_added = Event()
887919
service_removed = Event()
888920
service_updated = Event()
921+
service_updated2 = Event()
889922

890923
subtype_name = "My special Subtype"
891924
type_ = "_http._tcp.local."
@@ -902,7 +935,7 @@ def remove_service(self, zeroconf, type, name):
902935
service_removed.set()
903936

904937
def update_service(self, zeroconf, type, name):
905-
pass
938+
service_updated2.set()
906939

907940
class MySubListener(r.ServiceListener):
908941
def add_service(self, zeroconf, type, name):
@@ -966,7 +999,7 @@ def update_service(self, zeroconf, type, name):
966999
assert info is not None
9671000
assert info.properties[b'prop_none'] is False
9681001

969-
# Begin material test addition
1002+
# test TXT record update
9701003
sublistener = MySubListener()
9711004
zeroconf_browser.add_service_listener(registration_name, sublistener)
9721005
properties['prop_blank'] = b'an updated string'
@@ -981,7 +1014,6 @@ def update_service(self, zeroconf, type, name):
9811014
info = zeroconf_browser.get_service_info(type_, registration_name)
9821015
assert info is not None
9831016
assert info.properties[b'prop_blank'] == properties['prop_blank']
984-
# End material test addition
9851017

9861018
zeroconf_registrar.unregister_service(info_service)
9871019
service_removed.wait(1)
@@ -1043,7 +1075,7 @@ def mock_incoming_msg(service_state_change: r.ServiceStateChange) -> r.DNSIncomi
10431075
ttl = 0
10441076

10451077
generated.add_answer_at_time(
1046-
r.DNSPointer(service_type, r._TYPE_PTR, r._CLASS_IN | r._CLASS_UNIQUE, ttl, service_name), 0
1078+
r.DNSPointer(service_type, r._TYPE_PTR, r._CLASS_IN, ttl, service_name), 0
10471079
)
10481080
generated.add_answer_at_time(
10491081
r.DNSService(

0 commit comments

Comments
 (0)