diff --git a/src/ns.c b/src/ns.c index 1325c590..e6bf91a7 100644 --- a/src/ns.c +++ b/src/ns.c @@ -25,16 +25,6 @@ #include "uv.h" #include "dnssec.h" -// A RRSIG NSEC -static const uint8_t hsk_type_map_a[] = { - 0x00, 0x06, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03 -}; - -// AAAA RRSIG NSEC -static const uint8_t hsk_type_map_aaaa[] = { - 0x00, 0x06, 0x00, 0x00, 0x00, 0x80, 0x00, 0x03 -}; - /* * Types */ @@ -333,7 +323,9 @@ hsk_ns_onrecv( // The synth name then resolves to an A/AAAA record that is derived // by decoding the name itself (it does not have to be looked up). bool should_cache = true; - if (strcmp(req->tld, "_synth") == 0 && req->labels <= 2) { + if (strcmp(req->tld, "_synth") == 0 && + req->labels <= 2 && + req->name[0] == '_') { msg = hsk_dns_msg_alloc(); should_cache = false; @@ -342,7 +334,6 @@ hsk_ns_onrecv( hsk_dns_rrs_t *an = &msg->an; hsk_dns_rrs_t *rrns = &msg->ns; - hsk_dns_rrs_t *ar = &msg->ar; uint8_t ip[16]; uint16_t family; @@ -350,10 +341,19 @@ hsk_ns_onrecv( hsk_dns_label_from(req->name, -2, synth); if (req->labels == 1) { - hsk_resource_to_empty(req->tld, NULL, 0, rrns); - hsk_dnssec_sign_zsk(rrns, HSK_DNS_NSEC); + // TLD '._synth' is being queried on its own, send SOA + // so recursive asks again with complete synth record. hsk_resource_root_to_soa(rrns); hsk_dnssec_sign_zsk(rrns, HSK_DNS_SOA); + // Empty non-terminal proof: + hsk_resource_to_nsec( + "_synth.", + "\\000._synth.", + hsk_type_map_empty, + sizeof(hsk_type_map_empty), + rrns + ); + hsk_dnssec_sign_zsk(rrns, HSK_DNS_NSEC); } if (pointer_to_ip(synth, ip, &family)) { @@ -372,19 +372,20 @@ hsk_ns_onrecv( } if (!match) { - // Needs SOA. - // TODO: Make the reverse pointers TLDs. - // Empty proof: + char next[HSK_DNS_MAX_NAME] = "\\000."; + strcat(next, req->name); if (family == HSK_DNS_A) { - hsk_resource_to_empty( + hsk_resource_to_nsec( req->name, + next, hsk_type_map_a, sizeof(hsk_type_map_a), rrns ); } else { - hsk_resource_to_empty( + hsk_resource_to_nsec( req->name, + next, hsk_type_map_aaaa, sizeof(hsk_type_map_aaaa), rrns @@ -418,7 +419,7 @@ hsk_ns_onrecv( hsk_dns_rrs_push(an, rr); - hsk_dnssec_sign_zsk(ar, rrtype); + hsk_dnssec_sign_zsk(an, rrtype); } } @@ -445,7 +446,7 @@ hsk_ns_onrecv( || strcmp(req->tld, "onion") == 0 // Tor || strcmp(req->tld, "tor") == 0 // OnioNS || strcmp(req->tld, "zkey") == 0) { // GNS - msg = hsk_resource_to_nx(); + msg = hsk_resource_to_nx(req->tld); } else { req->ns = (void *)ns; @@ -538,9 +539,12 @@ hsk_ns_respond( // not possible for SPV nodes since they // can't arbitrarily iterate over the tree. // - // Instead, we give a phony proof, which - // makes the root zone look empty. - msg = hsk_resource_to_nx(); + // Instead, we give a minimally covering + // NSEC record based on rfc4470 + // https://tools.ietf.org/html/rfc4470 + + // Proving the name doesn't exist + msg = hsk_resource_to_nx(req->tld); if (!msg) hsk_ns_log(ns, "could not create nx response (%u)\n", req->id); diff --git a/src/resource.c b/src/resource.c index 96e2f5fa..edb52ab9 100644 --- a/src/resource.c +++ b/src/resource.c @@ -16,13 +16,6 @@ #include "resource.h" #include "utils.h" -// NS SOA RRSIG NSEC DNSKEY -// Possibly add A, AAAA, and DS -static const uint8_t hsk_type_map[] = { - 0x00, 0x07, 0x22, 0x00, 0x00, - 0x00, 0x00, 0x03, 0x80 -}; - /* * Helpers */ @@ -30,6 +23,12 @@ static const uint8_t hsk_type_map[] = { static void to_fqdn(char *name); +static void +next_name(const char *name, char *next); + +static void +prev_name(const char *name, char *prev); + /* * Resource serialization version 0 * Record types: read @@ -757,7 +756,7 @@ hsk_resource_root_to_soa(hsk_dns_rrs_t *an) { rd->refresh = 1800; rd->retry = 900; rd->expire = 604800; - rd->minttl = 86400; + rd->minttl = HSK_DEFAULT_TTL; hsk_dns_rrs_push(an, rr); @@ -867,8 +866,9 @@ hsk_resource_root_to_ds(hsk_dns_rrs_t *an) { } bool -hsk_resource_to_empty( +hsk_resource_to_nsec( const char *name, + const char *next, const uint8_t *type_map, size_t type_map_len, hsk_dns_rrs_t *an @@ -878,13 +878,14 @@ hsk_resource_to_empty( if (!rr) return false; - rr->ttl = 86400; + rr->ttl = HSK_DEFAULT_TTL; hsk_dns_rr_set_name(rr, name); hsk_dns_nsec_rd_t *rd = rr->rd; - strcpy(rd->next_domain, "."); + strcpy(rd->next_domain, next); + rd->type_map = NULL; rd->type_map_len = 0; @@ -907,37 +908,6 @@ hsk_resource_to_empty( return true; } -static bool -hsk_resource_root_to_nsec(hsk_dns_rrs_t *an) { - hsk_dns_rr_t *rr = hsk_dns_rr_create(HSK_DNS_NSEC); - - if (!rr) - return false; - - uint8_t *bitmap = malloc(sizeof(hsk_type_map)); - - if (!bitmap) { - hsk_dns_rr_free(rr); - return false; - } - - memcpy(bitmap, &hsk_type_map[0], sizeof(hsk_type_map)); - - rr->ttl = 86400; - - hsk_dns_rr_set_name(rr, "."); - - hsk_dns_nsec_rd_t *rd = rr->rd; - - strcpy(rd->next_domain, "."); - rd->type_map = bitmap; - rd->type_map_len = sizeof(hsk_type_map); - - hsk_dns_rrs_push(an, rr); - - return true; -} - hsk_dns_msg_t * hsk_resource_to_dns(const hsk_resource_t *rs, const char *name, uint16_t type) { assert(hsk_dns_name_is_fqdn(name)); @@ -959,21 +929,59 @@ hsk_resource_to_dns(const hsk_resource_t *rs, const char *name, uint16_t type) { hsk_dns_rrs_t *ns = &msg->ns; // authority hsk_dns_rrs_t *ar = &msg->ar; // additional + // Even though the name here is a single label (the TLD) + // we use a larger buffer size of 255 (instead of 63) + // to allow escaped byte codes like /000 + char next[HSK_DNS_MAX_NAME]; + next_name(tld, next); + // Referral. if (labels > 1) { if (hsk_resource_has_ns(rs)) { hsk_resource_to_ns(rs, tld, ns); hsk_resource_to_ds(rs, tld, ns); hsk_resource_to_glue(rs, tld, ar); - if (!hsk_resource_has(rs, HSK_DS)) - hsk_dnssec_sign_zsk(ns, HSK_DNS_NS); - else + if (!hsk_resource_has(rs, HSK_DS)) { + // Prove there is an NS but no DS and sign NSEC + // Root doesn't sign NS for anything other than "." + hsk_resource_to_nsec( + tld, + next, + hsk_type_map_ns, + sizeof(hsk_type_map_ns), + ns + ); + hsk_dnssec_sign_zsk(ns, HSK_DNS_NSEC); + } else { + // Domain has a DS and an NS + // Root only signs the DS hsk_dnssec_sign_zsk(ns, HSK_DNS_DS); + } } else { - // Needs SOA. - // Empty proof: - hsk_resource_to_empty(tld, NULL, 0, ns); + // Domain has no NS + // We can prove there is a TXT or empty and sign NSEC + if (hsk_resource_has(rs, HSK_TEXT)) { + hsk_resource_to_nsec( + tld, + next, + hsk_type_map_txt, + sizeof(hsk_type_map_txt), + ns + ); + } else { + hsk_resource_to_nsec( + tld, + next, + hsk_type_map_empty, + sizeof(hsk_type_map_empty), + ns + ); + } + + msg->flags |= HSK_DNS_AA; + hsk_dnssec_sign_zsk(ns, HSK_DNS_NSEC); + // Needs SOA. hsk_resource_root_to_soa(ns); hsk_dnssec_sign_zsk(ns, HSK_DNS_SOA); } @@ -984,39 +992,72 @@ hsk_resource_to_dns(const hsk_resource_t *rs, const char *name, uint16_t type) { // Record types actually on-chain for HNS TLDs. switch (type) { case HSK_DNS_DS: + msg->flags |= HSK_DNS_AA; hsk_resource_to_ds(rs, name, an); hsk_dnssec_sign_zsk(an, HSK_DNS_DS); break; - case HSK_DNS_NS: - // Includes SYNTH and GLUE records. - hsk_resource_to_ns(rs, name, ns); - hsk_resource_to_glue(rs, name, ar); - hsk_dnssec_sign_zsk(ns, HSK_DNS_NS); - break; case HSK_DNS_TXT: - hsk_resource_to_txt(rs, name, an); - hsk_dnssec_sign_zsk(an, HSK_DNS_TXT); + if (!hsk_resource_has_ns(rs)) { + msg->flags |= HSK_DNS_AA; + hsk_resource_to_txt(rs, name, an); + hsk_dnssec_sign_zsk(an, HSK_DNS_TXT); + } break; } - if (an->size > 0) - msg->flags |= HSK_DNS_AA; - // Attempt to force a referral if we don't have an answer. if (an->size == 0 && ns->size == 0) { - if (hsk_resource_has_ns(rs)) { + // No referrals for DS or without NS to refer to! + if (hsk_resource_has_ns(rs) && type != HSK_DNS_DS) { hsk_resource_to_ns(rs, name, ns); hsk_resource_to_ds(rs, name, ns); hsk_resource_to_glue(rs, name, ar); - if (!hsk_resource_has(rs, HSK_DS)) - hsk_dnssec_sign_zsk(ns, HSK_DNS_NS); - else - hsk_dnssec_sign_zsk(ns, HSK_DNS_DS); + if (!hsk_resource_has(rs, HSK_DS)) { + // No DS proof: + // This allows unbound to treat the zone as unsigned (and not bogus) + hsk_resource_to_nsec( + tld, + next, + hsk_type_map_ns, + sizeof(hsk_type_map_ns), + ns + ); + hsk_dnssec_sign_zsk(ns, HSK_DNS_NSEC); + } else { + hsk_dnssec_sign_zsk(ns, HSK_DNS_DS); + } } else { - // Needs SOA. - // Empty proof: - hsk_resource_to_empty(name, NULL, 0, ns); + if (hsk_resource_has_ns(rs)) { + // If NS is present, prove it + hsk_resource_to_nsec( + tld, + next, + hsk_type_map_ns, + sizeof(hsk_type_map_ns), + ns + ); + } else if (hsk_resource_has(rs, HSK_TEXT)) { + // No NS means we can prove TXT if applicable + hsk_resource_to_nsec( + tld, + next, + hsk_type_map_txt, + sizeof(hsk_type_map_txt), + ns + ); + } else { + // Otherwise, we prove there is nothing + hsk_resource_to_nsec( + tld, + next, + hsk_type_map_empty, + sizeof(hsk_type_map_empty), + ns + ); + } hsk_dnssec_sign_zsk(ns, HSK_DNS_NSEC); + // Needs SOA. + msg->flags |= HSK_DNS_AA; hsk_resource_root_to_soa(ns); hsk_dnssec_sign_zsk(ns, HSK_DNS_SOA); } @@ -1077,14 +1118,15 @@ hsk_resource_root(uint16_t type, const hsk_addr_t *addr) { hsk_resource_root_to_dnskey(an); hsk_dnssec_sign_ksk(an, HSK_DNS_DNSKEY); break; - case HSK_DNS_DS: - hsk_resource_root_to_ds(an); - hsk_dnssec_sign_zsk(an, HSK_DNS_DS); - break; default: - // Empty Proof: - // Show all the types that we signed. - hsk_resource_root_to_nsec(ns); + // Minimally covering NSEC proof: + hsk_resource_to_nsec( + ".", + "\\000.", + hsk_type_map_root, + sizeof(hsk_type_map_root), + ns + ); hsk_dnssec_sign_zsk(ns, HSK_DNS_NSEC); hsk_resource_root_to_soa(ns); hsk_dnssec_sign_zsk(ns, HSK_DNS_SOA); @@ -1095,7 +1137,7 @@ hsk_resource_root(uint16_t type, const hsk_addr_t *addr) { } hsk_dns_msg_t * -hsk_resource_to_nx(void) { +hsk_resource_to_nx(const char *tld) { hsk_dns_msg_t *msg = hsk_dns_msg_alloc(); if (!msg) @@ -1106,15 +1148,43 @@ hsk_resource_to_nx(void) { hsk_dns_rrs_t *ns = &msg->ns; - // NX Proof: - // Just make it look like an - // empty zone for the NX proof. - // It seems to fool unbound without - // breaking anything. - hsk_resource_root_to_nsec(ns); - hsk_resource_root_to_nsec(ns); + // Prove the wildcard doesn't exist + hsk_resource_to_nsec( + "!.", + "+.", + hsk_type_map_empty, + sizeof(hsk_type_map_empty), + ns + ); + // Sign RR set with name `!.` hsk_dnssec_sign_zsk(ns, HSK_DNS_NSEC); + // Pop the NSEC and RRSIG out of the RR set... + hsk_dns_rr_t *rr1 = hsk_dns_rrs_pop(ns); + hsk_dns_rr_t *rr2 = hsk_dns_rrs_pop(ns); + + // Prove the name doesn't exist. + // Even though the name here is a single label (the TLD) + // we use a larger buffer size of 255 (instead of 63) + // to allow escaped byte codes like /000 + char next[HSK_DNS_MAX_NAME]; + char prev[HSK_DNS_MAX_NAME]; + next_name(tld, next); + prev_name(tld, prev); + hsk_resource_to_nsec( + prev, + next, + hsk_type_map_empty, + sizeof(hsk_type_map_empty), + ns + ); + // Sign RR set with name `prev` + hsk_dnssec_sign_zsk(ns, HSK_DNS_NSEC); + + // ...now push the first two RRs back in + hsk_dns_rrs_push(ns, rr2); + hsk_dns_rrs_push(ns, rr1); + hsk_resource_root_to_soa(ns); hsk_dnssec_sign_zsk(ns, HSK_DNS_SOA); @@ -1157,6 +1227,36 @@ to_fqdn(char *name) { name[len + 1] = '\0'; } +static void +next_name(const char *name, char *next) { + size_t len = strlen(name); + if (name[len - 1] == '.') + len--; + + strcpy(next, name); + + if (len < 63) { + memcpy(&next[len], "\\000.", 6); + } else { + next[len - 1]++; + memcpy(&next[len], ".", 2); + } +} + +static void +prev_name(const char *name, char *prev) { + size_t len = strlen(name); + if (name[len - 1] == '.') + len--; + + strcpy(prev, name); + prev[len - 1]--; + + if (len < 63) { + memcpy(&prev[len], "\\255.", 6); + } +} + bool pointer_to_ip(const char *name, uint8_t *ip, uint16_t *family) { char label[HSK_DNS_MAX_LABEL + 1]; diff --git a/src/resource.h b/src/resource.h index 46e73b1f..96ef7140 100644 --- a/src/resource.h +++ b/src/resource.h @@ -16,6 +16,37 @@ #include "addr.h" #include "dns.h" +// A RRSIG NSEC +static const uint8_t hsk_type_map_a[] = { + 0x00, 0x06, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03 +}; + +// AAAA RRSIG NSEC +static const uint8_t hsk_type_map_aaaa[] = { + 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03 +}; + +// NS SOA RRSIG NSEC DNSKEY +static const uint8_t hsk_type_map_root[] = { + 0x00, 0x07, 0x22, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x80 +}; + +// NS RRSIG NSEC +static const uint8_t hsk_type_map_ns[] = { + 0x00, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00, 0x03 +}; + +// RRSIG NSEC +static const uint8_t hsk_type_map_empty[] = { + 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 +}; + +// TXT RRSIG NSEC +static const uint8_t hsk_type_map_txt[] = { + 0x00, 0x06, 0x00, 0x00, 0x80, 0x00, 0x00, 0x03 +}; + // Dummy record placeholder typedef struct hsk_record_s { uint8_t type; @@ -83,7 +114,7 @@ hsk_dns_msg_t * hsk_resource_root(uint16_t type, const hsk_addr_t *addr); hsk_dns_msg_t * -hsk_resource_to_nx(void); +hsk_resource_to_nx(const char *tld); hsk_dns_msg_t * hsk_resource_to_servfail(void); @@ -95,8 +126,9 @@ bool hsk_resource_is_ptr(const char *name); bool -hsk_resource_to_empty( +hsk_resource_to_nsec( const char *name, + const char *next, const uint8_t *type_map, size_t type_map_len, hsk_dns_rrs_t *an diff --git a/src/rs.c b/src/rs.c index 3794190d..d91d7e08 100644 --- a/src/rs.c +++ b/src/rs.c @@ -437,9 +437,15 @@ hsk_rs_respond( hsk_rs_log(ns, " secure: %d\n", result->secure); hsk_rs_log(ns, " bogus: %d\n", result->bogus); - if (result->why_bogus) + if (result->bogus) { hsk_rs_log(ns, " why_bogus: %s\n", result->why_bogus); + if (req->cd) + hsk_rs_log(ns, " (checking disabled)\n"); + else + goto fail; + } + uint8_t *data = result->answer_packet; size_t data_len = result->answer_len; @@ -455,7 +461,7 @@ hsk_rs_respond( msg->code = result->rcode; msg->flags |= HSK_DNS_RA; - if (result->secure && !result->bogus) + if (result->secure) msg->flags |= HSK_DNS_AD; // Strip out non-answer sections. diff --git a/test/data/resource_vectors.h b/test/data/resource_vectors.h new file mode 100644 index 00000000..53f4433e --- /dev/null +++ b/test/data/resource_vectors.h @@ -0,0 +1,338 @@ +#include "resource.h" + +/* + * Types + */ + +typedef struct type_vector { + uint16_t type; + char *type_string; + uint8_t an_size; + uint8_t ns_size; + uint8_t ar_size; + bool nsec; + bool aa; +} type_vector_t; + +typedef struct resource_vector { + char *name; + uint8_t data[255]; + uint8_t data_len; + type_vector_t type_vectors[4]; + // Expected in NSEC records + size_t type_map_len; + const uint8_t *type_map; +} resource_vector_t; + +/* + * Vectors + */ + +static const resource_vector_t resource_vectors[] = { + // { + // "records": [ + // { + // "type": "SYNTH4", + // "address": "50.60.70.80" + // } + // ] + // } + { + "test-synth4.", + { + 0x00, 0x04, 0x32, 0x3c, 0x46, 0x50 + }, + 6, + { + {HSK_DNS_DS, "DS", 0, 4, 0, true, true}, + {HSK_DNS_NS, "NS", 0, 3, 1, true, false}, + {HSK_DNS_TXT, "TXT", 0, 3, 1, true, false}, + {HSK_DNS_A, "A", 0, 3, 1, true, false} + }, + sizeof(hsk_type_map_ns), + hsk_type_map_ns + }, + + // { + // "records": [ + // { + // "type": "SYNTH6", + // "address": "8888:7777:6666:5555:4444:3333:2222:1111" + // } + // ] + // } + { + "test-synth6.", + { + 0x00, 0x05, 0x88, 0x88, 0x77, 0x77, 0x66, 0x66, 0x55, 0x55, 0x44, 0x44, + 0x33, 0x33, 0x22, 0x22, 0x11, 0x11 + }, + 18, + { + {HSK_DNS_DS, "DS", 0, 4, 0, true, true}, + {HSK_DNS_NS, "NS", 0, 3, 1, true, false}, + {HSK_DNS_TXT, "TXT", 0, 3, 1, true, false}, + {HSK_DNS_A, "A", 0, 3, 1, true, false} + }, + sizeof(hsk_type_map_ns), + hsk_type_map_ns + }, + + // { + // "records": [ + // { + // "type": "GLUE4", + // "ns": "ns2.hns.", + // "address": "10.20.30.40" + // } + // ] + // } + { + "test-glue4.", + { + 0x00, 0x02, 0x03, 0x6e, 0x73, 0x32, 0x03, 0x68, 0x6e, 0x73, 0x00, 0x0a, + 0x14, 0x1e, 0x28 + }, + 15, + { + {HSK_DNS_DS, "DS", 0, 4, 0, true, true}, + {HSK_DNS_NS, "NS", 0, 3, 0, true, false}, + {HSK_DNS_TXT, "TXT", 0, 3, 0, true, false}, + {HSK_DNS_A, "A", 0, 3, 0, true, false} + }, + sizeof(hsk_type_map_ns), + hsk_type_map_ns + }, + + // { + // "records": [ + // { + // "type": "GLUE4", + // "ns": "ns2.test-glue4-glue.", + // "address": "10.20.30.40" + // } + // ] + // } + { + "test-glue4-glue.", + { + 0x00, 0x02, 0x03, 0x6e, 0x73, 0x32, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x2d, + 0x67, 0x6c, 0x75, 0x65, 0x34, 0x2d, 0x67, 0x6c, 0x75, 0x65, 0x00, 0x0a, + 0x14, 0x1e, 0x28 + }, + 27, + { + {HSK_DNS_DS, "DS", 0, 4, 0, true, true}, + {HSK_DNS_NS, "NS", 0, 3, 1, true, false}, + {HSK_DNS_TXT, "TXT", 0, 3, 1, true, false}, + {HSK_DNS_A, "A", 0, 3, 1, true, false} + }, + sizeof(hsk_type_map_ns), + hsk_type_map_ns + }, + + // { + // "records": [ + // { + // "type": "GLUE6", + // "ns": "ns2.hns.", + // "address": "1111:2222:3333:4444:5555:6666:7777:8888" + // } + // ] + // } + { + "test-glue6.", + { + 0x00, 0x03, 0x03, 0x6e, 0x73, 0x32, 0x03, 0x68, 0x6e, 0x73, 0x00, 0x11, + 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, + 0x77, 0x88, 0x88 + }, + 27, + { + {HSK_DNS_DS, "DS", 0, 4, 0, true, true}, + {HSK_DNS_NS, "NS", 0, 3, 0, true, false}, + {HSK_DNS_TXT, "TXT", 0, 3, 0, true, false}, + {HSK_DNS_A, "A", 0, 3, 0, true, false} + }, + sizeof(hsk_type_map_ns), + hsk_type_map_ns + }, + + // { + // "records": [ + // { + // "type": "GLUE6", + // "ns": "ns2.test-glue6-glue.", + // "address": "1111:2222:3333:4444:5555:6666:7777:8888" + // } + // ] + // } + { + "test-glue6-glue.", + { + 0x00, 0x03, 0x03, 0x6e, 0x73, 0x32, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x2d, + 0x67, 0x6c, 0x75, 0x65, 0x36, 0x2d, 0x67, 0x6c, 0x75, 0x65, 0x00, 0x11, + 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, + 0x77, 0x88, 0x88 + }, + 39, + { + {HSK_DNS_DS, "DS", 0, 4, 0, true, true}, + {HSK_DNS_NS, "NS", 0, 3, 1, true, false}, + {HSK_DNS_TXT, "TXT", 0, 3, 1, true, false}, + {HSK_DNS_A, "A", 0, 3, 1, true, false} + }, + sizeof(hsk_type_map_ns), + hsk_type_map_ns + }, + + // { + // "records": [ + // { + // "type": "NS", + // "ns": "ns1.hns." + // } + // ] + // } + { + "test-ns.", + { + 0x00, 0x01, 0x03, 0x6e, 0x73, 0x31, 0x03, 0x68, 0x6e, 0x73, 0x00 + }, + 11, + { + {HSK_DNS_DS, "DS", 0, 4, 0, true, true}, + {HSK_DNS_NS, "NS", 0, 3, 0, true, false}, + {HSK_DNS_TXT, "TXT", 0, 3, 0, true, false}, + {HSK_DNS_A, "A", 0, 3, 0, true, false} + }, + sizeof(hsk_type_map_ns), + hsk_type_map_ns + }, + + // { + // "records": [ + // { + // "type": "NS", + // "ns": "ns1.test-ns-glue." + // } + // ] + // } + { + "test-ns-glue.", + { + 0x00, 0x01, 0x03, 0x6e, 0x73, 0x31, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x2d, + 0x6e, 0x73, 0x2d, 0x67, 0x6c, 0x75, 0x65, 0x00 + }, + 20, + { + {HSK_DNS_DS, "DS", 0, 4, 0, true, true}, + {HSK_DNS_NS, "NS", 0, 3, 0, true, false}, + {HSK_DNS_TXT, "TXT", 0, 3, 0, true, false}, + {HSK_DNS_A, "A", 0, 3, 0, true, false} + }, + sizeof(hsk_type_map_ns), + hsk_type_map_ns + }, + + // { + // "records": [ + // { + // "type": "DS", + // "keyTag": 57355, + // "algorithm": 8, + // "digestType": 2, + // "digest": "95a57c3bab7849dbcddf7c72ada71a88146b141110318ca5be672057e865c3e2" + // } + // ] + // } + { + "test-ds.", + { + 0x00, 0x00, 0xe0, 0x0b, 0x08, 0x02, 0x20, 0x95, 0xa5, 0x7c, 0x3b, 0xab, + 0x78, 0x49, 0xdb, 0xcd, 0xdf, 0x7c, 0x72, 0xad, 0xa7, 0x1a, 0x88, 0x14, + 0x6b, 0x14, 0x11, 0x10, 0x31, 0x8c, 0xa5, 0xbe, 0x67, 0x20, 0x57, 0xe8, + 0x65, 0xc3, 0xe2 + }, + 39, + { + {HSK_DNS_DS, "DS", 2, 0, 0, false, true}, + {HSK_DNS_NS, "NS", 0, 4, 0, true, true}, + {HSK_DNS_TXT, "TXT", 0, 4, 0, true, true}, + {HSK_DNS_A, "A", 0, 4, 0, true, true} + }, + sizeof(hsk_type_map_empty), + hsk_type_map_empty + }, + + // { + // "records": [ + // { + // "type": "TXT", + // "txt": [ + // "hello world", + // "how are you" + // ] + // } + // ] + // } + { + "test-txt.", + { + 0x00, 0x06, 0x02, 0x0b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x0b, 0x68, 0x6f, 0x77, 0x20, 0x61, 0x72, 0x65, 0x20, + 0x79, 0x6f, 0x75 + }, + 27, + { + {HSK_DNS_DS, "DS", 0, 4, 0, true, true}, + {HSK_DNS_NS, "NS", 0, 4, 0, true, true}, + {HSK_DNS_TXT, "TXT", 2, 0, 0, false, true}, + {HSK_DNS_A, "A", 0, 4, 0, true, true} + }, + sizeof(hsk_type_map_txt), + hsk_type_map_txt + }, + + // { + // "records": [ + // { + // "type": "DS", + // "keyTag": 57355, + // "algorithm": 8, + // "digestType": 2, + // "digest": "95a57c3bab7849dbcddf7c72ada71a88146b141110318ca5be672057e865c3e2" + // }, + // { + // "type": "GLUE6", + // "ns": "ns1.test-all.", + // "address": "4:8:15:16:23:42:108:815" + // }, + // { + // "type": "TXT", + // "txt": [":-)"] + // } + // ] + // } + { + "test-all.", + { + 0x00, 0x00, 0xe0, 0x0b, 0x08, 0x02, 0x20, 0x95, 0xa5, 0x7c, 0x3b, 0xab, + 0x78, 0x49, 0xdb, 0xcd, 0xdf, 0x7c, 0x72, 0xad, 0xa7, 0x1a, 0x88, 0x14, + 0x6b, 0x14, 0x11, 0x10, 0x31, 0x8c, 0xa5, 0xbe, 0x67, 0x20, 0x57, 0xe8, + 0x65, 0xc3, 0xe2, 0x03, 0x03, 0x6e, 0x73, 0x31, 0x08, 0x74, 0x65, 0x73, + 0x74, 0x2d, 0x61, 0x6c, 0x6c, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x15, + 0x00, 0x16, 0x00, 0x23, 0x00, 0x42, 0x01, 0x08, 0x08, 0x15, 0x06, 0x01, + 0x03, 0x3a, 0x2d, 0x29 + }, + 76, + { + {HSK_DNS_DS, "DS", 2, 0, 0, false, true}, + {HSK_DNS_NS, "NS", 0, 3, 1, false, false}, + {HSK_DNS_TXT, "TXT", 0, 3, 1, false, false}, + {HSK_DNS_A, "A", 0, 3, 1, false, false} + }, + 0, + NULL + } +}; diff --git a/test/hnsd-test.c b/test/hnsd-test.c index 7ec7b071..d964f741 100644 --- a/test/hnsd-test.c +++ b/test/hnsd-test.c @@ -2,6 +2,11 @@ #include "base32.h" #include "resource.h" #include "resource.c" +#include "dns.h" +#include "dns.c" +#include "data/resource_vectors.h" + +#define ARRAY_SIZE(x) ((sizeof(x))/(sizeof(x[0]))) void print_array(uint8_t *arr, size_t size){ @@ -60,11 +65,118 @@ test_pointer_to_ip() { assert(family6 == HSK_DNS_AAAA); } +void +test_next_name() { + printf("test_next_name\n"); + + const char *name1 = "icecream."; + const char *name2 = "this-domain-name-has-sixty-three-octets-taking-max-label-length."; + char next1[HSK_DNS_MAX_NAME]; + char next2[HSK_DNS_MAX_NAME]; + + next_name(name1, next1); + next_name(name2, next2); + + assert(strcmp( + next1, + "icecream\\000." + ) == 0); + assert(strcmp( + next2, + "this-domain-name-has-sixty-three-octets-taking-max-label-lengti." + ) == 0); +} + +void +test_prev_name() { + printf("test_prev_name\n"); + + const char *name1 = "icecream."; + const char *name2 = "this-domain-name-has-sixty-three-octets-taking-max-label-length."; + char prev1[HSK_DNS_MAX_NAME]; + char prev2[HSK_DNS_MAX_NAME]; + + prev_name(name1, prev1); + prev_name(name2, prev2); + + assert(strcmp( + prev1, + "icecreal\\255." + ) == 0); + assert(strcmp( + prev2, + "this-domain-name-has-sixty-three-octets-taking-max-label-lengtg." + ) == 0); +} + +void +test_decode_resource() { + printf("test_decode_resource\n"); + + for (int i = 0; i < ARRAY_SIZE(resource_vectors); i++) { + resource_vector_t resource_vector = resource_vectors[i]; + + hsk_resource_t *res = NULL; + hsk_resource_decode( + resource_vector.data, + resource_vector.data_len, + &res + ); + + for (int t = 0; t < ARRAY_SIZE(resource_vector.type_vectors); t++) { + type_vector_t type_vector = resource_vector.type_vectors[t]; + + hsk_dns_msg_t *msg = NULL; + msg = hsk_resource_to_dns(res, resource_vector.name, type_vector.type); + + printf(" %s %s \n", resource_vector.name, type_vector.type_string); + assert(msg->an.size == type_vector.an_size); + assert(msg->ns.size == type_vector.ns_size); + assert(msg->ar.size == type_vector.ar_size); + + // Check `aa` bit + assert((bool)(msg->flags & HSK_DNS_AA) == type_vector.aa); + + // Sanity check: NSEC never appears in ANSWER or ADDITIONAL + for (int i = 0; i < msg->an.size; i++) { + hsk_dns_rr_t *rr = msg->an.items[i]; + assert(rr->type != HSK_DNS_NSEC); + } + for (int i = 0; i < msg->ar.size; i++) { + hsk_dns_rr_t *rr = msg->ar.items[i]; + assert(rr->type != HSK_DNS_NSEC); + } + + // Check NSEC in AUTHORITY when appropriate and verify type map + for (int i = 0; i < msg->ns.size; i++) { + hsk_dns_rr_t *rr = msg->ns.items[i]; + if (rr->type != HSK_DNS_NSEC) + continue; + + // NSEC is expected + assert(type_vector.nsec); + + // Type map is correct + hsk_dns_nsec_rd_t *rd = rr->rd; + assert(resource_vector.type_map_len == rd->type_map_len); + assert(memcmp( + resource_vector.type_map, + rd->type_map, + rd->type_map_len + ) == 0); + } + } + } +} + int main() { printf("Testing hnsd...\n"); test_base32(); test_pointer_to_ip(); + test_next_name(); + test_prev_name(); + test_decode_resource(); printf("ok\n");