RFC 5417中定义了Option 138为CAPWAP AC DHCP Option,即无线AP通过DHCP在获取IP地址的同时解析Option 138获得AC所在地址。
DHCP Client
在OpenWrt中使用集成在bosybox里的udhcpc作为DHCP Client。
~# ps | grep udhcp
1249 root 1536 S udhcpc -p /var/run/udhcpc-br-lan.pid -P 9091 -s /lib/netifd/dhcp.script -f -t 0 -i br-lan -r 192.168.2.253 -H A
2472 root 1528 S grep udhcp
比较关键的参数是 -s,指明当产生DHCP事件时所执行的脚本文件,默认是/usr/share/udhcpc/default.script
udhcpc简单解析
udhcpc入口在networking/udhcp/dhcpc.c中,主要的处理逻辑如下图所示。
大致的处理过程如下,核心在于状态机部分对于对应报文字段的处理以及在调用脚本之前,将对应的参数至于环境变量之中。也就是通过环境变量完成udhcpc进程与脚本之间的参数传递。
实现思路
要支持对Option 138解析之后的动作,要完成以下工作:
(1)udhcpc支持对Option 138的解析
(2)获取Option 138对应字段值之后完成动作(可以直接在C代码里,也可以通过脚本,用脚本修改更加的方便)
udhcpc解析Option 138
当正常获取到IP地址(收到Offer时,如果后续收到的是NAK而不是ACK,这部分会调用脚本清除状态),核心函数
/* Call a script with a par file and env vars */
static void udhcp_run_script(struct dhcp_packet *packet, const char *name)
{
char **envp, **curr;
char *argv[3];
if (client_config.script == NULL)
return;
envp = fill_envp(packet);
/* call script */
log1("Executing %s %s", client_config.script, name);
argv[0] = (char*) client_config.script;
argv[1] = (char*) name;
argv[2] = NULL;
spawn_and_wait(argv);
for (curr = envp; *curr; curr++) {
log2(" %s", *curr);
bb_unsetenv_and_free(*curr);
}
free(envp);
}
其中fill_envp函数即为完成解析Option字段并放入环境变量中,关键代码
opt_name = dhcp_option_strings;
i = 0;
while (*opt_name) {
uint8_t code = dhcp_optflags[i].code;
...........
temp = udhcp_get_option(packet, code);
*curr = xmalloc_optname_optval(temp, &dhcp_optflags[i], opt_name);
putenv(*curr++);
...........
从这段代码可以看出,要支持Option 138需要修改的部分很少:
(1)dhcp_optflags:DHCP报文中Option的定义
(2)dhcp_option_strings:对应option的字符串描述(环境变量名)
需要注意的是,添加的部分在这两个数组中的下标要对应一致。
修改对应patch
--- a/networking/udhcp/common.c
+++ b/networking/udhcp/common.c
@@ -59,6 +59,8 @@ const struct dhcp_optflag dhcp_optflags[
{ OPTION_U16 , 0x84 }, /* DHCP_VLAN_ID */
{ OPTION_U8 , 0x85 }, /* DHCP_VLAN_PRIORITY */
#endif
+ {OPTION_IP | OPTION_LIST , 0x8a }, /* DHCP_CAPWAP_AC_V4 */
{ OPTION_STATIC_ROUTES , 0xf9 }, /* DHCP_MS_STATIC_ROUTES */
{ OPTION_6RD , 0xd4 }, /* DHCP_6RD (RFC) */
{ OPTION_6RD , 0x96 }, /* DHCP_6RD (Comcast) */
@@ -128,7 +130,9 @@ const char dhcp_option_strings[] ALIGN1
"vlanid" "\0" /* DHCP_VLAN_ID */
"vlanpriority" "\0"/* DHCP_VLAN_PRIORITY */
#endif
- "msstaticroutes""\0"/* DHCP_MS_STATIC_ROUTES */
+ "capwapacv4" "\0" /* DHCP_CAPWAP_AC_V4 */
+ "msstaticroutes" "\0"/* DHCP_MS_STATIC_ROUTES */
"ip6rd" "\0" /* DHCP_6RD (RFC) */
"ip6rd" "\0" /* DHCP_6RD (Comcast) */
"wpad" "\0" /* DHCP_WPAD */
执行对应动作
这部分就更简单了,执行的script脚本,对应的参数根据DHCP状态而定
case "$1" in
deconfig)
deconfig_interface
;;
renew|bound)
setup_interface
;;
esac
获取阶段执行setup部分,添加想要的动作即可
setup_interface () {
.........
[ -n "${capwapacv4}" ] && echo "${capwapacv4}" >> /tmp/ac.ip
}