ILD

netifd 源码分析
作者:Yuan Jianpeng 邮箱:yuanjianpeng@xiaomi.com
发布时间:2022-4-13 站点:Inside Linux Development

最近碰到一个问题,想给br-lan同时配置dhcp和静态,配置如下:

1
2
3
4
5
6
7
8
9
10
11
config interface 'lan'
        option ifname 'eth0 eth1'
        option force_link '1'
        option type 'bridge'
        option proto 'dhcp'
 
config alias lan_alias
        option interface 'lan'
        option proto 'static'
        option ipaddr '192.168.100.1'
        option netmask '255.255.255.0'

有问题,静态只有在dhcp拨上号之后才会生效。反过来则是可以的。netifd的源码以前也阅读过,但是实在过于复杂,这次准备再次阅读。


输出调试信息:

./netfid -d -1 -l 100 -S

-d是设置调试的掩码,-1 打开所有调试类型。

-l是调试等级,设置为一个很大数,开启为debug模式。

-S 输出到stderr。


某些初始化函数是在main之前完成的。netifd使用了

#define __init __attribute__((constructor))


netifd使用avl树来存储proto handler,device, interface等。


register proto

注册静态协议:

proto-static.c

__init static_proto_init()

{

    add_proto_handler(&static_proto);

}


main() -> proto-shell.c proto_shell_init()

打开/lib/netifd/proto目录,popen执行下面的所有的脚本,读取脚本的输出结果,每个脚本对应一个proto。脚本会产生json数据,包含了proto的名字,所有的uci配置等。解析后调用add_proto_handler(proto)来添加添加proto。


可以手动查看 脚本的输出:

# cd /lib/netifd/proto

# ./dhcp.sh '' dump

{ "name": "dhcp", "config": [ [ "ipaddr:ipaddr", 3 ], [ "hostname:hostname", 3 ], [ "clientid", 3 ], [ "vendorid", 3 ], [ "broadcast:bool", 7 ], [ "norelease:bool", 7 ], [ "reqopts:list(string)", 3 ], [ "defaultreqopts:bool", 7 ], [ "iface6rd", 3 ], [ "sendopts:list(string)", 1 ], [ "delegate", 7 ], [ "zone6rd", 3 ], [ "zone", 3 ], [ "mtu6rd", 3 ], [ "customroutes", 3 ], [ "classlessroute", 7 ] ], "no-device": false, "no-proto-task": false, "available": false, "renew-handler": true, "lasterror": false, "teardown-on-l3-link-down": false }


device type

device type定义了设备类型。指定了设备所有的参数、已经创建的方法等。设备类型在main之前的初始化函数中注册。


bridge.c

__init bridge_device_type_init(void)

{

    device_type_add(&bridge_device_type);

}


device.c

__init simple_device_type_init(void)

{

device_type_add(&simple_device_type);

}


vlandev.c

__init vlandev_device_type_init(void)

{

device_type_add(&vlan8021q_device_type);

}


还有一些其它用的不多的设备类型。


bridge device type结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
static struct device_type bridge_device_type = {
        .name = "bridge",
        .config_params = &bridge_attr_list,
        .bridge_capability = true,
        .name_prefix = "br",
        .create = bridge_create,
        .config_init = bridge_config_init,
        .vlan_update = bridge_dev_vlan_update,
        .reload = bridge_reload,
        .free = bridge_free,
        .dump_info = bridge_dump_info,
};

name是device type的名字。

config_params是一个数组,定义了这个设备类型对应的uci配置类型。

create是创建设备的接口。

config_init是解析设备配置参数,比如bridge类型解析brport。


config_params定义了这种类型的设备有哪些uci配置参数:


bridge type的参数:

static const struct blobmsg_policy bridge_attrs[__BRIDGE_ATTR_MAX] = {

[BRIDGE_ATTR_PORTS] = { "ports", BLOBMSG_TYPE_ARRAY },

[BRIDGE_ATTR_STP] = { "stp", BLOBMSG_TYPE_BOOL },

[BRIDGE_ATTR_FORWARD_DELAY] = { "forward_delay", BLOBMSG_TYPE_INT32 },

[BRIDGE_ATTR_PRIORITY] = { "priority", BLOBMSG_TYPE_INT32 },

[BRIDGE_ATTR_AGEING_TIME] = { "ageing_time", BLOBMSG_TYPE_INT32 },

[BRIDGE_ATTR_HELLO_TIME] = { "hello_time", BLOBMSG_TYPE_INT32 },

[BRIDGE_ATTR_MAX_AGE] = { "max_age", BLOBMSG_TYPE_INT32 },

[BRIDGE_ATTR_IGMP_SNOOP] = { "igmp_snooping", BLOBMSG_TYPE_BOOL },

[BRIDGE_ATTR_BRIDGE_EMPTY] = { "bridge_empty", BLOBMSG_TYPE_BOOL },

[BRIDGE_ATTR_MULTICAST_QUERIER] = { "multicast_querier", BLOBMSG_TYPE_BOOL },

[BRIDGE_ATTR_HASH_MAX] = { "hash_max", BLOBMSG_TYPE_INT32 },

[BRIDGE_ATTR_ROBUSTNESS] = { "robustness", BLOBMSG_TYPE_INT32 },

[BRIDGE_ATTR_QUERY_INTERVAL] = { "query_interval", BLOBMSG_TYPE_INT32 },

[BRIDGE_ATTR_LAST_MEMBER_INTERVAL] = { "last_member_interval", BLOBMSG_TYPE_INT32 },

[BRIDGE_ATTR_VLAN_FILTERING] = { "vlan_filtering", BLOBMSG_TYPE_BOOL },

};

比较重要的参数如上,ports表示这个桥有哪些成员,bridge_empty,表示允许没有成员。


simple device 的参数:

static const struct blobmsg_policy dev_attrs[__DEV_ATTR_MAX] = {

[DEV_ATTR_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING },

[DEV_ATTR_MTU] = { .name = "mtu", .type = BLOBMSG_TYPE_INT32 },

[DEV_ATTR_MTU6] = { .name = "mtu6", .type = BLOBMSG_TYPE_INT32 },

[DEV_ATTR_MACADDR] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },

[DEV_ATTR_TXQUEUELEN] = { .name = "txqueuelen", .type = BLOBMSG_TYPE_INT32 },

[DEV_ATTR_ENABLED] = { .name = "enabled", .type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_IPV6] = { .name = "ipv6", .type = BLOBMSG_TYPE_BOOL },

[] = { .name = "ip6segmentrouting", .type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_PROMISC] = { .name = "promisc", .type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_RPFILTER] = { .name = "rpfilter", .type = BLOBMSG_TYPE_STRING },

[DEV_ATTR_ACCEPTLOCAL] = { .name = "acceptlocal", .type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_IGMPVERSION] = { .name = "igmpversion", .type = BLOBMSG_TYPE_INT32 },

[DEV_ATTR_MLDVERSION] = { .name = "mldversion", .type = BLOBMSG_TYPE_INT32 },

[] = { .name = "neighreachabletime", .type = BLOBMSG_TYPE_INT32 },

[] = { .name = "neighgcstaletime", .type = BLOBMSG_TYPE_INT32 },

[DEV_ATTR_DADTRANSMITS] = { .name = "dadtransmits", .type = BLOBMSG_TYPE_INT32 },

[] = { .name = "multicast_to_unicast", .type = BLOBMSG_TYPE_BOOL },

[] = { .name = "multicast_router", .type = BLOBMSG_TYPE_INT32 },

[] = { .name = "multicast_fast_leave", . type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_MULTICAST] = { .name ="multicast", .type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_LEARNING] = { .name ="learning", .type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_UNICAST_FLOOD] = { .name ="unicast_flood", .type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_SENDREDIRECTS] = { .name = "sendredirects", .type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_NEIGHLOCKTIME] = { .name = "neighlocktime", .type = BLOBMSG_TYPE_INT32 },

[DEV_ATTR_ISOLATE] = { .name = "isolate", .type = BLOBMSG_TYPE_BOOL },

[] = { .name = "drop_v4_unicast_in_l2_multicast", .type = BLOBMSG_TYPE_BOOL },

[] = { .name = "drop_v6_unicast_in_l2_multicast", .type = BLOBMSG_TYPE_BOOL },

[] = { .name = "drop_gratuitous_arp", .type = BLOBMSG_TYPE_BOOL },

[] = { .name = "drop_unsolicited_na", .type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_ARP_ACCEPT] = { .name = "arp_accept", .type = BLOBMSG_TYPE_BOOL },

[DEV_ATTR_AUTH] = { .name = "auth", .type = BLOBMSG_TYPE_BOOL },

};

device

device的结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
struct device {
        struct device_type *type;    // device所属的类型
        struct avl_node avl;        // 所有的device存储在avl tree中,这个本devie的avl node
        struct safe_list users;      // device的所有用户
        struct safe_list aliases;
        struct vlist_tree vlans;
        struct kvlist vlan_aliases;
         
        char ifname[IFNAMSIZ + 1];
         
        struct blob_attr *config;
        bool config_pending;
        bool sys_present;
        /* DEV_EVENT_ADD */
        bool present;
        /* DEV_EVENT_UP */
        int active;
        /* DEV_EVENT_LINK_UP */
        bool link_active;
        bool auth_status;
 
        bool external;
        bool disabled;
        bool deferred;
        bool hidden;
 
        bool current_config;
        bool iface_config;
        bool default_config;
        bool wireless;
        bool wireless_ap;
        bool wireless_isolate;
 
        struct interface *config_iface;
 
        /* set interface up or down */
        device_state_cb set_state;
 
        const struct device_hotplug_ops *hotplug_ops;
 
        struct device_user parent;
 
        struct device_settings orig_settings;

          struct device_settings settings;

}

这里字段很多。


parent

是父device,主要是vlan的parent。如eth0是eth0.1的parent。当eth0.1状态发生变化时,会调用

device_claim/device_release,来增减对父device的引用计数,并更改父device的状态。


active

一个用户claim,就+1,一个用户release,就-1。


config_pending

是否延后配置,控制是否执行 device_init_pending()。


设备的用户,struct devce_user

1
2
3
4
5
6
7
8
9
struct device_user {
    struct safe_list list;
    bool claimed;
    bool hotplug;
    bool alias;
    uint8_t ev_idx[__DEV_EVENT_MAX];
    struct device *dev;
    void (*cb)(struct device_user *, enum device_event);
};

claimed,是否执行了device_claim(),执行一次设置为true,再次执行,检查为true,立即返回。

                执行device_release(),设置为false。



device解析和初始化流程


有些device是在配置文件里面直接定义的,如:

config device 'wan_dev'

        option name 'eth1'

        option mtu '1500'

        option keepup '1'


设备初始化,就是读取network中device类型的这些device。

初始化是在 config.c config_init_all()中执行的。

int config_init_all(void)

{

config_init = true;

config_init_devices(true);

config_init_vlans();

 config_init_devices(false);

config_init_interfaces();

config_init = false;


device_init_pending();

interface_start_pending();

}


device的初始化config_init_devices(),执行了两次,一次先初始化桥接口。接着在初始化非桥接口。


config_init_devices(bool bridge) 初始化流程:

1 遍历network的所有section,如果section type不是device则跳过。

2 读取name选项,没有则跳过。name作为device的名字。

3 读取type,如果有type,则使用device_type_get(type),得到devicetype。

4 获得设备的param list,如果有devtype,则使用devtype->config_params。如果没有devtype,

   或者devtype的config_params为空,则使用simple_device_type.config_params;

5 如果是桥设备,则调用config_fixup_bridge_ports()把当前这个device section的ifname选项重命名为

   ports。新的uci应该使用 

        list ports 'eth0'

        list ports 'eth1'

  这种配置,为了兼容老的代码,支持ifname。

6 将根据params,读取uci中的所有参数,并存储到blob buf中。uci_to_blob(&b, s, params);

7 如果有devtype,调用dev = device_create(name, devtype, b.head);创建设备。

    b.head是上一步中读取到的blob配置。

8 如果没有devtype,调用dev = device_get(name, 1); 创建设备,

   设置 dev->current_config = true;

   调用 device_apply_config(dev, dev->type, b.head); 应用配置。

9 设置dev->default_config为false。


device_init_pending(void)

遍历所有的device,如果其dev->config_pending为true。则执行:

dev->type->config_init(dev)

dev->config_pending = false

device_check_state(dev)


device_create()

参数

const char *name, struct device_type *type, struct blob_attr *config

1 查找 odev = device_find(name);

2 如果odev找到,则

    odev->current_config = true;

    change = device_apply_config(odev, type, config);

    如果change是DEV_CONFIG_RECREATE,则调用device_delete(odev);删除旧设备,代码继续往下走。

    如果不是,则直接返回旧设备。

3 复制一下config, config = blob_memdup(config);

4 调用devtype的创建函数创建设备dev = type->create(name, type, config);

5 如果 !config_init && dev->config_pending,则执行 type->config_init(dev);

    然后将 dev->config_pending = false;

    config_init 是 config.c的一个全局变量,在config_init_devices()和config_init_interfaces()期间为false。

    config_pending表示device还未配置,是挂起状态。在桥设备的创建函数中,被设置为true。

6 调用 device_check_state(dev);


device_get()

参数

const char *name, int create

1 avl查找设备 dev = avl_find_element(&devices, name, dev, avl);

2 如果name[0] == '@',则返回 device_alias_get(name + 1);

3 如果找到设备,且create > 1,且 !dev->external,则

    system_if_apply_settings(dev, &dev->settings, dev->settings.flags);

    dev->external = true;

    device_set_present(dev, true);

    create > 1 是ubus等命令创建设备时,指定,此时设备的dev->external设置为true。

4 找到设备,返回dev

5 没找到设备,且没有create,返回空。

6 如果有create,则调用 device_create_default(name, create > 1);


device_apply_config()

参数:struct device *dev, struct device_type *type, struct blob_attr *attr

1 调用change = device_set_config(dev, type, config);

    将config设置到dev->settings中去。

    a. 如果存在,则返回 dev->type->reload(dev, attr)

    b. uci_blob_check_equal(dev->config, attr, cfg) 如果相同,则返回 DEV_CONFIG_NO_CHANGE

    c. 如果是普通设备,则,调用device_init_settings(dev, tb);

    d. 否则返回DEV_CONFIG_RECREATE

2 如果change为DEV_CONFIG_RESTART

    执行down up动作。


device_check_state()

参数:struct device *dev

1 如果不存在dev->type->check_state,则执行simple_device_type.check_state(dev);

2 否则,执行 dev->type->check_state(dev);

这个函数是检查接口的状态,然后拉起接口。因此这个接口是触发事件的。


device_add_user()

参数 struct device_user *dep, struct device *dev

将dep添加到dev,作为dev的用户。

1 首先设置dep->dev = dev

2 如果dep->alias,则将dep添加到dev的alias链表。head = &dev->aliases;

3 否则,添加到普通用户链表。

3 如果 dep->cb存在,且dev->present,则

    因为添加用户后,可能dev可能已经ready,不会产生事件,所以需要初始化调用一下cb。

    a. 执行 dep->cb(dep, DEV_EVENT_ADD)

    b. 如果dev->active。则执行:dep->cb(dep, DEV_EVENT_UP);

    c. 如果dev->link_active,则:dep->cb(dep, DEV_EVENT_LINK_UP);


device_claim()

参数 struct device_user *dep

1 已经claim了,立即返回, if (dep->claimed) return 0

2 设置 dep->claimed = true;

3 device->active 增1,如果大于1,表明已经active过了,则立即返回

4 广播setup事件,device_broadcast_event(dev, DEV_EVENT_SETUP);

5 设置默认参数,当前只有mac参数,device_fill_default_settings(dev); 

6 如果是非external设备,则执行 ret = dev->set_state(dev, true);

7 广播UP事件, device_broadcast_event(dev, DEV_EVENT_UP);


device_release()

参数 struct device_user *dep

是device_claim的反向操作。

1 如果dep->claim为0,则立即返回,

2 设置dep->claim为false。

3 device->active 减1,如果没有减到0,则返回。

4 广播TEARDOWN事件

5 如果非external设备,执行dev->set_state(dev, false);

6 如果dev->active为0,广播 DOWN事件,device_broadcast_event(dev, DEV_EVENT_DOWN);


simple_device_set_state()

参数 struct device *dev, bool state

1 读取parent device,pdev = dev->parent.dev; 如果pdev不为空。

    如果state为up,则 device_claim(&dev->parent);

    如果state为0,则 device_release(&dev->parent);

2 调用set_device_state(dev, state)


system_if_get_settings()

参数:struct device *dev, struct device_settings *s

从内核读取接口的参数,到s,并设置flags。如:

if (ioctl(sock_ioctl, SIOCGIFMTU, &ifr) == 0) {

     s->mtu = ifr.ifr_mtu;

    s->flags |= DEV_OPT_MTU;

}


system_if_apply_settings

参数:struct device *dev, struct device_settings *s, unsigned int apply_mask

设置apply_maks中指定的参数,参数的值存储在s中。设置到dev中去。如:

if (apply_mask & DEV_OPT_MTU) {

     ifr.ifr_mtu = s->mtu;

    ioctl(sock_ioctl, SIOCSIFMTU, &ifr)

}


set_device_state()

参数 struct device *dev, bool state

a. 如果state是up,

    a.1 拿取ifindex, device_set_ifindex(dev, system_if_resolve(dev)); 如果不存在则退出

    a.2 读取旧的设置:system_if_get_settings(dev, &dev->orig_settings);

            保存旧的flags:dev->orig_settings.valid_flags = dev->orig_settings.flags;

            旧的flags与要设置的flags与,保存到旧的flag。这样后续恢复的时候,只要恢复这些被修改的设置。

            dev->orig_settings.flags &= dev->settings.flags;

    a.3 system_if_apply_settings(dev, &dev->settings, dev->settings.flags);

    a.4 拉起接口:system_if_up(dev);

b. 如果state是0

    b.1 system_if_down(dev); down掉接口

    b.2 应用旧配置:

        system_if_apply_settings(dev, &dev->orig_settings, dev->orig_settings.flags);


device_broadcast_cb()

参数 void *ctx, struct safe_list *list

1 计算得到 struct device_user *dep

2 执行 dep->cb(dep, *ev)


device_broadcast_event()

参数 struct device *dev, enum device_event ev

1 遍历dev->users,执行 device_broadcast_cb。

2 编译dev->aliases,执行 device_broadcast_cb


device_set_ifname()

参数:struct device *dev, const char *name

这里不仅支持设置,也支持修改。

1 如果和原来的相同,直接返回,if (!strcmp(dev->ifname, name)) return 0

2 如果有dev->avl.key,则表明插入过了,先删除。avl_delete(&devices, &dev->avl);

3 拷贝到ifname,strcpy(dev->ifname, name);

4 如果有dev-avl.key,则再次插入:ret = avl_insert(&devices, &dev->avl);

5 广播事件:device_broadcast_event(dev, DEV_EVENT_UPDATE_IFNAME);


类似的:

device_set_ifindex(),设置ifindex,并广播DEV_EVENT_UPDATE_IFINDEX事件。

device_set_link(),设置link_active,并广播 DEV_EVENT_LINK_UP、DOWN事件。

device_set_present(),设置sys_present。


device_init_virtual()

参数 struct device *dev, struct device_type *type, const char *name

1 初始化users和aliases链表 

    INIT_SAFE_LIST(&dev->users);

    INIT_SAFE_LIST(&dev->aliases);

2 设置type

    dev->type = type;

3 如果name为非空,则调用 device_set_ifname(dev, name) 设置name到dev->ifname

4 如果dev->set_state为空

    dev->set_state = set_device_state


system_if_clear_state()

参数:struct device *dev

清除桥成员,down掉device。

1 调用device_set_ifindex(),设置设备的ifindex。

2 如果dev->external || !dev->ifindex,则返回。

    这表明external的设备,其状态不会变。

3 将其down掉:system_if_flags(dev->ifname, 0, IFF_UP),移除IFF_UP标志。

4 如果是桥,则删除桥。

5 如果不是桥,但是是某个桥的成员,则将其从桥中移除。

6 调用system_if_clear_entries(),删除这个设备的ROUTE,ADDR等。


device_init()

参数:struct device *dev, struct device_type *type, const char *ifname

1 执行 device_init_virtual(dev, type, ifname)

2 设置 avl key, dev->avl.key = dev->ifname;

3 插入到avl,ret = avl_insert(&devices, &dev->avl);

4 执行,system_if_clear_state(dev);


device_create_default()

参数 const char *name, bool external

1 分配一个dev,dev = calloc(1, sizeof(*dev));

2 设置 dev->external = external;

    dev->set_state = simple_device_set_state;

3 初始化device, device_init(dev, &simple_device_type, name)

4 设置 dev->default_config = true;

5 执行 device_check_state(dev);

6 返回dev


alias device

alias设备是alias的interface。当一个interface up的时候,另外一个interface可以使用这个interface的l3设备拨号。比如:

interface lan

    option proto pppoe

    option ifname eth0


interface lan2

    option ifname @lan

    option proto static


这样,在lan拨上号之后,lan2就可以用ppp接口来设置静态地址。在interface.c的interface_event()函数中调用 alias_notify_device(iface->name, adev) 来通知alias device相关事件。


alias.c,alias设备存储在 aliases avl 树中:static struct avl_tree aliases;


结构体

struct alias_device {

struct avl_node avl;

struct device dev;

struct device_user dep;

struct device_user new_dep;

bool update;

char name[];

};


它也有个dev,嵌入在alias_device结构体里面。

alias device的创建入口是 device_get() 判断 name的第一个字符为 @,调用device_alias_get()。


struct device * device_alias_get(const char *name)

从aliases里面,查找,如果找到,则返回 &alias->dev。

否则,调用 alias_device_create(name, &alias_device_type, NULL) 创建设备。


static struct device *

alias_device_create(const char *name, struct device_type *devtype, struct blob_attr *attr)

1 分配alias结构体,拷贝name到alias->name,

2 device初始化

    alias->dev.set_state = alias_device_set_state

    alias->dev.hidden = true

    device_init_virtual(&alias->dev, devtype, NULL)

    这里device的名字为NULL,也不会加入到正常的device avl树中。

3 将alias插入到aliases avl树中。

4 设置alias->dep

    alias->dep.alias = true;

    alias->dep.cb = alias_device_cb;

5 调用device_check_state(&alias->dev);


alias devtype

static struct device_type alias_device_type = {

.name = "Network alias",

.create = alias_device_create,

.free = alias_device_free,

.check_state = alias_check_state,

};


这里的check_state回调,会在alias device device_check_state() 的时候执行。alias devtype也是一种device type。是不是可以在uci里面直接定义一个alias device。


int alias_check_state(struct device *dev)

这函数的目的,是检查alias对应的interface的l3是否up。如果up,则调用alias_set_device(alias, dev)。

设置alias的真实dev。这个函数是主动检查。而alias_notify_device()是被动通知。

1 得到alias

    alias = container_of(dev, struct alias_device, dev);

2 查找interface

    iface = vlist_find(&interfaces, alias->name, iface, node);

    如果找到,且iface up,则,获得l3 device。

    if (iface && iface->state == IFS_UP)

        ndev = iface->l3_dev.dev;

3 设置l3给alias,alias_set_device(alias, ndev);


void alias_notify_device(const char *name, struct device *dev)

这是interface down up等的时候,调用这个接口,通知alias设备。alias会在alias avl树中找出alias,

然后将dev设置进去。

1 查找alias:

    alias = avl_find_element(&aliases, name, alias, avl);

2 如果找到,则

    alias_set_device(alias, dev);


void alias_set_device(struct alias_device *alias, struct device *dev)

将dev设置到 device_add_user(&alias->dep, dev),这样就会调用cb。来更新alias->dev。

然后设置

    device_set_ifindex(&alias->dev, dev->ifindex);

    device_set_ifname(&alias->dev, dev->ifname);

注意其实alias需要维护新旧两个device user。因为某些拨号方式,前后接口名称是变化的。alias需要

处理这种情况,可能旧的还未释放,新的又来了。


int alias_device_set_state(struct device *dev, bool state)

set_state()通常由device_claim()调用,表示使用者用还是不用这个device。如果用,通常就拉起device。

如果不用,就down掉device。alias的set_state,将这个值传递给l3 device。

1 获取alias:

    alias = container_of(dev, struct alias_device, dev);

2 如果alias->dep.dev为空,则表明l3 device还没有拉起来。直接返回

3 如果state为true,则调用 device_claim(&alias->dep),并返回。

4 如果state为false,则调用  device_release(&alias->dep) 释放。

    如果alias->update为真,表明 l3 device 发生了变化。

    调用 alias_set_device(alias, alias->new_dep.dev),设置新的dev。


void alias_device_cb(struct device_user *dep, enum device_event ev)

这个是alias->dep->cb,在alias_set_device时,调用device_add_user()会调用这个函数。

因此这个函数主要是设置alias->dev的状态。当l3起来的时候,把这个alias dev拉起,然后

在广播到这个alias dev的用户。

1 获得alias

    alias = container_of(dep, struct alias_device, dep);

2 根据事件类型,设置dev的状态:

    DEV_EVENT_ADD:device_set_present(&alias->dev, true);

    DEV_EVENT_REMOVE:device_set_present(&alias->dev, false);

    DEV_EVENT_LINK_UP:device_set_link(&alias->dev, true);

    DEV_EVENT_LINK_DOWN:device_set_link(&alias->dev, false);

    DEV_EVENT_UPDATE_IFINDEX:device_set_ifindex(&alias->dev, dep->dev->ifindex);

    其它事件,直接广播:device_broadcast_event(&alias->dev, ev);


interface

interface是3层接口,可拨号,配置ip,路由。有两种类型接口,interface和alias。


接口初始化:

config_init_interfaces(void)

    遍历interface类型的section,执行 config_parse_interface(s, false);

    遍历alias类型的section,执行 config_parse_interface(s, true)


interface数据结构

struct interface {

struct vlist_node node;

struct list_head hotplug_list;

enum interface_event hotplug_ev;


const char *name;

const char *device;

char *jail;

char *jail_ifname;

int netns_fd;


bool available;

bool autostart;

bool config_autostart;

bool device_config;

bool enabled;

bool link_state;

bool force_link;

bool dynamic;

bool policy_rules_set;

bool link_up_event;


time_t start_time;

enum interface_state state;

enum interface_config_state config_state;

enum interface_update_flags updated;


struct list_head users;


/* for alias interface */

const char *parent_ifname;

struct interface_user parent_iface;


/* main interface that the interface is bound to */

struct device_user main_dev;

struct device_user ext_dev;


/* interface that layer 3 communication will go through */

struct device_user l3_dev;


struct blob_attr *config;


/* primary protocol state */

const struct proto_handler *proto_handler;

struct interface_proto_state *proto;


struct interface_ip_settings proto_ip;

struct interface_ip_settings config_ip;

struct vlist_tree host_routes;

struct vlist_tree host_neighbors;


int metric;

int dns_metric;

unsigned int ip4table;

unsigned int ip6table;


/* IPv6 assignment parameters */

enum interface_id_selection_type assignment_iface_id_selection;

struct in6_addr assignment_fixed_iface_id;

uint8_t assignment_length;

int32_t assignment_hint;

struct list_head assignment_classes;

int assignment_weight;


/* errors/warnings while trying to bring up the interface */

struct list_head errors;


/* extra data provided by protocol handlers or modules */

struct avl_tree data;


struct uloop_timeout remove_timer;

struct ubus_object ubus;

};


接口的用户:

struct interface_user {

struct list_head list;

struct interface *iface;

void (*cb)(struct interface_user *dep, struct interface *iface, enum interface_event ev);

};


接口的uci属性:

static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {

[IFACE_ATTR_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING },

[IFACE_ATTR_PROTO] = { .name = "proto", .type = BLOBMSG_TYPE_STRING },

[IFACE_ATTR_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },

[IFACE_ATTR_AUTO] = { .name = "auto", .type = BLOBMSG_TYPE_BOOL },

[IFACE_ATTR_JAIL] = { .name = "jail", .type = BLOBMSG_TYPE_STRING },

[IFACE_ATTR_JAIL_IFNAME] = { .name = "jail_ifname", .type = BLOBMSG_TYPE_STRING },

[IFACE_ATTR_DEFAULTROUTE] = { .name = "defaultroute", .type = BLOBMSG_TYPE_BOOL },

[IFACE_ATTR_PEERDNS] = { .name = "peerdns", .type = BLOBMSG_TYPE_BOOL },

[IFACE_ATTR_METRIC] = { .name = "metric", .type = BLOBMSG_TYPE_INT32 },

[IFACE_ATTR_DNS] = { .name = "dns", .type = BLOBMSG_TYPE_ARRAY },

[IFACE_ATTR_DNS_SEARCH] = { .name = "dns_search", .type = BLOBMSG_TYPE_ARRAY },

[IFACE_ATTR_DNS_METRIC] = { .name = "dns_metric", .type = BLOBMSG_TYPE_INT32 },

[IFACE_ATTR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_STRING },

[IFACE_ATTR_IP6ASSIGN] = { .name = "ip6assign", .type = BLOBMSG_TYPE_INT32 },

[IFACE_ATTR_IP6HINT] = { .name = "ip6hint", .type = BLOBMSG_TYPE_STRING },

[IFACE_ATTR_IP4TABLE] = { .name = "ip4table", .type = BLOBMSG_TYPE_STRING },

[IFACE_ATTR_IP6TABLE] = { .name = "ip6table", .type = BLOBMSG_TYPE_STRING },

[IFACE_ATTR_IP6CLASS] = { .name = "ip6class", .type = BLOBMSG_TYPE_ARRAY },

[IFACE_ATTR_DELEGATE] = { .name = "delegate", .type = BLOBMSG_TYPE_BOOL },

[IFACE_ATTR_IP6IFACEID] = { .name = "ip6ifaceid", .type = BLOBMSG_TYPE_STRING },

[IFACE_ATTR_FORCE_LINK] = { .name = "force_link", .type = BLOBMSG_TYPE_BOOL },

[IFACE_ATTR_IP6WEIGHT] = { .name = "ip6weight", .type = BLOBMSG_TYPE_INT32 },

};


解析、创建、添加接口:

void config_parse_interface(struct uci_section *s, bool alias)

1 如果不是 alias接口,读取type,并得到 devtype = device_type_get(type)

2 如果devtype是桥类型,则调用 config_parse_bridge_interface(s, devtype)。

    这个接口构造创建device的参数,并调用device_create(name, devtype, b.head),创建桥设备。

    a. 计算桥接口的名字 br-lan,并以name添加到blob,创建device都需要name。

    b. config_fixup_bridge_ports,将旧的ifname option转换为port。

    c. 调用 uci_to_blob(&b, s, devtype->config_params),将section中的参数,转换为blob_buf。

    d. 调用 device_create(name, devtype, b.head) 创建桥设备。

    e. 重新初始化blob_buf,将桥设备的接口名,已ifname参数写入blob_buf。这样继续解析interface

        的时候,有了ifname为br-lan。

3 读取接口配置参数:uci_to_blob(&b, s, &interface_attr_list) 到blob_buf。注意这里的接口属性,

    入、如上面的iface_attrs数组。包括device,proto等。不包括ipv4地址ipaddr等。ipv4是proto属性。

4 创建接口:iface = interface_alloc(s->e.name, b.head, false);

6 如果有proto,则读取proto相关的uci参数到blob_buff

    uci_to_blob(&b, s, iface->proto_handler->config_params)

7 如果不是桥,则读取simple device的uci参数到blob_buff。

8 复制一下配置:config = blob_memdup(b.head);

9 如果是alias interface,调用 interface_add_alias(iface, config) 添加。

10 否则,调用 interface_add(iface, config)添加。


创建接口:

struct interface * interface_alloc(const char *name, struct blob_attr *config, bool dynamic)

这个函数分配iface,然后初始化相关成员,以及根据uci参数,初始化相关成员。

1 分配iface

    初始化几个list,iface->errors,iface->users 等。

2 interface_ip_init(iface),初始化iface的iface->proto_ip,iface->config_ip,iface->host_routes

    config_ip是uci配置中的ip参数,如dns。

    proto_ip是拨号协议的ip参数。

3 初始化3个device user的cb

    iface->main_dev.cb = interface_main_dev_cb

    iface->l3_dev.cb = interface_l3_dev_cb

    iface->ext_dev.cb = interface_ext_dev_cb

4 根据uci参数实话,autostart, force_link等成员。

5 proto_attach_interface(iface, proto_name),将proto_handler赋给 iface->proto_handler。

6 返回iface。


添加 interface

bool __interface_add(struct interface *iface, struct blob_attr *config, bool alias)

这个函数,将iface加入到interfaces 链表中。还有一个重要的功能。解析接口。

如果是alias interface。读取uci option "interface" 到 iface->parent_ifname,如果没有则返回false。

如果不是alias interface,则读取uci option "device",如果没有,则读取ifname。到 iface->device。


初始化拉起接口

interface_start_pending(void)

遍历所有的接口,如果iface->autostart为true,则执行 interface_set_up(iface)。启动接口。

autostart为false的接口,只能通过ubus拉起了。


interface update 机制

在初始化interfaces vlist_tree的时候,指定了一个update接口函数。这个是vlist tree的一个机制。

在插入、更新、删除 成员的时候,都会掉这个函数。

vlist_init(&interfaces, avl_strcmp, interface_update)


interface_update()

参数:struct vlist_tree *tree, struct vlist_node *node_new, struct vlist_node *node_old

如果是新增interface,则执行下面的动作:

    interface_event(if_new, IFEV_CREATE)

    proto_init_interface(if_new, if_new->config)

    interface_claim_device(if_new)

    netifd_ubus_add_interface(if_new), 给这个interface添加ubus object。如network.interface.lan


interface_claim_device(struct interface *iface)

这是一个重要的函数,它执行interface的最后初始化动作。将interface、alias interface、device的关系建立起来。

1 如果有iface->parent_ifname,则是一个alias interface,则:

    在interface中查找parent。设置 iface->parent_iface.cb 为  interface_alias_cb。

    调用interface_add_user()然后将iface->parent_iface设置为parent的users。

    在interface_event()中,将遍历所有users,执行cb。也就是说interface_alias_cb()会被调用。

2 如果iface->device非空,且协议不支持NODEV,则

    dev = device_get(iface->device, true) 创建或获得一个device。

    如果dev存在,将其设置为main dev。interface_set_main_dev(iface, dev)


接口事件通知:

interface_event(struct interface *iface, enum interface_event ev)

接口发生某个事件后,调用这个函数,来通知接口的用户,以及接口的l3 device的alias用户。

1 遍历iface->users,执行 dep->cb(dep, iface, ev)

2 遍历 iface_all_users,执行 dep->cb(dep, iface, ev);

3 调用  alias_notify_device(iface->name, adev),通知alias device。


void interface_set_proto_state(struct interface *iface, struct interface_proto_state *state)

将interface_proto_state设置到interface。这个函数主要是设置变量。

iface->state = IFS_DOWN

iface->proto = state

state->proto_event = interface_proto_event_cb

state->iface = iface

最重要的是指定了proto event的回调函数为interface_proto_event_cb,在拨上号等事件的时候

会调用这个函数。


interface_set_up(struct interface *iface)

在config_init_all()中:

config_init_interfaces()-》config_parse_interface(),负责解析、创建、添加接口。

interface_start_pending()-》interface_set_up()负责拉起所有的接口,使其开始拨号、配置。

void

interface_proto_event_cb(struct interface_proto_state *state, enum interface_proto_event ev)

proto拨上号等事件,interface的处理函数。


proto

proto是拨号协议,用来实现DHCP, PPPoE等拨号。它主要有两个结构体。


struct proto_handler {

struct avl_node avl;

unsigned int flags;

const char *name;

const struct uci_blob_param_list *config_params;

struct interface_proto_state *(*attach)(const struct proto_handler *h,

struct interface *iface, struct blob_attr *attr);

};


proto_handler是具体的协议。每个协议都会注册一个proto_handler。在proto_shell_add_handler()函数会添加所有的shell协议,除了static外,所有的proto都是动态协议。attach 被设置为:proto->attach = proto_shell_attach。


struct interface_proto_state {

const struct proto_handler *handler;

struct interface *iface;


/* filled in by the protocol user */

void (*proto_event)(struct interface_proto_state *, enum interface_proto_event ev);


/* filled in by the protocol 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 *);

};


interface_proto_state 是一个拨号实例,由proto_handler的attach函数动态创建,附着在iface上。

对于shell proto:notify 设置为 proto_shell_notify。cb 设置为 proto_shell_handler。


proto_event是由interface层设置的,给proto层调用的回调函数,当proto层发生事件的时候,调用这个函数通知interface。调用过程:

    proto_shell_notify -》  proto_shell_update_link -》proto_event(&state->proto, IFPEV_UP)

    

notify是由拨号script通过ubus发送拨号结果,调用过程:

    .name = "notify_proto", .handler = netifd_iface_notify_proto

    netifd_iface_notify_proto -》 iface->proto->notify(iface->proto, msg)


cb是由interface调用,给proto发命令,用于控制proto的up、down,调用过程:

    interface_proto_event ->proto->cb(proto, cmd, force)


proto.c 接口

======================


proto_attach_interface(struct interface *iface, const char *proto_name)

就是找到proto_name对应的proto_handler,赋给 iface->proto_handler,如果没有,则把

no_proto赋给iface->proto_handler。


proto_init_interface(struct interface *iface, struct blob_attr *attr)

在interface update中被调用。

执行proto->attach(proto, iface, attr),分配 struct interface_proto_state *state,

然后调用 interface_set_proto_state(iface, state)。这个函数设置proto的interface回调proto_event。


interface_proto_event()

interface调用这个接口,来调用proto的cb。

主要是执行:ret = proto->cb(proto, cmd, force)




proto-shell.c 接口

===========================


struct proto_shell_handler {

struct list_head list;

struct proto_handler proto;

char *config_buf;

char *script_name;

bool init_available;


struct uci_blob_param_list config;

};


proto_handler结构体是内嵌在上述结构体中的,上述结构体还包含了proto_handler额外所需要的信息。

这些信息是通用proto层不需要的。



struct proto_shell_state {

struct interface_proto_state proto;

struct proto_shell_handler *handler;

struct blob_attr *config;


struct uloop_timeout teardown_timeout;


/*

* Teardown and setup interface again if it is still not up (IFS_UP)

* after checkup_interval seconds since previous attempt.  This check

* will be disabled when the config option "checkup_interval" is

* missing or has a negative value

*/

int checkup_interval;

struct uloop_timeout checkup_timeout;


struct netifd_process script_task;

struct netifd_process proto_task;


enum proto_shell_sm sm;

bool proto_task_killed;

bool renew_pending;


int last_error;


struct list_head deps;

};


interface_proto_state 结构体是内嵌在上述结构体中的。它还包含了拨号进程的信息等信息。

有两个进程信息:script_task 和 proto_task。

前者是 netifd 调用 拨号脚本,拨号脚本的进程信息存在script_task。

拨号脚本再给 netifd发ubus消息,由netifd 创建拨号进程。拨号进程存在proto_task。

netifd始终只发起script_task的启动和杀死。而拨号程序总是有脚本发ubus消息给netifd。

netifd按命令,启动和杀死拨号程序。


proto_shell_attach()

shell proto的attach()函数。分配struct proto_shell_state。初始化成员,最后返回interface_proto_state

1 struct proto_shell_state *state = calloc(1, sizeof(*state))


state->proto.free = proto_shell_free;

state->proto.notify = proto_shell_notify;

state->proto.cb = proto_shell_handler;

state->teardown_timeout.cb = proto_shell_teardown_timeout_cb;

state->script_task.cb = proto_shell_script_cb;

state->script_task.dir_fd = proto_fd;

state->script_task.log_prefix = iface->name;

state->proto_task.cb = proto_shell_task_cb;

state->proto_task.dir_fd = proto_fd;

state->proto_task.log_prefix = iface->name;

state->handler = container_of(h, struct proto_shell_handler, proto);


proto_shell_handler()

执行interface的命令,开始拨号,或结束拨号。这个函数的主要任务就是启动、停止拨号程序。

proto_shell_state结构体的script_task成员,存储了拨号程序的信息。

还有一个小的状态机,state->sm。其值有 S_IDLE/S_SETUP/S_SETUP_ABORT/S_TEARDOWN。

拨号命令:PROTO_CMD_SETUP/PROTO_CMD_TEARDOWN/PROTO_CMD_RENEW。


proto_shell_task_finish()

脚本和拨号程序退出的回调proto_shell_script_cb、proto_shell_script_cb会调用这个函数进行处理。

如果是异常退出会产生一些事件,比如:

    state->proto.proto_event(&state->proto, IFPEV_LINK_LOST)

proto_shell_notify ()

处理ubus消息。根据ACTION的不同,执行不同的动作。最重要的是 0/1/2

0:处理拨号结果,调用 proto_shell_update_link

1:启动拨号程序,调用 proto_shell_run_command

2:杀死拨号程序,调用 proto_shell_kill_command


proto_shell_update_link()

处理ubus发过来的拨号结果:

1 读取是 up = blobmsg_get_bool(tb[NOTIFY_LINK_UP])

    如果 没有up,则直接给interface一个LINK_LOST事件:

    state->proto.proto_event(&state->proto, IFPEV_LINK_LOST),并返回。

2 如果传递了NOTIFY_IFNAME,则更新l3 device:

    interface_set_l3_dev(iface, dev)

    device_claim(&iface->l3_dev)

    device_set_present(dev, true)

3 proto_apply_ip_settings(iface, data, addr_ext)

    interface_update_complete(state->proto.iface)

4 state->proto.proto_event(&state->proto, IFPEV_UP)



device激活、事件上报到interface过程




接口启动到开始拨号过程


拨号到生效调用过程

1 拨号脚本调用ubus notify_proto 方法。执行 ubus.c netifd_iface_notify_proto()

2 在 netifd_iface_notify_proto(),首先得到 struct interface *iface。然后调用

    iface->proto->notify(iface->proto, msg)

3 notify接口是在 proto-shell.c proto_shell_attach()的时候注册的

    state->proto.notify = proto_shell_notify。

4  proto_shell_attach()是proto的attach()接口。每个proto都有attach()接口。

    proto->attach()是在proto_init_interface()中执行的。后者又是在interface_update()中执行的。

5 proto_shell_notify()

    根据拨号脚本传递过来的ACTION参数。执行对应的命令。

    其中命令0,是拨上号的命令,执行proto_shell_update_link()

6 proto_shell_update_link()

    





Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.