从Openvswitch代码看网络包的旅程
我們知道,Openvwitch可以創(chuàng)建虛擬交換機(jī),而網(wǎng)絡(luò)包可以通過虛擬交換機(jī)進(jìn)行轉(zhuǎn)發(fā),并通過流表進(jìn)行處理,具體的過程如何呢?
?
一、內(nèi)核模塊Openvswitch.ko的加載
?
OVS是內(nèi)核態(tài)和用戶態(tài)配合工作的,所以首先要加載內(nèi)核態(tài)模塊Openvswitch.ko。
?
在datapath/datapath.c中會調(diào)用module_init(dp_init);來初始化內(nèi)核模塊。
?
其中比較重要的是調(diào)用了dp_register_genl(),這個就是注冊netlink函數(shù),從而用戶態(tài)進(jìn)程ovs-vswitchd可以通過netlink調(diào)用內(nèi)核。
?
這里dp_genl_families由四個netlink的family組成
?
static?struct?genl_family *dp_genl_families[] = {
???&dp_datapath_genl_family,
???&dp_vport_genl_family,
???&dp_flow_genl_family,
???&dp_packet_genl_family,
};
可以看出,在內(nèi)核中,包含對datapath的操作,例如OVS_DP_CMD_NEW,對虛擬端口vport的操作,例如OVS_VPORT_CMD_NEW,對flow流表的操作,例如OVS_FLOW_CMD_NEW,對packet包的操作,例如OVS_PACKET_CMD_EXECUTE。
?
二、用戶態(tài)進(jìn)程ovs-vswitchd的啟動
?
ovs-vswitchd.c的main函數(shù)最終會進(jìn)入一個while循環(huán),在這個無限循環(huán)中,里面最重要的兩個函數(shù)是bridge_run()和netdev_run()。
?
Openvswitch主要管理兩種類型的設(shè)備,一個是創(chuàng)建的虛擬網(wǎng)橋,一個是連接到虛擬網(wǎng)橋上的設(shè)備。
?
其中bridge_run就是初始化數(shù)據(jù)庫中已經(jīng)創(chuàng)建的虛擬網(wǎng)橋。
?
虛擬網(wǎng)卡的初始化則靠netdev_run()。
?
bridge_run會調(diào)用static void bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg),其中ovs_cfg是從ovsdb-server里面讀取出來的配置。
?
在這個函數(shù)里面,對于每一個網(wǎng)橋,將網(wǎng)卡添加進(jìn)去。
?
HMAP_FOR_EACH (br, node, &all_bridges) {
????bridge_add_ports(br, &br->wanted_ports);
????shash_destroy(&br->wanted_ports);
}
?
最終會調(diào)用dpif_netlink_port_add__在這個函數(shù)里面,會調(diào)用netlink的API,命令為OVS_VPORT_CMD_NEW。
?
三、內(nèi)核模塊監(jiān)聽網(wǎng)卡
?
ovs-vswitchd啟動的時候,將虛擬網(wǎng)卡添加到虛擬交換機(jī)上的時候,會調(diào)用netlink的OVS_VPORT_CMD_NEW命令,因而會調(diào)用函數(shù)ovs_vport_cmd_new。
?
它最終會調(diào)用ovs_netdev_link,其中有下面的代碼:
?
err = netdev_rx_handler_register(vport->dev, netdev_frame_hook,
????????????????vport);
?
注冊一個方法叫做netdev_frame_hook,每當(dāng)網(wǎng)卡收到包的時候,就調(diào)用這個方法。
?
四、內(nèi)核態(tài)網(wǎng)絡(luò)包處理
?
Openvswitch的內(nèi)核模塊openvswitch.ko會在網(wǎng)卡上注冊一個函數(shù)netdev_frame_hook,每當(dāng)有網(wǎng)絡(luò)包到達(dá)網(wǎng)卡的時候,這個函數(shù)就會被調(diào)用。
?
static?struct?sk_buff *netdev_frame_hook(struct?sk_buff *skb)
{
???if?(unlikely(skb->pkt_type == PACKET_LOOPBACK))
??????return?skb;
?
???port_receive(skb);
???return?NULL;
}
?
調(diào)用port_receive即是調(diào)用netdev_port_receive
?
在這個函數(shù)里面,首先聲明了變量struct sw_flow_key key;
如果我們看這個key的定義,可見這個key里面是一個大雜燴,數(shù)據(jù)包里面的幾乎任何部分都可以作為key來查找flow表
-
tunnel可以作為key
-
在物理層,in_port即包進(jìn)入的網(wǎng)口的ID
-
在MAC層,源和目的MAC地址
-
在IP層,源和目的IP地址
-
在傳輸層,源和目的端口號
-
IPV6
所以,要在內(nèi)核態(tài)匹配流表,首先需要調(diào)用ovs_flow_key_extract,從包的正文中提取key的值。
?
接下來就是要調(diào)用ovs_dp_process_packet了。
?
這個函數(shù)首先在內(nèi)核里面的流表中查找符合key的flow,也即ovs_flow_tbl_lookup_stats,如果找到了,很好說明用戶態(tài)的流表已經(jīng)放入內(nèi)核,則走fast path就可了。于是直接調(diào)用ovs_execute_actions,執(zhí)行這個key對應(yīng)的action。
?
如果不能找到,則只好調(diào)用ovs_dp_upcall,讓用戶態(tài)去查找流表。會調(diào)用static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, const struct sw_flow_key *key, const struct dp_upcall_info *upcall_info)
?
它會調(diào)用err = genlmsg_unicast(ovs_dp_get_net(dp), user_skb, upcall_info->portid);通過netlink將消息發(fā)送給用戶態(tài)。在用戶態(tài),有線程監(jiān)聽消息,一旦有消息,則觸發(fā)udpif_upcall_handler。
?
Slow Path & Fast Path
?
?
?
Slow Path:
當(dāng)Datapath找不到flow rule對packet進(jìn)行處理時
Vswitchd使用flow rule對packet進(jìn)行處理。
?
Fast Path:
將slow path的flow rule放在內(nèi)核態(tài),對packet進(jìn)行處理
?
Unknown Packet Processing
Datapath使用flow rule對packet進(jìn)行處理,如果沒有,則有vswitchd使用flow rule進(jìn)行處理
?
?
從Device接收Packet交給事先注冊的event handler進(jìn)行處理
接收Packet后識別是否是unknown packet,是則交由upcall處理
vswitchd對unknown packet找到flow rule進(jìn)行處理
將Flow rule發(fā)送給datapath
?
五、用戶態(tài)處理包
?
當(dāng)內(nèi)核無法查找到流表項(xiàng)的時候,則會通過upcall來調(diào)用用戶態(tài)ovs-vswtichd中的flow table。
?
會調(diào)用ofproto-dpif-upcall.c中的udpif_upcall_handler函數(shù)。
?
(1) 首先讀取upcall調(diào)用static int upcall_receive(struct upcall *upcall, const struct dpif_backer *backer, const struct dp_packet *packet, enum dpif_upcall_type type, const struct nlattr *userdata, const struct flow *flow, const unsigned int mru, const ovs_u128 *ufid, const unsigned pmd_id)
?
(2) 其次提取包頭調(diào)用void flow_extract(struct dp_packet *packet, struct flow *flow),提取出的flow如下:
?
? ??/* L2, Order the same as in the Ethernet header! (64-bit aligned) */
????struct?eth_addr dl_dst;?/* Ethernet destination address. */
????struct?eth_addr dl_src;?/* Ethernet source address. */
????ovs_be16 dl_type;?/* Ethernet frame type. */
????ovs_be16 vlan_tci;?/* If 802.1Q, TCI | VLAN_CFI; otherwise 0. */
????ovs_be32 mpls_lse[ROUND_UP(FLOW_MAX_MPLS_LABELS, 2)];?/* MPLS label stack
?????????????????????????????????????????????????????????????(with padding). */
????/* L3 (64-bit aligned) */
????ovs_be32 nw_src;?/* IPv4 source address. */
????ovs_be32 nw_dst;?/* IPv4 destination address. */
????struct?in6_addr ipv6_src;?/* IPv6 source address. */
????struct?in6_addr ipv6_dst;?/* IPv6 destination address. */
????ovs_be32 ipv6_label;?/* IPv6 flow label. */
????uint8_t nw_frag;?/* FLOW_FRAG_* flags. */
????uint8_t nw_tos;?/* IP ToS (including DSCP and ECN). */
????uint8_t nw_ttl;?/* IP TTL/Hop Limit. */
????uint8_t nw_proto;?/* IP protocol or low 8 bits of ARP opcode. */
????struct?in6_addr nd_target;?/* IPv6 neighbor discovery (ND) target. */
????struct?eth_addr arp_sha;?/* ARP/ND source hardware address. */
????struct?eth_addr arp_tha;?/* ARP/ND target hardware address. */
????ovs_be16 tcp_flags;?/* TCP flags. With L3 to avoid matching L4. */
????ovs_be16 pad3;?/* Pad to 64 bits. */
?
????/* L4 (64-bit aligned) */
????ovs_be16 tp_src;?/* TCP/UDP/SCTP source port/ICMP type. */
????ovs_be16 tp_dst;?/* TCP/UDP/SCTP destination port/ICMP code. */
????ovs_be32 igmp_group_ip4;?/* IGMP group IPv4 address.
?????????????????????????????????* Keep last for BUILD_ASSERT_DECL below. */
?
?
(3) 然后調(diào)用static int process_upcall(struct udpif *udpif, struct upcall *upcall, struct ofpbuf *odp_actions, struct flow_wildcards *wc)來處理upcall。
?
對于MISS_UPCALL,調(diào)用static void upcall_xlate(struct udpif *udpif, struct upcall *upcall, struct ofpbuf *odp_actions, struct flow_wildcards *wc)
?
會調(diào)用enum xlate_error xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
在這個函數(shù)里面,會在flow table里面查找rule
ctx.rule = rule_dpif_lookup_from_table( ctx.xbridge->ofproto, ctx.tables_version, flow, xin->wc, ctx.xin->resubmit_stats, &ctx.table_id, flow->in_port.ofp_port, true, true);
找到rule之后,調(diào)用static void do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len, struct xlate_ctx *ctx)在這個函數(shù)里面,根據(jù)action的不同,修改flow的內(nèi)容。
?
(4) 最后調(diào)用static void handle_upcalls(struct udpif *udpif, struct upcall *upcalls, size_t n_upcalls)將flow rule添加到內(nèi)核中的datapath
他會調(diào)用void dpif_operate(struct dpif *dpif, struct dpif_op **ops, size_t n_ops),他會調(diào)用dpif->dpif_class->operate(dpif, ops, chunk);
?
會調(diào)用dpif_netlink_operate()
?
會調(diào)用netlink修改內(nèi)核中datapath的規(guī)則。
?
case?DPIF_OP_FLOW_PUT:
????put = &op->u.flow_put;
????dpif_netlink_init_flow_put(dpif, put, &flow);
????if?(put->stats) {
????????flow.nlmsg_flags |= NLM_F_ECHO;
????????aux->txn.reply = &aux->reply;
????}
????dpif_netlink_flow_to_ofpbuf(&flow, &aux->request);
????break;
?
歡迎關(guān)注個人公眾號
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/popsuper1982/p/8948016.html
總結(jié)
以上是生活随笔為你收集整理的从Openvswitch代码看网络包的旅程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。