Openvswitch原理与代码分析(4):网络包的处理过程

?

在上一节提到,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

?

时间: 2024-10-24 13:23:35

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

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

有时候我们需要自定义一些自己的action,根据包头里面的信息,做一些自己的操作. ? 例如添加一个action名为handle_example ? 第一.修改ofp-actions.c文件 ? 首先在ofp-actions.c里面添加Openflow各个版本的这个action static const struct ofpact_map * get_ofpact_map(enum ofp_version version) { ????/* OpenFlow 1.0 actions. */ ??

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

一.Opevswitch总体架构 Openvswitch的架构网上有如下的图表示: 每个模块都有不同的功能 ovs-vswitchd 为主要模块,实现交换机的守护进程daemon 在Openvswitch所在的服务器进行ps aux可以看到以下的进程 root 1008 0.1 0.8 242948 31712 ? S<Ll Aug06 32:17 ovs-vswitchd unix:/var/run/openvswitch/db.sock -vconsole:emer -vsyslog:err

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

? 当一个数据包到达网卡的时候,首先要经过内核Openvswitch.ko,流表Flow Table在内核中有一份,通过key查找内核中的flow table,即可以得到action,然后执行action之后,直接发送这个包,只有在内核无法查找到流表项的时候,才会到用户态查找用户态的流表.仅仅查找内核中flow table的情况被称为fast path. ? ? 第一步:从数据包中提取出key ? 实现函数为int ovs_flow_key_extract(const struct ip_tun

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

在datapath/datapath.c中会调用module_init(dp_init);来初始化内核模块. static int __init dp_init(void){   int err;    BUILD_BUG_ON(sizeof(struct ovs_skb_cb) > FIELD_SIZEOF(struct sk_buff, cb));    pr_info("Open vSwitch switching datapath %s\n", VERSION);    

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

添加一个flow,调用的命令为 ovs-ofctl add-flow hello "hard_timeout=0 idle_timeout=0 priority=1 table=21 pkt_mark=0x55 tun_id=0x55 actions=mod_nw_dst:192.168.56.101,output:2" 这里调用的是调用ovs/utilities/ovs-ofctl.c的命令行工具 这个命令行工具支持的所有的命令及处理函数定义如下: static const stru

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

当内核无法查找到流表项的时候,则会通过upcall来调用用户态ovs-vswtichd中的flow table. 会调用ofproto-dpif-upcall.c中的udpif_upcall_handler函数. static void * udpif_upcall_handler(void *arg) { ????struct handler *handler = arg; ????struct udpif *udpif = handler->udpif; ? ????while (!latc

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

ovs-vswitchd.c的main函数最终会进入一个while循环,在这个无限循环中,里面最重要的两个函数是bridge_run()和netdev_run(). ? ? Openvswitch主要管理两种类型的设备,一个是创建的虚拟网桥,一个是连接到虚拟网桥上的设备. ? 其中bridge_run就是初始化数据库中已经创建的虚拟网桥. ? 一.虚拟网桥的初始化bridge_run ? bridge_run会调用bridge_run__,bridge_run__中最重要的是对于所有的网桥,都调

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

Lucene是一个基于Java的高效的全文检索库.那么什么是全文检索,为什么需要全文检索?目前人们生活中出现的数据总的来说分为两类:结构化数据和非结构化数据.很容易理解,结构化数据是有固定格式和结构的或者有限长度的数据,比如数据库,元数据等.非结构化数据则是不定长或者没有固定格式的数据,如图片,邮件,文档等.还有一种较少的分类为半结构化数据,如XML,HTML等,在一定程度上我们可以将其按照结构化数据来处理,也可以抽取纯文本按照非结构化数据来处理.非结构化数据又称为全文数据.,对其搜索主要有两种

Mesos原理与代码分析(5): Mesos Master的启动之四

? 5. Create an instance of allocator. ? 代码如下 ? Mesos源码中默认的Allocator,即HierarchicalDRFAllocator的位置在$MESOS_HOME/src/master/allocator/mesos/hierarchical.hpp,而DRF中对每个Framework排序的Sorter位于$MESOS_HOME/src/master/allocator/sorter/drf/sorter.cpp,可以查看其源码了解它的工作原