1. 程式人生 > 其它 >SONiC架構分析

SONiC架構分析

SONiC構建在Linux系統之上,並且利用鍵值資料庫(redis)、容器技術(docker)、標準化硬體介面定義等技術,使其成為一個軟硬體徹底解耦、軟體模組鬆耦合(loose coupling)、高可靠、易於擴充套件、開源開放的網路軟體系統。其架構特點主要體現在3個方面: 目錄

在傳統交換機的架構下,網路作業系統由各裝置廠商基於晶片廠商負責提供的ASIC晶片和SDK自行設計、開發,裝置廠商需要開發上層APP、適配層以在特定裝置商完成應用,實現各種網路功能。傳統交換機的軟硬體開發均由裝置廠商提供,系統完全封閉,無法適應新功能快速開發部署的需求:

為構建一個開放的系統,OCP(Open Compute Project, 開發計算專案)開始推動硬體、網路作業系統開源與標準化程序。其中,SONiC (Software for Open Network in the Cloud) 是由微軟於2016年發起,並在2017年貢獻給OCP的專案。SONiC的所有軟體功能模組都開源,這極大地推動了OCP社群以及其他廠商/使用者在開放網路方面的創新。SONiC通過將SAI作為南北向互聯的中介軟體,遮蔽不同ASIC之間的驅動差異,也正是由於SAI的存在,SONiC的網路功能應用才能夠支援多個廠家的ASIC。絡軟體建立在交換機抽象介面(SAI,SAI介面適配ASIC的工作由各個廠家實現)上,使其可以執行在各種硬體裝置中,形成白盒交換機軟體生態鏈。

SONiC自推出後,迅速得到了產業界的支援,大部分網路晶片供應商都在SDK上支援SAI,並配合微軟為SAI版本新增新的介面,如網路分析介面、L3快速重路由和BFD等。

系統架構

SONiC構建在Linux系統之上,並且利用鍵值資料庫(redis)、容器技術(docker)、標準化硬體介面定義等技術,使其成為一個軟硬體徹底解耦、軟體模組鬆耦合(loose coupling)、高可靠、易於擴充套件、開源開放的網路軟體系統。其架構特點主要體現在3個方面:

  • SAI介面:SAI是SONiC的核心,併為SONiC提供了統一的API。裝置廠家、網路開發者可以基於晶片廠家提供的SAI介面開發應用,而不需要關心底層硬體實現,加速產品迭代與創新;
  • 資料庫架構:在資料庫架構方面,SONiC使用資料庫架構代替原有的模組化耦合架構,將應用模組之間的傳遞資料模式變成應用模組之間通過資料庫進行資料交換的模式,從關注流程轉變為關注資料,實現了功能模組之間的解耦。資料庫成為了所有模組的樞紐,模組與模組之間解耦,資料庫是穩定的,各個模組升級與故障不會影響其他模組,在整個切換過程中轉發面不受到影響;
  • 容器化元件:容器化使得SONiC具有極高的可擴充套件性,網路運營管理人員能夠快速引入第三方、專有或開源元件,而不對原有業務造成影響;
  • 與核心互動少: 執行在使用者空間的SONiC系統,只有為數很少的模組(pmon、swss和syncd)與Linux核心之間存在互動關係,從而保證了系統的穩定性。

設計原則

  • 軟硬體解耦的理念和標準化的軟硬體介面定義,分別讓網路軟體系統和硬體平臺飛速發展;
  • 網路軟體系統的演進,也可以讓快速迭代、按需定製、社群共建成為可能;
  • 網路軟體的開發形成了全新的共建、共享、開放的網路生態系統,快速推進網路自身的發展;
  • 微軟開源SONiC的同時,將自己在資料中心網路運營實踐方面的經驗也一併開放給了外界,everflow、netbouncer等功能就是SONiC軟體裡要支援的功能。這些功能的開放,是廣大網路運維工程師的福音,讓網路排障再也不僅僅只是依靠ping、traceroute或者純人工經驗,而是走向智慧化和科學化;
  • SONiC也提供了Ipv6的支援、Fast Reboot的功能,能夠保證在升級的過程當中,資料平面的中斷不超過30秒;
  • 2018年,SONiC進一步引入了Warm Reboot功能,該功能可以把資料平面的中斷‍‍控制在一秒之內,在很多現有的通用平臺上,甚至能夠做到無中斷的升級。同時也‍‍引入了一些新的service,比如Streaming Telemetry‍‍和一些虛擬化的支援。

核心元件

  • sonic-swss-common : 1.3萬
  • sonic-swss: 6萬行
  • sonic-sairedis : 22萬

CLI、REST FUL API、CFG Manager 配置 redis 資料庫 CFG_DB,redis 通過 key-space 機制將修改的資訊釋出給 SWSS 中的各個 mgrd,xxx-mgrd 呼叫linux命令或者傳送 netlink 訊息,將資訊同步給linux,成功後,同時將資訊插入到APP_DB的PORT_TABLE中,portorch 訂閱 APPL_DB中的PORT_TABLE,接收相關資訊,處理加工,並更新ASIC_DB資訊。

大部分元件的表都是redis hash表,同類型中不同的物件表的字首相同,字尾不同,同一個雜湊表中,不同的key對應不同的屬性。

SWSS 容器

SWSS (switch state service)是一套工具,用於協調所有SONiC模組之間、模組與redis引擎之間的通訊。swss還承載以下服務,這些服務通過netlink與SONiC應用層互動(在其他容器中執行的程序除外,即fpmsyncd、teamsyncd和lldp_syncd)。下面的前三個服務(portsyncd、intfsyncd、neighsyncd)將狀態推送到redis引擎,而最後三個服務(orchagent、intfMgrd、vlanMgrd)從引擎收集狀態,並重新發布狀態到應用。

  • portsyncd :偵聽與埠相關的netlink事件。當交換機啟動時,portsyncd解析交換機的硬體配置檔案以獲取物理埠資訊,然後將收集的狀態推送到APPL_DB中。portsyncd 設定埠速度、lane和MTU,並將狀態注入狀態資料庫。
  • intfsyncd :偵聽與介面相關的netlink事件,並將收集的狀態推送到APPL_DB中。intfsyncd還管理與介面關聯的新的或更改的IP地址等元素。
  • neighsyncd :偵聽新發現的鄰居由於ARP處理而觸發的與鄰居相關的netlink事件,並將收集的狀態推送到APPL_DB中。neighsyncd管理諸如MAC地址和鄰居地址族之類的屬性,並最終構建資料平面中用於二級重寫目的所需的鄰接表。
  • teamd: 連結聚合(LAG)容器,它提供了在SONiC交換機上配置繫結的功能。teamd服務是LAG協議的一個基於Linux的開源實現。teamsyncd服務管理teamd和southbound子系統之間的互動。
  • orchagent :swss子系統中最關鍵的元件。orchagent提取各種syncd服務注入的所有相關狀態,相應地處理和傳送這些資訊,最後將其推送到ASIC_DB。orchagent在這些服務中是獨一無二的,因為它既是消費者(如從APPL_DB獲取狀態)又是生產者(如推進ASIC_DB)。
  • intfMgrd :對來自APPL_DB、CONFIG_DB和state_DB的狀態作出反應,以配置Linux核心中的介面,前提是所監視的任何資料庫中沒有衝突或不一致的狀態。
  • vlanMgrd :對來自APPL_DB、CONFIG_DB和state_DB的狀態作出反應,以在Linux核心中配置VLAN,前提是所監視的任何資料庫中沒有衝突或不一致的狀態。

相關的模組功能角色總結如下:

  • xxx-mgrd : 對來自APPL_DB、CONFIG_DB和state_DB的狀態作出反應,以配置linux核心中的介面。
  • xxx-syncd: 偵聽與核心相關的netlink事件,並將收集的狀態寫入APP_DB。
  • orchagent: swss子系統中最關鍵的元件。orchagent提取各種syncd服務注入的所有相關狀態,相應地處理和傳送這些資訊,最後將其推送到ASIC_DB。orchagent在這些服務中是獨一無二的,因為它既是消費者(如從APPL_DB獲取狀態)又是生產者(如推進ASIC_DB)。

syncd 容器

將交換機的網路狀態與ASIC同步,這包括ASIC當前狀態的初始化、配置和收集。主要邏輯元件包括:

  • syncd:執行同步邏輯的程序。在編譯時,syncd與ASIC SDK庫連結,並將從介面收集的狀態注入ASIC。syncd訂閱ASIC_DB以接收來自swss參與者的狀態,並將來自硬體的狀態推回ASIC_DB。
  • SAI API:交換機抽象介面(SAI)定義了API,以提供獨立於供應商的方式來控制轉發元素,如交換ASIC、NPU或軟體交換機。
  • ASIC SDK:驅動ASIC所需SDK的SAI相容實現。SDK鉤住syncd,syncd 負責驅動其執行。

網路應用容器

  • lldp:鏈路層發現協議容器,在其中執行以下程序:1) lldpd:LLDP服務,它與外部對等方建立LLDP連線,以公佈和接收系統功能。2) lldp_syncd:此服務將lldp發現狀態上載到redis引擎(集中式系統的訊息基礎結構),將lldp狀態傳遞給需要它的應用程式,如SNMP。3) lldpmgr:lldpd服務的配置工具。
  • snmp:承載snmp功能。此容器中有兩個相關流程:1) snmpd:處理來自外部網路元素的傳入SNMP輪詢的伺服器。2) snmp代理:這是SONiC對AgentX snmp子代理SONiC_ax_impl的實現。sonic_ax_impl子代理向主代理(snmpd)提供從集中式Redis引擎中的sonic資料庫收集的資訊。
  • pmon: 此容器是Sensord 服務執行的地方。sensord定期記錄硬體元件的感測器讀數,並在觸發報警時發出警報。pmon容器還承載fancontrol程序,該程序從相應的平臺驅動程式收集與風扇相關的狀態。
  • bgp: 執行路由堆疊。BGP容器執行以下服務: 1) bgpd:標準的BGP服務。外部方的路由狀態通過常規TCP或UDP套接字接收,並通過zebra/fpmsyncd介面向下推送到轉發平面。2) zebra:傳統的IP路由管理器。它提供核心路由表更新、介面查詢和跨各種路由協議的路由重新分配服務。zebra還通過netlink將計算出的FIB向下推送到核心,並通過轉發平面管理器(FPM)推送到轉發過程中涉及的南向元件。3) fpmsyncd:此服務收集zebra生成的FIB狀態,並將其內容轉儲到redis引擎內的應用程式資料庫(APPL_DB)中。

內部通訊模型

sonic訊息傳遞機制2sonic訊息傳遞機制3

SONiC使用了釋出訂閱機制與redis的鍵空間通知機制(當客戶端修改資料時,redis伺服器會通知其他相關訂閱者(client)的資料變化)。

SONiC 中使用到的資料庫如下:

  • 0號資料庫:APPL_DB,儲存所有應用程式生成的狀態——路由、下一跳、鄰居等。這是應用程式與其他SONiC子系統互動的入口點;
  • 1號資料庫:ASIC_DB,存放底層 ASIC 的狀態資訊和配置表項,格式對底層晶片友好,晶片重啟可以從ASIC_DB快速恢復;
  • 2號資料庫:CONTERS_DB,存放每個埠計數器和統計資訊,這些資訊可以被cli使用或者反饋給telemetry;
  • 3號資料庫:LOGLEVEL_DB,存放日誌配置等級資訊;
  • 4號資料庫:CONFIG_DB,儲存SONiC應用程式建立的配置狀態——埠配置、介面、VLAN等,有的APP/模組可能沒有配置,可以沒有對應表,有的配置直接呼叫linux的命令進行配置,有的配置還需要下發到晶片,這時需要往APPL_DB裡寫;
  • 5號資料庫:FLEX_COUNTER_DB,存放靈活計數器配置;
  • 6號資料庫:STATE_DB,儲存系統中配置實體的“關鍵”操作狀態。此狀態用於解決不同SONiC子系統之間的依賴關係。例如,LAG埠channel(由teamd子模組定義)可能指系統中可能存在或不存在的物理埠。另一個例子是VLAN的定義(通過vlanmgrd元件),它可能引用系統中存在未知的埠成員。本質上,該資料庫儲存瞭解決跨模組依賴關係所需的所有狀態。

SubscriberStateTable

在SONiC中,CONFIG_DB和STATE_DB之間的資料監聽通過 key-space 機制實現。key-space 機制的消費者通過 sonic-swss-common/common 中的 SubscriberStateTable 類實現。

對CONFIG_DB的修改一般用於對系統進行配置操作,如使用命令列來配置系統功能,SONiC在sonic-py-swsssdk元件中封裝了對CONFIG_DB的操作,根據傳遞data是否為空執行hmset或delete操作。

這裡以監聽CONFIG_DB配置VLAN為例說明。

VlanMgr 元件在初始化時監聽CFG_VLAN_TABLE_NAMECFG_VLAN_MEMBER_TABLE_NAME兩個 key-space 事件,當通過config命令(sonic cli)新增 vlan 100的操作時,redis伺服器的 CONFIG_DB 會為“VLAN|Vlan100”的KEY產生 key-space事件訊息, VlanMgr 元件收到後,呼叫 VlanMgr::doTask(Consumer &consumer) 處理。

@startuml

config -> redis伺服器 : 寫redis
redis伺服器 -> client : 產生keyspace訊息 
client -> client : 接收訊息,並呼叫函式處理

@enduml

收到訊息後,這裡的Consumer即是通過orch類封裝過的 SubscriberStateTable 。

NotificationProducer/Consumer

通過訊息佇列來傳遞資訊,內容可以靈活定義。在 SONiC 中,該通訊模型主要用於 SWSS容器中的 orchagent 與 syncd 容器中的之間的事件通知。

以FDB事件為例,SYNCD收到來自底層驅動的FDB事件,呼叫對資料庫的hset或del操作更新ASIC_DB中的FDB表項,同時作為Notification生產者傳送名為“fdb_event”的通知訊息。
Notification的消費者流程在fdborch中實現,通知訊息觸發FdbOrch::doTask(NotificationConsumer&consumer)來進行後續處理,更新orchagent中的FDB資訊。

Producer/ConsumerStateTable

sonic-swss-common 中 ProducerStateTable 與 ConsumerStateTable 實現

該機制通過一個set集合傳遞key,通過publish命令通知有新的key產生。消費者通過key組合成一個hash表的key,用於獲取真實的訊息,set不保證順序,在publish通知KEY修改事件前允許對key-value進行多次操作,操作過程不保證執行順序。這樣做的好處是不必在每次設定key-value時都觸發事件通知,可以提升處理效率,但對orchagent處理流程有一定要求。

示例:

## 在集合INTF_TABLE_KEY_SET中增加一個key
"SADD" "INTF_TABLE_KEY_SET" "PortChannel1:1.1.1.1/8"

## 在hash表INTF_TABLE:PortChannel1:1.1.1.1/8中新增內容
"HSET" "INTF_TABLE:PortChannel1:1.1.1.1/8" "scope" "global" 
"HSET" "INTF_TABLE:PortChannel1:1.1.1.1/8" "family" "IPv4"

## 通知訂閱者頻道 INTF_TABLE_CHANNEL 有訊息,訂閱者根據 INTF_TABLE_組合成 INTF_TABLE_KEY_SET 獲取key,
## 然後,根據key獲取hash表 INTF_TABLE:PortChannel1:1.1.1.1/8 的內容,如果該內容為空則表示刪除操作,否則表示SET操作。
"PUBLISH" "INTF_TABLE_CHANNEL" "G"    

Orchagent排程處理採用epoll事件通知模型,事件觸發即會產生排程;在排程處理中,可能出現因資源依賴等因素導致任務處理無法完成,此時可以選擇將任務保留在m_toSync中等待下一次排程處理。在大規模控制表項和較複雜邏輯關係的場景下,這種排程機制可能出現因資源限制、依賴條件不滿足等因素導致的頻繁或無效排程,Asterfusion通過優化處理順序、改進批量操作以及在STATE_DB中設定狀態標誌等改進方式,提高了元件執行效率並增強了可靠性。

ProducerTable & ConsumerTable

使用redis publish 通知KEY修改事件,利用Key-Value-Operate機制來傳遞資訊。該通訊模型通過有序連結串列(list)來傳遞key-value-operate三元訊息,一次操作在LIST中壓入三個值(通知訂閱者進行訊息處理,迴圈處理訊息,一次必須從連結串列中拿出三個key),分別為key,value,operate。其中的 value 是把一個 hash表進行json編碼後形成了一個單一的字串,所以訂閱者得到訊息後需要進行解碼還原,最後一個是操作型別。

SYNCD 通過這種方式List佇列獲得 key-value-operation, 然後解碼,寫入到ASIC_STATE,同時呼叫底層SAI介面。

在SONiC中,該模型用於圍繞ASIC_DB和FLEX_COUNTER_DB的訊息傳遞。與模型3相比,該模型保證了操作的嚴格執行順序,在 syncd 執行 SAI API 呼叫時保證了對底層ASIC的操作時序。

示例:

"LPUSH" "ASIC_STATE_KEY_VALUE_OP_QUEUE" "SAI_OBJECT_TYPE_ROUTE_ENTRY:{\"dest\":\"1.1.1.0/24\",\"switch_id\":\"oid:0x21000000000000\",\"table_id\":\"oid:0x0\",\"vr\":\"oid:0x3000000000043\"}" "[\"SAI_ROUTE_ENTRY_ATTR_PACKET_ACTION\",\"SAI_PACKET_ACTION_FORWARD\",\"SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID\",\"oid:0x600000000063a\"]" "Screate"

## 通知訂閱者進行訊息處理,迴圈處理訊息,一次必須從連結串列中拿出三個key
"PUBLISH" "ASIC_STATE_CHANNEL" "G"  

與核心的通訊方式

網路介面事件和路由等需要SONiC應用程式與Linux核心通訊,主要通過兩種方式:

  • 呼叫Linux工具命令, 如呼叫ip命令配置網路介面IP地址和設定VRF,又如呼叫bridge命令配置VLAN。用封裝的swss::exec方法最終通過popen執行拼裝好的command指令。
  • 對netlink訊息操作則是通過以libnl庫為基礎封裝的NetLink類來完成,同時SWSS也定義了一套NetDispatcher機制來實現netlink訊息監聽和分發處理。

teamd 聚合組配置

配置

teamdmgrd 負責配置過程。

  • CLI建立名稱為PortChannel0001的聚合組,加入聚合組成員口Ethernet0和Ethernet4,在CONFIG_DB中生成配置表項;
  • teamdmgrd 程序監聽相應鍵值變化,呼叫doLagTask和doLagMemberTask方法處理。a)doLagTask方法解析引數並生成所需的配置檔案conf,通過呼叫teamd命令建立並配置聚合組,並呼叫ip命令設定聚合組介面MAC地址和管理狀態;b) doLagMemberTask方法中先判斷聚合組及待加入聚合組成員介面狀態是否滿足要求,如果滿足則呼叫teamdctl和ip命令來配置聚合成員介面,這裡會將聚合成員口設定為down,否則掛起當前任務後續再處理;


  • teamdmgrd作為生產者將聚合組和成員的配置資訊寫入APPL_DB。

  • portsorch作為消費者訂閱APP_LAG_TABLEAPP_LAG_MEMBER_TABLE進行處理。

  • portsorch呼叫sairedis的API,檢查引數型別合法性並將LAG配置資訊寫入ASIC_DB。

  • SYNCD訂閱ASIC_DB中的LAG相關表項並處理。

  • SYNCD呼叫ASIC SDK對SAI API的實現,並通過ASICdriver下發到底層晶片。

聚合過程

teamsyncd 負責聚合過程:

  • teamsyncd初始化階段註冊監聽RTM_NEWLINK和RTM_DELLINK型別的netlink訊息,同時也會註冊teamd的操作handler,用於處理teamd聚合成員口狀態變化以及teamd引數變化觸發的事件。a) 一類是netlink訊息,當觸發NEWLINK或DELLINK時對應操作STATE_LAG_TABLE設定聚合組狀態;b) 另一類是teamd狀態變化訊息,當teamd通過LACP互動及內部狀態機產生聚合成員口狀態變化,呼叫TeamSync::TeamPortSync::onChange進行處理。

  • teamd感知聚合成員口發生狀態變化,teamsyncd從teamd獲取當前聚合成員列表和狀態,與變化前的聚合成員列表進行比較。如果聚合成員已存在且狀態發生變化,則直接修改相應的APP_LAG_MEMBER_TABLE成員狀態,如果成員列表發生變化,則向APP_LAG_MEMBER_TABLE新增新增的成員口並設定成員狀態以及刪除不存在的成員口。

  • portsorch作為消費者訂閱APP_LAG_MEMBER_TABLE進行處理,根據聚合成員口狀態設定SAI_LAG_MEMBER_ATTR_INGRESS_DISABLE和SAI_LAG_MEMBER_ATTR_EGRESS_DISABLE,決定是否允許通過該成員口接收流量以及從該成員口傳送流量。

  • portsorch 呼叫sairedis的API,並更新LAG Member配置表項及屬性到ASIC_DB。

  • SYNCD訂閱ASIC_DB中的LAG Member表項並處理。

  • 呼叫ASIC SDK對SAI API的實現,並通過ASIC driver下發到底層晶片。

埠狀態互動

埠初始化

如下圖所示,展示了公開系統中對生成或使用埠相關資訊感興趣的多個元件:

  • 建立連線,宣告角色:初始化期間,portsyncd與redis引擎中的主資料庫建立通訊通道。Portsyncd宣告它打算充當APPL_DB和STATE_DB的釋出者,以及CONFIG_DB的訂戶。同樣,portsyncd還訂閱負責承載埠/鏈路狀態資訊的系統netlink通道。
  • 解析配置:Portsyncd首先解析與系統中使用的硬體配置檔案/sku關聯的埠配置檔案(port_config.ini)(有關更多詳細資訊,請參閱配置部分)。與埠相關的資訊,如通道、介面名稱、介面別名、速度等,通過該通道傳輸到APPL_DB。
  • 轉換到ASIC_DB:Orchagent聽到所有這些新狀態,但將推遲對其進行操作,直到portsyncd通知它已完全完成對port_config.ini資訊的解析。一旦發生這種情況,orchagent將繼續初始化硬體/核心中相應的埠介面。Orchagent呼叫sairedis API,通過常用的ASIC_DB介面將此請求傳遞給syncd
  • 建立埠:Syncd通過ASIC_DB接收此新請求,並準備呼叫滿足Orchagent請求所需的SAI API。Syncd使用SAI API+ASIC SDK建立與正在初始化的物理埠相關聯的核心主機介面。
  • 更新狀態:上一步生成netlink訊息,被portsyncd接收,並與先前從port_config.ini(步驟1)解析的所有埠進行對比,若都完成,則宣告“初始化”過程已完成。portsyncd 將寫入一個條目到 STATE_DB 中,表示埠初始化完成。
  • 依賴使用:從現在起,以前訂閱STATE_DB content的應用程式將收到一個通知,允許這些應用程式開始使用它們所依賴的埠。換句話說,如果在STATE_DB中找不到特定埠的有效條目,則任何應用程式都無法使用它。

埠狀態變化

  • 當埠狀態發生變化時,驅動通過syncd註冊的函式通知syncd
  • syncd呼叫通知函式傳送事件給 ASIC_DB
  • Orchagent 通過notification 執行緒監聽ASIC_DB事件,執行埠狀態變化函式:1)產生APPL_DB更新訊息,通知上層應用;2)呼叫sairedis API以提醒syncd需要更新與所選埠的主機介面關聯的核心狀態,orchagent再次通過常用的ASIC_DB介面將此請求傳遞給syncd
  • Syncd使用SAI API+ASIC SDK將受影響主機介面狀態(關閉)更新;
  • portsyncd接收到與上一步相關的netlink訊息,由於所有SONiC元件現在都完全知道埠關閉事件,因此該訊息將被悄悄丟棄。

路由狀態互動

在本節中,我們將迭代SONiC中發生的一系列步驟,以處理從eBGP對等方接收到的新路由。我們將假設此會話已經建立,並且我們正在學習一種新的路由,該路由使用直接連線的對等方作為其下一跳。

(0)在BGP容器初始化期間,zebra通過常規TCP套接字連線到fpmsyncd。在穩定/非瞬態條件下,zebra、linux核心、APPL_DB和ASIC_DB中儲存的路由表應完全一致/等效。
(1) 一個新的TCP包到達核心空間中bgp的套接字。核心的網路堆疊最終將相關的有效負載交付給bgpd程序。
(2) Bgpd解析新資料包,處理bgp更新,並通知zebra此新字首及其關聯協議下一跳的存在。
(3) zebra確定此字首的可行性/可達性(例如,現有轉發nh)後,zebra生成路由netlink訊息,將此新狀態注入核心。
(4) Zebra利用FPM介面將此netlink路由訊息傳遞給fpmsyncd。
(5) Fpmsyncd處理netlink訊息並將此狀態推送到APPL_DB。
(6) 作為APPL_DB消費者,它將接收先前推送到APPL_DB的資訊內容。
(7) 在處理接收到的資訊後,orchagentd將呼叫sairedis API將路由資訊注入ASIC_DB。
(8) 當與ASIC_DB消費者同步時,它將接收orchagentd生成的新狀態。
(9) Syncd將處理該資訊並呼叫SAI API將該狀態注入相應的asic驅動程式。
(10) 新的路線最終被推到了硬體上。

LLDP狀態互動

  • (0)lldpmgrd程序啟動時訂閱STATE_DB資料中物理口的狀態,週期性獲取介面最新狀態。基於這些資訊,lldpd(及其網路對等方)將隨時瞭解系統埠狀態的更改以及影響其操作的任何配置更改
  • (1)核心收到lldp報文後會分發給lldp程序去處理
  • (2)lldp解析LLDP報文獲取最新狀態,lldp_syncd週期性執行lldpctl cli獲取該最新狀態
  • (3)lldp_syncd把最新的狀態寫入APPL_DB資料庫中的LLDP_ENTRY_TABLE表
  • (4)其他監聽這張表的app就能獲取lldp的最新狀態了

syncd 元件

syncd 通過註冊回撥函式與driver通訊,syncd和sai共享名稱空間。syncd 啟動執行緒監聽共享佇列,處理driver的通知。

syncd 程序是介於orchagent與driver之間的程序。syncd從asic-db中讀取的資料經轉換後呼叫驅動提供的sai介面下發到硬體,同時需要將驅動的應答進行一定的處理,還需要處理驅動的事件通知(比如埠up/down,mac老化等資訊)。處理的訊息如下圖所示:

@startuml

Orchagent -> asic_db : 步驟1
asic_db -> syncd : 步驟2
syncd -> driver : 步驟3
driver -> syncd : 步驟4
syncd -> asic_db : 步驟5
asic_db -> Orchagent : 步驟6
driver -> syncd : 步驟7
syncd -> asic_db : 步驟8
asic_db -> Orchagent : 步驟9
@enduml

orchagent 寫操作

create,remove,set寫操作:非同步寫。orchagent會在 sairedis 層構建一個虛擬的sai層:sairedis。orchagent執行sai介面只是對asic-db進行操作,生成或者刪除虛擬物件(vid)。預設所有操作都是成功的,直接返回,不等待syncd的應答。執行上圖的1和6。syncd從asic-db中讀出請求執行上圖的2,3,4。如果4步驟返回成功,則整個請求執行結束,否則,syncd將會發送shutdown通知給orchagent。orchagent會退出,如上圖的5,6;

orchagent 讀操作

get 讀操作:同步讀。orchagent執行1後會使用select阻塞等待syncd的應答,如果syncd在60分鐘內沒有應答,那麼orchagent會產生segment退出。get操作執行順序為1->2->3->4->5->6。

// sonic-sairedis/lib/src/sai_redis_generic_get.cpp:257:sai_status_t redis_generic_get(
sai_status_t redis_generic_get() {
// ...
// internal_redis_generic_get
    std::string str_object_type = sai_serialize_object_type(object_type);
    std::string key = str_object_type + ":" + serialized_object_id;

    // 寫入本次get事件,不會寫輸入到asic資料庫,只是加入到佇列中
    g_asicState->set(key, entry, "get");

    // 建立臨時 select,新增事件,等待響應
    swss::Select s;
    s.addSelectable(g_redisGetConsumer.get());
    //迴圈等待syncd的應答
    while (true)
    {
        swss::Selectable *sel;
        //阻塞等待,時間為GET_RESPONSE_TIMEOUT  
        int result = s.select(&sel, GET_RESPONSE_TIMEOUT);
        //只處理應答情況OBJECT
        if (result == swss::Select::OBJECT) {
            swss::KeyOpFieldsValuesTuple kco;
            g_redisGetConsumer->pop(kco);

            const std::string &op = kfvOp(kco);
            const std::string &opkey = kfvKey(kco);


            if (op != "getresponse") // ignore non response messages
            {
                continue;
            }

            sai_status_t status = internal_redis_get_process(
                    object_type,
                    attr_count,
                    attr_list,
                    kco);
           
            return status;
        }
        break;
    }
    //超時和異常都返回SAI_STATUS_FAILURE
    return SAI_STATUS_FAILURE;
}

對於get操作,當syncd比較忙的時候,極端情況下會導致orchagent異常退出。

獲得driver通知

driver的notify: 驅動檢測到硬體事件後,呼叫syncd註冊的回撥函式通知syncd,將相關事件寫入佇列。syncd中有一個專門處理driver-notify的執行緒ntf-thread。ntf-thread解析driver的notify,然後通過asic-db通知orchagent (orchagent會在主程序的select中監聽asic-db。)。執行順序7->8->9。

注: orchagent 與syncd關於sai這一層非常相似。它們會呼叫大量的同名函式。這些函式只是名字相同,orchagent呼叫的是sai-redis庫中的函式,而syncd呼叫的是driver提供的sai庫

  • syncd向驅動註冊回撥函式,syncd定義了幾個notify全域性函式指標
sai_switch_state_change_notification_fn     on_switch_state_change_ntf = on_switch_state_change;
sai_switch_shutdown_request_notification_fn on_switch_shutdown_request_ntf = on_switch_shutdown_request;
sai_fdb_event_notification_fn               on_fdb_event_ntf = on_fdb_event;
sai_port_state_change_notification_fn       on_port_state_change_ntf = on_port_state_change;
sai_packet_event_notification_fn            on_packet_event_ntf = on_packet_event;
sai_queue_pfc_deadlock_notification_fn      on_queue_deadlock_ntf = on_queue_deadlock;

syncd和sai共享名稱空間,所以驅動直接使用這些函式指標即可呼叫對應的函式,在初始化的時候將這些全域性函式指標通過驅動提供的sai_set_switch_attribute函式設定到sai層。

/*
* Routine Description:
*    Set switch attribute value
*
* Arguments:
 *   [in] switch_id Switch id
*    [in] attr - switch attribute
*
* Return Values:
*    SAI_STATUS_SUCCESS on success
*    Failure status code on error
*/
sai_status_t sai_set_switch_attribute(_In_ sai_object_id_t switch_id,
                                      _In_ const sai_attribute_t *attr) {

  sai_status_t status = SAI_STATUS_SUCCESS;
  switch_status_t switch_status = SWITCH_STATUS_SUCCESS;
  switch_uint64_t flags = 0;
  switch_api_device_info_t api_device_info;
  sai_packet_action_t sai_packet_action;
  switch_acl_action_t switch_packet_action;
  switch_packet_type_t switch_packet_type = SWITCH_PACKET_TYPE_UNICAST;
  bool cut_through = false;

  if (!attr) {
    status = SAI_STATUS_INVALID_PARAMETER;
    SAI_LOG_ERROR("null attribute: %s", sai_status_to_string(status));
    return status;
  }

  memset(&api_device_info, 0x0, sizeof(api_device_info));
  if (status != SAI_STATUS_SUCCESS) {
    return status;
  }
  if (attr->id <= SAI_SWITCH_ATTR_ACL_STAGE_EGRESS) {  // Unsupported
    SAI_LOG_DEBUG("Switch attribute set: %s", switch_attr_name[attr->id]);
  }

  switch (attr->id) {
    //......
        
    case SAI_SWITCH_ATTR_FDB_EVENT_NOTIFY:
      sai_switch_notifications.on_fdb_event = attr->value.ptr;
      break;
    case SAI_SWITCH_ATTR_PORT_STATE_CHANGE_NOTIFY:
      sai_switch_notifications.on_port_state_change = attr->value.ptr;
      break;
    case SAI_SWITCH_ATTR_PACKET_EVENT_NOTIFY:
      sai_switch_notifications.on_packet_event = attr->value.ptr;
      break;
    case SAI_SWITCH_ATTR_SWITCH_STATE_CHANGE_NOTIFY:
      sai_switch_notifications.on_switch_state_change = attr->value.ptr;
      break;
    case SAI_SWITCH_ATTR_SHUTDOWN_REQUEST_NOTIFY:
      sai_switch_notifications.on_switch_shutdown_request = attr->value.ptr;
      break;
    ......

    default:
      SAI_LOG_ERROR("Unsupported Switch attribute: %d", attr->id);
      // Unsupported: Temporary hack till all attrs are supported
      switch_status = SWITCH_STATUS_SUCCESS;
  }
  //......
}

sai介面初始化的時候會向驅動註冊回撥函式,回撥函式中會呼叫我們註冊的全域性函式指標,我們以fdb為例進行說明:

sai_status_t sai_fdb_initialize(sai_api_service_t *sai_api_service) {
  sai_api_service->fdb_api = fdb_api;
  switch_uint16_t mac_event_flags = 0;
  mac_event_flags |= SWITCH_MAC_EVENT_LEARN | SWITCH_MAC_EVENT_AGE |
                     SWITCH_MAC_EVENT_MOVE | SWITCH_MAC_EVENT_DELETE;
  //初始化fdb的sai介面的時候,向驅動註冊了 sai_mac_notify_cb 回撥函式。
  switch_api_mac_notification_register(
      device, SWITCH_SAI_APP_ID, mac_event_flags, &sai_mac_notify_cb);
  switch_api_mac_table_set_learning_timeout(device, SAI_L2_LEARN_TIMEOUT);
  return SAI_STATUS_SUCCESS;
}

static void sai_mac_notify_cb(const switch_device_t device,
                              const uint16_t num_entries,
                              const switch_api_mac_entry_t *mac_entry,
                              const switch_mac_event_t mac_event,
                              void *app_data) {
  SAI_LOG_ENTER();
  sai_fdb_event_notification_data_t fdb_event[num_entries];
  sai_attribute_t attr_lists[num_entries][2];
  uint16_t entry = 0;

  for (entry = 0; entry < num_entries; entry++) {
    memset(&fdb_event[entry], 0, sizeof(fdb_event[entry]));
    fdb_event[entry].event_type = switch_mac_event_to_sai_fdb_event(mac_event);
    memcpy(fdb_event[entry].fdb_entry.mac_address,
           mac_entry[entry].mac.mac_addr,
           ETH_ALEN);
    fdb_event[entry].fdb_entry.switch_id =
        (((unsigned long)SWITCH_HANDLE_TYPE_DEVICE)
         << SWITCH_HANDLE_TYPE_SHIFT) |
        0x1;
    fdb_event[entry].fdb_entry.bv_id = mac_entry[entry].network_handle;

    memset(attr_lists[entry], 0, sizeof(attr_lists[entry]));
    attr_lists[entry][0].id = SAI_FDB_ENTRY_ATTR_TYPE;
    attr_lists[entry][0].value.s32 = SAI_FDB_ENTRY_TYPE_DYNAMIC;
    attr_lists[entry][1].id = SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID;
    attr_lists[entry][1].value.oid = mac_entry->handle;
    fdb_event[entry].attr_count = 2;
    if (fdb_event[entry].event_type == SAI_FDB_EVENT_FLUSHED) {
      // Overwriting now for SONiC to be able to process it correctly
      fdb_event[entry].event_type = SAI_FDB_EVENT_AGED;
    }
    fdb_event[entry].attr = attr_lists[entry];
  }
  //呼叫syncd的回撥函式
  sai_switch_notifications.on_fdb_event(num_entries, fdb_event);
  return;
}

syncd 啟動 notify 執行緒

std::shared_ptr<std::thread> ntf_process_thread;

void startNotificationsProcessingThread()
{
    runThread = true;
    ntf_process_thread = std::make_shared<std::thread>(ntf_process_function);
}

void ntf_process_function()
{
    while (runThread) {
        cv.wait(ulock);
        // this is notifications processing thread context, which is different
        // from SAI notifications context, we can safe use g_mutex here,
        // processing each notification is under same mutex as processing main
        // events, counters and reinit
        swss::KeyOpFieldsValuesTuple item;
        while (tryDequeue(item))//從佇列中取出notify
        {
            processNotification(item);//處理notify
        }
    }
}

bool tryDequeue(
        _Out_ swss::KeyOpFieldsValuesTuple &item)
{
    std::lock_guard<std::mutex> lock(queue_mutex);
    if (ntf_queue.empty()) {
        return false;
    }
    item = ntf_queue.front();
    ntf_queue.pop();
    return true;
}

void processNotification (
        _In_ const swss::KeyOpFieldsValuesTuple &item)
{
    std::lock_guard<std::mutex> lock(g_mutex);

    SWSS_LOG_ENTER();

    std::string notification = kfvKey(item);
    std::string data = kfvOp(item);

    if (notification == "switch_state_change") {
        handle_switch_state_change(data);
    } else if (notification == "fdb_event") {
        handle_fdb_event(data);
    }
    else if (notification == "port_state_change")
    {
        handle_port_state_change(data);
    }
    else if (notification == "switch_shutdown_request")
    {
        handle_switch_shutdown_request(data);
    }
    else if (notification == "queue_deadlock")
    {
        handle_queue_deadlock(data);
    }
    else
    {
        SWSS_LOG_ERROR("unknow notification: %s", notification.c_str());
    }
}

// 以 fdb 為例
void handle_fdb_event(
        _In_ const std::string &data)
{
    SWSS_LOG_ENTER();
    uint32_t count;
    sai_fdb_event_notification_data_t *fdbevent = NULL;
    sai_deserialize_fdb_event_ntf(data, count, &fdbevent);
    process_on_fdb_event(count, fdbevent);
    sai_deserialize_free_fdb_event_ntf(count, fdbevent);
}

void process_on_fdb_event(
        _In_ uint32_t count,
        _In_ sai_fdb_event_notification_data_t *data)
{
    for (uint32_t i = 0; i < count; i++) {
        sai_fdb_event_notification_data_t *fdb = &data[i];
        fdb->fdb_entry.switch_id = translate_rid_to_vid(fdb->fdb_entry.switch_id, SAI_NULL_OBJECT_ID);
        fdb->fdb_entry.bv_id = translate_rid_to_vid(fdb->fdb_entry.bv_id, fdb->fdb_entry.switch_id);
        translate_rid_to_vid_list(SAI_OBJECT_TYPE_FDB_ENTRY, fdb->fdb_entry.switch_id, fdb->attr_count, fdb->attr);

        /*
         * Currently because of bcrm bug, we need to install fdb entries in
         * asic view and currently this event don't have fdb type which is
         * required on creation.
         */
        redisPutFdbEntryToAsicView(fdb);
    }

    std::string s = sai_serialize_fdb_event_ntf(count, data);
    send_notification("fdb_event", s);
}

void send_notification(
        _In_ std::string op,
        _In_ std::string data,
        _In_ std::vector<swss::FieldValueTuple> &entry)
{
    //寫入資料庫
    notifications->send(op, data, entry);
}

void send_notification(
        _In_ std::string op,
        _In_ std::string data)
{
    SWSS_LOG_ENTER();

    std::vector<swss::FieldValueTuple> entry;

    send_notification(op, data, entry);
}

syncd與SDE互動

在SONiC中,syncd容器是與ASIC通訊的唯一通道,控制面的業務程序想將資料下發ASIC時,最終處理流程都是將資料按照SAI標準介面格式寫入Redis中的ASIC_DB,syncd容器中的同名主程序syncd訂閱ASIC_DB中的相關表項並處理這些下發的資料,呼叫ASIC SDK對SAI API的實現,並通過ASIC driver下發到ASIC。

其中SDE就包括了ASIC SDK以及執行在Tofino ASIC上的tofino.bin(由P4原始碼編譯生成的二進位制程式)。

mgmt-framework

https://github.com/Azure/sonic-mgmt-framework
https://github.com/Azure/SONiC/blob/master/doc/mgmt/Management Framework.md

mgmt-framework 負責提供各種通用北向介面(North Bound Interfaces , NBI),用於管理SONiC交換機的配置和狀態
管理框架利用golang編寫的翻譯庫(Translib)將向管理客戶機公開的資料模型轉換為Redis ABNF模式格式。

配置

登入裝置後,SONiC軟體可以通過三種方式進行配置:

  • Command Line Interface (CLI)
  • config_db.json
  • minigraph.xml

測試框架

Sonic測試研究(一):測試架構介紹

FAQ

SONiC 未來的發力方向?

Chassis、Kubernetes、AI agent、Test Infrastructure

SONiC 目前的商用情況?

‍‍到2020年為止,SONiC現在‍‍已經被超過10個的雲運營商‍‍以及大型企業所採納。以‍‍微軟的Azure network為例,現在‍‍已經做到了新裝機完全採取SONiC。SONiC歷經多年,被很多運營商‍‍採用,現在SONiC的技術非常成熟和穩定了,截止到2020年初,‍‍據微軟不完全統計,SONiC的裝機容量已經接近400萬個埠。Criteo (該公司目前的核心業務是重定向廣告)所有新增裝機都採用 SONiC。

SONiC是開源的,這樣‍‍能夠獲得全球性的支援和全球性的供應鏈,其次,由於現在SONiC已經有了很多‍‍供應商和‍‍企業使用者,也就表明SONiC獲得了業界的共同承認,SONiC已經是一個穩定成熟的解決方案,對於降低組網‍‍和管理的複雜度‍‍非常有效。

SONiC有統一的命令列入口麼?

SONiC有命令列,但伴隨模組的功能分散存在,沒有統一的入口,並且有些模組的命令列缺少互動介面。命令列庫見:https://github.com/Azure/sonic-utilities/

SONiC支援API介面麼?

SONiC有一個原生RESTAPI框架,基於Golang實現,但功能僅有VLAN、VLAN interface、VXLAN tunnel、路由,功能上側重業務。

SONiC是全開源的麼?

SONiC不是全開源的,交換晶片適配部分,僅提供二進位制格式。這主要受限於晶片廠商的License保護,微軟也不能直接開源。

元件為什麼需要容器化?

容器化可以遮蔽不同元件之間的依賴衝突,版本限制,實現不影響的故障、恢復、升級。

Switchdev 與SONiC 區別?

1)感覺其思路不一樣: SONiC 把 交換機當交換機,switchdev 是把交換機當網絡卡(如1822),利用開放的Linux網路工具管理交換機;
2)SONiC 包括SAI介面、較重的容器化管理框架等,裝置驅動由廠商SDK提供。 switchdev 框架包括Linux核心(統一定義的介面)、驅動和應用等方面,大部分由廠家實現在核心中,對於使用者較輕量,使用起來像使用網絡卡一樣。

哪些公司在白盒領域?

  • 銳捷:白盒交換機的開發取決於裝置廠商的3個關鍵架構(可靠性、可擴充套件性和開放性)選擇和2個關鍵能力(晶片/SDK BUG修復能力和網路軟體功能支援能力)。而銳捷網路在資料通訊領域具有二十年軟硬體自主研發能力,恰好匹配了當前的這些能力訴求。銳捷網路以主動擁抱變化的態度參與白盒交換機的標準制定和商用落地,已經成為了SONiC生態的主要合作伙伴之一。

銳捷網路在白盒交換機產品設計方面,CPU採用標準的x86架構,配合博通資料中心專用ASIC構建了開放化白盒交換機的基礎,同時支援ONIE安裝環境、提供支援SAI的BSP+SDK包,並提供基於SONiC的軟體開發、諮詢服務。同時,銳捷網路基於多年商用交換機的開發及規模商用經驗,積累了完整的軟、硬體測試案例及全自動化測試套件、測試方法以及專業的測試人員,可以提供專業的硬體、軟體定製化服務,同時為白盒交換機的品質提供了強有力的支撐和保障。

nfvschool 微信公共號: nfvschool
nfvschool網址: nfvschool.cn

參考