一、桥模块初始化
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(®->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);
网桥处理
最新推荐文章于 2022-08-24 16:24:19 发布