dns: fix QU response source port and IPv6 hop limits - #35
Open
massimomazzariol wants to merge 2 commits into
Open
dns: fix QU response source port and IPv6 hop limits#35massimomazzariol wants to merge 2 commits into
massimomazzariol wants to merge 2 commits into
Conversation
IPV6_MULTICAST_HOPS and IPV6_UNICAST_HOPS expect an int-sized option value on Linux. interface_init_socket() currently passes a uint8_t value and sizeof(uint8_t), causing setsockopt() to fail with EINVAL. The failure is ignored, leaving IPv6 multicast packets at the default hop limit of 1 and unicast packets at the default hop limit of 64. Use the existing int-sized ittl value instead. This restores the int-sized socket option used before the global socket refactor and sets both IPv6 hop limits to 255 as intended. Tested on OpenWrt 25.12.5: - multicast mDNS hop limit: 1 -> 255 - unicast QU response hop limit: 64 -> 255 Signed-off-by: Massimo Mazzariol <[email protected]>
A QU question received on the multicast socket requests a response directly to the querier. The response is still an mDNS response and must originate from UDP port 5353. Since the global socket refactor in 4035fe4, parse_question() switches QU replies to the global unicast socket. That socket is not bound to port 5353, so the reply can use an ephemeral source port even though the destination address and port are correct. Keep QU replies on the mDNS socket and use the querier address only as the destination. Allow multicast interfaces to honor an explicit destination while continuing to use the socket bound to port 5353. This also makes an existing destination argument in the cache refresh path significant. Preserve its historical behavior explicitly: refreshes on multicast interfaces remain multicast-destination QU queries, while unicast interfaces continue to query the cached peer. Hardware validation on OpenWrt 25.12.5 confirmed: - IPv4 QU replies use UDP 5353 -> 5353 with TTL 255 - IPv6 QU replies use UDP 5353 -> 5353 with hop limit 255 - normal multicast responses remain multicast - multicast cache refresh queries remain multicast with the QU bit Signed-off-by: Massimo Mazzariol <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
Hello there!
I was working on a small personal project using an old Fonera 2.0n running latest OpenWrt version, and while trying to get mDNS working reliably on it I kept running into repeated resolution problems from a Windows client.
While looking at the traffic with tcpdump, I noticed that Windows was sending an mDNS QU query for
fonera.localfrom UDP port 5353, but the reply from umdns was originating from an ephemeral UDP source port instead of 5353.fonera.localis just the test hostname I assigned to the device for this mDNS investigation.The destination address and destination port looked correct, so I started following the QU response path through mdnsd to understand where the different source port was coming from.
I may be missing some design context in mdnsd, so I see this series as a proposed fix rather than the only possible solution. I would really appreciate feedback or suggestions if there is a better approach
What I found
For a question with the unicast-response bit set,
parse_question()currently keeps the querier address into, but switches from the multicast interface to the corresponding global unicast interface:The global socket change was introduced here:
4035fe42The multicast sockets are bound to UDP port 5353, while the unicast sockets are bound without setting a local port.
RFC 6762 section 6 states that the source UDP port in Multicast DNS responses must be 5353.
RFC 6762 section 5.4 also describes the unicast-response bit, which allows a querier to request a response via unicast
For additional context,
68af3114previously changed the IPv4 unicast response path so that the destination port is taken from the querier address. The response is still sent through the selected socket, however, which is what led me to look at the source socket separately.Based on that, I tried keeping the QU response on the mDNS socket and using the querier address only as the destination:
interface_send_packet()then uses the multicast destination when no explicit destination is supplied, and the supplied destination whentois present.With this change, the QU response is still sent directly to the querier, but originates from the socket bound to UDP port 5353.
For comparison, Apple's POSIX mDNSResponder implementation also selects the interface multicast socket separately from the destination address used for
sendto():Apple mDNSResponder -
mDNSPosix.cI do not mean this as proof that mdnsd should use the same design, but it was useful as a reference while looking at the socket behavior
Cache refresh behavior
While testing an earlier version of this change, I noticed a side effect in the cache refresh path.
cache_gc_timer()has passed the cached peer address todns_send_question()since480d7bc7.Previously that destination was ignored when the interface was a multicast interface. Once an explicit destination is allowed on that socket, the same call would instead send the refresh directly to the cached peer.
To avoid changing that existing behavior, this series makes the choice explicit:
In my hardware test this preserved multicast-destination cache refresh queries with the QU bit set.
For unicast interfaces, the conditional continues to pass the cached peer address as before
IPv6 hop-limit issue found while testing
While testing the same change over IPv6, I found another issue that seems independent of the QU destination change.
Before applying the IPv6 socket-option fix, while testing the QU change, I observed:
interface_init_socket()currently has both:but passes the one-byte
ttlvalue to:In the Linux implementation of
do_ipv6_setsockopt(), both options reject an option length smaller thansizeof(int)withEINVAL:Linux
ipv6_sockglue.c-do_ipv6_setsockopt()The return values of these
setsockopt()calls are currently not checked, so the failure is silent.The first commit therefore only changes those two calls to use the already existing int-sized
ittlvalue.I kept this as a separate commit because it is a separate issue and can be reviewed independently from the QU response change
Testing
I built the resulting code using the OpenWrt 25.12.5 ramips/rt305x SDK and installed it on the Fonera 2.0n.
The tests were observed directly on the wireless interface with tcpdump
QU response
Before the QU socket change, an IPv4 QU response was observed with an ephemeral UDP source port.
With this series:
The same test over IPv6 produced:
with hop limit 255.
IPv6 hop limit
Before the IPv6 socket-option fix:
After changing the two socket-option arguments to
ittl:A normal IPv6 QM query for
fonera.localalso produced a multicast response toff02::fb:5353with source port 5353 and hop limit 255.RFC 6762 section 11 discusses the TTL / hop-limit value of 255 for Multicast DNS packets, including responses sent via unicast
Cache refresh
I also tested the cache refresh path separately after noticing the regression in an earlier version of the patch.
The refresh remained multicast-destination and retained the QU bit.
Final code
The two-commit series is byte-for-byte identical to the patch that was built and used for the hardware tests above
Things I intentionally did not change
There is already a TODO in
parse_question()concerning the RFC 6762 section 5.4 quarter-TTL rule for deciding whether a QU response should instead be multicast, but I left that behavior unchanged.I also did not try to handle queries originating from a UDP source port other than 5353. RFC 6762 section 6.7 treats those as legacy queries with additional response requirements, and that work is already being discussed separately in PR #18
This series is therefore not intended to claim complete RFC 6762 compliance. It only addresses the specific behaviors described above
References
ipv6_sockglue.c-do_ipv6_setsockopt()mDNSPosix.c4035fe42- interface: use a global socket instead of per-interface ones480d7bc7- Fix sending unicast questions on cache expire68af3114- fix unicast response port and timeoutThanks for taking a look. I would especially appreciate feedback on whether keeping QU replies on the mDNS socket while using a unicast destination is the right approach for mdnsd, or if there is a better way to keep source port 5353 with the current global socket design