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);
}
至此,网桥与网桥端口的添加与删除函数分析完毕。