OVS原理

OVS原理

OVS架構

在這裏插入圖片描述
ovs的架構如上圖所示,主要由內核datapath和用戶空間的vswitchd、ovsdb組成。
在這裏插入圖片描述

主要模塊職責

  1. datapath是負責數據交換的內核模塊,其從網口讀取數據,並快速匹配Flowtable中的流表項,成功的直接轉發,失敗的上交vswitchd處理。它在初始化和port binding的時候註冊鉤子函數,把端口的報文處理接管到內核模塊。
  2. vswitchd是一個守護進程,是ovs的管理和控制服務,通過unix socket將配置信息保存到ovsdb,並通過netlink和內核模塊交互
  3. ovsdb則是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通用層來處理。
    在這裏插入圖片描述

流表匹配

  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,進行統一處理
    在這裏插入圖片描述

收包處理

  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等。
    在這裏插入圖片描述

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(),發送消息到用戶空間去處理
    在這裏插入圖片描述
    相關代碼:
/**
 * 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章