在上一节提到,Openvswitch的内核模块openvswitch.ko会在网卡上注册一个函数netdev_frame_hook,每当有网络包到达网卡的时候,这个函数就会被调用。

 

  1. static
    struct sk_buff *netdev_frame_hook(struct sk_buff *skb)
  2. {
  3.    if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
  4.       return skb;
  5.  
  6.    port_receive(skb);
  7.    return NULL;
  8. }

 

调用port_receive即是调用netdev_port_receive

#define port_receive(skb) netdev_port_receive(skb, NULL)

 

  1. void netdev_port_receive(struct sk_buff *skb, struct ip_tunnel_info *tun_info)
  2. {
  3.    struct vport *vport;
  4.  
  5.    vport = ovs_netdev_get_vport(skb->dev);
  6. ……
  7.    skb_push(skb, ETH_HLEN);
  8.    ovs_skb_postpush_rcsum(skb, skb->data, ETH_HLEN);
  9.    ovs_vport_receive(vport, skb, tun_info);
  10.    return;
  11. error:
  12.    kfree_skb(skb);
  13. }

 

在函数int ovs_vport_receive(struct vport *vport, struct sk_buff *skb, const struct ip_tunnel_info *tun_info)实现如下

  1. int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
  2.             const
    struct ip_tunnel_info *tun_info)
  3. {
  4.    struct sw_flow_key key;
  5.    ......
  6.    /* Extract flow from 'skb' into 'key'. */
  7.    error = ovs_flow_key_extract(tun_info, skb, &key);
  8.    if (unlikely(error)) {
  9.       kfree_skb(skb);
  10.       return error;
  11.    }
  12.    ovs_dp_process_packet(skb, &key);
  13.    return 0;
  14. }

 

在这个函数里面,首先声明了变量struct sw_flow_key key;

如果我们看这个key的定义

  1. struct sw_flow_key {
  2.    u8 tun_opts[255];
  3.    u8 tun_opts_len;
  4.    struct ip_tunnel_key tun_key; /* Encapsulating tunnel key. */
  5.    struct {
  6.       u32 priority; /* Packet QoS priority. */
  7.       u32 skb_mark; /* SKB mark. */
  8.       u16 in_port; /* Input switch port (or DP_MAX_PORTS). */
  9.    } __packed phy; /* Safe when right after 'tun_key'. */
  10.    u32 ovs_flow_hash; /* Datapath computed hash value. */
  11.    u32 recirc_id; /* Recirculation ID. */
  12.    struct {
  13.       u8 src[ETH_ALEN]; /* Ethernet source address. */
  14.       u8 dst[ETH_ALEN]; /* Ethernet destination address. */
  15.       __be16 tci; /* 0 if no VLAN, VLAN_TAG_PRESENT set otherwise. */
  16.       __be16 type; /* Ethernet frame type. */
  17.    } eth;
  18.    union {
  19.       struct {
  20.          __be32 top_lse; /* top label stack entry */
  21.       } mpls;
  22.       struct {
  23.          u8 proto; /* IP protocol or lower 8 bits of ARP opcode. */
  24.          u8 tos; /* IP ToS. */
  25.          u8 ttl; /* IP TTL/hop limit. */
  26.          u8 frag; /* One of OVS_FRAG_TYPE_*. */
  27.       } ip;
  28.    };
  29.    struct {
  30.       __be16 src; /* TCP/UDP/SCTP source port. */
  31.       __be16 dst; /* TCP/UDP/SCTP destination port. */
  32.       __be16 flags; /* TCP flags. */
  33.    } tp;
  34.    union {
  35.       struct {
  36.          struct {
  37.             __be32 src; /* IP source address. */
  38.             __be32 dst; /* IP destination address. */
  39.          } addr;
  40.          struct {
  41.             u8 sha[ETH_ALEN]; /* ARP source hardware address. */
  42.             u8 tha[ETH_ALEN]; /* ARP target hardware address. */
  43.          } arp;
  44.       } ipv4;
  45.       struct {
  46.          struct {
  47.             struct
    in6_addr src; /* IPv6 source address. */
  48.             struct
    in6_addr dst; /* IPv6 destination address. */
  49.          } addr;
  50.          __be32 label; /* IPv6 flow label. */
  51.          struct {
  52.             struct
    in6_addr target; /* ND target address. */
  53.             u8 sll[ETH_ALEN]; /* ND source link layer address. */
  54.             u8 tll[ETH_ALEN]; /* ND target link layer address. */
  55.          } nd;
  56.       } ipv6;
  57.    };
  58.    struct {
  59.       /* Connection tracking fields. */
  60.       u16 zone;
  61.       u32 mark;
  62.       u8 state;
  63.       struct ovs_key_ct_labels labels;
  64.    } ct;
  65.  
  66. } __aligned(BITS_PER_LONG/8); /* Ensure that we can do comparisons as longs. */

 

可见这个key里面是一个大杂烩,数据包里面的几乎任何部分都可以作为key来查找flow表

  • tunnel可以作为key
  • 在物理层,in_port即包进入的网口的ID
  • 在MAC层,源和目的MAC地址
  • 在IP层,源和目的IP地址
  • 在传输层,源和目的端口号
  • IPV6

所以,要在内核态匹配流表,首先需要调用ovs_flow_key_extract,从包的正文中提取key的值。

接下来就是要调用ovs_dp_process_packet了。

  1. void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
  2. {
  3.    const
    struct vport *p = OVS_CB(skb)->input_vport;
  4.    struct datapath *dp = p->dp;
  5.    struct sw_flow *flow;
  6.    struct sw_flow_actions *sf_acts;
  7.    struct dp_stats_percpu *stats;
  8.    u64 *stats_counter;
  9.    u32 n_mask_hit;
  10.  
  11.    stats = this_cpu_ptr(dp->stats_percpu);
  12.  
  13.    /* Look up flow. */
  14.    flow = ovs_flow_tbl_lookup_stats(&dp->table, key, skb_get_hash(skb),
  15.                 &n_mask_hit);
  16.    if (unlikely(!flow)) {
  17.       struct dp_upcall_info upcall;
  18.       int error;
  19.  
  20.       memset(&upcall, 0, sizeof(upcall));
  21.       upcall.cmd = OVS_PACKET_CMD_MISS;
  22.       upcall.portid = ovs_vport_find_upcall_portid(p, skb);
  23.       upcall.mru = OVS_CB(skb)->mru;
  24.       error = ovs_dp_upcall(dp, skb, key, &upcall);
  25.       if (unlikely(error))
  26.          kfree_skb(skb);
  27.       else
  28.          consume_skb(skb);
  29.       stats_counter = &stats->n_missed;
  30.       goto
    out;
  31.    }
  32.  
  33.    ovs_flow_stats_update(flow, key->tp.flags, skb);
  34.    sf_acts = rcu_dereference(flow->sf_acts);
  35.    ovs_execute_actions(dp, skb, sf_acts, key);
  36.  
  37.    stats_counter = &stats->n_hit;
  38.  
  39. out:
  40.    /* Update datapath statistics. */
  41.    u64_stats_update_begin(&stats->syncp);
  42.    (*stats_counter)++;
  43.    stats->n_mask_hit += n_mask_hit;
  44.    u64_stats_update_end(&stats->syncp);
  45. }

 

这个函数首先在内核里面的流表中查找符合key的flow,也即ovs_flow_tbl_lookup_stats,如果找到了,很好说明用户态的流表已经放入内核,则走fast path就可了。于是直接调用ovs_execute_actions,执行这个key对应的action。

如果不能找到,则只好调用ovs_dp_upcall,让用户态去查找流表。会调用static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, const struct sw_flow_key *key, const struct dp_upcall_info *upcall_info)

它会调用err = genlmsg_unicast(ovs_dp_get_net(dp), user_skb, upcall_info->portid);通过netlink将消息发送给用户态。在用户态,有线程监听消息,一旦有消息,则触发udpif_upcall_handler。

 

Slow Path & Fast Path

Slow Path:

当Datapath找不到flow rule对packet进行处理时

Vswitchd使用flow rule对packet进行处理。

 

Fast Path:

将slow path的flow rule放在内核态,对packet进行处理

 

Unknown Packet Processing

Datapath使用flow rule对packet进行处理,如果没有,则有vswitchd使用flow rule进行处理

 

 

  1. 从Device接收Packet交给事先注册的event handler进行处理
  2. 接收Packet后识别是否是unknown packet,是则交由upcall处理
  3. vswitchd对unknown packet找到flow rule进行处理
  4. 将Flow rule发送给datapath

 

Openvswitch原理与代码分析(4):网络包的处理过程的更多相关文章

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

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

  2. Openvswitch原理与代码分析(8): 修改Openvswitch代码添加自定义action

    有时候我们需要自定义一些自己的action,根据包头里面的信息,做一些自己的操作.   例如添加一个action名为handle_example   第一.修改ofp-actions.c文件   首先 ...

  3. Openvswitch原理与代码分析(1):总体架构

      一.Opevswitch总体架构   Openvswitch的架构网上有如下的图表示:       每个模块都有不同的功能 ovs-vswitchd 为主要模块,实现交换机的守护进程daemon ...

  4. Openvswitch原理与代码分析(5): 内核中的流表flow table操作

      当一个数据包到达网卡的时候,首先要经过内核Openvswitch.ko,流表Flow Table在内核中有一份,通过key查找内核中的flow table,即可以得到action,然后执行acti ...

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

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

  6. Openvswitch原理与代码分析(7): 添加一条流表flow

    添加一个flow,调用的命令为 ovs-ofctl add-flow hello "hard_timeout=0 idle_timeout=0 priority=1 table=21 pkt ...

  7. Openvswitch原理与代码分析(6):用户态流表flow table的操作

    当内核无法查找到流表项的时候,则会通过upcall来调用用户态ovs-vswtichd中的flow table. 会调用ofproto-dpif-upcall.c中的udpif_upcall_hand ...

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

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

  9. OpenStack 虚拟机冷/热迁移的实现原理与代码分析

    目录 文章目录 目录 前文列表 冷迁移代码分析(基于 Newton) Nova 冷迁移实现原理 热迁移代码分析 Nova 热迁移实现原理 向 libvirtd 发出 Live Migration 指令 ...

随机推荐

  1. PHP会话处理相关函数介绍

    PHP会话处理相关函数介绍 提交 我的评论 加载中 已评论 PHP会话处理相关函数介绍 2015-03-23 PHP100中文网 PHP100中文网 PHP100中文网 微信号 功能介绍 互联网开发者 ...

  2. 让IE支持placeholder属性~

    原文:https://www.oschina.net/code/snippet_206691_26471#44160 让支持的直接路过,不支持的,完美显示~~ /*  * jQuery placeho ...

  3. 控制反转(Inversion of Control)之我的理解

    关于控制反转(Inversion of Control),在具体实现上也有许多其它的叫法,如依赖倒置(Dependency Inversion Principles, DIP).依赖注入(Depend ...

  4. ASP.NET MVC学习之控制器篇

    一.前言 许久之后终于可以继续我的ASP.NET MVC连载了,之前我们全面的讲述了路由相关的知识,下面我们将开始控制器和动作的讲解. ASP.NET MVC学习之路由篇幅(1) ASP.NET MV ...

  5. Linq学习之操作符

    一.环境搭建 下面将逐步搭建我们学习的环境,这个环境不仅仅是这次需要使用,以后的教程一样需要使用这个环境.所以请大家务必按照 搭建这里的环境否则会影响你后面的学习. 我们用到的几张表 通知消息表: 用 ...

  6. 体验CoreCLR的stack unwinding特性在Linux/Mac上的初步实现

    有了stack unwinding特性,才能在.NET程序中获取调用堆栈(call stack)信息,才能在异常时显示调用堆栈信息.这个特性之前只在Windows上有实现,Linux/Mac上的实现最 ...

  7. Nim教程【四】

    这是国内第一个关于Nim的系列教程 先说废话 不得不说Rust也是门不错的语言,园子里 有人曾经说: Rust语言除了library或keyword缩写比较恶心以外其他所有地方完爆go 还有人曾这样评 ...

  8. 通用对象池ObjectPool的一种简易设计和实现方案

    对象池,最简单直接的作用当然是通过池来减少创建和销毁对象次数,实现对象的缓存和复用.我们熟知的线程池.数据库连接池.TCP连接池等等都是非常典型的对象池. 一个基本的简易对象池的主要功能实现我认为应该 ...

  9. java 解析xml

    http://developer.51cto.com/art/200903/117512.htm

  10. 面试求职中需要了解的Java多线程知识

    Java中多线程的实现方式 在java的历史版本中,有两种创建多线程程序的方法 1) 通过创建Thread类的子类来实现(Thread类提供了主线程调用其它线程并行运行的机制) 主要步骤: 自定义类继 ...