From: Florian Obser Subject: dig(1): Implement zoneversion edns option (RFC 9660) To: tech Date: Thu, 26 Dec 2024 10:03:51 +0100 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.