背景

月初偶然得知padavan固件在linux 3.4.x内核上有高人backport了3.7.x的SO_REUSEPORT特性。对于拥有多核CPU的路由器来说,利用该特性可以显著提升SS所能承载的吞吐。手头上正好有个老旧的A3004NS,虽说有点不舍得荒野无灯的精致固件,还是拿来折腾一番。期间学习了一部分padavan固件编写插件的方法,下文以某插件为例,做简单的介绍及记录。

准备

按照hanwckfrt-n56u说明准备编译环境。这个固件是在原版的padavan基础上增加了国内常用的一些插件,同时适配了不少常见的padavan兼容路由,在编译配置上做了简化,其他固件可能在一部分配置上有所出入。

插件相关文件

trunk/
|-- configs
|   |-- templates
|   |   |-- A3004NS.config // 路由器适配文件,新增的插件编译选项开关可在此处追加
|   |   └-- ... 
|   └-- ...
|-- user
|   |-- Makefile // 控制所有插件的编译,和路由器配置联动达到插件可选化编译和打包
|   |-- A_PLUGIN
|   |   |-- Makefile // 控制单个插件的编译、打包文件产出,参照其他插件例子即可
|   |   └-- ...
|   |-- www // 页面文件,padavan中多数为asp
|   |   |-- Makefile // 新增插件若有相关页面文件,需要调整该文件
|   |   |-- state.js // 控制页面目录中的插件页面入口,新增插件需要调整该文件
|   |   |-- dict // i18n实现,按需调整
|   |   └-- n56u_ribbon_fixed
|   |       |-- PLUGIN_RELATED.asp // 插件页面相关的asp文件
|   |       └-- ...
|   |-- httpd
|   |   |-- variables.c // 插件相关nvram持久化数据的变量结构体定义
|   |   |-- web_ex.c // 处理asp后端逻辑的核心文件,主要关注暴露出来的函数,如update_variables_ex
|   |   |-- common.h // 关注variable,为结构体属性的类型
|   |   └-- ...
|   |-- shared
|   |   |-- defaults.c // 定义插件相关变量的默认值
|   |   └-- ... 
|   └-- ...
└-- ...

插件代码

插件核心代码

对应目录中trunk/user/A_PLUGIN,主要通过Makefile编译插件并将产出的(部分)二进制文件加入固件。多数情况下需要调整编译选项,参考其他插件例子即可。需要注意的一点是,在整体编译固件时插件时若需要重新运行configure,要删除产出的config_done文件。

编译开关

trunk/user/Makefile中关联对应插件的目录,如dir_y += transmission,即可添加transmission到编译列表。

若需要通过路由器适配文件配置插件编译可选,则可以改为dir_$(CONFIG_FIRMWARE_INCLUDE_TRANSMISSION) += transmission, 并在trunk/configs/templates/XXX.config中配置CONFIG_FIRMWARE_INCLUDE_TRANSMISSION=y/n

页面代码

页面代码一般简单插件仅需提供asp文件,若存在额外的js或css文件,同样放在trunk/user/www目录下,调整trunk/user/www/Makefile文件即可。

核心流程

页面的核心逻辑是提交的数据通过httpd服务暴露的函数入口更新到nvram或持久化的文件中。对此padavan约定了一套处理的通用流程。

  1. 页面通过form post提交到start_apply.htm
  2. start_apply.htm调用相应的后端update_variablesasus_nvram_commit以及notify_services
  3. update_variables通过参数中的sid_list找到对应的在trunk/user/httpd/variables.c中定义的结构体变量,同时将其他参数映射到结构体定义的成员变量上。校验通过后,逐一更新到nvram或写到相应文件(详见trunk/user/httpd/web_ex.c中的update_variables_ex函数逻辑)
  4. 提交变更到nvram(持久化)
  5. 若需要,通知触发相关关服务,如系统重启等操作

nvram/文件内容输出

  1. 页面上展示nvram中的变量,使用<% nvram_get_x("","VARIABLE"); %>
  2. 页面上输出文件内容,使用<% nvram_dump("PREFIX.VARIABLE", ""); %>

nvram变量更新

一般情况无需关注,提交的参数名与插件结构体中的变量名相同即可。如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// trunk/user/httpd/variables.c
struct variable variables_dnsforwarderConf[] = {
	{"dns_forwarder_enable", "", NULL, EVM_RESTART_DNSFORWARDER},
	{"dns_forwarder_bind", "", NULL, EVM_RESTART_DNSFORWARDER},
	{"dns_forwarder_port", "", NULL, EVM_RESTART_DNSFORWARDER},
	{"dns_forwarder_server", "", NULL, EVM_RESTART_DNSFORWARDER},
	{0,0,0,0}
};
...
struct svcLink svcLinks[] = {
	...
	{"dnsforwarderConf",		variables_dnsforwarderConf},
	...
}

http提交参数中包含dns_forwarder_server,同时sid_list指定为dnsforwarderConf;, 即可更新到nvram中的dns_forwarder_server变量。

文件内容更新

关于rom storage中文件内容的更新,是通过将结构体变量单元(variable,见trunk/user/httpd/common.h)中的longname设置为File,以在trunk/user/httpd/web_ex.cvalidate_asp_apply函数改写为对应的文件路径实现更新的。如:

1
2
// trunk/user/httpd/variables.c
{"scripts.start_script.sh", "File", NULL, EVM_BLOCK_UNSAFE},
1
2
3
4
5
6
// trunk/user/httpd/web_ex.c - validate_asp_apply
...
} else if (!strncmp(v->name, "scripts.", 8)) {
	if (write_textarea_to_file(value, STORAGE_SCRIPTS_DIR, file_name))
		restart_needed_bits |= event_mask;
...

通过阅读代码可知,文件类型的variable有多个预定义的路径(见trunk/user/httpd/httpd.h),普通插件一般使用scripts目录即可,变量名格式需要为scripts.xxx,有如下映射:

  • scripts.FILE_NAME -> /etc/storage/FILE_NAME