Commit 3c745f0b authored by Marek Vavruša's avatar Marek Vavruša

query_module: configurable query processing modules

Current API provides a "query plan" for each zone, which is
basically a set of steps it needs to takes a query to be resolved.
The plan is divided into several stages that reflect answer section
processing. Now this query plan can be altered via configurable modules,
first module is a 'synth_record' that makes synthetic forward/reverse
records if the query isn't satisfied from the zone contents.
parent 7a6ca483
......@@ -223,10 +223,12 @@ libknotd_la_SOURCES = \
knot/nameserver/nsec_proofs.h \
knot/nameserver/process_query.c \
knot/nameserver/process_query.h \
knot/nameserver/synth_record.c \
knot/nameserver/synth_record.h \
knot/nameserver/query_module.c \
knot/nameserver/query_module.h \
knot/nameserver/update.c \
knot/nameserver/update.h \
knot/modules/synth_record.c \
knot/modules/synth_record.h \
knot/other/debug.h \
knot/server/dthreads.c \
knot/server/dthreads.h \
......
......@@ -132,9 +132,7 @@ transfers { lval.t = yytext; return TRANSFERS; }
dnssec-enable { lval.t = yytext; return DNSSEC_ENABLE; }
dnssec-keydir { lval.t = yytext; return DNSSEC_KEYDIR; }
signature-lifetime { lval.t = yytext; return SIGNATURE_LIFETIME; }
synth { lval.t = yytext; return SYNTH; }
forward { lval.t = yytext; return FORWARD; }
reverse { lval.t = yytext; return REVERSE; }
query_module { lval.t = yytext; return QUERY_MODULE; }
interfaces { lval.t = yytext; return INTERFACES; }
address { lval.t = yytext; return ADDRESS; }
......
......@@ -35,7 +35,7 @@
#include "libknot/binary.h"
#include "libknot/edns.h"
#include "knot/server/rrl.h"
#include "knot/nameserver/synth_record.h"
#include "knot/nameserver/query_module.h"
#include "knot/conf/conf.h"
#include "knot/conf/libknotd_la-cf-parse.h" /* Automake generated header. */
......@@ -276,45 +276,15 @@ static void conf_acl_item(void *scanner, char *item)
free(item);
}
static void synth_tpl_create(void *scanner)
static void query_module_create(void *scanner, const char *name, const char *param)
{
synth_template_t *tpl = malloc(sizeof(synth_template_t));
if (tpl == NULL) {
cf_error(scanner, "out of memory");
struct query_module *module = query_module_open(name, param, NULL);
if (module == NULL) {
cf_error(scanner, "cannot load query module '%s'", name);
return;
}
memset(tpl, 0, sizeof(synth_template_t));
add_tail(&this_zone->synth_templates, &tpl->node);
}
static void synth_tpl_define(void *scanner, enum synth_template_type template_type, char* format)
{
synth_template_t *tpl = TAIL(this_zone->synth_templates);
tpl->type = template_type;
tpl->format = format;
}
static void synth_tpl_set_addr(void *scanner, int family, char* addr, int netblock)
{
synth_template_t *tpl = TAIL(this_zone->synth_templates);
sockaddr_set(&tpl->subnet.ss, family, addr, 0);
int prefix_max = IPV4_PREFIXLEN;
if (family == AF_INET6) {
prefix_max = IPV6_PREFIXLEN;
}
SET_NUM(tpl->subnet.prefix, netblock, 0, prefix_max, "prefix length");
free(addr);
}
static void synth_tpl_set_ttl(void *scanner, uint32_t ttl)
{
synth_template_t *tpl = TAIL(this_zone->synth_templates);
SET_NUM(tpl->ttl, ttl, 0, UINT32_MAX, "ttl");
add_tail(&this_zone->query_modules, &module->node);
}
static int conf_key_exists(void *scanner, char *item)
......@@ -514,9 +484,7 @@ static void ident_auto(int tok, conf_t *conf, bool val)
%token <tok> SIGNATURE_LIFETIME
%token <tok> SERIAL_POLICY
%token <tok> SERIAL_POLICY_VAL
%token <tok> SYNTH
%token <tok> REVERSE
%token <tok> FORWARD
%token <tok> QUERY_MODULE
%token <tok> INTERFACES ADDRESS PORT
%token <tok> IPA
......@@ -872,22 +840,16 @@ zone_acl:
}
;
zone_synth_ttl:
| NUM { synth_tpl_set_ttl(scanner, $1.i); }
;
zone_synth_addr:
IPA '/' NUM { synth_tpl_set_addr(scanner, AF_INET, $1.t, $3.i); }
| IPA6 '/' NUM { synth_tpl_set_addr(scanner, AF_INET6, $1.t, $3.i); }
query_module:
TEXT TEXT { query_module_create(scanner, $1.t, $2.t); free($1.t); free($2.t); }
;
zone_synth:
FORWARD TEXT zone_synth_addr zone_synth_ttl { synth_tpl_define(scanner, SYNTH_FORWARD, $2.t); }
| REVERSE TEXT zone_synth_addr zone_synth_ttl { synth_tpl_define(scanner, SYNTH_REVERSE, $2.t); }
query_module_list:
query_module
| query_module ',' query_module_list
| query_module ';' query_module_list
;
zone_synth_start: SYNTH { synth_tpl_create(scanner); }
zone_start:
| USER { conf_zone_start(scanner, strdup($1.t)); }
| REMOTES { conf_zone_start(scanner, strdup($1.t)); }
......@@ -952,7 +914,7 @@ zone:
| zone SERIAL_POLICY SERIAL_POLICY_VAL ';' {
this_zone->serial_policy = $3.i;
}
| zone zone_synth_start zone_synth ';'
| zone QUERY_MODULE '{' query_module_list '}'
;
zones:
......
......@@ -29,7 +29,7 @@
#include "knot/conf/extra.h"
#include "knot/knot.h"
#include "knot/ctl/remote.h"
#include "knot/nameserver/synth_record.h"
#include "knot/nameserver/internet.h"
/*
* Defaults.
......@@ -414,6 +414,27 @@ static int conf_process(conf_t *conf)
}
memcpy(dpos + zname_len, dbext, strlen(dbext) + 1);
zone->ixfr_db = dest;
/* Initialize query plan if modules exist. */
if (!EMPTY_LIST(zone->query_modules)) {
zone->query_plan = query_plan_create(NULL);
if (zone->query_plan == NULL) {
ret = KNOT_ENOMEM;
continue;
}
/* Only supported zone class is now IN. */
internet_query_plan(zone->query_plan);
}
/* Load query modules. */
struct query_module *module = NULL;
WALK_LIST(module, zone->query_modules) {
ret = module->load(zone->query_plan, module);
if (ret != KNOT_EOK) {
break;
}
}
}
hattrie_iter_free(z_iter);
......@@ -942,7 +963,7 @@ void conf_init_zone(conf_zone_t *zone)
init_list(&zone->acl.update_in);
// Initialize synthesis templates
init_list(&zone->synth_templates);
init_list(&zone->query_modules);
}
void conf_free_zone(conf_zone_t *zone)
......@@ -958,13 +979,15 @@ void conf_free_zone(conf_zone_t *zone)
WALK_LIST_FREE(zone->acl.notify_out);
WALK_LIST_FREE(zone->acl.update_in);
/* Free answer synthesis rule list. */
synth_template_t *tpl = NULL, *tpl_next = NULL;
WALK_LIST_DELSAFE(tpl, tpl_next, zone->synth_templates) {
free(tpl->format);
free(tpl);
/* Unload query modules. */
struct query_module *module = NULL, *next = NULL;
WALK_LIST_DELSAFE(module, next, zone->query_modules) {
query_module_close(module);
}
/* Free query plan. */
query_plan_free(zone->query_plan);
free(zone->name);
free(zone->file);
free(zone->ixfr_db);
......
......@@ -43,6 +43,7 @@
#include "knot/updates/acl.h"
#include "common/sockaddr.h"
#include "common/hattrie/hat-trie.h"
#include "knot/nameserver/query_module.h"
/* Constants. */
#define CONFIG_DEFAULT_PORT 53
......@@ -136,7 +137,8 @@ typedef struct conf_zone_t {
list_t update_in; /*!< Remotes accepted for DDNS.*/
} acl;
list_t synth_templates;
struct query_plan *query_plan;
list_t query_modules;
} conf_zone_t;
/*!
......
......@@ -3,7 +3,14 @@
*
* \author Marek Vavrusa <marek.vavrusa@nic.cz>
*
* \brief Synthetic records
* \brief Synthetic records module
*
* Accepted configurations:
* * "forward <prefix> <ttl> <address>/<netblock>"
* * "reverse <prefix> <zone> <ttl> <address>/<netblock>"
*
* Module synthetises forward/reverse records based on a template when
* the queried record can't be found in the zone contents.
*
* \addtogroup query_processing
* @{
......@@ -26,40 +33,11 @@
#ifndef _SYNTH_RECORD_H
#include "knot/nameserver/process_query.h"
#include "knot/updates/acl.h"
#include "common/lists.h"
/*! \brief Supported answer synthesis template types. */
enum synth_template_type {
SYNTH_FORWARD,
SYNTH_REVERSE
};
/*!
* \brief Synthetic response template.
*/
typedef struct synth_template {
node_t node;
enum synth_template_type type;
char *format;
uint32_t ttl;
netblock_t subnet;
} synth_template_t;
#include "knot/nameserver/query_module.h"
/*!
* \brief Return true if it is possible to synthetize response.
* \param qdata
*/
bool synth_answer_possible(struct query_data *qdata);
/*!
* \brief Attempt to synthetize response.
* \param pkt
* \param qdata
* \return EOK if success, else error code
*/
int synth_answer(knot_pkt_t *pkt, struct query_data *qdata);
/*! \brief Module interface. */
int synth_record_load(struct query_plan *plan, struct query_module *self);
int synth_record_unload(struct query_module *self);
#define _SYNTH_RECORD_H
......
......@@ -2,24 +2,13 @@
#include "knot/nameserver/internet.h"
#include "knot/nameserver/nsec_proofs.h"
#include "knot/nameserver/process_query.h"
#include "knot/zone/zonedb.h"
#include "libknot/common.h"
#include "libknot/rdata.h"
#include "common/debug.h"
#include "common/descriptor.h"
#include "knot/server/zones.h"
/*! \brief Query processing states. */
enum {
BEGIN, /* Begin name resolution. */
NODATA, /* Positive result with NO data. */
HIT, /* Positive result. */
MISS, /* Negative result. */
DELEG, /* Result is delegation. */
FOLLOW, /* Resolution not complete (CNAME/DNAME chain). */
ERROR, /* Resolution failed. */
TRUNC /* Finished, but truncated. */
};
/*! \brief Check if given node was already visited. */
static int wildcard_has_visited(struct query_data *qdata, const knot_node_t *node)
{
......@@ -495,7 +484,7 @@ static int solve_name(int state, knot_pkt_t *pkt, struct query_data *qdata)
}
}
static int solve_answer_section(int state, knot_pkt_t *pkt, struct query_data *qdata)
static int solve_answer_section(int state, knot_pkt_t *pkt, struct query_data *qdata, void *ctx)
{
/* Get answer to QNAME. */
state = solve_name(state, pkt, qdata);
......@@ -514,7 +503,7 @@ static int solve_answer_section(int state, knot_pkt_t *pkt, struct query_data *q
return state;
}
static int solve_answer_dnssec(int state, knot_pkt_t *pkt, struct query_data *qdata)
static int solve_answer_dnssec(int state, knot_pkt_t *pkt, struct query_data *qdata, void *ctx)
{
if (!have_dnssec(qdata)) {
return state; /* DNSSEC not supported. */
......@@ -529,7 +518,7 @@ static int solve_answer_dnssec(int state, knot_pkt_t *pkt, struct query_data *qd
}
}
static int solve_authority(int state, knot_pkt_t *pkt, struct query_data *qdata)
static int solve_authority(int state, knot_pkt_t *pkt, struct query_data *qdata, void *ctx)
{
int ret = KNOT_ERROR;
const knot_zone_contents_t *zone_contents = qdata->zone->contents;
......@@ -569,7 +558,7 @@ static int solve_authority(int state, knot_pkt_t *pkt, struct query_data *qdata)
}
}
static int solve_authority_dnssec(int state, knot_pkt_t *pkt, struct query_data *qdata)
static int solve_authority_dnssec(int state, knot_pkt_t *pkt, struct query_data *qdata, void *ctx)
{
if (!have_dnssec(qdata)) {
return state; /* DNSSEC not supported. */
......@@ -612,7 +601,7 @@ static int solve_authority_dnssec(int state, knot_pkt_t *pkt, struct query_data
}
}
static int solve_additional(int state, knot_pkt_t *pkt, struct query_data *qdata)
static int solve_additional(int state, knot_pkt_t *pkt, struct query_data *qdata, void *ctx)
{
/* Put OPT RR. */
int ret = knot_pkt_put_opt(pkt);
......@@ -638,7 +627,7 @@ static int solve_additional(int state, knot_pkt_t *pkt, struct query_data *qdata
}
}
static int solve_additional_dnssec(int state, knot_pkt_t *pkt, struct query_data *qdata)
static int solve_additional_dnssec(int state, knot_pkt_t *pkt, struct query_data *qdata, void *ctx)
{
if (!have_dnssec(qdata)) {
return state; /* DNSSEC not supported. */
......@@ -705,14 +694,73 @@ int ns_put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr,
}
/*! \brief Helper for internet_answer repetitive code. */
#define SOLVE_STEP(solver, state) \
state = solver(state, response, qdata); \
#define SOLVE_STEP(solver, state, context) \
state = (solver)(state, response, qdata, context); \
if (state == TRUNC) { \
return NS_PROC_DONE; \
} else if (state == ERROR) { \
return NS_PROC_FAIL; \
}
static int default_answer(knot_pkt_t *response, struct query_data *qdata)
{
int state = BEGIN;
/* Resolve ANSWER. */
dbg_ns("%s: writing %p ANSWER\n", __func__, response);
knot_pkt_begin(response, KNOT_AUTHORITY);
SOLVE_STEP(solve_answer_section, state, NULL);
SOLVE_STEP(solve_answer_dnssec, state, NULL);
/* Resolve AUTHORITY. */
dbg_ns("%s: writing %p AUTHORITY\n", __func__, response);
knot_pkt_begin(response, KNOT_AUTHORITY);
SOLVE_STEP(solve_authority, state, NULL);
SOLVE_STEP(solve_authority_dnssec, state, NULL);
/* Resolve ADDITIONAL. */
dbg_ns("%s: writing %p ADDITIONAL\n", __func__, response);
knot_pkt_begin(response, KNOT_ADDITIONAL);
SOLVE_STEP(solve_additional, state, NULL);
SOLVE_STEP(solve_additional_dnssec, state, NULL);
/* Write resulting RCODE. */
knot_wire_set_rcode(response->wire, qdata->rcode);
return NS_PROC_DONE;
}
static int planned_answer(struct query_plan *plan, knot_pkt_t *response, struct query_data *qdata)
{
/* Before query processing code. */
int state = BEGIN;
struct query_step *step = NULL;
WALK_LIST(step, plan->stage[QPLAN_BEGIN]) {
SOLVE_STEP(step->process, state, step->ctx);
}
/* Begin processing. */
for (int section = KNOT_ANSWER; section <= KNOT_ADDITIONAL; ++section) {
dbg_ns("%s: writing section %u\n", __func__, section);
knot_pkt_begin(response, section);
WALK_LIST(step, plan->stage[QPLAN_STAGE + section]) {
SOLVE_STEP(step->process, state, step->ctx);
}
}
/* Write resulting RCODE. */
knot_wire_set_rcode(response->wire, qdata->rcode);
/* After query processing code. */
WALK_LIST(step, plan->stage[QPLAN_END]) {
SOLVE_STEP(step->process, state, step->ctx);
}
return NS_PROC_DONE;
}
#undef SOLVE_STEP
int internet_answer(knot_pkt_t *response, struct query_data *qdata)
{
dbg_ns("%s(%p, %p)\n", __func__, response, qdata);
......@@ -735,32 +783,25 @@ int internet_answer(knot_pkt_t *response, struct query_data *qdata)
NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL); /* Expired */
/* Get answer to QNAME. */
dbg_ns("%s: writing %p ANSWER\n", __func__, response);
knot_pkt_begin(response, KNOT_ANSWER);
qdata->name = knot_pkt_qname(qdata->query);
/* Begin processing. */
int state = BEGIN;
SOLVE_STEP(solve_answer_section, state);
SOLVE_STEP(solve_answer_dnssec, state);
/* Resolve AUTHORITY. */
dbg_ns("%s: writing %p AUTHORITY\n", __func__, response);
knot_pkt_begin(response, KNOT_AUTHORITY);
SOLVE_STEP(solve_authority, state);
SOLVE_STEP(solve_authority_dnssec, state);
/* If the zone doesn't have a query plan, go for fast default. */
conf_zone_t *zone_config = qdata->zone->conf;
if (zone_config->query_plan == NULL) {
return default_answer(response, qdata);
}
/* Resolve ADDITIONAL. */
dbg_ns("%s: writing %p ADDITIONAL\n", __func__, response);
knot_pkt_begin(response, KNOT_ADDITIONAL);
SOLVE_STEP(solve_additional, state);
SOLVE_STEP(solve_additional_dnssec, state);
return planned_answer(zone_config->query_plan, response, qdata);
}
/* Write resulting RCODE. */
knot_wire_set_rcode(response->wire, qdata->rcode);
int internet_query_plan(struct query_plan *plan)
{
query_plan_step(plan, QPLAN_ANSWER, solve_answer_section, NULL);
query_plan_step(plan, QPLAN_ANSWER, solve_answer_dnssec, NULL);
query_plan_step(plan, QPLAN_AUTHORITY, solve_authority, NULL);
query_plan_step(plan, QPLAN_AUTHORITY, solve_authority_dnssec, NULL);
query_plan_step(plan, QPLAN_ADDITIONAL, solve_additional, NULL);
query_plan_step(plan, QPLAN_ADDITIONAL, solve_additional_dnssec, NULL);
/* Complete response. */
return NS_PROC_DONE;
return KNOT_EOK;
}
#undef SOLVE_STEP
......@@ -28,10 +28,23 @@
#define _KNOT_INTERNET_H_
#include "libknot/packet/pkt.h"
#include "knot/zone/zonedb.h"
/* Query data (from query processing). */
struct query_data;
struct query_plan;
struct query_module;
/*! \brief Internet query processing states. */
enum {
BEGIN, /* Begin name resolution. */
NODATA, /* Positive result with NO data. */
HIT, /* Positive result. */
MISS, /* Negative result. */
DELEG, /* Result is delegation. */
FOLLOW, /* Resolution not complete (CNAME/DNAME chain). */
ERROR, /* Resolution failed. */
TRUNC /* Finished, but truncated. */
};
/*!
* \brief Answer query from IN class zone.
......@@ -41,6 +54,13 @@ struct query_data;
*/
int internet_answer(knot_pkt_t *resp, struct query_data *qdata);
/*!
* \brief Initialize query plan for IN class zone.
* \param plan
* \return
*/
int internet_query_plan(struct query_plan *plan);
/*!
* \brief Puts RRSet to packet, will store its RRSIG for later use.
*
......
......@@ -8,7 +8,6 @@
#include "knot/nameserver/ixfr.h"
#include "knot/nameserver/update.h"
#include "knot/nameserver/nsec_proofs.h"
#include "knot/nameserver/synth_record.h"
#include "knot/server/notify.h"
#include "knot/server/server.h"
#include "knot/server/rrl.h"
......@@ -168,11 +167,6 @@ int process_query_out(knot_pkt_t *pkt, knot_process_t *ctx)
* Postprocessing.
*/
/* Synthetic response (if applicable). */
if (synth_answer_possible(qdata)) {
next_state = synth_answer(pkt, qdata);
}
/* Transaction security (if applicable). */
if (next_state == NS_PROC_DONE || next_state == NS_PROC_FULL) {
if (process_query_sign_response(pkt, qdata) != KNOT_EOK) {
......
......@@ -199,6 +199,8 @@ int process_query_verify(struct query_data *qdata);
*/
int process_query_sign_response(knot_pkt_t *pkt, struct query_data *qdata);
int process_query_hooks(int qclass, int stage, knot_pkt_t *pkt, struct query_data *qdata);
#endif /* _PROCESS_QUERY_H_ */
/*! @} */
#include "knot/nameserver/query_module.h"
#include "common/mempattern.h"
#include "common/errcode.h"
/* Compiled-in module headers. */
#include "knot/modules/synth_record.h"
/* Compiled-in module table. */
struct compiled_module {
const char *name;
qmodule_load_t load;
qmodule_unload_t unload;
};
/*! \note All modules should be dynamically loaded later on. */
#define MODULE_COUNT 1
struct compiled_module MODULES[MODULE_COUNT] = {
{ "synth_record", &synth_record_load, &synth_record_unload }
};
struct query_plan *query_plan_create(mm_ctx_t *mm)
{
struct query_plan *plan = mm_alloc(mm, sizeof(struct query_plan));
if (plan == NULL) {
return NULL;
}
plan->mm = mm;
for (unsigned i = 0; i < QUERY_PLAN_STAGES; ++i) {
init_list(&plan->stage[i]);
}
return plan;
}
void query_plan_free(struct query_plan *plan)
{
if (plan == NULL) {
return;
}
for (unsigned i = 0; i < QUERY_PLAN_STAGES; ++i) {
struct query_step *step = NULL, *next = NULL;
WALK_LIST_DELSAFE(step, next, plan->stage[i]) {
mm_free(plan->mm, step);
}
}
mm_free(plan->mm, plan);
}
static struct query_step *make_step(mm_ctx_t *mm, qmodule_process_t process, void *ctx)
{
struct query_step *step = mm_alloc(mm, sizeof(struct query_step));
if (step == NULL) {
return NULL;
}
memset(step, 0, sizeof(struct query_step));
step->process = process;
step->ctx = ctx;
return step;
}
int query_plan_step(struct query_plan *plan, int stage, qmodule_process_t process, void *ctx)
{
struct query_step *step = make_step(plan->mm, process, ctx);
if (step == NULL) {
return KNOT_ENOMEM;
}
add_tail(&plan->stage[stage], &step->node);
return KNOT_EOK;
}
struct query_module *query_module_open(const char *name, const char *param, mm_ctx_t *mm)
{
/* Locate compiled-in modules. */
struct compiled_module *found = NULL;
for (unsigned i = 0; i < MODULE_COUNT; ++i) {
if (strcmp(MODULES[i].name, name) == 0) {
found = &MODULES[i];
break;
}
}
/* Module not found. */
if (found == NULL) {
return NULL;
}
struct query_module *module = mm_alloc(mm, sizeof(struct query_module));
if (module == NULL) {
return NULL;
}
memset(module, 0, sizeof(struct query_module));
module->mm = mm;
module->load = found->load;
module->unload = found->unload;
module->param = mm_alloc(mm, strlen(param) + 1);
if (module->param == NULL) {
mm_free(mm, module);
return NULL;
}
strcpy(module->param, param);
return module;
}
void query_module_close(struct query_module *module)
{
if (module == NULL) {
return;
}
module->unload(module);
mm_free(module->mm, module->param);
mm_free(module->mm, module);
}
/*!
* \file query_module.h
*
* \author Marek Vavrusa <marek.vavrusa@nic.cz>
*
* \brief Query module interface
*
* The concept of query plan is simple - each query requires requires a finite
* number of steps to be solved. For example IN query needs to find an answer and
* based on the result process authority and maybe supply additional records.
* This can be represented by a query plan:
* answer => { find_answer },
* authority => { process_authority },
* additional => { process_additional }
*
* The example is obvious, but if a state is passed between the callbacks,
* same principle applies for every query processing.
* This file provides an interface for basic query plan and more importantly
* dynamically loaded modules that can alter query plans.
* For a default internet zone query plan, see \file internet.h
*
* \addtogroup query_processing
* @{
*/
/* Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _QUERY_MODULE_H
#define _QUERY_MODULE_H
#include "common/lists.h"
#include "common/mempattern.h"
#include "libknot/consts.h"
#include "libknot/packet/pkt.h"
/* Query module processing stages. */
enum query_stage {
QPLAN_BEGIN = 0, /* Before query processing. */
QPLAN_STAGE = 1, /* Class-specific processing stages. */
QPLAN_ANSWER = QPLAN_STAGE + KNOT_ANSWER, /* Answer section processing. */
QPLAN_AUTHORITY, /* Authority section processing. */
QPLAN_ADDITIONAL, /* Additional section processing. */
QPLAN_END /* After query processing. */
};
#define QUERY_PLAN_STAGES (QPLAN_END + 1)
/* Forward declarations. */
struct query_data;
struct query_module;
struct query_plan;
/* Module callback required API. */
typedef int (*qmodule_load_t)(struct query_plan *plan, struct query_module *self);
typedef int (*qmodule_unload_t)(struct query_module *self);
typedef int (*qmodule_process_t)(int state, knot_pkt_t *pkt, struct query_data *qdata, void *ctx);
/*!
* Query module is a dynamically loadable unit that can alter query processing plan.
* Module requires load and unload callback handlers and is provided with a context
* and configuration string.