Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/zeroconf/_dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,17 +480,21 @@ def __init__(
def write(self, out: 'DNSOutgoing') -> None:
"""Used in constructing an outgoing packet."""
bitmap = bytearray(b'\0' * 32)
total_octets = 0
for rdtype in self.rdtypes:
if rdtype > 255: # mDNS only supports window 0
continue
offset = rdtype % 256
byte = offset // 8
raise ValueError(f"rdtype {rdtype} is too large for NSEC")
byte = rdtype // 8
total_octets = byte + 1
bitmap[byte] |= 0x80 >> (offset % 8)
bitmap[byte] |= 0x80 >> (rdtype % 8)
if total_octets == 0:
# NSEC must have at least one rdtype
# Writing an empty bitmap is not allowed
raise ValueError("NSEC must have at least one rdtype")
out_bytes = bytes(bitmap[0:total_octets])
out.write_name(self.next_name)
out.write_short(0)
out.write_short(len(out_bytes))
out._write_byte(0) # Always window 0
out._write_byte(len(out_bytes))
out.write_string(out_bytes)

def __eq__(self, other: Any) -> bool:
Expand Down
37 changes: 34 additions & 3 deletions tests/test_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import unittest.mock
from typing import cast

import pytest

import zeroconf as r
from zeroconf import DNSHinfo, DNSIncoming, DNSText, const, current_time_millis

Expand Down Expand Up @@ -65,7 +67,22 @@ def test_parse_own_packet_nsec(self):
parsed = r.DNSIncoming(generated.packets()[0])
assert answer in parsed.answers()

# Types > 255 should be ignored
# Now with the higher RD type first
answer = r.DNSNsec(
'eufy HomeBase2-2464._hap._tcp.local.',
const._TYPE_NSEC,
const._CLASS_IN | const._CLASS_UNIQUE,
const._DNS_OTHER_TTL,
'eufy HomeBase2-2464._hap._tcp.local.',
[const._TYPE_SRV, const._TYPE_TXT],
)

generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
generated.add_answer_at_time(answer, 0)
parsed = r.DNSIncoming(generated.packets()[0])
assert answer in parsed.answers()

# Types > 255 should raise an exception
answer_invalid_types = r.DNSNsec(
'eufy HomeBase2-2464._hap._tcp.local.',
const._TYPE_NSEC,
Expand All @@ -76,8 +93,22 @@ def test_parse_own_packet_nsec(self):
)
generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
generated.add_answer_at_time(answer_invalid_types, 0)
parsed = r.DNSIncoming(generated.packets()[0])
assert answer in parsed.answers()
with pytest.raises(ValueError, match='rdtype 1000 is too large for NSEC'):
generated.packets()

# Empty rdtypes are not allowed
answer_invalid_types = r.DNSNsec(
'eufy HomeBase2-2464._hap._tcp.local.',
const._TYPE_NSEC,
const._CLASS_IN | const._CLASS_UNIQUE,
const._DNS_OTHER_TTL,
'eufy HomeBase2-2464._hap._tcp.local.',
[],
)
generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
generated.add_answer_at_time(answer_invalid_types, 0)
with pytest.raises(ValueError, match='NSEC must have at least one rdtype'):
generated.packets()

def test_parse_own_packet_response(self):
generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
Expand Down