diff --git a/CHANGES b/CHANGES index 2a72463b1d..b8a7b7ede2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +6327. [func] Nsupdate can now set the UL EDNS option when sending + UPDATE requests. [GL #4419] + 6326. [func] Add workaround to enforce dynamic linker to pull jemalloc earlier than libc to ensure all memory allocations are done via jemalloc. [GL #4404] diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c index b9ceaa22dd..a02b4cbff9 100644 --- a/bin/dig/dighost.c +++ b/bin/dig/dighost.c @@ -1388,6 +1388,7 @@ typedef struct dig_ednsoptname { dig_ednsoptname_t optnames[] = { { 1, "LLQ" }, /* draft-sekar-dns-llq */ + { 2, "UL" }, /* draft-ietf-dnssd-update-lease */ { 3, "NSID" }, /* RFC 5001 */ { 5, "DAU" }, /* RFC 6975 */ { 6, "DHU" }, /* RFC 6975 */ diff --git a/bin/nsupdate/nsupdate.c b/bin/nsupdate/nsupdate.c index 3b77470e49..7290ab28aa 100644 --- a/bin/nsupdate/nsupdate.c +++ b/bin/nsupdate/nsupdate.c @@ -102,6 +102,8 @@ #define DNSDEFAULTPORT 53 +#define DEFAULT_EDNS_BUFSIZE 1232 + /* Number of addresses to request from isc_getaddresses() */ #define MAX_SERVERADDRS 4 @@ -175,6 +177,8 @@ static isc_mutex_t answer_lock; static dns_message_t *answer = NULL; static uint32_t default_ttl = 0; static bool default_ttl_set = false; +static uint32_t lease = 0, keylease = 0; +static bool lease_set = false, keylease_set = false; static bool checknames = true; static bool checksvcb = true; static const char *resolvconf = RESOLV_CONF; @@ -1518,6 +1522,90 @@ evaluate_prereq(char *cmdline) { return (make_prereq(cmdline, ispositive, isrrset)); } +static void +updateopt(void) { + isc_result_t result; + dns_ednsopt_t ednsopts[1]; + unsigned char ul[8]; + unsigned int count = 0; + + if (lease_set) { + isc_buffer_t b; + INSIST(count < ARRAY_SIZE(ednsopts)); + ednsopts[count++] = (dns_ednsopt_t){ .code = DNS_OPT_UL, + .length = keylease_set ? 8 + : 4, + .value = ul }; + + isc_buffer_init(&b, ul, sizeof(ul)); + isc_buffer_putuint32(&b, lease); + isc_buffer_putuint32(&b, keylease); + } + + if (count != 0) { + dns_rdataset_t *opt = NULL; + result = dns_message_buildopt(updatemsg, &opt, 0, + DEFAULT_EDNS_BUFSIZE, 0, ednsopts, + count); + check_result(result, "dns_message_buildopt"); + result = dns_message_setopt(updatemsg, opt); + check_result(result, "dns_message_setopt"); + } else { + result = dns_message_setopt(updatemsg, NULL); + check_result(result, "dns_message_setopt"); + } +} + +static uint16_t +evaluate_lease(char *cmdline) { + char *word; + isc_result_t result; + uint32_t value1, value2; + + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + fprintf(stderr, "could not read ttl\n"); + return (STATUS_SYNTAX); + } + + if (!strcasecmp(word, "none")) { + lease = 0; + lease_set = false; + keylease = 0; + keylease_set = false; + updateopt(); + return (STATUS_MORE); + } + + result = isc_parse_uint32(&value1, word, 10); + if (result != ISC_R_SUCCESS) { + return (STATUS_SYNTAX); + } + + word = nsu_strsep(&cmdline, " \t\r\n"); + if (word == NULL || *word == 0) { + lease = value1; + lease_set = true; + keylease = 0; + keylease_set = false; + updateopt(); + return (STATUS_MORE); + } + + result = isc_parse_uint32(&value2, word, 10); + if (result != ISC_R_SUCCESS) { + return (STATUS_SYNTAX); + } + + lease = value1; + lease_set = true; + keylease = value2; + keylease_set = true; + updateopt(); + + return (STATUS_MORE); +} + static uint16_t evaluate_server(char *cmdline) { char *word, *server; @@ -2222,6 +2310,9 @@ do_next_command(char *cmdline) { if (strcasecmp(word, "add") == 0) { return (update_addordelete(cmdline, false)); } + if (strcasecmp(word, "lease") == 0) { + return (evaluate_lease(cmdline)); + } if (strcasecmp(word, "server") == 0) { return (evaluate_server(cmdline)); } diff --git a/bin/nsupdate/nsupdate.rst b/bin/nsupdate/nsupdate.rst index 88263904ed..b98d70bbff 100644 --- a/bin/nsupdate/nsupdate.rst +++ b/bin/nsupdate/nsupdate.rst @@ -323,6 +323,11 @@ The command formats and their meanings are as follows: By default check-svcb processing is on. If check-svcb processing fails, the record is not added to the UPDATE message. +``lease time [keytime]`` + Set the EDNS Update Lease (UL) option to value to ``time`` and + optionally also set the key lease time to ``keytime`` in seconds. + If ``time`` is ``none`` the lease times are cleared. + ``prereq nxdomain domain-name`` This command requires that no resource record of any type exist with the name ``domain-name``. diff --git a/bin/tests/system/digdelv/tests.sh b/bin/tests/system/digdelv/tests.sh index ba9b25d9b6..5268e05a70 100644 --- a/bin/tests/system/digdelv/tests.sh +++ b/bin/tests/system/digdelv/tests.sh @@ -562,6 +562,25 @@ if [ -x "$DIG" ]; then status=$((status + ret)) n=$((n + 1)) + echo_i "checking ednsopt UL prints as expected (single lease) ($n)" + ret=0 + dig_with_opts @10.53.0.3 +ednsopt=UL:00000e10 +qr a.example >dig.out.test$n 2>&1 || ret=1 + pat='UL: 3600 (1 hour)' + grep "$pat" dig.out.test$n >/dev/null || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + n=$((n + 1)) + + n=$((n + 1)) + echo_i "checking ednsopt UL prints as expected (split lease) ($n)" + ret=0 + dig_with_opts @10.53.0.3 +ednsopt=UL:00000e1000127500 +qr a.example >dig.out.test$n 2>&1 || ret=1 + pat='UL: 3600/1209600 (1 hour/2 weeks)' + grep "$pat" dig.out.test$n >/dev/null || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + n=$((n + 1)) + echo_i "checking ednsopt LLQ prints as expected ($n)" ret=0 dig_with_opts @10.53.0.3 +ednsopt=llq:0001000200001234567812345678fefefefe +qr a.example >dig.out.test$n 2>&1 || ret=1 diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h index dc0c05846f..14c987f649 100644 --- a/lib/dns/include/dns/message.h +++ b/lib/dns/include/dns/message.h @@ -103,6 +103,7 @@ /*%< EDNS0 extended OPT codes */ #define DNS_OPT_LLQ 1 /*%< LLQ opt code */ +#define DNS_OPT_UL 2 /*%< UL opt code */ #define DNS_OPT_NSID 3 /*%< NSID opt code */ #define DNS_OPT_CLIENT_SUBNET 8 /*%< client subnet opt code */ #define DNS_OPT_EXPIRE 9 /*%< EXPIRE opt code */ @@ -1113,14 +1114,14 @@ dns_message_getopt(dns_message_t *msg); isc_result_t dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt); /*%< - * Set the OPT record for 'msg'. + * Set/clear the OPT record for 'msg'. * * Requires: * *\li 'msg' is a valid message with rendering intent * and no sections have been rendered. * - *\li 'opt' is a valid OPT record. + *\li 'opt' is a valid OPT record or NULL. * * Ensures: * diff --git a/lib/dns/message.c b/lib/dns/message.c index c85e579b02..f5fc1cad9a 100644 --- a/lib/dns/message.c +++ b/lib/dns/message.c @@ -2707,12 +2707,17 @@ dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt) { */ REQUIRE(DNS_MESSAGE_VALID(msg)); - REQUIRE(opt->type == dns_rdatatype_opt); + REQUIRE(opt == NULL || DNS_RDATASET_VALID(opt)); + REQUIRE(opt == NULL || opt->type == dns_rdatatype_opt); REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); REQUIRE(msg->state == DNS_SECTION_ANY); msgresetopt(msg); + if (opt == NULL) { + return (ISC_R_SUCCESS); + } + result = dns_rdataset_first(opt); if (result != ISC_R_SUCCESS) { goto cleanup; @@ -3428,7 +3433,7 @@ dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section, dns_rdataset_t *ps = NULL; const dns_name_t *name = NULL; isc_result_t result = ISC_R_SUCCESS; - char buf[sizeof("1234567890")]; + char buf[sizeof("/1234567890")]; uint32_t mbz; dns_rdata_t rdata; isc_buffer_t optbuf; @@ -3520,6 +3525,39 @@ dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section, ADD_STRING(target, "\n"); continue; } + } else if (optcode == DNS_OPT_UL) { + INDENT(style); + ADD_STRING(target, "UL:"); + if (optlen == 4U || optlen == 8U) { + uint32_t secs, key = 0; + secs = isc_buffer_getuint32(&optbuf); + snprintf(buf, sizeof(buf), " %u", secs); + ADD_STRING(target, buf); + if (optlen == 8U) { + key = isc_buffer_getuint32( + &optbuf); + snprintf(buf, sizeof(buf), + "/%u", key); + ADD_STRING(target, buf); + } + ADD_STRING(target, " ("); + result = dns_ttl_totext(secs, true, + true, target); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + if (optlen == 8U) { + ADD_STRING(target, "/"); + result = dns_ttl_totext( + key, true, true, + target); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + ADD_STRING(target, ")\n"); + continue; + } } else if (optcode == DNS_OPT_NSID) { INDENT(style); ADD_STRING(target, "NSID:"); @@ -3880,6 +3918,38 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section, ADD_STRING(target, "\n"); continue; } + } else if (optcode == DNS_OPT_UL) { + ADD_STRING(target, "; UL:"); + if (optlen == 4U || optlen == 8U) { + uint32_t secs, key = 0; + secs = isc_buffer_getuint32(&optbuf); + snprintf(buf, sizeof(buf), " %u", secs); + ADD_STRING(target, buf); + if (optlen == 8U) { + key = isc_buffer_getuint32( + &optbuf); + snprintf(buf, sizeof(buf), + "/%u", key); + ADD_STRING(target, buf); + } + ADD_STRING(target, " ("); + result = dns_ttl_totext(secs, true, + true, target); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + if (optlen == 8U) { + ADD_STRING(target, "/"); + result = dns_ttl_totext( + key, true, true, + target); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + ADD_STRING(target, ")\n"); + continue; + } } else if (optcode == DNS_OPT_NSID) { ADD_STRING(target, "; NSID:"); } else if (optcode == DNS_OPT_COOKIE) { diff --git a/lib/dns/rdata/generic/opt_41.c b/lib/dns/rdata/generic/opt_41.c index ce22cd9502..12695d1a50 100644 --- a/lib/dns/rdata/generic/opt_41.c +++ b/lib/dns/rdata/generic/opt_41.c @@ -129,6 +129,12 @@ fromwire_opt(ARGS_FROMWIRE) { } isc_region_consume(&sregion, length); break; + case DNS_OPT_UL: + if (length != 4U && length != 8U) { + return (DNS_R_OPTERR); + } + isc_region_consume(&sregion, length); + break; case DNS_OPT_CLIENT_SUBNET: { uint16_t family; uint8_t addrlen;