这个系列文章源于网络资料和自己的总结。关于Knot DNS分析的文章网络上不多,由于我的水平有限,错误在所难免。欢迎大家批评指正。
一、什么是查询动态定义
Knot DNS的查询处理流程可以根据配置文件进行动态定义。整个查询流程由几个步骤组成,每个步骤都是由多个模块以list方式组成的query plan,这些步骤分为几个阶段。
- KNOTD_STAGE_BEGIN Before query processing
- KNOTD_STAGE_PREANSWER Before section processing
- KNOTD_STAGE_ANSWER Answer section processing
- KNOTD_STAGE_AUTHORITY Authority section processing
- KNOTD_STAGE_ADDITIONAL Additional section processing
- KNOTD_STAGE_END After query processing
例如,处理class-IN查询需要查找answer,基于之前的状态,他可以添加附加的SOA权威记录,这个动作就是上面提到的步骤
二、实现原理
Knot DNS以mod-name作为全局唯一标识,将配置文件语句与代码定义的控制块关联起来,根据配置文件的定义动态加载初始化模块,并在查询处理流程中被执行。
1. 模块加载
在加载模块的时候,Knot DNS解析配置文件中的模块名以及模块ID,根据模块名查找代码中定义的控制块,经过多重检查后,调用之前定义的load函数进行模块初始化并将process-hook函数追加到mod->plan->stage[knotd_stage_t]这个list-array的尾端,mod数据块根据其作用域不同分别挂到conf()->query_modules 或者 zone->query_modules中。
2. 查询处理
在处理查询的主要流程中遍历依次执行之前注册的process-hook函数。不同锚点以stage下标区分
/* Resolve PREANSWER. */
if (plan != NULL) {
WALK_LIST(step, plan->stage[KNOTD_STAGE_PREANSWER]) {
SOLVE_STEP(step->process, state, step->ctx);
}
}
涉及到的函数接口:
- conf_mod_id() 解析配置文件中的模块名以及模块ID
- conf_mod_find() 根据模块名查找代码中定义的控制块
- typedef int (*knotd_mod_load_f)(knotd_mod_t *mod) 模块初始化load函数原型
- typedef knotd_in_state_t (*knotd_mod_in_hook_f)(knotd_in_state_t state, knot_pkt_t *pkt, knotd_qdata_t *qdata, knotd_mod_t *mod) process-hook函数原型
- answer_query() 查询处理的主要业务逻辑
三、自定义一个模块
Knot DNS提供了两种注册方式,一种是提供KNOTD_MOD_API()宏,构建全局结构体;一种是提供全局数组STATIC_MODULES[]
1. 注册宏参数说明
/*! Module API instance initialization helper macro. */
#define KNOTD_MOD_API(mod_name, mod_flags, mod_load, mod_unload, mod_conf, mod_conf_check) \
__attribute__((visibility("default"))) \
const knotd_mod_api_t KNOTD_MOD_API_NAME(mod_name) = { \
.version = KNOTD_MOD_ABI_VERSION, \
.name = KNOTD_MOD_NAME_PREFIX #mod_name, \
.flags = mod_flags, \
.load = mod_load, \
.unload = mod_unload, \
.config = mod_conf, \
.config_check = mod_conf_check, \
}
- const knotd_mod_api_t KNOTD_MOD_API_NAME(mod_name) 字符串
该结构块使用knotd_mod_api_##mod_name,所以编码必须保证模块名的全局唯一性
- .flags = mod_flags 枚举
标志位,表示该模块的作用域,常用三种值
KNOTD_MOD_FLAG_SCOPE_GLOBAL
KNOTD_MOD_FLAG_SCOPE_ZONE
KNOTD_MOD_FLAG_SCOPE_ANY
- .load = mod_load 回调函数
原型 typedef int (*knotd_mod_load_f)(knotd_mod_t *mod); 模块加载时被 conf_activate_modules() 调用以进行模块初始化与注册hook-process回掉
- .unload = mod_unload 回调函数
原型 typedef void (*knotd_mod_unload_f)(knotd_mod_t *mod); 模块去初始化阶段被调用,主要用来释放内存,socket等系统资源
- .config = mod_conf 数据结构
用来注册指定配置文件语法与默认值
- .config_check = mod_conf_check 回调函数
原型 typedef int (*knotd_conf_check_f)(knotd_conf_check_args_t *args); 模块加载时被用来检查模块配置
2. 实现三个主要的接口函数
初始化函数:模块相关配置的初始化,系统资源的初始化,业务入口函数的注册
使用 _public_ knotd_conf_t knotd_conf_mod(knotd_mod_t *mod, const yp_name_t *item_name) 获取模块配置
使用_public_ int knotd_mod_hook(knotd_mod_t *mod, knotd_stage_t stage, knotd_mod_hook_f hook) 注册业务入口函数
系统资源,包括内存池、队列、socket、描述符、后台线程等的初始化
去初始化函数:主要是释放在初始化阶段申请的系统资源
查询处理入口函数 :业务处理流程的不同阶段,进行当前模块功能的执行
typedef knotd_state_t (*knotd_mod_hook_f) (knotd_state_t state, knot_pkt_t *pkt, knotd_qdata_t *qdata, knotd_mod_t *mod);
knotd_state_t state 查询流程的当前状态,枚举类型
knot_pkt_t *pkt 解析后的报文结构
knotd_qdata_t *qdata 本次查询处理的上下文
knotd_mod_t *mod 当前模块的控制块
3. 添加模块需要的其他实现与信息
- 配置文件语法检查接口的实现int xxx(knotd_conf_check_args_t *args)
使用 _public_ knotd_conf_t knotd_conf_check_item(knotd_conf_check_args_t *args, const yp_name_t *item_name) 获取配置项并检查合法性
- 配置文件语法定义
使用 const yp_item_t xxx[] = {...} 定义配置语法
- 确定该模块的作用域是zone or global or 两者都有
- 确定一个全局唯一的mod-name
- 使用宏 KNOTD_MOD_API 将当前模块注册到Knot DNS
4.相关编译添加
将新模块的.c和.h文件添加到Knot.files文件
修改configure.ac文件,添加条件编译相关代码
修改 src/knot/Makefile.inc 将模块makefile文件包含进去
新增src/knot/modules/mode-name/Makefile.inc,编写模块编译指令
将源代码文件放到src/knot/modules/mode-name/ 里面
新增src/knot/modules/mode-name/xxx.rst,编写使用说明