OpenWrt netifd學習筆記
2017年07月11日 18:08:33 落塵紛擾 閱讀數 7958
版權聲明:本文爲博主原創文章,轉載請附上原博鏈接。 https://blog.csdn.net/jasonchen_gbd/article/details/74990247
Netifd簡介
Netifd是OpenWrt中用於進行網絡配置的守護進程,基本上所有網絡接口設置以及內核的netlink事件都可以由netifd來處理完成。
在啓動netifd之前用戶需要將所需的配置寫入uci配置文件/etc/config/network中,以告知netifd如何設置這些網絡接口,如IP地址、上網類型等。如果在netifd運行過程中需要修改配置,則只需更新並保存/etc/config/network,執行/etc/init.d/network reload,netifd便可根據配置文件差異快速地完成網絡接口的更新。
Netifd基本框架
我們配置一個網絡接口通常都要完成下面三類工作:
1. MAC地址、設備MTU、協商速率等L2屬性,這些都是直接操作實際網絡設備的。
2. IP地址、路由(包括應用層的DNS)等L3屬性。
3. 設置特定接入方式,如靜態IP、DHCP、PPPoE等。設置完成後可能需要更新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,像上面說的,device的user一般是interface,但也有device之間相互引用的情況,例如bridge member和bridge的關係。向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)(
structdevice_user *,
enumdevice_event);
};
各個成員的含義:
list:該user引用的deivce的user鏈表節點。
claimed:相當於引用計數,由於一個user實例只能作爲某一個device的user,因此設爲BOOL類型。一個user在綁定了device後,還要通過device_claim纔算真正使用了device。這樣就允許引用和生效可以不同時進行,例如要等熱插拔設備存在時才能啓動interface。與claim相反的操作爲device_release()。
hotplug:標識bridge下動態添加的、以及調ubus call network.interface add_device的設備。
alias:用來標記將該user加到device的users鏈表還是aliases鏈表。
dev:該user引用的device對象指針。
cb():當device狀態發生變化時,會調用該cb()函數以事件的形式來通知所有的users。目前支持的事件類型可參考netifd的DESIGN。
設備struct device中也維護了一個引用計數來控制設備的up/down狀態,每次device_claim(user)成功後,引用計數+1,每次device_release(user)成功後,引用計數-1。當引用計數從0變1,即有一個user使用了該device時,device就會被UP起來,而當引用計數從1變0,即最後一個user離開時,device就會立即被DOWN掉。
接口層(interface)
由於device屬於L2層的概念,如果用戶對一個網絡設備配置屬於L3或更高層協議的屬性,則要直接對interface進行操作,進而間接作用於device。因此一個interface必須綁定到一個device上,通過struct interface的main_dev成員指定。
Interface配置完成後,是否可以UP或DOWN由available和autostart兩個成員來決定:
struct
interface {
… …
bool
available;
bool
autostart;
bool
config_autostart;
/* uci配置中的"autostart",默認爲true。僅用於uci有關的操作 */
… …
};
available:interface是否是可用的(已準備好可以up了),通過interface_set_available()來設置。
autostart:interface在配置完成後,是否自動執行up操作,默認和config_autostart值相同。但如果用戶手動up了interface(如通過ubus來up),則autostart強制變爲true。如果用戶手動down了interface(如通過ubus來down),則autostart強制變爲false。
而一個interface的具體配置內容都放在struct interface_ip_settings的結構中,由於一個interface可能有多個IP/Route/DNS條目,因此將這些信息又封裝了一層,而不是直接放到struct interface中。
Interface user
不常用。Alias設備會產生一個interface user,用於指明自己的parent。Alias自身作爲user的信息保存在struct interface的parent_iface成員中,而其parent則用struct interface的parent_ifname來指定。
Netifd的uci配置
只要熟悉uci那些API,對netifd啓動和reload時讀取配置這部分內容就很容易理解了。Netfid使用名爲network的配置文件。
通過構造函數,在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-device
和
available
決定,
ppp
使用,
static
和
dhcp
不關注
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中配置的項。
而對於static的proto 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標記。
interface的config中具有名爲”proto”的屬性,在interface_init()函數中讀取uci配置獲取proto的名字(如”static”、”dhcp”、”pptp”),然後查找已註冊的對應名字的proto handler,並賦值給interface的proto_handler數據成員,這樣就使一個interface綁定到了一個特定的proto handler上。這是在初始化一個interface的時候完成的。
由於proto是更上層的概念,因此是與interface無關的,而一個interface總會關聯到一種proto。例如,WAN口總會要設置一種上網方式(static/dhcp/pppoe等),因此在interface進行配置的過程中,通過調用其proto_handler的attach()方法,爲自己分配一個struct interface_proto_state結構並賦值給interface的proto數據成員。這個過程是在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)(
structinterface_proto_state *,
enuminterface_proto_event ev);
/* 由特定proto handler來賦值。 */
int
(*notify)(
structinterface_proto_state *,
structblob_attr *data);
int
(*cb)(
structinterface_proto_state *,
enuminterface_proto_cmd cmd,
boolforce);
void
(*
free)(
structinterface_proto_state *);
};
這個結構中的幾個方法:
cb: 在interface向proto發送某個事件event時,proto的處理函數。目前proto接受的事件有兩個:PROTO_CMD_SETUP和PROTO_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_SETUP或IFS_TEARDOWN的時候都會通過該函數通知到proto,發送的事件對應爲PROTO_CMD_SETUP和PROTO_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