RFC 8764

Apple's DNS Long-Lived Queries Protocol

πŸ“… published June 2020, from ietf

Apple's DNS Long-Lived Queries Protocol πŸŽ€

  • Stream: Independent Submission ✨
  • RFC: 8764
  • Authors: S. Cheshire and M. Krochmal, both at Apple Inc. πŸ’–
  • Category: Informational πŸ“š
  • ISSN: 2070-1721
  • Date: June 2020 🌸
  • Obsoletes / Updates: nothing is listed in the title block, but the abstract says LLQ was superseded by RFC 8765 πŸ¦‹

Abstract πŸ’…

Okay so, Apple's DNS Long-Lived Queries (LLQ) is a way of extending DNS so it can send change notifications. That means clients find out when DNS data changes without having to keep asking the server πŸ”₯ From 2005 onwards, LLQ lived in Apple products like Mac OS X, Bonjour for Windows, and AirPort wireless base stations. In 2020 it got superseded by the IETF Standards Track RFC 8765, "DNS Push Notifications," which took everything learned from LLQ and made a much better replacement ✨

This document writes down the LLQ protocol that was deployed and used from 2005 to 2020. The point is to give background on the operational experience that shaped DNS Push Notifications, and to help everyone make a smooth switch from LLQ over to it 🫢

Status of This Memo πŸ“

This document is not an Internet Standards Track specification. It's published for informational purposes 🌈

This is a contribution to the RFC Series, independent of any other RFC stream. The RFC Editor chose to publish it at their discretion, and they make no statement about its value for implementation or deployment. Documents approved for publication by the RFC Editor are not candidates for any level of Internet Standard. See Section 2 of RFC 7841 for more on that πŸ’œ

Information about the current status of this document, any errata, and how to give feedback on it can be found at https://www.rfc-editor.org/info/rfc8764 🧚

Copyright (c) 2020 IETF Trust and the persons identified as the document authors. All rights reserved πŸ’–

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as in effect on the date of publication. Please review those documents carefully, because they describe your rights and restrictions with respect to this document πŸ™ˆ

Table of Contents πŸ—ΊοΈ

   1.  Introduction
     1.1.  Transition to DNS Push Notifications
   2.  Conventions and Terminology Used in This Document
   3.  Mechanisms
     3.1.  Assigned Numbers
     3.2.  Opt-RR Format
   4.  LLQ Address and Port Identification
   5.  LLQ Setup
     5.1.  Setup Message Retransmission
     5.2.  LLQ Setup Four-Way Handshake
       5.2.1.  Setup Request
       5.2.2.  Setup Challenge
       5.2.3.  Challenge Response
       5.2.4.  ACK + Answers
     5.3.  Resource Record TTLs
   6.  Event Responses
     6.1.  Add Events
     6.2.  Remove Events
     6.3.  Gratuitous Response Acknowledgments
   7.  LLQ Lease-Life Expiration
     7.1.  Refresh Request
     7.2.  LLQ Refresh Acknowledgment
   8.  Security Considerations
     8.1.  Server DoS
     8.2.  Client Packet Storms
     8.3.  Spoofing
   9.  IANA Considerations
   10. References
     10.1.  Normative References
     10.2.  Informative References
   Appendix A.  Problems with the LLQ Protocol
   Acknowledgments
   Authors' Addresses

1. Introduction ✨

Okay, bestie, so in dynamic environments, DNS-based Service Discovery [RFC6763] gets SO much better when clients can learn about changes to DNS info in a way that's both more timely and more efficient than plain old polling 🀩 That's how you get "live browses" that (a) learn when a new instance of a service shows up, (b) learn when an existing service instance vanishes from the network, and (c) let clients keep an eye on status changes for a service instance (think printer ink levels πŸ–¨οΈ).

Multicast DNS [RFC6762] does this natively, and it's giving main character energy. When a device on the network publishes or deletes Multicast DNS records, those changes get multicast to the other hosts on the network. The hosts then pass the change notifications along to interested clients, meaning the applications running on that host. Hosts also send the occasional query to the network, just in case a gratuitous announcement got lost to packet loss, and to catch records that were lost because their publisher crashed or got disconnected from the network πŸ’…

This document defines an Apple extension to unicast DNS. It lets a client issue long-lived queries, so a unicast DNS server can notify clients about changes to DNS data πŸ¦‹ This is way more scalable and practical than polling the name server. A low polling rate could leave the client with stale info, and a high polling rate would hammer the network and the server. Nobody wants that 😭

The mechanism in this document is now being replaced by DNS Push Notifications [RFC8765], as explained in Section 1.1 🌸

1.1. Transition to DNS Push Notifications 🌈

The LLQ protocol had over a decade of useful operation. It powered timely live updates for the service discovery UI in Apple's Back to My Mac [RFC6281] service πŸ’œ

Some problems were discovered, though, and they're described in Appendix A πŸ™ˆ The operational experience with LLQ shaped the design of its IETF Standards Track successor, DNS Push Notifications [RFC8765]. Since no further work is happening on the LLQ protocol, this LLQ specification won't be updated to fix those problems πŸ˜”

All existing LLQ implementations are RECOMMENDED to migrate to DNS Push Notifications instead πŸš€

Existing LLQ servers are RECOMMENDED to implement and support DNS Push Notifications, so clients can start migrating to the newer protocol 🧚

Existing LLQ clients are RECOMMENDED to query for the _dns-push-tls._tcp.<zone> SRV record first. Only if DNS Push Notifications fail should they fall back to querying for _dns-llq._udp.<zone> instead. Use of the _dns-llq._udp.<zone> SRV record is described in Section 4 πŸ“

This will make clients prefer the newer protocol whenever possible πŸ‘‘ It is RECOMMENDED that clients always attempt DNS Push Notifications first for every new request, and only fall back to LLQ if that fails. Clients SHOULD NOT record that a given server only speaks LLQ and then default to LLQ for that server from then on. Server software gets updated, and even a server that only speaks LLQ today may be updated to support DNS Push Notifications tomorrow ⭐

New client and server implementations are RECOMMENDED to support only DNS Push Notifications ✨

2. Conventions and Terminology Used in This Document πŸ“–

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here 🫢

3. Mechanisms πŸ”§

DNS Long-Lived Queries (LLQ) are implemented using the standard DNS message format [RFC1035], together with an EDNS(0) OPT pseudo-RR [RFC6891]. The OPT pseudo-RR carries a new OPTION-CODE and OPTION-DATA format, specified here. Encoding the LLQ request in an OPT pseudo-RR means LLQ can be implemented with minimal modification to a name server's front end, which is so clever 🧠

If a DNS query containing an LLQ option is sent to a server that doesn't implement LLQ, a server that complies with the EDNS(0) specification [RFC6891] will silently ignore the unrecognized option. It will answer the request as a normal DNS query, without establishing any long-lived state and without returning an LLQ option in its response πŸ™ƒ If a DNS query containing an LLQ option is sent to a server that doesn't implement EDNS(0) at all, the server may silently ignore the EDNS(0) OPT pseudo-RR, or it may return a nonzero RCODE. In practice though, this is mostly theoretical, since having a zone's _dns-llq._udp.<zone> SRV record target a host that doesn't implement LLQ is a configuration error πŸ˜…

Note that this protocol is designed for data set sizes of a few dozen resource records at most, and change rates no more than once every 10 seconds on average. Data sets that frequently exceed a single IP packet, or that go through a rapid change rate, may have undesirable performance implications πŸ’€

3.1. Assigned Numbers πŸ”’

This section describes constants used in this document πŸŽ€

   EDNS(0) OPTION-CODE (recorded with IANA):

      LLQ               1

   LLQ-PORT 5352 (recorded with IANA)

   LLQ Opcodes (specific to this LLQ EDNS(0) Option):

      LLQ-SETUP         1
      LLQ-REFRESH       2
      LLQ-EVENT         3

   LLQ Error Codes (specific to this LLQ EDNS(0) Option):

      NO-ERROR          0
      SERV-FULL         1
      STATIC            2
      FORMAT-ERR        3
      NO-SUCH-LLQ       4
      BAD-VERS          5
      UNKNOWN-ERR       6

3.2. Opt-RR Format ✨

Okay so EDNS(0) [RFC6891] is the rulebook here, and every OPT pseudo-RR used in LLQs has to follow its layout, no exceptions πŸ’… Here's the table:

          +------------+--------------+-------------------------+
          | Field Name | Field Type   | Description             |
          +============+==============+=========================+
          | NAME       | domain name  | MUST be 0 (root domain) |
          +------------+--------------+-------------------------+
          | TYPE       | u_int16_t    | OPT (41)                |
          +------------+--------------+-------------------------+
          | CLASS      | u_int16_t    | 0*                      |
          +------------+--------------+-------------------------+
          | TTL        | u_int32_t    | 0                       |
          +------------+--------------+-------------------------+
          | RDLEN      | u_int16_t    | length of all RDATA     |
          +------------+--------------+-------------------------+
          | RDATA      | octet stream | (see below)             |
          +------------+--------------+-------------------------+

                       Table 1: OPT-RRs Used in LLQs

About that * on the CLASS row πŸŽ€ The CLASS field tells the other side the sender's UDP payload size, as per the EDNS(0) spec [RFC6891]. BUT clients and servers are not required to figure out their reassembly buffer size, path MTU, etc., just to support LLQ. So the sender of an LLQ Request or Response MAY set the CLASS field to 0. And the recipient MUST ignore the class field if it is set to 0. Lowkey a relief, honestly πŸ₯Ή

The RDATA of an EDNS(0) OPT pseudo-RR is zero or more options shaped like { OPTION-CODE, OPTION-LENGTH, OPTION-DATA }, all packed together, with the RDLEN field set to match the total size. Here's the LLQ OPTION πŸ¦‹ An EDNS(0) OPT pseudo-RR can hold zero or more LLQ OPTIONS alongside zero or more other EDNS(0) options. Main character energy for the whole packet πŸ‘‘

   +---------------+------------+-------------------------------------+
   | Field Name    | Field Type | Description                         |
   +===============+============+=====================================+
   | OPTION-CODE   | u_int16_t  | LLQ (1)                             |
   +---------------+------------+-------------------------------------+
   | OPTION-LENGTH | u_int16_t  | Length of following fields (18)     |
   +---------------+------------+-------------------------------------+
   | LLQ-VERSION   | u_int16_t  | Version of LLQ protocol implemented |
   +---------------+------------+-------------------------------------+
   | LLQ-OPCODE    | u_int16_t  | Identifies LLQ operation            |
   +---------------+------------+-------------------------------------+
   | LLQ-ERROR     | u_int16_t  | Identifies LLQ errors               |
   +---------------+------------+-------------------------------------+
   | LLQ-ID        | u_int64_t  | Identifier for an LLQ               |
   +---------------+------------+-------------------------------------+
   | LLQ-LEASE     | u_int32_t  | Requested or granted life of LLQ,   |
   |               |            | in seconds                          |
   +---------------+------------+-------------------------------------+

                           Table 2: LLQ OPTION

The size and meaning of OPTION-CODE and OPTION-LENGTH come straight from the EDNS(0) spec [RFC6891]. Everything else is the OPTION-DATA of the EDNS(0) LLQ OPTION. Since the OPTION-DATA is a fixed size for LLQ, the OPTION-LENGTH field in EDNS(0) LLQ OPTIONS always has the value 18. Simple and iconic 🧚

Following Internet convention, all multi-byte numeric quantities (u_int16_t, u_int32_t, and u_int64_t) are written in big endian byte order, which means most significant byte first πŸ’œ


4. LLQ Address and Port Identification 🧭

First things first, the client needs a way to figure out which server it should send LLQ operations to 🎯

Also, some firewalls block direct communication with a name server on port 53 to avoid spoof responses. But that direct communication is necessary for LLQs, so servers MAY listen for LLQs on a different port (typically 5352). That means clients need a way to figure out which port to send LLQ operations to too πŸ™ˆ

The client finds the server responsible for a given LLQ pretty much the same way it finds the server for a DNS dynamic update. It starts by sending a standard DNS query for the name of the LLQ, with type SOA. If the record exists, the server MUST answer with that SOA record in the Answer section. If there's no SOA record with the LLQ name, the server SHOULD include an SOA record for that name's zone in the Authority section. For example, a query for "_ftp._tcp.example.com" with type SOA, when there's no SOA record with that name, might return an SOA record named "example.com" in the Authority section. And if the named SOA record doesn't exist and the server doesn't include the enclosing SOA record in the Authority section, the client strips the leading label off the name and tries again, repeating until it gets an answer πŸ”

This iterative zone apex discovery algorithm is explained in more detail in the DNS Push Notifications specification [RFC8765]. Go check it out, it's giving thorough πŸ“–

Once the client knows the zone apex (SOA), it builds and sends an SRV query for the name "_dns-llq._udp.<zone>", e.g. "_dns-llq._udp.example.com" 🌸

An authoritative server for a zone implementing LLQ MUST answer with an SRV record [RFC2782] for this name. The SRV RDATA looks like this:

    +----------+------------------------------------------------------+
    | PRIORITY | typically 0                                          |
    +----------+------------------------------------------------------+
    | WEIGHT   | typically 0                                          |
    +----------+------------------------------------------------------+
    | PORT     | typically 53 or 5352                                 |
    +----------+------------------------------------------------------+
    | TARGET   | name of server providing LLQs for the requested zone |
    +----------+------------------------------------------------------+

                             Table 3: SRV RDATA

The server SHOULD include the address record(s) for the target host in the Additional section of the response. And if it doesn't, the client SHOULD query explicitly for the address record(s) using the name of the SRV target 🫢

Now the important bit πŸ”₯ The client MUST send all LLQ requests, refreshes, and acknowledgments to the name server named in the SRV target, at the address in that target's address record. The queries in this section (including the SOA and SRV ones) MAY be sent to an intermediate DNS recursive resolver. They don't have to go straight to the name server, bestie πŸ¦‹

If the client sends the SRV query and gets a negative response saying the SRV record doesn't exist, the client SHOULD conclude that the zone doesn't support LLQ. Then it SHOULD NOT send an LLQ request for the desired name, and instead uses the behavior for LLQ-unaware servers described in Section 5, "LLQ Setup" πŸ™…β€β™€οΈ

Servers should send all messages to the source address and port of the LLQ setup message they got from the client πŸ’Œ


5. LLQ Setup 🀝

An LLQ is started by a client and finished through a four-way handshake. This handshake is resilient to packet loss, proves the client is reachable, and cuts down on denial-of-service attack opportunities (see Section 8, "Security Considerations") πŸ›‘οΈ Honestly the whole vibe is protection with a side of reliability ✨

5.1. Setup Message Retransmission ⏱️

LLQ Setup Requests and Responses sent by the client SHOULD be retransmitted if no acknowledgments come back. The client SHOULD retry up to two more times (so 3 attempts total) before deciding the server is down or unreachable. The client MUST wait at least 2 seconds before the first retransmission and 4 seconds between the first and second retransmissions. After the 3rd attempt, the client SHOULD listen for a response for at least 8 seconds before deciding the server is down or unreachable. Once a client has decided a server is down, it MAY periodically try to restart an LLQ setup, but no more than once per hour πŸ•

Servers MUST NOT retransmit acknowledgments that don't generate responses from the client. Retransmission during setup is client driven, which frees servers from keeping timers around for half-finished LLQ setups. If servers get duplicate messages from clients (maybe because the server's responses got lost mid-flight), the server MUST resend its reply, possibly changing the LLQ-LEASE as described in Section 5.2.4, "ACK + Answers" πŸ”„

Servers MUST NOT garbage collect LLQs that fail to complete the four-way handshake until the initially granted LLQ-LEASE has elapsed. Patience is a virtue, periodt 🌈

5.2. LLQ Setup Four-Way Handshake πŸŽ€

The four phases of the handshake are:

      1) Setup Request        client to server, identifies LLQ(s)
                              requested

      2) Setup Challenge      server to client, provides unique
                              identifiers for successful requested LLQs,
                              and error(s) for unsuccessful requested
                              LLQs.

      3) Challenge Response   client to server, echoes identifier(s),
                              demonstrating client's reachability and
                              willingness to participate

      4) ACK + Answers        server to client, confirms setup and
                              provides initial answers

5.2.1. Setup Request πŸŽ€

Okay so an LLQ request looks just like a regular DNS query, except it's got an OPT pseudo-RR tucked into the Additional section holding all the LLQ metadata πŸ’…. You can spot these Setup Requests by the LLQ-SETUP opcode and a zero-valued LLQ-ID πŸ¦‹.

The request MAY contain multiple questions to set up multiple LLQs ✨. When it does, it MUST include multiple LLQ OPTIONs, one per question, and they have to be in the same order as the questions. So the first LLQ OPTION matches the first question, the second matches the second, and so on 🫢. If you're requesting several LLQs, clients SHOULD request the same LLQ-LEASE for each one 🌸. Over UDP, a request MUST NOT contain multiple questions if doing so would make the message bigger than a single IP packet πŸ™ˆ.

A client MUST NOT request multiple identical LLQs (the same qname/type/class) from the same source IP address and port. This is so servers don't get hit with unnecessary load 😭. Now, if you've got multiple independent client implementations running on the same device that don't know about each other, it's fine if they happen to send LLQ requests for the same qname/type/class πŸ€·β€β™€οΈ. Those implementations will each be using different source ports, so it's giving separate clients. The same goes for independent clients behind the same NAT gateway. To the server, they look like different ports on one host, and that's allowed too πŸ¦‹.

The query MUST NOT be for record type ANY (255), class ANY (255), or class NONE (0) 🚫.

+---------------+------------+---------------------------------+
| Field Name    | Field Type | Description                     |
+===============+============+=================================+
| OPTION-CODE   | u_int16_t  | LLQ (1)                         |
+---------------+------------+---------------------------------+
| OPTION-LENGTH | u_int16_t  | Length of following fields (18) |
+---------------+------------+---------------------------------+
| LLQ-VERSION   | u_int16_t  | Version of LLQ protocol         |
|               |            | implemented by requester (1)    |
+---------------+------------+---------------------------------+
| LLQ-OPCODE    | u_int16_t  | LLQ-SETUP (1)                   |
+---------------+------------+---------------------------------+
| LLQ-ERROR     | u_int16_t  | NO-ERROR (0)                    |
+---------------+------------+---------------------------------+
| LLQ-ID        | u_int64_t  | 0                               |
+---------------+------------+---------------------------------+
| LLQ-LEASE     | u_int32_t  | Desired life of LLQ request     |
+---------------+------------+---------------------------------+

            Table 4: Setup Request LLQ OPTION Format

The Setup Request LLQ OPTION MUST be repeated once for each additional query in the Question section πŸ“.

5.2.2. Setup Challenge πŸ‘‘

When a server that does LLQs gets a Setup Request, it sends back a Setup Challenge to the requester (the client) 🧚. A Setup Challenge is a DNS response with the same DNS message ID as the Setup Request, and every question from the request shows up in its Question section. The challenge also has a single OPT pseudo-RR with one LLQ OPTION for each LLQ request, telling you whether each one worked πŸ’–. The LLQ OPTIONS MUST be in the same order as the questions they match up with. Heads up: in a request with multiple questions, some LLQs can succeed while others fail, so don't panic if you see a mix πŸ™ˆ.

+---------------+------------+---------------------------------+
| Field Name    | Field Type | Description                     |
+===============+============+=================================+
| OPTION-CODE   | u_int16_t  | LLQ (1)                         |
+---------------+------------+---------------------------------+
| OPTION-LENGTH | u_int16_t  | Length of following fields (18) |
+---------------+------------+---------------------------------+
| LLQ-VERSION   | u_int16_t  | Version of LLQ protocol         |
|               |            | implemented in server (1)       |
+---------------+------------+---------------------------------+
| LLQ-OPCODE    | u_int16_t  | LLQ-SETUP (1)                   |
+---------------+------------+---------------------------------+
| LLQ-ERROR     | u_int16_t  | [As Appropriate]                |
+---------------+------------+---------------------------------+
| LLQ-ID        | u_int64_t  | [As Appropriate]                |
+---------------+------------+---------------------------------+
| LLQ-LEASE     | u_int32_t  | [As Appropriate]                |
+---------------+------------+---------------------------------+

           Table 5: Setup Challenge LLQ OPTION Format

The Setup Challenge LLQ OPTION MUST be repeated once for each query in the Questions section of the Setup Challenge. Here's the tea on LLQ-ERROR, LLQ-ID and LLQ-LEASE πŸ’….

LLQ-ERROR has these possible values:

  • NO-ERROR: The LLQ Setup Request was successful ✨.
  • FORMAT-ERR: The LLQ was improperly formatted. If the rest of the DNS message is formatted properly, the DNS header error code MUST NOT include a format error code πŸ™…β€β™€οΈ. The reason is ambiguity: otherwise, a server that doesn't understand LLQ and a server that does but got a malformed request would look exactly the same to the client 😭.
  • SERV-FULL: The server can't grant the LLQ request because it's overloaded or the request goes over the server's rate limit (see Section 8, Security Considerations) 🚨. When the server returns this error, it MUST put a time interval, in seconds, in the LLQ-LEASE field, which tells the client when it can try the LLQ Setup again ⏰.
  • STATIC: The data for this name and type isn't expected to change often, so the server doesn't support the requested LLQ 🧊. The client MUST honor the resource record TTLs it gets back and MUST NOT poll sooner than those TTLs say. It also shouldn't retry the LLQ Setup for this name and type.
  • BAD-VERS: The protocol version in the client's Setup Request isn't supported by the server πŸ™ƒ.
  • UNKNOWN-ERR: The LLQ wasn't granted for some other reason that isn't covered by the error codes above πŸ€·β€β™€οΈ.

LLQ-ID: On success, this is a random number the server generates. It has to be unique on that server for the requested name/type/class πŸ¦‹. The LLQ-ID SHOULD be an unpredictable random number. Here's one way to hand out LLQ-IDs without keeping a ton of bookkeeping: put the time, in seconds since the Epoch, in the high 32 bits, and a cryptographically generated 32-bit random integer in the low 32 bits 🧠. On error, the LLQ-ID is set to 0.

LLQ-LEASE: On success, this is the actual life of the LLQ, in seconds ⏳. It can be bigger than, smaller than, or equal to what the client asked for, depending on the server administrator's policy. The server MAY discard the LLQ after this LLQ-LEASE expires, unless the client has renewed the LLQ (see Section 7, "LLQ Lease-Life Expiration") πŸ—‘οΈ. The server MUST NOT generate events (see Section 6, "Event Responses") for expired LLQs 🚫. On a SERV-FULL error, the LLQ-LEASE MUST be set to a time interval, in seconds, after which the client may retry the LLQ Setup. On other errors, the LLQ-LEASE MUST be set to 0.

5.2.3. Challenge Response πŸ’œ

After a client sends a Setup Request, it listens for a Setup Challenge (Section 5.2.2) and resends the Setup Request as needed (Section 5.1) πŸ‘€. Once it gets a successful Setup Challenge, the client SHOULD send a Challenge Response back to the server πŸ’Œ. This is a DNS request with the same questions as the Setup Request and Setup Challenge, plus a single OPT pseudo-RR in the Additional section. Its LLQ OPTIONS match the ones in the Setup Challenge, echoing back, for each LLQ OPTION, the random LLQ-ID and the granted LLQ-LEASE πŸ”. If the Challenge Response has multiple questions, the first question MUST match the first LLQ OPTION, and so on 🫢.

If the Setup Request for a particular LLQ fails with a STATIC error, the client MUST NOT poll the server for that LLQ 🧊. The client SHOULD honor the resource record TTLs in the response.

If a Setup Request fails with a SERV-FULL error, the client MAY retry the LLQ Setup Request (Section 5.2.1) once the time in the LLQ-LEASE field has passed ⏰.

Now, if the Setup Request fails with an error other than STATIC or SERV-FULL, or the server doesn't support LLQ at all (i.e., the client gets a DNS response with a nonzero RCODE, or a DNS response with no LLQ option), the client MAY fall back to polling the server periodically with standard DNS queries πŸ”„. It figures out Add and Remove Events (see Section 6, "Event Responses") by comparing the answers it gets back πŸ•΅οΈβ€β™€οΈ. The client SHOULD NOT poll more than once every 15 minutes for a given query. And one hard rule: the client MUST NOT poll if it receives a STATIC error code in the acknowledgment πŸ™…β€β™€οΈπŸ’€.

5.2.4. ACK + Answers ✨

Okay so, once the server gets a correct Challenge Response, it MUST return an acknowledgment to finish the LLQ setup, and it also MUST hand over all the current answers to the question(s) πŸ’…

To acknowledge a good Challenge Response (one where the LLQ-ID and LLQ-LEASE the client echoes back match what the server issued), the server MUST send a DNS response with every available answer to the question(s) from the original Setup Request, plus the matching additional resource records in the Additional section. That Additional section also carries LLQ OPTIONS, formatted like this:

  +---------------+------------+---------------------------------+
  | Field Name    | Field Type | Description                     |
  +===============+============+=================================+
  | OPTION-CODE   | u_int16_t  | LLQ (1)                         |
  +---------------+------------+---------------------------------+
  | OPTION-LENGTH | u_int16_t  | Length of following fields (18) |
  +---------------+------------+---------------------------------+
  | LLQ-VERSION   | u_int16_t  | Version of LLQ protocol         |
  |               |            | implemented in server (1)       |
  +---------------+------------+---------------------------------+
  | LLQ-OPCODE    | u_int16_t  | LLQ-SETUP (1)                   |
  +---------------+------------+---------------------------------+
  | LLQ-ERROR     | u_int16_t  | NO-ERROR (0)                    |
  +---------------+------------+---------------------------------+
  | LLQ-ID        | u_int64_t  | Originally granted ID, echoed   |
  |               |            | in client's Response            |
  +---------------+------------+---------------------------------+
  | LLQ-LEASE     | u_int32_t  | Remaining life of LLQ, in       |
  |               |            | seconds                         |
  +---------------+------------+---------------------------------+

          Table 6: Successful ACK + Answers LLQ OPTION Format

If a Challenge Response takes a long time to show up, or several Challenge Responses get issued (maybe because the first ones got lost on the way to the client, so the client resent its Challenge Response πŸ™ˆ), the server MAY decrement the LLQ-LEASE by the time that has passed since the Setup Challenge was first issued.

If the setup happens over UDP and the initially available answers, the additional records, and the OPT pseudo-RR don't all fit in a single IP packet, some or all of the additional records (but not the OPT pseudo-RR) MUST be omitted. If the answers still don't fit in one message after all the additional records are gone, answers MUST be removed until the message fits in a single IP packet. Any answers left out of the ACK + Answers MUST be delivered to the client without undue delay via Add Events (Section 6, "Event Responses") πŸŽ€


5.3. Resource Record TTLs πŸ’œ

The TTLs of resource records in answers to successful LLQs SHOULD be ignored by the client. The client MAY cache LLQ answers until it gets a gratuitous announcement (see Section 6, "Event Responses") saying the answer to the LLQ has changed. The client SHOULD NOT cache answers after the LLQ's LLQ-LEASE expires without being refreshed (see Section 7, "LLQ Lease-Life Expiration"). If an LLQ request fails, the client SHOULD NOT cache answers for longer than the client's polling interval.

Note that resource records meant specifically for LLQs (like DNS-based Service Discovery resource records) may have unusually short TTLs. That's because those records are assumed to change a lot, and the client's cache coherence is kept up through the LLQ and gratuitous responses. Short TTLs stop stale info from sticking around in intermediate DNS recursive resolvers that aren't LLQ aware 🧚

TTLs of resource records in the Additional section of an LLQ response (the ones that don't directly answer the LLQ) SHOULD be honored by the client.


6. Event Responses πŸ¦‹

When a change ("event") happens to a name server's zone, the server MUST check whether the new or deleted resource records answer any LLQs. If they do, the changes MUST be communicated to the LLQ requesters in the form of a gratuitous DNS response sent to the client, with the relevant question(s) in the Question section and the matching answers in the Answer section. The response also includes an OPT pseudo-RR in the Additional section. That OPT pseudo-RR holds, in its RDATA, one LLQ OPTION for each LLQ being answered in the message. Each LLQ OPTION must include the LLQ-ID, which cuts down on the potential for spoofed events being sent to a client πŸ”₯

  +---------------+------------+---------------------------------+
  | Field Name    | Field Type | Description                     |
  +===============+============+=================================+
  | OPTION-CODE   | u_int16_t  | LLQ (1)                         |
  +---------------+------------+---------------------------------+
  | OPTION-LENGTH | u_int16_t  | Length of following fields (18) |
  +---------------+------------+---------------------------------+
  | LLQ-VERSION   | u_int16_t  | Version of LLQ protocol         |
  |               |            | implemented in server (1)       |
  +---------------+------------+---------------------------------+
  | LLQ-OPCODE    | u_int16_t  | LLQ-EVENT (3)                   |
  +---------------+------------+---------------------------------+
  | LLQ-ERROR     | u_int16_t  | NO-ERROR (0)                    |
  +---------------+------------+---------------------------------+
  | LLQ-ID        | u_int64_t  | [As Appropriate]                |
  +---------------+------------+---------------------------------+
  | LLQ-LEASE     | u_int32_t  | 0                               |
  +---------------+------------+---------------------------------+

             Table 7: Event Response LLQ OPTION Format

Gratuitous responses for a single LLQ MAY be batched, so multiple changes go out in one message. Responses MUST NOT be batched if that would cause a message that would otherwise fit in a single IP packet to get truncated. Responses MAY be deferred to create batching opportunities, but they SHOULD NOT be held back for batching for more than 30 seconds, since that would mean unacceptable latency for the client.

After sending a gratuitous response, the server MUST listen for an acknowledgment from the client. If the client doesn't respond, the server MUST resend the response. The server MUST resend two times (3 transmissions in total), after which the server MUST consider the client unreachable and delete its LLQ. The server MUST listen for 2 seconds before resending the response, 4 more seconds before resending again, and must wait an additional 8 seconds after the 3rd transmission before terminating the LLQ ⏰

The DNS message header of the response SHOULD include an unpredictable random number in the DNS message ID field, which is to be echoed in the client's acknowledgment 🎲

6.1. Add Events 🌸

Add Events happen when a new resource record shows up, usually from a dynamic update [RFC2136], that answers an LLQ. That record must be sent in the Answer section of the event to the client. Records that normally come along with it in responses MAY be included in the Additional section, subject to the truncation restrictions described above.

6.2. Remove Events 🌈

Remove Events happen when a resource record that was previously sent to a client (in an initial response or in an Add Event) becomes invalid, normally because it was removed via a dynamic update. The deleted resource record goes in the Answer section of the event to the client, and its TTL is set to -1, which means the record has been removed.

6.3. Gratuitous Response Acknowledgments 🫢

Upon receiving a gratuitous response ("event"), the client MUST send an acknowledgment to the server. This acknowledgment is a DNS response that echoes the OPT pseudo-RR contained in the event, with the message ID of the gratuitous response echoed in the message header. The acknowledgment MUST be sent to the source IP address and port from which the event originated.


7. LLQ Lease-Life Expiration πŸ‘‘

7.1. Refresh Request ✨

If the client wants to keep the LLQ going beyond the duration specified in the LLQ-LEASE field of the ACK + Answers (Section 5.2.4), the client MUST send a Refresh Request. A Refresh Request is identical to an LLQ Challenge Response (Section 5.2.3), but with the LLQ-OPCODE set to LLQ-REFRESH. Unlike a Challenge Response, a Refresh Request returns no answers.

The client SHOULD refresh an LLQ when 80% of its LLQ-LEASE has elapsed.

To cut down on network traffic, when constructing refresh messages the client SHOULD include all LLQs established with a given server, even those not yet close to expiration. However, at least one LLQ MUST have elapsed at least 80% of its original LLQ-LEASE. The client MUST NOT include additional LLQs if doing so would cause the message to no longer fit in a single IP packet. In this case, the LLQs furthest from expiration should be omitted such that the message fits in a single IP packet. (These LLQs SHOULD be refreshed in a separate message when 80% of one or more of their lease lives have elapsed.) When refreshing multiple LLQs simultaneously, the message contains multiple questions and a single OPT pseudo-RR with multiple LLQ OPTIONS, one per question, with the LLQ OPTIONS in the same order as the questions they correspond to 🧩

The client SHOULD specify the original LLQ-LEASE granted in the LLQ response as the desired LLQ-LEASE in the Refresh Request. If refreshing multiple LLQs simultaneously, the client SHOULD request the same LLQ-LEASE for all LLQs being refreshed (with the exception of termination requests; see below).

To terminate an LLQ prior to its scheduled expiration (for instance, when the client terminates a DNS-based Service Discovery browse operation or when a client is about to go to sleep or shut down), the client specifies an LLQ-LEASE value of 0 πŸ›‘

The client MUST listen for an acknowledgment from the server. The client MAY retry up to two more times (for a total of 3 attempts) before considering the server down or unreachable. The client MUST NOT retry a first time before 90% of the LLQ-LEASE has expired, and MUST NOT retry again before 95% of the LLQ-LEASE has expired. If the server is determined to be down, the client MAY periodically attempt to re-establish the LLQ via an LLQ Setup Request message. The client MUST NOT attempt the LLQ Setup Request more than once per hour πŸ™ˆ

7.2. LLQ Refresh Acknowledgment πŸ’…

Upon receiving an LLQ Refresh message, a server MUST send an acknowledgment of the Refresh. This acknowledgment is formatted like the "ACK + Answers" message described in Section 5.2.4, but with the following variations:

  • It contains no answers.
  • The LLQ-OPCODE is set to LLQ-REFRESH.
  • NO-SUCH-LLQ MUST be returned as an error code if the client attempts to refresh an expired or non-existent LLQ (as determined by the LLQ-ID in the request).
  • The LLQ-ID in the acknowledgment is set to the LLQ-ID in the request.

8. Security Considerations πŸ”’βœ¨

Okay bestie, security time πŸ˜­πŸ”’ In datagram-based protocols (basically anything running over UDP, or straight over IP, or something similar), servers can be vulnerable to denial-of-service (DoS) attacks, and clients can get hit with packet storms πŸŒͺ️. So carefully designed mechanisms are needed to limit the chances of that happening πŸ›‘οΈ.

Note: This section contains no new protocol elements. It only explains the reasoning behind the protocol elements described above, as they relate to security 🧐.

8.1. Server DoS πŸ’₯

LLQs need servers to be stateful 🧠, which means they keep an entry for every LLQ over what could be a really long time ⏳. If there's no limit on how many of these there are, those entries can overload the server 😡. By returning SERV-FULL in Setup Challenges, the server can cap how many LLQs it keeps 🚧. On top of that, the server can send SERV-FULL to limit how many LLQs get requested for a single name and type, or by one single client. This throttling can be a hard limit or, preferably, token-bucket rate limiting πŸͺ£. That rate limiting should happen rarely in normal use and is there to stop DoS attacks, so it isn't built into the protocol explicitly. Instead it's up to an administrator's discretion, using the SERV-FULL error and the LLQ-LEASE field to tell the client when to retry ⏰.

8.2. Client Packet Storms πŸŒͺ️

Beyond protecting the server, the LLQ protocol also limits how much a malicious host can make the server flood a client with packets πŸ›‘. It does this with the four-way handshake at setup, which shows the client is reachable and actually wants to take part 🀝, and by requiring that gratuitous responses get ACK'd by the client βœ….

Additionally, rate limiting by LLQ client address, as described in Section 8.1, limits the number of packets that can be delivered to an unsuspecting client πŸ™ˆ.

8.3. Spoofing 🎭

A large random ID greatly reduces the risk of an off-path attacker sending spoof packets to the client (containing spoof events) or to the server (containing phony requests or refreshes) πŸ«£πŸ”.


9. IANA Considerations πŸ“‹

The EDNS(0) OPTION CODE 1 has already been assigned for this DNS extension πŸŽ€. IANA has updated the record in the DNS EDNS0 Option Codes (OPT) registry from "On-hold" to "Optional" and has set the reference to this document πŸ’….

TCP and UDP ports 5352 have already been assigned for LLQ πŸ”Œ. IANA has added a reference to this document πŸ“Ž.

10. References πŸ“š

10.1. Normative References

[RFC1035]  Mockapetris, P., "Domain names - implementation and
           specification", STD 13, RFC 1035, DOI 10.17487/RFC1035,
           November 1987, <https://www.rfc-editor.org/info/rfc1035>.

[RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
           Requirement Levels", BCP 14, RFC 2119,
           DOI 10.17487/RFC2119, March 1997,
           <https://www.rfc-editor.org/info/rfc2119>.

[RFC2782]  Gulbrandsen, A., Vixie, P., and L. Esibov, "A DNS RR for
           specifying the location of services (DNS SRV)", RFC 2782,
           DOI 10.17487/RFC2782, February 2000,
           <https://www.rfc-editor.org/info/rfc2782>.

[RFC6891]  Damas, J., Graff, M., and P. Vixie, "Extension Mechanisms
           for DNS (EDNS(0))", STD 75, RFC 6891,
           DOI 10.17487/RFC6891, April 2013,
           <https://www.rfc-editor.org/info/rfc6891>.

[RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
           2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
           May 2017, <https://www.rfc-editor.org/info/rfc8174>.

[RFC8765]  Pusateri, T. and S. Cheshire, "DNS Push Notifications",
           RFC 8765, DOI 10.17487/RFC8765, June 2020,
           <https://www.rfc-editor.org/info/rfc8765>.

10.2. Informative References

[RFC2136]  Vixie, P., Ed., Thomson, S., Rekhter, Y., and J. Bound,
           "Dynamic Updates in the Domain Name System (DNS UPDATE)",
           RFC 2136, DOI 10.17487/RFC2136, April 1997,
           <https://www.rfc-editor.org/info/rfc2136>.

[RFC4787]  Audet, F., Ed. and C. Jennings, "Network Address
           Translation (NAT) Behavioral Requirements for Unicast
           UDP", BCP 127, RFC 4787, DOI 10.17487/RFC4787, January
           2007, <https://www.rfc-editor.org/info/rfc4787>.

[RFC4953]  Touch, J., "Defending TCP Against Spoofing Attacks",
           RFC 4953, DOI 10.17487/RFC4953, July 2007,
           <https://www.rfc-editor.org/info/rfc4953>.

[RFC5382]  Guha, S., Ed., Biswas, K., Ford, B., Sivakumar, S., and P.
           Srisuresh, "NAT Behavioral Requirements for TCP", BCP 142,
           RFC 5382, DOI 10.17487/RFC5382, October 2008,
           <https://www.rfc-editor.org/info/rfc5382>.

[RFC6281]  Cheshire, S., Zhu, Z., Wakikawa, R., and L. Zhang,
           "Understanding Apple's Back to My Mac (BTMM) Service",
           RFC 6281, DOI 10.17487/RFC6281, June 2011,
           <https://www.rfc-editor.org/info/rfc6281>.

[RFC6762]  Cheshire, S. and M. Krochmal, "Multicast DNS", RFC 6762,
           DOI 10.17487/RFC6762, February 2013,
           <https://www.rfc-editor.org/info/rfc6762>.

[RFC6763]  Cheshire, S. and M. Krochmal, "DNS-Based Service
           Discovery", RFC 6763, DOI 10.17487/RFC6763, February 2013,
           <https://www.rfc-editor.org/info/rfc6763>.

[RFC6886]  Cheshire, S. and M. Krochmal, "NAT Port Mapping Protocol
           (NAT-PMP)", RFC 6886, DOI 10.17487/RFC6886, April 2013,
           <https://www.rfc-editor.org/info/rfc6886>.

[RFC6887]  Wing, D., Ed., Cheshire, S., Boucadair, M., Penno, R., and
           P. Selkirk, "Port Control Protocol (PCP)", RFC 6887,
           DOI 10.17487/RFC6887, April 2013,
           <https://www.rfc-editor.org/info/rfc6887>.

[RFC7857]  Penno, R., Perreault, S., Boucadair, M., Ed., Sivakumar,
           S., and K. Naito, "Updates to Network Address Translation
           (NAT) Behavioral Requirements", BCP 127, RFC 7857,
           DOI 10.17487/RFC7857, April 2016,
           <https://www.rfc-editor.org/info/rfc7857>.

[RFC8490]  Bellis, R., Cheshire, S., Dickinson, J., Dickinson, S.,
           Lemon, T., and T. Pusateri, "DNS Stateful Operations",
           RFC 8490, DOI 10.17487/RFC8490, March 2019,
           <https://www.rfc-editor.org/info/rfc8490>.

[SYN]      Eddy, W., "Defenses Against TCP SYN Flooding Attacks",
           Volume 9, Number 4, The Internet Protocol Journal, Cisco
           Systems, December 2006,
           <https://www.cisco.com/web/about/ac123/ac147/
           archived_issues/ipj_9-4/ipj_9-4.pdf>.

Appendix A. Problems with the LLQ Protocol 🧁

In the course of using LLQ since 2005, some problems were discovered 😬. Since no further work is being done on the LLQ protocol, this LLQ specification will not be updated to remedy these problems πŸ€·β€β™€οΈ.

LLQ's IETF Standards Track successor, "DNS Push Notifications" [RFC8765], does not suffer from these problems πŸ™Œ, so all existing LLQ implementations are RECOMMENDED to migrate to using DNS Push Notifications πŸš€, and all new implementations are RECOMMENDED to implement DNS Push Notifications instead of LLQ πŸ’œ.

Known problems with LLQ are documented here as a cautionary tale πŸ“– about the challenges of building an application protocol directly using datagrams (like IP or UDP) without the benefit of a mature and thoroughly reviewed intervening transport layer (such as TCP or QUIC) πŸ₯².

An LLQ "Setup Challenge" message from server to client is identical to an LLQ "ACK + Answers" message from server to client when there are no current answers for the query πŸ‘€. If there is packet loss, retransmission, and duplication in the network, then a duplicated "Setup Challenge" message arriving late at the client would look like an "ACK + Answers" message with no answers, causing the client to clear its cache of any records matching the query πŸ’€.

Section 5.1 of this LLQ specification states, "Servers MUST NOT garbage collect LLQs that fail to complete the four-way handshake until the initially granted LLQ-LEASE has elapsed." πŸ™ƒ This is probably a mistake, since it exposes LLQ servers to an easy resource-exhaustion denial-of-service attack 😱. LLQ's replacement, DNS Push Notifications [RFC8765], is built using DNS Stateful Operations [RFC8490], which uses TLS over TCP. A benefit of building on TCP is that there are already established industry best practices to guard against SYN flooding and similar attacks [SYN] [RFC4953] πŸ›‘οΈβœ¨.

The attempts here to pack multiple questions into a single UDP/IP packet for efficiency are awkward πŸ˜΅β€πŸ’« and lead to error-prone programming to deal with cases where some requests in a packet succeed and other requests in the same packet fail. Fully specifying the correct handling in all possible cases would be a lot of work to document, a lot of work to implement, and even more work to thoroughly test 🫠. DNS Push Notifications [RFC8765] avoids this problem by using an underlying stream protocol (TLS/TCP) to deal with packing small multiple messages into larger IP packets for efficiency 🧩.

In some cases, initial LLQ answers are delivered in the "ACK + Answers" message, and in other cases, such as when all the initial answers will not fit in a single IP packet, some of the initial answers are delivered in a subsequent "Add Event" message. Having two different ways to accomplish the same thing increases the possibility for programming errors πŸ™…β€β™€οΈ. DNS Push Notifications [RFC8765] corrects this error by having only one single consistent way to deliver results πŸ’–.

LLQ is built using UDP, and because UDP has no standardized way of indicating the start and end of a session, firewalls and NAT gateways tend to be fairly aggressive about recycling UDP mappings that they believe to be disused [RFC4787] [RFC5382] [RFC7857] 🧹. Using a high keepalive traffic rate to maintain firewall or NAT mapping state could remedy this, but it would largely defeat the purpose of using LLQ in the first place, which is to provide efficient change notification without wasteful polling πŸ™…β€β™€οΈ. Because of this, existing LLQ clients use the NAT Port Mapping Protocol (NAT-PMP) [RFC6886] and/or Port Control Protocol (PCP) [RFC6887] to establish longer port mapping lifetimes πŸ—ΊοΈ. This solves the problem, but it adds extra complexity and doesn't work with firewalls and NAT gateways that don't support NAT-PMP or PCP 😩. By using TCP instead of UDP, the DNS Push Notifications protocol benefits from better longevity of sessions through firewalls and NAT gateways that don't support NAT-PMP or PCP πŸŽ‰.

Acknowledgments 🫢

The concepts described in this document were originally explored, developed, and implemented with help from Chris Sharp and Roger Pantos 🌸.

Kiren Sekar made significant contributions to the first draft of this document, and he wrote much of the code for the implementation of LLQ that shipped in Mac OS X 10.4 Tiger in April 2005 πŸ―πŸ‘‘.

Thanks to Independent Stream Editor Adrian Farrel for his support and assistance in the publication of this RFC πŸ₯°.

Authors' Addresses πŸ’Œ

Stuart Cheshire
Apple Inc.
One Apple Park Way
Cupertino, CA 95014
United States of America

Phone: +1 (408) 996-1010
Email: cheshire@apple.com


Marc Krochmal
Apple Inc.
One Apple Park Way
Cupertino, CA 95014
United States of America

Phone: +1 (408) 996-1010
Email: marc@apple.com
πŸ“¦ where this page comes from