赵占旭的博客

OpenvSwitch 数据报文处理

简而言之


  • 网卡收取的报文,最终会调用到ovs的datapath。
  • 进行报文的key值提取。
  • 进行流表的匹配,匹配到就转发。
  • 不匹配则将提取的key值和报文,通过netlink上传到应用层
  • 未完待续。。。。

KERNEL


注册接收函数


netdev_create-->ovs_netdev_link-->netdev_rx_handler_register调用Linux kernel代码,将netdev_frame_hook函数注册到dev->rx_handler

Linux kernel收包流程


net_rx_action-->process_backlog-->__netif_receive_skb-->__netif_receive_skb_core(skb->dev->rx_handler)调用实际函数netdev_frame_hook

netdev_frame_hook->port_receive->netdev_port_receive

  • ovs_netdev_get_vport获取虚拟端口vport。
  • ovs_vport_receive将报文传递到datapath。

ovs_vport_receive

  • ovs_flow_key_extract函数主要是从skb中提取key值。
  • ovs_dp_process_packet主要是继续处理报文。

提取key值


ovs_flow_key_extract

  • 如果是隧道报文,记录隧道key值,主要是结构体ip_tunnel_key,主要内容为tun_id、源目的ip、源目的端口、tun_flags、tos和ttl。
  • 记录报文的优先级、入端口、conntrack的label信息。
  • key_extract函数解析更详细的内容。

### key_extract

  • 将源目的mac记录在key中。
  • 将vlan标签vlan_tci记录在key中。
  • 如果是IP报文,记录L3信息到key中,主要包括源目的ip、协议号、tos、ttl、分片信息。
  • 如果是TCP、UDP或者SCTP报文,记录源目的端口到key,TCP额外多一个flags标识。
  • ICMP的报文的话,记录类型和操作码。
  • 如果是ARP报文的话,记录操作码、源目的ip和源目的mac。
  • MPLS和IPv6略。

查看key值


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

报文处理


ovs_dp_process_packet

  • ovs_flow_tbl_lookup_stats主要是匹配流表,先将key结构做一个哈希计算,根据哈希值找到哈希桶,和哈希桶里面的flow key值比较,哈希桶大小为1k。
  • 如果查找不到流表,调用ovs_vport_find_upcall_portid查找upcall的portid,填充结构体dp_upcall_info,主要内容包括命令(此处是miss,表示没有匹配)、upcall的portid、mru。然后调用ovs_dp_upcall上传信息,释放skb。
  • 如果查找到了流表,调用ovs_flow_stats_update更新统计信息,最后调用ovs_execute_actions执行action。此处暂且不表。

ovs_dp_upcall

  • 如果skb支持gso,调用queue_gso_packets,然后分片报文,然后分别调用queue_userspace_packet上传报文。
  • 不支持gso,直接调用queue_userspace_packet上传报文。

queue_userspace_packet

  • 如果是VLAN报文,添加VLAN信息。
  • upcall_msg_size计算需要upcall的长度,必备的有报文长度和key长度。可选的有upcall的用户数据长度、出口隧道key长度、action长度和mru长度。
  • genlmsg_new_unicast-->genlmsg_new申请一个如上长度的新的skb。
  • genlmsg_put主要是给新skb添加netlink协议头。
  • ovs_nla_put_key将key信息填充到新skb。
  • 将可选的那些数据填充到skb。0拷贝旧skb的内容到新skb。
  • 调用genlmsg_unicast上报报文。

User Space


首先由udpif_start_threads函数创建一些处理upcall的线程udpif_upcall_handler

udpif_upcall_handler->recv_upcalls

  • 循环64次进行以下操作。
  • 调用dpif_recv->dpif_netlink_recv接收内核传递的报文信息,并且解析数据到dupcall。
  • 调用odp_flow_key_to_flow->odp_flow_key_to_flow__根据key来查找flow,此时需要将datapath的端口号翻译为OpenFlow端口号,返回值包括严格匹配、key值内容太多、key值内容少、key值不可用。
  • 调用upcall_receive->xlate_lookup->xlate_lookup_ofproto_->xport_lookup可能是获取入端口的一些信息。没看懂!!!
  • 将dupcall的一些内容赋给upcall。
  • 调用pkt_metadata_from_flow将flow里面的一些内容赋给报文的metadata部分。
  • 调用flow_extract根据报文和metadata的数据,生成一条新的miniflow,然后拷贝miniflow的内容到flow,不是特别懂!!!这样新的flow比之前多了一些skb的信息。
  • 调用process_upcall->upcall_xlate生成kernel的flow,或者生成pakcet_in,发送给controller
  • 循环完成。
  • 调用handle_upcalls下发packet_out和flow。
  • userspace 接收netlink报文的流程。
  • 尝试50次的接收报文。
  • nl_sock_recv->nl_sock_recv__->recvmsg接收到数据。
  • parse_odp_packet主要是将netlink接收的报文解析到dpif_upcall的upcall中。

parse_odp_packet

  • nl_policy_parse将之前添加到新的skb的各个内容都解析出来。之前queue_userspace_packet接口添加的各个信息。
  • 将以上解析到的内容存入upcall中。

upcall_xlate

  • xlate_in_init主要是创建一个结构为xlate_in的对象xin,并且将一些关键信息赋给xin。
  • xlate_actions函数主要是匹配应用层流表,生成kernel的flow。
  • 如果失败,生成一个packet_in,调用ofproto_dpif_send_packet_in向控制器发送。

xlate_actions

  • 根据xin和一些其他数据生成一个结构xlate_ctx
  • 如果xin的recirc存在,会做一些操作???
  • 获取流表的version。
  • 调用rule_dpif_lookup_from_table查找流表。
  • 如果支持缓存,将流表插入缓存。
  • 采样报文给sflow或者netflow

rule_dpif_lookup_from_table

  • 以table_id为基准循环。
  • 调用rule_dpif_lookup_in_table进行流表查找,找到的话返回rule。
  • 不匹配的话,查看是否有packet_in的规则,没有的话则下发miss_rule。

rule_dpif_lookup_in_table->classifier_lookup->classifier_lookup__

  • 根据不同的优先级获取每个subtable。
  • 调用find_match_wc查找流表。不匹配或者优先级不对的话直接continue。
  • 匹配的话还要区分hard和soft,hard直接返回匹配的,soft的没看!!!

find_match_wc

  • 没有通配符的话,直接调用find_match进行流表的匹配。传入参数subtable,version,flow和flow的hash。
  • 如果有通配符的话,需要进入循环,循环次数为subtable的stage个数。
  • check_tries判断当前的stage是否匹配,不匹配跳转到no_match。
  • flowmap_or将每个阶段的状态记录在stages_map。
  • flow_hash_in_minimask_range计算当前阶段的hash值。
  • cmap_find从当前的hash值和当前的stage查找cmap节点,找不到跳转到no_match。
  • cmap_node_next判定是否是单独的节点,不是则进入下一次循环。
  • 是的话表明已经可以锁定流表,miniflow_and_mask_matches_flow_wc判定流表是否匹配,也会判定version,不匹配或者version不在区间,则返回NULL。
  • 循环完成。
  • check_triesflow_hash_in_minimask_range计算hash值。
  • 调用find_match进行流表的匹配。和最上面的参数不同的是hash值。用的上面计算的stage的hash值。
  • 没有匹配的值则做一些操作,跳转到no_match。
  • 匹配的话返回rule。

find_match

  • 通过hash值找到对应的哈希桶,然后循环拿到ovs桶里的每一条流表。
  • miniflow_and_mask_matches_flow主要是判断流表是否匹配。
  • cls_match_visible_in_version主要是判断匹配的流表支持的version是否匹配。

miniflow_and_mask_matches_flow

  • 参入参数ovs流表flow、掩码mask、报文以及key生成的流表target。
  • (flow ^ target) & *mask按位判定是否相等,相等则匹配,不等则不匹配。

handle_upcalls

  • 创建结构ukey_op的结构。
  • 在循环中进行以下操作,循环次数为收包的netlink报文数。
  • upcall->ukey中的key赋给op->dop的成员。
  • 调用ukey_get_actionsupcall->ukey中的action操作赋给op->dop的成员。
  • 循环结束。
  • 循环调用ukey_install_start根据ukey信息检查是否已经插入umap中,如果存在则返回出错,意味着不下发此条流表;不存在则将key值插入umap中,返回值正确。
  • 调用dpif_operateop->dop安装到内核,即flow流表下发和状态获取。
  • 循环调用ukey_install_finish设置ukey->flow_exists = true
  • dpif_netlink_init_flow_put主要是将上面op->dop的一些信息填充到一个新的数据结构dpif_netlink_flow的flow中,主要信息为dpid、key、mask、action等。
  • 创建一个ofpbuf结构的request,通过dpif_netlink_flow_to_ofpbuf,向request中添加一些消息头信息和上面提到flow的一些信息。
  • nl_transact_multiple通过netlink向datapath发送流表的报文request,并且接收reply。
  • dpif_netlink_flow_from_ofpbuf将reply的信息解析到dpif_netlink_flow的flow中。
  • dpif_netlink_flow_get_stats将flow中的一些报文统计信息赋给op->dop成员。

nl_transact_multiple

  • nl_pool_alloc找到已有的socket,没有则新建。
  • nl_sock_transact_multiple->nl_sock_transact_multiple__首先通过sendmsg将request数据发送,接着nl_sock_recv__->recvmsg接收reply数据。
  • nl_pool_release释放socket,如果低于16个socket,则暂存。

注意:所有文章非特别说明皆为原创。为保证信息与源同步,转载时请务必注明文章出处!谢谢合作 :-)

原始链接:http://zhaozhanxu.com/2016/08/24/SDN/OVS/2016-08-24-pkts-processing/

许可协议: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。