一、問題背景
dpvs(1.9.10) 部署在虛機環境中,使用的是宿主機 mellanox cx6 的 vf 網卡,做極端場景下的測試過程發現一個奇怪的 rss hash 抖動問題:打流的過程中,同時調用 rte_eth_dev_set_link_down / rte_eth_dev_set_link_up 操作修改 kni 接口狀態,這期間一直可以收包,但是發現報文被送到了錯誤的核處理,導致長連接場景下出現連接異常。通過打日志發現,從down到up后會有幾個包的mbuf->hash.rss是0或錯誤的值,之后穩定后會恢復正常,只有一開始的部分包 rss hash 值錯誤。另外,打開混雜模式的場景的會觸發該問題,關閉混雜不會有此問題。使用 kni 接口不會有此問題,使用 virtio-user 接口會觸發該問題。另外,pf 場景下不會有該問題(down/up 期間會斷流),vf 場景下會出現此問題,現象非常奇怪。
二、問題分析
根據問題現象,反復排查代碼發現問題,在 kni 初始化代碼中發現一些蛛絲馬跡:

這個函數的職責是在給定的網口結構體(struct netif_port *dev)上建立并初始化一個 rtnetlink(路由/鏈路通知)套接字,然后把該套接字交給模塊的輪詢/定時器機制去定期檢查和處理內核發來的網絡事件(kni_rtnl_check)。
總體流程是:創建 AF_NETLINK/NETLINK_ROUTE 套接字 -> 配置要監聽的組(nl_groups) -> bind 到本地 netlink 地址 -> 把套接字設置為非阻塞 -> 將文件描述符保存到 dev->kni.kni_rtnl_fd -> 用 dpvs_timer_sched_period 安排一個周期性定時器(200ms)去調用 kni_rtnl_check。
具體來說:snl.nl_groups 在有 CONFIG_KNI_VIRTIO_USER 時設置為 RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR,這表示要接收鏈路變化和 IPv4/IPv6 地址變更事件;否則只注冊 RTMGRP_NOTIFY(通知事件)。bind 操作把 socket 綁定到 netlink 地址,以便接收內核發來的消息。將套接字設置為非阻塞意味著隨后通過定時器輪詢讀取時不會被阻塞。
通過執行 link down/up 操作之后,導致接口鏈路狀態發生變化。dpvs 收到鏈路狀態變化的事件之后會觸發調用 kni_rtnl_check。通過查看 kni_rtnl_check 函數可以發現,該函數在收到鏈路狀態變化的時間后會調用 kni_update_maddr 來更新 kni 設備的多播地址。

進一步分析代碼 kni_update_maddr 代碼,調用棧如下:
kni_update_maddr
-> kni_mc_list_cmp_set
-> __netif_set_mc_list
-> dev->netif_ops->op_set_mc_list
-> dpdk_set_mc_list
-> rte_eth_dev_set_mc_addr_list
-> dev->dev_ops->set_mc_addr_list
-> mlx5_set_mc_addr_list(針對 mellnaox 網卡)
最終會調用 mellanox 驅動注冊的 mlx5_set_mc_addr_list 回調函數,如下所示:

分析該函數可以看到:
關閉混雜的情況下,會調用 mlx5_traffic_restart,該函數會重置硬件默認規則(rss 配置也是以 flow 的形式下發到硬件),這就導致了,如果此時恰好 vf 口仍在收包,rss hash 就會受到影響。后面發現 dpdk 刷新組播地址,設置 mac 地址等,均是相同的處理邏(mlx5_set_mc_addr_list、mlx5_mac_addr_add、mlx5_mac_addr_remove 這些函數中都是這樣的處理邏輯),如下所示:

三、分析結論
根據上面的分析,就可以完美解釋上面的奇怪現象,總結如下:
(1)調用 rte_eth_dev_set_link_down / rte_eth_dev_set_link_up 操作修改 kni 接口狀態,最終會觸發調用接口更新 kni 設備的多播地址,更新的過程中,最終會調用 mlx5_traffic_restart 接口,該接口會重置 rss 配置,導致此時有流量過來的時候,rss hash 值出現短暫的錯誤問題;
(2)使用 kni 接口不會有此問題,使用 virtio-user 接口會觸發該問題,這一點從 kni 初始化流程中也很好理解,使用 kni 接口的時候不會訂閱鏈路狀態的消息,不會有這種問題發生,只有使用 virtio-user 接口的場景下才會有這些處理邏輯。
(3)關閉混雜的情況下,會觸發該問題,因為最終會調用到 mlx5_traffic_restart 接口。而打開混雜的場景下,不會調用 mlx5_traffic_restart 接口,自然不會觸發該問題,這是代碼本身的邏輯。
mlx 驅動的中的這段邏輯應該也是符合預期的:原因應該是 mlx 網卡驅動通過硬件流表來實現對 MAC/組播地址的精確過濾,所以當組播地址發生變更時,需要刷新硬件流表,以便只接收新的組播地址對應的數據包(規則中應該有匹配 mac或者組播地址的規則)。混雜模式開啟的時候,硬件已經被配置為全收,所有包都能進來,不需要依賴流表去精確匹配 MAC/組播地址。組播地址的變化對流表沒有影響,因為流表規則不會被用來過濾包,直接全收。
(4) 至于 pf 場景下不會有該問題(down/up 期間會斷流),vf 場景下會出現此問題。這一點和 mlx 網卡本身相關,
查看 pf 配置,如下所示,可以看到這兩個配置已經配置了 false。即軟件層面 down 掉接口會觸發硬件層面也 down 掉鏈路,這是針對 pf 的配置,vf 沒有這類配置。所以軟件層面進行 link-down 時,pf 層面會斷流,但是 vf 層面不會斷流。不斷流此時就會有問題,導致重啟過程中會重置硬件默認規則,此時 vf 恰好在收包時,就會有這樣的情況出現。
