P4學習筆記(二)一個簡單P4交換機實現

P4學習筆記(一)初始P4

P4學習筆記(二)一個簡單P4交換機實現


本節主要講訴利用P4實現一個最簡單的交換機。首先會講一下交換機的架構,然後給出具體的P4代碼實現。

1、 架構模型

簡單P4交換機(VSS:very simple switch)它只是一個教學示例,說明了可編程交換機如何利用P4實現和代碼編寫。VSS具有許多固定功能塊(在我們的示例中以淺藍色顯示),具體功能在下一小節描述。 白色塊爲P4代碼實現模塊(parse、match-action pipeline、 deparse)。
在這裏插入圖片描述
VSS通過8個輸入以太網端口之一,通過再循環通道或從直接連接到CPU的端口接收數據包。 VSS有一個單一的Parser,解析後輸出到match-action pipline,pipline處理後輸出到Deparse模塊,經過Deparser後,數據包通過8個輸出以太網端口之一或3個“特殊”端口之一發出:

  • 發送到“ CPU端口”的數據包將發送到控制平面
  • 發送到“丟棄端口”的數據包將被丟棄
  • 發送到“再循環端口”的數據包通過特殊的輸入端口重新注入到交換機中

圖中的白色塊是可編程的,用戶必須提供相應的P4程序來指定每個此類塊的行爲。紅色箭頭指示用戶定義的數據流。淺藍色塊是固定功能組件。綠色箭頭是數據平面接口,用於在固定功能塊和可編程塊之間傳遞信息,這些功能在P4程序中作爲內部元數據(譯者注:類似於全局變量的概念)。

2、預定義模塊詳細描述

P4僅能實現部分模塊功能,部分功能還是需要預定義模塊來實現。

2.1 Arbiter 模塊

  • 從物理輸入以太網端口之一,控制平面或輸入再循環端口接收數據包。
  • 對於從以太網端口接收的數據包,該模塊計算以太網尾部校驗和並進行驗證。 如果校驗和不匹配,則丟棄數據包。 如果校驗和確實匹配,它已從數據包payload中刪除。
  • 如果有多個數據包,則接收數據包涉及運行仲裁算法。(譯者注:沒有太明白啥意思)
  • 如果Arbiter 模塊正在忙於處理先前的數據包,並且沒有隊列空間可用,則輸入端口可能會丟棄到達的數據包,且不會提示。
  • 接收到數據包後,Arbiter 塊將inCtrl、inputPort值設置爲match-action pipline輸入,該值是數據包起源的輸入端口的標識。 物理以太網端口的編號爲0到7,而輸入再循環端口的編號爲13,CPU端口的編號爲14。

2.2 Parser runtime 模塊

Parser runtime模塊與Parser協同工作。 它基於Parser的結果match-action pipline提供錯誤代碼,並且向demux 模塊提供關於數據包payload的信息(例如,剩餘payload數據的大小)。 parser完成數據包的處理後,將使用關聯的元數據作爲輸入(數據包頭和用戶定義的元數據)調用match-action pipline。

2.3 Demux 模塊

Demux模塊的核心功能是從deparser接收輸出數據包的包頭,從parser接收數據包的payload,將它們組合成新的數據包,然後將結果發送到正確的輸出端口。 輸出端口由outCtrl.ouputPort的值指定,該值由match-action pipline設置。

  • 丟棄報文:將數據包發送到丟棄端口會讓數據包消失。
  • 轉發報文:將數據包發送到編號在0到7之間的輸出以太網端口會導致它在相應的物理接口上發出。 如果輸出接口已經在忙於發出另一個數據包,則可以將該數據包放入隊列中。 發出數據包時,物理接口會計算正確的以太網校驗和尾部並將其附加到數據包中。
  • 上升到CPU:將數據包發送到輸出CPU端口會使數據包傳輸到控制平面。 在這種情況下,發送到CPU的數據包是原始輸入數據包,而不是從Parser接收的數據包,從deparser接受後的包會被丟棄。
  • 循環處理包:將數據包發送到輸出再循環端口會使它出現在輸入再循環端口。 當無法單次完成數據包處理時,循環很有用。
  • 非法包: 如果outputPort的值非法(例如9),則數據包將被丟棄。
  • 業務繁忙丟包:如果Demux 模塊正忙於處理先前的數據包,並且沒有能力將來自deparser的數據包排隊,則解複用器可能會丟棄該數據包,而與指示的輸出端口無關

3、代碼聲明文件

(譯者注:文中的語法是基於P4_16的)下面一段代碼是VSS的定義文件,定義了VSS所要用到的結構體以及函數聲明。

// File "very_simple_switch_model.p4"
// Very Simple Switch P4 declaration
// core library needed for packet_in and packet_out definitions
# include <core.p4>

/* Various constants and structure declarations */
/* ports are represented using 4-bit values */
typedef bit<4> PortId;

/* only 8 ports are "real" */
const PortId REAL_PORT_COUNT = 4w8; // 4w8 is the number 8 in 4 bits

/* metadata accompanying an input packet */
struct InControl {
	PortId inputPort;
}

/* special input port values */
const PortId RECIRCULATE_IN_PORT = 0xD;
const PortId CPU_IN_PORT = 0xE;

/* metadata that must be computed for outgoing packets */
struct OutControl {
	PortId outputPort;
}

/* special output port values for outgoing packet */
const PortId DROP_PORT = 0xF;
const PortId CPU_OUT_PORT = 0xE;
const PortId RECIRCULATE_OUT_PORT = 0xD;

/* Prototypes for all programmable blocks */
/**
* Programmable parser.
* @param <H> type of headers; defined by user
* @param b input packet
* @param parsedHeaders headers constructed by parser
*/
parser Parser<H>(packet_in b,
				out H parsedHeaders);

/**
* Match-action pipeline
* @param <H> type of input and output headers
* @param headers headers received from the parser and sent to the deparser
* @param parseError error that may have surfaced during parsing
* @param inCtrl information from architecture, accompanying input packet
* @param outCtrl information for architecture, accompanying output packet
*/
control Pipe<H>(inout H headers,
				in error parseError,// parser error
				in InControl inCtrl,// input port
				out OutControl outCtrl); // output port

/**
* VSS deparser.
* @param <H> type of headers; defined by user
* @param b output packet
* @param outputHeaders headers for output packet
*/
control Deparser<H>(inout H outputHeaders,
					packet_out b);
					
/**
* Top-level package declaration - must be instantiated by user.
* The arguments to the package indicate blocks that
* must be instantiated by the user.
* @param <H> user-defined type of the headers processed.
*/
package VSS<H>(Parser<H> p,
				Pipe<H> map,
				Deparser<H> d);
				
// Architecture-specific objects that can be instantiated
// Checksum unit
extern Checksum16 {
	Checksum16(); // constructor
	void clear(); // prepare unit for computation
	void update<T>(in T data); // add data to checksum
	void remove<T>(in T data); // remove data from existing checksum
	bit<16> get(); // get the checksum for the data added since last clear
}
  • 引入的 core.p4 是P4語言的內置庫,定義了標準數據類型和錯誤類型

  • bit<4> 是具有4位的位字符串的類型。

  • 語法4w0xF表示使用4位表示值15。 另一種表示法是4w15。在許多情況下,可以省略width修飾符,只寫15。

  • error是用於保存錯誤代碼的內置P4類型

  • Parser 聲明

    parser Parser<H>(packet_in b, out H parsedHeaders);
    

    該聲明描述瞭解析器的接口,但尚未描述其實現,該接口將由程序員負責實現。 解析器從packet_in讀取其輸入,packet_in是在core.p4庫中聲明的P4 extern對象。 解析器將其輸出(關鍵字out)寫入parsedHeaders參數。 此參數的類型爲H,但是具體類型不知道,需要程序員提供。

  • Match-Action pipeline 聲明

    control Pipe<H>(inout H headers,
    			in error parseError,// parser error
    			in InControl inCtrl,// input port
    			out OutControl outCtrl); // output port
    

    該聲明需要輸入3個參數: 1、解析後的數據包頭,2、解析器錯誤parseError 3、inCtrl控制數據。上圖指出了這些信息的不同來源。 pipeline將其輸出寫入outCtrl,並且它必須在適當位置更新要由Deparser使用的包頭。

  • Package 定義

    package VSS<H>
    

    類型變量H表示尚待用戶稍後提供的類型,但未知。 在這種情況下,H是用戶程序將要處理的包頭的類型。 Parser將生成這些包頭的解析表示,並且match-action管道將在適當位置更新輸入包頭以生成輸出包頭。

4、代碼實現文件

在這裏插入圖片描述
上圖展示了VSS交換機數據處理流程。下面用代碼來實現上圖中的邏輯。

// Include P4 core library
# include <core.p4>
// Include very simple switch architecture declarations
# include "very_simple_switch_model.p4"
// This program processes packets comprising an Ethernet and an IPv4
// header, and it forwards packets using the destination IP address
typedef bit<48> EthernetAddress;
typedef bit<32> IPv4Address;
// Standard Ethernet header
header Ethernet_h {
	EthernetAddress dstAddr;
	EthernetAddress srcAddr;
	bit<16> etherType;
}
// IPv4 header (without options)
header IPv4_h {
	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;
	IPv4Address srcAddr;
	IPv4Address dstAddr;
}
// Structure of parsed headers
struct Parsed_packet {
	Ethernet_h ethernet;
	IPv4_h ip;
}
// Parser section
// User-defined errors that may be signaled during parsing
error {
	IPv4OptionsNotSupported,
	IPv4IncorrectVersion,
	IPv4ChecksumError
}
parser TopParser(packet_in b, out Parsed_packet p) {
	Checksum16() ck; // instantiate checksum unit
	
	state start {
		b.extract(p.ethernet);
		transition select(p.ethernet.etherType) {
			0x0800: parse_ipv4;
			// no default rule: all other packets rejected
		}
	}
	
	state parse_ipv4 {
		b.extract(p.ip);
		verify(p.ip.version == 4w4, error.IPv4IncorrectVersion);
		verify(p.ip.ihl == 4w5, error.IPv4OptionsNotSupported);
		ck.clear();
		ck.update(p.ip);
		// Verify that packet checksum is zero
		verify(ck.get() == 16w0, error.IPv4ChecksumError);
		transition accept;
	}
}

// Match-action pipeline section
control TopPipe(inout Parsed_packet headers,
				in error parseError, // parser error
				in InControl inCtrl, // input port
				out OutControl outCtrl) {
	IPv4Address nextHop; // local variable
	
	/**
	* Indicates that a packet is dropped by setting the
	* output port to the DROP_PORT
	*/
	action Drop_action() {
		outCtrl.outputPort = DROP_PORT;
	}
	
	/**
	* Set the next hop and the output port.
	* Decrements ipv4 ttl field.
	* @param ivp4_dest ipv4 address of next hop
	* @param port output port
	*/
	action Set_nhop(IPv4Address ipv4_dest, PortId port) {
		nextHop = ipv4_dest;
		headers.ip.ttl = headers.ip.ttl - 1;
		outCtrl.outputPort = port;
	}
	
	/**
	* Computes address of next IPv4 hop and output port
	* based on the IPv4 destination of the current packet.
	* Decrements packet IPv4 TTL.
	* @param nextHop IPv4 address of next hop
	*/
	table ipv4_match {
		key = { headers.ip.dstAddr: lpm; } // longest-prefix match
		actions = {
			Drop_action;
			Set_nhop;
		}
		size = 1024;
		default_action = Drop_action;
	}
	
	/**
	* Send the packet to the CPU port
	*/
	action Send_to_cpu() {
		outCtrl.outputPort = CPU_OUT_PORT;
	}
	
	/**
	* Check packet TTL and send to CPU if expired.
	*/
	table check_ttl {
		key = { headers.ip.ttl: exact; }
		actions = { Send_to_cpu; NoAction; }
		const default_action = NoAction; // defined in core.p4
	}
	/**
	* Set the destination MAC address of the packet
	* @param dmac destination MAC address.
	*/
	action Set_dmac(EthernetAddress dmac) {
		headers.ethernet.dstAddr = dmac;
	}
	/**
	* Set the destination Ethernet address of the packet
	* based on the next hop IP address.
	* @param nextHop IPv4 address of next hop.
	*/
	table dmac {
		key = { nextHop: exact; }
		actions = {
			Drop_action;
			Set_dmac;
		}
		size = 1024;
		default_action = Drop_action;
	}
	/**
	* Set the source MAC address.
	* @param smac: source MAC address to use
	*/
	action Set_smac(EthernetAddress smac) {
		headers.ethernet.srcAddr = smac;
	}
	/**
	* Set the source mac address based on the output port.
	*/
	table smac {
		key = { outCtrl.outputPort: exact; }
		actions = {
			Drop_action;
			Set_smac;
		}
		size = 16;
		default_action = Drop_action;
	}
	
	apply {
			if (parseError != error.NoError) {
				Drop_action(); // invoke drop directly
				return;
			}
			
			ipv4_match.apply(); // Match result will go into nextHop
			if (outCtrl.outputPort == DROP_PORT) return;
			
			check_ttl.apply();
			if (outCtrl.outputPort == CPU_OUT_PORT) return;
			
			dmac.apply();
			if (outCtrl.outputPort == DROP_PORT) return;
			
			smac.apply();
	}
}
	
	// deparser section
control TopDeparser(inout Parsed_packet p, packet_out b) {
	Checksum16() ck;
	apply {
		b.emit(p.ethernet);
		if (p.ip.isValid()) {
			ck.clear(); // prepare checksum unit
			p.ip.hdrChecksum = 16w0; // clear checksum
			ck.update(p.ip); // compute new checksum.
			p.ip.hdrChecksum = ck.get();
		}
		b.emit(p.ip);
	}
}

// Instantiate the top-level VSS package
VSS(TopParser(),
	TopPipe(),
	TopDeparser()) main;
	
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章