OVS原理
OVS架構
ovs的架構如上圖所示,主要由內核datapath和用戶空間的vswitchd、ovsdb組成。
主要模塊職責
- datapath是負責數據交換的內核模塊,其從網口讀取數據,並快速匹配Flowtable中的流表項,成功的直接轉發,失敗的上交vswitchd處理。它在初始化和port binding的時候註冊鉤子函數,把端口的報文處理接管到內核模塊。
- vswitchd是一個守護進程,是ovs的管理和控制服務,通過unix socket將配置信息保存到ovsdb,並通過netlink和內核模塊交互
- ovsdb則是ovs的數據庫,保存了ovs配置信息
主要數據結構
主要流程
添加網橋
- 鍵入命令ovs-vsctl add-br testBR
- 內核中的 openvswitch.ko 收到一個添加網橋的命令時候——即收到OVS_DATAPATH_FAMILY通道的 OVS_DP_CMD_NEW命令。該命令綁定的回調函數爲 ovs_dp_cmd_new
- ovs_dp_cmd_new 函數除了初始化 dp 結構外,調用 new_vport 函數來生成新的 vport
- new_vport 函數調用 ovs_vport_add()來嘗試生成一個新的 vport
- ovs_vport_add()函數會檢查 vport 類型(通過vport_ops_list[]數組),並調用相關的 create()函數來生成 vport 結構
- 當dp是網絡設備時(vport_netdev.c),最終由 ovs_vport_add()函數調用的是netdev_create()【在 vport_ops_list的ovs_netdev_ops 中】
- netdev_create()函數最關鍵的一步是註冊了收到網包時的回調函數
- err=netdev_rx_handler_register(netdev_vport->dev,netdev_frame_hook,vport);
- 操作是將 netdev_vport->dev 收到網包時的相關數據由netdev_frame_hook()函數來處理,都是些輔助處理,依次調用各處理函數,在netdev_port_receive()【這裏會進行數據包的拷貝,避免損壞】進入 ovs_vport_receive()回到vport.c,從 ovs_dp_process_receive_packet()回到 datapath.c,進行統一處理
- 流程:netdev_frame_hook()->netdev_port_receive->ovs_vport_receive->ovs_dp_process_received_packet()
- net_port_receive()首先檢測是否 skb 被共享,若是則得到 packet 的拷貝。
- net_port_receive()其調用ovs_vport_receive(),檢查包的校驗和,然後交付給我們的vport通用層來處理。
流表匹配
- flow_lookup()查找對應的流表項
- for 循環調用 rcu_dereference_ovs 對流表結構體中的 mask_list成員遍歷,找到對應的的 成員
- flow=masked_flow_lookup()遍歷進行下一級 hmap查找,找到爲止
- 進入 包含函數ovs_flow_mask_key(&masked_key,unmasked,mask),將最開始提取的 Key 值和 mask 的key 值進行“與”操作,結果存放在 masked_key 中,用來得到後面的 Hash 值
- hash=flow_hash(&masked_key,key_start,key_end)key 值的匹配字段只有部分
- ovs_vport_add()函數會檢查 vport 類型(通過 vport_ops_list[]數組),並調用相關的create()函數來生成 vport 結構
- 可見,當 dp 時網絡設備時(vport_netdev.c),最終由ovs_vport_add()函數調用的是 netdev_create()【在 vport_ops_list的ovs_netdev_ops中】
- netdev_vport->dev 收到網包時的相關數據由netdev_frame_hook()函數來處理,都是些輔助處理,依次調用各處理函數,在netdev_port_receive()【這裏會進行數據包的拷貝,避免損壞】進入 ovs_vport_receive()回到vport.c,從 ovs_dp_process_receive_packet()回到 datapath.c,進行統一處理
收包處理
- ovs_vport_receive_packets()調用ovs_flow_extract基於skb生成key值,並檢查是否有錯,然後調用ovs_dp_process_packet。交付給datapath處理
- ovs_flow_tbl_lookup_stats。基於前面生成的key值進行流表查找,返回匹配的流表項,結構爲sw_flow。
- 若不存在匹配,則調用ovs_dp_upcall上傳至userspace進行匹配。 (包括包和key都要上傳)
- 若存在匹配,則直接調用ovs_execute_actions執行對應的action,比如添加vlan頭,轉發到某個port等。
upcall 消息處理
- ovs_dp_upcall()首先調用err=queue_userspace_packet()將信息排隊發到用戶空間去
- dp_ifindex=get_dpifindex(dp)獲取網卡設備索引號
- 調整 VLAN的 MAC 地址頭指針
- 網絡鏈路屬性,如果不需要填充則調用此函數
- len=upcall_msg_size(),獲得 upcall 發送消息的大
- user_skb=genlmsg_new_unicast,創建一個新的 netlink 消息
- upcall=genlmsg_put()增加一個新的 netlink 消息到 skb
- 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