网桥处理

一、桥模块初始化
br_init
  //创建BSPAN协议,同时设置了接收处理回调函数为br_stp_rcv
  br_stp_sap = llc_sap_open(LLC_SAP_BSPAN, br_stp_rcv);
  sap = llc_sap_alloc()
  sap->laddr.lsap = lsap;
  sap->rcv_func	= func;
  llc_add_sap(sap);

  //初始化转发数据库
  br_fdb_init()
  br_fdb_cache = kmem_cache_create("bridge_fdb_cache",
  sizeof(struct net_bridge_fdb_entry),0,SLAB_HWCACHE_ALIGN, NULL, NULL);

  //网桥的netfilter回调钩子初始化,根据br_nf_ops数组初始化nf_hooks	
  br_netfilter_init();
  for (i = 0; i < ARRAY_SIZE(br_nf_ops); i++)
  nf_register_hook(&br_nf_ops[i]))
  //每次注册一个钩子,都要在原钩子列表中查找,按优先级排序,将新的
  //钩子加入到比它优先级低的前面(priority 越小优先级越高)
  list_for_each(i, &nf_hooks[reg->pf][reg->hooknum])
  if (reg->priority < ((struct nf_hook_ops *)i)->priority)
  Break;
  list_add_rcu(&reg->list, i->prev);

  //注册sysctl控制对象
  register_sysctl_table(brnf_net_table, 0);

  //向netdevice模块注册通知链,当接口UP/DOWN,地址变化等情况,网桥对应进行处
  //理
  register_netdevice_notifier(&br_device_notifier);
  
  //初始化netlink,用于处理用户层更改桥端口状态,查看桥信息等
  br_netlink_init()
  rtnetlink_links[PF_BRIDGE] = bridge_rtnetlink_table;
  
  //设置网桥的ioctl回调,用于应用层桥接口设置	
  brioctl_set(br_ioctl_deviceless_stub);
  br_ioctl_hook = hook;
  
  //接收报文后的网桥处理入口函数
  br_handle_frame_hook = br_handle_frame;
  
  //代码调用位置显示是LEC(LAN仿真驱动)使用,暂时先不分析
  br_fdb_get_hook = br_fdb_get;
  br_fdb_put_hook = br_fdb_put;
  
二、添加网桥设备
1、用户侧brctl addbr br0//创建AF_LOCAL地址族套接口(AF_LOCAL等同于AF_UNIX)
br_init()
  br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)

//搜索命令条目块,当前为
//{ 1, "addbr", br_cmd_addbr, "<bridge>\t\tadd bridge" },
cmd = command_lookup(*argv)

//执行该命令,当前命令为br_cmd_addbr
cmd->func(argc, argv);
  br_cmd_addbr
  br_add_bridge(argv[1])
  //调用ioctl添加网桥设备
  ioctl(br_socket_fd, SIOCBRADDBR, brname);

2、内核侧接口
sys_ioctl
  vfs_ioctl
  do_ioctl
  //这里最终调用了sys_socket创建的sock_ioctl
  filp->f_op->unlocked_ioctl(filp, cmd, arg)
----------------------------------------------------------------------------------------------------------------
sock_ioctl
  switch (cmd)
  case SIOCBRADDBR:
  //如果当前没有该钩子函数,表示网桥模块还未加载,则进行模块加载
  if (!br_ioctl_hook)
  request_module("bridge");
  
  //这里的br_ioctl_hook是网桥模块初始化时设置的钩子函数br_ioctl_deviceless_stub
  br_ioctl_hook(cmd, argp)
  br_ioctl_deviceless_stub
  switch (cmd)
  case SIOCBRADDBR:
  br_add_bridge(buf)
  dev = new_bridge_dev(name);		//初始化网桥虚拟设备块,这里主要使用br_dev_setup进行
  //网桥设备的特有设置
  dev = alloc_netdev(sizeof(struct net_bridge), name,
  br_dev_setup);
  
  //网桥设置的私有内容为net_bridge结构体
  br = netdev_priv(dev);
  
  br->dev = dev;
  spin_lock_init(&br->lock);
  INIT_LIST_HEAD(&br->port_list);
  spin_lock_init(&br->hash_lock);
  
  //设置固定优先级为0x8000
  br->bridge_id.prio[0] = 0x80;
  br->bridge_id.prio[1] = 0x00;
  
  //网桥特有的组播地址0x0180c20000
  memcpy(br->group_addr, br_group_address, ETH_ALEN);
  
  br->feature_mask = dev->features;
  br->stp_enabled = 0;	//关闭STP
  br->designated_root = br->bridge_id;	//根先认为自身
  br->root_path_cost = 0;				//根路径开销为0
  br->root_port = 0;					//没有根端口
  br->bridge_max_age = br->max_age = 20 * HZ;
  //老化时间20秒
  br->bridge_hello_time = br->hello_time = 2 * HZ;
  //hello定时器时间
  br->bridge_forward_delay = br->forward_delay = 15 * HZ;
  //端口转发状态时间
  br->topology_change = 0;			//拓扑未改变
  br->topology_change_detected = 0;	//拓扑未检测到改变
  br->ageing_time = 300 * HZ;			//转发表的老化时间
  INIT_LIST_HEAD(&br->age_list);
  
  //设置STP几个内核定时器
  br_stp_timer_init(br);
  //定时器作用分别为主桥的hello定时器、拓扑改变通
  //知定时器、拓扑改变定时器、转发表垃圾回收定时器
  setup_timer(&br->hello_timer, br_hello_timer_expired,br)
  setup_timer(&br->tcn_timer, br_tcn_timer_expired,br)
  setup_timer(&br->topology_change_timer,
  br_topology_change_timer_expired,br)
  setup_timer(&br->gc_timer, br_fdb_cleanup,br)
---------------------------------------------------------------------------------------------------------------------
br_dev_setup
  //先调用以太网公共接口设置函数
  ether_setup(dev);
  dev->change_mtu		= eth_change_mtu;
  dev->hard_header	= eth_header;
  dev->rebuild_header 	= eth_rebuild_header;
  dev->set_mac_address 	= eth_mac_addr;
  dev->hard_header_cache	= eth_header_cache;
  dev->header_cache_update= eth_header_cache_update;
  dev->hard_header_parse	= eth_header_parse;
  dev->type		= ARPHRD_ETHER;
  dev->hard_header_len 	= ETH_HLEN;
  dev->mtu		= ETH_DATA_LEN;
  dev->addr_len		= ETH_ALEN;
  dev->tx_queue_len	= 1000;
  dev->flags		= IFF_BROADCAST|IFF_MULTICAST;
  memset(dev->broadcast, 0xFF, ETH_ALEN);

  //设置网桥设备特定的回调处理
  dev->do_ioctl = br_dev_ioctl;
  dev->get_stats = br_dev_get_stats;
  dev->hard_start_xmit = br_dev_xmit;
  dev->open = br_dev_open;
  dev->set_multicast_list = br_dev_set_multicast_list;
  dev->change_mtu = br_change_mtu;
  dev->destructor = free_netdev;
  SET_MODULE_OWNER(dev);
  SET_ETHTOOL_OPS(dev, &br_ethtool_ops);
  dev->stop = br_dev_stop;
  
  dev->tx_queue_len = 0;
  dev->set_mac_address = br_set_mac_address;
  dev->priv_flags = IFF_EBRIDGE;
  
  dev->features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA |
  NETIF_F_TSO | NETIF_F_NO_CSUM | NETIF_F_GSO_ROBUST;
  
三、向网桥设备添加端口设备
1、用户侧 brctl addif br0 eth0
//创建AF_LOCAL地址族套接口(AF_LOCAL等同于AF_UNIX)
br_init()
  br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)

//搜索命令条目块,当前为
//{ 2, "addif", br_cmd_addif,"<bridge> <device>\tadd interface to bridge" },
cmd = command_lookup(*argv)

//执行该命令,当前命令为br_cmd_addif
cmd->func(argc, argv);
  br_cmd_addif
  br_add_interface(brname, ifname)
  //获取接口设备索引
  ifindex = if_nametoindex(dev);
  
  //调用ioctl添加接口
  strncpy(ifr.ifr_name, bridge, IFNAMSIZ);
  ifr.ifr_ifindex = ifindex;
  ioctl(br_socket_fd, SIOCBRADDIF, brname);

2、内核侧接口
sys_ioctl
  vfs_ioctl
  do_ioctl
  //这里最终调用了sys_socket创建的sock_ioctl
  filp->f_op->unlocked_ioctl(filp, cmd, arg)
  sock_ioctl
  dev_ioctl(cmd, argp)
  switch (cmd)
  case SIOCBRADDIF:
  //如果网桥设备没有加载模块,则进行模块加载
  dev_load(ifr.ifr_name);
  
  dev_ifsioc(&ifr, cmd);
  //获取网桥设备
  dev = __dev_get_by_name(ifr->ifr_name);
  
  switch (cmd)
  default:
  //如果设备有ioctl回调,同时设备未节能关闭,则
  //进行回调处理,当前网桥设备的回调函数是在网
  //桥设备创建时设置的br_dev_ioctl
  if (dev->do_ioctl)
  if (netif_device_present(dev)
  dev->do_ioctl(dev, ifr,cmd);
------------------------------------------------------------------------------------------------------------------
br_dev_ioctl
  //从设备对象中获取私有控制块
  br = netdev_priv(dev);

  switch(cmd)
  case SIOCBRADDIF:
  add_del_if(br, rq->ifr_ifindex, cmd == SIOCBRADDIF);
  //根据设备索引,获取要添加的端口物理设备
  dev = dev_get_by_index(ifindex);
  
  //向网桥中添加端口
  br_add_if(br, dev);
  //如果添加的端口设备为回环设备,或者不是以太网设备,则为非法
  if (dev->flags & IFF_LOOPBACK || dev->type != ARPHRD_ETHER)
  return -EINVAL;
  
  //如果添加的端口设备本身也是网桥设备,则为非法
  if (dev->hard_start_xmit == br_dev_xmit)
  return -ELOOP;

  //当前端口设备已经加入到一个网桥中,返回BUSY
  if (dev->br_port != NULL)
  return -EBUSY;

  //创建端口设备控制块
  new_nbp(br, dev);
  //查找一个未使用的端口索引
  index = find_portno(br);
  
  //分配桥端口控制块,p类型为net_bridge_port
  p = kzalloc(sizeof(*p), GFP_KERNEL);
  
  p->br = br;		//端口关联到桥上
  dev_hold(dev);	//增加该物理设备的引用
  p->dev = dev;		//桥端口关联到物理端口上
  //利用物理设备的ethtool回调,根据设备的速率设定端口路径开销,
  //ethtool出错,则默认认为设备为10M速率,对应开销为100
  //速率越高,该值越小(如1000M时为4)
  p->path_cost = port_cost(dev);
  p->priority = 0x8000 >> BR_PORT_BITS;	//桥端口优先级
  p->port_no = index;	//桥端口索引
  br_init_port(p);
  //构建桥端口ID
  p->port_id = br_make_port_id(p->priority, p->port_no);
  //设置指定桥、指定端口
  br_become_designated_port(p);
  p->designated_root = br->designated_root;
  p->designated_cost = br->root_path_cost;
  p->designated_bridge = br->bridge_id;
  p->designated_port = p->port_id;
  p->state = BR_STATE_BLOCKING;	//初始桥端口状态为阻塞
  p->topology_change_ack = 0;
  p->config_pending = 0;
  p->state = BR_STATE_DISABLED;		//桥端口状态更为关闭
  //初始化延时工作队列
  INIT_DELAYED_WORK_NAR(&p->carrier_check, 	port_carrier_check);
  //初始化桥端口相关stp定时
  br_stp_port_timer_init(p);
  setup_timer(&p->message_age_timer, 
  br_message_age_timer_expired,p)
  setup_timer(&p->forward_delay_timer, 
  br_forward_delay_timer_expired,p)
  setup_timer(&p->hold_timer, br_hold_timer_expired,p)

  //kobject相关节点初始化
  kobject_init(&p->kobj);
  kobject_set_name(&p->kobj, SYSFS_BRIDGE_PORT_ATTR);
  p->kobj.ktype = &brport_ktype;
  p->kobj.parent = &(dev->class_dev.kobj);
  p->kobj.kset = NULL;
  
  //添加kobject对象
  kobject_add(&p->kobj)
  
  //将桥端口设备的MAC地址添加到转发表中,关键数据如下
  //fdb->dst = source;		桥端口设备
  //fdb->is_local = is_local;	TRUE
  //fdb->is_static = is_local;	TRUE
  //fdb->ageing_timer = jiffies;
  br_fdb_insert(br, p, dev->dev_addr)
  fdb_insert(br, source, addr)
  
  //向sysfs添加条目
  //1、在桥端口目录下创建软链接bridge,指向桥设备的kobj
  //2、在桥端口目录下创建属性文件
  //3、在桥设备接口目录下创建软链接,名字为端口设备名,指向端
  //口设备的kobj
  br_sysfs_addif(p)
  
  //将物理设备的br_port指向初始化好的桥端口对象
  rcu_assign_pointer(dev->br_port, p);
  //开启设备的混杂模式
  dev_set_promiscuity(dev, 1);
  
  //将桥端口添加到桥设备的端口列表中
  list_add_rcu(&p->list, &br->port_list);
  
  //stp重新校正
  br_stp_recalculate_bridge_id(br);
  //从所有加入网桥的物理端口中选择一个比较小的物理地址做为
  //网桥设备的物理地址
  list_for_each_entry(p, &br->port_list, list)
  if (addr == br_mac_zero ||
  memcmp(p->dev->dev_addr, addr, ETH_ALEN) < 0)
  addr = p->dev->dev_addr;

  //如果当前桥设备ID中的地址与需要设置的地址不同
  if (compare_ether_addr(br->bridge_id.addr, addr))
  //stp参数需要更改
  br_stp_change_bridge_id(br, addr);
  //通过判断br->bridge_id与br->designated_root,检测当前是
  //否是根桥,当前接口刚使用,默认认为自己是根桥。
  wasroot = br_is_root_bridge(br);
  
  memcpy(oldaddr, br->bridge_id.addr, ETH_ALEN);
  
  //把物理地址设置到桥ID末尾MAC地址位置
  //把物理地址设置为当前桥设备的物理地址
  memcpy(br->bridge_id.addr, addr, ETH_ALEN);
  memcpy(br->dev->dev_addr, addr, ETH_ALEN);
  
  //如果之前选定的根桥与指定桥为当前桥,则进行地址更新
  list_for_each_entry(p, &br->port_list, list)
  if (!compare_ether_addr(p->designated_bridge.addr, 
  oldaddr))
  memcpy(p->designated_bridge.addr, addr, 
  ETH_ALEN);
  if (!compare_ether_addr(p->designated_root.addr, 
  oldaddr))
  memcpy(p->designated_root.addr, addr, 
  ETH_ALEN);
  
  br_configuration_update(br);
  //重新选择根桥
  br_root_selection(br);
  root_port = 0;
  //遍历所有桥端口,检查是否可以更新为根端
  //口,这里第一个判断条件就是如果当前桥端
  //口的状态为BR_STATE_DISABLED,则返回
  //0,所以这里root_port不更新
  list_for_each_entry(p, &br->port_list, list)
  if (br_should_become_root_port(p, 
  root_port)
  root_port = p->port_no;
  
  //当前root_port为特定值0,表示没有根端口
  br->root_port = root_port;
  
  //无根端口时,指定根桥为自身,路径开销为
  //0
  if (!root_port)
  br->designated_root = br->bridge_id;
  br->root_path_cost = 0;
  
  //进行指定端口选择
  br_designated_port_selection(br);
  list_for_each_entry(p, &br->port_list, list)
  //这里会检测当前桥端口状态不为
  //BR_STATE_DISABLED时才进行处理,当前
  //条件不满足,所以不进行处理,保持原有值
  if (p->state != BR_STATE_DISABLED &&
  br_should_become_designated_port(p))
  br_become_designated_port(p);
  
  //当前端口为关闭状态,不进行处理
  br_port_state_selection(br);
  
  //从非根桥转为根桥进行更新处理,当前条件不满足,当前
  //一直认为是根桥。
  if (br_is_root_bridge(br) && !wasroot)
  br_become_root_bridge(br);

  //接口特性校正
  br_features_recompute(br)
  //如果当前绑定的接口已有支持硬件校验和,则先置为不需要校验合
  checksum = br->feature_mask & NETIF_F_ALL_CSUM ? 
  NETIF_F_NO_CSUM : 0;
  //去除支持硬件校验和特性
  features = br->feature_mask & ~NETIF_F_ALL_CSUM;
  list_for_each_entry(p, &br->port_list, list)
  feature = p->dev->features;
  //如果当前加入的物理网卡没有标记不需要校验合,则将
  //checksum设置为NETIF_F_HW_CSUM
  if (checksum & NETIF_F_NO_CSUM && !(feature & 
  NETIF_F_NO_CSUM))
  checksum ^= NETIF_F_NO_CSUM | NETIF_F_HW_CSUM;
  //如果当前物理网卡没有标记支持硬件校验合,则将checksum
  //设置为NETIF_F_IP_CSUM
  if (checksum & NETIF_F_HW_CSUM && !(feature & 
  NETIF_F_HW_CSUM))
  checksum ^= NETIF_F_HW_CSUM | NETIF_F_IP_CSUM;
  //如果物理网卡连IP校验合也没设置,则没有设置任何校验合特
  //性
  if (!(feature & NETIF_F_IP_CSUM))
  checksum = 0;
  
  //如果硬件支持NETIF_F_GSO,则设置TCP相关拆分支持?
  if (feature & NETIF_F_GSO)
  feature |= NETIF_F_GSO_SOFTWARE;
  //直接设置桥设备支持GSO?
  feature |= NETIF_F_GSO;
  
  features &= feature;
  
  //如果接口不支持硬件负责所有校验合功能,则去除支持分散聚合特
  //性,如果接口不支持分散聚合特性,则去除
  //NETIF_F_GSO_SOFTWARE设置的一系列TCP拆分支持。
  if (!(checksum & NETIF_F_ALL_CSUM))
  features &= ~NETIF_F_SG;
  if (!(features & NETIF_F_SG))
  features &= ~NETIF_F_GSO_MASK;
  
  //更新桥设备特性,同时附加两个特性
  //NETIF_F_LLTX 无锁特性
  //NETIF_F_GSO_ROBUST 当前为不可信的GSO生成源
  br->dev->features = features | checksum | NETIF_F_LLTX 
  |NETIF_F_GSO_ROBUST;
  
  //10分之1秒后,启动工作队列任务p->carrier_check,该工作队列的主函
  //数为port_carrier_check,在上创建桥端口的new_nbp函数中初始化的。
  schedule_delayed_work(&p->carrier_check, BR_PORT_DEBOUNCE);
  
  //调用网桥设备设置mtu接口函数br_change_mtu(在添加网桥设备时
  //赋值)进行mtu设置,这里取当前桥中所有绑定设备最小的MTU值
  dev_set_mtu(br->dev, br_min_mtu(br));
  
  //发送对象添加事件
  kobject_uevent(&p->kobj, KOBJ_ADD);
---------------------------------------------------------------------------------------------------------------------
//当一个接口加入到桥设备后,一定时间后就进行端口的载波检测
port_carrier_check
  //该工作阶列仅运行一次
  work_release(work);
  
  //检测到当前桥端口有载波,则重新计算端口路径的开销,这里仍然是使用ethtool接口
  //根据网卡的速率进行转化,在上面有介绍。
  if (netif_carrier_ok(dev))
  p->path_cost = port_cost(dev);
  
  //根据当前接口是否开启,以及开启后,是否有载波来确定是否启用桥端口,当前接口
  //未执行ifup,所以这里不进行处理。
  if (br->dev->flags & IFF_UP)
  if (netif_carrier_ok(dev))
  if (p->state == BR_STATE_DISABLED)
  br_stp_enable_port(p);
  Else
  if (p->state != BR_STATE_DISABLED)
  br_stp_disable_port(p);

四、开启网桥设备
1、用户接口ifup br0
//创建UNIX套接口
skfd = sockets_open(0)
  af->fd = socket(af->af, type, 0);
  
//获取接口名
safe_strncpy(ifr.ifr_name, *spp++, IFNAMSIZ);

//使用默认的地址族
ap = get_aftype(DFLT_AF);

if (!strcmp(*spp, "up"))
  //调用ioctl设置IFF_UP 标记位
  set_flag(ifr.ifr_name, (IFF_UP | IFF_RUNNING));
  safe_strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
  ioctl(skfd, SIOCGIFFLAGS, &ifr)
  ifr.ifr_flags |= flag;
  ioctl(skfd, SIOCSIFFLAGS, &ifr)

2、内核侧接口
sys_ioctl
  vfs_ioctl
  do_ioctl
  //这里最终调用了sys_socket创建的sock_ioctl
  filp->f_op->unlocked_ioctl(filp, cmd, arg)
  sock_ioctl
  dev_ioctl(cmd, argp);
  //检测设备驱动如果不存在,则进行加载
  dev_load(ifr.ifr_name);
  
  dev_ifsioc(&ifr, cmd);
  //获取要处理的设备
  dev = __dev_get_by_name(ifr->ifr_name);
  
  //保留设备原标记,同时限制只能设置几种标记情况
  dev_change_flags(dev, ifr->ifr_flags);
  dev->flags = (flags & (IFF_DEBUG | IFF_NOTRAILERS 
  | IFF_NOARP |IFF_DYNAMIC | IFF_MULTICAST | 
  IFF_PORTSEL |IFF_AUTOMEDIA)) |
  (dev->flags & (IFF_UP | IFF_VOLATILE | 
  IFF_PROMISC |IFF_ALLMULTI));

  //重新加载多播地址,虚拟网桥设备的多播设置回调是空的
  dev_mc_upload(dev);
  
  //当接口变成UP,调用dev_open
  if ((old_flags ^ flags) & IFF_UP)
  dev_open(dev)
  //已经UP,直接返回
  if (dev->flags & IFF_UP)
  Return
  //设备还未呈现(比如节能模式)
  if (!netif_device_present(dev))
  return -ENODEV;
  
  //标记设备状态为__LINK_STATE_START
  set_bit(__LINK_STATE_START, &dev->state);
  
  //调用桥设备接口的open回调,这里桥设备的
  //回调函数为br_dev_open
  dev->open(dev);
  
  dev->flags |= IFF_UP;
  
  //重新加载多播地址,桥设备不需要
  dev_mc_upload(dev);
  
  //设备激活
  dev_activate(dev);
  if (dev->qdisc_sleeping == &noop_qdisc)
  //当前桥设备没有该值设置
  
  //当前桥设备没有state值,所有这里判断
  //就判断成有载波了
  if (!netif_carrier_ok(dev))
  return
  
  rcu_assign_pointer(dev->qdisc, 
  dev->qdisc_sleeping);
  
  dev->trans_start = jiffies;
  
  //如果桥设备的dev->tx_timeout为0,所以
  //这里看门狗程序未做任何实现。
  dev_watchdog_up(dev);
  
  //使用通知链发送NETDEV_UP通知
  raw_notifier_call_chain(&netdev_chain, 
  NETDEV_UP, dev);
  
  //如果当前接口为UP,当前标志改变不为(IFF_UP |
  // IFF_PROMISC | IFF_ALLMULTI |IFF_VOLATILE))这几
  //情况时发送设备改变的通知链
  if (dev->flags & IFF_UP &&
  ((old_flags ^ dev->flags) &~ (iff_up | iff_promisc | iff_allmulti 	|IFF_VOLATILE)))
  raw_notifier_call_chain(&netdev_chain,
  NETDEV_CHANGE, dev);

  //如果混杂模式有改变,则进行混杂模式处理
  if ((flags ^ dev->gflags) & IFF_PROMISC)
  int inc = (flags & IFF_PROMISC) ? +1 : -1;
  dev->gflags ^= IFF_PROMISC;
  dev_set_promiscuity(dev, inc);
  
  //如果多播标记改变,则进行多播设置
  if ((flags ^ dev->gflags) & IFF_ALLMULTI)
  int inc = (flags & IFF_ALLMULTI) ? +1 : -1;
  dev->gflags ^= IFF_ALLMULTI;
  dev_set_allmulti(dev, inc);
  
  //有标记改变,向用户层的netlink接口发送消息
  if (old_flags ^ dev->flags)
  rtmsg_ifinfo(RTM_NEWLINK, dev, 
  old_flags ^ dev->flags);
---------------------------------------------------------------------------------------------------------------------
//桥设备特有open回调
br_dev_open
  //桥设备特性重新校正,在上面已经分析过,这里不再进行分析
  br_features_recompute(br);
  
  //清除发送关闭标记
  netif_start_queue(dev);
  clear_bit(__LINK_STATE_XOFF, &dev->state);
  
  //开启桥
  br_stp_enable_bridge(br);
  //修改hello定时器,垃圾回收定时器
  mod_timer(&br->hello_timer, jiffies + br->hello_time);
  mod_timer(&br->gc_timer, jiffies + HZ/10);
  
  //发送配置BPDU
  br_config_bpdu_generation(br);
  list_for_each_entry(p, &br->port_list, list)
  //这里由于桥端口还是关闭状态,所以不会触发
  if (p->state != BR_STATE_DISABLED &&br_is_designated_port(p))
  br_transmit_config(p);
  
  //遍历所有桥端口绑定的设备,如果物理设备为UP,并且有载波,则开启桥端口
  list_for_each_entry(p, &br->port_list, list)
  if ((p->dev->flags & IFF_UP) && netif_carrier_ok(p->dev))
  //开启桥端口
  br_stp_enable_port(p);
  //初始化桥端口
  br_init_port(p);
  //根据优先级和端口号构建桥端口ID
  p->port_id = br_make_port_id(p->priority, p->port_no);
  
  //设置桥端口的指定根桥、路径开销、指定桥和指定端口
  br_become_designated_port(p);
  p->designated_root = br->designated_root;
  p->designated_cost = br->root_path_cost;
  p->designated_bridge = br->bridge_id;
  p->designated_port = p->port_id;
  
  //桥端口状态迁为BLOCKING
  p->state = BR_STATE_BLOCKING;
  
  p->topology_change_ack = 0;
  p->config_pending = 0;	
  //发送netlink通知,告诉有新链接
  br_ifinfo_notify(RTM_NEWLINK, p);
  
  //桥端口状态更新
  br_port_state_selection(p->br);
  list_for_each_entry(p, &br->port_list, list)
  //当前桥端口已经为BLOCKING
  if (p->state != BR_STATE_DISABLED)
  if (p->port_no == br->root_port)
  //当前root_port为0,表示没有根端口,则条件不
  //满足
  //当前指定桥和指定端口都匹配,所以该端口为指定端
  //口类型
  else if (br_is_designated_port(p))
  //删除消息老化定时器
  del_timer(&p->message_age_timer);
  
  //状态改化
  br_make_forwarding(p);
  if (p->state == BR_STATE_BLOCKING)
  //当前STP没有开启,所以状态迁为
  //BR_STATE_LEARNING
  //这里先迁到学习状态是为了防止开始
  //因为转化数据库中没有MAC记录,如
  //果桥进行处理会有大概扩散包,所以延
  //时一下,先学习一些信息
  if (p->br->stp_enabled)
  p->state = BR_STATE_LISTENING;
  Else
  p->state = BR_STATE_LEARNING;
  
  //修改端口状态改化定时器
  //这里转发延时器的超时处理函数为
  //br_forward_delay_timer_expired
  //是在新添加设备到桥时创建的
  mod_timer(&p->forward_delay_timer, 
  jiffies + p->br->forward_delay);
--------------------------------------------------------------------------------------------------------------------
//转发延时超时定时器
br_forward_delay_timer_expired
  if (p->state == BR_STATE_LEARNING)
  p->state = BR_STATE_FORWARDING;	//迁为转发状态
  
  //如果当前桥中含有指定端口
  if (br_is_designated_for_some_port(br))
  //通知逻辑拓扑改变检测到
  br_topology_change_detection(br);
  //当前为根桥
  isroot = br_is_root_bridge(br);
  
  if (isroot)
  //标记逻辑拓扑改变,并修改拓扑改变定时器
  br->topology_change = 1;
  mod_timer(&br->topology_change_timer, jiffies+ 
  br->bridge_forward_delay + br->bridge_max_age);
  
  //标记检测到拓扑改变
  br->topology_change_detected = 1;

五、处理收包
在《net_dev_init及网卡驱动收包》中已经分析,当物理网卡因中断触发了接收报文的执行后,在netif_receive_skb函数中有调用handle_bridge进行桥处理,这里如果handle_bridge返回true,则不再将报文传到上层协议处理。

handle_bridge
  //如果发前设备为环回设备,或者当前物理设备没有加入到桥中,则返回0,继续将报
  //文传送到上传协议处理
  if ((*pskb)->pkt_type == PACKET_LOOPBACK ||
  (port = rcu_dereference((*pskb)->dev->br_port)) == NULL)
  return 0;

  //先前type_all类型的包,最后一个还未处理,在这里执行,type_all类型的包主要用于
  //一些类似tcpdump之类的监听软件
  if (*pt_prev)
  deliver_skb(*pskb, *pt_prev, orig_dev);
  
  //桥处理的主要入口函数,该函数指针是在桥模块初始化时注册的,其回调函数为
  //br_handle_frame
  return br_handle_frame_hook(port, pskb);
---------------------------------------------------------------------------------------------------------------------
br_handle_frame
  //检测有校的源MAC,如果源MAC为全0、或组播、广播类型,则为非法源MAC
  //将该包丢弃。
  if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
  goto err;
  
  //检测目地MAC地址是否为01:80:c2:00:00:0X,该地址是特殊的组播地址,主要用于
  //接收桥使用的STP(生成树协议)报文。
  if (unlikely(is_link_local(dest)))
  skb->pkt_type = PACKET_HOST;		//标记是发给本机的
  
  //进入ebtables的LOCAL_IN链去处理
  return NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
  NULL, br_handle_local_finish) != 0;
  //一系列宏处理,这里先使用nf_hook_thresh进行ebtables链处理,当
  //nf_hook_thresh返回为1时,再调用br_handle_local_finish处理,如果
  //返回值为非1,则可能会ebtables规则有丢弃报文等类似情况,不再让
  //报文继续前行,所以不执行br_handle_local_finish。
  #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
  NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)
  #define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh)	 \
  ({int __ret;
  if ((__ret=nf_hook_thresh(pf, hook, &(skb), indev, outdev, okfn, thresh, 1)) 
  == 1)
  __ret = (okfn)(skb);
  __ret;})

  //桥端口状态仅仅为转发态或学习态才有效,其它状态都将接收的包丢弃。
  if (p->state == BR_STATE_FORWARDING || p->state == BR_STATE_LEARNING)
  //这个回调函数是ebtables中的broute扩展模块设置的,主要是在ebtables中增加
  //brouting链,该链主要用于控制包是进行桥处理,还是交给上层协议处理。
  //当前回调函数为ebt_broute
  if (br_should_route_hook)
  Result = ebt_broute(pskb)	//br_should_route_hook = ebt_broute
  //执行ebtables查表处理
  ret = ebt_do_table(NF_BR_BROUTING, pskb, (*pskb)->dev, 
  NULL,&broute_table);
  if (ret == NF_DROP)	
  //broute表比较特殊,当返回DROP时表示将包交给上层协议处理,走
  //路由模式,ACCESS表示走桥模式。
  return 1;
  return 0;

  //如果上面broute的查表处理结果为DROP,则表示将包交给上层协议处理
  //所以直接返回0,表示没有进行桥相关处理,由netif_receive_skb继续将包
  //传给上层协议来处理。
  If( result == 1 )
  return 0;

  skb = *pskb;
  dest = eth_hdr(skb)->h_dest;
  
  //如果目标MAC与虚拟桥设备的MAC相同,则将包标记为本地包类型
  if (!compare_ether_addr(p->br->dev->dev_addr, dest))
  skb->pkt_type = PACKET_HOST;
  
  //进入NF_BR_PRE_ROUTING链处理,如果被接受,则继续执行
  //br_handle_frame_finish
  NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
  br_handle_frame_finish);
  
  return 1;
---------------------------------------------------------------------------------------------------------------------
//ebtables钩子处理入口
nf_hook_thresh
  //控制不需要钩子处理
  if (!cond)
  return 1;

  return nf_hook_slow(pf, hook, pskb, indev, outdev, okfn, thresh);
  //pf为处理的协议族类型,当前桥处理通常为PF_BRIDGE
  //hook为当前要处理的ebtables链
  elem = &nf_hooks[pf][hook];
  
  next_hook:
  
  //遍历当前nf_hooks中注册的钩子函数进行处理,这里处理优先级在上面设置为
  //INT_MIN,表示从该钩子链子中第一个元素开始进行处理。在当前链表处理中
  //如果处理ebtables规则返回NF_ACCEPT,则不再进行当前链的其它钩子处理,
  //直接返回NF_ACCEPT结果,否则返回非NF_REPEAT结果,也直接将对应的结
  //果返回,当返回NF_REPEAT结果时,则处理处理当前链表中下一个优先级钩子
  //函数。
  verdict = nf_iterate(&nf_hooks[pf][hook], pskb, hook, indev,
  outdev, &elem, okfn, hook_thresh);
  
  //如果处理ebtables结果为ACCEPT或NF_STOP,返回1,表示允许包继续前行
  if (verdict == NF_ACCEPT || verdict == NF_STOP)
  ret = 1;
  
  //如果结果为DROP,则丢弃该包
  else if (verdict == NF_DROP)
  kfree_skb(*pskb);
  ret = -EPERM;

  //如果结果QUEUE,则表明用户希望自己处理该包,不虽然上层协议处理。
  //这里调用nf_queue进行用户队列处理,同时检测一下当前用户是否为队列
  //处理设置了回调函数等,如果检测错误,则表明该规则项是无效的,继续
  //使用当前链表的下一个优先级钩子进行处理。
  else if ((verdict & NF_VERDICT_MASK)  == NF_QUEUE)
  if (!nf_queue(*pskb, elem, pf, hook, indev, outdev, okfn,
  verdict >> NF_VERDICT_BITS))
  goto next_hook;
--------------------------------------------------------------------------------------------------------------------
//ebtables LOCAL链规则处理完成,允许继续处理
br_handle_local_finish
  if (p && p->state != BR_STATE_DISABLED)
  //更新转发数据库
  br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
  //根据源MAC在桥的hash表中查找头链
  head = &br->hash[br_mac_hash(addr)];
  
  //检测老化时间如果为0,表明用户不想向转发库中添加信息,用户就想让
  //桥中的每个处理包发散到桥中每个端口。
  if (hold_time(br) == 0)
  return;
  
  //从头链中查找是否已经记录此地址
  fdb = fdb_find(head, addr);
  
  //如果在HASH表中存在
  if (likely(fdb))
  if (unlikely(fdb->is_local)) 
  //本地地址,则仅提示打印信息
  Else
  //地址和老化时间重新更新
  fdb->dst = source;
  fdb->ageing_timer = jiffies;
  //还未在HASH表中出现
  Else
  //加入到HASH链中,is_local=0、is_static=0	
  fdb_create(head, source, addr, 0);
  
  //返回0,表示发送到目的MAC为01:80:c2:00:00:0X的特殊组播需要交给上层协议去
  //处理,这里就是指将桥使用的STP(生成树协议)交给上传802.2协议层中的
  //LLC SAP包类型处理。
  return 0;
  
---------------------------------------------------------------------------------------------------------------------
//ebtables PRE_ROUTING链规则处理完成,允许继续处理
br_handle_frame_finish
  //如果桥端口关闭,直接将报文丢弃
  if (!p || p->state == BR_STATE_DISABLED)
  goto drop;

  //更新转发表,上面已经分析过
  br_fdb_update(br, p, eth_hdr(skb)->h_source);
  
  //临时学习状态不收发任意数据报
  if (p->state == BR_STATE_LEARNING)
  goto drop;
  
  //如果桥设备为混杂模式
  if (br->dev->flags & IFF_PROMISC)
  //复制一份报文
  skb2 = skb_clone(skb, GFP_ATOMIC);
  
  passedup = 1;	//标记已经传给上层协议
  
  //将包传给上层协议处理
  br_pass_frame_up(br, skb2);
  br->statistics.rx_packets++;
  br->statistics.rx_bytes += skb->len;
  
  //此时skb中的dev换成桥设备,这样就确保该设备接口一定不会是绑定到某
  //个虚拟桥中
  indev = skb->dev;		//物理设备
  skb->dev = br->dev;	//桥设备
  
  //执行ebtables的LOCAL_IN链处理,如果允许包继续处理,则又一次执行
  //netif_receive_skb,注意此时在执行netif_receive_skb,因为上面skb->dev已
  //经被替换过,所以此时就不会再走桥处理流程,而是将报文传给上层协议
  NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,
  netif_receive_skb);

  //如果是多播地址类型
  if (is_multicast_ether_addr(dest))
  br->statistics.multicast++;
  
  //向桥中每个端口扩散发送
  br_flood_forward(br, skb, !passedup);
  //针对桥中每个端口使用__br_forward进行处理
  br_flood(br, skb, clone, __br_forward);
  ---------------------------------------------------------------------
  __br_forward
  indev = skb->dev;
  skb->dev = to->dev;
  //转发的目的设备接口,这里为桥端口对应的真实物理接口
  skb->ip_summed = CHECKSUM_NONE;	//没有进行校验合
  
  //执行ebtables的FORWARD链处理,如果允许包继续处理
  //br_forward_finish
  NF_HOOK(PF_BRIDGE, NF_BR_FORWARD, skb, indev, skb->dev,
  br_forward_finish);
  
  //之前没有向上层协议处理的话,则将该包交给上层协议处理
  if (!passedup)
  br_pass_frame_up(br, skb);

  goto out;
  
  //使用目的MAC在当前转发数据库中查找,如果找到,表示该报文是发给桥自身的,
  //如果之前没向上层协议传递的话,将报文交给上层协议处理。
  //这里需要注意一点,每当一个物理接口加入到桥设备时,都会将物理接口的MAC加
  //入到转发表中,这样就保证发向桥中绑定物理接口的报文,也可以使用三层路由走出。
  dst = __br_fdb_get(br, dest);
  if (dst != NULL && dst->is_local)
  if (!passedup)
  br_pass_frame_up(br, skb);
  goto out;

  //如果该报文非本地、非广播、组播,并且之前已经记录到转发数据库,则根据转发数
  //据库中的记录,将报文从指定物理端口中发出。
  if (dst != NULL)
  br_forward(dst->dst, skb);
  __br_forward(to, skb);	//上面已经分析过
  goto out;
  
  //非本地、非广播、组播,并且也没在转发数据库中找到,则向桥中每个端口扩散发送。
  br_flood_forward(br, skb, 0);
  
  out:
  return 0;

---------------------------------------------------------------------------------------------------------------------
//处理完转发链后继续处理
br_forward_finish
  //执行ebtables的POST_ROUTING链处理,如果允许包继续处理
  NF_HOOK(PF_BRIDGE, NF_BR_POST_ROUTING, skb, NULL, skb->dev,
  br_dev_queue_push_xmit);

---------------------------------------------------------------------------------------------------------------------
//处理完POST_ROUTING链后继续处理
br_dev_queue_push_xmit
  //如果包长度大于当前设备的MTU,并且当前没有使用GSO机制则丢弃该包
  if (packet_length(skb) > skb->dev->mtu && !skb_is_gso(skb))
  kfree_skb(skb);
  Else
  //检查是否需要拷贝其它头部数据
  result = nf_bridge_maybe_copy_header(skb)
  if (skb->nf_bridge)
  return nf_bridge_copy_header(skb);
  //当前报文为VLAN协议,则使用skb_cow检测skb中从head字段
  //到data字段的头部空间是否还可以填充VLAN头信息,如果不可以
  //则需要将skb重新分配。
  if (skb->protocol == htons(ETH_P_8021Q))
  header_size += VLAN_HLEN;
  err = skb_cow(skb, header_size);
  if (err)
  //如果重新分配产生错误,则返回错误
  return err;
  
  //将VLAN头复制到skb中,同时将skb的data指针移到当前VLAN
  //头部,以衣增加skb数据大小。
  //这里VLAN数据是在post routing链中设置的。
  memcpy(skb->data - header_size, skb->nf_bridge->data, header_size);
  if (skb->protocol == htons(ETH_P_8021Q))
  __skb_push(skb, VLAN_HLEN);
  return 0;
  
  //报文没有经过netfilter bridge相关模块处理,则没有nf_bridge信息
  Return 0
  
  If(result)
  //因重新分配空间产生错误,则直接将报文丢弃。
  kfree_skb(skb);
  Else
  //将data指向以太网二层头信息位置,调用公共用dev_queue_xmit接口将报
  //文发出。
  skb_push(skb, ETH_HLEN);
  dev_queue_xmit(skb);

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值