/* Basic response check (4 TAP tests). */ static void answer_sanity_check(const uint8_t *query, const uint8_t *answer, uint16_t answer_len, uint8_t expected_rcode, const char *name) { ok(answer_len >= KNOT_WIRE_HEADER_SIZE, "ns: len(%s answer) >= DNS header", name); if (answer_len >= KNOT_WIRE_HEADER_SIZE) { ok(knot_wire_get_qr(answer), "ns: %s answer has QR=1", name); is_int(expected_rcode, knot_wire_get_rcode(answer), "ns: %s answer RCODE=%d", name, expected_rcode); is_int(knot_wire_get_id(query), knot_wire_get_id(answer), "ns: %s MSGID match", name); } else { skip_block(3, "ns: can't check DNS header"); } }
static uint8_t rrl_clsid(rrl_req_t *p) { /* Check error code */ int ret = CLS_NULL; switch (knot_wire_get_rcode(p->w)) { case KNOT_RCODE_NOERROR: ret = CLS_NORMAL; break; case KNOT_RCODE_NXDOMAIN: return CLS_NXDOMAIN; break; default: return CLS_ERROR; break; } /* Check if answered from a qname */ if (ret == CLS_NORMAL && p->flags & RRL_WILDCARD) { return CLS_WILDCARD; } /* Check query type for spec. classes. */ if (p->query) { switch(knot_pkt_qtype(p->query)) { case KNOT_RRTYPE_ANY: /* ANY spec. class */ return CLS_ANY; break; case KNOT_RRTYPE_DNSKEY: case KNOT_RRTYPE_RRSIG: case KNOT_RRTYPE_DS: /* DNSSEC-related RR class. */ return CLS_DNSSEC; break; default: break; } } /* Check packet size for threshold. */ if (p->len >= RRL_PSIZE_LARGE) { return CLS_LARGE; } /* Check ancount */ if (knot_wire_get_ancount(p->w) == 0) { return CLS_EMPTY; } return ret; }
_public_ uint16_t knot_pkt_get_ext_rcode(const knot_pkt_t *pkt) { if (pkt == NULL) { return 0; } uint8_t rcode = knot_wire_get_rcode(pkt->wire); if (pkt->opt_rr) { uint8_t opt_rcode = knot_edns_get_ext_rcode(pkt->opt_rr); return knot_edns_whole_rcode(opt_rcode, rcode); } else { return rcode; } }
int notify_process_answer(knot_pkt_t *pkt, struct answer_data *adata) { if (pkt == NULL || adata == NULL) { return KNOT_STATE_FAIL; } /* Check RCODE. */ uint8_t rcode = knot_wire_get_rcode(pkt->wire); if (rcode != KNOT_RCODE_NOERROR) { lookup_table_t *lut = lookup_by_id(knot_rcode_names, rcode); if (lut != NULL) { NOTIFY_RLOG(LOG_WARNING, "server responded with %s", lut->name); } return KNOT_STATE_FAIL; } NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 0); return KNOT_STATE_DONE; /* No processing. */ }
static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) { int ret = 0; struct kr_request *req = ctx->data; struct kr_query *qry = req->current_query; /* Ignore faulty or unprocessed responses. */ if (ctx->state & (KNOT_STATE_FAIL|KNOT_STATE_CONSUME)) { return ctx->state; } /* Pass-through if user doesn't want secure answer or stub. */ /* @todo: Validating stub resolver mode. */ if (!(qry->flags & QUERY_DNSSEC_WANT) || (qry->flags & QUERY_STUB)) { return ctx->state; } /* Answer for RRSIG may not set DO=1, but all records MUST still validate. */ bool use_signatures = (knot_pkt_qtype(pkt) != KNOT_RRTYPE_RRSIG); if (!(qry->flags & QUERY_CACHED) && !knot_pkt_has_dnssec(pkt) && !use_signatures) { DEBUG_MSG(qry, "<= got insecure response\n"); qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } /* Track difference between current TA and signer name. * This indicates that the NS is auth for both parent-child, and we must update DS/DNSKEY to validate it. */ const bool track_pc_change = (!(qry->flags & QUERY_CACHED) && (qry->flags & QUERY_DNSSEC_WANT)); const knot_dname_t *ta_name = qry->zone_cut.trust_anchor ? qry->zone_cut.trust_anchor->owner : NULL; const knot_dname_t *signer = signature_authority(pkt); if (track_pc_change && ta_name && (!signer || !knot_dname_is_equal(ta_name, signer))) { if (ctx->state == KNOT_STATE_YIELD) { /* Already yielded for revalidation. */ return KNOT_STATE_FAIL; } DEBUG_MSG(qry, ">< cut changed, needs revalidation\n"); if (!signer) { /* Not a DNSSEC-signed response, ask parent for DS to prove transition to INSECURE. */ } else if (knot_dname_is_sub(signer, qry->zone_cut.name)) { /* Key signer is below current cut, advance and refetch keys. */ qry->zone_cut.name = knot_dname_copy(signer, &req->pool); } else if (!knot_dname_is_equal(signer, qry->zone_cut.name)) { /* Key signer is above the current cut, so we can't validate it. This happens when a server is authoritative for both grandparent, parent and child zone. Ascend to parent cut, and refetch authority for signer. */ if (qry->zone_cut.parent) { memcpy(&qry->zone_cut, qry->zone_cut.parent, sizeof(qry->zone_cut)); } else { qry->flags |= QUERY_AWAIT_CUT; } qry->zone_cut.name = knot_dname_copy(signer, &req->pool); } /* else zone cut matches, but DS/DNSKEY doesn't => refetch. */ return KNOT_STATE_YIELD; } /* Check if this is a DNSKEY answer, check trust chain and store. */ uint8_t pkt_rcode = knot_wire_get_rcode(pkt->wire); uint16_t qtype = knot_pkt_qtype(pkt); bool has_nsec3 = pkt_has_type(pkt, KNOT_RRTYPE_NSEC3); if (knot_wire_get_aa(pkt->wire) && qtype == KNOT_RRTYPE_DNSKEY) { ret = validate_keyset(qry, pkt, has_nsec3); if (ret != 0) { DEBUG_MSG(qry, "<= bad keys, broken trust chain\n"); qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } } /* Validate non-existence proof if not positive answer. */ if (!(qry->flags & QUERY_CACHED) && pkt_rcode == KNOT_RCODE_NXDOMAIN) { /* @todo If knot_pkt_qname(pkt) is used instead of qry->sname then the tests crash. */ if (!has_nsec3) { ret = kr_nsec_name_error_response_check(pkt, KNOT_AUTHORITY, qry->sname); } else { ret = kr_nsec3_name_error_response_check(pkt, KNOT_AUTHORITY, qry->sname); } if (ret != 0) { DEBUG_MSG(qry, "<= bad NXDOMAIN proof\n"); qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } } /* @todo WTH, this needs API that just tries to find a proof and the caller * doesn't have to worry about NSEC/NSEC3 * @todo rework this */ if (!(qry->flags & QUERY_CACHED)) { const knot_pktsection_t *an = knot_pkt_section(pkt, KNOT_ANSWER); if (pkt_rcode == KNOT_RCODE_NOERROR && an->count == 0 && knot_wire_get_aa(pkt->wire)) { /* @todo * ? quick mechanism to determine which check to preform first * ? merge the functionality together to share code/resources */ if (!has_nsec3) { ret = kr_nsec_existence_denial(pkt, KNOT_AUTHORITY, knot_pkt_qname(pkt), knot_pkt_qtype(pkt)); } else { ret = kr_nsec3_no_data(pkt, KNOT_AUTHORITY, knot_pkt_qname(pkt), knot_pkt_qtype(pkt)); } if (ret != 0) { if (has_nsec3 && (ret == kr_error(DNSSEC_NOT_FOUND))) { DEBUG_MSG(qry, "<= can't prove NODATA due to optout, going insecure\n"); qry->flags &= ~QUERY_DNSSEC_WANT; qry->flags |= QUERY_DNSSEC_INSECURE; } else { DEBUG_MSG(qry, "<= bad NODATA proof\n"); qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } } } } /* Validate all records, fail as bogus if it doesn't match. * Do not revalidate data from cache, as it's already trusted. */ if (!(qry->flags & QUERY_CACHED)) { ret = validate_records(qry, pkt, req->rplan.pool, has_nsec3); if (ret != 0) { DEBUG_MSG(qry, "<= couldn't validate RRSIGs\n"); qry->flags |= QUERY_DNSSEC_BOGUS; return KNOT_STATE_FAIL; } } /* Check if wildcard expansion detected for final query. * If yes, copy authority. */ if ((qry->parent == NULL) && (qry->flags & QUERY_DNSSEC_WEXPAND)) { const knot_pktsection_t *auth = knot_pkt_section(pkt, KNOT_AUTHORITY); for (unsigned i = 0; i < auth->count; ++i) { const knot_rrset_t *rr = knot_pkt_rr(auth, i); kr_rrarray_add(&req->authority, rr, &req->answer->mm); } } /* Check and update current delegation point security status. */ ret = update_delegation(req, qry, pkt, has_nsec3); if (ret != 0) { return KNOT_STATE_FAIL; } /* Update parent query zone cut */ if (qry->parent) { if (update_parent_keys(qry, qtype) != 0) { return KNOT_STATE_FAIL; } } DEBUG_MSG(qry, "<= answer valid, OK\n"); return KNOT_STATE_DONE; }
static int axfr_answer_packet(knot_pkt_t *pkt, struct xfr_proc *proc) { assert(pkt != NULL); assert(proc != NULL); /* Update counters. */ proc->npkts += 1; proc->nbytes += pkt->size; /* Init zone creator. */ zcreator_t zc = {.z = proc->contents, .master = false, .ret = KNOT_EOK }; const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); for (uint16_t i = 0; i < answer->count; ++i) { const knot_rrset_t *rr = &answer->rr[i]; if (rr->type == KNOT_RRTYPE_SOA && node_rrtype_exists(zc.z->apex, KNOT_RRTYPE_SOA)) { return KNOT_NS_PROC_DONE; } else { int ret = zcreator_step(&zc, rr); if (ret != KNOT_EOK) { return KNOT_NS_PROC_FAIL; } } } return KNOT_NS_PROC_MORE; } int axfr_answer_process(knot_pkt_t *pkt, struct answer_data *adata) { if (pkt == NULL || adata == NULL) { return KNOT_NS_PROC_FAIL; } /* Check RCODE. */ uint8_t rcode = knot_wire_get_rcode(pkt->wire); if (rcode != KNOT_RCODE_NOERROR) { lookup_table_t *lut = lookup_by_id(knot_rcode_names, rcode); if (lut != NULL) { AXFRIN_LOG(LOG_ERR, "server responded with %s", lut->name); } return KNOT_NS_PROC_FAIL; } /* Initialize processing with first packet. */ if (adata->ext == NULL) { NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 0); AXFRIN_LOG(LOG_INFO, "starting"); int ret = axfr_answer_init(adata); if (ret != KNOT_EOK) { AXFRIN_LOG(LOG_ERR, "failed (%s)", knot_strerror(ret)); return KNOT_NS_PROC_FAIL; } } else { NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 100); } /* Process answer packet. */ int ret = axfr_answer_packet(pkt, (struct xfr_proc *)adata->ext); if (ret == KNOT_NS_PROC_DONE) { NS_NEED_TSIG_SIGNED(&adata->param->tsig_ctx, 0); /* This was the last packet, finalize zone and publish it. */ int fret = axfr_answer_finalize(adata); if (fret != KNOT_EOK) { ret = KNOT_NS_PROC_FAIL; } } return ret; }
void print_packet(const knot_pkt_t *packet, const net_t *net, const size_t size, const float elapsed, const time_t exec_time, const bool incoming, const style_t *style) { if (packet == NULL || style == NULL) { DBG_NULL; return; } const knot_pktsection_t *answers = knot_pkt_section(packet, KNOT_ANSWER); const knot_pktsection_t *authority = knot_pkt_section(packet, KNOT_AUTHORITY); const knot_pktsection_t *additional = knot_pkt_section(packet, KNOT_ADDITIONAL); uint16_t qdcount = knot_wire_get_qdcount(packet->wire); uint16_t ancount = knot_wire_get_ancount(packet->wire); uint16_t nscount = knot_wire_get_nscount(packet->wire); uint16_t arcount = knot_wire_get_arcount(packet->wire); // Get Extended RCODE from the packet. uint16_t rcode = knot_pkt_get_ext_rcode(packet); // Disable additionals printing if there are no other records. // OPT record may be placed anywhere within additionals! if (knot_pkt_has_edns(packet) && arcount == 1) { arcount = 0; } // Print packet information header. if (style->show_header) { print_header(packet, style, rcode); } // Print EDNS section. if (style->show_edns && knot_pkt_has_edns(packet)) { printf("\n;; EDNS PSEUDOSECTION:\n;; "); print_section_opt(packet->opt_rr, knot_wire_get_rcode(packet->wire)); } // Print DNS sections. switch (style->format) { case FORMAT_DIG: if (ancount > 0) { print_section_dig(knot_pkt_rr(answers, 0), ancount, style); } break; case FORMAT_HOST: if (ancount > 0) { print_section_host(knot_pkt_rr(answers, 0), ancount, style); } else { print_error_host(rcode, packet, style); } break; case FORMAT_NSUPDATE: if (style->show_question && qdcount > 0) { printf("\n;; ZONE SECTION:\n;; "); print_section_question(knot_pkt_qname(packet), knot_pkt_qclass(packet), knot_pkt_qtype(packet), style); } if (style->show_answer && ancount > 0) { printf("\n;; PREREQUISITE SECTION:\n"); print_section_full(knot_pkt_rr(answers, 0), ancount, style, true); } if (style->show_authority && nscount > 0) { printf("\n;; UPDATE SECTION:\n"); print_section_full(knot_pkt_rr(authority, 0), nscount, style, true); } if (style->show_additional && arcount > 0) { printf("\n;; ADDITIONAL DATA:\n"); print_section_full(knot_pkt_rr(additional, 0), arcount, style, true); } break; case FORMAT_FULL: if (style->show_question && qdcount > 0) { printf("\n;; QUESTION SECTION:\n;; "); print_section_question(knot_pkt_qname(packet), knot_pkt_qclass(packet), knot_pkt_qtype(packet), style); } if (style->show_answer && ancount > 0) { printf("\n;; ANSWER SECTION:\n"); print_section_full(knot_pkt_rr(answers, 0), ancount, style, true); } if (style->show_authority && nscount > 0) { printf("\n;; AUTHORITY SECTION:\n"); print_section_full(knot_pkt_rr(authority, 0), nscount, style, true); } if (style->show_additional && arcount > 0) { printf("\n;; ADDITIONAL SECTION:\n"); print_section_full(knot_pkt_rr(additional, 0), arcount, style, true); } break; default: break; } // Print TSIG section. if (style->show_tsig && knot_pkt_has_tsig(packet)) { printf("\n;; TSIG PSEUDOSECTION:\n"); print_section_full(packet->tsig_rr, 1, style, false); } // Print packet statistics. if (style->show_footer) { printf("\n"); print_footer(size, 0, 0, net, elapsed, exec_time, incoming); } }