ILD

bridge packet rx flow on 4.4 kernel
作者:Yuan Jianpeng 邮箱:yuanjianpeng@xiaomi.com
发布时间:2022-6-17 站点:Inside Linux Development

    本文分析了桥上包RX处理流程。注意本文是openwrt版本ipq5018 soc的内核,openwrt和高通都可能修改了一些逻辑

1 rx处理函数注册

在桥添加接口的函数(net/bridge/br_if.c)中:

        int br_add_if(struct net_bridge *br, struct net_device *dev)

调用

        struct net_bridge_port *p;

        err = netdev_rx_handler_register(dev, br_handle_frame, p);

给子接口注册一个rx handler。这样子接口收到包之后,就会调用br_handle_frame进行处理。


在 __netif_receive_skb_core()中,会调用rx_handler。

        rx_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 out;

                case RX_HANDLER_ANOTHER:

                        goto another_round;

                case RX_HANDLER_EXACT:

                        deliver_exact = true;

                case RX_HANDLER_PASS:

                        break;

                default:

                        BUG();

                }

        }



桥处理入口函数 br_handle_frame

定义在net/bridge/br_input.c


先计算出bridge port:

    p = br_port_get_rcu(skb->dev);


如果定义了br_should_route_hook指针,则调用这个函数处理skb。ebtable(net/bridge/netfilter/ebtable_broute.c)的broute功能会设置br_should_route_hook为ebt_broute。


        switch (p->state) {


        case BR_STATE_FORWARDING:

                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 */

        case BR_STATE_LEARNING:

                if (ether_addr_equal(p->br->dev->dev_addr, dest))

                        skb->pkt_type = PACKET_HOST;


                BR_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING,

                        dev_net(skb->dev), NULL, skb, skb->dev, NULL,

                        br_handle_frame_finish);

                break;

        }


        return RX_HANDLER_CONSUMED;



最后,使用BR_HOOK(),调用bridge netfilter的NF_BR_PRE_ROUTING hook,然后再调用br_handle_frame_finish。


bool br_netfilter_run_hooks(void)

{

        return brnf_call_iptables | brnf_call_ip6tables | brnf_call_arptables |

                brnf_call_ebtables | brnf_call_custom;

}


static inline int

BR_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk,

        struct sk_buff *skb, struct net_device *in, struct net_device *out,

        int (*okfn)(struct net *, struct sock *, struct sk_buff *))

{

        if (!br_netfilter_run_hooks())

                return okfn(net, sk, skb);


        return NF_HOOK(pf, hook, net, sk, skb, in, out, okfn);

}


如果没有打开br_netfilter,则直接 调用okfn,否则走正常的NF_HOOK流程。


3 桥转发处理函数 br_handle_frame_finish

net/bridge/br_input.c


计算出桥:br = p->br;


如果子接口地址学习打开,则更新fdb

        if (p->flags & BR_LEARNING)

                br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, false);


处理multicast帧,注意,这里不是转发组播帧,只是学习组播帧的内容,用于multicast snooping。

        if (!is_broadcast_ether_addr(dest) && is_multicast_ether_addr(dest) &&

            br_multicast_rcv(br, p, skb, vid))

                goto drop;


接口开始处理帧转发或者local in


        /* The packet skb2 goes to the local host (NULL to skip). */

        skb2 = NULL;


        if (br->dev->flags & IFF_PROMISC)

                skb2 = skb;


        dst = NULL;


        if (IS_ENABLED(CONFIG_INET) && skb->protocol == htons(ETH_P_ARP))

                br_do_proxy_arp(skb, br, vid, p);


        if (skb->protocol == htons(ETH_P_PAE)) {

                skb2 = skb;

                /* Do not forward 802.1x/EAP frames */

                skb = NULL;

        } else if (is_broadcast_ether_addr(dest)) {

                skb2 = skb;

                unicast = false;

        } else if (is_multicast_ether_addr(dest)) {

                br_multicast_handle_hook_t *multicast_handle_hook =

                        rcu_dereference(br_multicast_handle_hook);

                if (!__br_get(multicast_handle_hook, true, p, skb))

                        goto out;


                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->mglist) ||

                            br_multicast_is_router(br))

                                skb2 = skb;

                        br_multicast_forward(mdst, skb, skb2);

                        skb = NULL;

                        if (!skb2)

                                goto out;

                } else

                        skb2 = skb;


                unicast = false;

                br->dev->stats.multicast++;

        } else {

                pdst = __br_get(get_dst_hook, NULL, p, &skb);

                if (pdst) {

                        if (!skb)

                                goto out;

                } else {

                        dst = __br_fdb_get(br, dest, vid);

                        if ((p->flags & BR_ISOLATE_MODE) ||

                            (dst && dst->is_local)) {

                                skb2 = skb;

                                /* Do not forward the packet since it's local.*/

                                skb = NULL;

                        }

                }

        }


        if (skb) {

                if (dst) {

                        dst->used = jiffies;

                        pdst = dst->dst;

                }


                if (pdst)

                        br_forward(pdst, skb, skb2);

                else

                        br_flood_forward(br, skb, skb2, unicast);

        }


        if (skb2)

                return br_pass_frame_up(skb2);


out:

        return 0;

drop:

        kfree_skb(skb);

        goto out;


总结如下:skb是要转发的帧,skb2是要上到本地协议栈的帧。


如果是广播包,则skb2 = skb,


如果是多播包,

multicast_handle_hook,如果这个hook定义了,执行这个hook,如果hook返回false,则直接跳出去,不进行转发处理。

mdst = br_mdb_get(br, skb, vid);

如果mdst是空,则进行广播处理。否则,调用 br_multicast_forward(mdst, skb, skb2) 处理。


如果是单播包

则查找fdb,


调用:

br_forward(pdst, skb, skb2);

br_flood_forward(br, skb, skb2, unicast);

br_pass_frame_up(skb2);

等进行转发处理。


4 桥转发函数 br_flood

net/bridge/br_forward.c

遍历桥的port,执行转发。




Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.