OpenWrt netifd学习笔记

OpenWrt netifd学习笔记

20170711 18:08:33 落尘纷扰 阅读数 7958

 版权声明:本文为博主原创文章,转载请附上原博链接。 https://blog.csdn.net/jasonchen_gbd/article/details/74990247

Netifd简介

NetifdOpenWrt中用于进行网络配置的守护进程,基本上所有网络接口设置以及内核的netlink事件都可以由netifd来处理完成。 
在启动netifd之前用户需要将所需的配置写入uci配置文件/etc/config/network中,以告知netifd如何设置这些网络接口,如IP地址、上网类型等。如果在netifd运行过程中需要修改配置,则只需更新并保存/etc/config/network,执行/etc/init.d/network reloadnetifd便可根据配置文件差异快速地完成网络接口的更新。

Netifd基本框架

我们配置一个网络接口通常都要完成下面三类工作: 
1. MAC
地址、设备MTU、协商速率等L2属性,这些都是直接操作实际网络设备的。 
2. IP
地址、路由(包括应用层的DNS)等L3属性。 
3.
设置特定接入方式,如静态IPDHCPPPPoE等。设置完成后可能需要更新L3的属性。 
我们可以通过上述思路来理解netifd的设计: 
 
拿我们最常用的路由器来讲,作为路由器的使用者我们只关心要配置interface层的哪个接口(LAN口、WAN口?),以及配置成怎样的上网方式。使用netifd配置网络,也是interface为中心:创建一个interface并指明其依赖的实际网络设备(device),及其绑定的上网方式(proto handler),就完成了一个网络接口的配置并可使其开始工作。当网络状态发生变化时,这三者之间也能相互通知(事件通知或引用计数)以完成自身状态的转换。 
例如,在/etc/config/network中对WAN口的配置如下

config interface 'wan'
    option ifname 'eth0'
    option proto 'static'
    option mtu '1500'
    option auto '1'
    option netmask '255.255.255.0'
    option ipaddr '192.168.1.100'   
    option gateway '192.168.1.1'
    option dns '8.8.8.8'

Netifd通过读取上述配置,来创建出一个名为”WAN”interface实例并将其中与interface相关的配置项应用到这个新创建的实例中。同时,如果其指定依赖的设备(ifname)不存在,就通过配置中与device相关的配置项创建一个新的device,并确定二者的依赖(引用)关系。由于proto handler中每一种proto的工作方式是确定的,不依赖于任何配置,因此在netifd启动时就会初始化好所有的proto handler,因而要求配置中的proto一项必须是在netifd中已存在的proto handler的名字。 
也可以单独用一个uci section来保存device的配置信息,让netifd先把device创建好。

设备层(Device)

Device是指链路层的设备,在netifd中就特指链路层的网络设备。Netifd中每个设备都用一个struct device结构的实例来表示,例如典型的物理设备、bridge(一个网桥设备实际是对应一个struct bridge_state实例,但和通用设备相关的属性仍放在其dev成员中)、VLAN设备等。 
创建好一个设备后,如果要引用这个设备,就要注册一个device_user,像上面说的,deviceuser一般是interface,但也有device之间相互引用的情况,例如bridge memberbridge的关系。向device注册和注销device_user的函数为device_add_user(user, dev)device_remove_user(user)

Device user

/*
 * device dependency with callbacks
*/
struct device_user {
    struct list_head list;
    bool claimed;
    bool hotplug;
    bool alias;
    struct device *dev;
    void (*cb)(struct device_user *, enum device_event);
};

各个成员的含义: 
list
:该user引用的deivceuser链表节点。 
claimed
:相当于引用计数,由于一个user实例只能作为某一个deviceuser,因此设为BOOL类型。一个user在绑定了device后,还要通过device_claim才算真正使用了device。这样就允许引用和生效可以不同时进行,例如要等热插拔设备存在时才能启动interface。与claim相反的操作为device_release() 
hotplug
:标识bridge下动态添加的、以及调ubus call network.interface add_device的设备。 
alias
:用来标记将该user加到deviceusers链表还是aliases链表。 
dev
:该user引用的device对象指针。 
cb()
:当device状态发生变化时,会调用该cb()函数以事件的形式来通知所有的users。目前支持的事件类型可参考netifdDESIGN

设备struct device中也维护了一个引用计数来控制设备的up/down状态,每次device_claim(user)成功后,引用计数+1,每次device_release(user)成功后,引用计数-1。当引用计数从01,即有一个user使用了该device时,device就会被UP起来,而当引用计数从10,即最后一个user离开时,device就会立即被DOWN掉。

接口层(interface)

由于device属于L2层的概念,如果用户对一个网络设备配置属于L3或更高层协议的属性,则要直接对interface进行操作,进而间接作用于device。因此一个interface必须绑定到一个device上,通过struct interfacemain_dev成员指定。 
Interface
配置完成后,是否可以UPDOWNavailableautostart两个成员来决定:

struct interface {
    … …
    bool available;
    bool autostart;
    bool config_autostart; /* uci配置中的"autostart",默认为true。仅用于uci有关的操作 */
    … …
};

availableinterface是否是可用的(已准备好可以up),通过interface_set_available()来设置。 
autostart
interface在配置完成后,是否自动执行up操作,默认和config_autostart值相同。但如果用户手动upinterface(如通过ubusup),则autostart强制变为true。如果用户手动downinterface(如通过ubusdown),则autostart强制变为false

而一个interface的具体配置内容都放在struct interface_ip_settings的结构中,由于一个interface可能有多个IP/Route/DNS条目,因此将这些信息又封装了一层,而不是直接放到struct interface中。

Interface user

不常用。Alias设备会产生一个interface user,用于指明自己的parentAlias自身作为user的信息保存在struct interfaceparent_iface成员中,而其parent则用struct interfaceparent_ifname来指定。

Netifd的uci配置

只要熟悉uci那些API,对netifd启动和reload时读取配置这部分内容就很容易理解了。Netfid使用名为network的配置文件。

Proto shell

Proto handler的注册

通过构造函数,在netifd启动的时候便注册了一系列的proto handler(注册到一个全局的AVL树中)。

proto_shell_init() -> proto_shell_add_script() -> proto_shell_add_handler() -> add_proto_handler();
static_proto_init() -> add_proto_handler();

注册的过程为:在/lib/netifd/proto目录下对每个.sh文件执行./xxx.sh ” dump,然后分析执行结果。例如对于dhcp.sh

root@openwrt:/lib/netifd/proto# ./dhcp.sh '' dump
{ "name": "dhcp", "config": [ [ "ipaddr", 3 ], [ "netmask", 3 ], [ "hostname", 3 ], [ "clientid", 3 ]
, [ "vendorid", 3 ], [ "enable_broadcast", 7 ], [ "reqopts", 3 ] ], "no-device": false, "available": 
false }

而最终该脚本的proto handler保存在一个struct proto_handler结构的实体中,并添加到全局的handler树中,供后续查找和引用。上述的dhcp.sh注册的结果为:

struct proto_shell_handler *handler;
handler->script_name = "./dhcp.sh";
handler->proto.name = "dhcp";
handler->proto.config_params = &handler->config;
handler->proto.attach = proto_shell_attach;
handler->proto.flags |= ...; //no-deviceavailable决定,ppp使用,staticdhcp不关注
handler->proto.avl.key = handler->proto.name; //插到handler树中的key
handler->config.n_params = 7; //下面config param的数目
handler->config_buf = "ipaddr\0netmask\0hostname ..."
handler->config.params[0].name = "ipaddr";
handler->config.params[0].type = 3; //BLOBMSG_TYPE_STRING
handler->config.params[1].name = "netmask";
handler->config.params[1].type = 3;
... ...

其中handler->proto.config_params中的配置列表,就是为了使proto生效,而需要在uci中配置的项。 
而对于staticproto handler内容就比较简单,直接在netifd代码中定义的:

static struct proto_handler static_proto = {
    .name = "static",
    .flags = PROTO_FLAG_IMMEDIATE,
    .config_params = &proto_ip_attr,
    .attach = static_attach,
};

值得注意的是,static proto携带了PROTO_FLAG_IMMEDIATE标记。

Proto与interface的绑定

interfaceconfig中具有名为”proto”的属性,在interface_init()函数中读取uci配置获取proto的名字(如”static””dhcp””pptp”),然后查找已注册的对应名字的proto handler,并赋值给interfaceproto_handler数据成员,这样就使一个interface绑定到了一个特定的proto handler上。这是在初始化一个interface的时候完成的。

Proto与interface的交互

由于proto是更上层的概念,因此是与interface无关的,而一个interface总会关联到一种proto。例如,WAN口总会要设置一种上网方式(static/dhcp/pppoe等),因此在interface进行配置的过程中,通过调用其proto_handlerattach()方法,为自己分配一个struct interface_proto_state结构并赋值给interfaceproto数据成员。这个过程是在proto_init_interface()函数中完成的。也就是说,interface通过其proto成员与proto层交互来通知proto自身状态的变化。

struct interface_proto_state {
    const struct proto_handler *handler; //其对应的proto handler
    struct interface_t *iface; //其attach的interface
 
    /* 由proto的user来赋值,对于interface,被统一赋值为了interface_proto_cb */
    void (*proto_event)(struct interface_proto_state *, enum interface_proto_event ev);
 
    /* 由特定proto handler来赋值。 */
    int (*notify)(struct interface_proto_state *, struct blob_attr *data);
    int (*cb)(struct interface_proto_state *, enum interface_proto_cmd cmd, bool force);
    void (*free)(struct interface_proto_state *);
};

这个结构中的几个方法: 
cb:
interfaceproto发送某个事件event时,proto的处理函数。目前proto接受的事件有两个:PROTO_CMD_SETUPPROTO_CMD_TEARDOWN 
proto_event:
proto处理event完成后,如果需要告知interface处理已完成,就调用该方法。Interface会根据这个回复的消息做这个event的收尾工作。 
notify:
如果proto需要主动改变interface的状态,则调用该方法。可以在/lib/netifd/netifd-proto.sh中去了解不同”action”的值的含义以及如何通过ubus通知到netifd的,netifd收到notify的消息后,由proto_shell_notify()进行处理。 
free:
释放自己的struct interface_proto_state指针。

Interface通过interface_proto_event()函数向proto层发送事件来告知其自身状态的变化,即这个函数是interface层通向proto层的入口,在interface的状态变为IFS_SETUPIFS_TEARDOWN的时候都会通过该函数通知到proto,发送的事件对应为PROTO_CMD_SETUPPROTO_CMD_TEARDOWN

参考资料

[1] https://wiki.openwrt.org/doc/techref/netifd OpenWrt Wiki - netifd Technical Reference 
[2] http://git.openwrt.org/?p=project/netifd.git;a=blob;f=DESIGN netifd DESIGN 
[3] http://git.openwrt.org/?p=project/netifd.git;a=summary netifd project

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值