openvswitch 系列第一篇 简介及基本数据结构
openvswitch 系列第一篇 简介及基本数据结构
1. openvswitch
1.1 基本描述
datapath为ovs内核模块,负责执行数据交换,也就是把从接收端口收到的数据包在流表中进行匹配,并执行匹配到的动作。内核中可以实现多个datapath(可以理解为桥,就是我们用ovs-vsctl看到的br0/br1之类), 一个datapath类似一个物理交换机,它可以对应多个vport(vport类似物理交换机的端口概念)。一个datapth关联一个flow table,一个flow table包含多个条目,每个条目包括两个内容:一个是flow的 match/key; 另一个是对应的action. 最常见的action是在不同vport中进行转发。
当一个数据报到达vport, 内核首先将它的flow key解析出来,之后在内核模块datapath的flow cache (大小为(sizeof(struct sw_flow)+ (nr_cpu_ids * sizeof(struct flow_stats ) = ( 1248 + 68 )=1296, 根据系统实际的CUP的数量多少会稍有不同)中的flow table中查找. 如果找到匹配的flow 规则,则去执行对应的action. 如果没有找到匹配的flow规则,则将数据报通过netlink的方式发送到用户空间的ovs-vswitchd处理,而用户空间的流表空间(65536)大的多,所以匹配到的几率也更高,通过在用户空间查找,并执行对应的action;如果找到,会通过netlink把用户空间的流表推送到datapath的flow cache中,后续的报文就可以直接在内核态处理.如果最后还是没有找到,那就使用默认的流表规则(丢掉这个包,或者其他).
用户空间有两个进程组成:ovs-vswitchd和ovsdb-server。
ovsdb-server保存了ovs-vswitchd的配置信息,ovsdb通常是一个文件, 并且保存在文件系统中,通常来说是/etc/openvswitch/conf.db
ovs-vswitchd是一个daemon,北向与Controller通过OpenFlow协议通信.南向与openvswitch内核模块通过netlink通信.东西向过OVSDB协议与ovsdb-server通信.
1.2 openvswitch 内核模块示例
1 2 3 4 5 6 7 8
| # lsmod |grep openvswitch openvswitch 114793 3 nf_nat_ipv6 14131 1 openvswitch nf_defrag_ipv6 35104 2 openvswitch,nf_conntrack_ipv6 nf_nat_ipv4 14115 2 openvswitch,iptable_nat nf_nat 26787 4 openvswitch,nf_nat_ipv4,nf_nat_ipv6,nf_nat_masquerade_ipv4 nf_conntrack 133095 8 openvswitch,nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,nf_nat_masquerade_ipv4,nf_conntrack_ipv4,nf_conntrack_ipv6 libcrc32c 12644 4 xfs,openvswitch,nf_nat,nf_conntrack
|
1.3 通过openvswitch实现的bridge示例
1.4 通过openvswitch实现的bridge里面的voprt示例
1 2 3
| # ovs-vsctl list-ports br0 vport1 vport2
|
1.5 通过openvswitch实现的bridge和vport的整体概览.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # ovs-vsctl show 173a48d6-dbd3-420d-9433-384c437451a8 Bridge "br0" fail_mode: secure Port "br0" Interface "br0" type: internal Port "vport1" Interface "vport1" type: internal Port "vport2" Interface "vport2" type: internal ovs_version: "2.0.0"
|
1.6 流表示例
1 2 3
| # ovs-ofctl dump-flows br0 NXST_FLOW reply (xid=0x4): cookie=0x0, duration=24.784s, table=0, n_packets=0, n_bytes=0, idle_age=24, ip,nw_src=200.200.200.0 actions=drop
|
2. 相对应的结构体示例
2.1 datapath的代码描述 (datapath 类似数据通路,其实是bridge的抽象)
1 2 3 4 5 6 7 8 9 10 11
| struct datapath { struct rcu_head rcu; // RCU回调, 用来负责推迟延迟销毁datapath struct list_head list_node; // datapath链表,主要用来把datapath连接起来. struct flow_table table; // datapath里面的流表 struct hlist_head *ports; // datapath里面的Switch ports. 是以哈希表的形式表示. %OVSP_LOCAL 这个端口一直在datapath建立的时候就存在. 用 ovs_mutex 和 RCU 来进行锁保护. struct dp_stats_percpu __percpu *stats_percpu; // Pre-CPU的datapath状态信息 possible_net_t net; // datapat的网络命名空间的引用. u32 user_features; // datapath用户所具有的能力. u32 max_headroom; //留给datapath里面的所有vports使用的最大headroom. struct hlist_head *meters; // datapath的meters, 参数之类. };
|
2.2 vport的代码描述
1 2 3 4 5 6 7 8 9 10 11
| struct vport { struct net_device *dev; // 指向net_device的指针 struct datapath *dp; // 指向这个port所在datapath的指针,表示该端口是属于哪个datapath的 struct vport_portids __rcu *upcall_portids; //通过RCU机制保护的结构 'struct vport_portids' u16 port_no; //在datapath里面所有端口数组的索引,唯一标识该端口.因为一个datapath上有多个端口,而这些端口都是用哈希链表来存储的,所以这是链表元素(里面没有数据,只有next和prev前驱后继指针,数据部分就是vport结构体中的其他成员) struct hlist_node hash_node; //在设备hash表里面的元素 struct hlist_node dp_hash_node; //在datapath的hash表里面的元素 const struct vport_ops *ops; // 指向操作函数的指针,结构体里面存放了很多操作函数的函数指针 struct list_head detach_list; //用来在net-exit调用时撤销vport的链表 struct rcu_head rcu; //撤销datapath的RCU 回调函数头 };
|
2.3 流表flow_table (有五部分组成,一个是流表,另外是流表的实例,流表的内容,流表的key值,流表操作集和)
2.3.1 流表
1 2 3 4 5 6 7 8
| struct flow_table { // 流表 struct table_instance __rcu *ti; // 具体流表实例 struct table_instance __rcu *ufid_ti; //包含unique flow identifier的流实例, struct list_head mask_list; // 链表用来串联整个流表 (一般配合container_of使用来获取结构体的头指针) unsigned long last_rehash; // 会初始化为当前的jiffies. 用来计间用 unsigned int count; // 具体流表的个数, ovs_flow_tbl_init流表初始化时,会置为0 unsigned int ufid_count; // 具体unique flow identifier流表的个数,ovs_flow_tbl_init流表初始化时,会置为0 };
|
2.3.2 流表的具体实例
1 2 3 4 5 6 7 8
| struct table_instance { //流表的具体实例 struct flex_array *buckets; //哈希桶地址指针. 具体的流表项, 主要是方便处理.(真实的流表应该在这里面) unsigned int n_buckets; // 哈希桶个数 struct rcu_head rcu;// 操作(撤销?)流表的RCU 回调函数头 int node_ver; //node_ver的存在使得我们可以控制sw_flow的哪个hlist_node链入到bucket中 u32 hash_seed; //哈希算法需要的种子 bool keep_flows;//是否保留流表项 };
|
2.3.3 流表里面所具体保存的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct sw_flow { // 流表里面所具体保存的内容. struct rcu_head rcu; // rcu保护机制 (撤销流表项的RCU 回调函数头) struct { struct hlist_node node[2]; // 两个节点指针,用来链接作用,前驱后继指针 u32 hash; // hash值 } flow_table, ufid_table; // 为两种流表各定义对应的结构. int stats_last_writer; // 最近一个CPU写操作的ID struct sw_flow_key key; // 流表中的key值, 这个是个关键东东了,关系到报文要匹配那些流表key struct sw_flow_id id; // 流表自身的ID struct cpumask cpu_used_mask; // 也是流表中的key struct sw_flow_mask *mask; // 要匹配的mask结构体 struct sw_flow_actions __rcu *sf_acts; // 相应的action动作. 使用了rcu机制保护的. struct flow_stats __rcu *stats[]; /* 数据流的状态,每一个CPU上都有,第一个是流建立的时候, 其余的是在获取stats[0].lock锁之后,根据需求分配. 使用了rcu机制保护的. };
|
2.3.4 流表中的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[IP_TUNNEL_OPTS_MAX]; u8 tun_opts_len; struct ip_tunnel_key tun_key; // 隧道的封装key. struct { u32 priority; // 包的Qos优先级 u32 skb_mark; // skb包的标记 u16 in_port; // 包进入的端口号或者DP_MAX_PORTS } __packed phy; // Safe when right after 'tun_key'. u8 mac_proto; // 链路层协议, 比如 Ethernet, ATM 等等 u8 tun_proto; // 封装协议, 比如GRE, VXLAN 等等 u32 ovs_flow_hash; // Datapath 所计算出的 hash 值 u32 recirc_id; // 转发的ID Recirculation ID. struct { u8 src[ETH_ALEN]; // Ethernet源mac地址 u8 dst[ETH_ALEN]; // Ethernet目的mac地址 struct vlan_head vlan; // vlan 的信息, 802.1q or 802.1ad 类型 以及vlan id struct vlan_head cvlan; // vlan 的信息给Conntrack用的, 802.1q or 802.1ad 类型 以及vlan id __be16 type; // 以太网帧类型 } eth; // 2 层的. 数据链路层的匹配信息. u8 ct_state; // Conntrack的状态,有或者没有 u8 ct_orig_proto; //Conntrack 的原始路径的ip协议. 其实用来表示有没有包含original direction key 内容. union { struct { __be32 top_lse; /* top label stack entry */ } mpls; struct { u8 proto; // IP包协议类型 TCP:6;UDP:17;ARP操作码类型用低8位表示 u8 tos; // IP包服务类型 u8 ttl; // IP包生存时间,经过多少跳路由 u8 frag; // 网桥中的OVS_FRAG_TYPE_*标记 } ip; // 3 层的. ip 层的匹配信息. }; u16 ct_zone; // 追踪连接状态区 struct { __be16 src; // TCP/UDP/SCTP的源端口,应用层发送数据的端口 __be16 dst; // TCP/UDP/SCTP的目的端口,也是指应用层传输数据端口 __be16 flags; // TCP 的标记 } tp; // 4 层的. 传输层的匹配信息 union { struct { struct { __be32 src; // IP源地址 __be32 dst; // IP目的地址 } addr; // IP 的信息 union { struct { __be32 src; // Conntrack IP源地址 __be32 dst; // Conntrack IP目的地址 } ct_orig; // 追踪连接的原始目的区域 struct { u8 sha[ETH_ALEN]; // ARP的源Mac地址 u8 tha[ETH_ALEN]; // ARP的目的Mac地址 } arp; // arp 的信息 }; } ipv4; // IPv4 的信息 struct { struct { struct in6_addr src; // IPv6 源地址 struct in6_addr dst; // IPv6 目的地址 } addr; // IPv6 的IP层信息 __be32 label; // IPv6 流的标示 union { struct { struct in6_addr src; // Conntrack IP源地址 struct in6_addr dst; // Conntrack IP目的地址 } ct_orig; // 追踪连接的原始目的区域 struct { struct in6_addr target; // Neighbor Discovery 目标地址 u8 sll[ETH_ALEN]; // Neighbor Discovery 源链接层地址 u8 tll[ETH_ALEN]; // Neighbor Discovery 目标接层地址 } nd; //Neighbor Discovery (ND) protocol 是一个IPV6 的协议. 主机或者路由使用ND协议去侦测邻居的链路层地址,在必要的时候,可以及时清除无效的cache }; } ipv6; // IPV6 信息. struct ovs_key_nsh nsh; // 网络服务头 }; struct { // Connection tracking fields 连接监测的信息, CT主要用来做网络连接状态的识别. OVS2.5版本开始支持. 涉及到有状态的防火墙和无状态的防火墙.Openstack则从M版开始,使用OVS的新特性,来实现“有状态防火墙”中的“Security Group”功能 struct { __be16 src; /* CT orig tuple tp src port. 连接监测源端口.用于“有状态防火墙”的流识别 __be16 dst; /* CT orig tuple tp dst port. 连接监测目的端口.用于“有状态防火墙”的流识别 } orig_tp; u32 mark; struct ovs_key_ct_labels labels; // 这个是个32bit联合体,作为一个labels } ct; // Connection tracking, 报文进来可能会先进入这里,如果有的话, 然后匹配自身的流表之后再去匹配datapath的流表,再之后执行action.
} __aligned(BITS_PER_LONG/8); // 主要是用来做数据对齐用的. 考虑到存取的效率, 在64bit机器上,就是8字节对齐
|
2.3.5 流表项操作, 也就是能对流表做些什么操作.
1 2 3 4 5 6
| struct sw_flow_actions { struct rcu_head rcu; // 要操作流表,肯定要个rcu来加锁同步了. size_t orig_len; // 来自 flow_cmd_new netlink actions 操作的长度 u32 actions_len; // 操作的长度. struct nlattr actions[]; //流表项的操作集合,这个是一个netlink的结构,一个就是数据包的长度,一个就是类型. };
|
到此,第一部分就介绍完毕了. 后续会有网桥的操作,vport的操作,数据报的处理等等的介绍…