_public_ uint16_t knot_pkt_type(const knot_pkt_t *pkt) { if (pkt == NULL) { return 0; } bool is_query = (knot_wire_get_qr(pkt->wire) == 0); uint16_t ret = KNOT_QUERY_INVALID; uint8_t opcode = knot_wire_get_opcode(pkt->wire); uint8_t query_type = knot_pkt_qtype(pkt); switch (opcode) { case KNOT_OPCODE_QUERY: switch (query_type) { case 0 /* RESERVED */: /* INVALID */ break; case KNOT_RRTYPE_AXFR: ret = KNOT_QUERY_AXFR; break; case KNOT_RRTYPE_IXFR: ret = KNOT_QUERY_IXFR; break; default: ret = KNOT_QUERY_NORMAL; break; } break; case KNOT_OPCODE_NOTIFY: ret = KNOT_QUERY_NOTIFY; break; case KNOT_OPCODE_UPDATE: ret = KNOT_QUERY_UPDATE; break; default: break; } if (!is_query) { ret = ret|KNOT_RESPONSE; } return ret; }
static int update_delegation(struct kr_request *req, struct kr_query *qry, knot_pkt_t *answer, bool has_nsec3) { struct kr_zonecut *cut = &qry->zone_cut; /* RFC4035 3.1.4. authoritative must send either DS or proof of non-existence. * If it contains neither, the referral is bogus (or an attempted downgrade attack). */ unsigned section = KNOT_ANSWER; if (!knot_wire_get_aa(answer->wire)) { /* Referral */ section = KNOT_AUTHORITY; } else if (knot_pkt_qtype(answer) == KNOT_RRTYPE_DS) { /* Subrequest */ section = KNOT_ANSWER; } else { /* N/A */ return kr_ok(); } int ret = 0; const knot_dname_t *proved_name = knot_pkt_qname(answer); /* Aggregate DS records (if using multiple keys) */ knot_rrset_t *new_ds = update_ds(cut, knot_pkt_section(answer, section)); if (!new_ds) { /* No DS provided, check for proof of non-existence. */ if (!has_nsec3) { if (!knot_wire_get_aa(answer->wire)) { /* Referral, check if it is referral to unsigned, rfc4035 5.2 */ ret = kr_nsec_ref_to_unsigned(answer); } else { /* No-data answer */ ret = kr_nsec_existence_denial(answer, KNOT_AUTHORITY, proved_name, KNOT_RRTYPE_DS); } } else { if (!knot_wire_get_aa(answer->wire)) { /* Referral, check if it is referral to unsigned, rfc5155 8.9 */ ret = kr_nsec3_ref_to_unsigned(answer); } else { /* No-data answer, QTYPE is DS, rfc5155 8.6 */ ret = kr_nsec3_no_data(answer, KNOT_AUTHORITY, proved_name, KNOT_RRTYPE_DS); } if (ret == kr_error(DNSSEC_NOT_FOUND)) { /* Not bogus, going insecure due to optout */ ret = 0; } } if (ret != 0) { DEBUG_MSG(qry, "<= bogus proof of DS non-existence\n"); qry->flags |= QUERY_DNSSEC_BOGUS; } else { DEBUG_MSG(qry, "<= DS doesn't exist, going insecure\n"); qry->flags &= ~QUERY_DNSSEC_WANT; qry->flags |= QUERY_DNSSEC_INSECURE; } return ret; } /* Extend trust anchor */ DEBUG_MSG(qry, "<= DS: OK\n"); cut->trust_anchor = new_ds; return ret; }
void print_header_xfr(const knot_pkt_t *packet, const style_t *style) { if (style == NULL) { DBG_NULL; return; } char xfr[16] = "AXFR"; switch (knot_pkt_qtype(packet)) { case KNOT_RRTYPE_AXFR: break; case KNOT_RRTYPE_IXFR: xfr[0] = 'I'; break; default: return; } if (style->show_header) { char *owner = knot_dname_to_str_alloc(knot_pkt_qname(packet)); if (style->style.ascii_to_idn != NULL) { style->style.ascii_to_idn(&owner); } if (owner != NULL) { printf(";; %s for %s\n", xfr, owner); free(owner); } } }
static void print_error_host(const uint16_t code, const knot_pkt_t *packet, const style_t *style) { const char *rcode_str = "Unknown"; char type[32] = "Unknown"; char *owner; lookup_table_t *rcode; owner = knot_dname_to_str_alloc(knot_pkt_qname(packet)); if (style->style.ascii_to_idn != NULL) { style->style.ascii_to_idn(&owner); } rcode = lookup_by_id(knot_rcode_names, code); if (rcode != NULL) { rcode_str = rcode->name; } knot_rrtype_to_string(knot_pkt_qtype(packet), type, sizeof(type)); if (code == KNOT_RCODE_NOERROR) { printf("Host %s has no %s record\n", owner, type); } else { printf("Host %s type %s error: %s\n", owner, type, rcode_str); } free(owner); }
/*! \brief Check if query fits the template requirements. */ static int template_match(int state, synth_template_t *tpl, knot_pkt_t *pkt, struct query_data *qdata) { /* Parse address from query name. */ char addr_str[SOCKADDR_STRLEN] = { '\0' }; int ret = addr_parse(qdata, tpl, addr_str); if (ret != KNOT_EOK) { return state; /* Can't identify addr in QNAME, not applicable. */ } /* Match against template netblock. */ struct sockaddr_storage query_addr = { '\0' }; int provided_af = tpl->subnet.ss.ss_family; ret = sockaddr_set(&query_addr, provided_af, addr_str, 0); if (ret == KNOT_EOK) { ret = netblock_match(&tpl->subnet, &query_addr); } if (ret != 0) { return state; /* Out of our netblock, not applicable. */ } /* Check if the request is for an available query type. */ uint16_t qtype = knot_pkt_qtype(qdata->query); switch (tpl->type) { case SYNTH_FORWARD: if (!query_satisfied_by_family(qtype, provided_af)) { qdata->rcode = KNOT_RCODE_NOERROR; return NODATA; } break; case SYNTH_REVERSE: if (qtype != KNOT_RRTYPE_PTR && qtype != KNOT_RRTYPE_ANY) { qdata->rcode = KNOT_RCODE_NOERROR; return NODATA; } break; default: break; } /* Synthetise record from template. */ knot_rrset_t *rr = synth_rr(addr_str, tpl, pkt, qdata); if (rr == NULL) { qdata->rcode = KNOT_RCODE_SERVFAIL; return ERROR; } /* Insert synthetic response into packet. */ if (knot_pkt_put(pkt, 0, rr, KNOT_PF_FREE) != KNOT_EOK) { return ERROR; } /* Authoritative response. */ knot_wire_set_aa(pkt->wire); return HIT; }
static int rosedb_synth(knot_pkt_t *pkt, const knot_dname_t *key, struct iter *it, struct query_data *qdata) { struct entry entry; int ret = KNOT_EOK; uint16_t qtype = knot_pkt_qtype(qdata->query); /* Answer section. */ while (ret == KNOT_EOK) { if (cache_iter_val(it, &entry) == 0) { ret = rosedb_synth_rr(pkt, &entry, qtype); } if (cache_iter_next(it) != 0) { break; } } /* Authority section. */ knot_pkt_begin(pkt, KNOT_AUTHORITY); /* Not found (zone cut if records exist). */ ret = cache_iter_begin(it, key); while (ret == KNOT_EOK) { if (cache_iter_val(it, &entry) == 0) { ret = rosedb_synth_rr(pkt, &entry, KNOT_RRTYPE_NS); ret = rosedb_synth_rr(pkt, &entry, KNOT_RRTYPE_SOA); } if (cache_iter_next(it) != 0) { break; } } /* Our response is authoritative. */ if (knot_wire_get_nscount(pkt->wire) > 0) { knot_wire_set_aa(pkt->wire); if (knot_wire_get_ancount(pkt->wire) == 0) { qdata->rcode = KNOT_RCODE_NXDOMAIN; } } /* Send message to syslog. */ struct sockaddr_storage syslog_addr; if (sockaddr_set(&syslog_addr, AF_INET, entry.syslog_ip, DEFAULT_PORT) == KNOT_EOK) { int sock = net_unbound_socket(AF_INET, &syslog_addr); if (sock > 0) { rosedb_send_log(sock, (struct sockaddr *)&syslog_addr, pkt, entry.threat_code, qdata); close(sock); } } return ret; }
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; }
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 rosedb_log_message(char *stream, size_t *maxlen, knot_pkt_t *pkt, const char *threat_code, struct query_data *qdata) { char dname_buf[KNOT_DNAME_MAXLEN] = {'\0'}; struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); time_t now = time(NULL); struct tm tm; gmtime_r(&now, &tm); /* Field 1 Timestamp (UTC). */ STREAM_WRITE(stream, maxlen, strftime, "%Y-%m-%d %H:%M:%S\t", &tm); /* Field 2/3 Remote, local address. */ const struct sockaddr *remote = (const struct sockaddr *)qdata->param->remote; memcpy(&addr, remote, sockaddr_len(remote)); int client_port = sockaddr_port(&addr); sockaddr_port_set(&addr, 0); STREAM_WRITE(stream, maxlen, sockaddr_tostr, &addr); STREAM_WRITE(stream, maxlen, snprintf, "\t"); getsockname(qdata->param->socket, (struct sockaddr *)&addr, &addr_len); int server_port = sockaddr_port(&addr); sockaddr_port_set(&addr, 0); STREAM_WRITE(stream, maxlen, sockaddr_tostr, &addr); STREAM_WRITE(stream, maxlen, snprintf, "\t"); /* Field 4/5 Local, remote port. */ STREAM_WRITE(stream, maxlen, snprintf, "%d\t%d\t", client_port, server_port); /* Field 6 Threat ID. */ STREAM_WRITE(stream, maxlen, snprintf, "%s\t", threat_code); /* Field 7 - 13 NULL */ STREAM_WRITE(stream, maxlen, snprintf, "\t\t\t\t\t\t\t"); /* Field 14 QNAME */ knot_dname_to_str(dname_buf, knot_pkt_qname(qdata->query), sizeof(dname_buf)); STREAM_WRITE(stream, maxlen, snprintf, "%s\t", dname_buf); /* Field 15 Resolution (0 = local, 1 = lookup)*/ STREAM_WRITE(stream, maxlen, snprintf, "0\t"); /* Field 16 RDATA. * - Return randomly RDATA in the answer section (probabilistic rotation). * - Empty if no answer. */ const knot_pktsection_t *ans = knot_pkt_section(pkt, KNOT_ANSWER); if (ans->count > 0) { const knot_rrset_t *rr = &ans->rr[knot_random_uint16_t() % ans->count]; int ret = knot_rrset_txt_dump_data(rr, 0, stream, *maxlen, &KNOT_DUMP_STYLE_DEFAULT); if (ret < 0) { return ret; } stream_skip(&stream, maxlen, ret); } STREAM_WRITE(stream, maxlen, snprintf, "\t"); /* Field 17 Connection type. */ STREAM_WRITE(stream, maxlen, snprintf, "%s\t", net_is_connected(qdata->param->socket) ? "TCP" : "UDP"); /* Field 18 Query type. */ char type_str[16] = { '\0' }; knot_rrtype_to_string(knot_pkt_qtype(qdata->query), type_str, sizeof(type_str)); STREAM_WRITE(stream, maxlen, snprintf, "%s\t", type_str); /* Field 19 First authority. */ const knot_pktsection_t *ns = knot_pkt_section(pkt, KNOT_AUTHORITY); if (ns->count > 0 && ns->rr[0].type == KNOT_RRTYPE_NS) { const knot_dname_t *label = knot_ns_name(&ns->rr[0].rrs, 0); memset(dname_buf, 0, sizeof(dname_buf)); memcpy(dname_buf, label + 1, *label); STREAM_WRITE(stream, maxlen, snprintf, "%s", dname_buf); } return KNOT_EOK; }
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); } }