]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Return EDNS ZONEVERSION if requested
authorMark Andrews <marka@isc.org>
Wed, 12 Jun 2024 22:36:32 +0000 (08:36 +1000)
committerMark Andrews <marka@isc.org>
Mon, 24 Mar 2025 22:16:09 +0000 (22:16 +0000)
If there was an EDNS ZONEVERSION option in the DNS request and the
answer was from a zone, return the zone's serial and number of
labels excluding the root label with the type set to 0 (ZONE-SERIAL).

lib/ns/client.c
lib/ns/include/ns/client.h
lib/ns/query.c

index 8420dcacc00e7b158f8272aaefc87dccf3da00c3..e1cdd3492d02343b1acf751bd542ac9298586fbb 100644 (file)
 #define WANTNSID(x)    (((x)->attributes & NS_CLIENTATTR_WANTNSID) != 0)
 #define WANTPAD(x)     (((x)->attributes & NS_CLIENTATTR_WANTPAD) != 0)
 #define WANTRC(x)      (((x)->attributes & NS_CLIENTATTR_WANTRC) != 0)
+#define WANTZONEVERSION(x) \
+       (((x)->attributes & NS_CLIENTATTR_WANTZONEVERSION) != 0)
 
 #define MANAGER_MAGIC   ISC_MAGIC('N', 'S', 'C', 'm')
 #define VALID_MANAGER(m) ISC_MAGIC_VALID(m, MANAGER_MAGIC)
@@ -218,6 +220,17 @@ ns_client_settimeout(ns_client_t *client, unsigned int seconds) {
        /* XXXWPK TODO use netmgr to set timeout */
 }
 
+static void
+client_zoneversion_reset(ns_client_t *client) {
+       if (client->zoneversion == NULL) {
+               return;
+       }
+       isc_mem_put(client->manager->mctx, client->zoneversion,
+                   client->zoneversionlength);
+       client->zoneversion = NULL;
+       client->zoneversionlength = 0;
+}
+
 static void
 ns_client_endrequest(ns_client_t *client) {
        INSIST(client->state == NS_CLIENTSTATE_WORKING ||
@@ -258,6 +271,7 @@ ns_client_endrequest(ns_client_t *client) {
                dns_message_puttemprdataset(client->message, &client->opt);
        }
 
+       client_zoneversion_reset(client);
        client->signer = NULL;
        client->udpsize = 512;
        client->extflags = 0;
@@ -1192,6 +1206,13 @@ no_nsid:
                ednsopts[count].value = ede->value;
                count++;
        }
+       if ((client->attributes & NS_CLIENTATTR_HAVEZONEVERSION) != 0) {
+               INSIST(count < DNS_EDNSOPTIONS);
+               ednsopts[count].code = DNS_OPT_ZONEVERSION;
+               ednsopts[count].length = client->zoneversionlength;
+               ednsopts[count].value = client->zoneversion;
+               count++;
+       }
 
        if (WANTRC(client)) {
                dns_name_t *rad = NULL;
@@ -1609,8 +1630,7 @@ process_opt(ns_client_t *client, dns_rdataset_t *opt) {
                        case DNS_OPT_CLIENT_SUBNET:
                                result = process_ecs(client, &optbuf, optlen);
                                if (result != ISC_R_SUCCESS) {
-                                       ns_client_error(client, result);
-                                       return result;
+                                       goto formerr;
                                }
                                ns_stats_increment(
                                        client->manager->sctx->nsstats,
@@ -1638,13 +1658,24 @@ process_opt(ns_client_t *client, dns_rdataset_t *opt) {
                                result = process_keytag(client, &optbuf,
                                                        optlen);
                                if (result != ISC_R_SUCCESS) {
-                                       ns_client_error(client, result);
-                                       return result;
+                                       goto formerr;
                                }
                                ns_stats_increment(
                                        client->manager->sctx->nsstats,
                                        ns_statscounter_keytagopt);
                                break;
+                       case DNS_OPT_ZONEVERSION:
+                               if (optlen != 0 || WANTZONEVERSION(client)) {
+                                       result = DNS_R_FORMERR;
+                                       goto formerr;
+                               }
+                               ns_stats_increment(
+                                       client->manager->sctx->nsstats,
+                                       ns_statscounter_zoneversionopt);
+                               client->attributes |=
+                                       NS_CLIENTATTR_WANTZONEVERSION;
+                               isc_buffer_forward(&optbuf, optlen);
+                               break;
                        default:
                                ns_stats_increment(
                                        client->manager->sctx->nsstats,
@@ -1660,6 +1691,17 @@ process_opt(ns_client_t *client, dns_rdataset_t *opt) {
        client->attributes |= NS_CLIENTATTR_WANTOPT;
 
        return result;
+
+formerr:
+       if (result == DNS_R_FORMERR || result == DNS_R_OPTERR) {
+               result = ns_client_addopt(client, client->message,
+                                         &client->opt);
+               if (result == ISC_R_SUCCESS) {
+                       result = DNS_R_FORMERR;
+               }
+       }
+       ns_client_error(client, result);
+       return result;
 }
 
 static void
@@ -1724,6 +1766,7 @@ ns__client_put_cb(void *client0) {
         */
        ns_query_free(client);
        dns_ede_invalidate(&client->edectx);
+       client_zoneversion_reset(client);
 
        client->magic = 0;
 
index 86d1ff5b6e7a9ba0a8e19a3eadbd7b61fd3facee..773647a2eab6f457d62abf97279499af7c5e6b3b 100644 (file)
@@ -224,6 +224,8 @@ struct ns_client {
        ISC_LINK(ns_client_t) rlink;
        unsigned char  cookie[8];
        uint32_t       expire;
+       unsigned char *zoneversion;
+       uint32_t       zoneversionlength;
        unsigned char *keytag;
        uint16_t       keytag_len;
 
@@ -241,26 +243,28 @@ struct ns_client {
 #define NS_CLIENT_MAGIC           ISC_MAGIC('N', 'S', 'C', 'c')
 #define NS_CLIENT_VALID(c) ISC_MAGIC_VALID(c, NS_CLIENT_MAGIC)
 
-#define NS_CLIENTATTR_TCP       0x00001
-#define NS_CLIENTATTR_RA        0x00002 /*%< Client gets recursive service */
-#define NS_CLIENTATTR_PKTINFO   0x00004 /*%< pktinfo is valid */
-#define NS_CLIENTATTR_MULTICAST         0x00008 /*%< recv'd from multicast */
-#define NS_CLIENTATTR_WANTDNSSEC 0x00010 /*%< include dnssec records */
-#define NS_CLIENTATTR_WANTNSID  0x00020 /*%< include nameserver ID */
+#define NS_CLIENTATTR_TCP       0x000001
+#define NS_CLIENTATTR_RA        0x000002 /*%< Client gets recursive service */
+#define NS_CLIENTATTR_PKTINFO   0x000004 /*%< pktinfo is valid */
+#define NS_CLIENTATTR_MULTICAST         0x000008 /*%< recv'd from multicast */
+#define NS_CLIENTATTR_WANTDNSSEC 0x000010 /*%< include dnssec records */
+#define NS_CLIENTATTR_WANTNSID  0x000020 /*%< include nameserver ID */
 #define NS_CLIENTATTR_BADCOOKIE \
-       0x00040 /*%< Presented cookie is bad/out-of-date */
-#define NS_CLIENTATTR_WANTRC      0x00080 /*%< include Report-Channel */
-#define NS_CLIENTATTR_WANTAD      0x00100 /*%< want AD in response if possible */
-#define NS_CLIENTATTR_WANTCOOKIE   0x00200 /*%< return a COOKIE */
-#define NS_CLIENTATTR_HAVECOOKIE   0x00400 /*%< has a valid COOKIE */
-#define NS_CLIENTATTR_WANTEXPIRE   0x00800 /*%< return seconds to expire */
-#define NS_CLIENTATTR_HAVEEXPIRE   0x01000 /*%< return seconds to expire */
-#define NS_CLIENTATTR_WANTOPT     0x02000 /*%< add opt to reply */
-#define NS_CLIENTATTR_HAVEECS     0x04000 /*%< received an ECS option */
-#define NS_CLIENTATTR_WANTPAD     0x08000 /*%< pad reply */
-#define NS_CLIENTATTR_USEKEEPALIVE 0x10000 /*%< use TCP keepalive */
-#define NS_CLIENTATTR_NOSETFC     0x20000 /*%< don't set servfail cache */
-#define NS_CLIENTATTR_NEEDTCP     0x40000 /*%< send TC=1 */
+       0x000040 /*%< Presented cookie is bad/out-of-date */
+#define NS_CLIENTATTR_WANTRC         0x000080 /*%< include Report-Channel */
+#define NS_CLIENTATTR_WANTAD         0x000100 /*%< want AD in response if possible */
+#define NS_CLIENTATTR_WANTCOOKIE      0x000200 /*%< return a COOKIE */
+#define NS_CLIENTATTR_HAVECOOKIE      0x000400 /*%< has a valid COOKIE */
+#define NS_CLIENTATTR_WANTEXPIRE      0x000800 /*%< return seconds to expire */
+#define NS_CLIENTATTR_HAVEEXPIRE      0x001000 /*%< return seconds to expire */
+#define NS_CLIENTATTR_WANTOPT        0x002000 /*%< add opt to reply */
+#define NS_CLIENTATTR_HAVEECS        0x004000 /*%< received an ECS option */
+#define NS_CLIENTATTR_WANTPAD        0x008000 /*%< pad reply */
+#define NS_CLIENTATTR_USEKEEPALIVE    0x010000 /*%< use TCP keepalive */
+#define NS_CLIENTATTR_NOSETFC        0x020000 /*%< don't set servfail cache */
+#define NS_CLIENTATTR_NEEDTCP        0x040000 /*%< send TC=1 */
+#define NS_CLIENTATTR_WANTZONEVERSION 0x100000 /*%< return zoneversion */
+#define NS_CLIENTATTR_HAVEZONEVERSION 0x200000 /*%< return zoneversion */
 
 /*
  * Flag to use with the SERVFAIL cache to indicate
index c5e07bfecd7653a23782d093b27c8b34f80f9545..218900dcaf4bfeafa7e699aee68b42dad4bf7f12 100644 (file)
@@ -7938,6 +7938,55 @@ query_getexpire(query_ctx_t *qctx) {
        }
 }
 
+/*
+ * Set the zone version, if requested, when answering from a secondary,
+ * mirror, or primary zone.
+ */
+static void
+query_getzoneversion(query_ctx_t *qctx) {
+       CCTRACE(ISC_LOG_DEBUG(3), __func__);
+
+       if (qctx->zone == NULL || !qctx->is_zone ||
+           qctx->client->query.restarts != 0 ||
+           (qctx->client->attributes & NS_CLIENTATTR_WANTZONEVERSION) == 0 ||
+           (qctx->client->attributes & NS_CLIENTATTR_HAVEZONEVERSION) != 0)
+       {
+               return;
+       }
+
+       switch (dns_zone_gettype(qctx->zone)) {
+       case dns_zone_mirror:
+       case dns_zone_primary:
+       case dns_zone_secondary: {
+               isc_buffer_t b;
+               unsigned char buf[128];
+               isc_buffer_init(&b, buf, sizeof(buf));
+               isc_result_t result = dns_zone_getzoneversion(qctx->zone, &b);
+               if (result == ISC_R_SUCCESS) {
+                       size_t len = isc_buffer_usedlength(&b);
+                       /*
+                        * Sanity check zone version from database
+                        * implementations.  Minimum length and type 0
+                        * contraints.
+                        */
+                       if (len < 2 || (buf[1] == 0 && len != 6)) {
+                               return;
+                       }
+                       qctx->client->attributes |=
+                               NS_CLIENTATTR_HAVEZONEVERSION;
+                       INSIST(qctx->client->zoneversion == NULL);
+                       qctx->client->zoneversion =
+                               isc_mem_get(qctx->client->manager->mctx, len);
+                       qctx->client->zoneversionlength = len;
+                       memmove(qctx->client->zoneversion, buf, len);
+               }
+               break;
+       }
+       default:
+               break;
+       }
+}
+
 /*%
  * Fill the ANSWER section of a positive response.
  */
@@ -8101,6 +8150,11 @@ query_respond(query_ctx_t *qctx) {
         */
        query_getexpire(qctx);
 
+       /*
+        * Set the zone version
+        */
+       query_getzoneversion(qctx);
+
        result = query_addanswer(qctx);
        if (result != ISC_R_COMPLETE) {
                return result;