SDN系統方法 | 4. 裸金屬交換機

隨著互聯網和數據中心流量的爆炸式增長,SDN已經逐步取代靜態路由交換設備成為構建網絡的主流方式,本系列是免費電子書《Software-Defined Networks: A Systems Approach》的中文版,完整介紹了SDN的概念、原理、架構和實現方式。原文: Software-Defined Networks: A Systems Approach

第4章 裸金屬交換機

本章將介紹作為SDN網絡底層硬件基礎的裸金屬交換機,我們的目標不是介紹詳細的硬件原理,而是提供足夠的設計概念,從而能夠欣賞在其上運行的軟件棧。請注意,這是個仍在發展中的技術棧,不同廠商采用了不同的實現方法。本章討論了作為交換機數據平面編程語言的P4以及作為第一代解決方案的OpenFlow。我們將從更一般的、可編程的P4開始,按照逆時間順序介紹這兩種方法。

4.1 交換層概要(Switch-Level Schematic)

首先,我們把裸金屬交換機作為整體來考慮,可以類比為一臺由一組商業化的、現成的組件組裝而成的PC。事實上,利用這些組件構建交換機的完整架構規范可以在開放計算項目(OCP, Open Compute Project) 上在線獲得。這是類似于開源軟件的硬件項目,類似于家用PC,任何人都有可能依此構建高性能交換機。但就像PC生態系統包括戴爾和惠普這樣的商品服務器供應商一樣,也可以從裸金屬交換機供應商(如EdgeCore、Delta等)那里購買預構建(OCP兼容)交換機。

圖18給出了裸金屬交換機的高層示意圖。網絡處理單元(NPU, Network Processing Unit) 是一種商業硅交換芯片,被優化來解析包頭并做出轉發決策。NPU能夠以每秒太比特(Tbps)的速度處理和轉發數據包,其速度足以與32x100-Gbps端口或圖中所示的48x40-Gbps端口保持一致。在撰寫本文時,這些芯片的最先進技術是在400 Gbps端口實現的25.6 Tbps。

圖18. 裸金屬交換機高層概要。

請注意,我們對術語NPU的使用可能會被認為有點不標準。過去,NPU指的是更狹義的網絡處理芯片,用于實現智能防火墻或深度包檢測。它們不像我們在本章討論的NPU那樣通用,性能也不高。然而,長期趨勢是NPU的性能越來越接近固定功能ASIC,同時提供更高的靈活性。目前的商用硅交換芯片似乎有可能淘汰早期的專用網絡處理器。這里使用的NPU命名規范與構建可編程特定處理器的行業趨勢一致,包括用于圖形處理的GPU(圖形處理單元, Graphic Processing Unit)和用于AI的TPU(張量處理單元, Tensor Processing Unit)。

圖18顯示的NPU由基于SRAM的內存(在處理數據包時進行緩沖)和基于ASIC的轉發流水線組合實現,這些流水線通過一系列(Match, Action)對實現,我們將在下一節中更詳細介紹轉發流水線。交換機還包括一個通用處理器,通常是一個x86芯片,用以控制NPU。如果控制面在交換機上運行,那么CPU將運行BGP或OSPF,但就我們的目的而言,控制面是在交換機外以Switch OS的方式運行的,因此需要導出控制數據平面的API。該控制處理器與NPU通信,并通過標準PCIe總線連接到外部管理網絡。

圖18還顯示了使這一切變得實用的其他商品組件。無論是40G以太網、10G PON還是SONET或者光學設備,都可以購買可插拔收發模塊,用于處理所有媒介接入細節。這些收發器都符合SFP+等標準化要求,也可以反過來通過標準化總線連接到其他組件(如SFI)。最關鍵的是,網絡行業正在進入計算機行業在過去20年里所享受的同樣的商品化世界。

最后,雖然圖18中沒有顯示,但每個交換機都包括BIOS,很像微處理器中的對應物,用來提供和引導裸金屬交換機的固件。在OCP的指導下,出現了一種被稱為開放網絡安裝環境(ONIE, Open Network Install Environment) 的標準BIOS,因此我們在本章的其余部分假定基于ONIE討論。

4.2 轉發流水線(Forwarding Pipeline)

高速交換機使用多級流水線處理數據包。使用多級流水線而不是單級處理器的原因在于,單個包的轉發可能涉及到查看多個報頭字段,每個階段都可以通過編程來處理不同的領域組合。多階段流水線給每個包增加了一點端到端延遲(以納秒為單位),但意味著可以同時處理多個包。例如,在階段1對包B進行初始查找時,階段2可以對包A進行二次查找,以此類推。從而意味著流水線作為一個整體能夠匹配所有輸入端口的總帶寬。重復上一節中的數字,目前最先進的是25.6 Tbps。

一個特定NPU如何實現流水線的主要區別在于,每個階段功能是固定的(即每個階段都知道如何處理某些固定協議的報頭)還是可編程的(即每個階段都是通過動態編程知道要處理哪些報頭字段)。在接下來的討論中,我們從更一般的情況(可編程流水線)開始,并在最后回到固定功能的對應流水線。

在體系架構層面,可編程流水線通常被稱為協議獨立交換架構(PISA, Protocol Independent Switching Architecture)。圖19給出了PISA概述,包括三個主要組成部分。第一個是解析器(Parser),通過編程定義哪些報頭字段(以及在包中的位置)將被后面的階段識別和匹配。第二個是匹配操作單元(Match-Action Unit),每個單元都被編程來匹配(并可能對其進行操作)一個或多個已標識的報頭字段。第三個是編碼器(Deparser),將包元數據重新序列化到包中,然后在輸出鏈路上傳輸。deparser根據之前處理階段緩存在內存中的所有報頭字段重新構建在鏈路上傳輸的每個包。

圖中沒有顯示關于穿越流水線的數據包的元數據集合,包括每個包的狀態(如輸入端口和到達時間戳),以及跨連續包計算的流級別狀態(如交換機計數器和隊列深度)。這種元數據(對應于ASIC的寄存器),可用于各個階段的讀寫,也可以被Match-Action Unit使用(例如匹配輸入端口)。

圖19所示. PISA多級流水線概要。

圖19中的各個Match-Action Unit值得我們仔細觀察。圖中顯示的內存通常是基于SRAM和TCAM組合構建,實現了一個存儲正在處理的數據包中匹配的位模式的表。在這一特定組合中,TCAM比SRAM更昂貴、更耗電,但能夠支持通配符匹配。具體來說,TCAM中的"CAM"代表"內容可尋址內存(Content Addressable Memory,)",意味著希望在表中查找的鍵可被有效用作實現該表的內存地址。"T"代表"三元(Ternary)",這是一種技術方法,表示想要查找的鍵可以包含通配符(例如,鍵10*1可以同時匹配1001和1011)。從軟件角度來看,主要結論是通配符匹配比精確匹配更昂貴,應該盡可能避免。

然后,圖中顯示的ALU與相應的模式配對實現操作。可能的操作包括修改特定報頭字段(例如,減少TTL),增加或彈出標簽(例如,VLAN, MPLS),增加或清除交換機內部的各種計數器(例如,處理的數據包個數),以及設置用戶/內部元數據(例如,路由表中使用的VRF ID)。

直接編寫parser、match-action unit和deparser會很乏味,類似于編寫微處理器匯編代碼,所以我們改用像P4這樣的高級語言來表示所需行為,并依賴編譯器生成等價的低級程序。我們將在后面章節討論P4的細節,現在我們用圖20所示的更抽象的方式來代替所需的轉發流水線。(為了與其他示例保持一致,我們將此程序稱為forward.p4。)該示例程序首先匹配L2報頭字段,然后匹配IPv4或IPv6報頭字段,最后對數據包應用一些ACL規則,然后允許它們通過(例如,將后者視為防火墻過濾規則)。這是第1.2.3節中圖7中所示OpenFlow流水線的一個示例。

除了將流水線的高級表示轉換為底層PISA之外,P4編譯器還負責分配可用的PISA資源。在這種情況下,有4個槽位(或行)可用作Match-Action Unit,如圖19所示。在可用的Match-Action Unit中分配槽位(slot)類似于傳統編程語言在通用微處理器上分配寄存器。在我們的例子中,假設IPv4的Match-Action規則比IPv6或ACL規則多得多,編譯器可以相應的在可用的Match-Action Unit中分配條目。

圖20. 將期望的轉發行為(由圖示P4程序指定)映射到PISA。

4.3 流水線抽象(Abstracting the Pipeline)

另一個難題是考慮不同交換芯片實現的不同物理流水線。要做到這一點,需要一個足夠通用,可以公平表示可用硬件的抽象(規范)流水線,以及抽象流水線如何映射到物理流水線的定義。有了這樣一個流水線的邏輯模型,就能夠支持流水線無關的控制器,如圖21所示。

理想情況下,只有一個邏輯流水線,P4編譯器負責將該邏輯流水線映射到各種對應的物理流水線。不幸的是,市場還沒有統一的單一邏輯流水線,但我們暫時把這種復雜性放在一邊。另一方面,目前需要考慮的目標ASIC大約有10個。市場上有數十家交換機供應商,但在實踐中,我們主要需要考慮那些為高端市場服務的供應商。

圖21. 通過定義邏輯流水線作為支持流水線無關控制平面的通用方法。

如何指定邏輯流水線?同樣是通過P4程序實現,結果如圖22所示。注意,我們正在重新審視圖15中引入的兩個P4程序。第一個程序(forward.p4)定義了我們希望從可用的交換芯片獲得的功能。這個程序是由想要建立數據平面行為的開發人員編寫的。第二個程序(arch.p4)本質上是一個頭文件,表示P4程序和P4編譯器之間的契約。具體來說,arch.p4定義了可用的P4可編程塊、每個階段的接口以及每個階段的功能。誰負責編寫這樣的架構程序?P4聯盟是這種定義的一個來源,但不同的交換機供應商已經創建了自己的架構規范,從而密切描述其交換芯片的能力。因為有單一公共架構,可以在不同供應商的不同ASIC上執行相同的P4程序,并且該架構能夠最好很好的表示任何給定ASIC的能力差異。

圖22所示例子被稱為可移植交換機架構(PSA, Portable Switch Architecture),旨在為P4開發人員提供實現轉發程序(如forward.p4)的抽象的目標機,類似于Java虛擬機。其目標與Java相同,支持一次編寫、隨處運行的編程范式。(注意,圖22包括了通用arch.p4作為體系架構模型規范,但實際上體系架構模型是特定于PSA的,類似于psa.p4。)

圖22. P4架構被稱為PSA(Portable Switch Architecture)。包括通用```arch.p4```作為架構模型規范,但是對于PSA來說,這將被```psa.p4```所取代。

與圖19和圖20中使用的更簡單的PISA模型相比,我們看到了兩個主要差異。首先,流水線包含一個新的固定功能階段: 流量管理器(Traffic Manager) 。這個階段負責對數據包進行排隊、復制和調度,可以用定義良好的方式配置(例如,設置隊列大小和調度策略等參數),但不能用通用方式重新編程(例如,定義一個新的調度算法)。其次,流水線被分為兩部分: 入口處理(ingress processing)(流量管理器左邊)和出口處理(egress processing)(流量管理器右邊)。

到底arch.p4定義了什么?基本上是三件事:

  1. 如圖22所示,根據輸入和輸出信號(想想“函數參數和返回類型”)定義了塊間接口簽名。P4開發人員為每個P4可編程塊提供對應的實現,處理輸入信號(如輸入端口收到數據包),并將輸出信號寫到下一個受影響的可編程塊(例如數據包指示的輸出隊列/端口)。
  2. 被聲明為擴展(externs) 的類型,可被視為由目標硬件開放的附加固定功能的服務,可以被P4開發人員調用。這類外部服務的例子包括checksum以及哈希計算單元、數據包或字節計數器、加密/解密數據包負載,等等。P4體系架構中不會指定這些外部服務的實現,但會指定其接口。
  3. 擴展核心P4語言類型,包括可選匹配類型(如4.4.3節中描述的rangelpm)。

P4編譯器(像所有編譯器一樣)有一個與硬件無關的前端,為被編譯程序生成抽象語法樹(AST, Abstract Syntax Tree),以及與硬件相關的后端,從而輸出特定ASIC的可執行文件。arch.p4只是類型和接口定義的集合。

4.3.1 V1Model

圖22中所示的PSA仍在開發中,其代表了一個理想的架構,位于P4開發人員和底層硬件之間,但是開發人員目前正在編寫的架構模型稍微簡單一些。該模型稱為V1Model,如圖23.1所示[1]。它不包括流量管理器之后的重新解析步驟,相反,它隱式連接了從入口到出口處理的所有元數據。此外,V1Model包括一個checksum驗證/更新塊,而PSA將checksum視為一個擴展,并支持在入口/出口處理流程的任何點上進行增量計算。

[1] V1Model最初是作為P4早期版本(即P4_14 "V1Model")的參考體系架構引入,隨后被用于簡化P4程序從P4_14到P4_16的移植。

本書其余部分將使用這個更簡單的模型。說句題外話,V1Model被廣泛使用的最重要因素是交換機供應商沒有提供從PSA映射到各自ASIC的編譯器后端。因此在此之前,PSA還是主要停留在紙面上。

圖23. V1Model在實踐中用于抽象出不同物理轉發流水線的細節,開發人員為這個抽象架構模型編寫P4。

當我們說P4開發人員"編寫這個模型"時,其實際含義比你想的更多。在實踐中,每個P4程序都從下面的模板開始,它實際為圖23所示的每個可編程元素的抽象描述提供了一個代碼塊。

#include <core.p4>
#include <v1model.p4>

/* Headers */
struct metadata { ... }
struct headers {
    ethernet_t  ethernet;
    ipv4_t      ipv4;
}

/* Parser */
parser MyParser(
    packet_in packet,
    out headers hdr,
    inout metadata meta,
    inout standard_metadata_t smeta) {
    ...
}

/* Checksum Verification */
control MyVerifyChecksum(
    in headers, hdr,
    inout metadata meta) {
    ...
}

/* Ingress Processing */
control MyIngress(
    inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t smeta) {
    ...
}

/* Egress Processing */
control MyEgress(
    inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t smeta) {
    ...
}

/* Checksum Update */
control MyComputeChecksum(
    inout headers, hdr,
    inout metadata meta) {
    ...
}

/* Deparser */
parser MyDeparser(
    inout headers hdr,
    inout metadata meta) {
    ...
}

/* Switch */
V1Switch(
    MyParser(),
    MyVerifyChecksum(),
    MyIngress(),
    MyEgress(),
    MyComputeChecksum(),
    MyDeparser()
) main;

也就是說,在包含兩個定義文件(core.p4, v1model.p4)以及定義流水線將要處理的包頭之后,開發人員需要編寫p4代碼用于解析、驗證checksum、處理輸入等等。最后一個塊(V1Switch)是"main"函數,將所有部件整合到一起形成完整的交換機流水線。至于模板中每個"…"對應的細節,我們將在后面章節中詳細介紹。現在,最重要的是看到forward.p4是一個高度程式化的程序,其結構來自v1model.p4中定義的抽象模型。

4.3.2 TNA

正如剛才提到的,V1Model是許多可能的流水線體系架構之一。PSA是另一種,但不同的交換機供應商也提供了自己的體系架構定義。這樣做有不同的動機,一是隨著時間的推移,供應商不斷發布新的芯片,他們有自己版本的多ASIC問題。另一個原因是,它使供應商能夠暴露其ASIC的獨特功能,而不受標準化過程的限制。Tofino Native Architecture(TNA) 就是一個例子,它是Barefoot為其可編程交換芯片家族定義的架構模型。

我們不打算在這里詳細介紹TNA(詳細信息可以在GitHub上查看),但可以作為第二個實際案例幫助說明這一領域的自由度。實際上,P4語言定義了一個編寫程序的通用框架(我們將在下一節中看到其語法),但只有在定義了P4體系架構(通常我們稱之為arch.p4)時(具體例子是v1model.p4, psa.p4tna.p4),開發人員才能夠實際編寫和編譯轉發程序。

延伸閱讀:
Open Tofino. 2021.

與渴望在不同交換芯片之間抽象出共性的v1model.p4psa.p4相反,tna.p4忠實的定義了給定芯片的底層功能。通常,這樣的功能就是使像Tofino這樣的芯片區別于競爭對手的地方。在為新的P4程序選擇架構模型時,一定要問這樣的問題: 我要編程的交換機支持哪些可用架構?我的程序是否需要訪問特定芯片的功能(例如,用來加密/解密數據包有效負載的P4擴展),或者能否只依賴通用/非區分特性(例如,簡單的匹配操作表或計數數據包的P4擴展)?

至于轉發程序(我們通常稱之為forward.p4),一個有趣的例子是一個忠實實現傳統L2/L3交換機所有特性的程序,我們稱之為switch.p4[2]。奇怪的是,這只是讓我們重現構建了一個本可以從幾十個供應商處購買的傳統交換機,但是有兩個顯著差異: (1)我們可以使用SDN控制器通過P4Runtime控制交換機,(2)當我們發現需要某個新功能時可以很容易的修改程序。

[2] Barefoot為他們的芯片組編寫了這樣一個程序,使用tna.p4作為架構模型,但不開源。有一個稱為fabric.p4的大致相同的開源變體,使用v1model.p4,支持大多數L2/L3特性,這些特性是為第7章中介紹的SD-Fabric用例定制的。

總而言之,總體目標是使控制程序的開發不需要考慮設備轉發流水線的具體細節。引入P4體系架構模型有助于實現這一目標,使相同的轉發流水線(P4程序)可以在支持相應體系架構模型的多個目標(交換芯片)之間移植。然而,這并不能完全解決問題,因為業界仍然可以自由定義多個轉發流水線。但從目前情況來看,擁有一個或多個可編程交換機為控制程序和轉發流水線的可編程性打開了大門,從而向開發人員展示最終的可編程性,即包括數據面轉發芯片在內都是可編程的。因此如果有一個創新的新功能想要注入到網絡中,那么需要同時編寫該功能的控制平面和數據平面兩部分,并通過工具鏈加載到SDN軟件棧中!與幾年前相比,這是一個重大進步。在幾年前,也許可以修改路由協議(因為是在軟件中),但沒有機會改變轉發流水線,因為都是在固定功能的硬件中。

這種復雜性值得嗎?

此時,你可能想知道引入的所有復雜性是否值得,我們甚至還沒有接觸控制平面!到目前為止,所討論的都是有或沒有SDN的復雜問題。這是因為我們工作在軟硬件的邊界,硬件被設計成以每秒太比特的速度轉發數據包。這種復雜性通常隱藏在專有設備中。SDN所做的就是向市場施壓,為其他人的創新打開空間。

在任何人能夠創新之前,第一步是復制我們以前運行的東西,只是現在基于開放接口和可編程硬件。盡管本章使用了forward.p4作為假設的新的數據平面功能,實際上通過switch.p4這樣的程序(以及下一章描述的Switch OS)與傳統網絡設備建立對應關系。一旦有了這些,就可以準備好做一些新事情了。但是做什么呢?

我們的目標不是明確回答這個問題。第二章中介紹的VNF卸載和INT示例可以作為一個開始,軟件定義的5G網絡(第9章)和閉環驗證(第10章)是潛在的殺手級應用。但歷史告訴我們,殺手級應用是不可能被準確預測的。另一方面,歷史上也有很多關于開放封閉、功能固定的系統如何產生新功能的例子。

4.4 P4程序

最后我們簡要介紹一下P4語言,以下不是P4的全面參考手冊,我們的目標是讓人們了解P4程序的概要,從而串聯起之前引入的所有的點。我們通過示例(即通過遍歷實現基本IP轉發的P4程序)來實現這一點,這個例子摘自P4教程,可以在網上訪問該教程并自己嘗試。

延伸閱讀:
P4 Tutorials. P4 Consortium, May 2019.

為了幫助理解,可以將P4看作類似于C語言。P4和C共享類似的語法,這很好理解,因為都是為低級系統設計的程序語言。然而,與C不同的是,P4不支持循環、指針或動態內存分配。如果你記得我們的目的是定義在單個流水線階段發生的事情時,不支持循環就很好理解了。實際上,P4"展開"了可能的循環,在一個控制塊序列(即階段)中實現每個迭代。在下面的示例程序中,可以想象將每個代碼塊插入到上一節所示的模板中。

4.4.1 頭聲明與元數據(Header Declarations and Metadata)

首先是協議頭聲明,對于我們的簡單示例,包括以太網頭和IP頭,這也是定義希望與正在處理的數據包關聯的任何特定于程序的元數據的地方。示例中該結構為空,但是v1model.p4為整個體系架構定義了標準的元數據結構。盡管下面的代碼塊中沒有顯示,但這個標準元數據結構包括諸如ingress_port(數據包到達的端口)、egress_port(選擇發送數據包的端口)和drop(置位表示數據包將被丟棄)等字段。這些字段可以由組成程序其余部分的功能塊讀取或寫入[3]

[3] V1Model的奇特之處在于,元數據結構中有兩個出口端口字段。其中一個(egress_port)是只讀的,僅在出口處理階段有效。第二個(egress_spec)是從入口處理階段寫入的字段,用于選擇輸出端口。 PSA和其他架構通過為入口和出口流水線定義不同的元數據來解決這個問題。

/***** P4_16 *****/
#include <core.p4>
#include <v1model.p4>

const bit<16> TYPE_IPV4 = 0x800;

/****************************************************
************* H E A D E R S  ************************
****************************************************/

typedef bit<9>  egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;

header ethernet_t {
    macAddr_t dstAddr;
    macAddr_t srcAddr;
    bit<16>   etherType;
}

header ipv4_t {
    bit<4>    version;
    bit<4>    ihl;
    bit<8>    diffserv;
    bit<16>   totalLen;
    bit<16>   identification;
    bit<3>    flags;
    bit<13>   fragOffset;
    bit<8>    ttl;
    bit<8>    protocol;
    bit<16>   hdrChecksum;
    ip4Addr_t srcAddr;
    ip4Addr_t dstAddr;
}

struct metadata {
   /* empty */
}

struct headers {
    ethernet_t   ethernet;
    ipv4_t       ipv4;
}
4.4.2 解析器(Parser)

下一個塊實現了解析器。解析器的底層編程模型是狀態轉換圖,包括內置的startacceptreject狀態。開發人員可以添加其他狀態(在我們的示例中是parse_ethernetparse_ipv4),以及狀態轉換邏輯。例如,下面的解析器總是從start狀態轉換到parse_ethernet狀態,如果在以太網報頭的etherType字段中找到TYPE_IPV4(請參閱前面代碼塊中的常量定義),那么接下來就轉換到parse_ipv4狀態。作為遍歷每個狀態的副作用,將從數據包中提取相應的報頭。這些內存結構中的值隨后可用于其他例程,如下所示。

/****************************************************
************* P A R S E R  **************************
****************************************************/

parser MyParser(
    packet_in packet,
    out headers hdr,
    inout metadata meta,
    inout standard_metadata_t standard_metadata) {

    state start {
        transition parse_ethernet;
    }

    state parse_ethernet {
        packet.extract(hdr.ethernet);
        transition select(hdr.ethernet.etherType) {
            TYPE_IPV4: parse_ipv4;
            default: accept;
        }
    }

    state parse_ipv4 {
        packet.extract(hdr.ipv4);
        transition accept;
    }
}

與本節所有代碼塊一樣,解析器的函數簽名是由體系架構模型定義的,在本例中為v1model.p4。我們沒有對具體參數做進一步介紹,只是籠統指出P4是與架構無關的,所編寫的程序很大程度上依賴于所包含的架構模型。

4.4.3 入口處理(Ingress Processing)

入口處理分為兩部分。首先是驗證checksum,在我們的例子中只是應用默認值[4]。該示例引入的有趣的新特性是control結構,它實際上是P4版本的過程調用。雖然開發人員也可以根據模塊化的設計來定義"子例程",但從整體上看,控制塊與邏輯流水線模型定義的階段一一匹配。

[4] 這是V1Model特有的,PSA在入口或出口階段沒有明確的checksum驗證或計算。

/****************************************************
***  C H E C K S U M    V E R I F I C A T I O N   ***
****************************************************/

control MyVerifyChecksum(
    inout headers hdr,
    inout metadata meta) {   
    apply {  }
}

現在進入轉發算法的核心,其在Match-Action流水線的入口段實現。我們發現定義了兩個actions: drop()ipv4_forward()。第二個很有趣,它以dstAddr和出口端口作為參數,將端口分配給標準元數據結構中的相應字段,在數據包的以太網報頭中設置srcAddr/dstAddr字段,并減小IP報頭的ttl字段。執行完該動作后,與報文相關的報文頭和元數據中包含了足夠的信息,可以正確做出轉發決策。

但是這個決策是如何做出的呢?這就是table結構的目的。table定義包含一個要查找的key、一組可能的actions(ipv4_forwarddropNoAction)、表的大小(1024個條目)以及當沒有匹配時所采取的默認操作(drop)。關鍵規范包括要查找的報頭字段(IPv4報頭的dstAddr字段)和期望的匹配類型(lpm表示最長前綴匹配)。其他可能的匹配類型包括exact(精確匹配)和ternary(三元匹配),后者有效應用掩碼來選擇在匹配中考慮的鍵中的哪些位域。lpmexactternary是核心P4語言類型的一部分,其定義可以在core.p4中找到。P4體系架構可以擴展額外的匹配類型,例如PSA還定義了range(范圍)和selector(選擇器)匹配。

入口處理的最后一步是"應用"剛剛定義的表(只有當解析器或之前的流水線階段將IP頭標記為有效時才會這樣做)。

/****************************************************
******  I N G R E S S   P R O C E S S I N G   *******
****************************************************/

control MyIngress(
    inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t standard_metadata) {          

    action drop() {
        mark_to_drop(standard_metadata);
    }
    
    action ipv4_forward(macAddr_t dstAddr,
            egressSpec_t port) {
    standard_metadata.egress_spec = port;
        hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
        hdr.ethernet.dstAddr = dstAddr;
        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
    }
    
    table ipv4_lpm {
        key = {
            hdr.ipv4.dstAddr: lpm;
        }
        actions = {
            ipv4_forward;
            drop;
            NoAction;
        }
        size = 1024;
       default_action = drop();
    }
   
    apply {
        if (hdr.ipv4.isValid()) {
            ipv4_lpm.apply();
        }
   }
}
4.4.4 出口處理(Egress Processing)

在我們的簡單示例中出口處理沒有任何操作,但通常這是基于出口端口執行操作的機會,而這一信息在入口處理期間可能不知道(例如,可能依賴于流量管理器)。例如,可以通過在入口處理中設置相應的內部元數據將一個數據包復制到多個出口端口以進行多播,這種元數據的含義由體系架構定義。出口處理將看到與流量管理器生成的相同包的副本數量相同的副本。再舉個例子,如果期望交換機的一個端口發送帶VLAN標簽的報文,則必須用VLAN id擴展報文頭。處理這種情況的一種簡單方法是創建一個與元數據的egress_port匹配的表。其他例子包括對組播/廣播數據包進行入口端口修剪,并為傳遞到控制平面的攔截數據包添加特殊的“CPU頭”。

/****************************************************
*******  E G R E S S   P R O C E S S I N G   ********
****************************************************/

control MyEgress(
    inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t standard_metadata) {
   
    apply {  }
}

/****************************************************
***   C H E C K S U M    C O M P U T A T I O N   ****
****************************************************/

control MyComputeChecksum(
    inout headers  hdr,
    inout metadata meta) {
   
     apply {
    update_checksum(
        hdr.ipv4.isValid(),
              { hdr.ipv4.version,
            hdr.ipv4.ihl,
                hdr.ipv4.diffserv,
                hdr.ipv4.totalLen,
                hdr.ipv4.identification,
                hdr.ipv4.flags,
                hdr.ipv4.fragOffset,
                hdr.ipv4.ttl,
                hdr.ipv4.protocol,
                hdr.ipv4.srcAddr,
                hdr.ipv4.dstAddr },
            hdr.ipv4.hdrChecksum,
            HashAlgorithm.csum16);
    }
}
4.4.5 編碼器(Deparser)

Deparser的處理通常比較直接。我們在包處理過程中可能更改了各種報頭字段,現在有機會將更新了報頭字段的報文發送出去。如果在流水線的某個階段更改了報頭,則需要記住在發包的時候帶上對應的改動。只有那些被標記為有效的報頭才會被重新序列化到包中。不需要對包的其余部分(即有效負載)做任何處理,因為默認情況下,在我們停止解析的數據之后的所有字節都將被包含在輸出消息中。數據包怎樣被發送的細節由架構指定。例如,TNA支持根據deparser的特殊元數據值的設置來截斷有效負載。

/****************************************************
*************  D E P A R S E R  *********************
****************************************************/

control MyDeparser(
    packet_out packet,
    in headers hdr) {
        
    apply {
        packet.emit(hdr.ethernet);
        packet.emit(hdr.ipv4);
    }
}
4.4.6 交換機定義(Switch Definition)

最后,P4程序必須從整體上定義交換機的行為,如下示例由V1Switch包給出。這個包中的元素集是由v1model.p4,由對上面定義的所有其他例程的引用組成。

/****************************************************
*************  S W I T C H  *************************
****************************************************/

V1Switch(
    MyParser(),
    MyVerifyChecksum(),
    MyIngress(),
    MyEgress(),
    MyComputeChecksum(),
    MyDeparser()
) main;

請記住,這是一個最小示例,但確實有助于說明P4程序的基本思想。本例中隱藏了控制平面用來向路由表注入數據的接口,table ipv4_lpm定義了這個表,但我們沒有填充相應的值。在第5章討論P4Runtime時,我們將解決控制平面將值填入表中的問題。

4.5 固定功能流水線(Fixed-Function Pipelines)

我們現在回到固定功能轉發流水線,目標是將它們置于更大的生態系統中。記住,固定功能交換芯片仍然主導著市場[5],我們并不是要低估它們的價值,它們無疑將繼續發揮作用,但它們確實少了一個自由度,即重新編程數據平面的能力,這有助于突出本章中介紹的所有組件之間的關系。

[5] 固定功能流水線和可編程流水線之間的區別并不像本文所討論的那樣明確,因為固定功能流水線也支持配置。但參數化交換芯片和為交換芯片編程在本質上是不同的,只有后者能夠引入新的功能。

4.5.1 OF-DPA

我們從一個具體例子開始: Broadcom為其交換芯片提供的硬件抽象層--OpenFlow數據平面抽象(OF-DPA, OpenFlow—Data Plane Abstraction) 。OF-DPA定義了可用于將流規則配置到底層Broadcom ASIC中的API。從技術上講,OpenFlow代理位于OF-DPA之上(實現了OpenFlow協議),而Broadcom SDK位于OF-DPA之下(實現了基于底層芯片細節的專有接口),但OF-DPA層提供了Tomahawk ASIC固定轉發流水線的抽象表示。圖24顯示了最終的軟件棧,其中OF-Agent和OF-DPA是開源的(OF-Agent對應一個名為Indigo的軟件模塊,最初由Big Switch編寫),而Broadcom SDK是專有的。圖25描述了OF-DPA流水線的樣子。

圖24. Tomahawk固定功能轉發流水線軟件棧。
圖25. 由OF-DPA定義的固定功能邏輯流水線。

我們不會深入探究圖25的細節,但是讀者可以識別出幾個知名協議。就我們的目的而言,有意義的是了解OF-DPA與可編程流水線的對應關系。在可編程場景下,直到我們實現一個類似switch.p4這樣的程序,才能得到大致相當于OF-DPA的東西。也就是說,v1model.p4定義了可用的階段(控制塊),但直到我們添加了switch.p4,才能將這些階段的功能運行起來。

考慮到這種關系,我們可能希望在單個網絡中綜合使用可編程交換機和固定功能交換機,并運行公共SDN軟件棧。這可以通過將兩種類型的芯片隱藏在v1model.p4(或類似的)架構模型后面來實現,并讓P4編譯器輸出各自SDK能夠理解的后端代碼。顯然,這不適用于想要做一些Tomahawk芯片不支持的事情的P4程序,但適用于標準L2/L3交換行為。

4.5.2 SAI

正如我們所看到的供應商定義和社區定義的體系架構模型(分別是TNA和V1Model)一樣,也存在供應商定義和社區定義的固定功能邏輯流水線。前者是OF-DPA,而交換機抽象接口(SAI, Switch Abstraction Interface) 是后者的一個例子。因為SAI必須跨一系列交換機(以及轉發流水線)工作,所以必須專注于所有供應商都認同的功能子集,也就是最小公約數上。

SAI包括一個配置接口和一個控制接口,其中后者與本節最為相關,因為它抽象了轉發流水線。另一方面,研究另一個轉發流水線并沒有什么價值,所以我們建議感興趣的讀者參考SAI規范以獲得更多細節。

延伸閱讀:
SAI Pipeline Behavioral Model. Open Compute Project.

我們將在下一章討論配置API。

4.6 比較

關于邏輯流水線及其與P4程序的關系的討論是微妙的,值得重述一遍。一方面,對物理流水線進行抽象表示有明顯的價值,如圖21中引入的一般概念。當以這種方式使用時,邏輯流水線就是引入硬件抽象層這種經過驗證的想法的一個例子。在我們的案例中,有助于控制平面的可移植性。OF-DPA是Broadcom固定功能交換芯片硬件抽象層的特定示例。

另一方面,P4提供了一個編程模型,通過v1model.p4tna.p4等架構向P4通用語言結構(例如,controltableparser)添加細節。實際上,這些架構模型是通用轉發設備基于語言的抽象,可以通過添加特定P4程序(如switch.p4)將其完全解析為邏輯流水線。P4體系架構模型沒有定義匹配操作表的流水線,但定義了可以被P4開發人員用來定義流水線(無論是邏輯的還是物理的)的構建塊(包括簽名)。因此,從某種意義上說,P4架構相當于傳統交換機SDK,如圖26中五個并排的示例所示。

圖26. 5個示例流水線/SDK/ASIC堆棧。最左邊的兩個加上第四個,至今仍然存在;中間是假設的棧;最右邊是一個未完成的棧。

圖26中的每個示例由三層組成: 交換芯片專用ASIC、用于編程ASIC的廠商特定SDK,以及轉發流水線定義。通過提供編程接口,中間層的SDK有效抽象了底層硬件。它們要么是傳統的(例如,在第二個和第四個例子中顯示的Broadcom SDK),要么是,正如剛才所指出的,邏輯上對應于P4架構模型與專用于ASIC的P4編譯器。這五個示例的最頂層定義了一個邏輯流水線,隨后可以通過OpenFlow或P4Runtime之類的接口來控制(沒有顯示)。這五個示例的不同在于流水線是由P4程序定義還是通過其他方法(例如,OF-DPA規范)定義。

請注意,只有那些在棧頂使用P4定義的邏輯流水線的配置(即第一、第三、第五個示例)可以使用P4Runtime進行控制。這是出于實用的原因,P4Runtime接口是使用下一章介紹的工具基于P4程序自動生成的。

最左邊的兩個例子現在已經存在了,分別代表了可編程和固定功能ASIC的規范層。中間的例子純粹是假設,但說明了即使對于固定功能流水線,也可以定義基于P4的堆棧(并暗示使用P4Runtime進行控制)。第四個例子同樣存在,即Broadcom ASIC如何遵從SAI定義的邏輯流水線。最后,最右邊的例子將會在未來出現,SAI將會被擴展以支持P4的可編程性,并在多個ASIC上運行。

你好,我是俞凡,在Motorola做過研發,現在在Mavenir做技術工作,對通信、網絡、后端架構、云原生、DevOps、CICD、區塊鏈、AI等技術始終保持著濃厚的興趣,平時喜歡閱讀、思考,相信持續學習、終身成長,歡迎一起交流學習。
微信公眾號:DeepNoMind

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容