/* Copyright (C) 2011 CZ.NIC, z.s.p.o. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "nameserver/name-server.h" #include "updates/xfr-in.h" #include "util/error.h" #include "libknot.h" #include "util/debug.h" #include "packet/packet.h" #include "packet/response.h" #include "packet/query.h" #include "consts.h" #include "updates/changesets.h" #include "updates/ddns.h" #include "tsig-op.h" /*----------------------------------------------------------------------------*/ /*! \brief Maximum UDP payload with EDNS enabled. */ static const uint16_t MAX_UDP_PAYLOAD_EDNS = 4096; /*! \brief Maximum UDP payload with EDNS disabled. */ static const uint16_t MAX_UDP_PAYLOAD = 504; // 512 - 8B header /*! \brief Maximum size of one AXFR response packet. */ static const uint16_t MAX_AXFR_PAYLOAD = 65535; /*! \brief Supported EDNS version. */ static const uint8_t EDNS_VERSION = 0; /*! \brief Determines whether EDNS is enabled. */ static const int EDNS_ENABLED = 1; /*! \brief TTL of a CNAME synthetized from a DNAME. */ static const uint32_t SYNTH_CNAME_TTL = 0; /*! \brief Determines whether DNSSEC is enabled. */ static const int DNSSEC_ENABLED = 1; /*! \brief Determines whether NSID is enabled. */ static const int NSID_ENABLED = 1; /*! \brief Length of NSID option data. */ static const uint16_t NSID_LENGTH = 6; /*! \brief NSID option data. */ static const uint8_t NSID_DATA[6] = {0x46, 0x6f, 0x6f, 0x42, 0x61, 0x72}; /*! \brief Internal error code to propagate need for SERVFAIL response. */ static const int NS_ERR_SERVFAIL = -999; /*----------------------------------------------------------------------------*/ /* Private functions */ /*----------------------------------------------------------------------------*/ /*! * \brief Finds zone where to search for the QNAME. * * \note As QTYPE DS requires special handling, this function finds a zone for * a direct predecessor of QNAME in such case. * * \param zdb Zone database where to search for the proper zone. * \param qname QNAME. * \param qtype QTYPE. * * \return Zone to which QNAME belongs (according to QTYPE), or NULL if no such * zone was found. */ static const knot_zone_t *ns_get_zone_for_qname(knot_zonedb_t *zdb, const knot_dname_t *qname, uint16_t qtype) { const knot_zone_t *zone; /* * Find a zone in which to search. * * In case of DS query, we strip the leftmost label when searching for * the zone (but use whole qname in search for the record), as the DS * records are only present in a parent zone. */ if (qtype == KNOT_RRTYPE_DS) { /*! \todo Optimize, do not deep copy dname. */ knot_dname_t *name = knot_dname_left_chop(qname); zone = knot_zonedb_find_zone_for_name(zdb, name); /* Directly discard. */ knot_dname_free(&name); } else { zone = knot_zonedb_find_zone_for_name(zdb, qname); } return zone; } /*----------------------------------------------------------------------------*/ /*! * \brief Synthetizes RRSet from a wildcard RRSet using the given QNAME. * * The synthetized RRSet is identical to the wildcard RRSets, except that the * owner name is replaced by \a qname. * * \param wildcard_rrset Wildcard RRSet to synthetize from. * \param qname Domain name to be used as the owner of the synthetized RRset. * * \return The synthetized RRSet (this is a newly created RRSet, remember to * free it). */ static knot_rrset_t *ns_synth_from_wildcard( const knot_rrset_t *wildcard_rrset, const knot_dname_t *qname) { dbg_ns("Synthetizing RRSet from wildcard...\n"); knot_dname_t *owner = knot_dname_deep_copy(qname); // printf("Copied owner ptr: %p\n", owner); knot_rrset_t *synth_rrset = knot_rrset_new( owner, knot_rrset_type(wildcard_rrset), knot_rrset_class(wildcard_rrset), knot_rrset_ttl(wildcard_rrset)); /* Release owner, as it's retained in rrset. */ knot_dname_release(owner); if (synth_rrset == NULL) { return NULL; } dbg_ns("Created RRSet header:\n"); knot_rrset_dump(synth_rrset, 1); // copy all RDATA const knot_rdata_t *rdata = knot_rrset_rdata(wildcard_rrset); while (rdata != NULL) { // we could use the RDATA from the wildcard rrset // but there is no way to distinguish it when deleting // temporary RRSets knot_rdata_t *rdata_copy = knot_rdata_deep_copy(rdata, knot_rrset_type(synth_rrset)); if (rdata_copy == NULL) { knot_rrset_deep_free(&synth_rrset, 1, 1, 0); return NULL; } dbg_ns("Copied RDATA:\n"); knot_rdata_dump(rdata_copy, knot_rrset_type(synth_rrset), 1); knot_rrset_add_rdata(synth_rrset, rdata_copy); rdata = knot_rrset_rdata_next(wildcard_rrset, rdata); } // printf("Synthetized RRSet pointer: %p\n", synth_rrset); return synth_rrset; } /*----------------------------------------------------------------------------*/ /*! * \brief Checks if the given RRSet is a wildcard RRSet and replaces it with * a synthetized RRSet if required. * * \param name Domain name to be used as the owner of the possibly synthetized * RRSet * \param resp Response to which the synthetized RRSet should be stored (as a * temporary RRSet). * \param rrset RRSet to check (and possibly replace). */ static void ns_check_wildcard(const knot_dname_t *name, knot_packet_t *resp, const knot_rrset_t **rrset) { assert(name != NULL); assert(resp != NULL); assert(rrset != NULL); assert(*rrset != NULL); if (knot_dname_is_wildcard((*rrset)->owner)) { knot_rrset_t *synth_rrset = ns_synth_from_wildcard(*rrset, name); dbg_ns("Synthetized RRSet:\n"); knot_rrset_dump(synth_rrset, 1); knot_packet_add_tmp_rrset(resp, synth_rrset); *rrset = synth_rrset; } } /*----------------------------------------------------------------------------*/ /*! * \brief Adds signatures (RRSIGs) for the given RRSet to the response. * * This function first checks if DNSSEC is enabled and if it was requested in * the response (DO bit set). If not, it does nothing and returns 0. If yes, * it retrieves RRSIGs stored in the RRSet, deals with possible wildcard owner * and adds the RRSIGs to response using the given function (that determines * to which section of the response they will be added). * * \param rrset RRSet to get the RRSIGs from. * \param resp Response where to add the RRSIGs. * \param name Actual name to be used as owner in case of wildcard RRSet. * \param add_rrset_to_resp Function for adding the RRSIG RRset to the response. * \param tc Set to 1 if omitting the RRSIG RRSet should result in setting the * TC bit in the response. * * \return KNOT_EOK * \return KNOT_ENOMEM * \return KNOT_ESPACE */ static int ns_add_rrsigs(const knot_rrset_t *rrset, knot_packet_t *resp, const knot_dname_t *name, int (*add_rrset_to_resp)(knot_packet_t *, const knot_rrset_t *, int, int, int), int tc) { const knot_rrset_t *rrsigs; dbg_ns("Adding RRSIGs for RRSet, type: %s.\n", knot_rrtype_to_string(knot_rrset_type(rrset))); assert(resp != NULL); assert(add_rrset_to_resp != NULL); dbg_ns("DNSSEC requested: %d\n", knot_query_dnssec_requested(knot_packet_query(resp))); dbg_ns("RRSIGS: %p\n", knot_rrset_rrsigs(rrset)); if (DNSSEC_ENABLED && knot_query_dnssec_requested(knot_packet_query(resp)) && (rrsigs = knot_rrset_rrsigs(rrset)) != NULL) { if (name != NULL) { ns_check_wildcard(name, resp, &rrsigs); } return add_rrset_to_resp(resp, rrsigs, tc, 0, 0); } return KNOT_EOK; } /*----------------------------------------------------------------------------*/ /*! * \brief Resolves CNAME chain starting in \a node, stores all the CNAMEs in the * response and updates \a node and \a qname to the last node in the * chain. * * \param node Node (possibly) containing a CNAME RR. * \param qname Searched name. Will be updated to the canonical name. * \param resp Response where to add the CNAME RRs. * \param add_rrset_to_resp Function for adding the CNAME RRs to the response. * \param tc Set to 1 if omitting the RRSIG RRSet should result in setting the * TC bit in the response. */ static void ns_follow_cname(const knot_node_t **node, const knot_dname_t **qname, knot_packet_t *resp, int (*add_rrset_to_resp)(knot_packet_t *, const knot_rrset_t *, int, int, int), int tc) { dbg_ns("Resolving CNAME chain...\n"); const knot_rrset_t *cname_rrset; while (*node != NULL && (cname_rrset = knot_node_rrset(*node, KNOT_RRTYPE_CNAME)) != NULL) { /* put the CNAME record to answer, but replace the possible wildcard name with qname */ assert(cname_rrset != NULL); dbg_ns("CNAME RRSet: %p, owner: %p\n", cname_rrset, cname_rrset->owner); const knot_rrset_t *rrset = cname_rrset; // ignoring other than the first record if (knot_dname_is_wildcard(knot_node_owner(*node))) { /* if wildcard node, we must copy the RRSet and replace its owner */ rrset = ns_synth_from_wildcard(cname_rrset, *qname); knot_packet_add_tmp_rrset(resp, (knot_rrset_t *)rrset); add_rrset_to_resp(resp, rrset, tc, 0, 0); ns_add_rrsigs(cname_rrset, resp, *qname, add_rrset_to_resp, tc); } else { add_rrset_to_resp(resp, rrset, tc, 0, 0); ns_add_rrsigs(rrset, resp, *qname, add_rrset_to_resp, tc); } dbg_ns("Using RRSet: %p, owner: %p\n", rrset, rrset->owner); dbg_ns_exec( char *name = knot_dname_to_str(knot_rrset_owner(rrset)); dbg_ns("CNAME record for owner %s put to response.\n", name); free(name); ); // get the name from the CNAME RDATA const knot_dname_t *cname = knot_rdata_cname_name( knot_rrset_rdata(cname_rrset)); dbg_ns("CNAME name from RDATA: %p\n", cname); // change the node to the node of that name *node = knot_dname_node(cname, 1); dbg_ns("This name's node: %p\n", *node); // // it is not an old node and if yes, skip it // if (knot_node_is_old(*node)) { // *node = knot_node_new_node(*node); // } // save the new name which should be used for replacing wildcard *qname = cname; }; } /*----------------------------------------------------------------------------*/ /*! * \brief Retrieves RRSet(s) of given type from the given node and adds them to * the response's Answer section. * * \param node Node where to take the RRSet from. * \param name Actual searched name (used in case of wildcard RRSet(s)). * \param type Type of the RRSet(s). If set to KNOT_RRTYPE_ANY, all RRSets * from the node will be added to the answer. * \param resp Response where to add the RRSets. * * \return Number of RRSets added. */ static int ns_put_answer(const knot_node_t *node, const knot_dname_t *name, uint16_t type, knot_packet_t *resp) { int added = 0; dbg_ns_exec( char *name_str = knot_dname_to_str(node->owner); dbg_ns("Putting answers from node %s.\n", name_str); free(name_str); ); switch (type) { case KNOT_RRTYPE_ANY: { dbg_ns("Returning all RRTYPES.\n"); const knot_rrset_t **rrsets = knot_node_rrsets(node); if (rrsets == NULL) { break; } int i = 0; int ret = 0; const knot_rrset_t *rrset; while (i < knot_node_rrset_count(node)) { assert(rrsets[i] != NULL); rrset = rrsets[i]; dbg_ns(" Type: %s\n", knot_rrtype_to_string(knot_rrset_type(rrset))); ns_check_wildcard(name, resp, &rrset); ret = knot_response_add_rrset_answer(resp, rrset, 1, 0, 0); if (ret >= 0 && (added += 1) && (ret = ns_add_rrsigs(rrset, resp, name, knot_response_add_rrset_answer, 1)) >=0 ) { added += 1; } else { free(rrsets); rrsets = NULL; break; } ++i; } if (rrsets != NULL) { free(rrsets); } break; } case KNOT_RRTYPE_RRSIG: { dbg_ns("Returning all RRSIGs.\n"); const knot_rrset_t **rrsets = knot_node_rrsets(node); if (rrsets == NULL) { break; } int i = 0; int ret = 0; const knot_rrset_t *rrset; while (i < knot_node_rrset_count(node)) { assert(rrsets[i] != NULL); rrset = knot_rrset_rrsigs(rrsets[i]); if (rrset == NULL) { ++i; continue; } ns_check_wildcard(name, resp, &rrset); ret = knot_response_add_rrset_answer(resp, rrset, 1, 0, 0); if (ret < 0) { break; } added += 1; ++i; } free(rrsets); break; } default: { int ret = 0; const knot_rrset_t *rrset = knot_node_rrset(node, type); const knot_rrset_t *rrset2 = rrset; if (rrset != NULL) { dbg_ns("Found RRSet of type %s\n", knot_rrtype_to_string(type)); ns_check_wildcard(name, resp, &rrset2); ret = knot_response_add_rrset_answer(resp, rrset2, 1, 0, 0); if (ret >= 0 && (added += 1) && (ret = ns_add_rrsigs(rrset, resp, name, knot_response_add_rrset_answer, 1)) > 0) { added += 1; } } } } knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); return added; } /*----------------------------------------------------------------------------*/ /*! * \brief Adds RRSets to Additional section of the response. * * This function uses knot_rdata_get_name() to get the domain name from the * RDATA of the RRSet according to its type. It also does not search for the * retrieved domain name, but just uses its node field. Thus to work correctly, * the zone where the RRSet is from should be adjusted using * knot_zone_adjust_dnames(). * * A and AAAA RRSets (and possible CNAMEs) for the found domain names are added. * * \warning Use this function only with types containing some domain name, * otherwise it will crash (or behave strangely). * * \param resp Response where to add the Additional data. * \param rrset RRSet to get the Additional data for. */ static void ns_put_additional_for_rrset(knot_packet_t *resp, const knot_rrset_t *rrset) { const knot_node_t *node = NULL; const knot_rdata_t *rdata = NULL; const knot_dname_t *dname = NULL; // for all RRs in the RRset rdata = knot_rrset_rdata(rrset); while (rdata != NULL) { dbg_ns("Getting name from RDATA, type %s..\n", knot_rrtype_to_string(knot_rrset_type(rrset))); dname = knot_rdata_get_name(rdata, knot_rrset_type(rrset)); assert(dname != NULL); node = knot_dname_node(dname, 1); // // check if the node is not old and if yes, take the new one // if (knot_node_is_old(node)) { // node = knot_node_new_node(node); // } dbg_ns_detail("Node saved in RDATA dname: %p\n", node); if (node != NULL && node->owner != dname) { // the stored node should be the closest encloser assert(knot_dname_is_subdomain(dname, node->owner)); // try the wildcard child, if any node = knot_node_wildcard_child(node, 1); // // this should not be old node!! // assert(!knot_node_is_old(node)); } const knot_rrset_t *rrset_add; if (node != NULL) { dbg_ns_exec( char *name = knot_dname_to_str(node->owner); dbg_ns("Putting additional from node %s\n", name); free(name); ); dbg_ns("Checking CNAMEs...\n"); if (knot_node_rrset(node, KNOT_RRTYPE_CNAME) != NULL) { dbg_ns("Found CNAME in node, following...\n"); const knot_dname_t *dname = knot_node_owner(node); ns_follow_cname(&node, &dname, resp, knot_response_add_rrset_additional, 0); } // A RRSet dbg_ns("A RRSets...\n"); rrset_add = knot_node_rrset(node, KNOT_RRTYPE_A); if (rrset_add != NULL) { dbg_ns("Found A RRsets.\n"); const knot_rrset_t *rrset_add2 = rrset_add; ns_check_wildcard(dname, resp, &rrset_add2); knot_response_add_rrset_additional( resp, rrset_add2, 0, 1, 0); ns_add_rrsigs(rrset_add, resp, dname, knot_response_add_rrset_additional, 0); } // AAAA RRSet dbg_ns("AAAA RRSets...\n"); rrset_add = knot_node_rrset(node, KNOT_RRTYPE_AAAA); if (rrset_add != NULL) { dbg_ns("Found AAAA RRsets.\n"); const knot_rrset_t *rrset_add2 = rrset_add; ns_check_wildcard(dname, resp, &rrset_add2); knot_response_add_rrset_additional( resp, rrset_add2, 0, 1, 0); ns_add_rrsigs(rrset_add, resp, dname, knot_response_add_rrset_additional, 0); } } assert(rrset != NULL); assert(rdata != NULL); rdata = knot_rrset_rdata_next(rrset, rdata); } } /*----------------------------------------------------------------------------*/ /*! * \brief Checks whether the given type requires additional processing. * * Only MX, NS and SRV types require additional processing. * * \param qtype Type to check. * * \retval <> 0 if additional processing is needed for \a qtype. * \retval 0 otherwise. */ static int ns_additional_needed(uint16_t qtype) { return (qtype == KNOT_RRTYPE_MX || qtype == KNOT_RRTYPE_NS || qtype == KNOT_RRTYPE_SRV); } /*----------------------------------------------------------------------------*/ /*! * \brief Adds whatever Additional RRSets are required for the response. * * For each RRSet in Answer and Authority sections this function checks if * additional processing is needed and if yes, it puts any Additional RRSets * available to the Additional section of the response. * * \param resp Response to process. */ static void ns_put_additional(knot_packet_t *resp) { dbg_ns("ADDITIONAL SECTION PROCESSING\n"); const knot_rrset_t *rrset = NULL; for (int i = 0; i < knot_packet_answer_rrset_count(resp); ++i) { rrset = knot_packet_answer_rrset(resp, i); assert(rrset != NULL); if (ns_additional_needed(knot_rrset_type(rrset))) { ns_put_additional_for_rrset(resp, rrset); } } for (int i = 0; i < knot_packet_authority_rrset_count(resp); ++i) { rrset = knot_packet_authority_rrset(resp, i); if (ns_additional_needed(knot_rrset_type(rrset))) { ns_put_additional_for_rrset(resp, rrset); } } } /*----------------------------------------------------------------------------*/ /*! * \brief Puts authority NS RRSet to the Auhority section of the response. * * \param zone Zone to take the authority NS RRSet from. * \param resp Response where to add the RRSet. */ static void ns_put_authority_ns(const knot_zone_contents_t *zone, knot_packet_t *resp) { const knot_rrset_t *ns_rrset = knot_node_rrset( knot_zone_contents_apex(zone), KNOT_RRTYPE_NS); if (ns_rrset != NULL) { knot_response_add_rrset_authority(resp, ns_rrset, 0, 1, 0); ns_add_rrsigs(ns_rrset, resp, knot_node_owner( knot_zone_contents_apex(zone)), knot_response_add_rrset_authority, 1); } } /*----------------------------------------------------------------------------*/ /*! * \brief Puts SOA RRSet to the Auhority section of the response. * * \param zone Zone to take the SOA RRSet from. * \param resp Response where to add the RRSet. */ static void ns_put_authority_soa(const knot_zone_contents_t *zone, knot_packet_t *resp) { const knot_rrset_t *soa_rrset = knot_node_rrset( knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA); assert(soa_rrset != NULL); knot_response_add_rrset_authority(resp, soa_rrset, 0, 0, 0); ns_add_rrsigs(soa_rrset, resp, knot_node_owner(knot_zone_contents_apex(zone)), knot_response_add_rrset_authority, 1); } /*----------------------------------------------------------------------------*/ /*! * \brief Creates a 'next closer name' to the given domain name. * * For definition of 'next closer name', see RFC5155, Page 6. * * \param closest_encloser Closest encloser of \a name. * \param name Domain name to create the 'next closer' name to. * * \return 'Next closer name' to the given domain name or NULL if an error * occured. */ static knot_dname_t *ns_next_closer(const knot_dname_t *closest_encloser, const knot_dname_t *name) { int ce_labels = knot_dname_label_count(closest_encloser); int qname_labels = knot_dname_label_count(name); assert(ce_labels < qname_labels); // the common labels should match assert(knot_dname_matched_labels(closest_encloser, name) == ce_labels); // chop some labels from the qname knot_dname_t *next_closer = knot_dname_deep_copy(name); if (next_closer == NULL) { return NULL; } for (int i = 0; i < (qname_labels - ce_labels - 1); ++i) { knot_dname_left_chop_no_copy(next_closer); } return next_closer; } /*----------------------------------------------------------------------------*/ /*! * \brief Adds NSEC3 RRSet (together with corresponding RRSIGs) from the given * node into the response. * * \param node Node to get the NSEC3 RRSet from. * \param resp Response where to add the RRSets. */ static void ns_put_nsec3_from_node(const knot_node_t *node, knot_packet_t *resp) { assert(DNSSEC_ENABLED && knot_query_dnssec_requested(knot_packet_query(resp))); const knot_rrset_t *rrset = knot_node_rrset(node, KNOT_RRTYPE_NSEC3); assert(rrset != NULL); int res = knot_response_add_rrset_authority(resp, rrset, 1, 1, 0); // add RRSIG for the RRSet if (res == 0 && (rrset = knot_rrset_rrsigs(rrset)) != NULL) { knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); } } /*----------------------------------------------------------------------------*/ /*! * \brief Finds and adds NSEC3 covering the given domain name (and their * associated RRSIGs) to the response. * * \param zone Zone used for answering. * \param name Domain name to cover. * \param resp Response where to add the RRSets. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL if a runtime collision occured. The server should * respond with SERVFAIL in such case. */ static int ns_put_covering_nsec3(const knot_zone_contents_t *zone, const knot_dname_t *name, knot_packet_t *resp) { const knot_node_t *prev, *node; /*! \todo Check version. */ int match = knot_zone_contents_find_nsec3_for_name(zone, name, &node, &prev, 1); assert(match >= 0); node = knot_node_current(node); prev = knot_node_current(prev); if (match == KNOT_ZONE_NAME_FOUND){ // run-time collision => SERVFAIL return KNOT_EOK; } // // check if the prev node is not old and if yes, take the new one // if (knot_node_is_old(prev)) { // prev = knot_node_new_node(prev); // assert(prev != NULL); // } dbg_ns_exec( char *name = knot_dname_to_str(prev->owner); dbg_ns("Covering NSEC3 node: %s\n", name); free(name); ); ns_put_nsec3_from_node(prev, resp); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ /*! * \brief Adds NSEC3s comprising the 'closest encloser proof' for the given * (non-existent) domain name (and their associated RRSIGs) to the * response. * * For definition of 'closest encloser proof', see RFC5155, section 7.2.1, * Page 18. * * \note This function does not check if DNSSEC is enabled, nor if it is * requested by the query. * * \param zone Zone used for answering. * \param closest_encloser Closest encloser of \a qname in the zone. * \param qname Searched (non-existent) name. * \param resp Response where to add the NSEC3s. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_put_nsec3_closest_encloser_proof( const knot_zone_contents_t *zone, const knot_node_t **closest_encloser, const knot_dname_t *qname, knot_packet_t *resp) { assert(zone != NULL); assert(closest_encloser != NULL); assert(*closest_encloser != NULL); assert(qname != NULL); assert(resp != NULL); if (knot_zone_contents_nsec3params(zone) == NULL) { dbg_ns_exec( char *name = knot_dname_to_str(knot_node_owner( knot_zone_contents_apex(zone))); dbg_ns("No NSEC3PARAM found in zone %s.\n", name); free(name); ); return KNOT_EOK; } dbg_ns_exec( char *name = knot_dname_to_str(knot_node_owner(*closest_encloser)); dbg_ns("Closest encloser: %s\n", name); free(name); ); /* * 1) NSEC3 that matches closest provable encloser. */ const knot_node_t *nsec3_node = NULL; const knot_dname_t *next_closer = NULL; while ((nsec3_node = knot_node_nsec3_node((*closest_encloser), 1)) == NULL) { next_closer = knot_node_owner((*closest_encloser)); *closest_encloser = knot_node_parent(*closest_encloser, 1); if (*closest_encloser == NULL) { // there are no NSEC3s to add return KNOT_EOK; } } assert(nsec3_node != NULL); dbg_ns_exec( char *name = knot_dname_to_str(nsec3_node->owner); dbg_ns("NSEC3 node: %s\n", name); free(name); name = knot_dname_to_str((*closest_encloser)->owner); dbg_ns("Closest provable encloser: %s\n", name); free(name); if (next_closer != NULL) { name = knot_dname_to_str(next_closer); dbg_ns("Next closer name: %s\n", name); free(name); } else { dbg_ns("Next closer name: none\n"); } ); ns_put_nsec3_from_node(nsec3_node, resp); /* * 2) NSEC3 that covers the "next closer" name. */ int ret = 0; if (next_closer == NULL) { // create the "next closer" name by appending from qname next_closer = ns_next_closer( knot_node_owner(*closest_encloser), qname); if (next_closer == NULL) { return NS_ERR_SERVFAIL; } dbg_ns_exec( char *name = knot_dname_to_str(next_closer); dbg_ns("Next closer name: %s\n", name); free(name); ); ret = ns_put_covering_nsec3(zone, next_closer, resp); // the cast is ugly, but no better way around it knot_dname_release((knot_dname_t *)next_closer); } else { ret = ns_put_covering_nsec3(zone, next_closer, resp); } return ret; } /*----------------------------------------------------------------------------*/ /*! * \brief Creates a name of a wildcard child of \a name. * * \param name Domain name to get the wildcard child name of. * * \return Wildcard child name or NULL if an error occured. */ static knot_dname_t *ns_wildcard_child_name(const knot_dname_t *name) { assert(name != NULL); knot_dname_t *wildcard = knot_dname_new_from_str("*", 1, NULL); if (wildcard == NULL) { return NULL; } if (knot_dname_cat(wildcard, name) == NULL) { /* Directly discard dname. */ knot_dname_free(&wildcard); return NULL; } dbg_ns_exec( char *name = knot_dname_to_str(wildcard); dbg_ns("Wildcard: %s\n", name); free(name); ); return wildcard; } /*----------------------------------------------------------------------------*/ /*! * \brief Puts NSEC3s covering the non-existent wildcard child of a node * (and their associated RRSIGs) into the response. * * \note This function does not check if DNSSEC is enabled, nor if it is * requested by the query. * * \param zone Zone used for answering. * \param node Node whose non-existent wildcard child should be covered. * \param resp Response where to add the NSEC3s. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_put_nsec3_no_wildcard_child(const knot_zone_contents_t *zone, const knot_node_t *node, knot_packet_t *resp) { assert(node != NULL); assert(resp != NULL); assert(node->owner != NULL); int ret = 0; knot_dname_t *wildcard = ns_wildcard_child_name(node->owner); if (wildcard == NULL) { ret = NS_ERR_SERVFAIL; } else { ret = ns_put_covering_nsec3(zone, wildcard, resp); /* Directly discard wildcard. */ knot_dname_free(&wildcard); } return ret; } /*----------------------------------------------------------------------------*/ /*! * \brief Puts NSECs or NSEC3s for NODATA error (and their associated RRSIGs) * to the response. * * \note This function first checks if DNSSEC is enabled and requested by the * query. * \note Note that for each zone there are either NSEC or NSEC3 records used. * * \param node Node which generated the NODATA response (i.e. not containing * RRSets of the requested type). * \param resp Response where to add the NSECs or NSEC3s. */ static void ns_put_nsec_nsec3_nodata(const knot_node_t *node, knot_packet_t *resp) { if (!DNSSEC_ENABLED || !knot_query_dnssec_requested(knot_packet_query(resp))) { return; } const knot_node_t *nsec3_node = knot_node_nsec3_node(node, 1); const knot_rrset_t *rrset = NULL; if ((rrset = knot_node_rrset(node, KNOT_RRTYPE_NSEC)) != NULL || (nsec3_node != NULL && (rrset = knot_node_rrset(nsec3_node, KNOT_RRTYPE_NSEC3)) != NULL)) { knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); // add RRSIG for the RRSet if ((rrset = knot_rrset_rrsigs(rrset)) != NULL) { knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); } } } /*----------------------------------------------------------------------------*/ /*! * \brief Puts NSECs for NXDOMAIN error to the response. * * \note This function does not check if DNSSEC is enabled, nor if it is * requested by the query. * * \param qname QNAME which generated the NXDOMAIN error (i.e. not found in the * zone). * \param zone Zone used for answering. * \param previous Previous node to \a qname in the zone. May also be NULL. In * such case the function finds the previous node in the zone. * \param closest_encloser Closest encloser of \a qname. Must not be NULL. * \param resp Response where to put the NSECs. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_put_nsec_nxdomain(const knot_dname_t *qname, const knot_zone_contents_t *zone, const knot_node_t *previous, const knot_node_t *closest_encloser, knot_packet_t *resp) { const knot_rrset_t *rrset = NULL; // check if we have previous; if not, find one using the tree if (previous == NULL) { /*! \todo Check version. */ previous = knot_zone_contents_find_previous(zone, qname); while (!knot_node_is_auth(previous)) { previous = knot_node_previous(previous, 1); } previous = knot_node_current(previous); assert(previous != NULL); } char *name = knot_dname_to_str(previous->owner); dbg_ns("Previous node: %s\n", name); free(name); // 1) NSEC proving that there is no node with the searched name rrset = knot_node_rrset(previous, KNOT_RRTYPE_NSEC); if (rrset == NULL) { // no NSEC records //return NS_ERR_SERVFAIL; return KNOT_EOK; } knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); rrset = knot_rrset_rrsigs(rrset); assert(rrset != NULL); knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); // 2) NSEC proving that there is no wildcard covering the name // this is only different from 1) if the wildcard would be // before 'previous' in canonical order, i.e. we can // search for previous until we find name lesser than wildcard assert(closest_encloser != NULL); knot_dname_t *wildcard = ns_wildcard_child_name(closest_encloser->owner); if (wildcard == NULL) { return NS_ERR_SERVFAIL; } const knot_node_t *prev_new = previous; while (knot_dname_compare(knot_node_owner(prev_new), wildcard) > 0) { dbg_ns("Previous node: %s\n", knot_dname_to_str(knot_node_owner(prev_new))); assert(prev_new != knot_zone_contents_apex(zone)); prev_new = knot_node_previous(prev_new, 1); } assert(knot_dname_compare(knot_node_owner(prev_new), wildcard) < 0); dbg_ns("Previous node: %s\n", knot_dname_to_str(knot_node_owner(prev_new))); /* Directly discard dname. */ knot_dname_free(&wildcard); if (prev_new != previous) { rrset = knot_node_rrset(prev_new, KNOT_RRTYPE_NSEC); assert(rrset != NULL); knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); rrset = knot_rrset_rrsigs(rrset); assert(rrset != NULL); knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); } return KNOT_EOK; } /*----------------------------------------------------------------------------*/ /*! * \brief Puts NSEC3s for NXDOMAIN error to the response. * * \note This function does not check if DNSSEC is enabled, nor if it is * requested by the query. * * \param zone Zone used for answering. * \param closest_encloser Closest encloser of \a qname. * \param qname Domain name which generated the NXDOMAIN error (i.e. not found * in the zone. * \param resp Response where to put the NSEC3s. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_put_nsec3_nxdomain(const knot_zone_contents_t *zone, const knot_node_t *closest_encloser, const knot_dname_t *qname, knot_packet_t *resp) { // 1) Closest encloser proof dbg_ns("Putting closest encloser proof.\n"); int ret = ns_put_nsec3_closest_encloser_proof(zone, &closest_encloser, qname, resp); // 2) NSEC3 covering non-existent wildcard if (ret == KNOT_EOK && closest_encloser != NULL) { dbg_ns("Putting NSEC3 for no wildcard child of closest " "encloser.\n"); ret = ns_put_nsec3_no_wildcard_child(zone, closest_encloser, resp); } return ret; } /*----------------------------------------------------------------------------*/ /*! * \brief Puts NSECs or NSEC3s for the NXDOMAIN error to the response. * * \note This function first checks if DNSSEC is enabled and requested by the * query. * \note Note that for each zone there are either NSEC or NSEC3 records used. * * \param zone Zone used for answering. * \param previous Previous node to \a qname in the zone. May also be NULL. In * such case the function finds the previous node in the zone. * \param closest_encloser Closest encloser of \a qname. Must not be NULL. * \param qname QNAME which generated the NXDOMAIN error (i.e. not found in the * zone). * \param resp Response where to put the NSECs. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_put_nsec_nsec3_nxdomain(const knot_zone_contents_t *zone, const knot_node_t *previous, const knot_node_t *closest_encloser, const knot_dname_t *qname, knot_packet_t *resp) { int ret = 0; if (DNSSEC_ENABLED && knot_query_dnssec_requested(knot_packet_query(resp))) { if (knot_zone_contents_nsec3_enabled(zone)) { ret = ns_put_nsec3_nxdomain(zone, closest_encloser, qname, resp); } else { ret = ns_put_nsec_nxdomain(qname, zone, previous, closest_encloser, resp); } } return ret; } /*----------------------------------------------------------------------------*/ /*! * \brief Puts NSEC3s for wildcard answer into the response. * * \note This function does not check if DNSSEC is enabled, nor if it is * requested by the query. * * \param zone Zone used for answering. * \param closest_encloser Closest encloser of \a qname in the zone. In this * case it is the parent of the source of synthesis. * \param qname Domain name covered by the wildcard used for answering the * query. * \param resp Response to put the NSEC3s into. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_put_nsec3_wildcard(const knot_zone_contents_t *zone, const knot_node_t *closest_encloser, const knot_dname_t *qname, knot_packet_t *resp) { assert(closest_encloser != NULL); assert(qname != NULL); assert(resp != NULL); assert(DNSSEC_ENABLED && knot_query_dnssec_requested(knot_packet_query(resp))); if (!knot_zone_contents_nsec3_enabled(zone)) { return KNOT_EOK; } /* * NSEC3 that covers the "next closer" name. */ // create the "next closer" name by appending from qname dbg_ns("Finding next closer name for wildcard NSEC3.\n"); knot_dname_t *next_closer = ns_next_closer(closest_encloser->owner, qname); if (next_closer == NULL) { return NS_ERR_SERVFAIL; } dbg_ns_exec( char *name = knot_dname_to_str(next_closer); dbg_ns("Next closer name: %s\n", name); free(name); ); int ret = ns_put_covering_nsec3(zone, next_closer, resp); /* Duplicate from ns_next_close(), safe to discard. */ knot_dname_release(next_closer); return ret; } /*----------------------------------------------------------------------------*/ /*! * \brief Puts NSECs for wildcard answer into the response. * * \note This function does not check if DNSSEC is enabled, nor if it is * requested by the query. * * \param zone Zone used for answering. * \param qname Domain name covered by the wildcard used for answering the * query. * \param previous Previous node of \a qname in canonical order. * \param resp Response to put the NSEC3s into. */ static void ns_put_nsec_wildcard(const knot_zone_contents_t *zone, const knot_dname_t *qname, const knot_node_t *previous, knot_packet_t *resp) { assert(DNSSEC_ENABLED && knot_query_dnssec_requested(knot_packet_query(resp))); // check if we have previous; if not, find one using the tree if (previous == NULL) { previous = knot_zone_contents_find_previous(zone, qname); while (!knot_node_is_auth(previous)) { previous = knot_node_previous(previous, 1); } previous = knot_node_current(previous); assert(previous != NULL); } const knot_rrset_t *rrset = knot_node_rrset(previous, KNOT_RRTYPE_NSEC); if (rrset != NULL) { // NSEC proving that there is no node with the searched name knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); rrset = knot_rrset_rrsigs(rrset); assert(rrset != NULL); knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); } } /*----------------------------------------------------------------------------*/ /*! * \brief Puts NSECs or NSEC3s for wildcard NODATA answer into the response. * * \note This function first checks if DNSSEC is enabled and requested by the * query. * * \param node Node used for answering. * \param closest_encloser Closest encloser of \a qname in the zone. * \param previous Previous node of \a qname in canonical order. * \param zone Zone used for answering. * \param qname Actual searched domain name. * \param resp Response where to put the NSECs and NSEC3s. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_put_nsec_nsec3_wildcard_nodata(const knot_node_t *node, const knot_node_t *closest_encloser, const knot_node_t *previous, const knot_zone_contents_t *zone, const knot_dname_t *qname, knot_packet_t *resp) { int ret = KNOT_EOK; if (DNSSEC_ENABLED && knot_query_dnssec_requested(knot_packet_query(resp))) { if (knot_zone_contents_nsec3_enabled(zone)) { ret = ns_put_nsec3_closest_encloser_proof(zone, &closest_encloser, qname, resp); const knot_node_t *nsec3_node; if (ret == KNOT_EOK && (nsec3_node = knot_node_nsec3_node(node, 1)) != NULL) { ns_put_nsec3_from_node(nsec3_node, resp); } } else { ns_put_nsec_wildcard(zone, qname, previous, resp); } } return ret; } /*----------------------------------------------------------------------------*/ /*! * \brief Puts NSECs or NSEC3s for wildcard answer into the response. * * \note This function first checks if DNSSEC is enabled and requested by the * query and if the node's owner is a wildcard. * * \param node Node used for answering. * \param closest_encloser Closest encloser of \a qname in the zone. * \param previous Previous node of \a qname in canonical order. * \param zone Zone used for answering. * \param qname Actual searched domain name. * \param resp Response where to put the NSECs and NSEC3s. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_put_nsec_nsec3_wildcard_answer(const knot_node_t *node, const knot_node_t *closest_encloser, const knot_node_t *previous, const knot_zone_contents_t *zone, const knot_dname_t *qname, knot_packet_t *resp) { int r = KNOT_EOK; if (DNSSEC_ENABLED && knot_query_dnssec_requested(knot_packet_query(resp)) && knot_dname_is_wildcard(knot_node_owner(node))) { if (knot_zone_contents_nsec3_enabled(zone)) { r = ns_put_nsec3_wildcard(zone, closest_encloser, qname, resp); } else { ns_put_nsec_wildcard(zone, qname, previous, resp); } } return r; } /*----------------------------------------------------------------------------*/ /*! * \brief Creates a referral response. * * This function puts the delegation NS RRSet to the Authority section of the * response, possibly adds DS and their associated RRSIGs (if DNSSEC is enabled * and requested by the query) and adds any available additional data (A and * AAAA RRSets for the names in the NS RRs) with their associated RRSIGs * to the Additional section. * * \param node Delegation point node. * \param zone Parent zone (the one from which the response is generated). * \param qname Searched name (which caused the referral). * \param resp Response. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static inline int ns_referral(const knot_node_t *node, const knot_zone_contents_t *zone, const knot_dname_t *qname, knot_packet_t *resp) { dbg_ns("Referral response.\n"); while (!knot_node_is_deleg_point(node)) { assert(knot_node_parent(node, 1) != NULL); node = knot_node_parent(node, 1); } const knot_rrset_t *rrset = knot_node_rrset(node, KNOT_RRTYPE_NS); assert(rrset != NULL); // TODO: wildcards?? //ns_check_wildcard(name, resp, &rrset); knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); ns_add_rrsigs(rrset, resp, node->owner, knot_response_add_rrset_authority, 1); int ret = KNOT_EOK; // add DS records dbg_ns("DNSSEC requested: %d\n", knot_query_dnssec_requested(knot_packet_query(resp))); dbg_ns("DS records: %p\n", knot_node_rrset(node, KNOT_RRTYPE_DS)); if (DNSSEC_ENABLED && knot_query_dnssec_requested(knot_packet_query(resp))) { rrset = knot_node_rrset(node, KNOT_RRTYPE_DS); if (rrset != NULL) { knot_response_add_rrset_authority(resp, rrset, 1, 0, 0); ns_add_rrsigs(rrset, resp, node->owner, knot_response_add_rrset_authority, 1); } else { // no DS, add NSEC3 or NSEC // if NSEC3 enabled, search for NSEC3 if (knot_zone_contents_nsec3_enabled(zone)) { const knot_node_t *nsec3_node = knot_node_nsec3_node(node, 1); dbg_ns("There is no DS, putting NSEC3s...\n"); if (nsec3_node != NULL) { dbg_ns("Putting NSEC3s from the node.\n"); ns_put_nsec3_from_node(nsec3_node, resp); } else { dbg_ns("Putting Opt-Out NSEC3s.\n"); // no NSEC3 (probably Opt-Out) // TODO: check if the zone is Opt-Out ret = ns_put_nsec3_closest_encloser_proof(zone, &node, qname, resp); } } else { const knot_rrset_t *nsec = knot_node_rrset( node, KNOT_RRTYPE_NSEC); if (nsec) { /*! \todo Check return value? */ knot_response_add_rrset_authority( resp, nsec, 1, 1, 0); if ((nsec = knot_rrset_rrsigs(nsec)) != NULL) { knot_response_add_rrset_authority(resp, nsec, 1, 1, 0); } } } } } if (ret == KNOT_EOK) { ns_put_additional(resp); knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); } return ret; } /*----------------------------------------------------------------------------*/ /*! * \brief Tries to answer the query from the given node. * * Tries to put RRSets of requested type (\a qtype) to the Answer section of the * response. If successful, it also adds authority NS RRSet to the Authority * section and it may add NSEC or NSEC3s in case of a wildcard answer (\a node * is a wildcard node). If not successful (there are no such RRSets), it adds * the SOA record to the Authority section and may add NSEC or NSEC3s according * to the type of the response (NXDOMAIN if \a node is an empty non-terminal, * NODATA if it is a regular node). It also adds any additional data that may * be required. * * \param node Node to answer from. * \param closest_encloser Closest encloser of \a qname in the zone. * \param previous Previous domain name of \a qname in canonical order. * \param zone Zone used for answering. * \param qname Searched domain name. * \param qtype Searched RR type. * \param resp Response. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_answer_from_node(const knot_node_t *node, const knot_node_t *closest_encloser, const knot_node_t *previous, const knot_zone_contents_t *zone, const knot_dname_t *qname, uint16_t qtype, knot_packet_t *resp) { dbg_ns("Putting answers from found node to the response...\n"); int answers = ns_put_answer(node, qname, qtype, resp); int ret = KNOT_EOK; if (answers == 0) { // if NODATA response, put SOA if (knot_node_rrset_count(node) == 0 && !knot_zone_contents_nsec3_enabled(zone)) { // node is an empty non-terminal => NSEC for NXDOMAIN //assert(knot_node_rrset_count(closest_encloser) > 0); dbg_ns("Adding NSEC/NSEC3 for NXDOMAIN.\n"); ret = ns_put_nsec_nsec3_nxdomain(zone, knot_node_previous(node, 1), closest_encloser, qname, resp); } else { dbg_ns("Adding NSEC/NSEC3 for NODATA.\n"); ns_put_nsec_nsec3_nodata(node, resp); if (knot_dname_is_wildcard(node->owner)) { dbg_ns("Putting NSEC/NSEC3 for wildcard" " NODATA\n"); ret = ns_put_nsec_nsec3_wildcard_nodata(node, closest_encloser, previous, zone, qname, resp); } } ns_put_authority_soa(zone, resp); } else { // else put authority NS // if wildcard answer, add NSEC / NSEC3 dbg_ns("Adding NSEC/NSEC3 for wildcard answer.\n"); ret = ns_put_nsec_nsec3_wildcard_answer(node, closest_encloser, previous, zone, qname, resp); ns_put_authority_ns(zone, resp); } if (ret == KNOT_EOK) { ns_put_additional(resp); } return ret; } /*----------------------------------------------------------------------------*/ /*! * \brief Synthetizes a CNAME RR from a DNAME. * * \param dname_rrset DNAME RRSet to synthetize from (only the first RR is * used). * \param qname Name to be used as the owner name of the synthetized CNAME. * * \return Synthetized CNAME RRset (this is a newly created RRSet, remember to * free it). */ static knot_rrset_t *ns_cname_from_dname(const knot_rrset_t *dname_rrset, const knot_dname_t *qname) { dbg_ns("Synthetizing CNAME from DNAME...\n"); // create new CNAME RRSet knot_dname_t *owner = knot_dname_deep_copy(qname); if (owner == NULL) { return NULL; } knot_rrset_t *cname_rrset = knot_rrset_new( owner, KNOT_RRTYPE_CNAME, KNOT_CLASS_IN, SYNTH_CNAME_TTL); /* Release owner, as it's retained in rrset. */ knot_dname_release(owner); if (cname_rrset == NULL) { return NULL; } // replace last labels of qname with DNAME knot_dname_t *cname = knot_dname_replace_suffix(qname, knot_dname_size(knot_rrset_owner(dname_rrset)), knot_rdata_get_item(knot_rrset_rdata(dname_rrset), 0)->dname); dbg_ns_exec( char *name = knot_dname_to_str(cname); dbg_ns("CNAME canonical name: %s.\n", name); free(name); ); knot_rdata_t *cname_rdata = knot_rdata_new(); knot_rdata_item_t cname_rdata_item; cname_rdata_item.dname = cname; knot_rdata_set_items(cname_rdata, &cname_rdata_item, 1); knot_rrset_add_rdata(cname_rrset, cname_rdata); return cname_rrset; } /*----------------------------------------------------------------------------*/ /*! * \brief Checks if the name created by replacing the owner of \a dname_rrset * in the \a qname by the DNAME's target would be longer than allowed. * * \param dname_rrset DNAME RRSet to be used for the check. * \param qname Name whose part is to be replaced. * * \retval <>0 if the created domain name would be too long. * \retval 0 otherwise. */ static int ns_dname_is_too_long(const knot_rrset_t *dname_rrset, const knot_dname_t *qname) { // TODO: add function for getting DNAME target if (knot_dname_label_count(qname) - knot_dname_label_count(knot_rrset_owner(dname_rrset)) + knot_dname_label_count(knot_rdata_get_item( knot_rrset_rdata(dname_rrset), 0)->dname) > KNOT_MAX_DNAME_LENGTH) { return 1; } else { return 0; } } /*----------------------------------------------------------------------------*/ /*! * \brief DNAME processing. * * This function adds the DNAME RRSet (and possibly its associated RRSIGs to the * Answer section of the response, synthetizes CNAME record from the DNAME and * adds it there too. It also stores the synthetized CNAME in the temporary * RRSets of the response. * * \param dname_rrset DNAME RRSet to use. * \param qname Searched name. * \param resp Response. */ static void ns_process_dname(const knot_rrset_t *dname_rrset, const knot_dname_t *qname, knot_packet_t *resp) { dbg_ns_exec( char *name = knot_dname_to_str(knot_rrset_owner(dname_rrset)); dbg_ns("Processing DNAME for owner %s...\n", name); free(name); ); // TODO: check the number of RRs in the RRSet?? // put the DNAME RRSet into the answer knot_response_add_rrset_answer(resp, dname_rrset, 1, 0, 0); ns_add_rrsigs(dname_rrset, resp, qname, knot_response_add_rrset_answer, 1); if (ns_dname_is_too_long(dname_rrset, qname)) { knot_response_set_rcode(resp, KNOT_RCODE_YXDOMAIN); return; } // synthetize CNAME (no way to tell that client supports DNAME) knot_rrset_t *synth_cname = ns_cname_from_dname(dname_rrset, qname); // add the synthetized RRSet to the Answer knot_response_add_rrset_answer(resp, synth_cname, 1, 0, 0); // no RRSIGs for this RRSet // add the synthetized RRSet into list of temporary RRSets of response knot_packet_add_tmp_rrset(resp, synth_cname); // do not search for the name in new zone (out-of-bailiwick) } /*----------------------------------------------------------------------------*/ /*! * \brief Adds DNSKEY RRSet from the apex of a zone to the response. * * \param apex Zone apex node. * \param resp Response. */ static void ns_add_dnskey(const knot_node_t *apex, knot_packet_t *resp) { const knot_rrset_t *rrset = knot_node_rrset(apex, KNOT_RRTYPE_DNSKEY); if (rrset != NULL) { knot_response_add_rrset_additional(resp, rrset, 0, 0, 0); ns_add_rrsigs(rrset, resp, apex->owner, knot_response_add_rrset_additional, 0); } } /*----------------------------------------------------------------------------*/ /*! * \brief Answers the query from the given zone. * * This function performs the actual answering logic. * * \param zone Zone to use for answering. * \param qname QNAME from the query. * \param qtype QTYPE from the query. * \param resp Response to fill in. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL * * \todo Describe the answering logic in detail. */ static int ns_answer_from_zone(const knot_zone_contents_t *zone, const knot_dname_t *qname, uint16_t qtype, knot_packet_t *resp) { const knot_node_t *node = NULL, *closest_encloser = NULL, *previous = NULL; int cname = 0, auth_soa = 0, ret = 0, find_ret = 0; search: #ifdef USE_HASH_TABLE /*! \todo Check version. */ find_ret = knot_zone_contents_find_dname_hash(zone, qname, &node, &closest_encloser); // node = knot_node_current(node); // closest_encloser = knot_node_current(closest_encloser); #else /*! \todo Check version. */ find_ret = knot_zone_contents_find_dname(zone, qname, &node, &closest_encloser, &previous); node = knot_node_current(node); closest_encloser = knot_node_current(closest_encloser); previous = knot_node_current(previous); #endif if (find_ret == KNOT_EBADARG) { return NS_ERR_SERVFAIL; } dbg_ns_exec( char *name; if (node) { name = knot_dname_to_str(node->owner); dbg_ns("zone_find_dname() returned node %s ", name); free(name); } else { dbg_ns("zone_find_dname() returned no node,"); } if (closest_encloser != NULL) { name = knot_dname_to_str(closest_encloser->owner); dbg_ns(" closest encloser %s.\n", name); free(name); } else { dbg_ns(" closest encloser (nil).\n"); } if (previous != NULL) { name = knot_dname_to_str(previous->owner); dbg_ns(" and previous node: %s.\n", name); free(name); } else { dbg_ns(" and previous node: (nil).\n"); } ); if (find_ret == KNOT_EBADZONE) { // possible only if we followed cname assert(cname != 0); knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); auth_soa = 1; knot_response_set_aa(resp); goto finalize; } have_node: dbg_ns("Closest encloser is deleg. point? %s\n", (knot_node_is_deleg_point(closest_encloser)) ? "yes" : "no"); dbg_ns("Closest encloser is non authoritative? %s\n", (knot_node_is_non_auth(closest_encloser)) ? "yes" : "no"); if (knot_node_is_deleg_point(closest_encloser) || knot_node_is_non_auth(closest_encloser)) { ret = ns_referral(closest_encloser, zone, qname, resp); goto finalize; } if (find_ret == KNOT_ZONE_NAME_NOT_FOUND) { // DNAME? const knot_rrset_t *dname_rrset = knot_node_rrset( closest_encloser, KNOT_RRTYPE_DNAME); if (dname_rrset != NULL) { ns_process_dname(dname_rrset, qname, resp); auth_soa = 1; knot_response_set_aa(resp); goto finalize; } // else check for a wildcard child const knot_node_t *wildcard_node = knot_node_wildcard_child(closest_encloser, 1); if (wildcard_node == NULL) { dbg_ns("No wildcard node. (cname: %d)\n", cname); auth_soa = 1; if (cname == 0) { dbg_ns("Setting NXDOMAIN RCODE.\n"); // return NXDOMAIN knot_response_set_rcode(resp, KNOT_RCODE_NXDOMAIN); if (ns_put_nsec_nsec3_nxdomain(zone, previous, closest_encloser, qname, resp) != 0) { return NS_ERR_SERVFAIL; } } else { knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); } knot_response_set_aa(resp); goto finalize; } // else set the node from which to take the answers to wild.node node = wildcard_node; } // now we have the node for answering if (knot_node_is_deleg_point(node) || knot_node_is_non_auth(node)) { ret = ns_referral(node, zone, qname, resp); goto finalize; } if (knot_node_rrset(node, KNOT_RRTYPE_CNAME) != NULL) { dbg_ns_exec( char *name = knot_dname_to_str(node->owner); dbg_ns("Node %s has CNAME record, resolving...\n", name); free(name); ); const knot_dname_t *act_name = qname; ns_follow_cname(&node, &act_name, resp, knot_response_add_rrset_answer, 1); dbg_ns_exec( char *name = (node != NULL) ? knot_dname_to_str(node->owner) : "(nil)"; char *name2 = knot_dname_to_str(act_name); dbg_ns("Canonical name: %s (%p), node found: %p\n", name2, act_name, node); dbg_ns("The node's owner: %s (%p)\n", name, (node != NULL) ? node->owner : NULL); if (node != NULL) { free(name); } free(name2); ); qname = act_name; cname = 1; // otherwise search for the new name if (node == NULL) { goto search; } else if (node->owner != act_name) { // the stored node is closest encloser find_ret = KNOT_ZONE_NAME_NOT_FOUND; closest_encloser = node; node = NULL; goto have_node; } // else do nothing, just continue } ret = ns_answer_from_node(node, closest_encloser, previous, zone, qname, qtype, resp); if (ret == NS_ERR_SERVFAIL) { // in this case we should drop the response and send an error // for now, just send the error code with a non-complete answer // knot_response_set_rcode(resp, KNOT_RCODE_SERVFAIL); // goto finalize; return ret; } else if (ret != KNOT_EOK) { /*! \todo Handle RCODE return values!!! */ goto finalize; } knot_response_set_aa(resp); knot_response_set_rcode(resp, KNOT_RCODE_NOERROR); // this is the only case when the servers answers from // particular node, i.e. the only case when it may return SOA // or NS records in Answer section if (DNSSEC_ENABLED && knot_query_dnssec_requested(knot_packet_query(resp)) && node == knot_zone_contents_apex(zone) && (qtype == KNOT_RRTYPE_SOA || qtype == KNOT_RRTYPE_NS)) { ns_add_dnskey(node, resp); } finalize: if (ret == KNOT_EOK && auth_soa) { ns_put_authority_soa(zone, resp); } return ret; } /*----------------------------------------------------------------------------*/ /*! * \brief Answers the query from the given zone database. * * First it searches for a zone to answer from. If there is none, it sets * RCODE REFUSED to the response and ends. Otherwise it tries to answer the * query using the found zone (see ns_answer_from_zone()). * * \param db Zone database to use for answering. * \param resp Response that holds the parsed query. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_answer(knot_zonedb_t *db, knot_packet_t *resp) { const knot_dname_t *qname = knot_packet_qname(resp); assert(qname != NULL); uint16_t qtype = knot_packet_qtype(resp); dbg_ns_exec( char *name_str = knot_dname_to_str(qname); dbg_ns("Trying to find zone for QNAME %s\n", name_str); free(name_str); ); // find zone in which to search for the name const knot_zone_t *zone = ns_get_zone_for_qname(db, qname, qtype); const knot_zone_contents_t *contents = knot_zone_contents(zone); // if no zone found, return REFUSED if (zone == NULL) { dbg_ns("No zone found.\n"); knot_response_set_rcode(resp, KNOT_RCODE_REFUSED); //knot_dname_free(&qname); return KNOT_EOK; } else if (contents == NULL) { dbg_ns("Zone expired or not bootstrapped. Reply SERVFAIL.\n"); knot_response_set_rcode(resp, KNOT_RCODE_SERVFAIL); return KNOT_EOK; } dbg_ns_exec( char *name_str2 = knot_dname_to_str(zone->contents->apex->owner); dbg_ns("Found zone for QNAME %s\n", name_str2); free(name_str2); ); // take the zone contents and use only them for answering return ns_answer_from_zone(contents, qname, qtype, resp); //knot_dname_free(&qname); } /*----------------------------------------------------------------------------*/ /*! * \brief Converts the response to wire format. * * \param resp Response to convert. * \param wire Place for the wire format of the response. * \param wire_size In: space available for the wire format in bytes. * Out: actual size of the wire format in bytes. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_response_to_wire(knot_packet_t *resp, uint8_t *wire, size_t *wire_size) { uint8_t *rwire = NULL; size_t rsize = 0; int ret = 0; if ((ret = knot_packet_to_wire(resp, &rwire, &rsize)) != KNOT_EOK) { dbg_ns("Error converting response packet " "to wire format (error %d).\n", ret); return NS_ERR_SERVFAIL; } if (rsize > *wire_size) { dbg_ns("Reponse size (%zu) larger than allowed wire size " "(%zu).\n", rsize, *wire_size); return NS_ERR_SERVFAIL; } if (rwire != wire) { dbg_ns("Wire format reallocated, copying to place for " "wire.\n"); memcpy(wire, rwire, rsize); } else { dbg_ns("Using the same space or wire format.\n"); } *wire_size = rsize; //free(rwire); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ /*! * \brief Creates a wire format of an error response from partially created * response. * * \param resp Response to use. * \param wire Place for the wire format of the response. * \param wire_size In: space available for the wire format in bytes. * Out: actual size of the wire format in bytes. * * \retval KNOT_EOK * \retval NS_ERR_SERVFAIL */ static int ns_error_response_to_wire(knot_packet_t *resp, uint8_t *wire, size_t *wire_size) { /* Do not call the packet conversion function * wire format is assembled, but COUNTs in header are not set. * This is ideal, we just truncate the packet after the question. */ dbg_ns("Creating error response.\n"); size_t rsize = knot_packet_question_size(knot_packet_query(resp)); dbg_ns("Error response (~ query) size: %zu\n", rsize); // take 'qsize' from the current wireformat of the response // it is already assembled - Header and Question section are copied const uint8_t *rwire = knot_packet_wireformat(resp); if (rsize > *wire_size) { dbg_ns("Reponse size (%zu) larger than allowed wire size" " (%zu).\n", rsize, *wire_size); return NS_ERR_SERVFAIL; } assert(rwire != wire); /*! \todo Why is this copied?? Why we cannot use resp->wireformat?? */ memcpy(wire, rwire, rsize); *wire_size = rsize; return KNOT_EOK; } /*----------------------------------------------------------------------------*/ typedef struct ns_axfr_params { knot_ns_xfr_t *xfr; int ret; } ns_axfr_params_t; /*----------------------------------------------------------------------------*/ int knot_ns_tsig_required(int packet_nr) { dbg_ns_detail("ns_tsig_required(%d): %d\n", packet_nr, (packet_nr % KNOT_NS_TSIG_FREQ == 0)); return (packet_nr % KNOT_NS_TSIG_FREQ == 0); } /*----------------------------------------------------------------------------*/ static int ns_xfr_send_and_clear(knot_ns_xfr_t *xfr, int add_tsig) { assert(xfr != NULL); assert(xfr->query != NULL); assert(xfr->response != NULL); assert(xfr->wire != NULL); assert(xfr->send != NULL); // Transform the packet into wire format dbg_ns("Converting response to wire format..\n"); size_t real_size = xfr->wire_size; if (ns_response_to_wire(xfr->response, xfr->wire, &real_size) != 0) { return NS_ERR_SERVFAIL; // // send back SERVFAIL (as this is our problem) // ns_error_response(nameserver, // knot_wire_get_id(query_wire), // KNOT_RCODE_SERVFAIL, response_wire, // rsize); } int res = 0; size_t digest_real_size = xfr->digest_max_size; dbg_ns_detail("xfr->tsig_key=%p\n", xfr->tsig_key); /*! \note [TSIG] Generate TSIG if required (during XFR/IN). */ if (xfr->tsig_key && add_tsig) { if (xfr->packet_nr == 0) { /* Add key, digest and digest length. */ dbg_ns_detail("Calling tsig_sign(): %p, %zu, %zu, " "%p, %zu, %p, %zu, %p\n", xfr->wire, real_size, xfr->wire_size, xfr->digest, xfr->digest_size, xfr->digest, digest_real_size, xfr->tsig_key); res = knot_tsig_sign(xfr->wire, &real_size, xfr->wire_size, xfr->digest, xfr->digest_size, xfr->digest, &digest_real_size, xfr->tsig_key); } else { /* Add key, digest and digest length. */ dbg_ns_detail("Calling tsig_sign_next()\n"); res = knot_tsig_sign_next(xfr->wire, &real_size, xfr->wire_size, xfr->digest, xfr->digest_size, xfr->digest, &digest_real_size, xfr->tsig_key); } dbg_ns_detail("Sign function returned: %s\n", knot_strerror(res)); dbg_ns_detail("Real size of digest: %zu\n", digest_real_size); if (res != KNOT_EOK) { return res; } assert(digest_real_size > 0); // save the new previous digest size xfr->digest_size = digest_real_size; } // Send the response dbg_ns("Sending response (size %zu)..\n", real_size); //dbg_ns_hex((const char *)xfr->wire, real_size); res = xfr->send(xfr->session, &xfr->addr, xfr->wire, real_size); if (res < 0) { dbg_ns("Send returned %d\n", res); return res; } else if (res != real_size) { dbg_ns("AXFR did not send right amount of bytes." " Transfer size: %zu, sent: %d\n", real_size, res); } // Clean the response structure dbg_ns("Clearing response structure..\n"); knot_response_clear(xfr->response, 0); // increment the packet number ++xfr->packet_nr; if (xfr->tsig_key && add_tsig) { knot_packet_set_tsig_size(xfr->response, xfr->tsig_size); } else { knot_packet_set_tsig_size(xfr->response, 0); } dbg_ns("Response structure after clearing:\n"); knot_packet_dump(xfr->response); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ static void ns_axfr_from_node(knot_node_t *node, void *data) { assert(node != NULL); assert(data != NULL); ns_axfr_params_t *params = (ns_axfr_params_t *)data; if (params->ret != KNOT_EOK) { // just skip (will be called on next node with the same params dbg_ns("Params contain error: %s, skipping node...\n", knot_strerror(params->ret)); return; } dbg_ns("Params OK, answering AXFR from node %p.\n", node); dbg_ns_exec( char *name = knot_dname_to_str(knot_node_owner(node)); dbg_ns("Node ownerr: %s\n", name); free(name); ); if (knot_node_rrset_count(node) == 0) { return; } const knot_rrset_t **rrsets = knot_node_rrsets(node); if (rrsets == NULL) { params->ret = KNOT_ENOMEM; return; } int i = 0; int ret = 0; const knot_rrset_t *rrset = NULL; while (i < knot_node_rrset_count(node)) { assert(rrsets[i] != NULL); rrset = rrsets[i]; rrset: dbg_ns(" Type: %s\n", knot_rrtype_to_string(knot_rrset_type(rrset))); // do not add SOA if (knot_rrset_type(rrset) == KNOT_RRTYPE_SOA) { ++i; continue; } ret = knot_response_add_rrset_answer(params->xfr->response, rrset, 0, 0, 1); if (ret == KNOT_ESPACE) { // TODO: send the packet and clean the structure dbg_ns("Packet full, sending..\n"); ret = ns_xfr_send_and_clear(params->xfr, knot_ns_tsig_required(params->xfr->packet_nr)); if (ret != KNOT_EOK) { // some wierd problem, we should end params->ret = KNOT_ERROR; break; } // otherwise try once more with the same RRSet goto rrset; } else if (ret != KNOT_EOK) { // some wierd problem, we should end params->ret = KNOT_ERROR; break; } // we can send the RRSets in any order, so add the RRSIGs now rrset = knot_rrset_rrsigs(rrset); rrsigs: if (rrset == NULL) { ++i; continue; } ret = knot_response_add_rrset_answer(params->xfr->response, rrset, 0, 0, 1); if (ret == KNOT_ESPACE) { // TODO: send the packet and clean the structure dbg_ns("Packet full, sending..\n"); ret = ns_xfr_send_and_clear(params->xfr, knot_ns_tsig_required(params->xfr->packet_nr)); if (ret != KNOT_EOK) { // some wierd problem, we should end params->ret = KNOT_ERROR; break; } // otherwise try once more with the same RRSet goto rrsigs; } else if (ret != KNOT_EOK) { // some wierd problem, we should end params->ret = KNOT_ERROR; break; } // this way only whole RRSets are always sent // we guess it will not create too much overhead ++i; } if (rrsets != NULL) { free(rrsets); } /*! \todo maybe distinguish some error codes. */ //params->ret = (ret == 0) ? KNOT_EOK : KNOT_ERROR; } /*----------------------------------------------------------------------------*/ static int ns_axfr_from_zone(knot_zone_contents_t *zone, knot_ns_xfr_t *xfr) { assert(xfr != NULL); assert(xfr->query != NULL); assert(xfr->response != NULL); assert(xfr->wire != NULL); assert(xfr->send != NULL); ns_axfr_params_t params; memset(¶ms, 0, sizeof(ns_axfr_params_t)); params.xfr = xfr; params.ret = KNOT_EOK; xfr->packet_nr = 0; /* * First SOA */ // retrieve SOA - must be send as first and last RR const knot_rrset_t *soa_rrset = knot_node_rrset( knot_zone_contents_apex(zone), KNOT_RRTYPE_SOA); if (soa_rrset == NULL) { // some really serious error return KNOT_ERROR; } int ret; // add SOA RR to the response ret = knot_response_add_rrset_answer(xfr->response, soa_rrset, 0, 0, 1); if (ret != KNOT_EOK) { // something is really wrong return KNOT_ERROR; } // add the SOA's RRSIG const knot_rrset_t *rrset = knot_rrset_rrsigs(soa_rrset); if (rrset != NULL && (ret = knot_response_add_rrset_answer(xfr->response, rrset, 0, 0, 1)) != KNOT_EOK) { // something is really wrong, these should definitely fit in return KNOT_ERROR; } knot_zone_contents_tree_apply_inorder(zone, ns_axfr_from_node, ¶ms); if (params.ret != KNOT_EOK) { return KNOT_ERROR; // maybe do something with the code } knot_zone_contents_nsec3_apply_inorder(zone, ns_axfr_from_node, ¶ms); if (params.ret != KNOT_EOK) { return KNOT_ERROR; // maybe do something with the code } /* * Last SOA */ // try to add the SOA to the response again (last RR) ret = knot_response_add_rrset_answer(xfr->response, soa_rrset, 0, 0, 1); if (ret == KNOT_ESPACE) { // if there is not enough space, send the response and // add the SOA record to a new packet dbg_ns("Packet full, sending..\n"); ret = ns_xfr_send_and_clear(xfr, knot_ns_tsig_required(xfr->packet_nr)); if (ret != KNOT_EOK) { return ret; } ret = knot_response_add_rrset_answer(xfr->response, soa_rrset, 0, 0, 1); if (ret != KNOT_EOK) { return KNOT_ERROR; } } else if (ret != KNOT_EOK) { // something is really wrong return KNOT_ERROR; } dbg_ns("Sending packet...\n"); return ns_xfr_send_and_clear(xfr, 1); } /*----------------------------------------------------------------------------*/ static int ns_ixfr_put_rrset(knot_ns_xfr_t *xfr, const knot_rrset_t *rrset) { int res = knot_response_add_rrset_answer(xfr->response, rrset, 0, 0, 0); if (res == KNOT_ESPACE) { knot_response_set_rcode(xfr->response, KNOT_RCODE_NOERROR); /*! \todo Probably rename the function. */ ns_xfr_send_and_clear(xfr, knot_ns_tsig_required(xfr->packet_nr)); res = knot_response_add_rrset_answer(xfr->response, rrset, 0, 0, 0); } if (res != KNOT_EOK) { dbg_ns("Error putting origin SOA to IXFR reply: %s\n", knot_strerror(res)); /*! \todo Probably send back AXFR instead. */ knot_response_set_rcode(xfr->response, KNOT_RCODE_SERVFAIL); /*! \todo Probably rename the function. */ ns_xfr_send_and_clear(xfr, 1); //socket_close(xfr->session); /*! \todo Remove for UDP.*/ return KNOT_ERROR; } return KNOT_EOK; } /*----------------------------------------------------------------------------*/ static int ns_ixfr_put_changeset(knot_ns_xfr_t *xfr, const knot_changeset_t *chgset) { // 1) put origin SOA int res = ns_ixfr_put_rrset(xfr, chgset->soa_from); if (res != KNOT_EOK) { return res; } // 2) put remove RRSets for (int i = 0; i < chgset->remove_count; ++i) { res = ns_ixfr_put_rrset(xfr, chgset->remove[i]); if (res != KNOT_EOK) { return res; } } // 1) put target SOA res = ns_ixfr_put_rrset(xfr, chgset->soa_to); if (res != KNOT_EOK) { return res; } // 2) put remove RRSets for (int i = 0; i < chgset->add_count; ++i) { res = ns_ixfr_put_rrset(xfr, chgset->add[i]); if (res != KNOT_EOK) { return res; } } return KNOT_EOK; } /*----------------------------------------------------------------------------*/ static int ns_ixfr_from_zone(knot_ns_xfr_t *xfr) { assert(xfr != NULL); assert(xfr->zone != NULL); assert(xfr->query != NULL); assert(xfr->response != NULL); assert(knot_packet_authority_rrset_count(xfr->query) > 0); assert(xfr->data != NULL); /*! \todo REMOVE start */ // const knot_rrset_t *zone_soa = // knot_node_rrset(knot_zone_contents_apex( // knot_zone_contents(xfr->zone)), // KNOT_RRTYPE_SOA); // // retrieve origin (xfr) serial and target (zone) serial // uint32_t zone_serial = knot_rdata_soa_serial( // knot_rrset_rdata(zone_soa)); // uint32_t xfr_serial = knot_rdata_soa_serial(knot_rrset_rdata( // knot_packet_authority_rrset(xfr->query, 0))); // // 3) load changesets from journal // knot_changesets_t *chgsets = (knot_changesets_t *) // calloc(1, sizeof(knot_changesets_t)); // int res = xfr_load_changesets(xfr->zone, chgsets, xfr_serial, // zone_serial); // if (res != KNOT_EOK) { // dbg_ns("IXFR query cannot be answered: %s.\n", // knot_strerror(res)); // /*! \todo Probably send back AXFR instead. */ // knot_response_set_rcode(xfr->response, KNOT_RCODE_SERVFAIL); // /*! \todo Probably rename the function. */ // ns_axfr_send_and_clear(xfr); // //socket_close(xfr->session); /*! \todo Remove for UDP. */ // return 1; // } /*! \todo REMOVE end */ knot_changesets_t *chgsets = (knot_changesets_t *)xfr->data; knot_zone_contents_t* contents = knot_zone_get_contents(xfr->zone); assert(contents); const knot_rrset_t *zone_soa = knot_node_rrset(knot_zone_contents_apex(contents), KNOT_RRTYPE_SOA); // 4) put the zone SOA as the first Answer RR int res = knot_response_add_rrset_answer(xfr->response, zone_soa, 0, 0, 0); if (res != KNOT_EOK) { dbg_ns("IXFR query cannot be answered: %s.\n", knot_strerror(res)); knot_response_set_rcode(xfr->response, KNOT_RCODE_SERVFAIL); /*! \todo Probably rename the function. */ ns_xfr_send_and_clear(xfr, 1); // socket_close(xfr->session); /*! \todo Remove for UDP.*/ return 1; } // 5) put the changesets into the response while they fit in for (int i = 0; i < chgsets->count; ++i) { res = ns_ixfr_put_changeset(xfr, &chgsets->sets[i]); if (res != KNOT_EOK) { // answer is sent, socket is closed return KNOT_EOK; } } if (chgsets->count > 0) { res = ns_ixfr_put_rrset(xfr, zone_soa); } if (res == KNOT_EOK) { /*! \todo Probably rename the function. */ ns_xfr_send_and_clear(xfr, 1); //socket_close(xfr->session); /*! \todo Remove for UDP.*/ return 1; } return KNOT_EOK; } /*----------------------------------------------------------------------------*/ static int ns_ixfr(knot_ns_xfr_t *xfr) { assert(xfr != NULL); assert(xfr->query != NULL); assert(xfr->response != NULL); assert(knot_packet_qtype(xfr->response) == KNOT_RRTYPE_IXFR); // check if there is the required authority record if ((knot_packet_authority_rrset_count(xfr->query) <= 0)) { // malformed packet dbg_ns("IXFR query does not contain authority record.\n"); knot_response_set_rcode(xfr->response, KNOT_RCODE_FORMERR); /*! \todo Probably rename the function. */ ns_xfr_send_and_clear(xfr, 1); //socket_close(xfr->session); return 1; } const knot_rrset_t *soa = knot_packet_authority_rrset(xfr->query, 0); const knot_dname_t *qname = knot_packet_qname(xfr->response); // check if XFR QNAME and SOA correspond if (knot_packet_qtype(xfr->query) != KNOT_RRTYPE_IXFR || knot_rrset_type(soa) != KNOT_RRTYPE_SOA || knot_dname_compare(qname, knot_rrset_owner(soa)) != 0) { // malformed packet dbg_ns("IXFR query is malformed.\n"); knot_response_set_rcode(xfr->response, KNOT_RCODE_FORMERR); /*! \todo Probably rename the function. */ ns_xfr_send_and_clear(xfr, 1); //socket_close(xfr->session); /*! \todo Remove for UDP. */ return 1; } return ns_ixfr_from_zone(xfr); } /*----------------------------------------------------------------------------*/ static int knot_ns_prepare_response(knot_nameserver_t *nameserver, knot_packet_t *query, knot_packet_t **resp, size_t max_size) { assert(max_size >= 500); // initialize response packet structure *resp = knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE); if (*resp == NULL) { dbg_ns("Failed to create packet structure.\n"); return KNOT_ENOMEM; } int ret = knot_packet_set_max_size(*resp, max_size); //(*resp)->wireformat = response_wire;; //(*resp)->max_size = max_size; if (ret != KNOT_EOK) { dbg_ns("Failed to init response structure.\n"); knot_packet_free(resp); return ret; } ret = knot_response_init_from_query(*resp, query); if (ret != KNOT_EOK) { dbg_ns("Failed to init response structure.\n"); knot_packet_free(resp); return ret; } return KNOT_EOK; } /*----------------------------------------------------------------------------*/ static int32_t ns_serial_difference(uint32_t s1, uint32_t s2) { return (((int64_t)s1 - s2) % ((int64_t)1 << 32)); } /*----------------------------------------------------------------------------*/ /* Public functions */ /*----------------------------------------------------------------------------*/ knot_nameserver_t *knot_ns_create() { knot_nameserver_t *ns = malloc(sizeof(knot_nameserver_t)); if (ns == NULL) { ERR_ALLOC_FAILED; return NULL; } ns->data = 0; // Create zone database structure dbg_ns("Creating Zone Database structure...\n"); ns->zone_db = knot_zonedb_new(); if (ns->zone_db == NULL) { ERR_ALLOC_FAILED; free(ns); return NULL; } // prepare empty response with SERVFAIL error knot_packet_t *err = knot_packet_new(KNOT_PACKET_PREALLOC_NONE); if (err == NULL) { ERR_ALLOC_FAILED; free(ns); return NULL; } dbg_ns("Created default empty response...\n"); int rc = knot_packet_set_max_size(err, KNOT_WIRE_HEADER_SIZE); if (rc != KNOT_EOK) { dbg_ns("Error creating default error response: %s.\n", knot_strerror(rc)); free(ns); knot_packet_free(&err); return NULL; } rc = knot_response_init(err); if (rc != KNOT_EOK) { dbg_ns("Error initializing default error response:" " %s.\n", knot_strerror(rc)); free(ns); knot_packet_free(&err); return NULL; } knot_response_set_rcode(err, KNOT_RCODE_SERVFAIL); ns->err_resp_size = 0; dbg_ns("Converting default empty response to wire format...\n"); uint8_t *error_wire = NULL; if (knot_packet_to_wire(err, &error_wire, &ns->err_resp_size) != 0) { dbg_ns("Error while converting " "default error response to " "wire format \n"); knot_packet_free(&err); free(ns); return NULL; } ns->err_response = (uint8_t *)malloc(ns->err_resp_size); if (ns->err_response == NULL) { dbg_ns("Error while converting default " "error response to wire format \n"); knot_packet_free(&err); free(ns); return NULL; } memcpy(ns->err_response, error_wire, ns->err_resp_size); dbg_ns("Done..\n"); knot_packet_free(&err); if (EDNS_ENABLED) { ns->opt_rr = knot_edns_new(); if (ns->opt_rr == NULL) { dbg_ns("Error while preparing OPT RR of the" " server.\n"); knot_packet_free(&err); free(ns); return NULL; } knot_edns_set_version(ns->opt_rr, EDNS_VERSION); knot_edns_set_payload(ns->opt_rr, MAX_UDP_PAYLOAD_EDNS); } else { ns->opt_rr = NULL; } knot_packet_free(&err); return ns; } /*----------------------------------------------------------------------------*/ int knot_ns_parse_packet(const uint8_t *query_wire, size_t qsize, knot_packet_t *packet, knot_packet_type_t *type) { if (packet == NULL || query_wire == NULL || type == NULL) { dbg_ns("Missing parameter to query parsing.\n"); return KNOT_EBADARG; } dbg_ns("ns_parse_packet() called with query size %zu.\n", qsize); //dbg_ns_hex((char *)query_wire, qsize); if (qsize < 2) { return KNOT_EMALF; } // 1) create empty response dbg_ns("Parsing packet...\n"); //parsed = knot_response_new_empty(NULL); int ret = 0; if ((ret = knot_packet_parse_from_wire(packet, query_wire, qsize, 1)) != 0) { dbg_ns("Error while parsing packet, " "libknot error '%s'.\n", knot_strerror(ret)); // knot_response_free(&parsed); return KNOT_RCODE_FORMERR; } dbg_ns("Parsed packet header and Question:\n"); knot_packet_dump(packet); // 3) determine the query type switch (knot_packet_opcode(packet)) { case KNOT_OPCODE_QUERY: switch (knot_packet_qtype(packet)) { case KNOT_RRTYPE_AXFR: *type = (knot_packet_is_query(packet)) ? KNOT_QUERY_AXFR : KNOT_RESPONSE_AXFR; break; case KNOT_RRTYPE_IXFR: *type = (knot_packet_is_query(packet)) ? KNOT_QUERY_IXFR : KNOT_RESPONSE_IXFR; break; default: *type = (knot_packet_is_query(packet)) ? KNOT_QUERY_NORMAL : KNOT_RESPONSE_NORMAL; } break; case KNOT_OPCODE_NOTIFY: *type = (knot_packet_is_query(packet)) ? KNOT_QUERY_NOTIFY : KNOT_RESPONSE_NOTIFY; break; case KNOT_OPCODE_UPDATE: if(knot_packet_is_query(packet)) { *type = KNOT_QUERY_UPDATE; } else { return KNOT_RCODE_FORMERR; } break; default: return KNOT_RCODE_NOTIMPL; } // knot_packet_free(&packet); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ void knot_ns_error_response(const knot_nameserver_t *nameserver, uint16_t query_id, uint8_t rcode, uint8_t *response_wire, size_t *rsize) { //dbg_ns("Error response: \n"); //dbg_ns_hex((const char *)nameserver->err_response, // nameserver->err_resp_size); memcpy(response_wire, nameserver->err_response, nameserver->err_resp_size); // copy ID of the query knot_wire_set_id(response_wire, query_id); // set the RCODE knot_wire_set_rcode(response_wire, rcode); *rsize = nameserver->err_resp_size; } /*----------------------------------------------------------------------------*/ void knot_ns_error_response_full(knot_nameserver_t *nameserver, knot_packet_t *response, uint8_t rcode, uint8_t *response_wire, size_t *rsize) { knot_response_set_rcode(response, rcode); if (ns_error_response_to_wire(response, response_wire, rsize) != 0) { knot_ns_error_response(nameserver, knot_packet_id( knot_packet_query(response)), KNOT_RCODE_SERVFAIL, response_wire, rsize); } } /*----------------------------------------------------------------------------*/ int knot_ns_answer_normal(knot_nameserver_t *nameserver, knot_packet_t *query, uint8_t *response_wire, size_t *rsize) { dbg_ns("ns_answer_normal()\n"); // first, parse the rest of the packet assert(knot_packet_is_query(query)); dbg_ns("Query - parsed: %zu, total wire size: %zu\n", knot_packet_parsed(query), knot_packet_size(query)); int ret; ret = knot_packet_parse_rest(query); if (ret != KNOT_EOK) { dbg_ns("Failed to parse rest of the query: " "%s.\n", knot_strerror(ret)); knot_ns_error_response(nameserver, knot_packet_id(query), (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR : KNOT_RCODE_SERVFAIL, response_wire, rsize); return KNOT_EOK; } /* * Semantic checks - if ANCOUNT > 0 or NSCOUNT > 0, return FORMERR. * * If any xxCOUNT is less or more than actual RR count * the previously called knot_packet_parse_rest() will recognize this. * * Check the QDCOUNT and in case of anything but 1 send back * FORMERR */ if (knot_packet_ancount(query) > 0 || knot_packet_nscount(query) > 0 || knot_packet_qdcount(query) != 1) { dbg_ns("ANCOUNT or NSCOUNT not 0 in query, reply FORMERR.\n"); knot_ns_error_response(nameserver, knot_packet_id(query), KNOT_RCODE_FORMERR, response_wire, rsize); return KNOT_EOK; } size_t resp_max_size = 0; assert(*rsize >= MAX_UDP_PAYLOAD); knot_packet_dump(query); if (knot_query_edns_supported(query)) { if (knot_edns_get_payload(&query->opt_rr) < knot_edns_get_payload(nameserver->opt_rr)) { resp_max_size = knot_edns_get_payload(&query->opt_rr); } else { resp_max_size = knot_edns_get_payload( nameserver->opt_rr); } } if (resp_max_size < MAX_UDP_PAYLOAD) { resp_max_size = MAX_UDP_PAYLOAD; } knot_packet_t *response; ret = knot_ns_prepare_response(nameserver, query, &response, resp_max_size); if (ret != KNOT_EOK) { knot_ns_error_response(nameserver, knot_packet_id(query), KNOT_RCODE_SERVFAIL, response_wire, rsize); return KNOT_EOK; } dbg_ns("Query - parsed: %zu, total wire size: %zu\n", query->parsed, query->size); dbg_ns("Opt RR: version: %d, payload: %d\n", query->opt_rr.version, query->opt_rr.payload); // get the answer for the query rcu_read_lock(); knot_zonedb_t *zonedb = rcu_dereference(nameserver->zone_db); dbg_ns("EDNS supported in query: %d\n", knot_query_edns_supported(query)); // set the OPT RR to the response if (knot_query_edns_supported(query)) { /*! \todo API. */ // if (knot_edns_get_payload(&query->opt_rr) > MAX_UDP_PAYLOAD) { // ret = knot_packet_set_max_size(response, // knot_edns_get_payload(&query->opt_rr)); // } else { // ret = knot_packet_set_max_size(response, // MAX_UDP_PAYLOAD); // } // if (ret != KNOT_EOK) { // dbg_ns("Failed to set max size.\n"); // knot_ns_error_response_full(nameserver, response, // KNOT_RCODE_SERVFAIL, // response_wire, rsize); // return KNOT_EOK; // } ret = knot_response_add_opt(response, nameserver->opt_rr, 1); if (ret != KNOT_EOK) { dbg_ns("Failed to set OPT RR to the response" ": %s\n",knot_strerror(ret)); } else { // copy the DO bit from the query if (knot_query_dnssec_requested(query)) { /*! \todo API for this. */ knot_edns_set_do(&response->opt_rr); } } }/* else { dbg_ns("Setting max size to %u.\n", MAX_UDP_PAYLOAD); ret = knot_packet_set_max_size(response, MAX_UDP_PAYLOAD); if (ret != KNOT_EOK) { dbg_ns("Failed to set max size to %u\n", MAX_UDP_PAYLOAD); knot_ns_error_response_full(nameserver, response, KNOT_RCODE_SERVFAIL, response_wire, rsize); return KNOT_EOK; } }*/ dbg_ns("Response max size: %zu\n", response->max_size); ret = ns_answer(zonedb, response); if (ret != 0) { // now only one type of error (SERVFAIL), later maybe more knot_ns_error_response_full(nameserver, response, KNOT_RCODE_SERVFAIL, response_wire, rsize); } else { dbg_ns("Created response packet.\n"); //knot_response_dump(resp); knot_packet_dump(response); // 4) Transform the packet into wire format if (ns_response_to_wire(response, response_wire, rsize) != 0) { // send back SERVFAIL (as this is our problem) knot_ns_error_response_full(nameserver, response, KNOT_RCODE_SERVFAIL, response_wire, rsize); } } rcu_read_unlock(); knot_packet_free(&response); dbg_ns("Returning response with wire size %zu\n", *rsize); //dbg_ns_hex((char *)response_wire, *rsize); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ int knot_ns_init_xfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) { dbg_ns("knot_ns_init_xfr()\n"); if (nameserver == NULL || xfr == NULL) { return KNOT_EBADARG; } // no need to parse rest of the packet /*! \todo Parse rest of packet because of EDNS. */ int ret = knot_packet_parse_rest(xfr->query); if (ret != KNOT_EOK) { dbg_ns("Failed to parse rest of the query: %s\n", knot_strerror(ret)); knot_ns_error_response(nameserver, xfr->query->header.id, (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR : KNOT_RCODE_SERVFAIL, xfr->wire, &xfr->wire_size); ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, xfr->wire_size); return ret; } dbg_packet("Parsed XFR query:\n"); knot_packet_dump(xfr->query); // initialize response packet structure knot_packet_t *response = knot_packet_new( KNOT_PACKET_PREALLOC_RESPONSE); if (response == NULL) { dbg_ns("Failed to create packet structure.\n"); /*! \todo xfr->wire is not NULL, will fail on assert! */ knot_ns_error_response(nameserver, xfr->query->header.id, KNOT_RCODE_SERVFAIL, xfr->wire, &xfr->wire_size); ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, xfr->wire_size); knot_packet_free(&response); return ret; } //int ret = knot_packet_set_max_size(response, xfr->wire_size); response->wireformat = xfr->wire; response->max_size = xfr->wire_size; // if (ret != KNOT_EOK) { // dbg_ns("Failed to init response structure.\n"); // /*! \todo xfr->wire is not NULL, will fail on assert! */ // knot_ns_error_response(nameserver, xfr->query->header.id, // KNOT_RCODE_SERVFAIL, xfr->wire, // &xfr->wire_size); // int res = xfr->send(xfr->session, &xfr->addr, xfr->wire, // xfr->wire_size); // knot_packet_free(&response); // return res; // } ret = knot_response_init_from_query(response, xfr->query); if (ret != KNOT_EOK) { dbg_ns("Failed to init response structure.\n"); /*! \todo xfr->wire is not NULL, will fail on assert! */ knot_ns_error_response(nameserver, xfr->query->header.id, KNOT_RCODE_SERVFAIL, xfr->wire, &xfr->wire_size); int res = xfr->send(xfr->session, &xfr->addr, xfr->wire, xfr->wire_size); knot_packet_free(&response); return res; } xfr->response = response; knot_zonedb_t *zonedb = rcu_dereference(nameserver->zone_db); const knot_dname_t *qname = knot_packet_qname(xfr->response); assert(knot_packet_qtype(xfr->response) == KNOT_RRTYPE_AXFR || knot_packet_qtype(xfr->response) == KNOT_RRTYPE_IXFR); dbg_ns_exec( char *name_str = knot_dname_to_str(qname); dbg_ns("Trying to find zone with name %s\n", name_str); free(name_str); ); // find zone in which to search for the name knot_zone_t *zone = knot_zonedb_find_zone(zonedb, qname); // if no zone found, return NotAuth if (zone == NULL) { dbg_ns("No zone found.\n"); knot_response_set_rcode(xfr->response, KNOT_RCODE_NOTAUTH); ns_xfr_send_and_clear(xfr, 1); return KNOT_ENOZONE; } dbg_ns_exec( char *name2_str = knot_dname_to_str(qname); dbg_ns("Found zone for name %s\n", name2_str); free(name2_str); ); xfr->zone = zone; return KNOT_EOK; } /*----------------------------------------------------------------------------*/ int ns_serial_compare(uint32_t s1, uint32_t s2) { int32_t diff = ns_serial_difference(s1, s2); return (s1 == s2) /* s1 equal to s2 */ ? 0 :((diff >= 1 && diff < ((uint32_t)1 << 31)) ? 1 /* s1 larger than s2 */ : -1); /* s1 less than s2 */ } /*----------------------------------------------------------------------------*/ int ns_ixfr_load_serials(const knot_ns_xfr_t *xfr, uint32_t *serial_from, uint32_t *serial_to) { if (xfr == NULL || xfr->zone == NULL || serial_from == NULL || serial_to == NULL) { dbg_ns_detail("Wrong parameters: xfr=%p," " xfr->zone = %p\n", xfr, xfr->zone); return KNOT_EBADARG; } const knot_zone_t *zone = xfr->zone; const knot_zone_contents_t *contents = knot_zone_contents(zone); if (!contents) { dbg_ns_detail("Missing contents\n"); return KNOT_EBADARG; } if (knot_zone_contents_apex(contents) == NULL) { dbg_ns_detail("No apex.\n"); return KNOT_EBADARG; } const knot_rrset_t *zone_soa = knot_node_rrset(knot_zone_contents_apex(contents), KNOT_RRTYPE_SOA); if (zone_soa == NULL) { dbg_ns_verb("No SOA.\n"); return KNOT_EBADARG; } if (knot_packet_nscount(xfr->query) < 1) { dbg_ns_verb("No Authority record.\n"); return KNOT_EMALF; } if (knot_packet_authority_rrset(xfr->query, 0) == NULL) { dbg_ns_verb("Authority record missing.\n"); return KNOT_ERROR; } // retrieve origin (xfr) serial and target (zone) serial *serial_to = knot_rdata_soa_serial(knot_rrset_rdata(zone_soa)); *serial_from = knot_rdata_soa_serial(knot_rrset_rdata( knot_packet_authority_rrset(xfr->query, 0))); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ int knot_ns_xfr_send_error(const knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr, knot_rcode_t rcode) { /*! \todo Handle TSIG errors differently. */ knot_response_set_rcode(xfr->response, rcode); /*! \todo Probably rename the function. */ int ret = 0; if ((ret = ns_xfr_send_and_clear(xfr, 1)) != KNOT_EOK) { size_t size = 0; knot_ns_error_response(nameserver, xfr->query->header.id, KNOT_RCODE_SERVFAIL, xfr->wire, &size); ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, size); } return ret; } /*----------------------------------------------------------------------------*/ int knot_ns_answer_axfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) { if (xfr == NULL || nameserver == NULL || xfr->zone == NULL) { return KNOT_EBADARG; } rcu_read_lock(); // take the contents and answer from them int ret = 0; knot_zone_contents_t *contents = knot_zone_get_contents(xfr->zone); if (!contents) { dbg_ns("AXFR failed on stub zone\n"); /*! \todo replace with knot_ns_xfr_send_error() */ knot_ns_error_response(nameserver, xfr->query->header.id, KNOT_RCODE_SERVFAIL, xfr->wire, &xfr->wire_size); ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, xfr->wire_size); rcu_read_unlock(); knot_packet_free(&xfr->response); return KNOT_EOK; } /*! * \todo [TSIG] The TSIG data should already be stored in 'xfr'. * Now just count the expected size of the TSIG RR and save it * to the response structure. */ /*! \todo [TSIG] Get the TSIG size from some API function. */ if (xfr->tsig_size > 0) { dbg_ns_detail("Setting TSIG size in packet: %zu\n", xfr->tsig_size); knot_packet_set_tsig_size(xfr->response, xfr->tsig_size); } ret = ns_axfr_from_zone(contents, xfr); /*! \todo Somehow distinguish when it makes sense to send the SERVFAIL * and when it does not. E.g. if there was problem in sending * packet, it will probably fail when sending the SERVFAIL also. */ if (ret < 0) { dbg_ns("AXFR failed, sending SERVFAIL.\n"); // now only one type of error (SERVFAIL), later maybe more /*! \todo xfr->wire is not NULL, will fail on assert! */ /*! \todo replace with knot_ns_xfr_send_error() */ knot_ns_error_response(nameserver, xfr->query->header.id, KNOT_RCODE_SERVFAIL, xfr->wire, &xfr->wire_size); ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, xfr->wire_size); } else if (ret > 0) { ret = KNOT_ERROR; } rcu_read_unlock(); knot_packet_free(&xfr->response); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ int knot_ns_answer_ixfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) { if (nameserver == NULL || xfr == NULL || xfr->zone == NULL || xfr->response == NULL) { return KNOT_EBADARG; } //uint8_t *wire = NULL; //size_t size = xfr->wire_size; // parse rest of the packet (we need the Authority record) int ret = knot_packet_parse_rest(xfr->query); if (ret != KNOT_EOK) { dbg_ns("Failed to parse rest of the packet. Reply FORMERR.\n"); // knot_ns_error_response_full(nameserver, xfr->response, // KNOT_RCODE_FORMERR, xfr->wire, // &size); knot_ns_xfr_send_error(nameserver, xfr, KNOT_RCODE_FORMERR); //ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, size); knot_packet_free(&xfr->response); return ret; } // check if the zone has contents if (knot_zone_contents(xfr->zone) == NULL) { dbg_ns("Zone expired or not bootstrapped. Reply SERVFAIL.\n"); ret = knot_ns_xfr_send_error(nameserver, xfr, KNOT_RCODE_SERVFAIL); // knot_ns_error_response_full(nameserver, xfr->response, // KNOT_RCODE_SERVFAIL, xfr->wire, // &size); // ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, size); knot_packet_free(&xfr->response); return ret; } /*! * \todo [TSIG] The TSIG data should already be stored in 'xfr'. * Now just count the expected size of the TSIG RR and save it * to the response structure. This should be optional, only if * the request contained TSIG, i.e. if there is the data in 'xfr'. */ /*! \todo [TSIG] Get the TSIG size from some API function. */ if (xfr->tsig_size > 0) { knot_packet_set_tsig_size(xfr->response, xfr->tsig_size); } ret = ns_ixfr(xfr); /*! \todo Somehow distinguish when it makes sense to send the SERVFAIL * and when it does not. E.g. if there was problem in sending * packet, it will probably fail when sending the SERVFAIL also. */ if (ret < 0) { dbg_ns("IXFR failed, sending SERVFAIL.\n"); // now only one type of error (SERVFAIL), later maybe more /*! \todo Extract this to some function. */ // knot_response_set_rcode(xfr->response, KNOT_RCODE_SERVFAIL); // uint8_t *wire = NULL; // ret = knot_packet_to_wire(xfr->response, &wire, &size); // if (ret != KNOT_EOK) { //// knot_ns_error_response(nameserver, //// xfr->query->header.id, //// KNOT_RCODE_SERVFAIL, xfr->wire, //// &size); //// ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, //// size); // knot_ns_xfr_send_error(xfr, KNOT_RCODE_SERVFAIL); // knot_packet_free(&xfr->response); // return ret; // } else { // ret = xfr->send(xfr->session, &xfr->addr, wire, size); // } knot_ns_xfr_send_error(nameserver, xfr, KNOT_RCODE_SERVFAIL); } /*else if (ret > 0) { ret = KNOT_ERROR; }*/ knot_packet_free(&xfr->response); return ret; } /*----------------------------------------------------------------------------*/ int knot_ns_process_axfrin(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) { /*! * \todo [TSIG] Here we assume that 'xfr' contains TSIG information * and the digest of the query sent to the master or the previous * digest. */ dbg_ns("ns_process_axfrin: incoming packet, wire size: %zu\n", xfr->wire_size); int ret = xfrin_process_axfr_packet(/*xfr->wire, xfr->wire_size,*/ /*(xfrin_constructed_zone_t **)(&xfr->data)*/ xfr); if (ret > 0) { // transfer finished dbg_ns("ns_process_axfrin: AXFR finished, zone created.\n"); /* * Adjust zone so that node count is set properly and nodes are * marked authoritative / delegation point. */ xfrin_constructed_zone_t *constr_zone = (xfrin_constructed_zone_t *)xfr->data; knot_zone_contents_t *zone = constr_zone->contents; assert(zone != NULL); dbg_ns("ns_process_axfrin: adjusting zone.\n"); knot_zone_contents_adjust(zone, 0); /* Create and fill hash table */ dbg_ns("ns_process_axfrin: filling hash table.\n"); int rc = knot_zone_contents_create_and_fill_hash_table(zone); if (rc != KNOT_EOK) { return KNOT_ERROR; // TODO: change error code } // save the zone contents to the xfr->data xfr->data = zone; // free the structure used for processing XFR assert(constr_zone->rrsigs == NULL); free(constr_zone); //knot_zone_contents_dump(zone, 0); } /*! * \todo In case of error, shouldn't the zone be destroyed here? */ return ret; } /*----------------------------------------------------------------------------*/ int knot_ns_switch_zone(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) { if (xfr == NULL || nameserver == NULL || xfr->data == NULL) { return KNOT_EBADARG; } knot_zone_contents_t *zone = (knot_zone_contents_t *)xfr->data; dbg_ns("Replacing zone by new one: %p\n", zone); // find the zone in the zone db knot_zone_t *z = knot_zonedb_find_zone(nameserver->zone_db, knot_node_owner(knot_zone_contents_apex(zone))); if (z == NULL) { char *name = knot_dname_to_str(knot_node_owner( knot_zone_contents_apex(zone))); dbg_ns("Failed to replace zone %s, old zone " "not found\n", name); free(name); } else { zone->zone = z; } knot_zone_contents_t *old = rcu_xchg_pointer(&z->contents, zone); // knot_zone_t *old = knot_zonedb_replace_zone(nameserver->zone_db, // zone); dbg_ns("Old zone: %p\n", old); // if (old == NULL) { // char *name = knot_dname_to_str( // knot_node_owner(knot_zone_apex(zone))); // dbg_ns("Failed to replace zone %s\n", name); // free(name); // } // wait for readers to finish dbg_ns("Waiting for readers to finish...\n"); synchronize_rcu(); // destroy the old zone dbg_ns("Freeing old zone: %p\n", old); knot_zone_contents_deep_free(&old, 0); dbg_ns_exec( dbg_ns("Zone db contents: (zone count: %zu)\n", nameserver->zone_db->zone_count); const knot_zone_t **zones = knot_zonedb_zones(nameserver->zone_db); for (int i = 0; i < knot_zonedb_zone_count (nameserver->zone_db); i++) { dbg_ns("%d. zone: %p", i, zones[i]); char *name = knot_dname_to_str(zones[i]->name); dbg_ns(" zone name: %s\n", name); free(name); } free(zones); ); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ /*! \todo In this function, xfr->zone is properly set. If this is so, we do not * have to search for the zone after the transfer has finished. */ int knot_ns_process_ixfrin(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr) { dbg_ns("ns_process_ixfrin: incoming packet\n"); /*! * \todo [TSIG] Here we assume that 'xfr' contains TSIG information * and the digest of the query sent to the master or the previous * digest. */ int ret = xfrin_process_ixfr_packet(xfr/*xfr->wire, xfr->wire_size, (knot_changesets_t **)(&xfr->data)*/); if (ret == XFRIN_RES_FALLBACK) { dbg_ns("ns_process_ixfrin: Fallback to AXFR.\n"); assert(xfr->data == NULL); // dbg_ns("xfr->zone = %p\n", xfr->zone); // dbg_ns("Zone name: %.*s\n", // xfr->zone->name->size, xfr->zone->name->name); // assert(xfr->zone == NULL); knot_packet_free(&xfr->query); return KNOT_ENOIXFR; } if (ret > 0) { dbg_ns("ns_process_ixfrin: IXFR finished\n"); knot_changesets_t *chgsets = (knot_changesets_t *)xfr->data; if (chgsets == NULL || chgsets->first_soa == NULL) { // nothing to be done?? dbg_ns("No changesets created for incoming IXFR!\n"); return ret; } // find zone associated with the changesets knot_zone_t *zone = knot_zonedb_find_zone( nameserver->zone_db, knot_rrset_owner(chgsets->first_soa)); if (zone == NULL) { dbg_ns("No zone found for incoming IXFR!\n"); knot_free_changesets( (knot_changesets_t **)(&xfr->data)); return KNOT_ENOZONE; /*! \todo Other error code? */ } switch (ret) { case XFRIN_RES_COMPLETE: xfr->zone = zone; break; case XFRIN_RES_SOA_ONLY: { // compare the SERIAL from the changeset with the zone's // serial const knot_node_t *apex = knot_zone_contents_apex( knot_zone_contents(zone)); if (apex == NULL) { return KNOT_ERROR; } const knot_rrset_t *zone_soa = knot_node_rrset( apex, KNOT_RRTYPE_SOA); if (zone_soa == NULL) { return KNOT_ERROR; } if (knot_rdata_soa_serial(knot_rrset_rdata( chgsets->first_soa)) != knot_rdata_soa_serial(knot_rrset_rdata( zone_soa))) { dbg_ns("Update did not fit.\n"); return KNOT_EAGAIN; } else { // free changesets dbg_ns("No update needed.\n"); knot_free_changesets( (knot_changesets_t **)(&xfr->data)); return KNOT_ENOXFR; } } break; } } /*! * \todo In case of error, shouldn't the zone be destroyed here? */ return ret; } /*----------------------------------------------------------------------------*/ int knot_ns_process_update(knot_nameserver_t *nameserver, knot_packet_t *query, uint8_t *response_wire, size_t *rsize, knot_zone_t **zone, knot_changeset_t **changeset) { // 1) Parse the rest of the packet assert(knot_packet_is_query(query)); knot_packet_t *response; assert(*rsize >= MAX_UDP_PAYLOAD); int ret = knot_ns_prepare_response(nameserver, query, &response, MAX_UDP_PAYLOAD); if (ret != KNOT_EOK) { knot_ns_error_response(nameserver, knot_packet_id(query), KNOT_RCODE_SERVFAIL, response_wire, rsize); return KNOT_EOK; } assert(response != NULL); dbg_ns("Query - parsed: %zu, total wire size: %zu\n", query->parsed, query->size); if (knot_packet_parsed(query) < knot_packet_size(query)) { ret = knot_packet_parse_rest(query); if (ret != KNOT_EOK) { dbg_ns("Failed to parse rest of the query: " "%s.\n", knot_strerror(ret)); knot_ns_error_response_full(nameserver, response, (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR : KNOT_RCODE_SERVFAIL, response_wire, rsize); knot_packet_free(&response); return KNOT_EOK; } } dbg_ns("Query - parsed: %zu, total wire size: %zu\n", knot_packet_parsed(query), knot_packet_size(query)); /*! \todo API for EDNS values. */ dbg_ns("Opt RR: version: %d, payload: %d\n", query->opt_rr.version, query->opt_rr.payload); // 2) Find zone for the query // we do not check if there is only one entry in the Question section // because the packet structure does not allow it /*! \todo Check number of Question entries while parsing. */ if (knot_packet_qtype(query) != KNOT_RRTYPE_SOA) { dbg_ns("Question is not of type SOA.\n"); knot_ns_error_response_full(nameserver, response, KNOT_RCODE_FORMERR, response_wire, rsize); knot_packet_free(&response); return KNOT_EOK; } *zone = knot_zonedb_find_zone(nameserver->zone_db, knot_packet_qname(query)); if (*zone == NULL) { dbg_ns("Zone not found for the update.\n"); knot_ns_error_response_full(nameserver, response, KNOT_RCODE_NOTAUTH, response_wire, rsize); knot_packet_free(&response); return KNOT_EOK; } uint8_t rcode = 0; // 3) Check zone ret = knot_ddns_check_zone(*zone, query, &rcode); if (ret == KNOT_EBADZONE) { // zone is slave, forward the request /*! \todo Implement forwarding. */ return KNOT_EBADZONE; } else if (ret != KNOT_EOK) { dbg_ns("Failed to check zone for update: " "%s.\n", knot_strerror(ret)); knot_ns_error_response_full(nameserver, response, rcode, response_wire, rsize); knot_packet_free(&response); return KNOT_EOK; } // 4) Convert prerequisities knot_ddns_prereq_t *prereqs = NULL; ret = knot_ddns_process_prereqs(query, &prereqs, &rcode); if (ret != KNOT_EOK) { dbg_ns("Failed to check zone for update: " "%s.\n", knot_strerror(ret)); knot_ns_error_response_full(nameserver, response, rcode, response_wire, rsize); knot_packet_free(&response); return KNOT_EOK; } assert(prereqs != NULL); // 5) Check prerequisities /*! \todo Somehow ensure the zone will not be changed until the update * is finished. */ ret = knot_ddns_check_prereqs(knot_zone_contents(*zone), &prereqs, &rcode); if (ret != KNOT_EOK) { dbg_ns("Failed to check zone for update: " "%s.\n", knot_strerror(ret)); knot_ns_error_response_full(nameserver, response, rcode, response_wire, rsize); knot_ddns_prereqs_free(&prereqs); knot_packet_free(&response); return KNOT_EOK; } // 6) Convert update to changeset ret = knot_ddns_process_update(query, changeset, &rcode); if (ret != KNOT_EOK) { dbg_ns("Failed to check zone for update: " "%s.\n", knot_strerror(ret)); knot_ns_error_response_full(nameserver, response, rcode, response_wire, rsize); knot_ddns_prereqs_free(&prereqs); knot_packet_free(&response); return KNOT_EOK; } assert(changeset != NULL); // 7) Create response dbg_ns("Update converted successfuly.\n"); /*! \todo No response yet. Distinguish somehow in the caller. * Maybe only this case will be EOK, other cases some error. */ knot_packet_free(&response); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ int knot_ns_create_forward_query(const knot_packet_t *query, uint8_t *query_wire, size_t *size) { // just copy the wireformat of the query and set a new random ID to it if (knot_packet_size(query) > *size) { return KNOT_ESPACE; } memcpy(query_wire, knot_packet_wireformat(query), knot_packet_size(query)); *size = knot_packet_size(query); knot_wire_set_id(query_wire, knot_random_id()); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ int knot_ns_process_forward_response(const knot_packet_t *response, uint16_t original_id, uint8_t *response_wire, size_t *size) { // just copy the wireformat of the response and set the original ID if (knot_packet_size(response) > *size) { return KNOT_ESPACE; } memcpy(response_wire, knot_packet_wireformat(response), knot_packet_size(response)); *size = knot_packet_size(response); knot_wire_set_id(response_wire, original_id); return KNOT_EOK; } /*----------------------------------------------------------------------------*/ void *knot_ns_data(knot_nameserver_t *nameserver) { return nameserver->data; } /*----------------------------------------------------------------------------*/ void *knot_ns_get_data(knot_nameserver_t *nameserver) { return nameserver->data; } /*----------------------------------------------------------------------------*/ void knot_ns_set_data(knot_nameserver_t *nameserver, void *data) { nameserver->data = data; } /*----------------------------------------------------------------------------*/ void knot_ns_destroy(knot_nameserver_t **nameserver) { synchronize_rcu(); free((*nameserver)->err_response); if ((*nameserver)->opt_rr != NULL) { knot_edns_free(&(*nameserver)->opt_rr); } // destroy the zone db knot_zonedb_deep_free(&(*nameserver)->zone_db); free(*nameserver); *nameserver = NULL; }