// 添加网桥设备 // 参数; // 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; }