1. 程式人生 > >從Openvswitch程式碼看網路包的旅程

從Openvswitch程式碼看網路包的旅程

我們知道,Openvwitch可以建立虛擬交換機,而網路包可以通過虛擬交換機進行轉發,並通過流表進行處理,具體的過程如何呢?

一、核心模組Openvswitch.ko的載入

OVS是核心態和使用者態配合工作的,所以首先要載入核心態模組Openvswitch.ko。

在datapath/datapath.c中會呼叫module_init(dp_init);來初始化核心模組。

其中比較重要的是呼叫了dp_register_genl(),這個就是註冊netlink函式,從而使用者態程序ovs-vswitchd可以通過netlink呼叫核心。

這裡dp_genl_families由四個netlink的family組成

  1. static struct genl_family *dp_genl_families[] = {

  2.    &dp_datapath_genl_family,

  3.    &dp_vport_genl_family,

  4.    &dp_flow_genl_family,

  5.    &dp_packet_genl_family,

  6. };

可以看出,在核心中,包含對datapath的操作,例如OVS_DP_CMD_NEW,對虛擬埠vport的操作,例如OVS_VPORT_CMD_NEW,對flow流表的操作,例如OVS_FLOW_CMD_NEW,對packet包的操作,例如OVS_PACKET_CMD_EXECUTE。

二、使用者態程序ovs-vswitchd的啟動

ovs-vswitchd.c的main函式最終會進入一個while迴圈,在這個無限迴圈中,裡面最重要的兩個函式是bridge_run()和netdev_run()。

Openvswitch主要管理兩種型別的裝置,一個是建立的虛擬網橋,一個是連線到虛擬網橋上的裝置。

其中bridge_run就是初始化資料庫中已經建立的虛擬網橋。

虛擬網絡卡的初始化則靠netdev_run()。

bridge_run會呼叫static void bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg),其中ovs_cfg是從ovsdb-server裡面讀取出來的配置。

在這個函式裡面,對於每一個網橋,將網絡卡新增進去。

  1. HMAP_FOR_EACH (br, node, &all_bridges) {

  2.     bridge_add_ports(br, &br->wanted_ports);

  3.     shash_destroy(&br->wanted_ports);

  4. }

最終會呼叫dpif_netlink_port_add__在這個函式裡面,會呼叫netlink的API,命令為OVS_VPORT_CMD_NEW。

三、核心模組監聽網絡卡

ovs-vswitchd啟動的時候,將虛擬網絡卡新增到虛擬交換機上的時候,會呼叫netlink的OVS_VPORT_CMD_NEW命令,因而會呼叫函式ovs_vport_cmd_new。

它最終會呼叫ovs_netdev_link,其中有下面的程式碼:

  1. err = netdev_rx_handler_register(vport->dev, netdev_frame_hook,

  2.                 vport);

註冊一個方法叫做netdev_frame_hook,每當網絡卡收到包的時候,就呼叫這個方法。

四、核心態網路包處理

Openvswitch的核心模組openvswitch.ko會在網絡卡上註冊一個函式netdev_frame_hook,每當有網路包到達網絡卡的時候,這個函式就會被呼叫。

  1. static struct sk_buff *netdev_frame_hook(struct sk_buff *skb)

  2. {

  3.    if (unlikely(skb->pkt_type == PACKET_LOOPBACK))

  4.       return skb;

  5.    port_receive(skb);

  6.    return NULL;

  7. }

呼叫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進行處理

  1. 從Device接收Packet交給事先註冊的event handler進行處理

  2. 接收Packet後識別是否是unknown packet,是則交由upcall處理

  3. vswitchd對unknown packet找到flow rule進行處理

  4. 將Flow rule傳送給datapath

五、使用者態處理包

當核心無法查詢到流表項的時候,則會通過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如下:

  1.     /* L2, Order the same as in the Ethernet header! (64-bit aligned) */

  2.     struct eth_addr dl_dst; /* Ethernet destination address. */

  3.     struct eth_addr dl_src; /* Ethernet source address. */

  4.     ovs_be16 dl_type; /* Ethernet frame type. */

  5.     ovs_be16 vlan_tci; /* If 802.1Q, TCI | VLAN_CFI; otherwise 0. */

  6.     ovs_be32 mpls_lse[ROUND_UP(FLOW_MAX_MPLS_LABELS, 2)]; /* MPLS label stack

  7.                                                              (with padding). */

  8.     /* L3 (64-bit aligned) */

  9.     ovs_be32 nw_src; /* IPv4 source address. */

  10.     ovs_be32 nw_dst; /* IPv4 destination address. */

  11.     struct in6_addr ipv6_src; /* IPv6 source address. */

  12.     struct in6_addr ipv6_dst; /* IPv6 destination address. */

  13.     ovs_be32 ipv6_label; /* IPv6 flow label. */

  14.     uint8_t nw_frag; /* FLOW_FRAG_* flags. */

  15.     uint8_t nw_tos; /* IP ToS (including DSCP and ECN). */

  16.     uint8_t nw_ttl; /* IP TTL/Hop Limit. */

  17.     uint8_t nw_proto; /* IP protocol or low 8 bits of ARP opcode. */

  18.     struct in6_addr nd_target; /* IPv6 neighbor discovery (ND) target. */

  19.     struct eth_addr arp_sha; /* ARP/ND source hardware address. */

  20.     struct eth_addr arp_tha; /* ARP/ND target hardware address. */

  21.     ovs_be16 tcp_flags; /* TCP flags. With L3 to avoid matching L4. */

  22.     ovs_be16 pad3; /* Pad to 64 bits. */

  23.     /* L4 (64-bit aligned) */

  24.     ovs_be16 tp_src; /* TCP/UDP/SCTP source port/ICMP type. */

  25.     ovs_be16 tp_dst; /* TCP/UDP/SCTP destination port/ICMP code. */

  26.     ovs_be32 igmp_group_ip4; /* IGMP group IPv4 address.

  27.                                  * 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的規則。

    1. case DPIF_OP_FLOW_PUT:

    2.     put = &op->u.flow_put;

    3.     dpif_netlink_init_flow_put(dpif, put, &flow);

    4.     if (put->stats) {

    5.         flow.nlmsg_flags |= NLM_F_ECHO;

    6.         aux->txn.reply = &aux->reply;

    7.     }

    8.     dpif_netlink_flow_to_ofpbuf(&flow, &aux->request);

    9.     break;

歡迎關注個人公眾號