咱們知道,Openvwitch能夠建立虛擬交換機,而網絡包能夠經過虛擬交換機進行轉發,並經過流表進行處理,具體的過程如何呢?node
1、內核模塊Openvswitch.ko的加載數據庫
OVS是內核態和用戶態配合工做的,因此首先要加載內核態模塊Openvswitch.ko。網絡
在datapath/datapath.c中會調用module_init(dp_init);來初始化內核模塊。tcp
其中比較重要的是調用了dp_register_genl(),這個就是註冊netlink函數,從而用戶態進程ovs-vswitchd能夠經過netlink調用內核。函數
這裏dp_genl_families由四個netlink的family組成ui
static struct genl_family *dp_genl_families[] = {spa
&dp_datapath_genl_family,線程
&dp_vport_genl_family,code
&dp_flow_genl_family,orm
&dp_packet_genl_family,
};
能夠看出,在內核中,包含對datapath的操做,例如OVS_DP_CMD_NEW,對虛擬端口vport的操做,例如OVS_VPORT_CMD_NEW,對flow流表的操做,例如OVS_FLOW_CMD_NEW,對packet包的操做,例如OVS_PACKET_CMD_EXECUTE。
2、用戶態進程ovs-vswitchd的啓動
ovs-vswitchd.c的main函數最終會進入一個while循環,在這個無限循環中,裏面最重要的兩個函數是bridge_run()和netdev_run()。
Openvswitch主要管理兩種類型的設備,一個是建立的虛擬網橋,一個是鏈接到虛擬網橋上的設備。
其中bridge_run就是初始化數據庫中已經建立的虛擬網橋。
bridge_run會調用static void bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg),其中ovs_cfg是從ovsdb-server裏面讀取出來的配置。
在這個函數裏面,對於每個網橋,將網卡添加進去。
HMAP_FOR_EACH (br, node, &all_bridges) {
bridge_add_ports(br, &br->wanted_ports);
shash_destroy(&br->wanted_ports);
}
最終會調用dpif_netlink_port_add__在這個函數裏面,會調用netlink的API,命令爲OVS_VPORT_CMD_NEW。
3、內核模塊監聽網卡
ovs-vswitchd啓動的時候,將虛擬網卡添加到虛擬交換機上的時候,會調用netlink的OVS_VPORT_CMD_NEW命令,於是會調用函數ovs_vport_cmd_new。
它最終會調用ovs_netdev_link,其中有下面的代碼:
err = netdev_rx_handler_register(vport->dev, netdev_frame_hook,
vport);
註冊一個方法叫作netdev_frame_hook,每當網卡收到包的時候,就調用這個方法。
4、內核態網絡包處理
Openvswitch的內核模塊openvswitch.ko會在網卡上註冊一個函數netdev_frame_hook,每當有網絡包到達網卡的時候,這個函數就會被調用。
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;
}
調用port_receive便是調用netdev_port_receive
在這個函數裏面,首先聲明瞭變量struct sw_flow_key key;
若是咱們看這個key的定義,可見這個key裏面是一個大雜燴,數據包裏面的幾乎任何部分均可以做爲key來查找flow表
tunnel能夠做爲key
在物理層,in_port即包進入的網口的ID
在MAC層,源和目的MAC地址
在IP層,源和目的IP地址
在傳輸層,源和目的端口號
IPV6
因此,要在內核態匹配流表,首先須要調用ovs_flow_key_extract,從包的正文中提取key的值。
接下來就是要調用ovs_dp_process_packet了。
這個函數首先在內核裏面的流表中查找符合key的flow,也即ovs_flow_tbl_lookup_stats,若是找到了,很好說明用戶態的流表已經放入內核,則走fast path就可了。因而直接調用ovs_execute_actions,執行這個key對應的action。
若是不能找到,則只好調用ovs_dp_upcall,讓用戶態去查找流表。會調用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)
它會調用err = genlmsg_unicast(ovs_dp_get_net(dp), user_skb, upcall_info->portid);經過netlink將消息發送給用戶態。在用戶態,有線程監聽消息,一旦有消息,則觸發udpif_upcall_handler。
Slow Path & Fast Path
Slow Path:
當Datapath找不到flow rule對packet進行處理時
Vswitchd使用flow rule對packet進行處理。
Fast Path:
將slow path的flow rule放在內核態,對packet進行處理
Unknown Packet Processing
Datapath使用flow rule對packet進行處理,若是沒有,則有vswitchd使用flow rule進行處理
從Device接收Packet交給事先註冊的event handler進行處理
接收Packet後識別是不是unknown packet,是則交由upcall處理
vswitchd對unknown packet找到flow rule進行處理
將Flow rule發送給datapath
5、用戶態處理包
當內核沒法查找到流表項的時候,則會經過upcall來調用用戶態ovs-vswtichd中的flow table。
會調用ofproto-dpif-upcall.c中的udpif_upcall_handler函數。
(1) 首先讀取upcall調用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) 其次提取包頭調用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) 而後調用static int process_upcall(struct udpif *udpif, struct upcall *upcall, struct ofpbuf *odp_actions, struct flow_wildcards *wc)來處理upcall。
對於MISS_UPCALL,調用static void upcall_xlate(struct udpif *udpif, struct upcall *upcall, struct ofpbuf *odp_actions, struct flow_wildcards *wc)
會調用enum xlate_error xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
在這個函數裏面,會在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以後,調用static void do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len, struct xlate_ctx *ctx)在這個函數裏面,根據action的不一樣,修改flow的內容。
(4) 最後調用static void handle_upcalls(struct udpif *udpif, struct upcall *upcalls, size_t n_upcalls)將flow rule添加到內核中的datapath
他會調用void dpif_operate(struct dpif *dpif, struct dpif_op **ops, size_t n_ops),他會調用dpif->dpif_class->operate(dpif, ops, chunk);
會調用dpif_netlink_operate()
會調用netlink修改內核中datapath的規則。
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;
歡迎關注我的公衆號