【openwrt】netifd组件——netifd-proto脚本分析
netifd-proto脚本utils.sh脚本netifd-proto.sh脚本netifd-wireless.shnetifd工具提供如下3个脚本用于网络配置utils.sh脚本netifd-proto.sh脚本netifd-wireless.sh脚本utils.sh脚本utils.sh为netifd-proto.sh和netifd-wireless.sh提供一些基础功能。一般用户不会直接调用
netifd-proto脚本
netifd工具提供如下3个脚本用于网络配置
- utils.sh脚本
- netifd-proto.sh脚本
- netifd-wireless.sh脚本
utils.sh脚本
utils.sh
为netifd-proto.sh
和netifd-wireless.sh
提供一些基础功能。一般用户不会直接调用。
- append
- add_default_handler
- set_default
- config_add_int
- config_add_array
- config_add_string
- config_add_boolean
下面仅列举出部分函数定义,详见源码
# /lib/netifd/utils.sh
N="
"
append() {
local var="$1"
local value="$2"
local sep="${3:- }"
eval "export -- \"$var=\${$var:+\${$var}\${value:+\$sep}}\$value\""
}
add_default_handler() {
case "$(type $1 2>/dev/null)" in
*function*) return;;
*) eval "$1() { return; }"
esac
}
set_default() {
local __s_var="$1"
local __s_val="$2"
eval "export -- \"$__s_var=\${$__s_var:-\$__s_val}\""
}
_config_add_generic() {
local type="$1"; shift
for name in "$@"; do
json_add_array ""
json_add_string "" "$name"
json_add_int "" "$type"
json_close_array
done
}
config_add_int() {
_config_add_generic 5 "$@"
}
config_add_array() {
_config_add_generic 1 "$@"
}
config_add_string() {
_config_add_generic 3 "$@"
}
config_add_boolean() {
_config_add_generic 7 "$@"
}
1.append()函数说明
append() {
local var="$1"
local value="$2"
local sep="${3:- }"
eval "export -- \"$var=\${$var:+\${$var}\${value:+\$sep}}\$value\""
}
append()
函数作用等效于:
- var为空
var = $value
var
不为空
var 的值是新增的value和旧的值的叠加
用法示例:
append PROTO_DNS "$address"
# PROTO_DNS是某个全局变量名
# $address 值
eval第一遍扫描结果如下:
export -- "PROTO_DNS=${PROTO_DNS:+${PROTO_DNS}${value:+$sep}}$value"
其执行情况为:
- 若
PROTO_DNS
为空,则不执行替换,PROTO_DNS=$value - 若
PROTO_DNS
不为空,需要执行替换,上述表达式变为PROTO_DNS=${PROTO_DNS}${value:+$sep}$value
,接下来需要继续判断$value
:$value
为空,原表达式为PROTO_DNS=${PROTO_DNS}$value
$value
不为为空,原表达式为PROTO_DNS=${PROTO_DNS}$sep$value
2.config_add_int() config_add_string() config_add_array()等函数使用示例
/ # cat test.sh
#!/bin/sh
. /usr/share/libubox/jshn.sh
_config_add_generic() {
local type="$1"; shift
echo "$@"
for name in "$@"; do
json_add_array ""
json_add_string "" "$name"
json_add_int "" "$type"
json_close_array
done
}
config_add_int() {
_config_add_generic 5 "$@"
}
config_add_string() {
_config_add_generic 3 "$@"
}
json_init
json_add_array "config"
config_add_int mtu
config_add_int auth
config_add_string apn
json_dump
执行结果如下:
{ "config": [ [ "mtu", 5 ], [ "auth", 5 ], [ "apn", 3 ] ] }
config_add_int()、config_add_string()的作用就是创建json数组保存用于后面的参数,一个参数对应一个数组,如果存在多个参数就会生成多个数组。上述示例中,config_add_int()和config_add_string()创建的数组是嵌套进config
数组的。
其中的数字表示不同的数据类型:
5 ==> int
3 ==> string
7 ==> bool
1 ==> array
config_add_int()、config_add_string()创建的数组必须嵌套进另一个json数组,不可以单独存在,因为_config_add_generic()
函数中json_add_array
创建的数组的name为空,后面使用json_add_string
添加成员后,只会将这些成员保存至环境变量,使用json_dump
打印该json也是{ }
空值。
netifd-proto.sh脚本
netifd-proto.sh
提供一些配置网络参数的函数,这些函数是基于utils.sh
里面的函数实现的。用户可以直接调用netifd-proto.sh
里面的部分函数。
- proto_config_add_int
- proto_config_add_string
- proto_config_add_boolean
- proto_config_add_array
- proto_init_update
- proto_set_keep
- proto_add_dns_server
- proto_add_ipv4_address
- proto_add_ipv6_address
- proto_add_ipv4_route
- proto_add_ipv6_route
- proto_send_update
- proto_run_command
- proto_kill_command
- proto_notify_error
- proto_block_restart
- proto_set_available
- proto_setup_failed
- init_proto
1.proto_init_update()
proto_init_update() {
local ifname="$1"
local up="$2"
local external="$3"
PROTO_KEEP=0
PROTO_INIT=1
PROTO_TUNNEL_OPEN=
PROTO_IPADDR=
PROTO_IP6ADDR=
PROTO_ROUTE=
PROTO_ROUTE6=
PROTO_PREFIX6=
PROTO_DNS=
PROTO_DNS_SEARCH=
PROTO_NEIGHBOR=
PROTO_NEIGHBOR6=
json_init
json_add_int action 0
[ -n "$ifname" -a "*" != "$ifname" ] && json_add_string "ifname" "$ifname"
json_add_boolean "link-up" "$up"
[ -n "$3" ] && json_add_boolean "address-external" "$external"
}
初始化一组interface
用到的参数,例如ifname
,link状态up
,ipv4信息PROTO_IPADDR
等等。
action
对应netifd的一种操作,详见netifd的proto_shell_notify()
函数。
同时初始化一个json对象用于保存这些参数,最终这些参数会通过json对象传递到netifd。
2.proto_set_keep()、proto_add_dns_server()、 proto_add_dns_search()、proto_add_ipv4_route()、proto_add_ipv6_route()
以上函数的作用是设置proto_init_update()
初始化的那些参数。用户会直接调用这些函数进行参数配置。
这里以proto_add_ipv4_address()
函数为例介绍其执行流程:
proto_add_ipv4_address() {
local address="$1"
local mask="$2"
local broadcast="$3"
local ptp="$4"
append PROTO_IPADDR "$address/$mask/$broadcast/$ptp"
}
proto_add_ipv4_address "192.168.1.2" "255.255.255.0" "192.168.1.0" "0"
proto_add_ipv4_address()
最多会接收4个参数,经过这一步设置后
PROTO_IPADDR=192.168.1.2/255.255.255.0/192.168.1.0/0
proto_add_ipv4_address()
做的事情实际上就这么多,只是设置PROTO_IPADDR
的值而已。为了方便理解,接下来会继续讲下PROTO_IPADDR
解析和传递过程。
协议脚本最终在发送时会调用proto_send_update()
函数向netifd
传递这些参数用于更新interface
,在发送时先会对参数进行解析,接下来以PROTO_IPADDR
参数为例讲解如何解析和传递的。
proto_send_update() {
......
_proto_push_array "ipaddr" "$PROTO_IPADDR" _proto_push_ipv4_addr
......
}
_proto_push_array()
函数执行流程如下:
- 创建一个名为
$1
(ipaddr)的json数组 - 依次用
$3
处理$2
中每一个参数
_proto_push_array() {
local name="$1"
local val="$2"
local cb="$3"
[ -n "$val" ] || return 0
json_add_array "$name"
for item in $val; do
eval "$cb \"\$item\""
done
json_close_array
}
上述$3
是 _proto_push_ipv4_addr(),$2
是PROTO_IPADDR
PROTO_IPADDR=192.168.1.2/255.255.255.0/192.168.1.0/0
_proto_push_ipv4_addr()
的行为如下:
- PROTO_IPADDR存入变量str
- address = str删掉第一个/及其右边的字符串 > 192.168.1.1
- str = str删掉第一个/及其左边的字符串 > 255.255.255.0/192.168.1.0/0
- mask = str删掉第一个/及其右边的字符串 > 255.255.255.0
- …
- 以此类推,从PROTO_IPADDR解析出全部4个变量
- 新建匿名json对象
- 将上述4个变量添加进json对象
_proto_push_ipv4_addr() {
local str="$1"
local address mask broadcast ptp
address="${str%%/*}"
str="${str#*/}"
mask="${str%%/*}"
str="${str#*/}"
broadcast="${str%%/*}"
str="${str#*/}"
ptp="$str"
json_add_object ""
json_add_string ipaddr "$address"
[ -n "$mask" ] && json_add_string mask "$mask"
[ -n "$broadcast" ] && json_add_string broadcast "$broadcast"
[ -n "$ptp" ] && json_add_string ptp "$ptp"
json_close_object
}
示例code:
#!/bin/sh
. /usr/share/libubox/jshn.sh
append() {
local var="$1"
local value="$2"
local sep="${3:- }"
eval "export -- \"$var=\${$var:+\${$var}\${value:+\$sep}}\$value\""
}
_proto_push_ipv4_addr() {
local str="$1"
local address mask broadcast ptp
address="${str%%/*}"
str="${str#*/}"
mask="${str%%/*}"
str="${str#*/}"
broadcast="${str%%/*}"
str="${str#*/}"
ptp="$str"
json_add_object ""
json_add_string ipaddr "$address"
[ -n "$mask" ] && json_add_string mask "$mask"
[ -n "$broadcast" ] && json_add_string broadcast "$broadcast"
[ -n "$ptp" ] && json_add_string ptp "$ptp"
json_close_object
}
_proto_push_array() {
local name="$1"
local val="$2"
local cb="$3"
[ -n "$val" ] || return 0
json_add_array "$name"
for item in $val; do
eval "$cb \"\$item\""
done
json_close_array
}
proto_add_ipv4_address() {
local address="$1"
local mask="$2"
local broadcast="$3"
local ptp="$4"
append PROTO_IPADDR "$address/$mask/$broadcast/$ptp"
}
proto_add_ipv4_address "192.168.1.2" "255.255.255.0" "192.168.1.255" "0"
echo PROTO_IPADDR = ${PROTO_IPADDR}
json_init
_proto_push_array "ipaddr" "$PROTO_IPADDR" _proto_push_ipv4_addr
json_dump
运行结果:
PROTO_IPADDR = 192.168.1.2/255.255.255.0/192.168.1.255/0
{ "ipaddr": [ { "ipaddr": "192.168.1.2", "mask": "255.255.255.0", "broadcast": "192.168.1.255", "ptp": "0" } ] }
3.proto_send_update()
_proto_notify() {
local interface="$1"
local options="$2"
json_add_string "interface" "$interface"
ubus $options call network.interface notify_proto "$(json_dump)"
}
proto_send_update() {
local interface="$1"
proto_close_nested
json_add_boolean keep "$PROTO_KEEP"
_proto_push_array "ipaddr" "$PROTO_IPADDR" _proto_push_ipv4_addr
_proto_push_array "ip6addr" "$PROTO_IP6ADDR" _proto_push_ipv6_addr
_proto_push_array "routes" "$PROTO_ROUTE" _proto_push_route
_proto_push_array "routes6" "$PROTO_ROUTE6" _proto_push_route
_proto_push_array "ip6prefix" "$PROTO_PREFIX6" _proto_push_string
_proto_push_array "dns" "$PROTO_DNS" _proto_push_string
_proto_push_array "dns_search" "$PROTO_DNS_SEARCH" _proto_push_string
_proto_push_array "neighbor" "$PROTO_NEIGHBOR" _proto_push_ipv4_neighbor
_proto_push_array "neighbor6" "$PROTO_NEIGHBOR6" _proto_push_ipv6_neighbor
_proto_notify "$interface"
}
proto_send_update()函数的目的是将proto_init_update()中创建的参数:ipaddr
,ip6addr
,routes
等传递到netifd。传递的方式是ubus call network.interface notify_proto
4.proto_run_command()、proto_kill_command()
proto_run_command() {
local interface="$1"; shift
json_init
json_add_int action 1
json_add_array command
while [ $# -gt 0 ]; do
json_add_string "" "$1"
shift
done
json_close_array
[ -n "$_EXPORT_VARS" ] && {
json_add_array env
for var in $_EXPORT_VARS; do
eval "json_add_string \"\" \"\${$var}\""
done
json_close_array
}
_proto_notify "$interface"
}
proto_kill_command() {
local interface="$1"; shift
json_init
json_add_int action 2
[ -n "$1" ] && json_add_int signal "$1"
_proto_notify "$interface"
}
proto_run_command()
是通知netifd运行某个命令;
proto_kill_command()
是通知netifd停止运行某个命令;
5.proto_block_restart()
proto_block_restart() {
local interface="$1"; shift
json_init
json_add_int action 4
_proto_notify "$interface"
}
proto_block_restart()
设置struct interface
成员autostart = false
,禁止interface自动set up。
6.proto_set_available()
proto_set_available() {
local interface="$1"
local state="$2"
json_init
json_add_int action 5
json_add_boolean available "$state"
_proto_notify "$interface"
}
proto_set_available()
设置interface的状态(struct interface
成员available),如果状态为true,表明这个interface已经准备好了,可以进行set up。
7.proto_notify_error()
proto_notify_error() {
local interface="$1"; shift
json_init
json_add_int action 3
json_add_array error
while [ $# -gt 0 ]; do
json_add_string "" "$1"
shift
done
json_close_array
_proto_notify "$interface"
}
proto_notify_error()
作用是向netifd发送指定interface的错误码。错误码可以任意的字符串。
8.init_proto()
init_proto() {
proto="$1"; shift
cmd="$1"; shift
case "$cmd" in
dump)
add_protocol() {
no_device=0
no_proto_task=0
available=0
renew_handler=0
teardown_on_l3_link_down=0
add_default_handler "proto_$1_init_config"
json_init
json_add_string "name" "$1"
json_add_array "config"
eval "proto_$1_init_config"
json_close_array
json_add_boolean no-device "$no_device"
json_add_boolean no-proto-task "$no_proto_task"
json_add_boolean available "$available"
json_add_boolean renew-handler "$renew_handler"
json_add_boolean lasterror "$lasterror"
json_add_boolean teardown-on-l3-link-down "$teardown_on_l3_link_down"
json_dump
}
;;
setup|teardown|renew)
interface="$1"; shift
data="$1"; shift
ifname="$1"; shift
add_protocol() {
[[ "$proto" == "$1" ]] || return 0
case "$cmd" in
setup) _proto_do_setup "$1";;
teardown) _proto_do_teardown "$1" ;;
renew) _proto_do_renew "$1" ;;
*) return 1 ;;
esac
}
;;
esac
}
/lib/netifd/proto/
目录下每一种协议执行时都会调用init_proto()
.然后再通过init_proto()
处理dump、setup、teardown等事件。
下面以dhcp.sh
协议为例介绍其执行流程:
1.ducp.sh '' dump
流程
init_proto "$@"
只会增加一个函数add_protocol()
add_protocol dhcp
才是真正调用add_protocol()
的地方
2.dhcp.sh setup
流程
ducp.sh setup
并不是用户手动调用的,通常都是由netifd调用,另外set up
一般是需要携带多个参数的。这里只是为了介绍流程而提供的一个简单示例。
netifd-wireless.sh
待续
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)