linux 网桥代码分析 三 网桥及网桥端口的添加与删除

来源:互联网 发布:塞班软件下载s60v3 编辑:程序博客网 时间:2024/06/06 07:17

 1、网桥的添加及相关的函数

 

 

br_add_bridge,其函数实现以下功能:

1、调用new_bridge_dev创建并初始化一个网桥设备与网桥

2、调用register_netdevice注册网桥设备

3、调用br_sysfs_addbr增加sysfs相关的功能,主要是建立kobject与sysfs之间的关系

       (分析完网桥的代码以后,我会好好学习一下kobject与sysfs的关系)

 

int br_add_bridge(struct net *net, const char *name)

{

    struct net_device*dev;

    int ret;

 

    dev =new_bridge_dev(net, name);

    if (!dev)

        return -ENOMEM;

 

    rtnl_lock();

    if(strchr(dev->name, '%')) {

        ret =dev_alloc_name(dev, dev->name);

        if (ret < 0)

            goto out_free;

    }

 

    SET_NETDEV_DEVTYPE(dev,&br_type);

 

    ret =register_netdevice(dev);

    if (ret)

        goto out_free;

 

    ret =br_sysfs_addbr(dev);

    if (ret)

        unregister_netdevice(dev);

 out:

    rtnl_unlock();

    return ret;

 

out_free:

    free_netdev(dev);

    goto out;

}

 

在br_add_bridge中,new_bridge_dev是最重要的函数,我们接着分析这个函数的功能及实现

new_bridge_dev代码实现过程如下:

1、调用alloc_netdev,申请一个net_device与net_bridge,并使用函数br_dev_setup初始化网桥

       设备相关的参数。

 2、初始化br相关的自旋锁与链表

 3、设置br的优先级为0x8000

 4、初始化netfilter相关的参数

 

static struct net_device *new_bridge_dev(struct net *net, constchar *name)

{

    struct net_bridge *br;

    struct net_device*dev;

 

    dev =alloc_netdev(sizeof(struct net_bridge), name,

               br_dev_setup);

 

    if (!dev)

        return NULL;

    dev_net_set(dev, net);

 

    br = netdev_priv(dev);

    br->dev = dev;

 

    spin_lock_init(&br->lock);

    INIT_LIST_HEAD(&br->port_list);

    spin_lock_init(&br->hash_lock);

 

    br->bridge_id.prio[0]= 0x80;

    br->bridge_id.prio[1]= 0x00;

 

    memcpy(br->group_addr,br_group_address, ETH_ALEN);

 

    br->feature_mask =dev->features;

    br->stp_enabled =BR_NO_STP;

    br->designated_root= br->bridge_id;

    br->root_path_cost= 0;

    br->root_port = 0;

    br->bridge_max_age= br->max_age = 20 * HZ;

    br->bridge_hello_time= br->hello_time = 2 * HZ;

    br->bridge_forward_delay= br->forward_delay = 15 * HZ;

    br->topology_change= 0;

    br->topology_change_detected= 0;

    br->ageing_time =300 * HZ;

 

    br_netfilter_rtable_init(br);


    INIT_LIST_HEAD(&br->age_list);

 

    br_stp_timer_init(br);

 

    return dev;

}

 在调用alloc_dev中,除了申请net_device的内存以外。还会调用br_dev_setup对网桥设备设备进行初始化。下面我们看下br_dev_setup函数:

void br_dev_setup(struct net_device *dev)
{
random_ether_addr(dev->dev_addr);
ether_setup(dev);
/*设置网桥设备的回调函数*/
dev->netdev_ops = &br_netdev_ops;
dev->destructor = free_netdev;
SET_ETHTOOL_OPS(dev, &br_ethtool_ops);
dev->tx_queue_len = 0;
/*设置设备的标签为IFF_EBRIDGE*/
dev->priv_flags = IFF_EBRIDGE;


dev->features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA |
NETIF_F_GSO_MASK | NETIF_F_NO_CSUM | NETIF_F_LLTX |
NETIF_F_NETNS_LOCAL | NETIF_F_GSO;
}

 此函数将br_netdev_ops赋值给br_dev->netdev_ops,而br_netdev_ops的定义如下:

static const struct net_device_ops br_netdev_ops = {
.ndo_open = br_dev_open,
.ndo_stop = br_dev_stop,
.ndo_start_xmit= br_dev_xmit,
.ndo_set_mac_address= br_set_mac_address,
.ndo_set_multicast_list= br_dev_set_multicast_list,
.ndo_change_mtu= br_change_mtu,
.ndo_do_ioctl = br_dev_ioctl,//网桥相关的ioctl,此处的ioctl 用于处理添加和删除网桥端口的。


/*
该函数会在什么时候调用呢,当socket接收到添加或者删除网桥端口的ioctl后,就会
调用到函数dev_ioctl,dev_ioctl会调用 dev_ifsioc,而 dev_ifsioc就通过 ops->ndo_do_ioctl,从而调用到
br_dev_ioctl


*/
};
在该数据结构中,将ndo_do_ioctl成员变量赋值为br_dev_ioctl,而br_dev_ioctl主要是处理add br if、del br if的ioctl,我们还记得在网桥代码初始化时,有对socket的ioctl进行

网桥相关的扩展,实现add br 、del br的ioctl。而此处则实现了add br if 、del br if的socket ioctl扩展。


那如何在socket的ioctl扩展中增加该ioctl呢?

当socket接收到添加或者删除网桥端口的ioctl后,就会调用到函数dev_ioctl,dev_ioctl会调用 dev_ifsioc,

而 dev_ifsioc就通过 ops->ndo_do_ioctl,从而调用到br_dev_ioctl



2、网桥的删除及相关函数

br_del_bridge

该函数实现删除网桥的功能,该函数的逻辑比较简单,主要实现以下两点

/*

1、首先判断能否删除网桥(如果设备不是网桥或者网桥设备是up状态时不能删除网桥)

2、若符合删除的条件,则调用del_br进行删除

*/

int br_del_bridge(struct net *net, const char *name)

{

    struct net_device*dev;

    int ret = 0;

 

    rtnl_lock();

    dev =__dev_get_by_name(net, name);

    if (dev == NULL)

        ret =  -ENXIO; /*Could not find device */

 

    else if(!(dev->priv_flags & IFF_EBRIDGE)) {

        /* Attempt todelete non bridge device! */

        ret = -EPERM;

    }

 

    else if (dev->flags& IFF_UP) {

        /* Not shutdownyet. */

        ret = -EBUSY;

    }

 

    else

        del_br(netdev_priv(dev));

 

    rtnl_unlock();

    return ret;

}

在br_del_bridge中,del_br是最终实现删除网桥的函数,接下来我们分析一下这个函数

del_br,该函数的实现原理如下:

由于删除网桥时,就需要先删除掉绑定在该网桥下的所有网桥端口

1、通过list_for_each_entry_safe,并调用del_nbp,循环删除掉该网桥下的所有网桥端口。此处使用list_for_each_entry_safe,是因为我们在遍历的同时需要删除节点

2、调用br_sysfs_delbr,从linux系统中删除网桥相关联的kobject

3、注销网桥设备。

*/

static void del_br(struct net_bridge *br)

{

    struct net_bridge_port*p, *n;

 

    list_for_each_entry_safe(p,n, &br->port_list, list) {

        del_nbp(p);

    }

 

    del_timer_sync(&br->gc_timer);

 

    br_sysfs_delbr(br->dev);

    unregister_netdevice(br->dev);

}

 

在这个函数里,del_nbp是删除网桥端口的函数,我们待会仔细分析。而函数br_sysfs_delbr则实现了删除网桥net_bridge的功能。

在br_sysfs_delbr中,会删除网桥设备所对应的kobjec与sysfs的关联,并释放kobject。

 

3、网桥端口的添加及相关的函数

首先我们需要知道,哪些设备不能作为网桥端口

不能添加到桥组的设备符合以下几个条件

1、回环设备不能作为网桥端口,

2、非以太网设备不能作为网桥端口

3、网桥设备不能作为网桥端口

4、已经加入到桥组中的设备不能再次加入桥组

 

下面分析函数br_add_if

该函数执行以下操作

1、首先判断该设备是否符合加入桥组的条件

2、若设备符合条件,则调用new_nbp,生成一个新的网桥端口并进行初始化

3、调用dev_set_promiscuity,将该设备设置为混杂模式

4、调用kobject_init_and_add,为该网桥端口创建kobject并与网桥的kobject关联

5、调用fdb_insert,为该网桥端口建立转发数据库项

6、将该网桥端口加入到网桥的port_list中

7、判断设备是否是up状态,若是up状态,则调用br_stp_enable_port,使能网桥端口

8、设置网桥设备的mtu

9、调用kobject与sysfs的接口函数,实现kobject与sysfs之间的关联

       (看来sysfs的设备模型很重要,需要好好分析分析)

*/

/* called with RTNL */

int br_add_if(struct net_bridge *br, struct net_device *dev)

{

    struct net_bridge_port*p;

    int err = 0;

 

    /* Don't allowbridging non-ethernet like devices */

    if ((dev->flags& IFF_LOOPBACK) ||

        dev->type != ARPHRD_ETHER ||dev->addr_len != ETH_ALEN)

        return -EINVAL;

 

    /* No bridging ofbridges */

    if(dev->netdev_ops->ndo_start_xmit == br_dev_xmit)

        return -ELOOP;

 

    /* Device is alreadybeing bridged */

    if (dev->br_port !=NULL)

        return -EBUSY;

 

    p = new_nbp(br, dev);

    if (IS_ERR(p))

        return PTR_ERR(p);

 

    err =dev_set_promiscuity(dev, 1);

    if (err)

        goto put_back;

 

    err =kobject_init_and_add(&p->kobj, &brport_ktype,&(dev->dev.kobj),

                   SYSFS_BRIDGE_PORT_ATTR);

    if (err)

        goto err0;

 

    err =br_fdb_insert(br, p, dev->dev_addr);

    if (err)

        goto err1;

 

    err =br_sysfs_addif(p);

    if (err)

        goto err2;

 

    rcu_assign_pointer(dev->br_port,p);

    dev_disable_lro(dev);

 

    list_add_rcu(&p->list,&br->port_list);

 

    spin_lock_bh(&br->lock);

    br_stp_recalculate_bridge_id(br);

    br_features_recompute(br);

 

    if ((dev->flags& IFF_UP) && netif_carrier_ok(dev) &&

        (br->dev->flags & IFF_UP))

        br_stp_enable_port(p);

    spin_unlock_bh(&br->lock);

 

    br_ifinfo_notify(RTM_NEWLINK,p);

 

    dev_set_mtu(br->dev,br_min_mtu(br));

 

    kobject_uevent(&p->kobj,KOBJ_ADD);

 

    return 0;

err2:

    br_fdb_delete_by_port(br,p, 1);

err1:

    kobject_put(&p->kobj);

    p = NULL; /*kobject_put frees */

err0:

    dev_set_promiscuity(dev,-1);

put_back:

    dev_put(dev);

    kfree(p);

    return err;

}

在函数br_add_if中,最主要的函数为new_nbp,该函数用于创建一个网桥端口

该函数的功能如下:

1、首先获取一个未被使用的端口号,主要使用函数find_portno

2、申请内存

3、对端口的br、dev、port_no进行初始化

static struct net_bridge_port *new_nbp(struct net_bridge *br,

                       struct net_device *dev)

{

    int index;

    struct net_bridge_port*p;

 

    index =find_portno(br);

    if (index < 0)

        returnERR_PTR(index);

 

    p =kzalloc(sizeof(*p), GFP_KERNEL);

    if (p == NULL)

        returnERR_PTR(-ENOMEM);

 

    p->br = br;

    dev_hold(dev);

    p->dev = dev;

    p->path_cost =port_cost(dev);

    p->priority =0x8000 >> BR_PORT_BITS;

    p->port_no = index;

    p->flags = 0;

    br_init_port(p);

    p->state =BR_STATE_DISABLED;

    br_stp_port_timer_init(p);

 

    return p;

}

 

 

4、网桥端口的删除及相关函数

br_del_if的作用是从桥组中删除一个网桥端口

1、判断端口是否在一个网桥中,若没在桥组中,则直接返回

2、调用del_nbp,删除一个网桥端口

int br_del_if(struct net_bridge *br, struct net_device *dev)

{

    struct net_bridge_port*p = dev->br_port;

 

    if (!p || p->br !=br)

        return -EINVAL;

 

    del_nbp(p);

 

    spin_lock_bh(&br->lock);

    br_stp_recalculate_bridge_id(br);

    br_features_recompute(br);

    spin_unlock_bh(&br->lock);

 

    return 0;

}

 

 

下面分析主要执行删除操作的del_nbp

该函数执行以下操作:

1、调用sysfs_remove_link,删除br->ifobj目录下名为name的软链接文件

2、调用dev_set_promiscuity,设置网卡的工作模式为普通模式

3、调用br_stp_disable_port,设置端口的状态为disable

4、调用br_fdb_delete_by_port,删除CAM表中与该port有关的表项

5、调用list_del_rcu,将该port端口从网桥的port_list链表中删除

6、设置dev->br_port的指针为NULL

7、从linux系统中删除该port对应的kobject项

8、通过call_rcu机制,调用destroy_nbp_rcu,将该网桥端口的内存释放掉

*/

static void del_nbp(struct net_bridge_port *p)

{

    struct net_bridge *br= p->br;

    struct net_device *dev= p->dev;

 

    sysfs_remove_link(br->ifobj,dev->name);

 

    dev_set_promiscuity(dev,-1);

 

    spin_lock_bh(&br->lock);

    br_stp_disable_port(p);

    spin_unlock_bh(&br->lock);

 

    br_ifinfo_notify(RTM_DELLINK,p);

 

    br_fdb_delete_by_port(br,p, 1);

 

    list_del_rcu(&p->list);

 

    rcu_assign_pointer(dev->br_port,NULL);

 

    kobject_uevent(&p->kobj,KOBJ_REMOVE);

    kobject_del(&p->kobj);

 

    call_rcu(&p->rcu,destroy_nbp_rcu);

}

该函数通过call_rcu,会调用destroy_nbp_rcu删除网桥端口

 

而destroy_nbp_rcu实现以下功能

1、通过调用container_of,获取到桥端口的地址

2、调用destory_nbp销毁端口

*/

static void destroy_nbp_rcu(struct rcu_head *head)

{

    struct net_bridge_port*p =

            container_of(head,struct net_bridge_port, rcu);

    destroy_nbp(p);

}

 

我们接着分析destroy_nbp,其执行以下操作。

1、将该桥端口中的br、dev指针设置为NULL

2、递减dev的使用计数

3、递减p->kobj的引用计数

 

static void destroy_nbp(struct net_bridge_port *p)

{

    struct net_device *dev= p->dev;

 

    p->br = NULL;

    p->dev = NULL;

    dev_put(dev);

 

    kobject_put(&p->kobj);

}

 

而对于kobject_put,若网桥端口对应的kobject的引用计数为0时,则会执行kobject_release,而kobject_release就会调用kobj_type->release,网桥端口的kobj_type的定义如下:

/*

该结构体里包含了kobject相关的属性,当kobject的引用计数归零后,就会调用

release函数释放kobject占用的内存。

*/

static struct kobj_type brport_ktype = {

#ifdef CONFIG_SYSFS

    .sysfs_ops =&brport_sysfs_ops,

#endif

    .release = release_nbp,

};

 

这样就执行了函数release_nbp,release_nbp就实现了是否网桥端口的操作。

/*

作用:根据kobj,释放掉一个网桥端口

1、通过调用container_of,获取kobj所属于的网桥端口

2、释放内存

*/

static void release_nbp(struct kobject *kobj)

{

    struct net_bridge_port*p

        =container_of(kobj, struct net_bridge_port, kobj);

    kfree(p);

}

 

 

至此,网桥与网桥端口的添加与删除函数分析完毕。

0 0
原创粉丝点击