目录

1 网桥处理报文概述

2 网桥收包入口 br_handle_frame()

2.1 网桥收包决策 br_handle_frame_finish()

2.1.1 网桥本地数据上送协议栈处理 br_pass_frame_up()

2.1.2 网桥本地数据转发函数 br_forward()


1 网桥处理报文概述

网桥是一种2层网络互连设备,而不是一种网络协议。它在协议结构上并没有占有一席之地,因此不能通过向协议栈注册协议的方式来申请网桥数据包的处理。相反,网桥接口的数据包和一般接口(如eth0)在格式上完全是一样的,不同之处是网桥在2层上就对它进行了转了,而一般接口要在3层 才能根据路由信息来决定是否要转发,如何转发。

linux内核是通过一个虚拟的网桥设备来实现桥接的。这个虚拟设备可以绑定若干个以太网接口设备,从而将它们桥接起来。如下图(摘自ULNI):

网桥设备br0绑定了eth0eth1。对于网络协议栈的上层来说,只看得到br0,因为桥接是在数据链路层实现的,上层不需要关心桥接的细节。于是协议栈上层需要发送的报文被送到br0,网桥设备的处理代码再来判断报文该被转发到eth0或是eth1,或者两者皆是;反过来,从eth0或从eth1接收到的报文被提交给网桥的处理代码,在这里会判断报文该转发、丢弃、或提交到协议栈上层

linux内核支持网口的桥接(目前只支持以太网接口)。但是与单纯的交换机不同,交换机只是一个二层设备,对于接收到的报文,要么转发、要么丢弃。小型的交换机里面只需要一块交换芯片即可,并不需要CPU。而运行着linux内核的机器本身就是一台主机,有可能就是网络报文的目的地。其收到的报文除了转发和丢弃,还可能被送到网络协议栈的上层(网络层),从而被自己消化。

2 网桥收包入口 br_handle_frame()

br_handle_frame是处理网桥入口函数,主要逻辑如下:

  1. 判断skb为环回数据包
  2. 判断mac地址是否有效(既不是组播 mac 地址也不是全0 mac 地址)
  3. 判断数据是否是共享并复制一份
  4. 获取net_bridge_port网桥接口,参见《linux 网桥代码分析之网桥及网桥端口的添加与删除Ⅲ》
  5. 判断目的mac地址是否是01:80:c2:00:00:0X类型,若是,则继续判断是0x8808协议,若是0x8808,个人理解则可能是mpcp相关的数据包,而mpcp是epon相关的协议,而mpcp协议中相关的消息和epon mac的硬件息息相关,linux内核对这类数据包就没有提供相关的公共函数了。对于其他类型的数据包,则调用函数br_handle_local_finish进行后续处理,而 br_handle_local_finish 也仅仅是调用br_fdb_update,更新fdb数据库
  6. 对于网桥端口是forward和learning状态的,则调用防火墙处理函数处理NF_BR_PRE_ROUTING的ebtables相关的规则
  7. 当通过NF_BR_PRE_ROUTING相关的 ebtables 规则后,则会调用函数br_handle_frame_finish 继续进行数据处理
rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
{struct net_bridge_port *p;struct sk_buff *skb = *pskb;//获取数据包的目的mac地址const unsigned char *dest = eth_hdr(skb)->h_dest;//开启ebtables时br_should_route_hook_t *rhook;//如果是本地环回包则跳过桥处理,直接返回if (unlikely(skb->pkt_type == PACKET_LOOPBACK))return RX_HANDLER_PASS;//源mac地址无效则丢弃if (!is_valid_ether_addr(eth_hdr(skb)->h_source))goto drop;//判断skb是否共享(skb->users!=1 ?),如果是共享则克隆一份,并将原skb的引用计数-1(skb->users-1)//如果不克隆则会影响共享此skb的其他函数,如果此skb为不共享,则直接返回此skbskb = skb_share_check(skb, GFP_ATOMIC);if (!skb)return RX_HANDLER_CONSUMED;//获取net_bridge_port网桥接口(dev->rx_handler_data),在br_add_if中赋值的,网桥处理函数的注册p = br_port_get_rcu(skb->dev);if (p->flags & BR_VLAN_TUNNEL) {if (br_handle_ingress_vlan_tunnel(skb, p,nbp_vlan_group_rcu(p)))goto drop;}//如果目的mac地址是本地链路地址link local reserved addr (01:80:c2:00:00:0X) STP报文if (unlikely(is_link_local_ether_addr(dest))) {u16 fwd_mask = p->br->group_fwd_mask_required;/** See IEEE 802.1D Table 7-10 Reserved addresses** Assignment                Value* Bridge Group Address     01-80-C2-00-00-00* (MAC Control) 802.3      01-80-C2-00-00-01* (Link Aggregation) 802.3 01-80-C2-00-00-02* 802.1X PAE address       01-80-C2-00-00-03** 802.1AB LLDP        01-80-C2-00-00-0E** Others reserved for future standardization*/fwd_mask |= p->group_fwd_mask;switch (dest[5]) {case 0x00:  /* Bridge Group Address *//* If STP is turned off,then must forward to keep loop detection */if (p->br->stp_enabled == BR_NO_STP ||fwd_mask & (1u << dest[5]))goto forward;*pskb = skb;__br_handle_local_finish(skb);return RX_HANDLER_PASS;case 0x01:   /* IEEE MAC (Pause) */goto drop;case 0x0E:  /* 802.1AB LLDP */fwd_mask |= p->br->group_fwd_mask;if (fwd_mask & (1u << dest[5]))goto forward;*pskb = skb;__br_handle_local_finish(skb);return RX_HANDLER_PASS;default:/* Allow selective forwarding for most other protocols */fwd_mask |= p->br->group_fwd_mask;if (fwd_mask & (1u << dest[5]))goto forward;}/* Deliver packet to local host only */NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, dev_net(skb->dev),NULL, skb, skb->dev, NULL, br_handle_local_finish);return RX_HANDLER_CONSUMED;}//p->flags = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD
forward://STP的五种状态switch (p->state) {//网桥端口处于转发状态case BR_STATE_FORWARDING://ebtables获取路由的hook点rhook = rcu_dereference(br_should_route_hook);if (rhook) {//转发数据包,然后返回if ((*rhook)(skb)) {*pskb = skb;return RX_HANDLER_PASS;}dest = eth_hdr(skb)->h_dest;}/* fall through *///网桥端口处于学习状态,处于BR_STATE_FORWARDING 状态也会执行下面的代码,因为上面的case没有breakcase BR_STATE_LEARNING://数据包目的mac地址等于网桥的mac地址,属于发往本地的数据包if (ether_addr_equal(p->br->dev->dev_addr, dest))skb->pkt_type = PACKET_HOST;//进入NF_BR_PRE_ROUTING钩子点,最后调用br_handle_frame_finish函数NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING,dev_net(skb->dev), NULL, skb, skb->dev, NULL,br_handle_frame_finish);break;default:
drop:kfree_skb(skb);}//最后返回RX_HANDLER_CONSUMED 表明我们在桥上已经处理了此数据包,原__netif_receive_skb_core应当直接返回return RX_HANDLER_CONSUMED;
}

2.1 网桥收包决策 br_handle_frame_finish()

br_handle_frame_finish()函数主要是决策将不同类别的数据包做不同的分发路径,它会决定数据包是转发还是交给上层协议栈去处理,主要逻辑如下:

1. 首先判断接收到数据包的设备对应的网桥端口的状态是否为 disable
2. 调用 br_fdb_update,更新fdb数据库,为数据包源mac地址与源网桥端口添加 fdb entry
3. 如果源网桥端口的状态为 learning,则不处理该数据包
4. a) 如果网桥设备处于混杂模式或者数据包的目的mac地址为组播地址,则需要将该 skb 的一个拷贝,发送给上层协议栈(通过调用 br_pass_frame_up 实现)
    b) 如果数据包的目的 mac 地址为本地 mac,则只只需要将该数据包发送给上次协议栈,而不需转发数据包。
5.调用 __br_fdb_get 查找符合条件的fdb entry
    a)若查找到了了符合条件的 fdb entry
       i)若该fdb entry为local类型的,说明该数据包是发往本地的,则将skb赋值给skb2,然后skb指向NULL,不对该数据包进行转发。
       ii)若该fdb entry不是local类型的,则调用br_forward,将数据包从指定端口转发出去。
    b)若没有查找到指定的端口,则调用br_flood_forward,将数据从其他所有网桥端口发送出去

int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{//获取网桥端口 dev->rx_handler_datastruct net_bridge_port *p = br_port_get_rcu(skb->dev);enum br_pkt_type pkt_type = BR_PKT_UNICAST;struct net_bridge_fdb_entry *dst = NULL;struct net_bridge_mdb_entry *mdst;bool local_rcv, mcast_hit = false;const unsigned char *dest;struct net_bridge *br;u16 vid = 0;//如果网桥端口不存在或者网桥端口状态为BR_STATE_DISABLED,则丢弃if (!p || p->state == BR_STATE_DISABLED)goto drop;//判断是否允许进入桥内,如果没有开启vlan则所有的数据包都可以进入, //如果开启了vlan则根据vlan相应的规则,从桥上进行数据包转发if (!br_allowed_ingress(p->br, nbp_vlan_group_rcu(p), skb, &vid))goto out;//BR_INPUT_SKB_CB(skb)->offload_fwd_mark = p->offload_fwd_marknbp_switchdev_frame_mark(p, skb);/* insert into forwarding database after filtering to avoid spoofing *///获取网桥,下面会将网桥的device放入skb的私有数据中br = p->br;//如果网桥端口标志有BR_LEARNING,则更新fdb表//一般新建网桥端口p->flags = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOODif (p->flags & BR_LEARNING)br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, false);//发往本地数据包标记,!!的作用是转换为bool值local_rcv = !!(br->dev->flags & IFF_PROMISC);dest = eth_hdr(skb)->h_dest;if (is_multicast_ether_addr(dest)) {/* by definition the broadcast is also a multicast address *///若目的mac地址为广播包 (FF:FF:FF:FF:FF:FF),会发往本地一份if (is_broadcast_ether_addr(dest)) {pkt_type = BR_PKT_BROADCAST;local_rcv = true;} else {//若为组播包pkt_type = BR_PKT_MULTICAST;//igmp snooping留给网桥子系统的外部接口函数,当网桥接收了igmp数据包后就会调用该函数进行后续处理if (br_multicast_rcv(br, p, skb, vid))goto drop;}}//如果网桥端口状态此时还是BR_STATE_LEARNING,则丢弃if (p->state == BR_STATE_LEARNING)goto drop;//将网桥所属的net_device放入skb的私有数据中(struct br_input_skb_cb)BR_INPUT_SKB_CB(skb)->brdev = br->dev;BR_INPUT_SKB_CB(skb)->src_port_isolated = !!(p->flags & BR_ISOLATED);//进行与arp协议相关的处理 ?TODOif (IS_ENABLED(CONFIG_INET) &&(skb->protocol == htons(ETH_P_ARP) ||skb->protocol == htons(ETH_P_RARP))) {br_do_proxy_suppress_arp(skb, br, vid, p);} else if (IS_ENABLED(CONFIG_IPV6) &&skb->protocol == htons(ETH_P_IPV6) &&br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED) &&pskb_may_pull(skb, sizeof(struct ipv6hdr) +sizeof(struct nd_msg)) &&ipv6_hdr(skb)->nexthdr == IPPROTO_ICMPV6) {struct nd_msg *msg, _msg;msg = br_is_nd_neigh_msg(skb, &_msg);if (msg)br_do_suppress_nd(skb, br, vid, p, msg);}switch (pkt_type) {//若为组播包case BR_PKT_MULTICAST://获取组播转发项,设置local_rcv为true,组播包也要发往本地一份mdst = br_mdb_get(br, skb, vid);if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&br_multicast_querier_exists(br, eth_hdr(skb))) {if ((mdst && mdst->host_joined) ||br_multicast_is_router(br)) {local_rcv = true;br->dev->stats.multicast++;}//可以获取到数据包对应的组播转发信息mcast_hit = true;} else {local_rcv = true;br->dev->stats.multicast++;}break;//既不是广播包,也不是组播包,则是单播包case BR_PKT_UNICAST://根据目的mac地址查找fdb表,看是否有对应的表项dst = br_fdb_find_rcu(br, dest, vid);default:break;}//如果找到目的mac对应转发表项if (dst) {unsigned long now = jiffies;//dst->is_local为真,送入上层处理if (dst->is_local)return br_pass_frame_up(skb);if (now != dst->used)dst->used = now;//根据fdb转发表项进行转发,若这里local_rcv 为1,(即端口处于混杂模式IFF_PROMISC),则会克隆一份再转发//传入的第一个参数dst->dst 即为要转发的目的端口br_forward(dst->dst, skb, local_rcv, false);} else {  //如果没有找到目的mac对应转发表项//进行广播或者组播洪泛if (!mcast_hit)br_flood(br, skb, pkt_type, local_rcv, false);elsebr_multicast_flood(mdst, skb, local_rcv, false);}//local_rcv标记为1,送入上层处理if (local_rcv)return br_pass_frame_up(skb);out:return 0;
drop:kfree_skb(skb);goto out;
}

网桥设备是否处于混杂模式,如果是,则会发一份到本地进行处理
如果是广播包,则会进行广播洪泛,并会发一份到本地处理
如果是组播包,则根据组播表进行组播转发,并发一份数数包到本地处理
如果是单播包,发往本地的单播包则送到本地处理,在fdb表中可以找到转发表项的单播包则进行转发,未知单播包在广播域内进行洪泛

因此,在调用 br_handle_frame_finish() 的处理后,数据包有二个走向:

  1. 进行转发,已知单播(非本地)根据目的mac地址进行转发,未知单播根据转发表(广播表 or 组播表)进行端口洪泛。
  2. 发往本地进行上层处理。

2.1.1 网桥本地数据上送协议栈处理 br_pass_frame_up()

处理本地数据包的情况,即数据包目的mac地址是本地的单播数据、广播、组播和网桥处于混杂模式时都需要交给上层处理,在处理完NF_BR_PRE_ROUTING链后会调用br_pass_frame_up进入上层处理。主要逻辑是用来

static int br_pass_frame_up(struct sk_buff *skb)
{struct net_device *indev, *brdev = BR_INPUT_SKB_CB(skb)->brdev;struct net_bridge *br = netdev_priv(brdev);struct net_bridge_vlan_group *vg;struct pcpu_sw_netstats *brstats = this_cpu_ptr(br->stats);//统计网桥设备上的收包流量数据u64_stats_update_begin(&brstats->syncp);brstats->rx_packets++;brstats->rx_bytes += skb->len;u64_stats_update_end(&brstats->syncp);//获取网桥设备上的vlan组vg = br_vlan_group_rcu(br);/* Bridge is just like any other port.  Make sure the* packet is allowed except in promisc modue when someone* may be running packet capture.*/if (!(brdev->flags & IFF_PROMISC) &&!br_allowed_egress(vg, skb)) {kfree_skb(skb);return NET_RX_DROP;}//记录数据包的收包网络设备indev = skb->dev;//将数据包的收包设备改为网桥设备//当再次进入__netif_receive_skb_core时就不会再次进入桥处理了,因为网桥上没有注册rx_handler 函数skb->dev = brdev;//配置数据包vlan相关信息skb = br_handle_vlan(br, NULL, vg, skb);if (!skb)return NET_RX_DROP;/* update the multicast stats if the packet is IGMP/MLD *///如果数据包是组播,更新组播数据包的统计信息br_multicast_count(br, NULL, skb, br_multicast_igmp_type(skb),BR_MCAST_DIR_TX);//进入NF_BR_LOCAL_IN 钩子点进行处理,最后调用br_netif_receive_skb 函数return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN,dev_net(indev), NULL, skb, indev, NULL,br_netif_receive_skb);
}

调用 netif_receive_skb,但此时 skb->dev = brdev; 已经替换为网桥设备,网桥上没有注册 rx_handler,因此调用 __netif_receive_skb_core 不会再次进入桥处理,然后会调用 ptype 协议链上对应的协议处理函数进入上层处理。代码如下:

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{struct packet_type *ptype, *pt_prev;rx_handler_func_t *rx_handler;struct net_device *orig_dev;struct net_device *null_or_dev;bool deliver_exact = false;int ret = NET_RX_DROP;__be16 type;...//网桥设备 rx_handler = NULL,网桥下的端口注册 rx_handler = br_handle_framerx_handler = rcu_dereference(skb->dev->rx_handler);if (rx_handler) {if (pt_prev) {ret = deliver_skb(skb, pt_prev, orig_dev);pt_prev = NULL;}switch (rx_handler(&skb)) {case RX_HANDLER_CONSUMED:ret = NET_RX_SUCCESS;goto unlock;case RX_HANDLER_ANOTHER:goto another_round;case RX_HANDLER_EXACT:deliver_exact = true;case RX_HANDLER_PASS:break;default:BUG();}}...type = skb->protocol;list_for_each_entry_rcu(ptype,&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {if (ptype->type == type &&(ptype->dev == null_or_dev || ptype->dev == skb->dev ||ptype->dev == orig_dev)) {if (pt_prev)ret = deliver_skb(skb, pt_prev, orig_dev);pt_prev = ptype;}}if (pt_prev) {if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))goto drop;elseret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);} else {...}
}

2.1.2 网桥本地数据转发函数 br_forward()

参加《linux 网桥代码分析之网桥数据转发函数分析Ⅵ》

linux 网桥代码分析之网桥端口设备接收数据包处理分析Ⅴ相关推荐

  1. linux收发包内核进程名称,Linux内核IP Queue机制的分析(一)——用户态接收数据包...

    序 笔者将会通过包括本文在内的三篇文章,对IP Queue机制从用户态的应用到内核态的模块程序设计进行分析.三篇文章的题目分别是: Linux内核IP Queue机制的分析(一)­--用户态接收数据包 ...

  2. 网络编程(wireshare抓数据包及分析、三次握手与四次挥手、数据库sqlite3及操作)笔记-day15

    前言 今天整理了网络编程的下篇,主要归纳了wireshark抓数据包及分析.TCP安全可靠原因分析(三次握手.四次挥手).数据库sqlite3及操作(shell脚本和C语言对数据库的增.删.改.查及关 ...

  3. Java抓包分析四(基于jnetpcap进行抓包)——分析Http请求数据包

    在上篇文章中Java抓包分析三(基于jnetpcap进行抓包)--抓取Http请求数据包,我们讲解了TCP三次握手的过程和如何抓取Http数据包,但是我们并没有进行一个数据分析,接下来这篇文章我们将要 ...

  4. 【Java 网络编程】UDP 服务器 客户端 通信 ( DatagramSocket | DatagramPacket | UDP 发送数据包 | UDP 接收数据包 | 端口号分配使用机制 )

    文章目录 I UDP 信息发送接收原理 II UDP 发送和接收端口相同 III UDP 发送信息代码示例 IV UDP 接收信息代码示例 V UDP 服务器端代码示例 VI UDP 客户端代码示例 ...

  5. bugku数据包流量分析题目总结

    因为内容有点多,文章还没写完,请谅解,后期会将内容更新补全 文章目录 1.flag被盗 2.中国菜刀 3.这么多数据包 4.手机热点 1.flag被盗 ​ 提示:flag被盗,赶紧溯源! ​ 下载数据 ...

  6. 数据包构造分析工具Hping3常用命令集合大学霸IT达人

    数据包构造分析工具Hping3常用命令集合大学霸IT达人 Hping是一个命令行下使用的TCPIP数据包组装分析工具.该工具的命令模式很像Unix下的ping命令.它不止能发送ICMP回应请求,还支持 ...

  7. wireshark筛选dhcp包_使用wireshark抓包工具,对DHCP、HTTP、DNS的数据包进行分析

    使用wireshark抓包工具,对DHCP.HTTP.DNS的数据包进行分析 本文标签: 服务器安全 服务器被攻击 网站防护 使用wireshark抓包工具,对DHCP.HTTP.DNS的数据包进行分 ...

  8. <整理总结>H264/265码流数据包格式分析(带mp4v2封装H264/265为MP4的源码示例)

    H264/265码流数据包格式分析 前言: 一.H.264码流解析 I帧P帧B帧说明: 二.H.265码流解析 三.主要源码 前言: 最近在学习使用MP4v2将H264/H265码流以及AAC音频封装 ...

  9. 使用Allegro进行数据包取证分析

    文章目录 简介 便携式取证分析解决方案 内联旁路+带外监控解决方案 数据中心流量聚合 设备方案优势 关注我们 简介 数据包是网络中传输的最小数据单位.网络管理员需要这个粒度单元来收集全面的网络分析,但 ...

  10. 计算机网络ip数据包分析题,计算机网络课程设计-IP数据包解析实验报告

    计算机网络课程设计-IP数据包解析实验报告 解析 IP数据报实验报告目录目录 21.课程设计目的 .22.课程设计要求 .23.相关知识 .24.课程设计分析 .64.1 网卡设置64.2 使用套接字 ...

最新文章

  1. Linux系统下基本命令
  2. 透过源码看Session
  3. C指针原理(34)-Ncurses-文本终端的图形
  4. 设计模式:讲在设计模式之前
  5. Struts2中的ActionContext
  6. 【hihocoder - offer编程练习赛60 C】路径包含问题(LCA,树上倍增)
  7. 发布:偶写的NHibernate代码生成器
  8. 数据挖掘_wget整站下载
  9. C# 无边框异型窗体制作
  10. Windows平台内核级文件访问
  11. 怎么避免后台被搜索_优化亚马逊后台关键词的6个技巧,让买家快速找到你
  12. VSCode中插件Code Spell Checker
  13. @keyframes详解
  14. 微型计算机主频一般为,【单选题】目前使用的微型计算机的主频一般为________。 A. 2.6GHz B. 256MHz C. 2.3THz D. 900Hz...
  15. Redis03-优惠券秒杀
  16. Matlab求分段函数的积分
  17. 动漫培训网课学费需要多少钱
  18. C++习题:6-1 CCircle And CCylinder
  19. Android AudioFocus机制小结
  20. 想哭!我相信“官方”消息,炒币仍然被骗

热门文章

  1. 通读SLA文档之后的感受
  2. linux系统能看抖音吗,在Linux系统下用XDroid来安装和运行抖音Android APP应用
  3. 关于SM2加密验签的操作
  4. python语言是不是多模型语言_Python模型转换为Modelica模型的方法与流程
  5. 从转行到入行(27岁跨行+跨专业+培训机构)
  6. 为什么大龄程序员不选择自己创业?
  7. 第一篇博客--随便聊聊
  8. Java 在Word中创建表格
  9. 怎么在服务器上显示u盘启动,电脑服务器怎么设置U盘启动
  10. 深度学习视觉领域常用数据集汇总