前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OVS BUG撸码回忆录 •上篇

OVS BUG撸码回忆录 •上篇

作者头像
腾讯云TStack
发布2019-07-19 10:40:41
1.3K0
发布2019-07-19 10:40:41
举报

本文作者 / 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 ·


点它!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯云TStack 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档