diff --git a/include/zone.h b/include/zone.h index 5812afd..850aab4 100644 --- a/include/zone.h +++ b/include/zone.h @@ -155,9 +155,9 @@ extern "C" { #define ZONE_CSYNC (62u) /** Zone message digest @rfc{8976} */ #define ZONE_ZONEMD (63u) -/** Service binding @draft{dnsop,svcb-https} */ +/** Service binding @rfc{9460} */ #define ZONE_SVCB (64u) -/** Service binding @draft{dnsop,svcb-https} */ +/** Service binding @rfc{9460} */ #define ZONE_HTTPS (65u) /** Sender Policy Framework @rfc{7208} */ #define ZONE_SPF (99u) @@ -314,7 +314,6 @@ struct zone_field_info { * * @{ */ -// ZONE_IN (1) can be used too #define ZONE_ANY (1<<2) #define ZONE_EXPERIMENTAL (1<<3) #define ZONE_OBSOLETE (1<<4) @@ -347,13 +346,13 @@ struct zone_type_info { typedef struct zone_name_buffer zone_name_buffer_t; struct zone_name_buffer { - size_t length; /**< Length of domain name stored in block */ + size_t length; /**< Length of domain name stored in buffer */ uint8_t octets[ ZONE_NAME_SIZE + ZONE_PADDING_SIZE ]; }; typedef struct zone_rdata_buffer zone_rdata_buffer_t; struct zone_rdata_buffer { - size_t length; /**< Length of RDATA stored in block */ + size_t length; /**< Length of RDATA stored in buffer */ uint8_t octets[ ZONE_RDATA_SIZE + 4096 /* nsec padding */ ]; }; @@ -538,7 +537,7 @@ typedef struct { */ typedef struct zone_buffers zone_buffers_t; struct zone_buffers { - size_t size; /**< Number of name and rdata storage blocks available */ + size_t size; /**< Number of name and rdata buffers available */ zone_name_buffer_t *owner; zone_rdata_buffer_t *rdata; }; diff --git a/src/fallback/parser.c b/src/fallback/parser.c index e983bec..1facd4f 100644 --- a/src/fallback/parser.c +++ b/src/fallback/parser.c @@ -34,6 +34,7 @@ #include "generic/loc.h" #include "generic/gpos.h" #include "generic/apl.h" +#include "generic/svcb.h" #include "types.h" #include "fallback/type.h" #include "parser.h" diff --git a/src/fallback/text.h b/src/fallback/text.h index bb653ba..3509eee 100644 --- a/src/fallback/text.h +++ b/src/fallback/text.h @@ -37,6 +37,8 @@ static zone_really_inline int32_t parse_string( const uint8_t *ws = w - 1, *we = w + 255; const char *t = token->data, *te = t + token->length; + // FIXME: SWAR can possibly applied to improve performance and copy + // eight bytes as opposed to one while ((t < te) & (w < we)) { *w = (uint8_t)*t; if (zone_unlikely(*t == '\\')) { diff --git a/src/generic/ip6.h b/src/generic/ip6.h index d25caa9..72a036d 100644 --- a/src/generic/ip6.h +++ b/src/generic/ip6.h @@ -42,7 +42,7 @@ inet_pton4(const char *src, uint8_t *dst) saw_digit = 0; octets = 0; *(tp = tmp) = 0; - while (contiguous[ (ch = *src++) ] == CONTIGUOUS) { + for (; (ch = *src); src++) { const char *pch; if ((pch = strchr(digits, ch)) != NULL) { @@ -106,7 +106,7 @@ inet_pton6(const char *src, uint8_t *dst) curtok = src; saw_xdigit = 0; val = 0; - while (contiguous[ (ch = *src++) ] == CONTIGUOUS) { + for (; (ch = *src); src++) { const char *pch; if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) @@ -120,7 +120,7 @@ inet_pton6(const char *src, uint8_t *dst) continue; } if (ch == ':') { - curtok = src; + curtok = src+1; if (!saw_xdigit) { if (colonp) return (0); @@ -137,12 +137,12 @@ inet_pton6(const char *src, uint8_t *dst) } if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && (len = inet_pton4(curtok, tp)) > 0) { - src += len; + src = curtok + len; tp += NS_INADDRSZ; saw_xdigit = 0; break; /* '\0' was seen by inet_pton4(). */ } - return -1; + break; } if (saw_xdigit) { if (tp + NS_INT16SZ > endp) diff --git a/src/generic/number.h b/src/generic/number.h index 87e86c5..d71bd0e 100644 --- a/src/generic/number.h +++ b/src/generic/number.h @@ -9,6 +9,7 @@ #ifndef NUMBER_H #define NUMBER_H +// FIXME: remove in favor of specialized functions, much easier zone_nonnull_all static zone_really_inline int32_t parse_symbol8( zone_parser_t *parser, @@ -21,6 +22,7 @@ static zone_really_inline int32_t parse_symbol8( if ((r = have_contiguous(parser, type, field, token)) < 0) return r; + // FIXME: implement generic number scanning uint64_t n = 0; const char *p = token->data; for (;; p++) { @@ -30,6 +32,7 @@ static zone_really_inline int32_t parse_symbol8( n = n * 10 + d; } + // FIXME: replace with simple length check if (is_contiguous((uint8_t)*p)) { const zone_symbol_t *s; if (!(s = lookup_symbol(&field->symbols, token))) diff --git a/src/generic/svcb.h b/src/generic/svcb.h new file mode 100644 index 0000000..11947df --- /dev/null +++ b/src/generic/svcb.h @@ -0,0 +1,823 @@ +/* + * svcb.h -- svcb parser + * + * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ +#ifndef SVCB_H +#define SVCB_H + +typedef struct svc_param_info svc_param_info_t; +struct svc_param_info; + +// FIXME: move, to be used by all parse functions +typedef struct rdata rdata_t; +struct rdata { + uint8_t *octets; + uint8_t *limit; +}; + +typedef int32_t (*svc_param_parse_t)( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *svc_param_info, + rdata_t *rdata, + const token_t *); + +struct svc_param_info { + zone_symbol_t name; + bool value; + svc_param_parse_t parse, parse_strict; +}; + +// RFC9460 section 7.1: +// The "alpn" and "no-default-alpn" SvcParamKeys together indicate the set +// of Application-Layer Protocol Negotiation (ALPN) protocol identifiers +// [ALPN] and associated transport protocols supported by this service +// endpoint (the "SVCB ALPN set"). +// +// RFC9460 section 7.1.1: +// ALPNs are identified by their registered "Identification Sequence" +// (alpn-id), which is a sequence of 1-255 octets. For "alpn", the +// presentation value SHALL be a comma-separated list (Appendix A.1) of +// one or more alpn-ids. Zone-file implementations MAY disallow the "," +// and "\\" characters in ALPN IDs instead of implementing the value-list +// escaping procedure, relying on the opaque key format (e.g., key=\002h2) +// in the event that these characters are needed. +// +// Application-Layer Protocol Negotiation (ALPN) protocol identifiers are +// maintained by IANA: +// https://www.iana.org/assignments/tls-extensiontype-values#alpn-protocol-ids +// +// RFC9460 appendix A.1: +// ... A value-list parser that splits on "," and prohibits items +// containing "\"" is sufficient to comply with all requirements in +// this document. ... +zone_nonnull_all +static int32_t parse_alpn( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *param, + rdata_t *rdata, + const token_t *token) +{ + // FIXME: easily optimized by applying vectorization + uint8_t *separator = rdata->octets; + uint8_t *octet = rdata->octets + 1; + uint8_t *limit = rdata->octets + 1 + token->length; + if (limit > rdata->limit) + SYNTAX_ERROR(parser, "Invalid alpn in %s", TNAME(type)); + + memcpy(octet, token->data, token->length); + + (void)field; + (void)key; + (void)param; + + for (; octet < limit; octet++) { + // FIXME: SIMD and possibly SWAR can easily be used to improve + if (*octet == '\\') + SYNTAX_ERROR(parser, "Invalid alpn in %s", TNAME(type)); + if (*octet != ',') + continue; + assert(separator < octet); + const size_t length = ((uintptr_t)octet - (uintptr_t)separator) - 1; + if (length == 0) + SYNTAX_ERROR(parser, "Invalid alpn in %s", TNAME(type)); + if (length > 255) + SYNTAX_ERROR(parser, "Invalid alpn in %s", TNAME(type)); + *separator = (uint8_t)length; + separator = octet; + } + + const size_t length = ((uintptr_t)octet - (uintptr_t)separator) - 1; + if (length == 0) + SYNTAX_ERROR(parser, "Invalid alpn in %s", TNAME(type)); + if (length > 255) + SYNTAX_ERROR(parser, "Invalid alpn in %s", TNAME(type)); + *separator = (uint8_t)length; + + rdata->octets = limit; + return 0; +} + +zone_nonnull_all +static int32_t parse_port( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *param, + rdata_t *rdata, + const token_t *token) +{ + const char *data = token->data; + + (void)field; + (void)key; + (void)param; + + if (!token->length || token->length > 5) + SYNTAX_ERROR(parser, "Invalid port in %s", TNAME(type)); + + uint64_t number = 0; + for (;; data++) { + const uint64_t digit = (uint8_t)*data - '0'; + if (digit > 9) + break; + number = number * 10 + digit; + } + + uint16_t port = (uint16_t)number; + port = htobe16(port); + memcpy(rdata->octets, &port, 2); + rdata->octets += 2; + + if (rdata->octets > rdata->limit) + SYNTAX_ERROR(parser, "Invalid %s", TNAME(type)); + if (data != token->data + token->length || number > 65535) + SYNTAX_ERROR(parser, "Invalid port in %s", TNAME(type)); + return 0; +} + +zone_nonnull_all +static int32_t parse_ipv4hint( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *param, + rdata_t *rdata, + const token_t *token) +{ + const char *t = token->data, *te = token->data + token->length; + size_t n = 0; + + (void)field; + (void)key; + (void)param; + + if (scan_ip4(t, rdata->octets, &n) == -1) + SYNTAX_ERROR(parser, "Invalid ipv4hint in %s", TNAME(type)); + rdata->octets += 4; + t += n; + + while (*t == ',') { + if (rdata->octets > rdata->limit) + SYNTAX_ERROR(parser, "Invalid ipv4hint in %s", TNAME(type)); + if (scan_ip4(t + 1, rdata->octets, &n) == -1) + SYNTAX_ERROR(parser, "Invalid ipv4hint in %s", TNAME(type)); + rdata->octets += 4; + t += n + 1; + } + + if (t != te || rdata->octets > rdata->limit) + SYNTAX_ERROR(parser, "Invalid ipv4hint in %s", TNAME(type)); + return 0; +} + +zone_nonnull_all +static int32_t parse_ech( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *param, + rdata_t *rdata, + const token_t *token) +{ + size_t size = (uintptr_t)rdata->limit - (uintptr_t)rdata->octets; + size_t length; + + (void)field; + (void)key; + (void)param; + + if (token->length / 4 > size / 3) + SYNTAX_ERROR(parser, "maximum size exceeded"); + + struct base64_state state = { 0 }; + if (!base64_stream_decode( + &state, token->data, token->length, rdata->octets, &length)) + SYNTAX_ERROR(parser, "Invalid ech in %s", TNAME(type)); + + rdata->octets += length; + if (state.bytes) + SYNTAX_ERROR(parser, "Invalid ech in %s", TNAME(type)); + + return 0; +} + +zone_nonnull_all +static int32_t parse_ipv6hint( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *param, + rdata_t *rdata, + const token_t *token) +{ + const char *t = token->data, *te = token->data + token->length; + size_t n = 0; + + (void)field; + (void)key; + (void)param; + + if (scan_ip6(t, rdata->octets, &n) == -1) + SYNTAX_ERROR(parser, "Invalid ipv6hint in %s", TNAME(type)); + rdata->octets += 16; + t += n; + + while (*t == ',') { + if (rdata->octets >= rdata->limit) + SYNTAX_ERROR(parser, "Invalid ipv6hint in %s", TNAME(type)); + if (scan_ip6(t + 1, rdata->octets, &n) == -1) + SYNTAX_ERROR(parser, "Invalid ipv6hint in %s", TNAME(type)); + rdata->octets += 16; + t += n + 1; + } + + if (t != te || rdata->octets > rdata->limit) + SYNTAX_ERROR(parser, "Invalid ipv6hint in %s", TNAME(type)); + return 0; +} + +// RFC9461 section 5: +// "dohpath" is a single-valued SvcParamKey whose value (in both +// presentation format and wire format) MUST be a URI Template in +// relative form ([RFC6570], Section 1.1) encoded in UTF-8 [RFC3629]. +zone_nonnull_all +static int32_t parse_dohpath_strict( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *param, + rdata_t *rdata, + const token_t *token) +{ + const char *t = token->data, *te = t + token->length; + + (void)field; + (void)key; + (void)param; + + // FIXME: easily optimized using SIMD (and possibly SWAR) + while ((t < te) & (rdata->octets < rdata->limit)) { + *rdata->octets = (uint8_t)*t; + if (*t == '\\') { + uint32_t o; + if (!(o = unescape(t, rdata->octets))) + SYNTAX_ERROR(parser, "Invalid dohpath in %s", TNAME(type)); + rdata->octets += 1; t += o; + } else { + rdata->octets += 1; t += 1; + } + } + + // RFC9461 section 5: + // The URI Template MUST contain a "dns" variable, and MUST be chosen such + // that the result after DoH URI Template expansion (RFC8484 section 6) + // is always a valid and function ":path" value (RFC9113 section 8.3.1) + // FIXME: implement + + if (t != te || rdata->octets >= rdata->limit) + SYNTAX_ERROR(parser, "Invalid dohpath in %s", TNAME(type)); + return 0; +} + +zone_nonnull_all +static int32_t parse_dohpath( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *param, + rdata_t *rdata, + const token_t *token) +{ + const char *t = token->data, *te = t + token->length; + + (void)field; + (void)key; + (void)param; + + // FIXME: easily optimized using SIMD (and possibly SWAR) + while ((t < te) & (rdata->octets < rdata->limit)) { + *rdata->octets = (uint8_t)*t; + if (*t == '\\') { + uint32_t o; + if (!(o = unescape(t, rdata->octets))) + SYNTAX_ERROR(parser, "Invalid dohpath in %s", TNAME(type)); + rdata->octets += 1; t += o; + } else { + rdata->octets += 1; t += 1; + } + } + + if (t != te || rdata->octets >= rdata->limit) + SYNTAX_ERROR(parser, "Invalid dohpath in %s", TNAME(type)); + return 0; +} + +zone_nonnull_all +static zone_never_inline int32_t parse_unknown( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *param, + rdata_t *rdata, + const token_t *token) +{ + const char *t = token->data, *te = t + token->length; + + (void)key; + (void)param; + + while ((t < te) & (rdata->octets < rdata->limit)) { + *rdata->octets = (uint8_t)*t; + if (*t == '\\') { + uint32_t o; + if (!(o = unescape(t, rdata->octets))) + SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), TNAME(type)); + rdata->octets += 1; t += o; + } else { + rdata->octets += 1; t += 1; + } + } + + if (t != te || rdata->octets >= rdata->limit) + SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), TNAME(type)); + return 0; +} + +/** + * @defgroup svc_params Service Parameter Keys + * + * [IANA registered service parameter keys](https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml) + * + * @{ + */ +/** Parameters clients must not ignore @rfc{9460} */ +#define SVC_PARAM_KEY_MANDATORY (0u) +/** Application Layer Protocol Negotiation (ALPN) protocol identifiers @rfc{9460} */ +#define SVC_PARAM_KEY_ALPN (1u) +/** No support for default protocol (alpn must be specified) @rfc{9460} */ +#define SVC_PARAM_KEY_NO_DEFAULT_ALPN (2u) +/** TCP or UDP port for alternative endpoint @rfc{9460} */ +#define SVC_PARAM_KEY_PORT (3u) +/** IPv4 address hints @rfc{9460} */ +#define SVC_PARAM_KEY_IPV4HINT (4u) +/** Encrypted ClientHello (ECH) configuration @draft{ietf, tls-svcb-ech} */ +#define SVC_PARAM_KEY_ECH (5u) +/** IPv6 address hints @rfc{9460} */ +#define SVC_PARAM_KEY_IPV6HINT (6u) +/** URI template in relative form @rfc{9461} */ +#define SVC_PARAM_KEY_DOHPATH (7u) +/** Target is an Oblivious HTTP service @draft{ohai,svcb-config} */ +#define SVC_PARAM_KEY_OHTTP (8u) +/** Reserved ("invalid key") @rfc{9460} */ +#define SVC_PARAM_KEY_INVALID_KEY (65535u) +/** @} */ + +zone_nonnull_all +static int32_t parse_mandatory_strict( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *svc_param, + rdata_t *rdata, + const token_t *token); + +zone_nonnull_all +static int32_t parse_mandatory( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *svc_param, + rdata_t *rdata, + const token_t *token); + +#define SVC_PARAM(name, key, value, parse, parse_sorted) \ + { { { name, sizeof(name) - 1 }, key }, value, parse, parse_sorted } + +static const svc_param_info_t svc_params[] = { + SVC_PARAM("mandatory", 0u, true, parse_mandatory, parse_mandatory_strict), + SVC_PARAM("alpn", 1u, true, parse_alpn, parse_alpn), + // RFC9460 section 7.1.1: + // For "no-default-alpn", the presentation and wire format values MUST be + // empty. When "no-default-alpn" is specified in an RR, "alpn" must also be + // specified in order for the RR to be "self-consistent" (Section 2.4.3). + SVC_PARAM("no-default-alpn", 2u, false, 0, 0), + SVC_PARAM("port", 3u, true, parse_port, parse_port), + SVC_PARAM("ipv4hint", 4u, true, parse_ipv4hint, parse_ipv4hint), + SVC_PARAM("ech", 5u, true, parse_ech, parse_ech), + SVC_PARAM("ipv6hint", 6u, true, parse_ipv6hint, parse_ipv6hint), + // RFC9461 section 5: + // If the "alpn" SvcParam indicates support for HTTP, "dohpath" MUST be + // present. + SVC_PARAM("dohpath", 7u, true, parse_dohpath, parse_dohpath_strict), + SVC_PARAM("ohttp", 8u, false, 0, 0), +}; + +static const svc_param_info_t unknown_svc_param = + SVC_PARAM("unknown", 0u, true, parse_unknown, parse_unknown); + +#undef SVC_PARAM + +zone_nonnull_all +static zone_really_inline size_t scan_unknown_svc_param_key( + const char *data, uint16_t *key, const svc_param_info_t **param) +{ + size_t length = 0; + uint64_t number = 0; + for (;; length++) { + const uint64_t digit = (uint8_t)data[length] - '0'; + if (digit > 9) + break; + number = number * 10 + digit; + } + + if (!length || length > 4) + return 0; + if (number < (sizeof(svc_params) / sizeof(svc_params[0]))) + return (void)(*param = &svc_params[(*key = (uint16_t)number)]), length; + if (number < 65535) + return (void)(*key = (uint16_t)number), (void)(*param = &unknown_svc_param), length + 3; + return 0; +} + +zone_nonnull_all +static zone_really_inline size_t scan_svc_param( + const char *data, uint16_t *key, const svc_param_info_t **param) +{ + // draft-ietf-dnsop-svcb-https-12 section 2.1: + // alpha-lc = %x61-7A ; a-z + // SvcParamKey = 1*63(alpha-lc / DIGIT / "-") + // + // FIXME: naive implementation + if (memcmp(data, "mandatory", 9) == 0) + return (void)(*param = &svc_params[(*key = SVC_PARAM_KEY_MANDATORY)]), 9; + else if (memcmp(data, "alpn", 4) == 0) + return (void)(*param = &svc_params[(*key = SVC_PARAM_KEY_ALPN)]), 4; + else if (memcmp(data, "no-default-alpn", 15) == 0) + return (void)(*param = &svc_params[(*key = SVC_PARAM_KEY_NO_DEFAULT_ALPN)]), 15; + else if (memcmp(data, "port", 4) == 0) + return (void)(*param = &svc_params[(*key = SVC_PARAM_KEY_PORT)]), 4; + else if (memcmp(data, "ipv4hint", 8) == 0) + return (void)(*param = &svc_params[(*key = SVC_PARAM_KEY_IPV4HINT)]), 8; + else if (memcmp(data, "ech", 3) == 0) + return (void)(*param = &svc_params[(*key = SVC_PARAM_KEY_ECH)]), 3; + else if (memcmp(data, "ipv6hint", 8) == 0) + return (void)(*param = &svc_params[(*key = SVC_PARAM_KEY_IPV6HINT)]), 8; + else if (memcmp(data, "dohpath", 7) == 0) + return (void)(*param = &svc_params[(*key = SVC_PARAM_KEY_DOHPATH)]), 7; + else if (memcmp(data, "ohttp", 5) == 0) + return (void)(*param = &svc_params[(*key = SVC_PARAM_KEY_OHTTP)]), 5; + else if (memcmp(data, "key", 0) == 0) + return scan_unknown_svc_param_key(data + 3, key, param); + else + return 0; +} + +zone_nonnull_all +static zone_really_inline size_t scan_svc_param_key( + const char *data, uint16_t *key) +{ + // FIXME: improve implementation + const svc_param_info_t *param; + return scan_svc_param(data, key, ¶m); +} + +zone_nonnull_all +static zone_really_inline int32_t parse_mandatory_strict( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *param, + rdata_t *rdata, + const token_t *token) +{ + (void)field; + + // RFC9460 section 8: + // The presentation value SHALL be a comma-seperatred list of one or more + // valid SvcParamKeys, ... + int32_t highest_key = -1; + const char *data = token->data; + uint8_t *whence = rdata->octets; + size_t skip; + + if (!(skip = scan_svc_param_key(data, &key))) + SYNTAX_ERROR(parser, "Invalid %s in %s", TNAME(param), TNAME(type)); + + highest_key = key; + key = htobe16(key); + memcpy(rdata->octets, &key, sizeof(key)); + rdata->octets += sizeof(key); + data += skip; + + while (*data == ',' && rdata->octets < rdata->limit) { + if (!(skip = scan_svc_param_key(data + 1, &key))) + SYNTAX_ERROR(parser, "Invalid mandatory of %s", TNAME(type)); + data += skip + 1; + if (key > highest_key) { + highest_key = key; + key = htobe16(key); + memcpy(rdata->octets, &key, 2); + rdata->octets += 2; + } else { + // RFC9460 section 8: + // In wire format, the keys are represented by their numeric values in + // network byte order, concatenated in ascending order. + uint8_t *octets = whence; + uint16_t smaller_key = 0; + while (octets < rdata->octets) { + memcpy(&smaller_key, octets, sizeof(smaller_key)); + smaller_key = be16toh(smaller_key); + if (key < smaller_key) + break; + octets += 2; + } + assert(octets < rdata->octets); + // RFC9460 section 8: + // Keys MAY appear in any order, but MUST NOT appear more than once. + if (key == smaller_key) + SYNTAX_ERROR(parser, "Duplicate key in mandatory of %s", TNAME(type)); + assert(key < smaller_key); + uint16_t length = (uint16_t)(rdata->octets - octets); + memmove(octets + 2, octets, length); + key = htobe16(key); + memcpy(octets, &key, 2); + rdata->octets += 2; + } + } + + if (rdata->octets >= rdata->limit) + SYNTAX_ERROR(parser, "Invalid %s", TNAME(type)); + if (data != token->data + token->length) + SYNTAX_ERROR(parser, "..."); + return 0; +} + +zone_nonnull_all +static zone_really_inline int32_t parse_mandatory( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + uint16_t key, + const svc_param_info_t *param, + rdata_t *rdata, + const token_t *token) +{ + size_t skip; + const char *data = token->data; + + (void)field; + + if (!(skip = scan_svc_param_key(data, &key))) + SYNTAX_ERROR(parser, "Invalid key in %s of %s", TNAME(param), TNAME(type)); + memcpy(rdata->octets, &key, 2); + rdata->octets += 2; + data += skip; + + while (*data == ',' && rdata->octets < rdata->limit) { + if (!(skip = scan_svc_param_key(data + 1, &key))) + SYNTAX_ERROR(parser, "Invalid key in %s of %s", TNAME(param), TNAME(type)); + data += skip + 1; + memcpy(rdata->octets, &key, 2); + rdata->octets += 2; + } + + if (rdata->octets >= rdata->limit - 2) + SYNTAX_ERROR(parser, "Invalid %s", TNAME(type)); + if (data != token->data + token->length) + SYNTAX_ERROR(parser, "Invalid %s", TNAME(type)); + return 0; +} + +// https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml +zone_nonnull_all +static zone_really_inline int32_t parse_svc_params_strict( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + // rdata_t rdata, + token_t *token) +{ + const uint16_t zero = 0; + int32_t code, highest_key = -1; + uint8_t *whence = parser->rdata->octets + parser->rdata->length; + rdata_t rdata = { parser->rdata->octets + parser->rdata->length, + parser->rdata->octets + ZONE_RDATA_SIZE }; + + while (token->code == CONTIGUOUS) { + size_t skip; + uint16_t key; + const svc_param_info_t *param; + + if (!(skip = scan_svc_param(token->data, &key, ¶m))) + SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), TNAME(type)); + assert(param); + + if (key > highest_key) { + highest_key = key; + + switch ((token->data[skip] == '=') + (param->value << 1)) { + case 1: // void parameter with value + SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), TNAME(type)); + case 0: // void parameter without value + case 2: // parameter without optional value + if (skip != token->length) + SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), TNAME(type)); + key = htobe16(key); + memcpy(rdata.octets, &key, sizeof(key)); + memcpy(rdata.octets+2, &zero, sizeof(zero)); + rdata.octets += 4; + break; + case 3: // parameter with value + skip += 1; + // quoted parameter, separate token + if (token->data[skip] == '"') + lex(parser, token); + else + (void)(token->data += skip), token->length -= skip; + { + uint8_t *octets = rdata.octets; + rdata.octets += 4; + code = param->parse_strict( + parser, type, field, key, param, &rdata, token); + if (code < 0) + return code; + uint16_t length = (uint16_t)(rdata.octets - octets) - 4; + key = htobe16(key); + length = htobe16(length); + memcpy(octets, &key, sizeof(key)); + memcpy(octets+2, &length, sizeof(length)); + } + break; + } + } else { + uint8_t *octets = whence; + uint16_t smaller_key = 65535; + + while (octets < rdata.octets) { + memcpy(&smaller_key, octets, sizeof(smaller_key)); + smaller_key = be16toh(smaller_key); + if (key <= smaller_key) + break; + uint16_t length; + memcpy(&length, octets + 2, sizeof(length)); + length = be16toh(length); + octets += length + 4; + } + + assert(octets < rdata.octets); + if (key == smaller_key) + SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), TNAME(type)); + + switch ((token->data[skip] == '=') + (param->value << 1)) { + case 1: // void parameter with value + SYNTAX_ERROR(parser, "foobar1"); + case 0: // void parameter without value + case 2: // parameter without value + key = htobe16(key); + memmove(octets + 4, octets, (uintptr_t)rdata.octets - (uintptr_t)octets); + memcpy(octets, &key, sizeof(key)); + memcpy(octets+2, &zero, sizeof(zero)); + rdata.octets += 4; + break; + case 3: // parameter with value + skip += 1; + // quoted parameter, separate token + if (token->data[skip] == '"') + lex(parser, token); + else + (void)(token->data += skip), token->length -= skip; + { + uint16_t length; + rdata_t param_rdata; + // RFC9460 section 2.2: + // SvcParamKeys SHALL appear in increasing numeric order. + // + // move existing data to end of the buffer and reset limit to + // avoid allocating memory + assert(rdata.octets - octets < ZONE_RDATA_SIZE); + length = (uint16_t)(rdata.octets - octets); + param_rdata.octets = octets + 4u; + param_rdata.limit = parser->rdata->octets + (ZONE_RDATA_SIZE - length); + // move data PADDING_SIZE past limit to ensure SIMD operatations + // do not overwrite existing data + memmove(param_rdata.limit + ZONE_PADDING_SIZE, octets, length); + code = param->parse_strict( + parser, type, field, key, param, ¶m_rdata, token); + if (code) + return code; + assert(param_rdata.octets < param_rdata.limit); + memmove(param_rdata.octets, param_rdata.limit + ZONE_PADDING_SIZE, length); + rdata.octets = param_rdata.octets + length; + length = (uint16_t)(param_rdata.octets - octets) - 4u; + key = htobe16(key); + length = htobe16(length); + memcpy(octets, &key, sizeof(key)); + memcpy(octets+2, &length, sizeof(length)); + } + break; + } + } + + lex(parser, token); + } + + // FIXME: check all keys specified in mandatory are actually specified!!!! + + // FIXME: remove once all parsers use rdata_t + parser->rdata->length = (uintptr_t)rdata.octets - (uintptr_t)parser->rdata->octets; + + return have_delimiter(parser, type, token); +} + +zone_nonnull_all +static zone_really_inline int32_t parse_svc_params( + zone_parser_t *parser, + const zone_type_info_t *type, + const zone_field_info_t *field, + // rdata_t *rdata, + token_t *token) +{ + // propagate data as-is if secondary + if (!parser->options.secondary) + return parse_svc_params_strict(parser, type, field, token); + + int32_t code; + const uint16_t zero = 0; + rdata_t rdata = { + parser->rdata->octets + parser->rdata->length, + parser->rdata->octets + 65535 }; + + while (token->code == CONTIGUOUS) { + size_t skip; + uint16_t key; + const svc_param_info_t *param; + + if (!(skip = scan_svc_param(token->data, &key, ¶m))) + SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), TNAME(type)); + assert(param); + + switch ((token->data[skip] == '=') + (param->value << 1)) { + case 1: // void parameter with value + SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), TNAME(type)); + case 0: // void parameter without value + case 2: // parameter without value + if (skip != token->length) + SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), TNAME(type)); + key = htobe16(key); + memcpy(rdata.octets, &key, sizeof(key)); + memcpy(rdata.octets+2, &zero, sizeof(zero)); + rdata.octets += 4; + break; + case 3: // parameter with value + skip += 1; + // quoted value, separate token + if (token->data[skip] == '"') + lex(parser, token); + else + (void)(token->data += skip), token->length -= skip; + { + uint8_t *octets = rdata.octets; + rdata.octets += 4; + code = param->parse( + parser, type, field, key, param, &rdata, token); + if (code) + return code; + uint16_t length = (uint16_t)(rdata.octets - octets) - 4; + key = htobe16(key); + length = htobe16(length); + memcpy(octets, &key, sizeof(key)); + memcpy(octets+2, &length, sizeof(length)); + } + break; + } + + lex(parser, token); + } + + // FIXME: remove once all parsers use rdata_t + parser->rdata->length = (uintptr_t)rdata.octets - (uintptr_t)parser->rdata->octets; + + return have_delimiter(parser, type, token); +} + +#endif // SVCB_H diff --git a/src/haswell/parser.c b/src/haswell/parser.c index f7fb3bc..06c7e67 100644 --- a/src/haswell/parser.c +++ b/src/haswell/parser.c @@ -35,6 +35,7 @@ #include "generic/loc.h" #include "generic/gpos.h" #include "generic/apl.h" +#include "generic/svcb.h" #include "types.h" #include "westmere/type.h" #include "parser.h" diff --git a/src/types.h b/src/types.h index 9c2ec86..2709cfe 100644 --- a/src/types.h +++ b/src/types.h @@ -1938,6 +1938,62 @@ static int32_t parse_zonemd_rdata( return accept_rr(parser, type); } +zone_nonnull_all +static int32_t check_svcb_rr( + zone_parser_t *parser, const zone_type_info_t *type) +{ + // FIXME: implement checking parameters etc + return accept_rr(parser, type); +} + +zone_nonnull_all +static int32_t parse_svcb_rdata( + zone_parser_t *parser, const zone_type_info_t *type, token_t *token) +{ + int32_t r; + + if ((r = parse_int16(parser, type, &type->rdata.fields[0], token)) < 0) + return r; + lex(parser, token); + // + // See 2.5. Special Handling of "." in TargetName + // if "." is used as the name it does not mean , it means something + // else for svcb!!!! + // + if ((r = parse_name(parser, type, &type->rdata.fields[1], token)) < 0) + return r; + lex(parser, token); + if ((r = parse_svc_params(parser, type, &type->rdata.fields[2], token)) < 0) + return r; + + return accept_rr(parser, type); +} + +zone_nonnull_all +static int32_t check_https_rr( + zone_parser_t *parser, const zone_type_info_t *type) +{ + return accept_rr(parser, type); +} + +zone_nonnull_all +static int32_t parse_https_rdata( + zone_parser_t *parser, const zone_type_info_t *type, token_t *token) +{ + int32_t r; + + if ((r = parse_int16(parser, type, &type->rdata.fields[0], token)) < 0) + return r; + lex(parser, token); + if ((r = parse_name(parser, type, &type->rdata.fields[1], token)) < 0) + return r; + lex(parser, token); + if ((r = parse_svc_params(parser, type, &type->rdata.fields[2], token)) < 0) + return r; + + return accept_rr(parser, type); +} + zone_nonnull_all static int32_t check_nid_rr( zone_parser_t *parser, const zone_type_info_t *type) @@ -2420,6 +2476,8 @@ static const zone_symbol_t cert_type_symbols[] = { // https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml static const zone_symbol_t dnssec_algorithm_symbols[] = { + // >> we should remove this from the table and use specialized functions + // instead. much easier! SYMBOL("DH", 2), SYMBOL("DSA", 3), SYMBOL("DSA-NSEC-SHA1", 6), @@ -2599,6 +2657,18 @@ static const zone_field_info_t zonemd_rdata_fields[] = { FIELD("digest", ZONE_BLOB, ZONE_BASE16), }; +static const zone_field_info_t svcb_rdata_fields[] = { + FIELD("priority", ZONE_INT16, 0), + FIELD("target", ZONE_NAME, 0), + FIELD("params", 0, 0) +}; + +static const zone_field_info_t https_rdata_fields[] = { + FIELD("priority", ZONE_INT16, 0), + FIELD("target", ZONE_NAME, 0), + FIELD("params", 0, 0) +}; + static const zone_field_info_t spf_rdata_fields[] = { FIELD("text", ZONE_STRING, ZONE_SEQUENCE) }; @@ -2790,9 +2860,11 @@ static const type_descriptor_t types[] = { check_csync_rr, parse_csync_rdata), TYPE("ZONEMD", ZONE_ZONEMD, ZONE_ANY, FIELDS(zonemd_rdata_fields), check_zonemd_rr, parse_zonemd_rdata), + TYPE("SVCB", ZONE_SVCB, ZONE_IN, FIELDS(svcb_rdata_fields), + check_svcb_rr, parse_svcb_rdata), + TYPE("HTTPS", ZONE_HTTPS, ZONE_IN, FIELDS(https_rdata_fields), + check_https_rr, parse_https_rdata), - UNKNOWN_TYPE(64), - UNKNOWN_TYPE(65), UNKNOWN_TYPE(66), UNKNOWN_TYPE(67), UNKNOWN_TYPE(68), diff --git a/src/westmere/parser.c b/src/westmere/parser.c index 9033a3e..b5f246d 100644 --- a/src/westmere/parser.c +++ b/src/westmere/parser.c @@ -35,6 +35,7 @@ #include "generic/loc.h" #include "generic/gpos.h" #include "generic/apl.h" +#include "generic/svcb.h" #include "types.h" #include "westmere/type.h" #include "parser.h" diff --git a/src/westmere/type.h b/src/westmere/type.h index 65e9847..1f34c3c 100644 --- a/src/westmere/type.h +++ b/src/westmere/type.h @@ -114,12 +114,15 @@ static zone_really_inline int32_t find_type_or_class( //if (token->length > 16) // zero_mask = _mm_loadu_si128((const __m128i *)zero_masks); //else - zero_mask = _mm_loadu_si128((const __m128i *)(zero_masks + 16 - token->length)); + // FIXME: fix! length may read out of bounds! + zero_mask = _mm_loadu_si128((const __m128i *)(zero_masks + 16 - token->length)); input = _mm_and_si128(input, _mm_shuffle_epi8(upper, nibbles)); input = _mm_andnot_si128(zero_mask, input); // input is now sanitized and upper case + // FIXME: there is no reason this cannot be used for fallback + // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/pull/85 const uint8_t index = hash((uint64_t)_mm_cvtsi128_si64(input)); *symbol = types_and_classes[index].symbol; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8e6bdc1..bc24304 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ find_package(cmocka REQUIRED) -cmocka_add_tests(zone-tests types.c include.c ip4.c time.c base32.c) +cmocka_add_tests(zone-tests types.c include.c ip4.c time.c base32.c svcb.c) target_link_libraries(zone-tests PRIVATE zone) if(CMAKE_C_COMPILER_ID MATCHES "Clang") diff --git a/tests/svcb.c b/tests/svcb.c new file mode 100644 index 0000000..d4d3f21 --- /dev/null +++ b/tests/svcb.c @@ -0,0 +1,311 @@ +/* + * types.c -- Happy path tests to demonstrate supported types + * + * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ +#include +#include +#include +#include +#if _WIN32 +#include +#else +#include +#endif + +#include "zone.h" + +// automatically pad string literal +#define PAD(literal) \ + literal \ + "\0\0\0\0\0\0\0\0" /* 0 - 7 */ \ + "\0\0\0\0\0\0\0\0" /* 8 - 15 */ \ + "\0\0\0\0\0\0\0\0" /* 16 - 23 */ \ + "\0\0\0\0\0\0\0\0" /* 24 - 31 */ \ + "\0\0\0\0\0\0\0\0" /* 32 - 39 */ \ + "\0\0\0\0\0\0\0\0" /* 40 - 47 */ \ + "\0\0\0\0\0\0\0\0" /* 48 - 55 */ \ + "\0\0\0\0\0\0\0\0" /* 56 - 63 */ \ + "" + +#define RDATA(...) \ + { sizeof( (const uint8_t[]){ __VA_ARGS__ } )/sizeof(uint8_t), (const uint8_t[]){ __VA_ARGS__ } } + +typedef struct rdata rdata_t; +struct rdata { + size_t length; + const uint8_t *octets; +}; + +/* RFC9460 Appendix D. Test Vectors */ + + +// D.1. AliasMode + +// Figure 2: AliasMode +static const char d1_text[] = + PAD("example.com. HTTPS 0 foo.example.com."); +static const rdata_t d1_rdata = RDATA( + // priority + 0x00, 0x00, + // target + 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, + 0x00); + + +// D.2. ServiceMode + +// Figure 3: TargetName is "." +static const char d2_f3_text[] = + PAD("example.com. SVCB 1 ."); +static const rdata_t d2_f3_rdata = RDATA( + // priority + 0x00, 0x01, + // target (root label) + 0x00); + +// Figure 4: Specifies a Port +static const char d2_f4_text[] = + PAD("example.com. SVCB 16 foo.example.com. port=53"); +static const rdata_t d2_f4_rdata = RDATA( + // priority + 0x00, 0x10, + // target + 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, + 0x00, + // key 3 + 0x00, 0x03, + // length 2 + 0x00, 0x02, + // value + 0x00, 0x35); + +// Figure 5: A Generic Key and Unquoted Value +static const char d2_f5_text[] = + PAD("example.com. SVCB 1 foo.example.com. key667=hello"); +static const rdata_t d2_f5_rdata = RDATA( + // priority + 0x00, 0x01, + // target + 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, + 0x00, + // key 667 + 0x02, 0x9b, + // length 9 + 0x00, 0x05, + // value + 0x68, 0x65, 0x6c, 0x6c, 0x6f); + +// Figure 6: A Generic Key and Quoted Value with a Decimal Escape +static const char d2_f6_text[] = + PAD("example.com. SVCB 1 foo.example.com. key667=\"hello\\210qoo\""); +static const rdata_t d2_f6_rdata = RDATA( + // priority + 0x00, 0x01, + // target + 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, + 0x00, + // key 667 + 0x02, 0x9b, + // length 9 + 0x00, 0x09, + // value + 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0xd2, 0x71, 0x6f, + 0x6f); + +// Figure 7: Two Quoted IPv6 Hints +static const char d2_f7_text[] = + PAD("example.com. SVCB 1 foo.example.com. (\n" + " ipv6hint=\"2001:db8::1,2001:db8::53:1\"\n" + " )\n"); +static const rdata_t d2_f7_rdata = RDATA( + // priority + 0x00, 0x01, + // target + 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, + 0x00, + // key 6 + 0x00, 0x06, + // length 32 + 0x00, 0x20, + // first address + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // second address + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x01); + +// Figure 8: An IPv6 Hint Using the Embedded IPv4 Syntax +static const char d2_f8_text[] = + PAD("example.com. SVCB 1 example.com. (\n" + " ipv6hint=\"2001:db8:122:344::192.0.2.33\"\n" + " )"); +static const rdata_t d2_f8_rdata = RDATA( + // priority + 0x00, 0x01, + // target + 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, + // key 6 + 0x00, 0x06, + // length 16 + 0x00, 0x10, + // address + 0x20, 0x01, 0x0d, 0xb8, 0x01, 0x22, 0x03, 0x44, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x02, 0x21); + +// Figure 9: SvcParamKey Ordering Is Arbitrary in Presentation Format but Sorted in Wire Format +static const char d2_f9_text[] = + PAD("example.com. SVCB 16 foo.example.org. (\n" + " alpn=h2,h3-19 mandatory=ipv4hint,alpn\n" + " ipv4hint=192.0.2.1\n" + " )"); + +static const rdata_t d2_f9_rdata = RDATA( + // priority + 0x00, 0x10, + // target + 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x6f, 0x72, 0x67, + 0x00, + // key 0 + 0x00, 0x00, + // param length 4 + 0x00, 0x04, + // value: key 1 + 0x00, 0x01, + // value: key 4 + 0x00, 0x04, + // key 1 + 0x00, 0x01, + // param length 9 + 0x00, 0x09, + // alpn length 2 + 0x02, + // alpn value + 0x68, 0x32, + // alpn length 5 + 0x05, + // alpn value + 0x68, 0x33, 0x2d, 0x31, 0x39, + // key 4 + 0x00, 0x04, + // param length 4 + 0x00, 0x04, + // param value + 0xc0, 0x00, 0x02, 0x01); + +#if 0 +// No Application-Layer Protocol Negotiation (ALPN) protocol identifiers that +// contain a "\" (backslash) exist. To simplify parsing, in accordance with +// RFC9460 appendix A.1, simdzone prohibits item lists containing backslashes +// (for now). +// +// Figure 10: An "alpn" Value with an Escaped Comma and an Escaped Backslash in Two Presentation Formats +static const char d2_f10_1_text[] = + PAD("example.com. SVCB 16 foo.example.org. alpn=\"f\\\\oo\\,bar,h2\""); + +static const char d2_f10_2_text[] = + PAD("example.com. SVCB 16 foo.example.org. alpn=f\\\092oo\092,bar,h2"); + +static const rdata_t d2_f10_rdata = RDATA( + // priority + 0x00, 0x10, + // target + 0x03, 0x66, 0x6f, 0x6f, 0x07, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x6f, 0x72, 0x67, + 0x00, + // key 1 + 0x00, 0x01, + // length 12 + 0x00, 0x0c, + // alpn length 8 + 0x08, + // alpn value + 0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, + // alpn length 2 + 0x02, + // alpn value + 0x68, 0x32); +#endif + +typedef struct test test_t; +struct test { + const uint16_t type; + const char *text; + const rdata_t *rdata; +}; + +static const test_t tests[] = { + { ZONE_HTTPS, d1_text, &d1_rdata }, + { ZONE_SVCB, d2_f3_text, &d2_f3_rdata }, + { ZONE_SVCB, d2_f4_text, &d2_f4_rdata }, + { ZONE_SVCB, d2_f5_text, &d2_f5_rdata }, + { ZONE_SVCB, d2_f6_text, &d2_f6_rdata }, + { ZONE_SVCB, d2_f7_text, &d2_f7_rdata }, + { ZONE_SVCB, d2_f8_text, &d2_f8_rdata }, + { ZONE_SVCB, d2_f9_text, &d2_f9_rdata }, +#if 0 + { ZONE_SVCB, d2_f10_1_text, &d2_f10_rdata }, + { ZONE_SVCB, d2_f10_2_text, &d2_f10_rdata } +#endif +}; + +static int32_t add_rr( + zone_parser_t *parser, + const zone_type_info_t *info, + const zone_name_t *owner, + uint16_t type, + uint16_t class, + uint32_t ttl, + uint16_t rdlength, + const uint8_t *rdata, + void *user_data) +{ + const test_t *test = user_data; + (void)parser; + (void)info; + (void)owner; + (void)class; + (void)ttl; + (void)rdlength; + (void)rdata; + assert_int_equal(type, test->type); + assert_int_equal(rdlength, test->rdata->length); + assert_memory_equal(rdata, test->rdata->octets, rdlength); + return ZONE_SUCCESS; +} + +/*!cmocka */ +void rfc9460_test_vectors(void **state) +{ + (void)state; + + for (size_t i = 0, n = sizeof(tests)/sizeof(tests[0]); i < n; i++) { + test_t test = tests[i]; + zone_parser_t parser = { 0 }; + zone_name_buffer_t name; + zone_rdata_buffer_t rdata; + zone_buffers_t buffers = { 1, &name, &rdata }; + zone_options_t options = { 0 }; + int32_t result; + + options.accept.add = add_rr; + options.origin = "example.com."; + options.default_ttl = 3600; + options.default_class = ZONE_IN; + + fprintf(stderr, "INPUT: '%s'\n", tests[i].text); + + result = zone_parse_string(&parser, &options, &buffers, tests[i].text, strlen(tests[i].text), &test); + assert_int_equal(result, ZONE_SUCCESS); + } +} diff --git a/tests/types.c b/tests/types.c index d768556..eed08e6 100644 --- a/tests/types.c +++ b/tests/types.c @@ -748,6 +748,13 @@ static const rdata_t zonemd_rdata = 0xdd, 0x5b, 0x97, 0xae, 0x49, 0x9f, 0xaf, 0xa4, 0xf2, 0x2c, 0x6b, 0xd6, 0x47, 0xde); +static const char svcb_text[] = + PAD("foo. 1 IN SVCB 0 foo. mandatory=mandatory,key16"); +static const rdata_t svcb_rdata = + RDATA(0x00, 0x00, + 3, 'f', 'o', 'o', 0, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10); + static const char spf_text[] = PAD(" SPF \"v=spf1 +all\""); static const char spf_generic_text[] = @@ -925,6 +932,7 @@ static const test_t tests[] = { { ZONE_CSYNC, csync_text, &csync_rdata }, { ZONE_ZONEMD, zonemd_text, &zonemd_rdata }, { ZONE_ZONEMD, zonemd_generic_text, &zonemd_rdata }, + { ZONE_SVCB, svcb_text, &svcb_rdata }, { ZONE_SPF, spf_text, &spf_rdata }, { ZONE_SPF, spf_generic_text, &spf_rdata }, { ZONE_NID, nid_text, &nid_rdata },