天天看点

Knot DNS架构详解3-查询动态定义一、什么是查询动态定义二、实现原理三、自定义一个模块

这个系列文章源于网络资料和自己的总结。关于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,编写使用说明

继续阅读