Skip to content

Legacy unicast responses do not echo the query ID or cap TTLs (RFC 6762 §6.7) #496

Description

@dhavli

Summary

Legacy unicast responses (RFC 6762 §6.7) are mostly implemented — the question
section is echoed, the cache-flush bit is cleared, and the reply is correctly
unicast to the querier's ephemeral port — but two requirements of the same
section are not met:

  1. The Query Identifier is not copied into the response (a MUST). Responses
    always carry ID 0.
  2. Record TTLs are not capped to 10 seconds (a SHOULD). The full multicast
    TTLs (4500 / 120) are sent.

The practical consequence of (1) is that standard DNS tooling refuses the
reply, which makes an mdns-sd-based responder hard to debug in the field:

$ dig +short @192.168.88.47 -p 5353 _bos._sub._http._tcp.local PTR
;; Warning: ID mismatch: expected ID 16033, got 0
;; communications error to 192.168.88.47#5353: timed out
;; no servers could be reached

Reproduction

Against any host running an mdns-sd responder (here one advertising
_http._tcp with a _bos subtype), sending a query from an ephemeral port:

import socket, struct
QID = 1234
def enc(n):
    return b"".join(bytes([len(l)]) + l.encode() for l in n.rstrip(".").split(".")) + b"\x00"
pkt = struct.pack("!HHHHHH", QID, 0, 1, 0, 0, 0) + enc("_http._tcp.local") + struct.pack("!HH", 12, 1)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.settimeout(4)
s.sendto(pkt, ("<responder-ip>", 5353))
d, _ = s.recvfrom(9000)
print("response id:", struct.unpack("!H", d[:2])[0])

Observed on mdns-sd 0.21.0:

query id sent : 0x04d2 (1234)
response id   : 0x0000 (0)
flags         : 0x8400
counts        : questions=1 answers=1 auth=0 addl=4  (question echoed: True)
record TTLs:
   type=12  ttl=4500   cache_flush=False

Expected per §6.7: response id == 1234, and TTLs of 10 seconds.

Where it comes from

DnsOutgoing::new hardcodes the header ID and exposes no way to set it
(src/dns_parser.rs, id: 0), so every outgoing message — including the
legacy unicast response built in handle_query
(DnsOutgoing::new(FLAGS_QR_RESPONSE | FLAGS_AA)) — goes out with ID 0.

The value needed is already parsed and available: DnsIncoming carries an
id: u16 field. So the fix looks like threading an id into DnsOutgoing
(or a set_id) and setting it only on the legacy unicast path, where
unicast_dest.is_some() — it must stay 0 for multicast responses.

The TTL cap would fit naturally in the same branch, alongside the existing
out.clear_cache_flush_bits() call.

Notes

I'm happy to put a PR together for either or both if you consider this in
scope — wanted to check first, since I may be missing a reason the ID is
deliberately left at 0.

Version: mdns-sd 0.21.0, responder on Linux (armv7, kernel 5.10), querier on
x86-64 Linux.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions