内核协议栈源码阅读(三) --- 网桥处理
文章目录
-
- 一、网桥处理概述
- 二、本地多播
- 三、ebtable 的实现原理以及 ebt_broute
-
- 3.1 ebtable 的实现
- 3.2 ebt_broute
- 3.3 ebt_do_table
- 四、br_handle_frame_finish
-
- 4.1 br_fdb_update
- 4.2 br_pass_frame_up
- 4.3 多播 br_flood_forward
- 4.4 非多播 __br_fdb_get + br_forward
- 4.5 最终转发函数 __br_forward
- 总结
- 附录
从前面我们知道,软中断收包后会送入 netif_receive_skb
函数处理,该函数主要处理 netpoll
、网桥
,前两步没有将报文处理掉最后才是寻找匹配的协议进行报文处理。
前面简单介绍了 netpoll
的逻辑,现在我们介绍网桥的处理。内核代码版本 linux 2.6
一、网桥处理概述
如果开启了网桥模块,则 handle_bridge
不是个空函数。
该函数:
static __inline__ int handle_bridge(struct sk_buff **pskb,struct packet_type **pt_prev, int *ret,struct net_device *orig_dev)
{struct net_bridge_port *port;if ((*pskb)->pkt_type == PACKET_LOOPBACK ||(port = rcu_dereference((*pskb)->dev->br_port)) == NULL)return 0;if (*pt_prev) {*ret = deliver_skb(*pskb, *pt_prev, orig_dev);*pt_prev = NULL;} return br_handle_frame_hook(port, pskb);
}
- 包如果是回环包或者接收包的设备没有挂载到网桥上,则返回 0 进行后续的协议栈处理
- 如果包非回环包且接收设备挂在网桥上,那么进行网桥处理,执行
br_handle_frame_hook
br_handle_frame_hook
在br_init
初始化网桥模块时设置为br_handle_frame
函数
接下来看 br_handle_frame
函数:
int br_handle_frame(struct net_bridge_port *p, struct sk_buff **pskb)
{struct sk_buff *skb = *pskb;const unsigned char *dest = eth_hdr(skb)->h_dest;if (!is_valid_ether_addr(eth_hdr(skb)->h_source))goto err;if (unlikely(is_link_local(dest))) {skb->pkt_type = PACKET_HOST;return NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,NULL, br_handle_local_finish) != 0;}if (p->state == BR_STATE_FORWARDING || p->state == BR_STATE_LEARNING) {if (br_should_route_hook) {if (br_should_route_hook(pskb)) return 0;skb = *pskb;dest = eth_hdr(skb)->h_dest;}if (!compare_ether_addr(p->br->dev->dev_addr, dest))skb->pkt_type = PACKET_HOST;NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,br_handle_frame_finish);return 1;}err:kfree_skb(skb);return 1;
}
- 判断
源mac
地址是否非法(is_valid_ether_addr
需要mac地址非多播地址且非0) - 判断目的地址是否是局域网多播,如果是的话扔到
BRIDGE
协议的LOCAL_IN
过滤链,通过过滤链后执行br_handle_local_finish
- NF 的组成为一个
二维数组
,数组元素为一个链表
;按找[pf][hook]
组织,其中PF_BRIDGE
为pf
,NF_BR_LOCAL_IN
为hook
名字。除此之外还有以下几种hook
:
#define NF_BR_PRE_ROUTING 0 /* If the packet is destined for this box. */ #define NF_BR_LOCAL_IN 1 /* If the packet is destined for another interface. */ #define NF_BR_FORWARD 2 /* Packets coming from a local process. */ #define NF_BR_LOCAL_OUT 3 /* Packets about to hit the wire. */ #define NF_BR_POST_ROUTING 4 /* Not really a hook, but used for the ebtables broute table */ #define NF_BR_BROUTING 5 #define NF_BR_NUMHOOKS 6 /* NF_BR_PRE_ROUTING:数据包刚进入桥接设备时。 NF_BR_LOCAL_IN:数据包被桥接设备本地处理时。 NF_BR_FORWARD:数据包被桥接设备转发时。 NF_BR_LOCAL_OUT:本地生成的数据包准备发送时。 NF_BR_POST_ROUTING:数据包即将离开桥接设备时。 */
- 非本地多播,只有
网桥port
处于转发或者学习模式才可正常处理包,执行br_should_route_hook
- NF 的组成为一个
br_should_route_hook
在ebtable
模块初始化时设置为ebt_broute
,该函数返回1 表示路由转发该包(三层转发),返回 0 表示桥接转发该包(二层转发)
。br_should_route_hook
返回 0 表示继续桥接处理,比较目的地址是否网桥 port 关联的设备 mac,是的话表示这个包就是发到虚拟网桥的这个网口的,修改包的type
为PACKET_HOST
- 执行
PF_BRIDGE
的NF_BR_PRE_ROUTING
过滤链,执行完毕后执行br_handle_frame_finish
小结:总共三个分支,下面我们分三个小节分别介绍这三部分
- 本地多播:经过
PF_BRIDGE
的NF_BR_LOCAL_IN
过滤链,最后执行br_handle_local_finish
ebt_broute
判断路由还是桥接- 桥接的情况下,经过
PF_BRIDGE
的NF_BR_PRE_ROUTING
过滤链,然后执行br_handle_frame_finish
二、本地多播
本地多播的情况下:br_handle_local_finish 逻辑比较简单,更新网桥的 fdb 表
static int br_handle_local_finish(struct sk_buff *skb)
{struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);if (p && p->state != BR_STATE_DISABLED)br_fdb_update(p->br, p, eth_hdr(skb)->h_source);return 0; /* process further */
}
- 网桥端口不能是禁用状态,然后调用
br_fdb_update
- fdb 表是二层转发表,记录了 mac 和网桥的端口的映射。即网桥收到一个包后根据
目的 mac
查找该表,然后找到对应的端口转发出去
void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,const unsigned char *addr)
{struct hlist_head *head = &br->hash[br_mac_hash(addr)];struct net_bridge_fdb_entry *fdb;/* some users want to always flood. */if (hold_time(br) == 0)return;fdb = fdb_find(head, addr);if (likely(fdb)) {/* attempt to update an entry for a local interface */if (unlikely(fdb->is_local)) {if (net_ratelimit()) printk(KERN_WARNING "%s: received packet with "" own address as source address\n",source->dev->name);} else {/* fastpath: update of existing entry */fdb->dst = source;fdb->ageing_timer = jiffies;}} else {spin_lock(&br->hash_lock);if (!fdb_find(head, addr))fdb_create(head, source, addr, 0);/* else we lose race and someone else inserts* it first, don't bother updating*/spin_unlock(&br->hash_lock);}
}
- 如果找到表项则更新,找不到则新建
- 查找过程则是遍历整个 hashmap,匹配 mac 地址(mac 地址是 hash key)
三、ebtable 的实现原理以及 ebt_broute
3.1 ebtable 的实现
ebtable
该模块只有内核编译时设置了 CONFIG_BRIDGE_EBT_BROUTE
才会参与编译,此时 br_should_route_hook
才不会是空指针,才会执行。
该模块功能类似于 iptables
,在二层做包过滤和操作
,专门针对以太网帧进行过滤和修改。
用法类似于 iptables:ebtables -A FORWARD -s 00:11:22:33:44:55 -j DROP
功能:
- 二层包过滤:根据 MAC 地址、以太网类型(EtherType)、VLAN 标签等信息过滤数据包。
- 桥接防火墙:实现类似防火墙的功能,但作用于二层,控制桥接设备上的流量。
- 包修改:可以修改以太网帧的头部信息,如修改源 MAC 地址、目的 MAC 地址等。
- 桥接 NAT:支持对以太网帧进行地址转换(虽然不常用)。
- 桥接流量控制:限制或允许特定类型的流量通过桥接设备。
ebtable
主要基于 netfilter
实现,前面我们已经简单介绍过 netfilter
的组成是一个二维数组
,按 pf 和 hook 组织
。ebtable
基于前面概述出现过的 PF_BRIDGE
的五种 hook 实现(忘记了可以往上翻 br_handle_frame
部分)。
下面我们看 ebtable
的具体实现:ebtable 在 PF_BRIDGE
的 NF_BR_LOCAL_IN
、NF_BR_FORWARD
、 NF_BR_LOCAL_OUT
、NF_BR_POST_ROUTING
、NF_BR_PRE_ROUTING
等五种 hook
上注册了几个钩子函数,这些钩子函数调用不同的 ebtable
函数,这些 ebtable
函数是进入 ebtable
模块的入口。
// net/bridge/netfilter/ebtable_fileter.c
// 在 NF_BR_LOCAL_IN、NF_BR_FORWARD、NF_BR_LOCAL_OUT 阶段执行
static struct nf_hook_ops ebt_ops_filter[] = {{.hook = ebt_hook,.owner = THIS_MODULE,.pf = PF_BRIDGE, // netfilter pf.hooknum = NF_BR_LOCAL_IN, // netfilter hook.priority = NF_BR_PRI_FILTER_BRIDGED,},{.hook = ebt_hook,.owner = THIS_MODULE,.pf = PF_BRIDGE,.hooknum = NF_BR_FORWARD,.priority = NF_BR_PRI_FILTER_BRIDGED,},{