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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章