OVS BUG撸码回忆录 •上篇

本文作者 / yogazhao

爱自然科学,赞叹于大师级码农高超的艺术境界;爱生命科学,诚服于古圣先贤的天地气象。

可能大多数瓜农都对《艺伎回忆录》比较熟悉,作为一个IT界的码农,当然也有自己类似的经历,该文分为上篇和下篇,此篇为《OVS BUG撸码回忆录•上篇》

回忆录缘起

以前为排查ovs的某个bug,无奈撸了把相关内核流程。当时因为调用链太多,脑袋栈溢出,处理不过来,所以临时用txt比较零散的记录了下关键点,做完了就丢了。后面想起来,无奈用everything找了好久才找到, 再读之,发现忘了很多,临时整理这零散的笔记,也算给自己加深下意识,因为是附带研究,如有错误,恳请指正。

<内核版本>

3.10

回忆录之收包流程

网卡种类较多,当时取了e1000这种比较通用的网卡为例。下述以精简代码的形式展示核心流程。

网卡收到报文->e1000_intr->__napi_schedule(&adapter->napi)

{

         local_irq_save(flags); // 关中断

         ____napi_schedule(&__get_cpu_var(softnet_data), n);

         local_irq_restore(flags); // 开中断

         preempt_check_resched_rt();

}

____napi_schedule --> raise NET_RX_SOFTIRQ中断触发软中断NET_RX_SOFTIRQ处理函数net_rx_action被调用

net_rx_action

{

       n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);

         ...

       work = n->poll(n, weight); // e1000e_poll

}

e1000e_poll->e1000_receive_skb->napi_gro_receive->netif_receive_skb->__netif_receive_skb->__netif_receive_skb_core

{

    skb_reset_network_header(skb);//把L3、L4的头都指向data数据结构,到这里的时候skb已经处理完L2层的头了

         ...

another_round: // vlan报文处理,循环剥掉vlan头,比如vlan over vlan 两个vlan都会剥掉

         ...

         if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||

             skb->protocol == cpu_to_be16(ETH_P_8021AD)) {

                  skb = vlan_untag(skb); //洞房花烛夜,赤诚相对

                  if (unlikely(!skb))

                           goto out;

         }

    list_for_each_entry_rcu(ptype, &ptype_all, list) {  //把包交给特定协议相关的处理函数前,先调用ptype_all中注册的函数

        if (!ptype->dev || ptype->dev == skb->dev) {//最常见的为tcpdump,该工具就是从这里拿到所有收到的包的,所以tcpdump抓包在投递到协议栈处理前。

tcpdump这种经常使用的,激起了我的肾上腺素,顺便追根溯源看一把究竟

对于ptype_all

对于tcpdump,应用层调用

socket(PF_PACKET,SOCK_RAW,768)=3

对应内核,创建socket最终会调用

packet_create

{

        po->prot_hook.func = packet_rcv;

                  ...

         if (proto) {

                po->prot_hook.type = proto;

                register_prot_hook(sk)

                {

                      dev_add_pack(&po->prot_hook);

                }

        }

}

static inline struct list_head *ptype_head(const struct packet_type *pt)

{

   if (pt->type == htons(ETH_P_ALL)) // Protocol类型为ETH_P_ALL(ntohs后为768), 即所有包,则放入ptype_all链表中

             return &ptype_all;

        else

 return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];//Protocol类型为其他值,则放入ptype_base hash表中

}

void dev_add_pack(struct packet_type *pt)

{

        struct list_head *head = ptype_head(pt);

        spin_lock(&ptype_lock);

        list_add_rcu(&pt->list, head);

        spin_unlock(&ptype_lock);

}

Protocol类型为ETH_P_ALL(ntohs后为768), 即所有包,则放入ptype_all链表中

static struct packet_type arp_packet_type __read_mostly = {

        .type = cpu_to_be16(ETH_P_ARP),

        .func = arp_rcv,

};

static int arp_proc_init(void);

void __init arp_init(void)

{

        neigh_table_init(&arp_tbl);

        dev_add_pack(&arp_packet_type); // 就是添加到ptype_all

        arp_proc_init();

#ifdef CONFIG_SYSCTL

        neigh_sysctl_register(NULL, &arp_tbl.parms, "ipv4", NULL);

#endif

        register_netdevice_notifier(&arp_netdev_notifier);

}

static struct packet_type ip_packet_type __read_mostly = {

        .type = cpu_to_be16(ETH_P_IP),

        .func = ip_rcv,

};

static int __init inet_init(void)

{

         ...

         dev_add_pack(&ip_packet_type);

         ...

}

        if (pt_prev)

                ret = deliver_skb(skb, pt_prev, orig_dev);

               pt_prev = ptype;          //pt_prev的加入是为了优化,只有当找到下一个匹配的时候,才执行这一次的回调函数

                  }

         }

         // rx_handler是重中之重,关注、关注、关注。linux内核为了高性能,很喜欢用rwlock的改进版rcu,这种思想是值得借鉴的。

         rx_handler = rcu_dereference(skb->dev->rx_handler);

         if (rx_handler){

                  ...

              switch (rx_handler(&skb)) // 交给rx_handler处理,例如ovs, linux bridge等 此类接口处理报文在协议栈之前,因此netfilter对此类接口不起作用,所以在云环境(openstack)中,需要在虚拟机tap口与虚拟交换机之间增加Linux bridge设备来使报文经过协议栈(netfilter起作用)来实现security group。

肾上腺素爆棚,rx_handler必须要一探究竟。

1. 看看【ovs】的rx_handler

int netdev_rx_handler_register(struct net_device *dev,

                               rx_handler_func_t *rx_handler,

                               void *rx_handler_data)

{

        ASSERT_RTNL();

        if (dev->rx_handler)

                return -EBUSY;

        /* Note: rx_handler_data must be set before rx_handler */

        rcu_assign_pointer(dev->rx_handler_data, rx_handler_data);

        rcu_assign_pointer(dev->rx_handler, rx_handler);

        return 0;

}

./net/openvswitch/vport-netdev.c:104:  err = netdev_rx_handler_register(netdev_vport->dev, netdev_frame_hook,

static struct vport *netdev_create(const struct vport_parms *parms)

{

         ...

         err = netdev_rx_handler_register(netdev_vport->dev, netdev_frame_hook,

                                         vport);

         ...

}

总结之就是 rx_handler=netdev_frame_hook

2. 看看【bridge】的rx_handler

./net/bridge/br_if.c:375:err= netdev_rx_handler_register(dev, br_handle_frame, p);

也即 rx_handler=br_handle_frame

         }

         /*type,二层封装内的协议,IP为 0x0800*/

         type = skb->protocol;

         /*获取协议注册的入口函数,ip为 ip_rcv,声明的变量为 ip_packet_type*/

         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); // 根据具体协议投递包(如 arp、ip)

                           pt_prev = ptype; //很喜欢这种写法

                  }

         }

}->deliver_skb{

         pt_prev->func // 如ip,则对应的就是ip_rcv ---- 进入第三层

}

ip_rcv

{

    return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,

                         ip_rcv_finish);   

}->ip_rcv_finish->dst_input->ip_local_deliver

{

         /*

          *     Reassemble IP fragments. //重组分片

          */

         if (ip_is_fragment(ip_hdr(skb))) {

                  if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))

                           return 0;

         }

         return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL,

                         ip_local_deliver_finish);

}->ip_local_deliver_finish{

ipprot->handler(skb)//对应的就是udp_rcv, 进入第四层

}

udp_rcv->__udp4_lib_rcv->udp_queue_rcv_skb->encap_rcv/vxlan_udp_encap_recv

vxlan_socket_create

{

         udp_sk(sk)->encap_type = 1;

         udp_sk(sk)->encap_rcv = vxlan_udp_encap_recv;

}

盘丝洞翻遍终于让我找到了你: vxlan_udp_encap_recv

vxlan_udp_encap_recv

{

         /* pop off outer UDP header */ -- 去udp头

         __skb_pull(skb, sizeof(struct udphdr));

         不是vxlan丢弃,不符合条件的vni丢弃

         ...

         __skb_pull(skb, sizeof(struct vxlanhdr));//剥掉vxlan头

         // 没有l2头则丢弃(这里的l2头指的是original l2 frame的l2头)

         if (!pskb_may_pull(skb, ETH_HLEN)) {

                  vxlan->dev->stats.rx_length_errors++;

                  vxlan->dev->stats.rx_errors++;

                  goto drop;

         }

         /*重置skb mac数据,因为解包了(既然真爱,那就真面目相见)*/

         skb_reset_mac_header(skb);

         /*学习报文记录到本地 vxlan fdb表*/

         if ((vxlan->flags & VXLAN_F_LEARN) &&

                  vxlan_snoop(skb->dev, oip->saddr, eth_hdr(skb)->h_source))

                  goto drop;

         /*更改skb收包接口*/

         __skb_tunnel_rx(skb, vxlan->dev);

         ...

         /*skb处理完成,进入主要函数*/

         netif_rx(skb);

}

这里有点绕:前面有讲在包递交到协议栈之前,ovs有截获。而这里netif_rx又是走的本机协议栈处理,到底走哪个?

[root@node01 ~]# ovs-dpctl show

system@ovs-system:

       lookups: hit:79272720733 missed:2990364171 lost:830495930

       flows: 70550

       masks: hit:623377534012 total:7 hit/pkt:7.58

       port 0: ovs-system (internal)

       port 1: qvoab699415-b9

       port 2: qvof45c0031-aa

       port 112: qvo318ac212-4a

       port 113: br-int (internal)

       port 114: qvo860fecf7-cf

       port 115: qvod7e180b2-db

       port 333: qvo59a425ee-ae

       port 334: vxlan_sys_4789 (vxlan: df_default=false, ttl=0)

       port 335: br-tun (internal)

ovs的port有好几种类型,br-tun为internal类型, internal接口报文一般会从system类型接口传入,从system接口收包处理过程继续,internal类型接口定义的send函数为’internal_dev_recv’。static int internal_dev_recv(struct vport *vport, struct sk_buff *skb) { struct net_device *netdev = netdev_vport_priv(vport)->dev; int len;

len = skb->len; skb_dst_drop(skb); nf_reset(skb); secpath_reset(skb); skb->dev = netdev; skb->pkt_type = PACKET_HOST; skb->protocol = eth_type_trans(skb, netdev); netif_rx(skb); return len;

}

const struct vport_ops ovs_internal_vport_ops = {

       .type             = OVS_VPORT_TYPE_INTERNAL,

       .create          = internal_dev_create,

       .destroy = internal_dev_destroy,

       .get_name    = ovs_netdev_get_name,

       .send            = internal_dev_recv,

};

internal类型的接口没有设置'rx_handler', 因此也就是没有走截获,而直接走的正常网络栈流程,最终跑到vxlan_udp_encap_recv

如果是vport接口,其注册了rx_handler,故会走到netdev_frame_hook

至此上篇完结。

下篇预告: 回忆OVS内部内核态与用户态的流程及发包处理流程。

猜你还想看这些内容

● 分分钟get腾讯云TStack技术汇总!

 小甲陪你一起看Ceph (OSDC | 上篇)

● 这次,千辛万苦搭好的虚拟机终于不用重头来过了!

● 玩转K8S AdmissionWebhook

· END ·


点它!

原文发布于微信公众号 - 腾讯云TStack(gh_035269c8aa5f)

原文发表时间:2019-07-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券