欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OVS原理

程序员文章站 2022-07-06 22:03:02
...

OVS原理

OVS架构

OVS原理
ovs的架构如上图所示,主要由内核datapath和用户空间的vswitchd、ovsdb组成。
OVS原理

主要模块职责

  1. datapath是负责数据交换的内核模块,其从网口读取数据,并快速匹配Flowtable中的流表项,成功的直接转发,失败的上交vswitchd处理。它在初始化和port binding的时候注册钩子函数,把端口的报文处理接管到内核模块。
  2. vswitchd是一个守护进程,是ovs的管理和控制服务,通过unix socket将配置信息保存到ovsdb,并通过netlink和内核模块交互
  3. ovsdb则是ovs的数据库,保存了ovs配置信息

主要数据结构

OVS原理

主要流程

添加网桥

  1. 键入命令ovs-vsctl add-br testBR
  2. 内核中的 openvswitch.ko 收到一个添加网桥的命令时候——即收到OVS_DATAPATH_FAMILY通道的 OVS_DP_CMD_NEW命令。该命令绑定的回调函数为 ovs_dp_cmd_new
  3. ovs_dp_cmd_new 函数除了初始化 dp 结构外,调用 new_vport 函数来生成新的 vport
  4. new_vport 函数调用 ovs_vport_add()来尝试生成一个新的 vport
  5. ovs_vport_add()函数会检查 vport 类型(通过vport_ops_list[]数组),并调用相关的 create()函数来生成 vport 结构
  6. 当dp是网络设备时(vport_netdev.c),最终由 ovs_vport_add()函数调用的是netdev_create()【在 vport_ops_list的ovs_netdev_ops 中】
  7. netdev_create()函数最关键的一步是注册了收到网包时的回调函数
  8. err=netdev_rx_handler_register(netdev_vport->dev,netdev_frame_hook,vport);
  9. 操作是将 netdev_vport->dev 收到网包时的相关数据由netdev_frame_hook()函数来处理,都是些辅助处理,依次调用各处理函数,在netdev_port_receive()【这里会进行数据包的拷贝,避免损坏】进入 ovs_vport_receive()回到vport.c,从 ovs_dp_process_receive_packet()回到 datapath.c,进行统一处理
  10. 流程:netdev_frame_hook()->netdev_port_receive->ovs_vport_receive->ovs_dp_process_received_packet()
  11. net_port_receive()首先检测是否 skb 被共享,若是则得到 packet 的拷贝。
  12. net_port_receive()其调用ovs_vport_receive(),检查包的校验和,然后交付给我们的vport通用层来处理。
    OVS原理

流表匹配

  1. flow_lookup()查找对应的流表项
  2. for 循环调用 rcu_dereference_ovs 对流表结构体中的 mask_list成员遍历,找到对应的的 成员
  3. flow=masked_flow_lookup()遍历进行下一级 hmap查找,找到为止
  4. 进入 包含函数ovs_flow_mask_key(&masked_key,unmasked,mask),将最开始提取的 Key 值和 mask 的key 值进行“与”操作,结果存放在 masked_key 中,用来得到后面的 Hash 值
  5. hash=flow_hash(&masked_key,key_start,key_end)key 值的匹配字段只有部分
  6. ovs_vport_add()函数会检查 vport 类型(通过 vport_ops_list[]数组),并调用相关的create()函数来生成 vport 结构
  7. 可见,当 dp 时网络设备时(vport_netdev.c),最终由ovs_vport_add()函数调用的是 netdev_create()【在 vport_ops_list的ovs_netdev_ops中】
  8. netdev_vport->dev 收到网包时的相关数据由netdev_frame_hook()函数来处理,都是些辅助处理,依次调用各处理函数,在netdev_port_receive()【这里会进行数据包的拷贝,避免损坏】进入 ovs_vport_receive()回到vport.c,从 ovs_dp_process_receive_packet()回到 datapath.c,进行统一处理
    OVS原理

收包处理

  1. ovs_vport_receive_packets()调用ovs_flow_extract基于skb生成key值,并检查是否有错,然后调用ovs_dp_process_packet。交付给datapath处理
  2. ovs_flow_tbl_lookup_stats。基于前面生成的key值进行流表查找,返回匹配的流表项,结构为sw_flow。
  3. 若不存在匹配,则调用ovs_dp_upcall上传至userspace进行匹配。 (包括包和key都要上传)
  4. 若存在匹配,则直接调用ovs_execute_actions执行对应的action,比如添加vlan头,转发到某个port等。
    OVS原理

upcall 消息处理

  1. ovs_dp_upcall()首先调用err=queue_userspace_packet()将信息排队发到用户空间去
  2. dp_ifindex=get_dpifindex(dp)获取网卡设备索引号
  3. 调整 VLAN的 MAC 地址头指针
  4. 网络链路属性,如果不需要填充则调用此函数
  5. len=upcall_msg_size(),获得 upcall 发送消息的大
  6. user_skb=genlmsg_new_unicast,创建一个新的 netlink 消息
  7. upcall=genlmsg_put()增加一个新的 netlink 消息到 skb
  8. err=genlmsg_unicast(),发送消息到用户空间去处理
    OVS原理
    相关代码:
/**
 * struct vport抽象的是datapath中的每个端口,
 */
struct vport {
	struct rcu_head rcu; //RCU callback head for deferred destruction.
	u16 port_no;		//端口号是dp中ports数组的索引;
	struct datapath	*dp; //这个端口所属的datapath;
	struct kobject kobj;  // Represents /sys/class/net/<devname>/brport
	char linkname[IFNAMSIZ]; 
	u32 upcall_portid;   //在这个端口收到的包如果匹配流表失败会通过这个netlink port传至用户空间;
 
	struct hlist_node hash_node; //  vport.c中的哈希表dev_table使用;
	struct hlist_node dp_hash_node; //是结构体datapath->ports中的构成元素,将所有vport连接起来;
	const struct vport_ops *ops;   //核心,定义vport的类型(能做的操作);
 
	struct vport_percpu_stats __percpu *percpu_stats;  //指向每个CPU的统计信息;
 
	spinlock_t stats_lock;      //自旋锁,保护下面俩字段的访问;
	struct vport_err_stats err_stats; //错误的统计信息;
	struct ovs_vport_stats offset_stats;  //过时了;
};

/**
 * struct vport_parms - parameters for creating a new vport
 * 端口参数,当创建一个新的vport端口是要传入的参数
 */
struct vport_parms {
	const char *name;
	enum ovs_vport_type type; //端口的类型
	struct nlattr *options; //保存从netlink msg中得到的属性
 
	/* For ovs_vport_alloc(). */
	struct datapath *dp; //端口属于哪个datapath(网桥)
	u16 port_no;   //端口号
	u32 upcall_portid;  //和用户空间通信的netlink 端口
};
 
/**
 * struct vport_ops -定义虚拟端口的类型(能做的操作)
 */
struct vport_ops {
	enum ovs_vport_type type; // 类型值,OVS_VPORT_TYPE_*;
	u32 flags;			// VPORT_F_*,影响通用虚拟端口层如何处理这个vport;
 
	/* Called at module init and exit respectively. */
	int (*init)(void);		// 模块初始化;如果设置了标识VPORT_F_REQUIRED,那么该函数执行失败后
							//停止模块加载,否则只是导致不创建这种类型的vport。
	void (*exit)(void);   //模块卸载之时;
 
	/* Called with RTNL lock. */
	struct vport *(*create)(const struct vport_parms *);
	//根据执行参数来创建一个新的vport,失败则返回对应的 ERR_PTR() 值;
	void (*destroy)(struct vport *);
	// 销毁这个vport,必须调用vport_free()释放(因为利用的是RCU,所以等到 RCU grace period之后实际执行) 
	
	int (*set_options)(struct vport *, struct nlattr *); //配置这个vport,如果不支持修改,就把该函数指针置为Null;
	int (*get_options)(const struct vport *, struct sk_buff *);//获得这个vport相关的配置属性到sk_buff中;
 
	int (*set_addr)(struct vport *, const unsigned char *);//设置MAC地址;
 
	/* Called with rcu_read_lock or RTNL lock. */
	const char *(*get_name)(const struct vport *); // 设备名
	const unsigned char *(*get_addr)(const struct vport *);
	void (*get_config)(const struct vport *, void *);
	struct kobject *(*get_kobj)(const struct vport *);//获得这个设备关联的kobj对象;
 
	unsigned (*get_dev_flags)(const struct vport *);//设备标志;
	int (*is_running)(const struct vport *);
	unsigned char (*get_operstate)(const struct vport *); //设备的工作状态
 
	int (*get_ifindex)(const struct vport *);//和这个设备关联的接口号(system interface index )
 
	int (*get_mtu)(const struct vport *);//设备的MTU,如果像tunnel这样的就没有MTU,返回null;
 
	int (*send)(struct vport *, struct sk_buff *); //在该设备上发送一个packet,返回发送的长度;
};
 
 
 
/* List of statically compiled vport implementations.  Don't forget to also
 * add yours to the list at the top of vport.c. */
extern const struct vport_ops ovs_netdev_vport_ops;
extern const struct vport_ops ovs_internal_vport_ops;
extern const struct vport_ops ovs_patch_vport_ops;

原文链接:

https://tonydeng.github.io/sdn-handbook/ovs/internal.html
https://www.kancloud.cn/digest/openvswitch/117245

相关标签: 云、虚拟化