当一个数据包到达网卡的时候,首先要经过内核Openvswitch.ko,流表Flow Table在内核中有一份,通过key查找内核中的flow table,即可以得到action,然后执行action之后,直接发送这个包,只有在内核无法查找到流表项的时候,才会到用户态查找用户态的流表。仅仅查找内核中flow table的情况被称为fast path.

第一步:从数据包中提取出key

实现函数为int ovs_flow_key_extract(const struct ip_tunnel_info *tun_info, struct sk_buff *skb, struct sw_flow_key *key)

在这个函数中,首先提取的是物理层的信息,主要是从哪个网口进入的。

  1. key->phy.priority = skb->priority;
  2. key->phy.in_port = OVS_CB(skb)->input_vport->port_no;
  3. key->phy.skb_mark = skb->mark;
  4. ovs_ct_fill_key(skb, key);
  5. key->ovs_flow_hash = 0;
  6. key->recirc_id = 0;

然后调用函数static int key_extract(struct sk_buff *skb, struct sw_flow_key *key)提取其他的key

提取MAC层

  1. /* Link layer. We are guaranteed to have at least the 14 byte Ethernet
  2.  * header in the linear data area.
  3.  */
  4. eth = eth_hdr(skb);
  5. ether_addr_copy(key->eth.src, eth->h_source);
  6. ether_addr_copy(key->eth.dst, eth->h_dest);
  7. __skb_pull(skb, 2 * ETH_ALEN);
  8. /* We are going to push all headers that we pull, so no need to
  9.  * update skb->csum here.
  10.  */
  11. key->eth.tci = 0;
  12. if (skb_vlan_tag_present(skb))
  13.    key->eth.tci = htons(vlan_get_tci(skb));
  14. else if (eth->h_proto == htons(ETH_P_8021Q))
  15.    if (unlikely(parse_vlan(skb, key)))
  16.       return -ENOMEM;
  17. key->eth.type = parse_ethertype(skb);

提取网络层

  1. struct iphdr *nh;
  2. __be16 offset;
  3. error = check_iphdr(skb);
  4. if (unlikely(error)) {
  5.    memset(&key->ip, 0, sizeof(key->ip));
  6.    memset(&key->ipv4, 0, sizeof(key->ipv4));
  7.    if (error == -EINVAL) {
  8.       skb->transport_header = skb->network_header;
  9.       error = 0;
  10.    }
  11.    return error;
  12. }
  13. nh = ip_hdr(skb);
  14. key->ipv4.addr.src = nh->saddr;
  15. key->ipv4.addr.dst = nh->daddr;
  16. key->ip.proto = nh->protocol;
  17. key->ip.tos = nh->tos;
  18. key->ip.ttl = nh->ttl;
  19. offset = nh->frag_off & htons(IP_OFFSET);
  20. if (offset) {
  21.    key->ip.frag = OVS_FRAG_TYPE_LATER;
  22.    return 0;
  23. }
  24. if (nh->frag_off & htons(IP_MF) ||
  25.    skb_shinfo(skb)->gso_type & SKB_GSO_UDP)
  26.    key->ip.frag = OVS_FRAG_TYPE_FIRST;
  27. else
  28.    key->ip.frag = OVS_FRAG_TYPE_NONE;

提取传输层

  1. /* Transport layer. */
  2. if (key->ip.proto == IPPROTO_TCP) {
  3.    if (tcphdr_ok(skb)) {
  4.       struct tcphdr *tcp = tcp_hdr(skb);
  5.       key->tp.src = tcp->source;
  6.       key->tp.dst = tcp->dest;
  7.       key->tp.flags = TCP_FLAGS_BE16(tcp);
  8.    } else {
  9.       memset(&key->tp, 0, sizeof(key->tp));
  10.    }
  11. } else if (key->ip.proto == IPPROTO_UDP) {
  12.    if (udphdr_ok(skb)) {
  13.       struct udphdr *udp = udp_hdr(skb);
  14.       key->tp.src = udp->source;
  15.       key->tp.dst = udp->dest;
  16.    } else {
  17.       memset(&key->tp, 0, sizeof(key->tp));
  18.    }
  19. } else if (key->ip.proto == IPPROTO_SCTP) {
  20.    if (sctphdr_ok(skb)) {
  21.       struct sctphdr *sctp = sctp_hdr(skb);
  22.       key->tp.src = sctp->source;
  23.       key->tp.dst = sctp->dest;
  24.    } else {
  25.       memset(&key->tp, 0, sizeof(key->tp));
  26.    }
  27. } else if (key->ip.proto == IPPROTO_ICMP) {
  28.    if (icmphdr_ok(skb)) {
  29.       struct icmphdr *icmp = icmp_hdr(skb);
  30.       /* The ICMP type and code fields use the 16-bit
  31.        * transport port fields, so we need to store
  32.        * them in 16-bit network byte order.
  33.        */
  34.       key->tp.src = htons(icmp->type);
  35.       key->tp.dst = htons(icmp->code);
  36.    } else {
  37.       memset(&key->tp, 0, sizeof(key->tp));
  38.    }
  39. }

第二步:根据key查找flow table

调用struct sw_flow *ovs_flow_tbl_lookup_stats(struct flow_table *tbl, const struct sw_flow_key *key, u32 skb_hash, u32 *n_mask_hit)进行查找。

在内核中,flow table的数据结构如上图所示。

每个虚拟交换机对应一个datapath,每个datapath有一个flow table,每个flow table分成N个桶,根据key进行哈希,不同的key分布在不同的桶里面。

每个桶的大小是一个内存页的大小,在内存页的头部保存了保存了多少个元素,每个元素的大小。每个元素都是sw_flow,里面有key,也有action。

ovs_flow_tbl_lookup_stats会调用static struct sw_flow *flow_lookup(struct flow_table *tbl, struct table_instance *ti, const struct mask_array *ma, const struct sw_flow_key *key, u32 *n_mask_hit, u32 *index)

会调用masked_flow_lookup如下

  1. static struct sw_flow *masked_flow_lookup(struct table_instance *ti,
  2.                  const struct sw_flow_key *unmasked,
  3.                  const struct sw_flow_mask *mask,
  4.                  u32 *n_mask_hit)
  5. {
  6.    struct sw_flow *flow;
  7.    struct hlist_head *head;
  8.    u32 hash;
  9.    struct sw_flow_key masked_key;
  10.    ovs_flow_mask_key(&masked_key, unmasked, false, mask);
  11.    hash = flow_hash(&masked_key, &mask->range);
  12.    head = find_bucket(ti, hash);
  13.    (*n_mask_hit)++;
  14.    hlist_for_each_entry_rcu(flow, head, flow_table.node[ti->node_ver]) {
  15.       if (flow->mask == mask && flow->flow_table.hash == hash &&
  16.           flow_cmp_masked_key(flow, &masked_key, &mask->range))
  17.          return flow;
  18.    }
  19.    return NULL;
  20. }

其中flow_hash计算哈希值,find_bucket根据哈希值查找桶,然后就是一个循环,逐个比较key是否相等,相等则返回flow。

第三步:执行action

调用int ovs_execute_actions(struct datapath *dp, struct sk_buff *skb, const struct sw_flow_actions *acts,struct sw_flow_key *key)

调用static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, struct sw_flow_key *key, const struct nlattr *attr, int len)

在这个函数中,通过case语句,不同的action进行不同的操作。

  1. static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
  2.                struct sw_flow_key *key,
  3.                const struct nlattr *attr, int len)
  4. {
  5.    /* Every output action needs a separate clone of 'skb', but the common
  6.     * case is just a single output action, so that doing a clone and
  7.     * then freeing the original skbuff is wasteful. So the following code
  8.     * is slightly obscure just to avoid that.
  9.     */
  10.    int prev_port = -1;
  11.    const struct nlattr *a;
  12.    int rem;
  13.    for (a = attr, rem = len; rem > 0;
  14.         a = nla_next(a, &rem)) {
  15.       int err = 0;
  16.       if (unlikely(prev_port != -1)) {
  17.          struct sk_buff *out_skb = skb_clone(skb, GFP_ATOMIC);
  18.          if (out_skb)
  19.             do_output(dp, out_skb, prev_port, key);
  20.          prev_port = -1;
  21.       }
  22.       switch (nla_type(a)) {
  23.       case OVS_ACTION_ATTR_OUTPUT:
  24.          prev_port = nla_get_u32(a);
  25.          break;
  26.       case OVS_ACTION_ATTR_USERSPACE:
  27.          output_userspace(dp, skb, key, a, attr, len);
  28.          break;
  29.       case OVS_ACTION_ATTR_HASH:
  30.          execute_hash(skb, key, a);
  31.          break;
  32.       case OVS_ACTION_ATTR_PUSH_MPLS:
  33.          err = push_mpls(skb, key, nla_data(a));
  34.          break;
  35.       case OVS_ACTION_ATTR_POP_MPLS:
  36.          err = pop_mpls(skb, key, nla_get_be16(a));
  37.          break;
  38.       case OVS_ACTION_ATTR_PUSH_VLAN:
  39.          err = push_vlan(skb, key, nla_data(a));
  40.          break;
  41.       case OVS_ACTION_ATTR_POP_VLAN:
  42.          err = pop_vlan(skb, key);
  43.          break;
  44.       case OVS_ACTION_ATTR_RECIRC:
  45.          err = execute_recirc(dp, skb, key, a, rem);
  46.          if (nla_is_last(a, rem)) {
  47.             /* If this is the last action, the skb has
  48.              * been consumed or freed.
  49.              * Return immediately.
  50.              */
  51.             return err;
  52.          }
  53.          break;
  54.       case OVS_ACTION_ATTR_SET:
  55.          err = execute_set_action(skb, key, nla_data(a));
  56.          break;
  57.       case OVS_ACTION_ATTR_SET_MASKED:
  58.       case OVS_ACTION_ATTR_SET_TO_MASKED:
  59.          err = execute_masked_set_action(skb, key, nla_data(a));
  60.          break;
  61.       case OVS_ACTION_ATTR_SAMPLE:
  62.          err = sample(dp, skb, key, a, attr, len);
  63.          break;
  64.       case OVS_ACTION_ATTR_CT:
  65.          if (!is_flow_key_valid(key)) {
  66.             err = ovs_flow_key_update(skb, key);
  67.             if (err)
  68.                return err;
  69.          }
  70.          err = ovs_ct_execute(ovs_dp_get_net(dp), skb, key,
  71.                     nla_data(a));
  72.          /* Hide stolen IP fragments from user space. */
  73.          if (err)
  74.             return err == -EINPROGRESS ? 0 : err;
  75.          break;
  76.       }
  77.       if (unlikely(err)) {
  78.          kfree_skb(skb);
  79.          return err;
  80.       }
  81.    }
  82.    if (prev_port != -1)
  83.       do_output(dp, skb, prev_port, key);
  84.    else
  85.       consume_skb(skb);
  86.    return 0;
  87. }

如果可以直接输出,则调用static void do_output(struct datapath *dp, struct sk_buff *skb, int out_port, struct sw_flow_key *key)他调用void ovs_vport_send(struct vport *vport, struct sk_buff *skb)进行发送。

转载于:https://www.cnblogs.com/popsuper1982/p/5886819.html

Openvswitch原理与代码分析(5): 内核中的流表flow table操作相关推荐

  1. Openvswitch原理与代码分析(2): ovs-vswitchd的启动

    ovs-vswitchd.c的main函数最终会进入一个while循环,在这个无限循环中,里面最重要的两个函数是bridge_run()和netdev_run(). Openvswitch主要管理两种 ...

  2. Openvswitch原理与代码分析(3): openvswitch内核模块的加载

    上一节我们讲了ovs-vswitchd,其中虚拟网桥初始化的时候,对调用内核模块来添加虚拟网卡. 我们从openvswitch内核模块的加载过程,来看这个过程. 在datapath/datapath. ...

  3. 对dpdk的rte_ring实现原理和代码分析

    对dpdk的rte_ring实现原理和代码分析 前言 dpdk的rte_ring是借鉴了linux内核的kfifo实现原理,这里统称为无锁环形缓冲队列. 环形缓冲区通常有一个读指针和一个写指针.读指针 ...

  4. TrueCrypt 6.2a原理及代码分析

    TrueCrypt 6.2a原理及代码分析 3 comments 25th Apr 10 rafa 1 项目物理布局 Project     |____ Boot /* MBR部分的代码 */     ...

  5. 免费的Lucene 原理与代码分析完整版下载

    Lucene是一个基于Java的高效的全文检索库. 那么什么是全文检索,为什么需要全文检索? 目前人们生活中出现的数据总的来说分为两类:结构化数据和非结构化数据.很容易理解,结构化数据是有固定格式和结 ...

  6. Lucene 原理与代码分析完整版

    原文地址为: Lucene 原理与代码分析完整版 Lucene 原理与代码分析系列文章已经基本告一段落,可能问题篇还会有新的更新. 完整版pdf可由以下链接下载. Lucene 原理与代码分析完整版 ...

  7. Lucene原理与代码分析(高手博客备忘)

    2019独角兽企业重金招聘Python工程师标准>>> 随笔 - 69  文章 - 77  评论 - 687 随笔分类 - Lucene原理与代码分析 Lucene 4.X 倒排索引 ...

  8. 二值图像分析:OpenCV中的二值化阈值操作

    二值图像分析:OpenCV中的二值化阈值操作 1.二值图像的定义 2.OpenCV中的基本阈值操作 3.OTSU二值寻找算法 3.1 OTSU二值寻找算法介绍 3.2 OTSU二值寻找算法分析 3.2 ...

  9. mysql 操作表的例子,mysql中库和表的简单操作总结(附示例)

    本篇文章给大家带来的内容是关于mysql中库和表的简单操作总结(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 一. 库的操作 1.创建数据库 创建数据库: create d ...

  10. mysql数据表案例_mysql中库和表的简单操作案例

    mysql中库和表的简单操作案例 发布时间:2020-12-05 09:54:06 来源:亿速云 阅读:71 作者:小新 这篇文章主要介绍mysql中库和表的简单操作案例,文中介绍的非常详细,具有一定 ...

最新文章

  1. IE6、 IE7、IE8、Firefox兼容性问题
  2. 电机编码器调零步骤_各种编码器的调零方法
  3. java求多项式回归_多项式回归(Polynomial Regression)(附代码)
  4. ASP.NET中共有哪几种类型的控件?其中,HTML控件、HTML服务器控件和WEB服务器控件之间有什么区别
  5. thinkphp v5.0.11漏洞_ThinkPHP5丨远程代码执行漏洞动态分析
  6. php header 跳转 ie问题
  7. VRTK HTC手柄发出射线,瞬移,选择物体 VRTK和steamVR对应版本
  8. 7000个源码批量下载---复制来的
  9. java 场景面试题_Java面试场景整理收录
  10. 爬虫 and 数据分析 | 一万条b站评论看工作细胞
  11. 计算机网络技能大赛感受,技能大赛心得体会
  12. Python学习培训方法
  13. 华为有望解决5G射频芯片问题,5G手机或将回归
  14. 【CXY】JAVA基础 之 System
  15. pandas 改列名称_01_Pandas.DataFrame的行名和列名的修改
  16. 用android写的微信闲聊机器人
  17. Unix 开机时如何自启动oracle
  18. android GoogleMap画导航线路图 路径规划
  19. 专科毕业学习python有前途吗_专科生学IT有前途吗?
  20. 让BAT执行php程序,不错哦!

热门文章

  1. 孤荷凌寒自学python第八十天开始写Python的第一个爬虫10
  2. 使用 profile 进行python代码性能分析
  3. bzoj 2243: [SDOI2011]染色 线段树区间合并+树链剖分
  4. linux设备驱动归纳总结(三):5.阻塞型IO实现【转】
  5. android shape的使用详解以及常用效果(渐变色、分割线、边框、半透明阴影效果等)...
  6. C#:Md5和Sha1两种加密方式
  7. javascript的table 对象 属性 方法
  8. 明天发布一个基于Silverlight的类Visio小型绘图工具项目。
  9. 人性的弱点---第三篇---得人同意于你的十二种方法3
  10. ASP.NET profile之 找不到存储过程'dbo.aspnet_CheckSchemaVersion'