Openwrt firewall hotplug

文章讲述了在Openwrt路由设备上,由于hotplug机制导致firewall在处理过多虚拟接口时无法正确重启的问题。通过分析hotplug脚本和firewall源码,发现ubus_connect()函数执行失败。为了解决这个问题,作者提出在hotplug脚本中针对vlan1接口添加retry机制,以避免源码修改带来的风险。目前的解决方案是在vlan1接口触发时,提供5次重试机会来确保firewall能正确执行。
摘要由CSDN通过智能技术生成

Hotplug解决问题思路

Hotplug 简介

解决问题之前需要大致了解什么是hotplug,有一个比较简单的概念才好下手。

在很多嵌入式设备中,都有着hotplug(热插拔)的身影,热插拔可以帮助设备更快更稳定的处理和维护设备节点。

有 2 个不同角度来看待热插拔:
从内核角度看,热插拔是在硬件、内核和内核驱动之间的交互。
从用户角度看,热插拔是内核和用户空间之间,通过调用用户空间程序(如hotplug、udev 和 mdev)的交互。 当需要通知用户内核发生了某种热插拔事件时,内核才调用这个用户空间程序。
现在的计算机系统,要求 Linux 内核能够在硬件从系统中增删时,可靠稳定地运行。这就对设备驱动作者增加了压力,因为在他们必须处理一个毫无征兆地突然出现或消失的设备。

对于我的Openwrt的路由设备,Openwrt Wiki给的描述为:

Procd (the init system and process management daemon) executes scripts located in /etc/hotplug. d/ when certain events happen, like for example when an interface goes up or down, when a new storage drive is detected, or when a button is pressed. It can be very useful with PPPoE connection or in an unstable network, or to use hardware buttons.

This functionality emulates/extends what was done by the long retired Hotplug2 package.

当某些 events事件发生时,Procd(init系统和进程管理守护进程)会执行位于/etc/hotplug.d/中的脚本,例如当接口启动或关闭时,检测到新的存储驱动器时,或者按下按钮时.
当使用PPPoE连接或者在不稳定的网络中,或使用硬件按钮时非常有用。
该功能模块模拟/扩展了已淘汰的Hotplug2软件包的功能。当在中执行脚本时/etc/hotplug.d,Procd 通常会以环境变量的形式提供大量信息。
如果要查看它提供了什么环境变量,请编写一个包含以下行的脚本:
env > /tmp/envs_log.log并将其放在我们需要调试的文件中,然后触发连接到该文件夹​​的事件,然后可以通过阅读/tmp/envs_log.log文本文件 来查看传递了哪些环境变量。

问题描述

这次需要分析的问题是当我们网络设备虚拟接口过多的时候,firewall不能通过hotplug正确的被restart起来,我们先贴上firewall的hotplug的脚本代码:

fw3 -q network "$INTERFACE"#!/bin/sh

[ "$ACTION" = ifup -o "$ACTION" = ifupdate ] || exit 0

#IFUPDATE_DATA=1 means some paramters of dhcp are changed, for example, lease time change, it's no need to restart firewall for this change.
#[ "$ACTION" = ifupdate -a -z "$IFUPDATE_ADDRESSES" -a -z "$IFUPDATE_DATA" ] && exit 0
#IFUPDATE_ADDRESSES=1 means ip address is changed.
[ "$ACTION" = ifupdate -a -z "$IFUPDATE_ADDRESSES" ] && exit 0

/etc/init.d/firewall enabled || exit 0

fw3 -q network "$INTERFACE" >/dev/null || exit 0

fw3 -q restart

我们可以使用上面提到的env > /tmp/envs_log.log 来放入脚本中任意位置,来查看procd触发该hotplug传入的参数都有哪些,我简单贴上一个以供参考:

INTERFACE=vlan1USER=root
ACTION=ifup
SHLVL=2
HOME=/
HOTPLUG_TYPE=iface
LOGNAME=root
DEVICENAME=
TERM=linux
PATH=/usr/sbin:/usr/bin:/sbin:/bin
INTERFACE=vlan1095
PWD=/
DEVICE=br-vlan1095
USER=root
ACTION=ifup
SHLVL=2
HOME=/
HOTPLUG_TYPE=iface
LOGNAME=root
DEVICENAME=
TERM=linux
PATH=/usr/sbin:/usr/bin:/sbin:/bin
INTERFACE=vlan1
PWD=/
DEVICE=br-vlan

回到问题本身,我们通过在hotplug脚本中添加debug log,我们找到在12行:

fw3 -q network "$INTERFACE" >/dev/null || exit 0 

当传入的 INTERFACE=vlan1的时候,并不会被正确的执行下去,而是走到了后面的exit 0,也就是退出了当前的脚本,所以并不会调到下面的fw3 restart,然而fw3是编译生成的一个用户空间应用程序,是编译时生成的,所以,我们如果想要知道为什么这条命令执行不成功,就需要去源码中去追,为了省篇幅,我直接贴上我debug的结果:

if (!fw3_ubus_connect())
			error("Failed to connect to ubus");

在fw3的源码中,fw3_ubus_connect 这个函数执行失败:

bool
fw3_ubus_connect(void)
{
	bool status = false;
	uint32_t id;
	struct ubus_context *ctx = ubus_connect(NULL);
	struct blob_buf b = { };

	if (!ctx)
		goto out;

	if (ubus_lookup_id(ctx, "network.interface", &id))
		goto out;

	if (ubus_invoke(ctx, id, "dump", NULL, dump_cb, NULL, 500))
		goto out;

	status = true;

	if (ubus_lookup_id(ctx, "service", &id))
		goto out;

	blob_buf_init(&b, 0);
	blobmsg_add_string(&b, "type", "firewall");
	ubus_invoke(ctx, id, "get_data", b.head, procd_data_cb, NULL, 2000);
	blob_buf_free(&b);

out:
	if (ctx)
		ubus_free(ctx);
	return status;
}

最后定位到该函数的:

	struct ubus_context *ctx = ubus_connect(NULL);
	...
	if (!ctx)
		goto out;

可以看到ubus_connect为结构体赋值的时候失败, 但是追到这里的时候我也只是仅仅知道了是哪里出的错,但是并不知道为什么在restart的这个时间点VLAN1传入的时候会出错(restart 完成后在shell上手动输入fw3 -q network vlan1 并不会报错),但是追到这里实际上已经浪费了很多时间,我的leader也并不希望以修改源码的方式解决问题(因为我们项目已经到了量产阶段,所以每一次修改都需要长期验证,需要稳重,像是修改源码这样的事情不到万不得已肯定是不会去做的),所以后续如果我有时间的话我一定继续往下追并更新到博文中,那么问题来了,如何最稳妥的去解决这个问题呢?

Hotplug retry

既然不让改源码,当然要从hotplug的脚本下手了…

既然在传入vlan1的时候:

fw3 -q network "$INTERFACE" >/dev/null || exit 0

一直退出,那么我们在传入vlan1的时候加一个retry机制不就可以了嘛,很简单的思路,为什么非要和fw3源码对着干呢QAQ, 经过修改代码如下:

#!/bin/sh
retry_count=0
fail_flag=0

[ "$ACTION" = ifup -o "$ACTION" = ifupdate ] || exit 0

#IFUPDATE_DATA=1 means some paramters of dhcp are changed, for example, lease time change, it's no need to restart firewall for this change.
#[ "$ACTION" = ifupdate -a -z "$IFUPDATE_ADDRESSES" -a -z "$IFUPDATE_DATA" ] && exit 0
#IFUPDATE_ADDRESSES=1 means ip address is changed.
[ "$ACTION" = ifupdate -a -z "$IFUPDATE_ADDRESSES" ] && exit 0

/etc/init.d/firewall enabled || exit 0

if [ $INTERFACE = vlan1 ]; then
        while true
        do
                fw3 -q network "$INTERFACE" >/dev/null

                if [ $? -ne 0 ]; then
                        retry_count=$(($retry_count+1))
                        if [ ${retry_count} -eq 5 ]; then
                                break;
                        fi
                else
                        break;
                fi
        done
fi

fw3 -q network "$INTERFACE" >/dev/null || exit 0

logger -t firewall "Reloading firewall due to $ACTION of $INTERFACE ($DEVICE)"
fw3 -q restart
echo 1 > /tmp/test_flag

这样在vlan1传入的时候,有5次试错的机会,如果在5次内成功,就可以正确执行下去,五次内不成功也可以正确的退出,不至于进入死循环。目前的解决方案就是这样,我后续还会继续追一下fw3的源码和ubus的通信机制更新到文章里。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值