集线器hub不同于Linux网桥实现的交换机,集线器不学习数据包的MAC地址,没有二层转发表FDB,对于接收到的数据包总是泛洪到所有的子接口上。相当于没有学习到任何MAC地址(FDB为空)的网桥交换机。
两个思路修改网桥为HUB工作模式,一是保持FDB表为空,不添加任何表项,将FDB添加表项函数去除;另外也可以单独写一个网桥接收处理函数,直接调用flood功能函数。以下代码采用第一种方式,实现也比较简单:
首先在向网桥添加子接口(br_add_if)时,不使用原本的网桥处理函数br_handle_frame,注册一个新的hub功能处理函数。
err = netdev_rx_handler_register(dev, br_handle_hub_frame, p);
两个思路修改网桥为HUB工作模式,一是保持FDB表为空,不添加任何表项,将FDB添加表项函数去除;另外也可以单独写一个网桥接收处理函数,直接调用flood功能函数。以下代码采用第一种方式,实现也比较简单:
首先在向网桥添加子接口(br_add_if)时,不使用原本的网桥处理函数br_handle_frame,注册一个新的hub功能处理函数。
err = netdev_rx_handler_register(dev, br_handle_hub_frame, p);
新的处理函数,主要有几个部分,一个是合法性检查;一个是初始化skb的brdev和br_nf_bypass成员;最后就是在网桥上泛洪和向本机发送一份数据包。
rx_handler_result_t br_handle_hub_frame(struct sk_buff **pskb)
{
struct net_bridge_port *p;
struct sk_buff *skb = *pskb;
struct sk_buff *skb2 = skb; //sent to host
int unicast = 0; //for a hub, all is broadcast
if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
return RX_HANDLER_PASS;
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
goto drop;
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb)
return RX_HANDLER_CONSUMED;
p = br_port_get_rcu(skb->dev);
BR_INPUT_SKB_CB(skb)->brdev = p->br->dev;
BR_INPUT_SKB_CB(skb)->br_nf_bypass = 1;
br_flood_forward(p->br, skb, skb2, unicast);
br_pass_frame_up(skb2);
return RX_HANDLER_CONSUMED;
drop:
kfree_skb(skb);
return RX_HANDLER_CONSUMED;
}
需要说明的是泛洪函数的最后一个参数unicast始终设置为零,表示对于一个hub来说无所谓单播,所有的数据包都是广播。相反如果是单播报文的话,通过bridge命令可以关闭某个网桥子接口的单播泛洪功能,将会破坏hub功能。
bridge link set dev eth0 flood off
对于集线器hub而言,其实不需要向本机发送一份数据包。此处这么做,网桥本身就可以接收数据包,可配置IP地址,可做管理接口,算是hub的一个扩展功能。
另外br_nf_bypass变量为增加的一个自定义变量。由于不管是转发还是本机接收,都会遇到bridge网桥的hook点,NF_BR_FORWARD或者NF_BR_LOCAL_IN,难保证用户不会增加ebtables的过滤策略,致使hub泛洪功能异常。增加变量br_nf_bypass掉过hook点函数,如下__br_forward函数。
static void __br_forward(const struct net_bridge_port *to, struct sk_buff *skb)
{
if (unlikely(BR_INPUT_SKB_CB(skb)->br_nf_bypass))
br_forward_finish(NULL, skb);
else
NF_HOOK(NFPROTO_BRIDGE, NF_BR_FORWARD, NULL, skb, indev, skb->dev, br_forward_finish);
}
最后注意,网桥的vlan filtering功能也不要打开(默认是关闭状态)。
内核版本
Linux-4.0