From 742f3066fe0f0007c41fc0e20f81107cbc0d88bd Mon Sep 17 00:00:00 2001 From: Jan Kadlec <jan.kadlec@nic.cz> Date: Fri, 21 Feb 2014 14:47:38 +0100 Subject: [PATCH] RRSIGs no longer part of pkt API, handled by nameserver instead. - previously static function 'put_rr' in internet.c has been made public, it handles additions of RRSIGs as well now, by adding the to a list that is traversed after each section has been solved. --- src/knot/ctl/knotc_main.c | 2 +- src/knot/ctl/remote.c | 2 +- src/knot/dnssec/nsec-chain.c | 2 +- src/knot/dnssec/zone-sign.c | 8 +- src/knot/nameserver/axfr.c | 6 +- src/knot/nameserver/chaos.c | 2 +- src/knot/nameserver/internet.c | 58 ++++++++---- src/knot/nameserver/internet.h | 16 ++++ src/knot/nameserver/ixfr.c | 4 +- src/knot/nameserver/nsec_proofs.c | 142 ++++++++++++++++------------ src/knot/nameserver/nsec_proofs.h | 2 +- src/knot/nameserver/process_query.c | 4 +- src/knot/nameserver/process_query.h | 8 ++ src/knot/updates/xfr-in.c | 2 +- src/knot/zone/semantic-check.c | 4 +- src/libknot/dnssec/rrset-sign.c | 3 +- src/libknot/packet/compr.h | 1 - src/libknot/packet/pkt.c | 3 +- src/libknot/packet/pkt.h | 3 +- src/libknot/rrset.c | 8 +- src/libknot/rrset.h | 5 +- src/utils/dig/dig_exec.c | 2 +- src/utils/nsupdate/nsupdate_exec.c | 2 +- tests/pkt.c | 4 +- tests/process_query.c | 2 +- 25 files changed, 178 insertions(+), 117 deletions(-) diff --git a/src/knot/ctl/knotc_main.c b/src/knot/ctl/knotc_main.c index c0591599f..727765da8 100644 --- a/src/knot/ctl/knotc_main.c +++ b/src/knot/ctl/knotc_main.c @@ -213,7 +213,7 @@ static int cmd_remote(const char *cmd, uint16_t rrt, int argc, char *argv[]) break; } } - int res = knot_pkt_put(pkt, 0, rr, NULL, KNOT_PF_FREE); + int res = knot_pkt_put(pkt, 0, rr, KNOT_PF_FREE); if (res != KNOT_EOK) { log_server_error("Couldn't create the query.\n"); knot_rrset_deep_free(&rr, 1, NULL); diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c index 5e7faa123..ec998d342 100644 --- a/src/knot/ctl/remote.c +++ b/src/knot/ctl/remote.c @@ -508,7 +508,7 @@ static int remote_send_chunk(int c, knot_pkt_t *query, const char* d, uint16_t l ret = remote_create_txt(rr, d, len); assert(ret == KNOT_EOK); - ret = knot_pkt_put(resp, 0, rr, NULL, KNOT_PF_FREE); + ret = knot_pkt_put(resp, 0, rr, KNOT_PF_FREE); if (ret != KNOT_EOK) { knot_rrset_deep_free(&rr, 1, NULL); goto failed; diff --git a/src/knot/dnssec/nsec-chain.c b/src/knot/dnssec/nsec-chain.c index 6874897b4..f048946e3 100644 --- a/src/knot/dnssec/nsec-chain.c +++ b/src/knot/dnssec/nsec-chain.c @@ -650,7 +650,7 @@ int knot_nsec_changeset_remove(const knot_rrset_t *oldrr, // extract copy of RRSIG knot_rrset_t *synth_rrsigs = NULL; - result = knot_rrset_synth_rrsig(oldrr, rrsigs, &synth_rrsigs, NULL); + result = knot_rrset_synth_rrsig(rrsigs, oldrr->type, &synth_rrsigs, NULL); if (result != KNOT_EOK && result != KNOT_ENOENT) { return result; } diff --git a/src/knot/dnssec/zone-sign.c b/src/knot/dnssec/zone-sign.c index 1ab84246c..908e4abad 100644 --- a/src/knot/dnssec/zone-sign.c +++ b/src/knot/dnssec/zone-sign.c @@ -218,7 +218,8 @@ static int remove_expired_rrsigs(const knot_rrset_t *covered, int result = KNOT_EOK; knot_rrset_t *synth_rrsig = NULL; - result = knot_rrset_synth_rrsig(covered, rrsigs, &synth_rrsig, NULL); + result = knot_rrset_synth_rrsig(rrsigs, covered->type, + &synth_rrsig, NULL); if (result == KNOT_ENOENT) { // Nothing to remove return KNOT_EOK; @@ -357,7 +358,7 @@ static int remove_rrset_rrsigs(const knot_rrset_t *rrset, assert(changeset); knot_rrset_t *synth_rrsig = NULL; - int ret = knot_rrset_synth_rrsig(rrset, rrsigs, &synth_rrsig, NULL); + int ret = knot_rrset_synth_rrsig(rrsigs, rrset->type, &synth_rrsig, NULL); if (ret == KNOT_ENOENT) { // Nothing to remove return KNOT_EOK; @@ -1009,7 +1010,8 @@ static int update_dnskeys(const knot_zone_contents_t *zone, } knot_rrset_t *dnskey_rrsig = NULL; - result = knot_rrset_synth_rrsig(dnskeys, rrsigs, &dnskey_rrsig, NULL); + result = knot_rrset_synth_rrsig(rrsigs, KNOT_RRTYPE_DNSKEY, + &dnskey_rrsig, NULL); if (result != KNOT_EOK && result != KNOT_ENOENT) { return result; } diff --git a/src/knot/nameserver/axfr.c b/src/knot/nameserver/axfr.c index 2213406fa..0e6257f23 100644 --- a/src/knot/nameserver/axfr.c +++ b/src/knot/nameserver/axfr.c @@ -44,7 +44,7 @@ static int put_rrsets(knot_pkt_t *pkt, knot_node_t *node, struct axfr_proc *stat if (rrset[i]->type == KNOT_RRTYPE_SOA) { continue; } - ret = knot_pkt_put(pkt, 0, rrset[i], NULL, flags); + ret = knot_pkt_put(pkt, 0, rrset[i], flags); /* If something failed, remember the current RR for later. */ if (ret != KNOT_EOK) { @@ -130,7 +130,7 @@ int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb process_item, struct query_data /* Prepend SOA on first packet. */ if (xfer->npkts == 0) { - ret = knot_pkt_put(pkt, 0, soa_rr, NULL, KNOT_PF_NOTRUNC); + ret = knot_pkt_put(pkt, 0, soa_rr, KNOT_PF_NOTRUNC); if (ret != KNOT_EOK) { return ret; } @@ -151,7 +151,7 @@ int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb process_item, struct query_data /* Append SOA on last packet. */ if (ret == KNOT_EOK) { - ret = knot_pkt_put(pkt, 0, soa_rr, NULL, KNOT_PF_NOTRUNC); + ret = knot_pkt_put(pkt, 0, soa_rr, KNOT_PF_NOTRUNC); } /* Update counters. */ diff --git a/src/knot/nameserver/chaos.c b/src/knot/nameserver/chaos.c index 63b3f820e..7e0aed43a 100644 --- a/src/knot/nameserver/chaos.c +++ b/src/knot/nameserver/chaos.c @@ -106,7 +106,7 @@ static int answer_txt(knot_pkt_t *response) return KNOT_RCODE_SERVFAIL; } - int result = knot_pkt_put(response, 0, rrset, NULL, KNOT_PF_FREE); + int result = knot_pkt_put(response, 0, rrset, KNOT_PF_FREE); if (result != KNOT_EOK) { knot_rrset_deep_free(&rrset, 1, &response->mm); return KNOT_RCODE_SERVFAIL; diff --git a/src/knot/nameserver/internet.c b/src/knot/nameserver/internet.c index d9c6805fd..9f0bd8811 100644 --- a/src/knot/nameserver/internet.c +++ b/src/knot/nameserver/internet.c @@ -120,9 +120,9 @@ static bool have_dnssec(struct query_data *qdata) } /*! \brief Put RR into packet, expand wildcards. */ -static int put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr, const knot_rrset_t *sigs, - uint16_t compr_hint, - uint32_t flags, struct query_data *qdata) +int ns_put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr, + const knot_rrset_t *rrsigs, uint16_t compr_hint, + uint32_t flags, struct query_data *qdata) { /* RFC3123 s.6 - empty APL is valid, ignore other empty RRs. */ if (knot_rrset_rr_count(rr) < 1 && @@ -145,12 +145,26 @@ static int put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr, const knot_rrset_t *s flags |= KNOT_PF_FREE; } - ret = knot_pkt_put(pkt, compr_hint, rr, sigs, flags); - if (ret != KNOT_EOK && (flags & KNOT_PF_FREE)) { - knot_rrset_deep_free((knot_rrset_t **)&rr, 1, &pkt->mm); + ret = knot_pkt_put(pkt, compr_hint, rr, flags); + if (ret != KNOT_EOK) { + if (flags & KNOT_PF_FREE) { + knot_rrset_deep_free((knot_rrset_t **)&rr, 1, &pkt->mm); + } + return ret; } - return ret; + if (rrsigs && rr->type != KNOT_RRTYPE_RRSIG) { + struct rrsig_info *i = mm_alloc(qdata->mm, + sizeof(struct rrsig_info)); + if (i == NULL) { + return KNOT_ENOMEM; + } + i->rrsigs = rrsigs; + i->type = rr->type; + add_tail(&qdata->rrsigs, &i->n); + } + + return KNOT_EOK; } /*! \brief This is a wildcard-covered or any other terminal node for QNAME. @@ -183,7 +197,7 @@ static int put_answer(knot_pkt_t *pkt, uint16_t type, struct query_data *qdata) return KNOT_ESPACE; } for (unsigned i = 0; i < knot_node_rrset_count(qdata->node); ++i) { - ret = put_rr(pkt, rrsets[i], NULL, compr_hint, 0, qdata); + ret = ns_put_rr(pkt, rrsets[i], NULL, compr_hint, 0, qdata); if (ret != KNOT_EOK) { break; } @@ -192,7 +206,7 @@ static int put_answer(knot_pkt_t *pkt, uint16_t type, struct query_data *qdata) default: /* Single RRSet of given type. */ rrset = knot_node_get_rrset(qdata->node, type); if (rrset) { - ret = put_rr(pkt, rrset, rrsigs, compr_hint, 0, qdata); + ret = ns_put_rr(pkt, rrset, rrsigs, compr_hint, 0, qdata); } break; } @@ -221,7 +235,8 @@ static int put_authority_ns(knot_pkt_t *pkt, struct query_data *qdata) const knot_rrset_t *ns_rrset = knot_node_rrset(zone->apex, KNOT_RRTYPE_NS); const knot_rrset_t *rrsigs = knot_node_rrset(zone->apex, KNOT_RRTYPE_RRSIG); if (ns_rrset) { - return knot_pkt_put(pkt, 0, ns_rrset, rrsigs, KNOT_PF_NOTRUNC|KNOT_PF_CHECKDUP); + return ns_put_rr(pkt, ns_rrset, rrsigs, COMPR_HINT_NONE, + KNOT_PF_NOTRUNC|KNOT_PF_CHECKDUP, qdata); } else { dbg_ns("%s: no NS RRSets in this zone, fishy...\n", __func__); } @@ -229,7 +244,8 @@ static int put_authority_ns(knot_pkt_t *pkt, struct query_data *qdata) } /*! \brief Puts optional SOA RRSet to the Authority section of the response. */ -static int put_authority_soa(knot_pkt_t *pkt, const knot_zone_contents_t *zone) +static int put_authority_soa(knot_pkt_t *pkt, struct query_data *qdata, + const knot_zone_contents_t *zone) { dbg_ns("%s(%p, %p)\n", __func__, pkt, zone); knot_rrset_t *soa_rrset = knot_node_get_rrset(zone->apex, KNOT_RRTYPE_SOA); @@ -251,7 +267,7 @@ static int put_authority_soa(knot_pkt_t *pkt, const knot_zone_contents_t *zone) flags |= KNOT_PF_FREE; } - ret = knot_pkt_put(pkt, 0, soa_rrset, rrsigs, flags); + ret = ns_put_rr(pkt, soa_rrset, rrsigs, COMPR_HINT_NONE, flags, qdata); if (ret != KNOT_EOK && (flags & KNOT_PF_FREE)) { knot_rrset_deep_free(&soa_rrset, 1, &pkt->mm); } @@ -270,7 +286,7 @@ static int put_delegation(knot_pkt_t *pkt, struct query_data *qdata) /* Insert NS record. */ const knot_rrset_t *rrset = knot_node_rrset(qdata->node, KNOT_RRTYPE_NS); const knot_rrset_t *rrsigs = knot_node_rrset(qdata->node, KNOT_RRTYPE_RRSIG); - return knot_pkt_put(pkt, 0, rrset, rrsigs, 0); + return ns_put_rr(pkt, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata); } /*! \brief Put additional records for given RR. */ @@ -303,7 +319,7 @@ static int put_additional(knot_pkt_t *pkt, const knot_rrset_t *rr, knot_rrinfo_t if (additional == NULL) { continue; } - ret = knot_pkt_put(pkt, hint, additional, NULL, flags); + ret = knot_pkt_put(pkt, hint, additional, flags); if (ret != KNOT_EOK) { break; } @@ -329,7 +345,7 @@ static int follow_cname(knot_pkt_t *pkt, uint16_t rrtype, struct query_data *qda /* Now, try to put CNAME to answer. */ uint16_t rr_count_before = pkt->rrset_count; - ret = put_rr(pkt, cname_rr, rrsigs, 0, flags, qdata); + ret = ns_put_rr(pkt, cname_rr, rrsigs, 0, flags, qdata); switch (ret) { case KNOT_EOK: break; case KNOT_ESPACE: return TRUNC; @@ -351,7 +367,7 @@ static int follow_cname(knot_pkt_t *pkt, uint16_t rrtype, struct query_data *qda return ERROR; } cname_rr = dname_cname_synth(cname_rr, qdata->name, &pkt->mm); - ret = put_rr(pkt, cname_rr, NULL, 0, KNOT_PF_FREE, qdata); + ret = ns_put_rr(pkt, cname_rr, NULL, 0, KNOT_PF_FREE, qdata); switch (ret) { case KNOT_EOK: break; case KNOT_ESPACE: return TRUNC; @@ -521,7 +537,7 @@ static int solve_answer_dnssec(int state, knot_pkt_t *pkt, struct query_data *qd } /* RFC4035, section 3.1 RRSIGs for RRs in ANSWER are mandatory. */ - int ret = nsec_append_rrsigs(pkt, false); + int ret = nsec_append_rrsigs(pkt, qdata, false); switch(ret) { case KNOT_ESPACE: return TRUNC; case KNOT_EOK: return state; @@ -542,11 +558,11 @@ static int solve_authority(int state, knot_pkt_t *pkt, struct query_data *qdata) case MISS: /* MISS, set NXDOMAIN RCODE. */ dbg_ns("%s: answer is NXDOMAIN\n", __func__); qdata->rcode = KNOT_RCODE_NXDOMAIN; - ret = put_authority_soa(pkt, zone_contents); + ret = put_authority_soa(pkt, qdata, zone_contents); break; case NODATA: /* NODATA append AUTHORITY SOA. */ dbg_ns("%s: answer is NODATA\n", __func__); - ret = put_authority_soa(pkt, zone_contents); + ret = put_authority_soa(pkt, qdata, zone_contents); break; case DELEG: /* Referral response. */ ret = put_delegation(pkt, qdata); @@ -600,7 +616,7 @@ static int solve_authority_dnssec(int state, knot_pkt_t *pkt, struct query_data /* RFC4035, section 3.1 RRSIGs for RRs in AUTHORITY are mandatory. */ if (ret == KNOT_EOK) { - ret = nsec_append_rrsigs(pkt, false); + ret = nsec_append_rrsigs(pkt, qdata, false); } /* Evaluate final state. */ @@ -644,7 +660,7 @@ static int solve_additional_dnssec(int state, knot_pkt_t *pkt, struct query_data } /* RFC4035, section 3.1 RRSIGs for RRs in ADDITIONAL are optional. */ - int ret = nsec_append_rrsigs(pkt, true); + int ret = nsec_append_rrsigs(pkt, qdata, true); switch(ret) { case KNOT_ESPACE: return TRUNC; case KNOT_EOK: return state; diff --git a/src/knot/nameserver/internet.h b/src/knot/nameserver/internet.h index 6b897386e..b1ef561ad 100644 --- a/src/knot/nameserver/internet.h +++ b/src/knot/nameserver/internet.h @@ -41,6 +41,22 @@ struct query_data; */ int internet_answer(knot_pkt_t *resp, struct query_data *qdata); +/*! + * \brief Puts RRSet to packet, will store its RRSIG for later use. + * + * \param pkt Packet to store RRSet into. + * \param rr RRSet to be stored. + * \param rrsigs RRSIGs to be stored. + * \param compr_hint Compression hint. + * \param flags Flags. + * \param qdata Query data structure. + * + * \return KNOT_E* + */ +int ns_put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr, + const knot_rrset_t *rrsigs, uint16_t compr_hint, + uint32_t flags, struct query_data *qdata); + /*! \brief Require given QUERY TYPE or return error code. */ #define NS_NEED_QTYPE(qdata, qtype_want, error_rcode) \ if (knot_pkt_qtype((qdata)->query) != (qtype_want)) { \ diff --git a/src/knot/nameserver/ixfr.c b/src/knot/nameserver/ixfr.c index 36d0264fa..5668df7ff 100644 --- a/src/knot/nameserver/ixfr.c +++ b/src/knot/nameserver/ixfr.c @@ -34,7 +34,7 @@ struct ixfr_proc { /*! \brief Helper macro for putting RRs into packet. */ #define IXFR_SAFE_PUT(pkt, rr) \ - ret = knot_pkt_put((pkt), 0, (rr), NULL, KNOT_PF_NOTRUNC); \ + ret = knot_pkt_put((pkt), 0, (rr), KNOT_PF_NOTRUNC); \ if (ret != KNOT_EOK) { \ return ret; \ } @@ -261,7 +261,7 @@ static int ixfr_answer_soa(knot_pkt_t *pkt, struct query_data *qdata) /* Guaranteed to have zone contents. */ const knot_node_t *apex = qdata->zone->contents->apex; const knot_rrset_t *soa_rr = knot_node_rrset(apex, KNOT_RRTYPE_SOA); - int ret = knot_pkt_put(pkt, 0, soa_rr, NULL, 0); + int ret = knot_pkt_put(pkt, 0, soa_rr, 0); if (ret != KNOT_EOK) { qdata->rcode = KNOT_RCODE_SERVFAIL; return NS_PROC_FAIL; diff --git a/src/knot/nameserver/nsec_proofs.c b/src/knot/nameserver/nsec_proofs.c index 7c6ad964f..a89f7deed 100644 --- a/src/knot/nameserver/nsec_proofs.c +++ b/src/knot/nameserver/nsec_proofs.c @@ -2,6 +2,7 @@ #include "knot/nameserver/nsec_proofs.h" #include "knot/nameserver/process_query.h" +#include "knot/nameserver/internet.h" #include "knot/dnssec/zone-nsec.h" #include "libknot/common.h" @@ -56,6 +57,7 @@ static knot_dname_t *ns_next_closer(const knot_dname_t *closest_encloser, * \param resp Response where to add the RRSets. */ static int ns_put_nsec3_from_node(const knot_node_t *node, + struct query_data *qdata, knot_pkt_t *resp) { knot_rrset_t *rrset = knot_node_get_rrset(node, KNOT_RRTYPE_NSEC3); @@ -67,7 +69,8 @@ static int ns_put_nsec3_from_node(const knot_node_t *node, int res = KNOT_EOK; if (knot_rrset_rr_count(rrset)) { - res = knot_pkt_put(resp, 0, rrset, rrsigs, KNOT_PF_CHECKDUP); + res = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE, + KNOT_PF_CHECKDUP, qdata); } /*! \note TC bit is already set, if something went wrong. */ @@ -91,6 +94,7 @@ static int ns_put_nsec3_from_node(const knot_node_t *node, */ static int ns_put_covering_nsec3(const knot_zone_contents_t *zone, const knot_dname_t *name, + struct query_data *qdata, knot_pkt_t *resp) { const knot_node_t *prev, *node; @@ -114,7 +118,7 @@ dbg_ns_exec_verb( free(name); ); - return ns_put_nsec3_from_node(prev, resp); + return ns_put_nsec3_from_node(prev, qdata, resp); } /*----------------------------------------------------------------------------*/ @@ -141,6 +145,7 @@ 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, + struct query_data *qdata, knot_pkt_t *resp) { assert(zone != NULL); @@ -203,7 +208,7 @@ dbg_ns_exec_verb( } ); - int ret = ns_put_nsec3_from_node(nsec3_node, resp); + int ret = ns_put_nsec3_from_node(nsec3_node, qdata, resp); if (ret != KNOT_EOK) { return ret; } @@ -224,11 +229,11 @@ dbg_ns_exec_verb( dbg_ns_verb("Next closer name: %s\n", name); free(name); ); - ret = ns_put_covering_nsec3(zone, new_next_closer, resp); + ret = ns_put_covering_nsec3(zone, new_next_closer, qdata, resp); knot_dname_free(&new_next_closer); } else { - ret = ns_put_covering_nsec3(zone, next_closer, resp); + ret = ns_put_covering_nsec3(zone, next_closer, qdata, resp); } return ret; @@ -279,6 +284,7 @@ dbg_ns_exec_verb( static int ns_put_nsec_wildcard(const knot_zone_contents_t *zone, const knot_dname_t *qname, const knot_node_t *previous, + struct query_data *qdata, knot_pkt_t *resp) { // check if we have previous; if not, find one using the tree @@ -295,16 +301,14 @@ static int ns_put_nsec_wildcard(const knot_zone_contents_t *zone, } } - knot_rrset_t *rrset = - knot_node_get_rrset(previous, KNOT_RRTYPE_NSEC); - const knot_rrset_t *rrsigs = - knot_node_rrset(previous, KNOT_RRTYPE_RRSIG); + const knot_rrset_t *rrset = knot_node_rrset(previous, KNOT_RRTYPE_NSEC); + const knot_rrset_t *rrsigs = knot_node_rrset(previous, KNOT_RRTYPE_RRSIG); int ret = KNOT_EOK; if (rrset != NULL && knot_rrset_rr_count(rrset)) { // NSEC proving that there is no node with the searched name - ret = knot_pkt_put(resp, 0, rrset, rrsigs, 0); + ret = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata); } return ret; @@ -327,6 +331,7 @@ static int ns_put_nsec_wildcard(const knot_zone_contents_t *zone, */ static int ns_put_nsec3_no_wildcard_child(const knot_zone_contents_t *zone, const knot_node_t *node, + struct query_data *qdata, knot_pkt_t *resp) { assert(node != NULL); @@ -338,7 +343,7 @@ static int ns_put_nsec3_no_wildcard_child(const knot_zone_contents_t *zone, if (wildcard == NULL) { ret = KNOT_ERROR; /* servfail */ } else { - ret = ns_put_covering_nsec3(zone, wildcard, resp); + ret = ns_put_covering_nsec3(zone, wildcard, qdata, resp); /* Directly discard wildcard. */ knot_dname_free(&wildcard); @@ -367,6 +372,7 @@ static int ns_put_nsec3_no_wildcard_child(const knot_zone_contents_t *zone, static int ns_put_nsec3_wildcard(const knot_zone_contents_t *zone, const knot_node_t *closest_encloser, const knot_dname_t *qname, + struct query_data *qdata, knot_pkt_t *resp) { assert(closest_encloser != NULL); @@ -393,7 +399,7 @@ dbg_ns_exec_verb( dbg_ns_verb("Next closer name: %s\n", name); free(name); ); - int ret = ns_put_covering_nsec3(zone, next_closer, resp); + int ret = ns_put_covering_nsec3(zone, next_closer, qdata, resp); /* Duplicate from ns_next_close(), safe to discard. */ knot_dname_free(&next_closer); @@ -419,11 +425,12 @@ dbg_ns_exec_verb( * \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_pkt_t *resp) + const knot_node_t *closest_encloser, + const knot_node_t *previous, + const knot_zone_contents_t *zone, + const knot_dname_t *qname, + struct query_data *qdata, + knot_pkt_t *resp) { // if wildcard answer, add NSEC / NSEC3 @@ -433,9 +440,10 @@ static int ns_put_nsec_nsec3_wildcard_answer(const knot_node_t *node, dbg_ns_verb("Adding NSEC/NSEC3 for wildcard answer.\n"); if (knot_is_nsec3_enabled(zone)) { ret = ns_put_nsec3_wildcard(zone, closest_encloser, - qname, resp); + qname, qdata, resp); } else { - ret = ns_put_nsec_wildcard(zone, qname, previous, resp); + ret = ns_put_nsec_wildcard(zone, qname, previous, qdata, + resp); } } return ret; @@ -463,6 +471,7 @@ 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, + struct query_data *qdata, knot_pkt_t *resp) { knot_rrset_t *rrset = NULL; @@ -499,7 +508,7 @@ dbg_ns_exec_verb( } - int ret = knot_pkt_put(resp, 0, rrset, rrsigs, 0); + int ret = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata); if (ret != KNOT_EOK) { dbg_ns("Failed to add NSEC for NXDOMAIN to response: %s\n", knot_strerror(ret)); @@ -549,7 +558,7 @@ dbg_ns_exec_verb( // bad zone, ignore return KNOT_EOK; } - ret = knot_pkt_put(resp, 0, rrset, rrsigs, 0); + ret = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata); if (ret != KNOT_EOK) { dbg_ns("Failed to add second NSEC for NXDOMAIN to " "response: %s\n", knot_strerror(ret)); @@ -579,17 +588,18 @@ dbg_ns_exec_verb( static int ns_put_nsec3_nxdomain(const knot_zone_contents_t *zone, const knot_node_t *closest_encloser, const knot_dname_t *qname, + struct query_data *qdata, knot_pkt_t *resp) { // 1) Closest encloser proof int ret = ns_put_nsec3_closest_encloser_proof(zone, &closest_encloser, - qname, resp); + qname, qdata, resp); // 2) NSEC3 covering non-existent wildcard if (ret == KNOT_EOK && closest_encloser != NULL) { dbg_ns_verb("Putting NSEC3 for no wildcard child of closest " "encloser.\n"); ret = ns_put_nsec3_no_wildcard_child(zone, closest_encloser, - resp); + qdata, resp); } return ret; @@ -618,16 +628,17 @@ 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, + struct query_data *qdata, knot_pkt_t *resp) { int ret = 0; if (knot_is_nsec3_enabled(zone)) { ret = ns_put_nsec3_nxdomain(zone, closest_encloser, - qname, resp); + qname, qdata, resp); } else { ret = ns_put_nsec_nxdomain(qname, zone, previous, - closest_encloser, resp); + closest_encloser, qdata, resp); } return ret; @@ -646,17 +657,18 @@ static int ns_put_nsec_nsec3_nxdomain(const knot_zone_contents_t *zone, * \param resp Response where to add the NSECs or NSEC3s. */ static int ns_put_nsec_nsec3_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_pkt_t *resp) + const knot_node_t *closest_encloser, + const knot_node_t *previous, + const knot_zone_contents_t *zone, + const knot_dname_t *qname, + struct query_data *qdata, + knot_pkt_t *resp) { // This case must be handled first, before handling the wildcard case if (knot_node_rrset_count(node) == 0 && !knot_is_nsec3_enabled(zone)) { // node is an empty non-terminal => NSEC for NXDOMAIN return ns_put_nsec_nxdomain(qname, zone, previous, - closest_encloser, resp); + closest_encloser, qdata, resp); } /*! \todo Maybe distinguish different errors. */ @@ -671,19 +683,22 @@ static int ns_put_nsec_nsec3_nodata(const knot_node_t *node, dbg_ns("%s: adding NSEC3 wildcard NODATA\n", __func__); ret = ns_put_nsec3_closest_encloser_proof(zone, &closest_encloser, - qname, resp); + qname, qdata, + resp); } /* RFC5155 7.2.3-7.2.5 common proof. */ dbg_ns("%s: adding NSEC3 NODATA\n", __func__); const knot_node_t *nsec3_node = knot_node_nsec3_node(node); if (nsec3_node) { - ret = ns_put_nsec3_from_node(nsec3_node, resp); + ret = ns_put_nsec3_from_node(nsec3_node, qdata, resp); } else { // No NSEC3 node => Opt-out return ns_put_nsec3_closest_encloser_proof(zone, - &node, - qname, resp); + &node, + qname, + qdata, + resp); } } else { @@ -693,7 +708,7 @@ static int ns_put_nsec_nsec3_nodata(const knot_node_t *node, && knot_rrset_rr_count(rrset)) { dbg_ns_detail("Putting the RRSet to Authority\n"); const knot_rrset_t *rrsigs = knot_node_rrset(node, KNOT_RRTYPE_RRSIG); - ret = knot_pkt_put(resp, 0, rrset, rrsigs, 0); + ret = ns_put_rr(resp, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata); } } @@ -716,7 +731,7 @@ int nsec_prove_wildcards(knot_pkt_t *pkt, struct query_data *qdata) item->node, knot_node_parent(item->node), NULL, qdata->zone->contents, - item->sname, + item->sname, qdata, pkt); if (ret != KNOT_EOK) { break; @@ -732,7 +747,7 @@ int nsec_prove_nodata(knot_pkt_t *pkt, struct query_data *qdata) return ns_put_nsec_nsec3_nodata(qdata->node, qdata->encloser, qdata->previous, qdata->zone->contents, - qdata->name, pkt); + qdata->name, qdata, pkt); } int nsec_prove_nxdomain(knot_pkt_t *pkt, struct query_data *qdata) @@ -740,7 +755,7 @@ int nsec_prove_nxdomain(knot_pkt_t *pkt, struct query_data *qdata) dbg_ns("%s(%p, %p)\n", __func__, pkt, qdata); return ns_put_nsec_nsec3_nxdomain(qdata->zone->contents, qdata->previous, - qdata->encloser, qdata->name, + qdata->encloser, qdata->name, qdata, pkt); } @@ -752,7 +767,7 @@ int nsec_prove_dp_security(knot_pkt_t *pkt, struct query_data *qdata) knot_rrset_t *rrset = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_DS); const knot_rrset_t *rrsigs = knot_node_get_rrset(qdata->node, KNOT_RRTYPE_RRSIG); if (rrset != NULL) { - return knot_pkt_put(pkt, 0, rrset, rrsigs, 0); + return ns_put_rr(pkt, rrset, rrsigs, COMPR_HINT_NONE, 0, qdata); } /* DS doesn't exist => NODATA proof. */ @@ -760,10 +775,10 @@ int nsec_prove_dp_security(knot_pkt_t *pkt, struct query_data *qdata) qdata->encloser, qdata->previous, qdata->zone->contents, - qdata->name, pkt); + qdata->name, qdata, pkt); } -int nsec_append_rrsigs(knot_pkt_t *pkt, bool optional) +int nsec_append_rrsigs(knot_pkt_t *pkt, struct query_data *qdata, bool optional) { dbg_ns("%s(%p, optional=%d)\n", __func__, pkt, optional); @@ -771,26 +786,31 @@ int nsec_append_rrsigs(knot_pkt_t *pkt, bool optional) uint32_t flags = (optional) ? KNOT_PF_NOTRUNC : KNOT_PF_NULL; flags |= KNOT_PF_FREE; // Free all RRSIGs, they are synthesized uint16_t compr_hint = COMPR_HINT_NONE; - const knot_rrset_t *rr = NULL; - const knot_pktsection_t *section = knot_pkt_section(pkt, pkt->current); - - /* Append RRSIG for each RR in given section. */ - for (uint16_t i = 0; i < section->count; ++i) { - rr = section->rr[i]; - compr_hint = section->rrinfo[i].compress_ptr[0]; - if (section->rrinfo[i].rrsigs) { - knot_rrset_t *synth_sig = NULL; - ret = knot_rrset_synth_rrsig(rr, section->rrinfo[i].rrsigs, &synth_sig, &pkt->mm); - if (ret != KNOT_EOK && ret != KNOT_ENOENT) { - break; - } - ret = knot_pkt_put(pkt, compr_hint, synth_sig, NULL, flags); - if (ret != KNOT_EOK) { - knot_rrset_deep_free(&synth_sig, 1, &pkt->mm); - break; + + /* Append RRSIGs for section. */ + node_t *n = NULL, *nxt = NULL; + WALK_LIST_DELSAFE(n, nxt, qdata->rrsigs) { + struct rrsig_info *info = (struct rrsig_info *)n; + rem_node(n); + const knot_rrset_t *rrsigs = info->rrsigs; + const uint16_t covered_type = info->type; + mm_free(qdata->mm, info); + knot_rrset_t *synth_sig = NULL; + ret = knot_rrset_synth_rrsig(rrsigs, covered_type, + &synth_sig, &pkt->mm); + if (ret != KNOT_EOK) { + if (ret != KNOT_ENOENT) { + return ret; } } - } + ret = knot_pkt_put(pkt, compr_hint, synth_sig, flags); + if (ret != KNOT_EOK) { + knot_rrset_deep_free(&synth_sig, 1, &pkt->mm); + return ret; + } + }; - return ret; + assert(EMPTY_LIST(qdata->rrsigs)); + + return KNOT_EOK; } diff --git a/src/knot/nameserver/nsec_proofs.h b/src/knot/nameserver/nsec_proofs.h index e8b1ad18d..59d18b0e1 100644 --- a/src/knot/nameserver/nsec_proofs.h +++ b/src/knot/nameserver/nsec_proofs.h @@ -46,7 +46,7 @@ int nsec_prove_nodata(knot_pkt_t *pkt, struct query_data *qdata); int nsec_prove_dp_security(knot_pkt_t *pkt, struct query_data *qdata); /*! \brief Append missing RRSIGs for current processing section. */ -int nsec_append_rrsigs(knot_pkt_t *pkt, bool optional); +int nsec_append_rrsigs(knot_pkt_t *pkt, struct query_data *qdata, bool optional); #endif /* _KNOT_NSEC_PROOFS_H_ */ diff --git a/src/knot/nameserver/process_query.c b/src/knot/nameserver/process_query.c index 047d63cd0..8d8bb02b6 100644 --- a/src/knot/nameserver/process_query.c +++ b/src/knot/nameserver/process_query.c @@ -50,8 +50,9 @@ static void query_data_init(knot_process_t *ctx, void *module_param) data->mm = &ctx->mm; data->param = (struct process_query_param*)module_param; - /* Initialize list. */ + /* Initialize lists. */ init_list(&data->wildcards); + init_list(&data->rrsigs); } int process_query_begin(knot_process_t *ctx, void *module_param) @@ -79,6 +80,7 @@ int process_query_reset(knot_process_t *ctx) /* Free allocated data. */ knot_pkt_free(&qdata->query); ptrlist_free(&qdata->wildcards, qdata->mm); + ptrlist_free(&qdata->rrsigs, qdata->mm); if (qdata->ext_cleanup != NULL) { qdata->ext_cleanup(qdata); } diff --git a/src/knot/nameserver/process_query.h b/src/knot/nameserver/process_query.h index f2981de42..66c320279 100644 --- a/src/knot/nameserver/process_query.h +++ b/src/knot/nameserver/process_query.h @@ -79,6 +79,7 @@ struct query_data { knot_pkt_t *query; /*!< Query to be solved. */ const zone_t *zone; /*!< Zone from which is answered. */ list_t wildcards; /*!< Visited wildcards. */ + list_t rrsigs; /*!< Section RRSIGs. */ /* Current processed name and nodes. */ const knot_node_t *node, *encloser, *previous; @@ -104,6 +105,13 @@ struct wildcard_hit { const knot_dname_t *sname; /* Name leading to this node. */ }; +/*! \brief RRSIGs info. */ +struct rrsig_info { + node_t n; + const knot_rrset_t *rrsigs; /* RRSIGs for node. */ + uint16_t type; /* Covered type. */ +}; + /*! * \brief Initialize query processing context. * diff --git a/src/knot/updates/xfr-in.c b/src/knot/updates/xfr-in.c index 78bdb2f9f..0ff25c4af 100644 --- a/src/knot/updates/xfr-in.c +++ b/src/knot/updates/xfr-in.c @@ -134,7 +134,7 @@ int xfrin_create_ixfr_query(const zone_t *zone, knot_pkt_t *pkt) knot_node_t *apex = zone->contents->apex; const knot_rrset_t *soa = knot_node_rrset(apex, KNOT_RRTYPE_SOA); knot_pkt_begin(pkt, KNOT_AUTHORITY); - return knot_pkt_put(pkt, COMPR_HINT_QNAME, soa, NULL, 0); + return knot_pkt_put(pkt, COMPR_HINT_QNAME, soa, 0); } /*----------------------------------------------------------------------------*/ diff --git a/src/knot/zone/semantic-check.c b/src/knot/zone/semantic-check.c index fff610e05..9ff3f0788 100644 --- a/src/knot/zone/semantic-check.c +++ b/src/knot/zone/semantic-check.c @@ -439,8 +439,8 @@ static int check_rrsig_in_rrset(err_handler_t *handler, return KNOT_ENOMEM; } knot_rrset_t *rrsigs = NULL; - ret = knot_rrset_synth_rrsig(rrset, - knot_node_rrset(node, KNOT_RRTYPE_RRSIG), + ret = knot_rrset_synth_rrsig(knot_node_rrset(node, KNOT_RRTYPE_RRSIG), + rrset->type, &rrsigs, NULL); if (ret != KNOT_EOK && ret != KNOT_ENOENT) { return ret; diff --git a/src/libknot/dnssec/rrset-sign.c b/src/libknot/dnssec/rrset-sign.c index a22cc4f1a..5e9184a37 100644 --- a/src/libknot/dnssec/rrset-sign.c +++ b/src/libknot/dnssec/rrset-sign.c @@ -361,7 +361,8 @@ int knot_is_valid_signature(const knot_rrset_t *covered, // Synthesize RRSIG for covered RRSet knot_rrset_t *synth_rrsigs = NULL; - int result = knot_rrset_synth_rrsig(covered, rrsigs, &synth_rrsigs, NULL); + int result = knot_rrset_synth_rrsig(rrsigs, covered->type, + &synth_rrsigs, NULL); if (result != KNOT_EOK) { return result; } diff --git a/src/libknot/packet/compr.h b/src/libknot/packet/compr.h index 4883ea63e..54a555fa6 100644 --- a/src/libknot/packet/compr.h +++ b/src/libknot/packet/compr.h @@ -63,7 +63,6 @@ typedef struct { uint16_t pos; /* RRSet position in the packet. */ uint16_t flags; /* RRSet flags. */ uint16_t compress_ptr[COMPR_HINT_COUNT]; /* Array of compr. ptr hints. */ - const knot_rrset_t *rrsigs; /* Optional: RRSet containing RRSet's RRSIGs. */ } knot_rrinfo_t; /*! diff --git a/src/libknot/packet/pkt.c b/src/libknot/packet/pkt.c index a8dd73c83..d10512e9a 100644 --- a/src/libknot/packet/pkt.c +++ b/src/libknot/packet/pkt.c @@ -495,7 +495,7 @@ int knot_pkt_put_opt(knot_pkt_t *pkt) return KNOT_EOK; } -int knot_pkt_put(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr, const knot_rrset_t *rrsigs, uint16_t flags) +int knot_pkt_put(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr, uint16_t flags) { dbg_packet("%s(%p, %u, %p, %u)\n", __func__, pkt, compr_hint, rr, flags); if (pkt == NULL || rr == NULL) { @@ -507,7 +507,6 @@ int knot_pkt_put(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr, c rrinfo->pos = pkt->size; rrinfo->flags = flags; rrinfo->compress_ptr[0] = compr_hint; - rrinfo->rrsigs = rr->type != KNOT_RRTYPE_RRSIG ? rrsigs : NULL; pkt->rr[pkt->rrset_count] = rr; /* Check for double insertion. */ diff --git a/src/libknot/packet/pkt.h b/src/libknot/packet/pkt.h index 6b8e0e82f..56113adb4 100644 --- a/src/libknot/packet/pkt.h +++ b/src/libknot/packet/pkt.h @@ -227,11 +227,10 @@ int knot_pkt_put_opt(knot_pkt_t *pkt); * \param pkt * \param compr_hint Compression hint, see enum knot_compr_hint or absolute position. * \param rr Given RRSet. - * \param rrsigs RRSIGs of all RRs in node (optional). * \param flags RRSet flags (set PF_FREE if you want RRSet to be freed with the packet). * \return KNOT_EOK, KNOT_ESPACE, various errors */ -int knot_pkt_put(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr, const knot_rrset_t *sig, uint16_t flags); +int knot_pkt_put(knot_pkt_t *pkt, uint16_t compr_hint, const knot_rrset_t *rr, uint16_t flags); /*! \brief Get description of the given packet section. */ const knot_pktsection_t *knot_pkt_section(const knot_pkt_t *pkt, knot_section_t section_id); diff --git a/src/libknot/rrset.c b/src/libknot/rrset.c index 69afd9ca3..0b181a576 100644 --- a/src/libknot/rrset.c +++ b/src/libknot/rrset.c @@ -1590,14 +1590,14 @@ static int add_rdata_to_rrsig(knot_rrset_t *new_sig, uint16_t type, return knot_rrset_rr_count(new_sig) > 0 ? KNOT_EOK : KNOT_ENOENT; } -int knot_rrset_synth_rrsig(const knot_rrset_t *covered, const knot_rrset_t *rrsigs, +int knot_rrset_synth_rrsig(const knot_rrset_t *rrsigs, uint16_t type, knot_rrset_t **out_sig, mm_ctx_t *mm) { - if (covered == NULL || rrsigs == NULL) { + if (rrsigs == NULL) { return KNOT_ENOENT; } - if (out_sig == NULL || !knot_dname_is_equal(covered->owner, rrsigs->owner)) { + if (out_sig == NULL) { return KNOT_EINVAL; } @@ -1606,7 +1606,7 @@ int knot_rrset_synth_rrsig(const knot_rrset_t *covered, const knot_rrset_t *rrsi return KNOT_ENOMEM; } - int ret = add_rdata_to_rrsig(*out_sig, covered->type, rrsigs, mm); + int ret = add_rdata_to_rrsig(*out_sig, type, rrsigs, mm); if (ret != KNOT_EOK) { knot_rrset_deep_free(out_sig, 1, mm); return ret; diff --git a/src/libknot/rrset.h b/src/libknot/rrset.h index 81e3e49ee..6a80e74e3 100644 --- a/src/libknot/rrset.h +++ b/src/libknot/rrset.h @@ -387,15 +387,14 @@ int rrset_additional_needed(uint16_t rrtype); /*! * \brief Creates RRSIG record from node RRSIGs for given RRSet. * - * \param covered RRSet to create RRSIG for. * \param rrsigs Node RRSIGs. + * \param type Type to cover. * \param out_sig Output RRSIG. * \param mm Memory context. * * \return KNOT_E* */ -int knot_rrset_synth_rrsig(const knot_rrset_t *covered, - const knot_rrset_t *rrsigs, knot_rrset_t **out_sig, +int knot_rrset_synth_rrsig(const knot_rrset_t *rrsigs, uint16_t type, knot_rrset_t **out_sig, mm_ctx_t *mm); size_t knot_rrset_rr_count(const knot_rrset_t *rrset); diff --git a/src/utils/dig/dig_exec.c b/src/utils/dig/dig_exec.c index 24005fa62..9b2409103 100644 --- a/src/utils/dig/dig_exec.c +++ b/src/utils/dig/dig_exec.c @@ -130,7 +130,7 @@ static knot_pkt_t* create_query_packet(const query_t *query) // Add authority section. knot_pkt_begin(packet, KNOT_AUTHORITY); - ret = knot_pkt_put(packet, 0, soa, NULL, KNOT_PF_FREE); + ret = knot_pkt_put(packet, 0, soa, KNOT_PF_FREE); if (ret != KNOT_EOK) { knot_rrset_deep_free(&soa, 1, &packet->mm); knot_pkt_free(&packet); diff --git a/src/utils/nsupdate/nsupdate_exec.c b/src/utils/nsupdate/nsupdate_exec.c index 159cc82b0..e825cd567 100644 --- a/src/utils/nsupdate/nsupdate_exec.c +++ b/src/utils/nsupdate/nsupdate_exec.c @@ -337,7 +337,7 @@ static int rr_list_to_packet(knot_pkt_t *dst, list_t *list) int ret = KNOT_EOK; ptrnode_t *node = NULL; WALK_LIST(node, *list) { - ret = knot_pkt_put(dst, COMPR_HINT_NONE, (knot_rrset_t *)node->d, NULL, 0); + ret = knot_pkt_put(dst, COMPR_HINT_NONE, (knot_rrset_t *)node->d, 0); if (ret != KNOT_EOK) { break; } diff --git a/tests/pkt.c b/tests/pkt.c index 29e71f76c..210322d04 100644 --- a/tests/pkt.c +++ b/tests/pkt.c @@ -105,7 +105,7 @@ int main(int argc, char *argv[]) /* Write ANSWER section. */ rrsets[0] = knot_rrset_new(dnames[0], KNOT_RRTYPE_A, KNOT_CLASS_IN, NULL); knot_rrset_add_rr(rrsets[0], RDVAL(0), RDLEN(0), TTL, NULL); - ret = knot_pkt_put(out, COMPR_HINT_QNAME, rrsets[0], NULL, 0); + ret = knot_pkt_put(out, COMPR_HINT_QNAME, rrsets[0], 0); ok(ret == KNOT_EOK, "pkt: write ANSWER"); /* Begin AUTHORITY. */ @@ -117,7 +117,7 @@ int main(int argc, char *argv[]) for (unsigned i = 1; i < NAMECOUNT; ++i) { rrsets[i] = knot_rrset_new(dnames[i], KNOT_RRTYPE_NS, KNOT_CLASS_IN, NULL); knot_rrset_add_rr(rrsets[i], RDVAL(i), RDLEN(i), TTL, NULL); - ret |= knot_pkt_put(out, COMPR_HINT_NONE, rrsets[i], NULL, 0); + ret |= knot_pkt_put(out, COMPR_HINT_NONE, rrsets[i], 0); } ok(ret == KNOT_EOK, "pkt: write AUTHORITY(%u)", NAMECOUNT - 1); diff --git a/tests/process_query.c b/tests/process_query.c index dbd850f00..437119dc7 100644 --- a/tests/process_query.c +++ b/tests/process_query.c @@ -196,7 +196,7 @@ int main(int argc, char *argv[]) /* Append SOA RR. */ knot_rrset_t *soa_rr = knot_node_get_rrset(zone->contents->apex, KNOT_RRTYPE_SOA); knot_pkt_begin(query, KNOT_AUTHORITY); - knot_pkt_put(query, COMPR_HINT_NONE, soa_rr, NULL, 0); + knot_pkt_put(query, COMPR_HINT_NONE, soa_rr, 0); exec_query(&query_ctx, "IN/ixfr", query->wire, query->size, KNOT_RCODE_NOTAUTH); /* \note Tests below are not possible without proper zone and zone data. */ -- GitLab