Index | Thread | Search

From:
Florian Obser <florian@openbsd.org>
Subject:
dig(1): Implement zoneversion edns option (RFC 9660)
To:
tech <tech@openbsd.org>
Date:
Thu, 26 Dec 2024 10:03:51 +0100

Download raw body.

Thread
I'm running nsd 4.11.0 on b.ns.sha256.net for now, which implements RFC
9660:

$ obj/dig +norec +zoneversion @b.ns.sha256.net sha256.net. AAAA

; <<>> dig 9.10.8-P1 <<>> +norec +zoneversion @b.ns.sha256.net sha256.net. AAAA
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8188
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; ZONEVERSION: 02 00 78 a5 a8 e9 ("SOA-SERIAL: 2024122601 (sha256.net.)")
;; QUESTION SECTION:
;sha256.net.			IN	AAAA

;; ANSWER SECTION:
sha256.net.		60	IN	AAAA	2a01:4f8:191:3241:662e:7b7d:deb5:93a3

;; Query time: 23 msec
;; SERVER: 2a05:f480:1000:22:3829:171:d537:dabe#53(2a05:f480:1000:22:3829:171:d537:dabe)
;; WHEN: Thu Dec 26 09:56:41 CET 2024
;; MSG SIZE  rcvd: 77

I've also tested it against nsd serving the root zone according to RFC
8806:

$ obj/dig +norec +zoneversion +nsid @$(hostname) sha256.net. AAAA

; <<>> dig 9.10.8-P1 <<>> +norec +zoneversion +nsid @XXX sha256.net. AAAA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62775
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 13, ADDITIONAL: 27

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; ZONEVERSION: 00 00 78 a5 a8 e8 ("SOA-SERIAL: 2024122600 (.)")
;; QUESTION SECTION:
;sha256.net.			IN	AAAA

;; AUTHORITY SECTION:
net.			172800	IN	NS	a.gtld-servers.net.
net.			172800	IN	NS	b.gtld-servers.net.
[...]

OK?

diff --git dig.1 dig.1
index 1031c377c32..063be1114d7 100644
--- dig.1
+++ dig.1
@@ -810,6 +810,12 @@ This alternate syntax to
 .Cm tcp
 is provided for backwards compatibility.
 The "vc" stands for "virtual circuit".
+.It Xo
+.Cm + Ns
+.Op Cm no Ns
+.Cm zoneversion
+.Xc
+Include an EDNS zone version request when sending a query (off by default).
 .El
 .Sh MULTIPLE QUERIES
 The BIND 9 implementation of
diff --git dig.c dig.c
index a23bcbeb4d9..14a854bbf4d 100644
--- dig.c
+++ dig.c
@@ -498,7 +498,7 @@ repopulate_buffer:
 	if (query->lookup->comments && headers && !short_form) {
 		result = dns_message_pseudosectiontotext(msg,
 			 DNS_PSEUDOSECTION_OPT,
-			 style, flags, buf);
+			 style, flags, query->lookup->textname, buf);
 		if (result == ISC_R_NOSPACE) {
 buftoosmall:
 			len += OUTPUTBUF;
@@ -563,7 +563,9 @@ buftoosmall:
 				result = dns_message_pseudosectiontotext(
 						   msg,
 						   DNS_PSEUDOSECTION_TSIG,
-						   style, flags, buf);
+						   style, flags,
+						   query->lookup->textname,
+						   buf);
 				if (result == ISC_R_NOSPACE)
 					goto buftoosmall;
 				check_result(result,
@@ -571,7 +573,9 @@ buftoosmall:
 				result = dns_message_pseudosectiontotext(
 						   msg,
 						   DNS_PSEUDOSECTION_SIG0,
-						   style, flags, buf);
+						   style, flags,
+						   query->lookup->textname,
+						   buf);
 				if (result == ISC_R_NOSPACE)
 					goto buftoosmall;
 				check_result(result,
@@ -1250,6 +1254,12 @@ plus_option(const char *option, int is_batchfile,
 			lookup->tcp_mode_set = 1;
 		}
 		break;
+	case 'z':
+		FULLCHECK("zoneversion");
+		if (!state)
+			break;
+		save_opt(lookup, "zoneversion", NULL);
+		break;
 	default:
 	invalid_option:
 	need_value:
diff --git dighost.c dighost.c
index 57a8e4a2deb..b8dda7a9f13 100644
--- dighost.c
+++ dighost.c
@@ -1274,6 +1274,7 @@ dig_ednsoptname_t optnames[] = {
 	{ 12, "PAD" },		/* shorthand */
 	{ 13, "CHAIN" },	/* RFC 7901 */
 	{ 14, "KEY-TAG" },	/* RFC 8145 */
+	{ 19, "ZONEVERSION" },	/* RFC 9660 */
 	{ 26946, "DEVICEID" },	/* Brian Hartvigsen */
 };
 
diff --git lib/dns/include/dns/message.h lib/dns/include/dns/message.h
index 6cd42be4176..6d0f2a50d32 100644
--- lib/dns/include/dns/message.h
+++ lib/dns/include/dns/message.h
@@ -105,6 +105,7 @@
 #define DNS_OPT_PAD		12		/*%< PAD opt code */
 #define DNS_OPT_KEY_TAG		14		/*%< Key tag opt code */
 #define DNS_OPT_EDE		15		/* RFC 8914 */
+#define DNS_OPT_ZONEVERSION	19		/* RFC 9660 */
 
 /*%< The number of EDNS options we know about. */
 #define DNS_EDNSOPTIONS	4
@@ -288,6 +289,7 @@ dns_message_pseudosectiontotext(dns_message_t *msg,
 				dns_pseudosection_t section,
 				const dns_master_style_t *style,
 				dns_messagetextflag_t flags,
+				const char *textname,
 				isc_buffer_t *target);
 /*%<
  * Convert section 'section' or 'pseudosection' of message 'msg' to
diff --git lib/dns/message.c lib/dns/message.c
index 49ea973ffa3..8ecedacf4f1 100644
--- lib/dns/message.c
+++ lib/dns/message.c
@@ -2484,11 +2484,42 @@ ede_info_code2str(uint16_t info_code)
 	}
 }
 
+static const char *
+zoneversion_zone(const char *zone, int labelcount)
+{
+	size_t pos;
+
+	if (zone == NULL || labelcount == 0)
+		return ".";
+
+	pos = strlen(zone);
+	if (pos == 0)
+		return ".";
+
+	pos--; /* go to last char in string */
+	if (zone[pos] == '.')
+		pos--; /* the labelcount does not count the empty root label */
+
+	for (; pos > 0; pos--) {
+		if (zone[pos] == '.') {
+			labelcount--;
+
+			if (labelcount == 0) {
+				pos++;
+				break;
+			}
+		}
+	}
+
+	return (zone + pos);
+}
+
 isc_result_t
 dns_message_pseudosectiontotext(dns_message_t *msg,
 				dns_pseudosection_t section,
 				const dns_master_style_t *style,
 				dns_messagetextflag_t flags,
+				const char *textname,
 				isc_buffer_t *target)
 {
 	dns_rdataset_t *ps = NULL;
@@ -2621,6 +2652,46 @@ dns_message_pseudosectiontotext(dns_message_t *msg,
 					    ede_info_code2str(info_code));
 					ADD_STRING(target, ")");
 				}
+			} else if (optcode == DNS_OPT_ZONEVERSION) {
+				int i;
+
+				ADD_STRING(target, "; ZONEVERSION: ");
+				optdata = isc_buffer_current(&optbuf);
+				for (i = 0; i < optlen; i++) {
+					snprintf(buf, sizeof(buf), "%02x ",
+						 optdata[i]);
+					ADD_STRING(target, buf);
+				}
+
+				if (optlen >= 2) {
+					uint8_t labelcount, type;
+					const char *zone;
+
+					labelcount =
+					    isc_buffer_getuint8(&optbuf);
+					optlen -= 1;
+					type = isc_buffer_getuint8(&optbuf);
+					optlen -= 1;
+					zone = zoneversion_zone(textname,
+					    labelcount);
+
+					if (type == 0 && optlen == 4) {
+						uint32_t serial;
+
+						serial = isc_buffer_getuint32(
+						    &optbuf);
+						optlen -= 4;
+						ADD_STRING(target,
+						    "(\"SOA-SERIAL: ");
+						snprintf(buf, sizeof(buf), "%u",
+						    serial);
+						ADD_STRING(target, buf);
+						ADD_STRING(target, " (");
+						ADD_STRING(target, zone);
+						ADD_STRING(target, ")");
+						ADD_STRING(target, "\")");
+					}
+				}
 			} else {
 				ADD_STRING(target, "; OPT=");
 				snprintf(buf, sizeof(buf), "%u", optcode);

-- 
In my defence, I have been left unsupervised.