diff --git a/bin/tests/system/checkzone/zones/good-svcb.db b/bin/tests/system/checkzone/zones/good-svcb.db index 7b7b31d6f0..df868f3d4e 100644 --- a/bin/tests/system/checkzone/zones/good-svcb.db +++ b/bin/tests/system/checkzone/zones/good-svcb.db @@ -24,3 +24,4 @@ svcb6 SVCB 6 . mandatory=port,alpn port=60 alpn=h3 svcb7 SVCB 7 . mandatory=port,alpn port=60 alpn=h1,h3 svcb8 SVCB 8 . mandatory=port,alpn port=60 alpn="h1\\,h2,h3" svcb9 SVCB 0 44._svbc.example.net. +svcb10 SVCB 7 . alpn="h2,h3" dohpath=/{?dns} diff --git a/lib/dns/rdata/in_1/svcb_64.c b/lib/dns/rdata/in_1/svcb_64.c index 115d805bf2..f64bcb30e5 100644 --- a/lib/dns/rdata/in_1/svcb_64.c +++ b/lib/dns/rdata/in_1/svcb_64.c @@ -34,13 +34,14 @@ enum encoding { sbpr_base64, sbpr_empty, sbpr_alpn, - sbpr_keylist + sbpr_keylist, + sbpr_dohpath }; static const struct { const char *name; /* Restricted to lowercase LDH by registry. */ unsigned int value; enum encoding encoding; - bool initial; + bool initial; /* Part of the first defined set of encodings. */ } sbpr[] = { { "mandatory", 0, sbpr_keylist, true }, { "alpn", 1, sbpr_alpn, true }, @@ -49,6 +50,7 @@ static const struct { { "ipv4hint", 4, sbpr_ipv4s, true }, { "ech", 5, sbpr_base64, true }, { "ipv6hint", 6, sbpr_ipv6s, true }, + { "dohpath", 7, sbpr_dohpath, false }, }; static isc_result_t @@ -147,6 +149,30 @@ svcb_validate(uint16_t key, isc_region_t *region) { case sbpr_text: case sbpr_base64: break; + case sbpr_dohpath: + /* + * Minimum valid dohpath is "/{?dns}" as + * it MUST be relative (leading "/") and + * MUST contain "{?dns}". + */ + if (region->length < 7) { + return (DNS_R_FORMERR); + } + /* MUST be relative */ + if (region->base[0] != '/') { + return (DNS_R_FORMERR); + } + /* MUST be UTF8 */ + if (!isc_utf8_valid(region->base, + region->length)) { + return (DNS_R_FORMERR); + } + /* MUST contain "{?dns}" */ + if (strnstr((char *)region->base, "{?dns}", + region->length) == NULL) { + return (DNS_R_FORMERR); + } + break; case sbpr_empty: if (region->length != 0) { return (DNS_R_FORMERR); @@ -252,6 +278,7 @@ svc_fromtext(isc_textregion_t *region, isc_buffer_t *target) { switch (sbpr[i].encoding) { case sbpr_text: + case sbpr_dohpath: RETERR(multitxt_fromtext(region, target)); break; case sbpr_alpn: @@ -328,6 +355,19 @@ svc_fromtext(isc_textregion_t *region, isc_buffer_t *target) { len = isc_buffer_usedlength(target) - isc_buffer_usedlength(&sb) - 2; RETERR(uint16_tobuffer(len, &sb)); /* length */ + switch (sbpr[i].encoding) { + case sbpr_dohpath: + /* + * Apply constraints not applied by multitxt_fromtext. + */ + keyregion.base = isc_buffer_used(&sb); + keyregion.length = isc_buffer_usedlength(target) - + isc_buffer_usedlength(&sb); + RETERR(svcb_validate(sbpr[i].value, &keyregion)); + break; + default: + break; + } return (ISC_R_SUCCESS); } diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c index 028b8b9b2f..387121e417 100644 --- a/tests/dns/rdata_test.c +++ b/tests/dns/rdata_test.c @@ -2463,7 +2463,7 @@ ISC_RUN_TEST_IMPL(wks) { ISC_RUN_TEST_IMPL(https_svcb) { /* * Known keys: mandatory, apln, no-default-alpn, port, - * ipv4hint, port, ipv6hint. + * ipv4hint, port, ipv6hint, dohpath. */ text_ok_t text_ok[] = { /* unknown key invalid */ @@ -2482,9 +2482,9 @@ ISC_RUN_TEST_IMPL(https_svcb) { TEXT_INVALID("2 svc.example.net. key=\"2222\""), /* zero pad invalid */ TEXT_INVALID("2 svc.example.net. key07=\"2222\""), - TEXT_VALID_LOOP(1, "2 svc.example.net. key7=\"2222\""), - TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key7=2222", - "2 svc.example.net. key7=\"2222\""), + TEXT_VALID_LOOP(1, "2 svc.example.net. key8=\"2222\""), + TEXT_VALID_LOOPCHG(1, "2 svc.example.net. key8=2222", + "2 svc.example.net. key8=\"2222\""), TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h2", "2 svc.example.net. alpn=\"h2\""), TEXT_VALID_LOOPCHG(1, "2 svc.example.net. alpn=h3", @@ -2512,12 +2512,12 @@ ISC_RUN_TEST_IMPL(https_svcb) { TEXT_VALID_LOOP(1, "2 svc.example.net. ech=abcdefghijkl"), /* bad base64 */ TEXT_INVALID("2 svc.example.net. ech=abcdefghijklm"), - TEXT_VALID_LOOP(1, "2 svc.example.net. key7=\"2222\""), + TEXT_VALID_LOOP(1, "2 svc.example.net. key8=\"2222\""), /* Out of key order on input (alpn == key1). */ TEXT_VALID_LOOPCHG(2, - "2 svc.example.net. key7=\"2222\" alpn=h2", + "2 svc.example.net. key8=\"2222\" alpn=h2", "2 svc.example.net. alpn=\"h2\" " - "key7=\"2222\""), + "key8=\"2222\""), TEXT_VALID_LOOP(1, "2 svc.example.net. key65535=\"2222\""), TEXT_INVALID("2 svc.example.net. key65536=\"2222\""), TEXT_VALID_LOOP(1, "2 svc.example.net. key10"), @@ -2548,18 +2548,18 @@ ISC_RUN_TEST_IMPL(https_svcb) { TEXT_INVALID("2 svc.example.net. " "mandatory=alpn,,port alpn=h2 port=433"), /* mandatory w/ unknown key values */ - TEXT_VALID_LOOP(2, "2 svc.example.net. mandatory=key7 key7"), - TEXT_VALID_LOOP(3, "2 svc.example.net. mandatory=key7,key8 " - "key7 key8"), + TEXT_VALID_LOOP(2, "2 svc.example.net. mandatory=key8 key8"), + TEXT_VALID_LOOP(3, "2 svc.example.net. mandatory=key8,key9 " + "key8 key9"), TEXT_VALID_LOOPCHG( - 3, "2 svc.example.net. mandatory=key8,key7 key7 key8", - "2 svc.example.net. mandatory=key7,key8 key7 key8"), + 3, "2 svc.example.net. mandatory=key9,key8 key8 key9", + "2 svc.example.net. mandatory=key8,key9 key8 key9"), TEXT_INVALID("2 svc.example.net. " - "mandatory=key7,key7"), - TEXT_INVALID("2 svc.example.net. mandatory=,key7"), - TEXT_INVALID("2 svc.example.net. mandatory=key7,"), + "mandatory=key8,key8"), + TEXT_INVALID("2 svc.example.net. mandatory=,key8"), + TEXT_INVALID("2 svc.example.net. mandatory=key8,"), TEXT_INVALID("2 svc.example.net. " - "mandatory=key7,,key7"), + "mandatory=key8,,key8"), /* Invalid test vectors */ TEXT_INVALID("1 foo.example.com. ( key123=abc key123=def )"), TEXT_INVALID("1 foo.example.com. mandatory"), @@ -2572,6 +2572,14 @@ ISC_RUN_TEST_IMPL(https_svcb) { TEXT_INVALID("1 foo.example.com. mandatory=mandatory"), TEXT_INVALID("1 foo.example.com. ( mandatory=key123,key123 " "key123=abc)"), + /* dohpath tests */ + TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/{?dns}", + "1 example.net. key7=\"/{?dns}\""), + TEXT_VALID_LOOPCHG(1, "1 example.net. dohpath=/some/path{?dns}", + "1 example.net. key7=\"/some/path{?dns}\""), + TEXT_INVALID("1 example.com. dohpath=no-slash"), + TEXT_INVALID("1 example.com. dohpath=/{?notdns}"), + TEXT_INVALID("1 example.com. dohpath=/notvariable"), TEXT_SENTINEL() };