Skip to content

Separately send large mDNS responses to comply with RFC 6762#248

Merged
jstasiak merged 14 commits into
python-zeroconf:masterfrom
gjbadros:master
May 26, 2020
Merged

Separately send large mDNS responses to comply with RFC 6762#248
jstasiak merged 14 commits into
python-zeroconf:masterfrom
gjbadros:master

Conversation

@gjbadros

Copy link
Copy Markdown

This fixes issue #245

Split up large multi-response packets into separate packets instead of relying on IP Fragmentation. IP Fragmentation of mDNS packets causes ChromeCast Audios to crash their mDNS responder processes and RFC 6762
(https://tools.ietf.org/html/rfc6762) section 17 states some
requirements for Multicast DNS Message Size, and the fourth paragraph reads:

"A Multicast DNS packet larger than the interface MTU, which is sent
using fragments, MUST NOT contain more than one resource record."

This change makes this implementation conform with this MUST NOT clause.

gjbadros added 9 commits May 8, 2020 19:11
… responses won't fit. This is required by section 17 of RFC6762. Make packet() into packets() and have it return a list of the packets that need to be sent to each of the interfaces.
…the question in the initial packet) and fixed the tests. In particular, the lots_of_names test's behaviour is explicitly supposed to change because the old behaviour was non-conforming.
@coveralls

coveralls commented May 23, 2020

Copy link
Copy Markdown

Coverage Status

Coverage decreased (-0.1%) to 92.705% when pulling 6d2a7a1 on gjbadros:master into 445d7f5 on jstasiak:master.

@gjbadros

Copy link
Copy Markdown
Author

The "X" is now just a very minor code coverage reduction and the missed code coverage is related to the change.

@jstasiak, what are your thoughts? It's pretty clear that the old implementation did not handle extra long packets with lots of responses, and I believe I've done a correct implementation of sending them out as multiple packets. I'm certain this fixes the ChromeCast Audios failing on my network.

Thanks!

@jstasiak jstasiak left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @gjbadros, it's a solid piece of work! I'll have another look tomorrow, but I think after my comments are addressed it's ready to be merged. I'd not worry about the logging method not being called and lowering the coverage slightly – it's just a number.

Comment thread zeroconf/__init__.py Outdated


class DNSOutgoing:
class DNSOutgoing(QuietLogger):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember the details about QuietLogger precisely, but its methods are static so let's not inherit from it (I know some classes already do that – I'll remove it).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I changed mine back but didn't change the others to avoid a merge conflict since it sounds like you're planning to fix those.

Comment thread zeroconf/__init__.py Outdated
def write_record(self, record: DNSRecord, now: float, allow_long: bool = False) -> bool:
"""Writes a record (answer, authoritative answer, additional) to
the packet"""
the packet. Returns True on success. """

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you mention the failure scenarios in this docstring as well?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread zeroconf/__init__.py Outdated
Generally, you want to use packets() in case the response
does not fit in a single packet, but this exists for
backward compatibility."""
p = self.packets()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to just call the variable packets, not much saved by shortening it to just p and it can help avoiding some confusion.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I changed it; I don't like local variable names shadowing class instances/functions, but I get that other people don't like short variable names. My philosophy is that a variable names's length should be proportional to its scope and in this case the scope is tiny so I was comfortable with a single-letter variable name.

Comment thread zeroconf/__init__.py Outdated
return b''

def packets(self) -> List[bytes]:
"""Returns a list of strings containing the packets' bytes

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"bytestrings" instead of "strings"? "strings" implies text.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I think I was just copying the former docstring language, but this is a good idea to be clear about.

Comment thread zeroconf/__init__.py
self.insert_short(0, len(self.answers) - overrun_answers)
self.insert_short(0, len(self.questions))
questions_written += 1
allow_long = True # at most one answer is allowed to be a long packet

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it at most one answer per packet or per group of packets?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added docstring -- multiple answers per packet are fine. It's just the case of having so many answers that it overflows the smaller 1470ish MTU of ethernet -- in that scenario, the application layer MUST send separate application-level responses rather than rely on IP fragmentation to break up a bigger packet. The only exception is when a single answer does not fit inside the smaller MTU in which case it's allowed to be bigger. I suspect that scenario will still crash ChromeCast Audios, but that situation is exceedingly rare, whereas many responses that exceed 1470 bytes happens within a large but not crazy set of devices.

Comment thread zeroconf/test.py Outdated
# wait until the browse request packet has maxed out in size
sleep_count = 0
while sleep_count < 100 and longest_packet_len < r._MAX_MSG_ABSOLUTE - 100:
while sleep_count < 100 and longest_packet_len < (5 * r._MAX_MSG_TYPICAL) - 100:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does 5 have some specific meaning in this context?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I changed it back since it's really immaterial now, and I explain why it doesn't matter in a comment.

gjbadros added 2 commits May 24, 2020 16:09
…e bit more documentation, a variable rename, and a better explanation of a test.

@jstasiak jstasiak left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I'd like to merge it after #258 is merged and released.

@gjbadros

gjbadros commented May 25, 2020 via email

Copy link
Copy Markdown
Author

@jstasiak jstasiak merged commit 87a0fe2 into python-zeroconf:master May 26, 2020
bdraco added a commit to bdraco/python-zeroconf that referenced this pull request Jun 13, 2021
- DNSOutgoing.packet only returned a partial message when the
  DNSOutgoing contents exceeded _MAX_MSG_ABSOLUTE or _MAX_MSG_TYPICAL
  This was a legacy function that was replaced with .packets()
  which always returns a complete payload in python-zeroconf#248  As packet()
  should not be used since it will end up missing data, it has
  been removed
bdraco added a commit that referenced this pull request Jun 13, 2021
- DNSOutgoing.packet only returned a partial message when the
  DNSOutgoing contents exceeded _MAX_MSG_ABSOLUTE or _MAX_MSG_TYPICAL
  This was a legacy function that was replaced with .packets()
  which always returns a complete payload in #248  As packet()
  should not be used since it will end up missing data, it has
  been removed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants