/** - struct vport - one port within a datapath - @rcu: RCU callback head for deferred destruction. - @dp: Datapath to which this port belongs. - @upcall_portids: RCU protected 'struct vport_portids'. - @port_no: Index into @dp's @ports array. - @hash_node: Element in @dev_table hash table in vport.c. - @dp_hash_node: Element in @datapath->ports hash table in datapath.c. - @ops: Class structure. - @percpu_stats: Points to per-CPU statistics used and maintained by vport - @err_stats: Points to error statistics used and maintained by vport */ struct vport { struct rcu_head rcu; // 一種鎖機制 struct datapath *dp; // 網橋結構體指針,表示該端口是屬於哪一個網橋的 u32 upcall_portid; // Netlink端口收到的數據包時使用的端口id u16 port_no; // 端口號,惟一標識該端口 // 由於一個網橋上有多個端口,而這些端口都是用哈希鏈表來存儲的, // 因此這是鏈表元素(裏面沒有數據,只有next和prev前驅後繼指針,數據部分就是vport結構體中的其餘成員) struct hlist_node hash_node; struct hlist_node dp_hash_node; // 這是網橋的哈希鏈表元素 const struct vport_ops *ops; // 這是端口結構體的操做函數指針結構體,結構體裏面存放了不少操做函數的函數指針
struct pcpu_tstats __percpu *percpu_stats;// vport指向每一個cpu的統計數據使用和維護 spinlock_t stats_lock; // 自旋鎖,防止異步操做,保護下面的兩個成員 struct vport_err_stats err_stats; // 錯誤狀態(錯誤標識)指出錯誤vport使用和維護的統計數字 struct ovs_vport_stats offset_stats; // 添加到實際統計數據,部分緣由是爲了兼容 };
/** - struct vport_parms - parameters for creating a new vport * - @name: New vport's name. - @type: New vport's type. - @options: %OVS_VPORT_ATTR_OPTIONS attribute from Netlink message, %NULL if - none was supplied. - @dp: New vport's datapath. - @port_no: New vport's port number. */ struct vport_parms { const char *name; // 新端口的名字 enum ovs_vport_type type; // 新端口的類型(端口不只僅只有一種類型,後面會分析到) struct nlattr *options; // 這個沒怎麼用到過,好像是從Netlink消息中獲得的OVS_VPORT_ATTR_OPTIONS屬性 /* For ovs_vport_alloc(). */ struct datapath *dp; // 新的端口屬於哪一個網橋的 u16 port_no; // 新端口的端口號 u32 upcall_portid; // 和Netlink通訊時使用的端口id };
/** - struct vport_ops - definition of a type of virtual port * - @type: %OVS_VPORT_TYPE_* value for this type of virtual port. - @create: Create a new vport configured as specified. On success returns - a new vport allocated with ovs_vport_alloc(), otherwise an ERR_PTR() value. - @destroy: Destroys a vport. Must call vport_free() on the vport but not - before an RCU grace period has elapsed. - @set_options: Modify the configuration of an existing vport. May be %NULL - if modification is not supported. - @get_options: Appends vport-specific attributes for the configuration of an - existing vport to a &struct sk_buff. May be %NULL for a vport that does not - have any configuration. - @get_name: Get the device's name. - @send: Send a packet on the device. Returns the length of the packet sent, - zero for dropped packets or negative for error. - @get_egress_tun_info: Get the egress tunnel 5-tuple and other info for - a packet. */ struct vport_ops { enum ovs_vport_type type; // 端口的類型 // 新vport端口的建立函數和銷燬端口的函數 struct vport *(*create)(const struct vport_parms *); // 根據指定的參數配置建立個新的vport,成功返回新端口指針 void (*destroy)(struct vport *); // 銷燬端口函數 // 獲得和設置option成員函數 int (*set_options)(struct vport *, struct nlattr *); int (*get_options)(const struct vport *, struct sk_buff *); // 獲得端口名稱和配置以及發送數據包函數 const char *(*get_name)(const struct vport *); // int (*send)(struct vport *, struct sk_buff *); // 發送數據包到設備上 };
/* List of statically compiled vport implementations. Don't forget to also - add yours to the list at the bottom of vport.h. */ static const struct vport_ops *vport_ops_list[] = { &ovs_netdev_vport_ops, &ovs_internal_vport_ops, &ovs_geneve_vport_ops, #if IS_ENABLED(CONFIG_NET_IPGRE_DEMUX) &ovs_gre_vport_ops, &ovs_gre64_vport_ops, #endif &ovs_vxlan_vport_ops, &ovs_lisp_vport_ops, };
struct datapath { struct rcu_head rcu; // RCU調延遲破壞。 struct list_head list_node; // 網橋哈希鏈表元素,裏面只有next和prev前驅後繼指針,數據時該結構體其餘成員 /* Flow table. */ struct flow_table __rcu *table;// 這是哈希流表,裏面包含了哈希桶的地址指針。該哈希表受_rcu機制保護 /* Switch ports. */ struct hlist_head *ports;// 一個網橋有多個端口,這些端口都是用哈希鏈表來連接的 /* Stats. */ struct dp_stats_percpu __percpu *stats_percpu; #ifdef CONFIG_NET_NS /* Network namespace ref. */ struct net *net; #endif };
struct sw_flow_key { // 這是隧道相關的變量 struct ovs_key_ipv4_tunnel tun_key; /* Encapsulating tunnel key. */ struct { // 包的優先級 u32 priority; // 包的優先級 u32 skb_mark; // 包的mark值 u16 in_port; // 包進入的端口號 } phy; // 這是包的物理層信息結構體提取到的 struct { u8 src[ETH_ALEN]; // 源mac地址 u8 dst[ETH_ALEN]; // 目的mac地址 __be16 tci; // 這好像是局域網組號 __be16 type; // 包的類型,即:是IP包仍是ARP包 } eth; // 這是包的二層幀頭信息結構體提取到的 struct { u8 proto; // 協議類型 TCP:6;UDP:17;ARP類型用低8位表示 u8 tos; // 服務類型 u8 ttl; // 生存時間,通過多少跳路由 u8 frag; // 一種OVS中特有的OVS_FRAG_TYPE_*. } ip; // 這是包的三層IP頭信息結構體提取到的 // 下面是共用體,有IPV4和IPV6兩個結構,爲了後期使用IPV6適應 union { struct { struct { __be32 src; // 源IP地址 __be32 dst; // 目標IP地址 } addr; // IP中地址信息 // 這又是個共用體,有ARP包和TCP包(包含UDP)兩種 union { struct { __be16 src; // 源端口,應用層發送數據的端口 __be16 dst; // 目的端口,也是指應用層傳輸數據端口 } tp; // TCP(包含UDP)地址提取 struct { u8 sha[ETH_ALEN]; // ARP頭中源Mac地址 u8 tha[ETH_ALEN]; // ARP頭中目的Mac地址 } arp;ARP頭結構地址提取 }; } ipv4; // 下面是IPV6的相關信息,基本和IPV4相似,這裏不講 struct { struct { struct in6_addr src; /* IPv6 source address. */ struct in6_addr dst; /* IPv6 destination address. */ } addr; __be32 label; /* IPv6 flow label. */ struct { __be16 src; /* TCP/UDP source port. */ __be16 dst; /* TCP/UDP destination port. */ } tp; 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 flow_table { struct flex_array *buckets; //哈希桶地址指針 unsigned int count, n_buckets; // 哈希桶個數 struct rcu_head rcu; // rcu包含機制 struct list_head *mask_list; // struct sw_flow_mask鏈表頭指針 int node_ver; u32 hash_seed; //哈希算法須要的種子,後期匹配時要用到 bool keep_flows; //是否保留流表項 };
};html
struct sw_flow { struct rcu_head rcu; // rcu保護機制 struct hlist_node hash_node[2]; // 兩個節點指針,用來連接做用,前驅後繼指針 u32 hash; // hash值 struct sw_flow_key key; // 流表中的key值 struct sw_flow_key unmasked_key; // 也是流表中的key struct sw_flow_mask *mask; // 要匹配的mask結構體 struct sw_flow_actions __rcu *sf_acts; // 相應的action動做 spinlock_t lock; // 保護機制自旋鎖 unsigned long used; // 最後使用的時間 u64 packet_count; // 匹配過的數據包數量 u64 byte_count; // 匹配字節長度 u8 tcp_flags; // TCP標識 };
struct sw_flow_mask { int ref_count; struct rcu_head rcu; struct list_head list;// mask鏈表元素,由於mask結構是個雙鏈表結構體 struct sw_flow_key_range range;// 操做範圍結構體,由於key值中有些數據時不要用來匹配的 struct sw_flow_key key;// 要和數據包操做的key,將要被用來匹配的key值 };
datapath爲 ovs內核模塊,負責執行數據交換,也就是把從接收端口收到的數據包在流表中進行匹配,並執行匹配到的動做。node
一個datapath能夠對應多個vport,一個vport相似物理交換機的端口概念。一個datapth關聯一個flow table,一個flow table包含多個條目,每一個條目包括兩個內容:一個match/key和一個action
linux
static int __init dp_init(void) { int err; BUILD_BUG_ON(sizeof(struct ovs_skb_cb) > FIELD_SIZEOF(struct sk_buff, cb)); pr_info("Open vSwitch switching datapath %s, built "__DATE__" "__TIME__"\n", VERSION); err = ovs_flow_init();//申請 flow_cache和 flow_stats_cache if (err) goto error; err = ovs_vport_init();//vport 數據結構初始化,申請 dev_table if (err) goto error_flow_exit; err = register_pernet_device(&ovs_net_ops);//註冊網絡名字空間設備 if (err) goto error_vport_exit; err = register_netdevice_notifier(&ovs_dp_device_notifier);//註冊設備通知事件 if (err) goto error_netns_exit; err = dp_register_genl();//dp_register_genl 初始化 dp 相關的 netlink 的 family和ops if (err < 0) goto error_unreg_notifier; return 0; error_unreg_notifier: unregister_netdevice_notifier(&ovs_dp_device_notifier); error_netns_exit: unregister_pernet_device(&ovs_net_ops); error_vport_exit: ovs_vport_exit(); error_flow_exit: ovs_flow_exit(); error: return err; }
設置程序名稱、版本、編譯日期等信息算法
複製出輸入的參數列表到新的存儲中,讓argv指向這塊內存【主要是爲了後面的proctitle_set()函數準備】數據庫
註冊回調和服務管理器出現故障錯誤時操做的配置api
解析參數,其中unixctl_path存儲unixctrl域的sock名,做爲接受外部控制命令的渠道;而remote存儲鏈接到ovsdb的信息,即鏈接到配置數據庫的sock名數組
數據表結構初始化,包括13張數據表安全
若是系統守護進程被配置了,啓動系統守護進程,經過派生和在返回的子進程。父進程徘徊,直到
讓子進程知道它完成啓動成功(經過調用daemon_complete()),或者它沒有啓動(用非零退出
退出代碼。服務器
建立一個unixctl_server(存放在unixctl),並監聽在unixctl_path指定的punix路徑,該路徑做爲ovs-appctl發送命令給ovsd的通道網絡
註冊unixctl命令
從remote數據庫獲取配置信息,並初始化bridge
運行內存監視器,客戶端調用memory_should_report()。此函數以及該模塊的接口的剩餘部分,僅被一個線程調用。
主要對網包進行完整處理過程。包括完成必要的配置更新【在配置更新中會從數據庫中讀取配置信息,生成必要的bridge和dp等數據結構】
處理了一批從'IDL'數據庫服務器的消息。這可能會致使IDL的內容發生變化。客戶端能夠檢查與ovsdb_idl_get_seqno()。
由於咱們不運行system_stats的run()在這個進程中有多個OVS-vswitchd守護進程的現狀,關閉系統自動統計信息收集。
初始化ofproto庫。這僅須要執行一次,但配置設置以後它必需要作的。若是已經出現了初始化,bridge_init_ofproto()當即返回。
可選調用較多,自行查看
以 PACKET_OUT消息爲例,調用的是handle_packout 函數
首先調用ofputil_decode_packet_out()對of消息進行解析 調用ofconn_pktbuf_retrieve()獲取payload信息 利用ofproto_class->packet_out()將網包發出
packet_out() { ofproto_dpif_execute_actions() { dpif_flow_stats_extract() 流狀態提取 xlate_actions()將ofpacts轉化爲dp的行動格式odp_actions 調用dpif_execute()函數讓dpif執行給定的action構建OVS_PACKET_CMD_EXECUTE netlink消息併發給datapath datapath中將對應調用ovs_packet_cmd_execute函數處理收到的nlmsg ovs_packet_cmd_execute的調用過程 ovs_packet_cmd_execute()->ovs_execute_actions()->do_execute_actions() } }
經過主循環每一次遍歷,而不是隻當數據庫的變化,由於密鑰和證書文件的內容能夠更改在數據庫不更改中。咱們完成這些在bridge_reconfigure()以前,由於該功能可能會啓動SSL鏈接以前作到這一點,所以須要SSL進行配置。
若是打開了一些netted,則執行對應在netdev_classes上定義的每一個netdev_class實體,調用它們的run()包括處理網卡註冊的各個通知事件,獲取網卡的最新的信息等
從unixctl指定的server中獲取來自ovs-appctl發出的命令數據,並執行對應的命令
包括memory、bridge、unixctl_server、netted等事件,被poll_fd_wait()註冊的最短期
阻塞知道以前被poll_fd_wait()註冊過的事件發生,或者等待時間超過poll_timer_wait()註冊的最短期
退出bridge,關閉unixctl鏈接
通常的數據包在 Linux網絡協議中的流向爲黑色箭頭流向:網卡收到數據包後層層網上分析,最後離開內核態,把數據傳送到用戶態。
有 OVS時:數據流流向不一樣
(1)創網橋(ovs-vsctl add-br br0)
(2)綁網卡(ovs-vsctl add-port bro eth0 默認爲 eth0)
數據流:
從網卡 eth0到 ovs 的 vport 進入OVS,根據 key值流表匹配
成功——>執行流表 action
失敗——>upcall處理
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通用層來處理。
netdev_rx_handler_register()
linux 內核實現的一個函數,爲網絡設備 dev 註冊一個handler_frame_hook,rx_handle_data 指向的是handler_frame_hook 內存的區域,這個 handler 之後會被__netif_receive_skb()呼叫,就是說netdev_rx_handler_register(netdev_vport->dev,netdev_frame_hook,vport);在收到packet 後會調用 netdev_frame_hook 函數處理
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都要上傳) 若存在匹配,則直接調用ovs_execute_actions執行對應的action,好比添加vlan頭,轉發到某個port等。
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_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(),發送消息到用戶空間去處理
RCU是linux的新型鎖機制(RCU是在linux 2.6內核版本中開始正式使用)
讀鎖(共享鎖):若請求是讀數據時,上讀鎖,多個讀鎖不排斥(即訪問數據的讀者上限未達到時,能夠對數據區再上讀鎖),若請求是寫數據時,不能立刻上寫鎖,得等數據區的全部鎖(包括讀鎖和寫鎖)都釋放才能上寫鎖寫鎖(獨佔鎖):要操做的數據區上了寫鎖,無論什麼請求都要等到數據區的寫鎖釋放掉後才能上鎖訪問
寫數據:(1)不需讀寫鎖那樣等待全部鎖釋放【拷貝一份數據區的副本,在副本中修改,修改完後,用副本替代原來的數據區】(2)替換的時候須要讀寫鎖上寫鎖那樣,等到數據區上全部訪問者退出後,才進行數據的替換
(3)RCU鎖能夠有多個寫者,拷貝多份數據區數據,修改後,各個數據區陸續替換掉原數據區內容
讀數據:不用上任何鎖,幾乎不須要等待(讀寫鎖須要等寫鎖釋放)就能夠直接訪問數據
,「幾乎」,由於寫數據中替換原數據,只需修改個指針,消耗的時間幾乎不算
• 容許多個讀者和多個寫者同時訪問共享數據區內容
• 對多讀少寫的數據來講很是高效,能夠減小 CPU 開銷
• 寫數據操做多了,就不如讀寫鎖那麼好了,由於RCU 對寫數據開銷大,須要拷貝數據,修改,等待替換
rcu_read_lock();
• 這不是和上讀寫鎖的那種上鎖,這僅僅只是標識了臨界區的開始位置。代表在臨界區內不能阻塞和休眠,也不能讓寫者進行數據的替換(其實這功能遠不止這些)。rcu _read_unlock()則是和上面rcu_read_lock()對應的,用來界定一個臨界區(就是要用鎖保護起來的數據區)。
synchronize_rcu();
• 當該函數被一個CPU調用時(通常是有寫者替換數據時調用),而其餘的CPU都在RCU保護的臨界區讀數據,那麼synchronize_rcu()將會保證阻塞寫者,直到全部其它讀數據的CPU都退出臨界區時,才停止阻塞,讓寫着開始替換數據。該函數做用就是保證在替換數據前,全部讀數據的CPU可以安全的退出臨界區。一樣,還有個call_rcu()函數功能也是相似的。若是call_rcu()被一個CPU調用,而其餘的CPU都在RCU保護的臨界區內讀數據,相應的RCU回調的調用將被推遲到其餘讀臨界區數據的CPU所有安全退出後才執行(能夠看linux內核源文件的註釋,在Rcupdate.h文件中rcu_read_look()函數前面的註釋)。
rcu_dereference();
• 獲取在一個RCU保護的指針,指向RCU讀端臨界區。他的指針之後可能會被安全地解除引用。說到底就是一個RCU保護指針。
list_add_rcu();
• 往RCU保護的數據結構中添加一個數據節點進去。這個和通常的往鏈表中增長一個節點操做是相似的,惟一不一樣的是多了這條代碼:rcu_assign_pointer(prev->next, new); 代碼大概含義:分配指向一個新初始化的結構指針,將由RCU讀端臨界區被解除引用,返回指定的值。
list_for_each_entry_rcu();
• 這是個遍歷RCU鏈表的操做,和通常的鏈表遍歷差很少。不一樣點就是必需要進入RCU保護的CPU(即:調用了rcu_read_lock()函數的CPU)才能調用這個操做,能夠和其餘CPU共同遍歷這個RCU鏈表。
+---------------------+ +---------------------+ | (3) application "A" | | (3) application "B" | +------+--------------+ +--------------+------+ | | \ / \ / | | +-------+--------------------------------+-------+ | : : | user-space =====+ : (5) Kernel socket API : +================ | : : | kernel-space +--------+-------------------------------+-------+ | | +-----+-------------------------------+----+ | (1) Netlink subsystem | +---------------------+--------------------+ | +---------------------+--------------------+ | (2) Generic Netlink bus | +--+--------------------------+-------+----+ | | | +-------+---------+ | | | (4) Controller | / \ +-----------------+ / \ | | +------------------+--+ +--+------------------+ | (3) kernel user "X" | | (3) kernel user "Y" | +---------------------+ +---------------------+
(5)API向用戶空間和內核空間分別提供接口。
Netlink子系統(1)是全部genl通訊的基礎。Netlink子系統中收到的全部Generic類型的netlink數據都被送到genl總線(2)上;從內核發出的數據也經由genl總線送至netlink子系統,再打包送至用戶空間。
Generic Netlink控制器(4)做爲內核的一部分,負責動態地分配genl通道(即genl family id),並管理genl任務。genl控制器是一個特殊的genl內核用戶,它負責監聽genl bus上的通訊通道。genl通訊創建在一系列的通訊通道的基礎上,每一個genl family對應多個通道,這些通道由genl控制器動態分配。
Generic Netlink是基於客戶端-服務端模型的通訊機制。服務端註冊family(family是對genl服務的各項定義的集合)。控制器和客戶端都經過已註冊的信息與服務端通訊。
genl family的結構體以下:
struct genl_family { unsigned int id; unsigned int hdrsize; char name[GENL_NAMSIZ]; unsigned int version; unsigned int maxattr; struct nlattr ** attrbuf; struct list_head ops_list; struct list_head family_list; };
以上的三個字段爲私有字段,由系統自動配置,開發者不須要作配置。
struct genl_ops { u8 cmd; unsigned int flags; struct nla_policy *policy; int (*doit)(struct sk_buff *skb, struct genl_info *info); int (*dumpit)(struct sk_buff *skb, struct netlink_callback *cb); struct list_head ops_list; };
cmd: 命令名。用於識別各genl_ops
flag: 各類設置屬性,以「或」鏈接。在須要admin特權級別時,使用GENL_ADMIN_PERM
policy:定義了attr規則。若是此指針非空,genl在觸發事件處理程序以前,會使用這個字段來對幀中的attr作校驗(見nlmsg_parse函數)。該字段能夠爲空,表示在觸發事件處理程序以前,不作校驗。
doit:這是一個回調函數。在generic netlink收到數據時觸發,運行在進程上下文。
doit傳入兩個參數,skb爲觸發此回調函數的socket buffer。第二個參數是一個genl_info結構體
struct genl_info { u32 snd_seq; u32 snd_pid; struct nlmsghdr * nlhdr; struct genlmsghdr * genlhdr; void * userhdr; struct nlattr ** attrs; };
dumpit
這是一個回調函數,當genl_ops的flag標誌被添加了NLM_F_DUMP之後,每次收到genl消息即會回觸發這個函數。dumpit與doit的區別是:dumpit的第一個參數skb不會攜帶從客戶端發來的數據。相反地,開發者應該在skb中填入須要傳給客戶端的數據,而後,並skb的數據長度(能夠用skb->len)return。skb中攜帶的數據會被自動送到客戶端。只要dumpit的返回值大於0,dumpit函數就會再次被調用,並被要求在skb中填入數據。當服務端沒有數據要傳給客戶端時,dumpit要返回0。若是函數中出錯,要求返回一個負值。關於doit和dumpit的觸發過程,能夠查看源碼中的genl_rcv_msg函數。
ops_list
爲私有字段,由系統自動配置,開發者不須要作配置。
初始化Generic Netlink的過程分爲如下四步:定義family,定義operation,註冊family,註冊operation。
Datapath使用 generic netlink
在 dp_init()函數(datapath.c)中,調用 dp_register_genl()完成對四種類型的 family 以 及相應操做的註冊,包括 datapath、vport、flow 和 packet。前三種 family,都對應四種操 做都包括 NEW、DEL、GET、SET,而 packet 的操做僅爲 EXECUTE。
這些 family 和操做的定義均在 datapath.c 中。 以 flow family 爲例。代碼爲
static const struct nla_policy flow_policy[OVS_FLOW_ATTR_MAX + 1] = { [OVS_FLOW_ATTR_KEY] = { .type = NLA_NESTED }, [OVS_FLOW_ATTR_ACTIONS] = { .type = NLA_NESTED }, [OVS_FLOW_ATTR_CLEAR] = { .type = NLA_FLAG }, }; static struct genl_family dp_flow_genl_family = { .id = GENL_ID_GENERATE, .hdrsize = sizeof(struct ovs_header), .name = OVS_FLOW_FAMILY, .version = OVS_FLOW_VERSION, .maxattr = OVS_FLOW_ATTR_MAX, SET_NETNSOK };
綁定的 ops 的定義
static struct genl_ops dp_flow_genl_ops[] = { { .cmd = OVS_FLOW_CMD_NEW, .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ .policy = flow_policy, .doit = ovs_flow_cmd_new_or_set }, { .cmd = OVS_FLOW_CMD_DEL, .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ .policy = flow_policy, .doit = ovs_flow_cmd_del }, { .cmd = OVS_FLOW_CMD_GET, .flags = 0, /* OK for unprivileged users. */ .policy = flow_policy, .doit = ovs_flow_cmd_get, .dumpit = ovs_flow_cmd_dump }, { .cmd = OVS_FLOW_CMD_SET, .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ .policy = flow_policy, .doit = ovs_flow_cmd_new_or_set, }, };
ovsd 使用 netlink
ovsd 對於 netlink 的實現,主要在 lib/netlink-socket.c 文件中。而對這些 netlink 操做的 調用,主要在 lib/dpif-linux.c 文件(以 dpif_linux_class 爲例)中對於各個行爲的處理,各 種可能的消息類型在 datapath 模塊中事先進行了內核註冊。
datapath 中對 netlink family 類型進行了註冊,ovsd 在使用這些 netlink family 以前須要 獲取它們的信息,這一過程主要在 lib/dpif-linux.c 文件(以 dpif_linux_class 爲例), dpif_linux_init()函數。代碼爲
static int dpif_linux_init(void) { static int error = -1; if (error < 0) { unsigned int ovs_vport_mcgroup; error = nl_lookup_genl_family(OVS_DATAPATH_FAMILY,&ovs_datapath_family); if (error) { VLOG_ERR("Generic Netlink family '%s' does not exist. ""The Open vSwitch kernel module is probably not loaded.",OVS_DATAPATH_FAMILY); } if (!error) { error = nl_lookup_genl_family(OVS_VPORT_FAMILY, &ovs_vport_family);} if (!error) { error = nl_lookup_genl_family(OVS_FLOW_FAMILY, &ovs_flow_family); } if (!error) { error = nl_lookup_genl_family(OVS_PACKET_FAMILY,&ovs_packet_family);} if (!error) { error = nl_sock_create(NETLINK_GENERIC, &genl_sock); } if (!error) { error = nl_lookup_genl_mcgroup(OVS_VPORT_FAMILY, OVS_VPORT_MCGROUP,&ovs_vport_mcgroup, OVS_VPORT_MCGROUP_FALLBACK_ID);} if (!error) { static struct dpif_linux_vport vport; nln = nln_create(NETLINK_GENERIC, ovs_vport_mcgroup, dpif_linux_nln_parse, &vport);} } return error; }
完成這些查找後,ovsd 便可利用 dpif 中的 api,經過發出這些 netlink 消息給 datapath 實現對 datapath 的操做。
相關的中間層 API 定義主要在 dpif_class(位於 lib/dpif-provider.h)的抽象類型中
dpif_class結構體的註釋:
/* Datapath interface class structure, to be defined by each implementation of -a datapath interface. * - These functions return 0 if successful or a positive errno value on failure, - except where otherwise noted. * - These functions are expected to execute synchronously, that is, to block as - necessary to obtain a result. Thus, they may not return EAGAIN or - EWOULDBLOCK or EINPROGRESS. We may relax this requirement in the future if - and when we encounter performance problems. */
一共有兩種dpif_class實例化類型,分別爲dpif_netlink_class和dpif_netdev_class。dpif_netlink_class表示的是經過netlink和本地的datapath通訊,而dpif_netdev_class經過網絡協議和遠程的datapath通訊
ovsd使用netlink進行消息發送的過程:
庾志輝OVS 專欄
http://blog.csdn.net/column/d...
datapath 模塊分析
http://vinllen.com/ovs-datapa...
Baohua Yang的OpenvSwitch 代碼分析
OpenvSwitch2.4.0源碼解讀
http://www.cnblogs.com/cotyb/...
GenerRic Netlink 詳解
http://www.tuicool.com/articl...