// 添加网桥设备
// 参数;
// name,需要全局唯一
// 调用路径:socket ioctl->br_add_bridge
// 函数主要任务:
// 1.创建一个新的网络设备
// 2.初始化网络设备的通用字段以及网桥设备的字段
// 3.向系统注册网络设备
1.1 int br_add_bridge(const char *name)
{
struct net_device *dev;//net_bridge->dev
int ret;
dev = new_bridge_dev(name);//创建一个新的网桥设备
if (!dev)
return -ENOMEM;
rtnl_lock();//获取rtnl锁
if (strchr(dev->name, '%')) {//名字中提供了通配符,通过系统递增网桥名中的%d
ret = dev_alloc_name(dev, dev->name);//由系统分配设备名
if (ret < 0)
goto err1;
}
ret = register_netdevice(dev);//注册网络设备设备
if (ret)
goto err2;
dev_hold(dev);//递增设备引用计数
rtnl_unlock();//由rtnl_unlock完成register_netdevice的下半部操作
ret = br_sysfs_addbr(dev);//初始化网桥相关的sysfs
dev_put(dev);
if (ret)
unregister_netdev(dev);
out:
return ret;
err2:
free_netdev(dev);
err1:
rtnl_unlock();
goto out;
}
// 分配网桥设备
// 网桥设备使用网络设备的通用控制块net_device
// 调用路径:br_add_bridge->new_bridge_dev
// 函数主要任务:
// 1.分配网络设备描述符
// 2.特定于网桥设备的初始化函数初始化设备描述符
// 3.初始化网桥id = [0x80,0x00,0x00 0x00 0x00 0x00 0x00 0x00],其中6字节的以太网地址部分,添加网桥端口时更新。
// 4.初始化网桥路径开销,拓扑变化,定时器等字段
// 5.初始化网桥定时器
1.2 static struct net_device *new_bridge_dev(const char *name)
{
struct net_bridge *br;
struct net_device *dev;
dev = alloc_netdev(sizeof(struct net_bridge), name,
br_dev_setup);//分配一个net_device,提供初始化函数
if (!dev)
return NULL;
br = netdev_priv(dev);//dev->priv为一个net_bridge结构
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;//网桥id中第一部分优先级
br->bridge_id.prio[1] = 0x00;
memset(br->bridge_id.addr, 0, ETH_ALEN);//网桥id中第二部分mac地址
br->stp_enabled = 0;//stp不使能
br->designated_root = br->bridge_id;///初始化时,根网桥id为自己的id
br->root_path_cost = 0;//到根网桥的最佳路径开销为0,因为初始化时,认为自己就是根网桥
br->root_port = 0;//根端口的端口号
br->bridge_max_age = br->max_age = 20 * HZ;//BPDU信息的生存期
br->bridge_hello_time = br->hello_time = 2 * HZ;//定期产生配置BPDU的时间间隔
br->bridge_forward_delay = br->forward_delay = 15 * HZ;//状态转移定时器
br->topology_change = 0;//指示根网桥在配置BPDU中设定一个特殊标识TC,将拓扑变化通知其他网桥
br->topology_change_detected = 0;//当探测到拓扑变化时,就会设定该标志
br->ageing_time = 300 * HZ;//转发项最长没有被使用的时间
INIT_LIST_HEAD(&br->age_list);//转发项的最近最少被使用链表
br_stp_timer_init(br);//初始化网桥与端口使用的定时器
return dev;
}
// 网桥net_device初始化函数
// 参数:
// dev, 网桥设备的设备描述符
// 调用路径: br_add_bridge->new_bridge_dev->alloc_netdev->br_dev_setup
// 函数主要任务:
// 1.以太网通用例程初始化dev结构
// 2.初始化dev的函数指针为网桥设备专用指针
// 注:linux网桥是以太网桥,以虚拟网络设备存在于内核中
1.3 void br_dev_setup(struct net_device *dev)
{
memset(dev->dev_addr, 0, ETH_ALEN);//设备mac地址为0
ether_setup(dev);//调用以太网设备的初始化例程
dev->do_ioctl = br_dev_ioctl;//网桥设备特殊文件的ioctl命令
dev->get_stats = br_dev_get_stats;//统计信息
dev->hard_start_xmit = br_dev_xmit;//传输函数
dev->open = br_dev_open;//设备开启函数,在dev_open中被调用
dev->set_multicast_list = br_dev_set_multicast_list;//设置多播列表
dev->change_mtu = br_change_mtu;//mtu改变
dev->destructor = free_netdev;//在dev_destory中被调用
SET_MODULE_OWNER(dev);
dev->stop = br_dev_stop;//在dev_close中被调用
dev->accept_fastpath = br_dev_accept_fastpath;
dev->tx_queue_len = 0;//不使用队列规则
dev->set_mac_address = NULL;
dev->priv_flags = IFF_EBRIDGE;//私有字段,通用框架不会使用此字段,由网桥设备指示此设备为网桥设备
}
// 删除网桥设备
// 在删除网桥前,需要先关闭网桥
// 调用路径:socket ioctl->br_del_bridge
// 函数主要任务:
// 1.在rtnl锁的保护下,执行删除操作
// 2.获取设备描述符
// 3.检查是否为网桥设备
// 2.1 dev->priv_flags=IFF_EBRIDGE
// 4.删除网桥
2.1 int br_del_bridge(const char *name)
{
struct net_device *dev;
int ret = 0;
rtnl_lock();//获取rtnl锁
dev = __dev_get_by_name(name);//通过设备名hash表中查找网桥设备
if (dev == NULL)
ret = -ENXIO;
else if (!(dev->priv_flags & IFF_EBRIDGE)) {//试图去释放一个非网桥设备
ret = -EPERM;
}
else if (dev->flags & IFF_UP) {//删除设备前要先关闭设备
ret = -EBUSY;
}
else
del_br(netdev_priv(dev));//执行实际的删除工作
rtnl_unlock();//在解锁时,完成注销设备的下半部操作
return ret;
}
// 删除网桥设备
// 调用路径:br_del_bridge->del_br
// 函数主要任务:
// 1.依次删除网桥端口
// 2.停用网桥的垃圾回收定时器
// 3.注销网络设备
2.2 static void del_br(struct net_bridge *br)
{
struct net_bridge_port *p, *n;
//通过xx_safe遍历链表,可以边遍历边删除
list_for_each_entry_safe(p, n, &br->port_list, list) {
br_sysfs_removeif(p);//在sysfs中删除网桥端口
del_nbp(p);//删除网桥端口
}
del_timer_sync(&br->gc_timer);//停用垃圾回收机制
br_sysfs_delbr(br->dev);//在sysfs中删除网桥设备
unregister_netdevice(br->dev);//注销设备
}
// 删除网桥端口
// 函数主要任务:
// 1.恢复端口的非混杂模式
// 2.关闭端口stp功能
// 3.删除端口在转发数据中的信息
// 4.删除端口使用的定时器
// 注:linux网桥端口需要运行在混杂模式,这样便可以接收共享介质上所有的数据帧。
2.3 static void del_nbp(struct net_bridge_port *p)
{
struct net_bridge *br = p->br;
struct net_device *dev = p->dev;
//混杂模式通过计数器的方式,而不是二值的形式记录当前状态
dev_set_promiscuity(dev, -1);//网桥的端口均处于混杂模式,递减混杂模式计数器
spin_lock_bh(&br->lock);
br_stp_disable_port(p);//设置端口为指定端口,删除端口相关的三个定时器,更新网桥的配置信息,必要时发送配置BPDU
spin_unlock_bh(&br->lock);
br_fdb_delete_by_port(br, p);//删除转发数据库中关于端口的信息
list_del_rcu(&p->list);
del_timer_sync(&p->message_age_timer);//删除端口使用的三个定时器,这三个定时器在br_stp_disable_port中已经被删除
del_timer_sync(&p->forward_delay_timer);
del_timer_sync(&p->hold_timer);//BPDU的传输速率限制
call_rcu(&p->rcu, destroy_nbp_rcu);//释放net_bridge_port内存
}
// 添加删除网桥端口
// 参数:
// ifindex,以太网设备索引
// isadd,指示添加或删除操作
// 调用路径:网桥特殊设备文件ioctl->add_del_if
3.1 static int add_del_if(struct net_bridge *br, int ifindex, int isadd)
{
struct net_device *dev;
int ret;
//admin权限
if (!capable(CAP_NET_ADMIN))
return -EPERM;
//通过index获取设备
dev = dev_get_by_index(ifindex);
if (dev == NULL)
return -EINVAL;
//添加删除端口
if (isadd)
ret = br_add_if(br, dev);
else
ret = br_del_if(br, dev);
//递减端口引用计数,因为在dev_get_by_index中会递增设备的引用计数
dev_put(dev);
return ret;
}
// 添加网桥端口
// 调用路径:add_del_if->br_add_if
// 函数主要任务:
// 1.检查设备描述符合法性
// 1.1 回环设备,非以太网设备,均不能作为网桥端口
// 1.2 网桥设备不能作为网桥端口
// 1.3 已经作为网桥端口的设备,不能重复添加
// 2. 分配网桥端口描述符
// 3. 将该端口信息添加到转发数据库
// 4. 设置端口混杂模式
// 5. 重新计算网桥id
// 6. 如果端口处于开启模式,开启端口stp
// 注:网桥添加删除端口,会导致重新计算网桥id
3.2 int br_add_if(struct net_bridge *br, struct net_device *dev)
{
struct net_bridge_port *p;
int err = 0;
//网桥端口只能是以太网设备,不能为回环设备
if (dev->flags & IFF_LOOPBACK || dev->type != ARPHRD_ETHER)
return -EINVAL;
//网桥设备的hard_start_xmit为br_dev_xmit
if (dev->hard_start_xmit == br_dev_xmit)
return -ELOOP;
//已经为某个网桥的端口
if (dev->br_port != NULL)
return -EBUSY;
//分配一个网桥端口控制块
if (IS_ERR(p = new_nbp(br, dev, br_initial_port_cost(dev))))
return PTR_ERR(p);
//将端口地址添加到转发数据库
if ((err = br_fdb_insert(br, p, dev->dev_addr, 1)))
destroy_nbp(p);
//在sysyfs中添加信息
else if ((err = br_sysfs_addif(p)))
del_nbp(p);
else {
dev_set_promiscuity(dev, 1);//设置端口为混杂模式
//将端口添加到网桥的port_list中
list_add_rcu(&p->list, &br->port_list);
spin_lock_bh(&br->lock);
br_stp_recalculate_bridge_id(br);//重新计算网桥的id
if ((br->dev->flags & IFF_UP)
&& (dev->flags & IFF_UP) && netif_carrier_ok(dev))
br_stp_enable_port(p);//如果设备有载波,并且处于开启状态,则使能端口的stp,设置端口为指定端口,开启端口的定时器,开启状态选择
spin_unlock_bh(&br->lock);
dev_set_mtu(br->dev, br_min_mtu(br));//更新网桥设备的mtu为所有端口中最小的mtu
}
return err;
}
// 重新计算网桥
// 必要时通过stp协议开始发送配置BPDU
// 函数主要任务:
// 1.选择端口中最小的mac地址
// 2.更新网桥id
// 注:选择网桥端口中mac地址最小者,作为网桥id的mac部分
3.3 void br_stp_recalculate_bridge_id(struct net_bridge *br)
{
const unsigned char *addr = br_mac_zero;//0地址
struct net_bridge_port *p;
list_for_each_entry(p, &br->port_list, list) {//遍历所有的端口
if (addr == br_mac_zero ||//第一次是成立
memcmp(p->dev->dev_addr, addr, ETH_ALEN) < 0)//选择端口中最小的mac地址
addr = p->dev->dev_addr;
}
if (memcmp(br->bridge_id.addr, addr, ETH_ALEN))//如果端口中最小的mac地址与网桥使用的mac地址不同
br_stp_change_bridge_id(br, addr);//更新网桥id
}
// 更新网桥id
// 参数:
// addr, 网桥id中的mac部分
// 调用路径:br_stp_recalculate_bridge_id->br_stp_change_bridge_id
// 函数主要任务:
// 1.拷贝新mac地址到网桥id中
// 2.拷贝新mac地址到网桥设备描述符的addr中
// 3.更新所有指定端口,使用新的mac地址
// 4.更新网桥配置信息
// 5.重启端口状态选择
// 6.如果由非根网桥变为根网桥,发送配置bpdu
// 注:根网桥dev->dev_addr,指定端口dev,使用所有端口中最小的mac地址。
3.4 static void br_stp_change_bridge_id(struct net_bridge *br,
const unsigned char *addr)
{
unsigned char oldaddr[6];
struct net_bridge_port *p;
int wasroot;
wasroot = br_is_root_bridge(br);
memcpy(oldaddr, br->bridge_id.addr, ETH_ALEN);
memcpy(br->bridge_id.addr, addr, ETH_ALEN);
memcpy(br->dev->dev_addr, addr, ETH_ALEN);//设置网桥设备的mac地址为新的mac地址
list_for_each_entry(p, &br->port_list, list) {//遍历所有的网桥端口
if (!memcmp(p->designated_bridge.addr, oldaddr, ETH_ALEN))//更新使用原mac地址的端口
memcpy(p->designated_bridge.addr, addr, ETH_ALEN);
if (!memcmp(p->designated_root.addr, oldaddr, ETH_ALEN))//
memcpy(p->designated_root.addr, addr, ETH_ALEN);
}
br_configuration_update(br);//选择根端口,指定端口,通过已有信息进行选择
br_port_state_selection(br);//开启端口的状态选择
if (br_is_root_bridge(br) && !wasroot)//网桥由非根网桥变为根网桥
br_become_root_bridge(br);//开启topology change timer,发送设置tc标志的配置bpdu
}
// 删除端口
// 函数主要任务:
// 1.删除网桥端口
// 2.重新计算网桥id
4.1 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;
br_sysfs_removeif(p);
del_nbp(p);
spin_lock_bh(&br->lock);
br_stp_recalculate_bridge_id(br);
spin_unlock_bh(&br->lock);
return 0;
}