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

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;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);

}

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

冬天已经到来,春天还会远吗?

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

相关文章:

你感兴趣的文章:

标签云: