Linux 网桥代码分析 五 网桥端口设备接收数据包的处理分析
对于网桥端口设备,底层接收到数据以后,经过网卡驱动的接收函数处理以后,最终会调用函数netif_receive_skb,而netif_receive_skb在对数据包头进行相关处理以及对ptype_all链上注册的相关协议进行调用deliver_skb处理后(包括PF_PACKET类型的rawsocket处理),会调用handle_bridge进入网桥处理,而其会调用br_handle_frame_hook,即会调用br_handle_frame。
函数调用流程为netif_receive_skb->handle_bridge->br_handle_frame.
下面分析函数br_handle_frame,其作用是处理网桥入口数据主要函数。
1、判断mac地址是否有效(既不是组播mac地址也不是0mac地址)
2、判断skb包是否有效数据包
3、判断目的mac地址是否是01 80 c2 00 0x类型,若是,则继续判断是0x8808 协议,若是0x8808,个人理解则可能是mpcp相关
的数据包,而mpcp是epon相关的协议,而mpcp协议中相关的消息和epon mac的硬件息息相关,linux内核对这类数据包就
没有提供相关的公共函数了。对于其他类型的 数据包,则调用函数br_handle_local_finish进行后续处理,而br_handle_local_finish
也仅仅是调用br_fdb_update,更新fdb数据库
4、对于网桥端口是forward和learning状态的,则调用防火墙处理函数
处理NF_BR_PRE_ROUTING的ebtables相关的规则。
5、当通过NF_BR_PRE_ROUTING相关的ebtables规则后,则会调用函数
br_handle_frame_finish继续进行数据处理
struct sk_buff *br_handle_frame(struct net_bridge_port *p,struct sk_buff *skb)
{
const unsigned char*dest = eth_hdr(skb)->h_dest;
int (*rhook)(structsk_buff *skb);
if(!is_valid_ether_addr(eth_hdr(skb)->h_source))
goto drop;
skb =skb_share_check(skb, GFP_ATOMIC);
if (!skb)
return NULL;
if(unlikely(is_link_local(dest))) {
/* Pause framesshouldn't be passed up by driver anyway */
if(skb->protocol == htons(ETH_P_PAUSE))
goto drop;
/* If STP isturned off, then forward */
if(p->br->stp_enabled == BR_NO_STP && dest[5] == 0)
goto forward;
if(NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
NULL, br_handle_local_finish))
return NULL; /* frame consumed by filter */
else
return skb; /* continue processing */
}
forward:
switch (p->state) {
caseBR_STATE_FORWARDING:
rhook =rcu_dereference(br_should_route_hook);
if (rhook != NULL){
if (rhook(skb))
returnskb;
dest =eth_hdr(skb)->h_dest;
}
/* fall through */
caseBR_STATE_LEARNING:
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);
break;
default:
drop:
kfree_skb(skb);
}
return NULL;
}
在这个函数的尾部,会调用br_handle_frame_finish,进行后续的处理,我们继续分析函数br_handle_frame_finish。该函数的作用是决定数据包的走向:转发、扩散或是丢弃
下面分析这个函数的主要流程:
1、首先判断接收到数据包的设备对应的网桥端口的状态是否为disable
2、调用br_fdb_update,更新fdb数据库,为数据包源mac地址与源网桥端口
添加fdb entry,关于br_fdb_update,请参考我前面的分析文档
3、如果源网桥端口的 状态为learning,则不处理该数据包
4、a)如果网桥设备处于混杂模式或者数据包的目的mac地址为组播
地址,则需要将该skb的一个拷贝,发送给上层协议栈(通过调用
br_pass_frame_up实现)
b)如果数据包的目的mac地址为本地mac,则只只需要将该数据包
发送给上次协议栈,而不需转发数据包。
5、调用__br_fdb_get查找符合条件的fdb entry,
a)若查找到了了符合条件的fdbentr
i)若该fdb entry为local类型的,说明该数据包是发往本地的,则将
skb赋值给skb2,然后skb指向NULL,不对该数据包进行转发。
ii)若该fdb entry不是local类型的,则调用br_forward,将数据包从指定端口转发
出去。
b)若没有查找到指定的端口,则调用br_flood_forward,将数据从其他所有
网桥端口发送出去
int br_handle_frame_finish(struct sk_buff *skb)
{
const unsigned char*dest = eth_hdr(skb)->h_dest;
struct net_bridge_port*p = rcu_dereference(skb->dev->br_port);
struct net_bridge *br;
structnet_bridge_fdb_entry *dst;
struct sk_buff *skb2;
if (!p || p->state== BR_STATE_DISABLED)
goto drop;
/* insert intoforwarding database after filtering to avoid spoofing */
br = p->br;
br_fdb_update(br, p,eth_hdr(skb)->h_source);
if (p->state ==BR_STATE_LEARNING)
goto drop;
/* The packet skb2goes to the local host (NULL to skip). */
skb2 = NULL;
if(br->dev->flags & IFF_PROMISC)
skb2 = skb;
dst = NULL;
if(is_multicast_ether_addr(dest)) {
br->dev->stats.multicast++;
skb2 = skb;
} else if ((dst =__br_fdb_get(br, dest)) && dst->is_local) {
skb2 = skb;
/* Do not forwardthe packet since it's local. */
skb = NULL;
}
if (skb2 == skb)
skb2 =skb_clone(skb, GFP_ATOMIC);
if (skb2)
br_pass_frame_up(br,skb2);
if (skb) {
if (dst)
br_forward(dst->dst,skb);
else
br_flood_forward(br,skb);
}
out:
return 0;
drop:
kfree_skb(skb);
goto out;
}
在这个函数里,主要的处理函数有br_pass_frame_up、br_forward、br_flood_forward,而br_forward、br_flood_forward是转发相关的函数,我们下节进行分析。
我们现在分析下函数br_pass_frame_up
该函数的作用是将数据包传递给上层协议栈进行处理。该函数的处理流程如下:
1、将skb->dev设置为brdev
2、调用NF_HOOK处理input链上的防火墙规则,对于准许通过的
数据包,则调用netif_receive_skb进行后续处理,此时由于skb->dev
为brdev,所以这次netif_receive_skb调用则会跳过brdige_handle,上传给
上层协议栈进行后续处理。
static void br_pass_frame_up(struct net_bridge *br, structsk_buff *skb)
{
struct net_device*indev, *brdev = br->dev;
brdev->stats.rx_packets++;
brdev->stats.rx_bytes+= skb->len;
indev = skb->dev;
skb->dev = brdev;
NF_HOOK(PF_BRIDGE,NF_BR_LOCAL_IN, skb, indev, NULL,
netif_receive_skb);
}
至此,入口数据处理函数分析完毕,主要是br_input.c中的函数。
更多推荐
所有评论(0)