From c6cb107ac2a7e0b9fc94b81030cdaf288bcc0ea2 Mon Sep 17 00:00:00 2001 From: Daniel Salzman Date: Tue, 10 Feb 2026 20:05:30 +0100 Subject: [PATCH] nameserver: extend process_query --- src/knot/nameserver/process_query.c | 317 +++++++++++++++++++++------- src/knot/nameserver/process_query.h | 7 + 2 files changed, 247 insertions(+), 77 deletions(-) diff --git a/src/knot/nameserver/process_query.c b/src/knot/nameserver/process_query.c index cd0ea5ea0..c097a1e85 100644 --- a/src/knot/nameserver/process_query.c +++ b/src/knot/nameserver/process_query.c @@ -10,6 +10,7 @@ #include "knot/dnssec/rrset-sign.h" #include "knot/nameserver/process_query.h" #include "knot/nameserver/query_module.h" +#include "knot/nameserver/query_state.h" #include "knot/nameserver/chaos.h" #include "knot/nameserver/internet.h" #include "knot/nameserver/axfr.h" @@ -176,8 +177,42 @@ static knot_layer_state_t query_chaos(knot_pkt_t *pkt, knot_layer_t *ctx) return KNOT_STATE_DONE; } +/*! + * \brief Lookup zone from plan if available, else fallback to regular DB lookup. + */ +static zone_t *lookup_zone(struct query_plan *plan, knotd_qdata_t *qdata, knot_pkt_t *query, + knot_zonedb_t *db, const knot_dname_t *zone_name, bool is_suffix_match) +{ + struct query_step *step; + int next_state = KNOT_STATE_PRODUCE; + qdata->extra->zone_lookup_params.is_suffix_match = is_suffix_match; + qdata->extra->zone_lookup_params.zone_name = zone_name; + + zone_t *static_zone_response = is_suffix_match ? knot_zonedb_find_suffix(db, zone_name) + : knot_zonedb_find(db, zone_name); + if (static_zone_response != NULL) { + return static_zone_response; + } + + if (plan != NULL) { + WALK_LIST(step, plan->stage[KNOTD_STAGE_ZONE_LOOKUP]) { + next_state = step->general_hook(next_state, query, qdata, step->ctx); + if (next_state == KNOT_STATE_FAIL) { + break; + } + } + } + + if (next_state == KNOTD_STATE_ZONE_LOOKUPDONE) { + return qdata->extra->zone; + } + + return NULL; +} + /*! \brief Find zone for given question. */ -static zone_t *answer_zone_find(const knot_pkt_t *query, knot_zonedb_t *zonedb) +static zone_t *answer_zone_find(struct query_plan *plan, knotd_qdata_t *qdata, + knot_pkt_t *query, knot_zonedb_t *zonedb) { uint16_t qtype = knot_pkt_qtype(query); uint16_t qclass = knot_pkt_qclass(query); @@ -197,7 +232,7 @@ static zone_t *answer_zone_find(const knot_pkt_t *query, knot_zonedb_t *zonedb) bool deleg_query = (qtype == KNOT_RRTYPE_DELEG) && knot_pkt_has_deleg_aware(query); if ((ds_query || deleg_query) && qname[0] != '\0') { const knot_dname_t *parent = knot_dname_next_label(qname); - zone = knot_zonedb_find_suffix(zonedb, parent); + zone = lookup_zone(plan, qdata, query, zonedb, parent, true); /* If zone does not exist, search for its parent zone, this will later result to NODATA answer. */ /*! \note This is not 100% right, it may lead to DS name for example @@ -208,10 +243,10 @@ static zone_t *answer_zone_find(const knot_pkt_t *query, knot_zonedb_t *zonedb) if (zone == NULL) { if (query_type(query) == KNOTD_QUERY_TYPE_NORMAL) { - zone = knot_zonedb_find_suffix(zonedb, qname); + zone = lookup_zone(plan, qdata, query, zonedb, qname, true); } else { // Direct match required. - zone = knot_zonedb_find(zonedb, qname); + zone = lookup_zone(plan, qdata, query, zonedb, qname, false); } } @@ -439,9 +474,9 @@ static int answer_edns_put(knot_pkt_t *resp, knotd_qdata_t *qdata) } /*! \brief Initialize response, sizes and find zone from which we're going to answer. */ -static int prepare_answer(knot_pkt_t *query, knot_pkt_t *resp, knot_layer_t *ctx) +static int prepare_answer(struct query_plan *plan, knotd_qdata_t *qdata, knot_pkt_t *query, + knot_pkt_t *resp, knot_layer_t *ctx) { - knotd_qdata_t *qdata = QUERY_DATA(ctx); server_t *server = qdata->params->server; /* Initialize response. */ @@ -458,8 +493,9 @@ static int prepare_answer(knot_pkt_t *query, knot_pkt_t *resp, knot_layer_t *ctx } /* Update maximal answer size. */ + uint16_t resp_size = KNOT_WIRE_MAX_PKTSIZE; if (qdata->params->proto == KNOTD_QUERY_PROTO_UDP) { - resp->max_size = KNOT_WIRE_MIN_PKTSIZE; + resp_size = KNOT_WIRE_MIN_PKTSIZE; if (knot_pkt_has_edns(query)) { uint16_t server_size; switch (knotd_qdata_remote_addr(qdata)->ss_family) { @@ -474,11 +510,10 @@ static int prepare_answer(knot_pkt_t *query, knot_pkt_t *resp, knot_layer_t *ctx } uint16_t client_size = knot_edns_get_payload(query->opt_rr); uint16_t transfer = MIN(client_size, server_size); - resp->max_size = MAX(resp->max_size, transfer); + resp_size = MAX(resp_size, transfer); } - } else { - resp->max_size = KNOT_WIRE_MAX_PKTSIZE; } + resp->max_size = MIN(resp->max_size, resp_size); /* All supported OPCODEs require a question. */ const knot_dname_t *qname = knot_pkt_qname(query); @@ -496,7 +531,7 @@ static int prepare_answer(knot_pkt_t *query, knot_pkt_t *resp, knot_layer_t *ctx } /* Find zone for QNAME. */ - qdata->extra->zone = answer_zone_find(query, server->zone_db); + qdata->extra->zone = answer_zone_find(plan, qdata, query, server->zone_db); if (qdata->extra->zone != NULL && qdata->extra->contents == NULL) { qdata->extra->contents = qdata->extra->zone->contents; } @@ -564,12 +599,20 @@ static knot_layer_state_t process_query_err(knot_layer_t *ctx, knot_pkt_t *pkt) return KNOT_STATE_DONE; } +#ifdef ENABLE_ASYNC_QUERY_HANDLING +#define NON_CONTINUABLE_STATE(next_state) ((next_state) == KNOT_STATE_FAIL || (next_state) == KNOT_STATE_ASYNC) +#define BREAK_IF_ASYNC(next_state) if ((next_state) == KNOT_STATE_ASYNC) { break; } +#else +#define NON_CONTINUABLE_STATE(next_state) ((next_state) == KNOT_STATE_FAIL) +#define BREAK_IF_ASYNC(next_state) +#endif + #define PROCESS_BEGIN(plan, step, next_state, qdata) \ if (plan != NULL) { \ - WALK_LIST(step, plan->stage[KNOTD_STAGE_BEGIN]) { \ - assert(step->type == QUERY_HOOK_TYPE_GENERAL); \ - next_state = step->general_hook(next_state, pkt, qdata, step->ctx); \ - if (next_state == KNOT_STATE_FAIL) { \ + WALK_LIST_RESUME((step), plan->stage[KNOTD_STAGE_BEGIN]) { \ + assert((step)->type == QUERY_HOOK_TYPE_GENERAL); \ + next_state = (step)->general_hook(next_state, pkt, qdata, (step)->ctx); \ + if (NON_CONTINUABLE_STATE(next_state)) { \ goto finish; \ } \ } \ @@ -577,15 +620,46 @@ static knot_layer_state_t process_query_err(knot_layer_t *ctx, knot_pkt_t *pkt) #define PROCESS_END(plan, step, next_state, qdata) \ if (plan != NULL) { \ - WALK_LIST(step, plan->stage[KNOTD_STAGE_END]) { \ - assert(step->type == QUERY_HOOK_TYPE_GENERAL); \ - next_state = step->general_hook(next_state, pkt, qdata, step->ctx); \ + WALK_LIST_RESUME((step), plan->stage[KNOTD_STAGE_END]) { \ + assert((step)->type == QUERY_HOOK_TYPE_GENERAL); \ + next_state = (step)->general_hook(next_state, pkt, qdata, (step)->ctx); \ if (next_state == KNOT_STATE_FAIL) { \ next_state = process_query_err(ctx, pkt); \ } \ + BREAK_IF_ASYNC(next_state); \ } \ } +static void init_state_machine(state_machine_t *state) +{ + memset(state, 0, sizeof(*state)); + state->process_query_next_state = KNOT_STATE_PRODUCE; +} + +#ifdef ENABLE_ASYNC_QUERY_HANDLING +static int complete_async_call(knotd_qdata_t *qdata) +{ + return qdata->params->async_completed_callback(qdata->params); +} + +static int async_operation_in_completed_callback(knotd_qdata_t *qdata, int state) +{ + assert(qdata->state); + state_machine_t *state_machine = qdata->state; + state_machine->process_query_next_state_in = state; + return complete_async_call(qdata); +} + +static int async_operation_completed_callback(knotd_qdata_t *qdata, int state) +{ + assert(qdata->state); + state_machine_t *state_machine = qdata->state; + state_machine->process_query_next_state = state; + + return complete_async_call(qdata); +} +#endif + static int process_query_out(knot_layer_t *ctx, knot_pkt_t *pkt) { assert(pkt && ctx); @@ -595,9 +669,10 @@ static int process_query_out(knot_layer_t *ctx, knot_pkt_t *pkt) knotd_qdata_t *qdata = QUERY_DATA(ctx); struct query_plan *plan = conf()->query_plan; struct query_plan *zone_plan = NULL; - struct query_step *step; - - int next_state = KNOT_STATE_PRODUCE; + state_machine_t *state = NULL; + struct query_step *step = NULL; + struct query_step **step_to_use = &step; + int next_state; /* Check parse state. */ knot_pkt_t *query = qdata->query; @@ -607,10 +682,44 @@ static int process_query_out(knot_layer_t *ctx, knot_pkt_t *pkt) goto finish; } - /* Preprocessing. */ - if (prepare_answer(query, pkt, ctx) != KNOT_EOK) { - next_state = KNOT_STATE_FAIL; - goto finish; + state = qdata->state; + if (state == NULL) { + state = mm_alloc(ctx->mm, sizeof(*state)); + if (state == NULL) { + qdata->rcode = KNOT_RCODE_SERVFAIL; + next_state = KNOT_STATE_FAIL; + goto finish; + } + init_state_machine(state); + qdata->state = state; +#ifdef ENABLE_ASYNC_QUERY_HANDLING + qdata->async_completed = async_operation_completed_callback; + qdata->async_in_completed = async_operation_in_completed_callback; +#endif + } + step_to_use = &state->step; + + next_state = state->process_query_next_state; +#ifdef ENABLE_ASYNC_QUERY_HANDLING + assert(next_state != KNOT_STATE_ASYNC); /* at the beginning or resuming cant be in async */ +#endif + if (NON_CONTINUABLE_STATE(next_state)) { + if (state->process_query_state == PROCESS_QUERY_STATE_DONE_ZONE_PLAN_BEGIN) { + /* Async state and failurs are result of query_* methods + * Go to query_* and recover execution from there. */ + goto run_query; + } else { + goto finish; + } + } + + STATE_MACHINE_RUN_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_PREPARE_ANSWER) { + /* Preprocessing. */ + if (prepare_answer(plan, qdata, query, pkt, ctx) != KNOT_EOK) { + next_state = KNOT_STATE_FAIL; + goto finish; + } + STATE_MACHINE_COMPLETED_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_PREPARE_ANSWER); } if (qdata->extra->zone != NULL && qdata->extra->zone->query_plan != NULL) { @@ -618,77 +727,128 @@ static int process_query_out(knot_layer_t *ctx, knot_pkt_t *pkt) } /* Before query processing code. */ - PROCESS_BEGIN(plan, step, next_state, qdata); - PROCESS_BEGIN(zone_plan, step, next_state, qdata); - - /* Answer based on qclass. */ - if (next_state == KNOT_STATE_PRODUCE) { - switch (knot_pkt_qclass(pkt)) { - case KNOT_CLASS_CH: - next_state = query_chaos(pkt, ctx); - break; - case KNOT_CLASS_ANY: - case KNOT_CLASS_IN: - next_state = query_internet(pkt, ctx); - break; - default: - qdata->rcode = KNOT_RCODE_REFUSED; - next_state = KNOT_STATE_FAIL; - break; - } + STATE_MACHINE_RUN_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_PLAN_BEGIN) { + PROCESS_BEGIN(plan, *step_to_use, next_state, qdata); + STATE_MACHINE_COMPLETED_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_PLAN_BEGIN); } - /* Postprocessing. */ - if (next_state == KNOT_STATE_DONE || next_state == KNOT_STATE_PRODUCE) { - /* Move to Additionals to add OPT and TSIG. */ - if (pkt->current != KNOT_ADDITIONAL) { - (void)knot_pkt_begin(pkt, KNOT_ADDITIONAL); - } + STATE_MACHINE_RUN_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_ZONE_PLAN_BEGIN) { + PROCESS_BEGIN(zone_plan, *step_to_use, next_state, qdata); + STATE_MACHINE_COMPLETED_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_ZONE_PLAN_BEGIN); + } - /* Put OPT RR to the additional section. */ - if (answer_edns_put(pkt, qdata) != KNOT_EOK) { - qdata->rcode = KNOT_RCODE_FORMERR; - next_state = KNOT_STATE_FAIL; - goto finish; +run_query: + STATE_MACHINE_RUN_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_QUERY) { + /* Answer based on qclass. */ + if (next_state == KNOT_STATE_PRODUCE) { + switch (knot_pkt_qclass(pkt)) { + case KNOT_CLASS_CH: + next_state = query_chaos(pkt, ctx); + break; + case KNOT_CLASS_ANY: + case KNOT_CLASS_IN: + next_state = query_internet(pkt, ctx); + break; + default: + qdata->rcode = KNOT_RCODE_REFUSED; + next_state = KNOT_STATE_FAIL; + break; + } } + STATE_MACHINE_COMPLETED_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_QUERY); + } - /* Transaction security (if applicable). */ - if (process_query_sign_response(pkt, qdata) != KNOT_EOK) { - next_state = KNOT_STATE_FAIL; - goto finish; - } + STATE_MACHINE_RUN_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_POST_QUERY) { + /* Postprocessing. */ + if (next_state == KNOT_STATE_DONE || next_state == KNOT_STATE_PRODUCE) { + /* Move to Additionals to add OPT and TSIG. */ + if (pkt->current != KNOT_ADDITIONAL) { + (void)knot_pkt_begin(pkt, KNOT_ADDITIONAL); + } - /* Optional postprocessing with known final EDNS + TSIG (for XFR stats). */ - if (qdata->extra->ext_finished != NULL) { - qdata->extra->ext_finished(qdata, pkt, next_state); + /* Put OPT RR to the additional section. */ + if (answer_edns_put(pkt, qdata) != KNOT_EOK) { + qdata->rcode = KNOT_RCODE_FORMERR; + next_state = KNOT_STATE_FAIL; + goto finish; + } + + /* Transaction security (if applicable). */ + if (process_query_sign_response(pkt, qdata) != KNOT_EOK) { + next_state = KNOT_STATE_FAIL; + goto finish; + } + + /* Optional postprocessing with known final EDNS + TSIG (for XFR stats). */ + if (qdata->extra->ext_finished != NULL) { + qdata->extra->ext_finished(qdata, pkt, next_state); + } } + STATE_MACHINE_COMPLETED_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_POST_QUERY); } finish: - switch (next_state) { - case KNOT_STATE_NOOP: - break; - case KNOT_STATE_FAIL: - /* Error processing. */ - next_state = process_query_err(ctx, pkt); - break; - case KNOT_STATE_FINAL: - /* Just skipped postprocessing. */ - next_state = KNOT_STATE_DONE; - break; - default: - set_rcode_to_packet(pkt, qdata); + STATE_MACHINE_RUN_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_HANDLE_ERROR) { + switch (next_state) { + case KNOT_STATE_NOOP: + break; + case KNOT_STATE_FAIL: + /* Error processing. */ + next_state = process_query_err(ctx, pkt); + break; + case KNOT_STATE_FINAL: + /* Just skipped postprocessing. */ + next_state = KNOT_STATE_DONE; + break; + default: + set_rcode_to_packet(pkt, qdata); + } + STATE_MACHINE_COMPLETED_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_HANDLE_ERROR); } /* After query processing code. */ - PROCESS_END(plan, step, next_state, qdata); - PROCESS_END(zone_plan, step, next_state, qdata); + STATE_MACHINE_RUN_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_PLAN_END) { + PROCESS_END(plan, *step_to_use, next_state, qdata); + STATE_MACHINE_COMPLETED_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_PLAN_END); + } + + STATE_MACHINE_RUN_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_ZONE_PLAN_END) { + PROCESS_END(zone_plan, *step_to_use, next_state, qdata); + STATE_MACHINE_COMPLETED_STATE(state, next_state, KNOT_STATE_ASYNC, process_query_state, PROCESS_QUERY_STATE_DONE_ZONE_PLAN_END); + } rcu_read_unlock(); + if (knot_layer_active_state(next_state)) { + /* Exiting the state machine with an active state will result in more produce calls which need to resume from beginning. + * Reset the state machine so it will execute all steps. */ + if (state) { + init_state_machine(state); + } + } + return next_state; } +#ifdef ENABLE_ASYNC_QUERY_HANDLING +static int process_query_set_async_state(knot_layer_t *ctx, knot_pkt_t *pkt, int layer_state) +{ + assert(pkt && ctx); + knotd_qdata_t *qdata = QUERY_DATA(ctx); + if (qdata != NULL) { + state_machine_t *state = qdata->state; + if (state != NULL) { + state->process_query_next_state = layer_state; + if (layer_state == KNOT_STATE_FAIL) { + state->process_query_next_state_in = KNOTD_IN_STATE_ERROR; + } + } + } + + return layer_state; +} +#endif + bool process_query_acl_check(conf_t *conf, acl_action_t action, knotd_qdata_t *qdata) { @@ -1055,6 +1215,9 @@ const knot_layer_api_t *process_query_layer(void) .finish = &process_query_finish, .consume = &process_query_in, .produce = &process_query_out, +#ifdef ENABLE_ASYNC_QUERY_HANDLING + .set_async_state = &process_query_set_async_state, +#endif }; return &api; } diff --git a/src/knot/nameserver/process_query.h b/src/knot/nameserver/process_query.h index 0c4abc850..fdb4eff2c 100644 --- a/src/knot/nameserver/process_query.h +++ b/src/knot/nameserver/process_query.h @@ -30,6 +30,13 @@ typedef struct knotd_qdata_extra { void *ext; void (*ext_cleanup)(knotd_qdata_t *); /*!< Extensions cleanup callback. */ void (*ext_finished)(knotd_qdata_t *, knot_pkt_t *, int); /*!< Optional postprocessing callback. */ + + struct { + const knot_dname_t *zone_name; + bool is_suffix_match; + } zone_lookup_params; + + int ext_result; /*!< Additional error code from module. Modules MUST return this value for : KNOTD_STAGE_NAME_LOOKUP */ } knotd_qdata_extra_t; /*! \brief Visited wildcard node list. */